@wordpress/components
Version:
UI components for WordPress.
256 lines (251 loc) • 8.94 kB
JavaScript
/**
* External dependencies
*/
import { Animated, PanResponder, StyleSheet, View } from 'react-native';
import Video from 'react-native-video';
/**
* WordPress dependencies
*/
import { requestFocalPointPickerTooltipShown, setFocalPointPickerTooltipShown } from '@wordpress/react-native-bridge';
import { __ } from '@wordpress/i18n';
import { useRef, useState, useMemo, useEffect } from '@wordpress/element';
import { usePreferredColorSchemeStyle } from '@wordpress/compose';
/**
* Internal dependencies
*/
import FocalPoint from './focal-point';
import Tooltip from './tooltip';
import styles from './style.scss';
import { isVideoType } from './utils';
import { clamp } from '../utils/math';
import Image from '../mobile/image';
import UnitControl from '../unit-control';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const MIN_POSITION_VALUE = 0;
const MAX_POSITION_VALUE = 100;
const FOCAL_POINT_UNITS = [{
default: 50,
label: '%',
value: '%'
}];
function FocalPointPicker(props) {
const {
focalPoint,
onChange,
shouldEnableBottomSheetScroll,
url
} = props;
const isVideo = isVideoType(url);
const [containerSize, setContainerSize] = useState(null);
const [sliderKey, setSliderKey] = useState(0);
const [displayPlaceholder, setDisplayPlaceholder] = useState(true);
const [videoNaturalSize, setVideoNaturalSize] = useState(null);
const [tooltipVisible, setTooltipVisible] = useState(false);
const locationPageOffsetX = useRef();
const locationPageOffsetY = useRef();
const videoRef = useRef(null);
useEffect(() => {
requestFocalPointPickerTooltipShown(tooltipShown => {
if (!tooltipShown) {
setTooltipVisible(true);
setFocalPointPickerTooltipShown(true);
}
});
}, []);
// Animated coordinates for drag handle.
const pan = useRef(new Animated.ValueXY()).current;
/**
* Set drag handle position anytime focal point coordinates change.
* E.g. initial render, dragging range sliders.
*/
useEffect(() => {
if (containerSize) {
pan.setValue({
x: focalPoint.x * containerSize.width,
y: focalPoint.y * containerSize.height
});
}
}, [focalPoint, containerSize, pan]);
// Pan responder to manage drag handle interactivity.
const panResponder = useMemo(() => PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => true,
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: event => {
shouldEnableBottomSheetScroll(false);
const {
locationX: x,
locationY: y,
pageX,
pageY
} = event.nativeEvent;
locationPageOffsetX.current = pageX - x;
locationPageOffsetY.current = pageY - y;
pan.setValue({
x,
y
}); // Set cursor to tap location.
pan.extractOffset(); // Set offset to current value.
},
// Move cursor to match delta drag.
onPanResponderMove: Animated.event([null, {
dx: pan.x,
dy: pan.y
}], {
useNativeDriver: false
}),
onPanResponderRelease: event => {
shouldEnableBottomSheetScroll(true);
pan.flattenOffset(); // Flatten offset into value.
const {
pageX,
pageY
} = event.nativeEvent;
// Ideally, x and y below are merely locationX and locationY from the
// nativeEvent. However, we are required to compute these relative
// coordinates to workaround a bug affecting Android's PanResponder.
// Specifically, dragging the handle outside the bounds of the image
// results in inaccurate locationX and locationY coordinates to be
// reported. https://github.com/facebook/react-native/issues/15290#issuecomment-435494944
const x = pageX - locationPageOffsetX.current;
const y = pageY - locationPageOffsetY.current;
onChange({
x: clamp(x / containerSize?.width, 0, 1).toFixed(2),
y: clamp(y / containerSize?.height, 0, 1).toFixed(2)
});
// Slider (child of RangeCell) is uncontrolled, so we must increment a
// key to re-mount and sync the pan gesture values to the sliders
// https://github.com/callstack/react-native-slider/tree/v3.0.3#value
setSliderKey(prevState => prevState + 1);
}
}), [containerSize, pan, onChange, shouldEnableBottomSheetScroll]);
const mediaBackground = usePreferredColorSchemeStyle(styles.mediaBackground, styles.mediaBackgroundDark);
const imagePreviewStyles = [displayPlaceholder && styles.mediaPlaceholder, styles.image];
const videoPreviewStyles = [{
aspectRatio: videoNaturalSize && videoNaturalSize.width / videoNaturalSize.height,
// Hide Video component since it has black background while loading the source
opacity: displayPlaceholder ? 0 : 1
}, styles.video, displayPlaceholder && styles.mediaPlaceholder];
const focalPointGroupStyles = [styles.focalPointGroup, {
transform: [{
translateX: pan.x.interpolate({
inputRange: [0, containerSize?.width || 0],
outputRange: [0, containerSize?.width || 0],
extrapolate: 'clamp'
})
}, {
translateY: pan.y.interpolate({
inputRange: [0, containerSize?.height || 0],
outputRange: [0, containerSize?.height || 0],
extrapolate: 'clamp'
})
}]
}];
const FOCAL_POINT_SIZE = 50;
const focalPointStyles = StyleSheet.flatten([styles.focalPoint, {
height: FOCAL_POINT_SIZE,
marginLeft: -(FOCAL_POINT_SIZE / 2),
marginTop: -(FOCAL_POINT_SIZE / 2),
width: FOCAL_POINT_SIZE
}]);
const onTooltipPress = () => setTooltipVisible(false);
const onMediaLayout = event => {
const {
height,
width
} = event.nativeEvent.layout;
if (width !== 0 && height !== 0 && (containerSize?.width !== width || containerSize?.height !== height)) {
setContainerSize({
width,
height
});
}
};
const onImageDataLoad = () => setDisplayPlaceholder(false);
const onVideoLoad = event => {
const {
height,
width
} = event.naturalSize;
setVideoNaturalSize({
height,
width
});
setDisplayPlaceholder(false);
// Avoid invisible, paused video on Android, presumably related to
// https://github.com/react-native-video/react-native-video/issues/1979
videoRef?.current.seek(0);
};
const onXCoordinateChange = x => onChange({
x: (x / 100).toFixed(2)
});
const onYCoordinateChange = y => onChange({
y: (y / 100).toFixed(2)
});
return /*#__PURE__*/_jsx(View, {
style: styles.container,
children: /*#__PURE__*/_jsxs(Tooltip, {
onPress: onTooltipPress,
visible: tooltipVisible,
children: [/*#__PURE__*/_jsx(View, {
style: [styles.media, mediaBackground],
children: /*#__PURE__*/_jsxs(View, {
...panResponder.panHandlers,
onLayout: onMediaLayout,
style: styles.mediaContainer,
children: [!isVideo && /*#__PURE__*/_jsx(Image, {
editButton: false,
highlightSelected: false,
isSelected: !displayPlaceholder,
height: "100%",
url: url,
style: imagePreviewStyles,
onImageDataLoad: onImageDataLoad
}), isVideo && /*#__PURE__*/_jsx(Video, {
muted: true,
paused: true,
disableFocus: true,
onLoad: onVideoLoad,
ref: videoRef,
resizeMode: "contain",
source: {
uri: url
},
style: videoPreviewStyles
}), !displayPlaceholder && /*#__PURE__*/_jsxs(Animated.View, {
pointerEvents: "none",
style: focalPointGroupStyles,
children: [/*#__PURE__*/_jsx(Tooltip.Label, {
text: __('Drag to adjust focal point'),
yOffset: -(FOCAL_POINT_SIZE / 2)
}), /*#__PURE__*/_jsx(FocalPoint, {
height: focalPointStyles.height,
style: focalPointStyles,
testID: "focal-point-picker-handle",
width: focalPointStyles.width
})]
})]
})
}), /*#__PURE__*/_jsx(UnitControl, {
label: __('X-Axis Position'),
max: MAX_POSITION_VALUE,
min: MIN_POSITION_VALUE,
onChange: onXCoordinateChange,
unit: "%",
units: FOCAL_POINT_UNITS,
value: Math.round(focalPoint.x * 100)
}, `xAxis-${sliderKey}`), /*#__PURE__*/_jsx(UnitControl, {
label: __('Y-Axis Position'),
max: MAX_POSITION_VALUE,
min: MIN_POSITION_VALUE,
onChange: onYCoordinateChange,
unit: "%",
units: FOCAL_POINT_UNITS,
value: Math.round(focalPoint.y * 100)
}, `yAxis-${sliderKey}`)]
})
});
}
export default FocalPointPicker;
//# sourceMappingURL=index.native.js.map