UNPKG

@wordpress/components

Version:
256 lines (251 loc) 8.94 kB
/** * 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