From 65cac317dc3e062ab07a0366c6fd80d2b9010242 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 25 Jul 2023 13:27:22 -0700 Subject: [PATCH 01/37] Rename dropZoneID prop to follow conventions --- src/components/DragAndDrop/DropZone/index.js | 4 ++-- src/components/DragAndDrop/dragAndDropPropTypes.js | 4 ++-- src/components/DragAndDrop/index.js | 4 ++-- src/pages/home/report/ReportActionCompose.js | 4 ++-- src/pages/home/report/ReportDropUI.js | 2 +- src/stories/DragAndDrop.stories.js | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/DragAndDrop/DropZone/index.js b/src/components/DragAndDrop/DropZone/index.js index 525e5a8c1484..a073009e22d5 100644 --- a/src/components/DragAndDrop/DropZone/index.js +++ b/src/components/DragAndDrop/DropZone/index.js @@ -12,7 +12,7 @@ const propTypes = { children: PropTypes.node.isRequired, /** Required for drag and drop to properly detect dropzone */ - dropZoneId: PropTypes.string.isRequired, + dropZoneID: PropTypes.string.isRequired, }; function DropZone(props) { @@ -21,7 +21,7 @@ function DropZone(props) { {props.children} {/* Necessary for blocking events on content which can publish unwanted dragleave even if we are inside dropzone */} diff --git a/src/components/DragAndDrop/dragAndDropPropTypes.js b/src/components/DragAndDrop/dragAndDropPropTypes.js index f0b56caeb3f7..b7e84e2994bf 100644 --- a/src/components/DragAndDrop/dragAndDropPropTypes.js +++ b/src/components/DragAndDrop/dragAndDropPropTypes.js @@ -17,8 +17,8 @@ export default { shouldAcceptDrop: PropTypes.func, /** Id of the element on which we want to detect drag */ - dropZoneId: PropTypes.string.isRequired, + dropZoneID: PropTypes.string.isRequired, /** Id of the element which is shown while drag is active */ - activeDropZoneId: PropTypes.string.isRequired, + activeDropZoneID: PropTypes.string.isRequired, }; diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js index 5c5d17a4a272..71fa18c733b5 100644 --- a/src/components/DragAndDrop/index.js +++ b/src/components/DragAndDrop/index.js @@ -83,7 +83,7 @@ class DragAndDrop extends React.Component { } addEventListeners() { - this.dropZone = document.getElementById(this.props.dropZoneId); + this.dropZone = document.getElementById(this.props.dropZoneID); this.dropZoneRect = this.calculateDropZoneClientReact(); document.addEventListener('dragover', this.dropZoneDragListener); document.addEventListener('dragenter', this.dropZoneDragListener); @@ -154,7 +154,7 @@ class DragAndDrop extends React.Component { event.clientX <= this.dropZoneRect.left || event.clientX >= this.dropZoneRect.right || // Cancel drag when file manager is on top of the drop zone area - works only on chromium - (event.target.getAttribute('id') === this.props.activeDropZoneId && !event.relatedTarget) + (event.target.getAttribute('id') === this.props.activeDropZoneID && !event.relatedTarget) ) { this.dropZoneDragState = 'dragleave'; this.props.onDragLeave(event); diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 8fc3eb1bb30d..12945125b6a5 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -1119,8 +1119,8 @@ class ReportActionCompose extends React.Component { { this.setState({isDraggingOver: true}); }} diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js index f59d0a31135d..6e0e6ef68ce4 100644 --- a/src/pages/home/report/ReportDropUI.js +++ b/src/pages/home/report/ReportDropUI.js @@ -16,7 +16,7 @@ function ReportDropUI(props) { return ( { setDraggingOver(true); }} @@ -75,7 +75,7 @@ function Default() { {draggingOver && ( )} From 9851ad1fb85a9e31570ad39c74b1ced4261aa9f1 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 25 Jul 2023 13:28:45 -0700 Subject: [PATCH 02/37] Add prop types on default components --- src/components/DragAndDrop/index.native.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/DragAndDrop/index.native.js b/src/components/DragAndDrop/index.native.js index 4c00e56130d4..2f2419b5f06e 100644 --- a/src/components/DragAndDrop/index.native.js +++ b/src/components/DragAndDrop/index.native.js @@ -1,5 +1,8 @@ +import DragAndDropPropTypes from './dragAndDropPropTypes'; + const DragAndDrop = (props) => props.children; +DragAndDrop.propTypes = DragAndDropPropTypes; DragAndDrop.displayName = 'DragAndDrop'; export default DragAndDrop; From f5f874312a2d6c21c8edd8ba38222e939ee80c1f Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 25 Jul 2023 13:32:23 -0700 Subject: [PATCH 03/37] Rename disabled prop to isDisabled --- .../DragAndDrop/dragAndDropPropTypes.js | 6 ++++++ src/components/DragAndDrop/index.js | 17 +++++------------ src/pages/home/report/ReportActionCompose.js | 2 +- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/components/DragAndDrop/dragAndDropPropTypes.js b/src/components/DragAndDrop/dragAndDropPropTypes.js index b7e84e2994bf..ccd6b7440e28 100644 --- a/src/components/DragAndDrop/dragAndDropPropTypes.js +++ b/src/components/DragAndDrop/dragAndDropPropTypes.js @@ -21,4 +21,10 @@ export default { /** Id of the element which is shown while drag is active */ activeDropZoneID: PropTypes.string.isRequired, + + /** Whether drag & drop should be disabled */ + isDisabled: PropTypes.bool, + + /** Rendered child component */ + children: PropTypes.node.isRequired, }; diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js index 71fa18c733b5..b89289d9d2c7 100644 --- a/src/components/DragAndDrop/index.js +++ b/src/components/DragAndDrop/index.js @@ -17,12 +17,6 @@ const propTypes = { /** Guard for accepting drops in drop zone. Drag event is passed to this function as first parameter. This prop is necessary to be inlined to satisfy the linter */ shouldAcceptDrop: PropTypes.func, - - /** Whether drag & drop should be disabled */ - disabled: PropTypes.bool, - - /** Rendered child component */ - children: PropTypes.node.isRequired, }; const defaultProps = { @@ -37,7 +31,7 @@ const defaultProps = { } return false; }, - disabled: false, + isDisabled: false, }; class DragAndDrop extends React.Component { @@ -57,18 +51,17 @@ class DragAndDrop extends React.Component { } componentDidMount() { - if (this.props.disabled) { + if (this.props.isDisabled) { return; } this.addEventListeners(); } componentDidUpdate(prevProps) { - const isDisabled = this.props.disabled; - if (this.props.isFocused === prevProps.isFocused && isDisabled === prevProps.disabled) { + if (this.props.isFocused === prevProps.isFocused && this.props.isDisabled === prevProps.isDisabled) { return; } - if (!this.props.isFocused || isDisabled) { + if (!this.props.isFocused || this.props.isDisabled) { this.removeEventListeners(); } else { this.addEventListeners(); @@ -76,7 +69,7 @@ class DragAndDrop extends React.Component { } componentWillUnmount() { - if (this.props.disabled) { + if (this.props.isDisabled) { return; } this.removeEventListeners(); diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 12945125b6a5..0c4601f90645 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -1140,7 +1140,7 @@ class ReportActionCompose extends React.Component { this.setState({isDraggingOver: false}); }} - disabled={this.props.disabled} + isDisabled={this.props.disabled} > this.checkComposerVisibility()} From faa216ba14c9777d2e806ef41ed618a829def0bf Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 25 Jul 2023 13:40:04 -0700 Subject: [PATCH 04/37] Create useEffectOnPageLoad hook --- src/hooks/useEffectOnPageLoad/index.js | 20 +++++++++++++++++++ src/hooks/useEffectOnPageLoad/index.native.js | 3 +++ 2 files changed, 23 insertions(+) create mode 100644 src/hooks/useEffectOnPageLoad/index.js create mode 100644 src/hooks/useEffectOnPageLoad/index.native.js diff --git a/src/hooks/useEffectOnPageLoad/index.js b/src/hooks/useEffectOnPageLoad/index.js new file mode 100644 index 000000000000..a7338fe55a6b --- /dev/null +++ b/src/hooks/useEffectOnPageLoad/index.js @@ -0,0 +1,20 @@ +import {useEffect, useRef} from 'react'; + +export default function useEffectOnPageLoad(onPageLoad, dependencies = []) { + const onPageLoadRef = useRef(onPageLoad); + onPageLoadRef.current = onPageLoad; + + useEffect(() => { + function onPageLoadCallback() { + onPageLoadRef.current(); + } + + if (document.readyState === 'complete') { + onPageLoadCallback(); + } else { + window.addEventListener('load', onPageLoadCallback); + return () => window.removeEventListener('load', onPageLoadCallback); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, dependencies); +} diff --git a/src/hooks/useEffectOnPageLoad/index.native.js b/src/hooks/useEffectOnPageLoad/index.native.js new file mode 100644 index 000000000000..0c55bf66d3e6 --- /dev/null +++ b/src/hooks/useEffectOnPageLoad/index.native.js @@ -0,0 +1,3 @@ +import {useEffect} from 'react'; + +export default useEffect; From 931043bd19e06ff557972250613ea11c477d88ec Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 25 Jul 2023 13:48:32 -0700 Subject: [PATCH 05/37] Convert DragAndDrop to functional component --- src/components/DragAndDrop/index.js | 282 ++++++++++++---------------- 1 file changed, 115 insertions(+), 167 deletions(-) diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js index b89289d9d2c7..12a2195aa0cf 100644 --- a/src/components/DragAndDrop/index.js +++ b/src/components/DragAndDrop/index.js @@ -1,190 +1,138 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import {useEffect, useRef, useCallback} from 'react'; import _ from 'underscore'; - -import variables from '../../styles/variables'; +import {useIsFocused} from '@react-navigation/native'; import DragAndDropPropTypes from './dragAndDropPropTypes'; -import withNavigationFocus from '../withNavigationFocus'; +import useEffectOnPageLoad from '../../hooks/useEffectOnPageLoad'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; const COPY_DROP_EFFECT = 'copy'; const NONE_DROP_EFFECT = 'none'; +const DRAG_ENTER_EVENT = 'dragenter'; +const DRAG_LEAVE_EVENT = 'dragleave'; +const DROP_EVENT = 'drop'; + +/** + * @param {Event} event – drag event + * @returns {Boolean} + */ +function shouldAcceptDrop(event) { + return _.some(event.dataTransfer.types, (type) => type === 'Files'); +} -const propTypes = { - ...DragAndDropPropTypes, - - /** Callback to fire when a file has being dragged over the text input & report body. This prop is necessary to be inlined to satisfy the linter */ - onDragOver: DragAndDropPropTypes.onDragOver, - - /** Guard for accepting drops in drop zone. Drag event is passed to this function as first parameter. This prop is necessary to be inlined to satisfy the linter */ - shouldAcceptDrop: PropTypes.func, -}; - -const defaultProps = { - onDragOver: () => {}, - shouldAcceptDrop: (e) => { - if (e.dataTransfer.types) { - for (let i = 0; i < e.dataTransfer.types.length; i++) { - if (e.dataTransfer.types[i] === 'Files') { - return true; - } - } - } - return false; - }, - isDisabled: false, -}; - -class DragAndDrop extends React.Component { - constructor(props) { - super(props); - - this.throttledDragOverHandler = _.throttle(this.dragOverHandler.bind(this), 100); - this.throttledDragNDropWindowResizeListener = _.throttle(this.dragNDropWindowResizeListener.bind(this), 100); - this.dropZoneDragHandler = this.dropZoneDragHandler.bind(this); - this.dropZoneDragListener = this.dropZoneDragListener.bind(this); - - /* - Last detected drag state on the dropzone -> we start with dragleave since user is not dragging initially. - This state is updated when drop zone is left/entered entirely(not taking the children in the account) or entire window is left - */ - this.dropZoneDragState = 'dragleave'; - } +function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZoneID, children, isDisabled = false}) { + const isFocused = useIsFocused(); + const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); - componentDidMount() { - if (this.props.isDisabled) { - return; - } - this.addEventListeners(); - } - - componentDidUpdate(prevProps) { - if (this.props.isFocused === prevProps.isFocused && this.props.isDisabled === prevProps.isDisabled) { - return; - } - if (!this.props.isFocused || this.props.isDisabled) { - this.removeEventListeners(); - } else { - this.addEventListeners(); - } - } + const dropZone = useRef(null); + const dropZoneRect = useRef(null); - componentWillUnmount() { - if (this.props.isDisabled) { - return; + useEffect(() => { + const dropZoneElement = document.getElementById(dropZoneID); + if (!dropZoneElement) { + throw new Error('Error: element specified by dropZoneID not found'); } - this.removeEventListeners(); - } - - addEventListeners() { - this.dropZone = document.getElementById(this.props.dropZoneID); - this.dropZoneRect = this.calculateDropZoneClientReact(); - document.addEventListener('dragover', this.dropZoneDragListener); - document.addEventListener('dragenter', this.dropZoneDragListener); - document.addEventListener('dragleave', this.dropZoneDragListener); - document.addEventListener('drop', this.dropZoneDragListener); - window.addEventListener('resize', this.throttledDragNDropWindowResizeListener); - } - - removeEventListeners() { - document.removeEventListener('dragover', this.dropZoneDragListener); - document.removeEventListener('dragenter', this.dropZoneDragListener); - document.removeEventListener('dragleave', this.dropZoneDragListener); - document.removeEventListener('drop', this.dropZoneDragListener); - window.removeEventListener('resize', this.throttledDragNDropWindowResizeListener); - } - - /** - * @param {Object} event native Event + dropZone.current = dropZoneElement; + }, [dropZoneID]); + + useEffectOnPageLoad( + () => + _.throttle(() => { + const boundingClientRect = dropZone.current.getBoundingClientRect(); + dropZoneRect.current = { + width: boundingClientRect.width, + left: isSmallScreenWidth ? 0 : boundingClientRect.left, + right: isSmallScreenWidth ? windowWidth : boundingClientRect.right, + top: boundingClientRect.top, + bottom: boundingClientRect.bottom, + }; + }), + [windowWidth, isSmallScreenWidth], + ); + + /* + * Last detected drag state on the dropzone -> we start with dragleave since user is not dragging initially. + * This state is updated when drop zone is left/entered entirely(not taking the children in the account) or entire window is left */ - dragOverHandler(event) { - this.props.onDragOver(event); - } - - dragNDropWindowResizeListener() { - // Update bounding client rect on window resize - this.dropZoneRect = this.calculateDropZoneClientReact(); - } - - calculateDropZoneClientReact() { - const boundingClientRect = this.dropZone.getBoundingClientRect(); + const dropZoneDragState = useRef(DRAG_LEAVE_EVENT); - // Handle edge case when we are under responsive breakpoint the browser doesn't normalize rect.left to 0 and rect.right to window.innerWidth - return { - width: boundingClientRect.width, - left: window.innerWidth <= variables.mobileResponsiveWidthBreakpoint ? 0 : boundingClientRect.left, - right: window.innerWidth <= variables.mobileResponsiveWidthBreakpoint ? window.innerWidth : boundingClientRect.right, - top: boundingClientRect.top, - bottom: boundingClientRect.bottom, - }; - } - - /** - * @param {Object} event native Event - */ - dropZoneDragHandler(event) { - // Setting dropEffect for dragover is required for '+' icon on certain platforms/browsers (eg. Safari) - switch (event.type) { - case 'dragover': - // Continuous event -> can hurt performance, be careful when subscribing - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = COPY_DROP_EFFECT; - this.throttledDragOverHandler(event); - break; - case 'dragenter': - // Avoid reporting onDragEnter for children views -> not performant - if (this.dropZoneDragState === 'dragleave') { - this.dropZoneDragState = 'dragenter'; - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = COPY_DROP_EFFECT; - this.props.onDragEnter(event); - } - break; - case 'dragleave': - if (this.dropZoneDragState === 'dragenter') { - if ( - event.clientY <= this.dropZoneRect.top || - event.clientY >= this.dropZoneRect.bottom || - event.clientX <= this.dropZoneRect.left || - event.clientX >= this.dropZoneRect.right || - // Cancel drag when file manager is on top of the drop zone area - works only on chromium - (event.target.getAttribute('id') === this.props.activeDropZoneID && !event.relatedTarget) - ) { - this.dropZoneDragState = 'dragleave'; - this.props.onDragLeave(event); - } - } - break; - case 'drop': - this.dropZoneDragState = 'dragleave'; - this.props.onDrop(event); - break; - default: - break; + // If this component is out of focus or disabled, reset the drag state back to the default + useEffect(() => { + if (isFocused && !isDisabled) { + return; } - } + dropZoneDragState.current = DRAG_LEAVE_EVENT; + }, [isFocused, isDisabled]); /** * Handles all types of drag-N-drop events on the drop zone associated with composer * * @param {Object} event native Event */ - dropZoneDragListener(event) { - event.preventDefault(); + const dropZoneDragHandler = useCallback( + (event) => { + if (!isFocused || isDisabled) { + return; + } - if (this.dropZone.contains(event.target) && this.props.shouldAcceptDrop(event)) { - this.dropZoneDragHandler(event); - } else { - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = NONE_DROP_EFFECT; - } - } + event.preventDefault(); + + if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) { + switch (event.type) { + case DRAG_ENTER_EVENT: + // Avoid reporting onDragEnter for children views -> not performant + if (dropZoneDragState.current === DRAG_LEAVE_EVENT) { + dropZoneDragState.current = DRAG_ENTER_EVENT; + // eslint-disable-next-line no-param-reassign + event.dataTransfer.dropEffect = COPY_DROP_EFFECT; + onDragEnter(event); + } + break; + case DRAG_LEAVE_EVENT: + if (dropZoneDragState.current === DRAG_ENTER_EVENT) { + if ( + event.clientY <= dropZoneRect.current.top || + event.clientY >= dropZoneRect.current.bottom || + event.clientX <= dropZoneRect.current.left || + event.clientX >= dropZoneRect.current.right || + // Cancel drag when file manager is on top of the drop zone area - works only on chromium + (event.target.getAttribute('id') === activeDropZoneID && !event.relatedTarget) + ) { + dropZoneDragState.current = DRAG_LEAVE_EVENT; + onDragLeave(event); + } + } + break; + case DROP_EVENT: + dropZoneDragState.current = DRAG_LEAVE_EVENT; + onDrop(event); + break; + default: + break; + } + } else { + // eslint-disable-next-line no-param-reassign + event.dataTransfer.dropEffect = NONE_DROP_EFFECT; + } + }, + [isFocused, isDisabled, onDragEnter, onDragLeave, activeDropZoneID, onDrop], + ); + + useEffect(() => { + document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + document.addEventListener(DROP_EVENT, dropZoneDragHandler); + return () => { + document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + document.removeEventListener(DROP_EVENT, dropZoneDragHandler); + }; + }, [dropZoneDragHandler]); - render() { - return this.props.children; - } + return children; } -DragAndDrop.propTypes = propTypes; -DragAndDrop.defaultProps = defaultProps; +DragAndDrop.propTypes = DragAndDropPropTypes; +DragAndDrop.displayName = 'DragAndDrop'; -export default withNavigationFocus(DragAndDrop); +export default DragAndDrop; From 76654259485146c57c5ec02119ec12a4c38104a9 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 25 Jul 2023 14:27:37 -0700 Subject: [PATCH 06/37] Handle dragOver event to fix onDrop --- src/components/DragAndDrop/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js index 12a2195aa0cf..1cc6985bb993 100644 --- a/src/components/DragAndDrop/index.js +++ b/src/components/DragAndDrop/index.js @@ -8,6 +8,7 @@ import useWindowDimensions from '../../hooks/useWindowDimensions'; const COPY_DROP_EFFECT = 'copy'; const NONE_DROP_EFFECT = 'none'; const DRAG_ENTER_EVENT = 'dragenter'; +const DRAG_OVER_EVENT = 'dragover'; const DRAG_LEAVE_EVENT = 'dragleave'; const DROP_EVENT = 'drop'; @@ -78,6 +79,9 @@ function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZo if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) { switch (event.type) { + case DRAG_OVER_EVENT: + // Nothing needed here, just needed to preventDefault in order for the drop event to fire later + break; case DRAG_ENTER_EVENT: // Avoid reporting onDragEnter for children views -> not performant if (dropZoneDragState.current === DRAG_LEAVE_EVENT) { @@ -118,10 +122,14 @@ function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZo ); useEffect(() => { + // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired: + // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome + document.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); document.addEventListener(DROP_EVENT, dropZoneDragHandler); return () => { + document.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); From 401e2c65d68ce42c759d54038b36791ccea6620c Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 25 Jul 2023 14:28:09 -0700 Subject: [PATCH 07/37] Fix passing throttled function to useEffectOnPageLoad --- src/components/DragAndDrop/index.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js index 1cc6985bb993..d6a739c8991d 100644 --- a/src/components/DragAndDrop/index.js +++ b/src/components/DragAndDrop/index.js @@ -36,18 +36,16 @@ function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZo }, [dropZoneID]); useEffectOnPageLoad( - () => - _.throttle(() => { - const boundingClientRect = dropZone.current.getBoundingClientRect(); - dropZoneRect.current = { - width: boundingClientRect.width, - left: isSmallScreenWidth ? 0 : boundingClientRect.left, - right: isSmallScreenWidth ? windowWidth : boundingClientRect.right, - top: boundingClientRect.top, - bottom: boundingClientRect.bottom, - }; - }), - [windowWidth, isSmallScreenWidth], + _.throttle(() => { + const boundingClientRect = dropZone.current.getBoundingClientRect(); + dropZoneRect.current = { + width: boundingClientRect.width, + left: isSmallScreenWidth ? 0 : boundingClientRect.left, + right: isSmallScreenWidth ? windowWidth : boundingClientRect.right, + top: boundingClientRect.top, + bottom: boundingClientRect.bottom, + }; + }, [windowWidth, isSmallScreenWidth]), ); /* From 1918bfada5ef81eea7d6a89abcb5d5a1c801a134 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 25 Jul 2023 14:33:31 -0700 Subject: [PATCH 08/37] Fix yet another whoops with the throttled function --- src/components/DragAndDrop/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js index d6a739c8991d..f05cb69b16d2 100644 --- a/src/components/DragAndDrop/index.js +++ b/src/components/DragAndDrop/index.js @@ -45,7 +45,8 @@ function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZo top: boundingClientRect.top, bottom: boundingClientRect.bottom, }; - }, [windowWidth, isSmallScreenWidth]), + }, 100), + [windowWidth, isSmallScreenWidth], ); /* From e0bd6f76b33c316123b3badfe62c8acd5f9fb5fb Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 26 Jul 2023 22:51:32 -0700 Subject: [PATCH 09/37] Re-implement DragAndDrop with context --- .../Consumer/dragAndDropConsumerPropTypes.js | 15 ++ src/components/DragAndDrop/Consumer/index.js | 36 +++++ .../DragAndDrop/Consumer/index.native.js | 10 ++ .../Provider/dragAndDropProviderPropTypes.js | 15 ++ src/components/DragAndDrop/Provider/index.js | 145 ++++++++++++++++++ .../DragAndDrop/Provider/index.native.js | 10 ++ src/components/DragAndDrop/Utils/index.js | 71 +++++++++ .../DragAndDrop/Utils/index.native.js | 7 + .../DragAndDrop/dragAndDropPropTypes.js | 30 ---- src/components/DragAndDrop/index.js | 145 ------------------ src/components/DragAndDrop/index.native.js | 8 - src/pages/home/ReportScreen.js | 116 +++++++------- src/pages/home/report/ReportActionCompose.js | 118 ++++++-------- src/pages/home/report/ReportDropUI.js | 27 ++-- src/pages/home/report/ReportFooter.js | 4 +- 15 files changed, 438 insertions(+), 319 deletions(-) create mode 100644 src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js create mode 100644 src/components/DragAndDrop/Consumer/index.js create mode 100644 src/components/DragAndDrop/Consumer/index.native.js create mode 100644 src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js create mode 100644 src/components/DragAndDrop/Provider/index.js create mode 100644 src/components/DragAndDrop/Provider/index.native.js create mode 100644 src/components/DragAndDrop/Utils/index.js create mode 100644 src/components/DragAndDrop/Utils/index.native.js delete mode 100644 src/components/DragAndDrop/dragAndDropPropTypes.js delete mode 100644 src/components/DragAndDrop/index.js delete mode 100644 src/components/DragAndDrop/index.native.js diff --git a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js new file mode 100644 index 000000000000..4351a1f9a8b7 --- /dev/null +++ b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js @@ -0,0 +1,15 @@ +import PropTypes from 'prop-types'; + +export default { + /** Children to render inside this component. */ + children: PropTypes.node.isRequired, + + /** String ID to identify the dropZone. It should match the dropZoneID of the associated provider. */ + dropZoneID: PropTypes.string.isRequired, + + /** String name to identify the dropZone Portal. Multiple dropZoneIDs may share a single dropZoneHostName. */ + dropZoneHostName: PropTypes.string.isRequired, + + /** Function to execute when an item is dropped in the drop zone. */ + onDrop: PropTypes.func.isRequired, +}; diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js new file mode 100644 index 000000000000..c9c7712de8a8 --- /dev/null +++ b/src/components/DragAndDrop/Consumer/index.js @@ -0,0 +1,36 @@ +import _ from 'underscore'; +import React, {useContext, useEffect, useRef} from 'react'; +import {Portal} from '@gorhom/portal'; +import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes'; +import DNDUtils from '../Utils'; + +function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) { + const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID); + const {isDraggingOver, dropZoneRect} = useContext(DragAndDropContext); + + const onDropRef = useRef(onDrop); + onDropRef.current = onDrop; + useEffect(() => { + // Internal function ensures that we only register the onDrop listener once for this consumer, + // even if the onDrop function passed in changes + const onDropCallback = () => { + if (!onDropRef.current) { + return; + } + onDropRef.current(); + }; + DNDUtils.registerOnDropCallback(dropZoneID, onDropCallback); + return () => DNDUtils.deregisterOnDropCallback(dropZoneID, onDropCallback); + }, [dropZoneID]); + + if (!isDraggingOver) { + return null; + } + + return {_.isFunction(children) ? children(dropZoneRect) : children}; +} + +DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes; +DragAndDropConsumer.displayName = 'DragAndDropConsumer'; + +export default DragAndDropConsumer; diff --git a/src/components/DragAndDrop/Consumer/index.native.js b/src/components/DragAndDrop/Consumer/index.native.js new file mode 100644 index 000000000000..fd15e5de87e0 --- /dev/null +++ b/src/components/DragAndDrop/Consumer/index.native.js @@ -0,0 +1,10 @@ +import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes'; + +function DragAndDropConsumer({children}) { + return children; +} + +DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes; +DragAndDropConsumer.displayName = 'DragAndDropConsumer'; + +export default DragAndDropConsumer; diff --git a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js new file mode 100644 index 000000000000..f1461a098f67 --- /dev/null +++ b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js @@ -0,0 +1,15 @@ +import PropTypes from 'prop-types'; + +export default { + /** Children to render inside this component. */ + children: PropTypes.node.isRequired, + + /** ID for the drop zone. */ + dropZoneID: PropTypes.string.isRequired, + + /** ID for the Portal host where content can be rendered by consumers. */ + dropZoneHostName: PropTypes.string.isRequired, + + /** Should this dropZone be disabled? */ + isDisabled: PropTypes.bool, +}; diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js new file mode 100644 index 000000000000..bfdcb98eeaf4 --- /dev/null +++ b/src/components/DragAndDrop/Provider/index.js @@ -0,0 +1,145 @@ +import _ from 'underscore'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; +import {View} from 'react-native'; +import {useIsFocused} from '@react-navigation/native'; +import {PortalHost} from '@gorhom/portal'; +import useWindowDimensions from '../../../hooks/useWindowDimensions'; +import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; +import DNDUtils from '../Utils'; + +const COPY_DROP_EFFECT = 'copy'; +const NONE_DROP_EFFECT = 'none'; +const DRAG_ENTER_EVENT = 'dragenter'; +const DRAG_OVER_EVENT = 'dragover'; +const DRAG_LEAVE_EVENT = 'dragleave'; +const DROP_EVENT = 'drop'; + +/** + * @param {Event} event – drag event + * @returns {Boolean} + */ +function shouldAcceptDrop(event) { + return _.some(event.dataTransfer.types, (type) => type === 'Files'); +} + +function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled = false}) { + const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID, true); + useEffect( + () => () => { + DNDUtils.deleteDragAndDropContext(dropZoneID); + }, + [dropZoneID], + ); + + const isFocused = useIsFocused(); + const {windowWidth, windowHeight} = useWindowDimensions(); + + const dropZone = useRef(null); + + const [isDraggingOver, setIsDraggingOver] = useState(false); + const [dropZoneRect, setDropZoneRect] = useState({}); + + // If this component is out of focus or disabled, reset the drag state back to the default + useEffect(() => { + if (isFocused && !isDisabled) { + return; + } + setIsDraggingOver(false); + }, [isFocused, isDisabled]); + + // Measure the position of the drop zone + useEffect( + _.throttle(() => { + if (!dropZone.current) { + return; + } + dropZone.current.measureInWindow((x, y, width, height) => { + setDropZoneRect({ + left: x, + top: y, + right: x + width, + bottom: y + height, + }); + }); + }, 100), + [windowWidth, windowHeight], + ); + + /** + * Handles all types of drag-N-drop events on the drop zone associated with composer + * + * @param {Object} event native Event + */ + const dropZoneDragHandler = useCallback( + (event) => { + if (!isFocused || isDisabled) { + return; + } + + event.preventDefault(); + + if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) { + switch (event.type) { + case DRAG_OVER_EVENT: + // Nothing needed here, just needed to preventDefault in order for the drop event to fire later + break; + case DRAG_ENTER_EVENT: + if (isDraggingOver) { + return; + } + // eslint-disable-next-line no-param-reassign + event.dataTransfer.dropEffect = COPY_DROP_EFFECT; + setIsDraggingOver(true); + break; + case DRAG_LEAVE_EVENT: + if ( + !isDraggingOver || + !(event.clientY <= dropZoneRect.top || event.clientY >= dropZoneRect.bottom || event.clientX <= dropZoneRect.left || event.clientX >= dropZoneRect.right) + ) { + return; + } + + setIsDraggingOver(false); + break; + case DROP_EVENT: + setIsDraggingOver(false); + DNDUtils.executeOnDropCallbacks(event, dropZoneID); + break; + default: + break; + } + } else { + // eslint-disable-next-line no-param-reassign + event.dataTransfer.dropEffect = NONE_DROP_EFFECT; + } + }, + [isFocused, isDisabled, isDraggingOver, dropZoneRect.top, dropZoneRect.bottom, dropZoneRect.left, dropZoneRect.right, dropZoneID], + ); + + useEffect(() => { + // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired: + // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome + document.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + document.addEventListener(DROP_EVENT, dropZoneDragHandler); + return () => { + document.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + document.removeEventListener(DROP_EVENT, dropZoneDragHandler); + }; + }, [dropZoneDragHandler]); + + return ( + + (dropZone.current = e)}>{children} + + + ); +} + +DragAndDropProvider.propTypes = dragAndDropProviderPropTypes; +DragAndDropProvider.displayName = 'DragAndDropProvider'; + +export default DragAndDropProvider; diff --git a/src/components/DragAndDrop/Provider/index.native.js b/src/components/DragAndDrop/Provider/index.native.js new file mode 100644 index 000000000000..f2539e37dc8d --- /dev/null +++ b/src/components/DragAndDrop/Provider/index.native.js @@ -0,0 +1,10 @@ +import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; + +function DragAndDropProvider({children}) { + return children; +} + +DragAndDropProvider.propTypes = dragAndDropProviderPropTypes; +DragAndDropProvider.displayName = 'DragAndDropProvider'; + +export default DragAndDropProvider; diff --git a/src/components/DragAndDrop/Utils/index.js b/src/components/DragAndDrop/Utils/index.js new file mode 100644 index 000000000000..cc9c34c5abbc --- /dev/null +++ b/src/components/DragAndDrop/Utils/index.js @@ -0,0 +1,71 @@ +import {createContext} from 'react'; + +const dragAndDropContexts = {}; +const onDropCallbacks = {}; + +/** + * @param {String} dropZoneID + * @param {Boolean} [shouldCreateNewContext] – should this create a new context for the dropZoneID if one does not already exist? + * @returns {Object} + */ +function getDragAndDropContext(dropZoneID, shouldCreateNewContext = false) { + if (dropZoneID in dragAndDropContexts) { + return dragAndDropContexts[dropZoneID]; + } + + if (!shouldCreateNewContext) { + throw new Error(`Could not find DragAndDrop context with dropZoneID: ${dropZoneID}`); + } + + dragAndDropContexts[dropZoneID] = createContext({}); + return dragAndDropContexts[dropZoneID]; +} + +function deleteDragAndDropContext(dropZoneID) { + delete dragAndDropContexts[dropZoneID]; +} + +/** + * @param {String} dropZoneID + * @param {Function} callback + */ +function registerOnDropCallback(dropZoneID, callback) { + if (!(dropZoneID in onDropCallbacks)) { + onDropCallbacks[dropZoneID] = []; + } + onDropCallbacks[dropZoneID].push(callback); +} + +/** + * @param {String} dropZoneID + * @param {Function} callback + */ +function deregisterOnDropCallback(dropZoneID, callback) { + if (!(dropZoneID in onDropCallbacks)) { + return; + } + const index = onDropCallbacks[dropZoneID].indexOf(callback); + if (index < 0) { + return; + } + onDropCallbacks[dropZoneID] = onDropCallbacks[dropZoneID].splice(index, 1); +} + +/** + * @param {Event} event – onDrop event + * @param {String} dropZoneID – dropZoneID that triggered the event + */ +function executeOnDropCallbacks(event, dropZoneID) { + if (!(dropZoneID in onDropCallbacks)) { + return; + } + onDropCallbacks[dropZoneID].forEach((callback) => callback(event)); +} + +export default { + getDragAndDropContext, + deleteDragAndDropContext, + registerOnDropCallback, + deregisterOnDropCallback, + executeOnDropCallbacks, +}; diff --git a/src/components/DragAndDrop/Utils/index.native.js b/src/components/DragAndDrop/Utils/index.native.js new file mode 100644 index 000000000000..24ca8384d5c4 --- /dev/null +++ b/src/components/DragAndDrop/Utils/index.native.js @@ -0,0 +1,7 @@ +export default { + getDragAndDropContext: () => {}, + deleteDragAndDropContext: () => {}, + registerOnDropCallback: () => {}, + deregisterOnDropCallback: () => {}, + executeOnDropCallbacks: () => {}, +}; diff --git a/src/components/DragAndDrop/dragAndDropPropTypes.js b/src/components/DragAndDrop/dragAndDropPropTypes.js deleted file mode 100644 index ccd6b7440e28..000000000000 --- a/src/components/DragAndDrop/dragAndDropPropTypes.js +++ /dev/null @@ -1,30 +0,0 @@ -import PropTypes from 'prop-types'; - -export default { - /** Callback to fire when a file has being dragged over the text input & report body */ - onDragOver: PropTypes.func, - - /** Callback to fire when a file has been dragged into the text input & report body */ - onDragEnter: PropTypes.func.isRequired, - - /** Callback to fire when the user is no longer dragging over the text input & report body */ - onDragLeave: PropTypes.func.isRequired, - - /** Callback to fire when a file is dropped on the text input & report body */ - onDrop: PropTypes.func.isRequired, - - /** Guard for accepting drops in drop zone. Drag event is passed to this function as first parameter */ - shouldAcceptDrop: PropTypes.func, - - /** Id of the element on which we want to detect drag */ - dropZoneID: PropTypes.string.isRequired, - - /** Id of the element which is shown while drag is active */ - activeDropZoneID: PropTypes.string.isRequired, - - /** Whether drag & drop should be disabled */ - isDisabled: PropTypes.bool, - - /** Rendered child component */ - children: PropTypes.node.isRequired, -}; diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js deleted file mode 100644 index f05cb69b16d2..000000000000 --- a/src/components/DragAndDrop/index.js +++ /dev/null @@ -1,145 +0,0 @@ -import {useEffect, useRef, useCallback} from 'react'; -import _ from 'underscore'; -import {useIsFocused} from '@react-navigation/native'; -import DragAndDropPropTypes from './dragAndDropPropTypes'; -import useEffectOnPageLoad from '../../hooks/useEffectOnPageLoad'; -import useWindowDimensions from '../../hooks/useWindowDimensions'; - -const COPY_DROP_EFFECT = 'copy'; -const NONE_DROP_EFFECT = 'none'; -const DRAG_ENTER_EVENT = 'dragenter'; -const DRAG_OVER_EVENT = 'dragover'; -const DRAG_LEAVE_EVENT = 'dragleave'; -const DROP_EVENT = 'drop'; - -/** - * @param {Event} event – drag event - * @returns {Boolean} - */ -function shouldAcceptDrop(event) { - return _.some(event.dataTransfer.types, (type) => type === 'Files'); -} - -function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZoneID, children, isDisabled = false}) { - const isFocused = useIsFocused(); - const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); - - const dropZone = useRef(null); - const dropZoneRect = useRef(null); - - useEffect(() => { - const dropZoneElement = document.getElementById(dropZoneID); - if (!dropZoneElement) { - throw new Error('Error: element specified by dropZoneID not found'); - } - dropZone.current = dropZoneElement; - }, [dropZoneID]); - - useEffectOnPageLoad( - _.throttle(() => { - const boundingClientRect = dropZone.current.getBoundingClientRect(); - dropZoneRect.current = { - width: boundingClientRect.width, - left: isSmallScreenWidth ? 0 : boundingClientRect.left, - right: isSmallScreenWidth ? windowWidth : boundingClientRect.right, - top: boundingClientRect.top, - bottom: boundingClientRect.bottom, - }; - }, 100), - [windowWidth, isSmallScreenWidth], - ); - - /* - * Last detected drag state on the dropzone -> we start with dragleave since user is not dragging initially. - * This state is updated when drop zone is left/entered entirely(not taking the children in the account) or entire window is left - */ - const dropZoneDragState = useRef(DRAG_LEAVE_EVENT); - - // If this component is out of focus or disabled, reset the drag state back to the default - useEffect(() => { - if (isFocused && !isDisabled) { - return; - } - dropZoneDragState.current = DRAG_LEAVE_EVENT; - }, [isFocused, isDisabled]); - - /** - * Handles all types of drag-N-drop events on the drop zone associated with composer - * - * @param {Object} event native Event - */ - const dropZoneDragHandler = useCallback( - (event) => { - if (!isFocused || isDisabled) { - return; - } - - event.preventDefault(); - - if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) { - switch (event.type) { - case DRAG_OVER_EVENT: - // Nothing needed here, just needed to preventDefault in order for the drop event to fire later - break; - case DRAG_ENTER_EVENT: - // Avoid reporting onDragEnter for children views -> not performant - if (dropZoneDragState.current === DRAG_LEAVE_EVENT) { - dropZoneDragState.current = DRAG_ENTER_EVENT; - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = COPY_DROP_EFFECT; - onDragEnter(event); - } - break; - case DRAG_LEAVE_EVENT: - if (dropZoneDragState.current === DRAG_ENTER_EVENT) { - if ( - event.clientY <= dropZoneRect.current.top || - event.clientY >= dropZoneRect.current.bottom || - event.clientX <= dropZoneRect.current.left || - event.clientX >= dropZoneRect.current.right || - // Cancel drag when file manager is on top of the drop zone area - works only on chromium - (event.target.getAttribute('id') === activeDropZoneID && !event.relatedTarget) - ) { - dropZoneDragState.current = DRAG_LEAVE_EVENT; - onDragLeave(event); - } - } - break; - case DROP_EVENT: - dropZoneDragState.current = DRAG_LEAVE_EVENT; - onDrop(event); - break; - default: - break; - } - } else { - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = NONE_DROP_EFFECT; - } - }, - [isFocused, isDisabled, onDragEnter, onDragLeave, activeDropZoneID, onDrop], - ); - - useEffect(() => { - // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired: - // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome - document.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - document.addEventListener(DROP_EVENT, dropZoneDragHandler); - return () => { - document.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - document.removeEventListener(DROP_EVENT, dropZoneDragHandler); - }; - }, [dropZoneDragHandler]); - - return children; -} - -DragAndDrop.propTypes = DragAndDropPropTypes; -DragAndDrop.displayName = 'DragAndDrop'; - -export default DragAndDrop; diff --git a/src/components/DragAndDrop/index.native.js b/src/components/DragAndDrop/index.native.js deleted file mode 100644 index 2f2419b5f06e..000000000000 --- a/src/components/DragAndDrop/index.native.js +++ /dev/null @@ -1,8 +0,0 @@ -import DragAndDropPropTypes from './dragAndDropPropTypes'; - -const DragAndDrop = (props) => props.children; - -DragAndDrop.propTypes = DragAndDropPropTypes; -DragAndDrop.displayName = 'DragAndDrop'; - -export default DragAndDrop; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 5d825a528bdf..28649a291115 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -38,6 +38,7 @@ import MoneyReportHeader from '../../components/MoneyReportHeader'; import * as ComposerActions from '../../libs/actions/Composer'; import ReportScreenContext from './ReportScreenContext'; import TaskHeaderActionButton from '../../components/TaskHeaderActionButton'; +import DragAndDropProvider from '../../components/DragAndDrop/Provider'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -139,7 +140,7 @@ class ReportScreen extends React.Component { this.reactionListRef = React.createRef(); // We need unique ID for drag and drop to work properly with stack navigator. - this.dragAndDropId = CONST.REPORT.DROP_NATIVE_ID + ReportUtils.generateReportID(); + this.dragAndDropID = CONST.REPORT.DROP_NATIVE_ID + ReportUtils.generateReportID(); } componentDidMount() { @@ -349,67 +350,70 @@ class ReportScreen extends React.Component { shouldShowCloseButton /> )} - { - // Rounding this value for comparison because they can look like this: 411.9999694824219 - const skeletonViewContainerHeight = Math.round(event.nativeEvent.layout.height); - - // Only set state when the height changes to avoid unnecessary renders - if (reportActionsListViewHeight === skeletonViewContainerHeight) return; - - // The height can be 0 if the component unmounts - we are not interested in this value and want to know how much space it - // takes up so we can set the skeleton view container height. - if (skeletonViewContainerHeight === 0) { - return; - } - reportActionsListViewHeight = skeletonViewContainerHeight; - this.setState({skeletonViewContainerHeight}); - }} + - {this.isReportReadyForDisplay() && !isLoadingInitialReportActions && !isLoading && ( - - )} - - {/* Note: The report should be allowed to mount even if the initial report actions are not loaded. If we prevent rendering the report while they are loading then - we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */} - {(!this.isReportReadyForDisplay() || isLoadingInitialReportActions || isLoading) && ( - - )} - - {this.isReportReadyForDisplay() && ( - <> - { + // Rounding this value for comparison because they can look like this: 411.9999694824219 + const skeletonViewContainerHeight = Math.round(event.nativeEvent.layout.height); + + // Only set state when the height changes to avoid unnecessary renders + if (reportActionsListViewHeight === skeletonViewContainerHeight) return; + + // The height can be 0 if the component unmounts - we are not interested in this value and want to know how much space it + // takes up so we can set the skeleton view container height. + if (skeletonViewContainerHeight === 0) { + return; + } + reportActionsListViewHeight = skeletonViewContainerHeight; + this.setState({skeletonViewContainerHeight}); + }} + > + {this.isReportReadyForDisplay() && !isLoadingInitialReportActions && !isLoading && ( + - - )} - - {!this.isReportReadyForDisplay() && ( - - )} + )} - - + {/* Note: The report should be allowed to mount even if the initial report actions are not loaded. If we prevent rendering the report while they are loading then + we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */} + {(!this.isReportReadyForDisplay() || isLoadingInitialReportActions || isLoading) && ( + + )} + + {this.isReportReadyForDisplay() && ( + <> + + + )} + + {!this.isReportReadyForDisplay() && ( + + )} + + diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 0c4601f90645..11395b091169 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -39,7 +39,6 @@ import withNavigation from '../../../components/withNavigation'; import * as EmojiUtils from '../../../libs/EmojiUtils'; import * as UserUtils from '../../../libs/UserUtils'; import ReportDropUI from './ReportDropUI'; -import DragAndDrop from '../../../components/DragAndDrop'; import reportPropTypes from '../../reportPropTypes'; import EmojiSuggestions from '../../../components/EmojiSuggestions'; import MentionSuggestions from '../../../components/MentionSuggestions'; @@ -119,7 +118,7 @@ const propTypes = { pendingAction: PropTypes.oneOf(['add', 'update', 'delete']), /** Unique id for nativeId in DragAndDrop */ - dragAndDropId: PropTypes.string.isRequired, + dragAndDropID: PropTypes.string.isRequired, ...windowDimensionsPropTypes, ...withLocalizePropTypes, @@ -225,7 +224,6 @@ class ReportActionCompose extends React.Component { textInputShouldClear: false, isCommentEmpty: props.comment.length === 0, isMenuVisible: false, - isDraggingOver: false, selection: { start: isMobileSafari && !this.shouldAutoFocus ? 0 : props.comment.length, end: isMobileSafari && !this.shouldAutoFocus ? 0 : props.comment.length, @@ -979,7 +977,7 @@ class ReportActionCompose extends React.Component { // Prevents focusing and showing the keyboard while the drawer is covering the chat. const isBlockedFromConcierge = ReportUtils.chatIncludesConcierge(this.props.report) && User.isBlockedFromConcierge(this.props.blockedFromConcierge); const inputPlaceholder = this.getInputPlaceholder(); - const shouldUseFocusedColor = !isBlockedFromConcierge && !this.props.disabled && (this.state.isFocused || this.state.isDraggingOver); + const shouldUseFocusedColor = !isBlockedFromConcierge && !this.props.disabled && this.state.isFocused; const hasExceededMaxCommentLength = this.state.hasExceededMaxCommentLength; const isFullComposerAvailable = this.state.isFullComposerAvailable && !_.isEmpty(this.state.value); const hasReportRecipient = _.isObject(reportRecipient) && !_.isEmpty(reportRecipient); @@ -1118,75 +1116,60 @@ class ReportActionCompose extends React.Component { )} - { - this.setState({isDraggingOver: true}); + this.checkComposerVisibility()} + autoFocus={this.shouldAutoFocus} + multiline + ref={this.setTextInputRef} + textAlignVertical="top" + placeholder={inputPlaceholder} + placeholderTextColor={themeColors.placeholderText} + onChangeText={(comment) => this.updateComment(comment, true)} + onKeyPress={this.triggerHotkeyActions} + style={[styles.textInputCompose, this.props.isComposerFullSize ? styles.textInputFullCompose : styles.flex4]} + maxLines={maxComposerLines} + onFocus={() => this.setIsFocused(true)} + onBlur={() => { + this.setIsFocused(false); + this.resetSuggestions(); }} - onDragLeave={() => { - this.setState({isDraggingOver: false}); + onClick={() => { + this.shouldBlockEmojiCalc = false; + this.shouldBlockMentionCalc = false; }} - onDrop={(e) => { - e.preventDefault(); - if (this.state.isAttachmentPreviewActive) { - this.setState({isDraggingOver: false}); + onPasteFile={displayFileInModal} + shouldClear={this.state.textInputShouldClear} + onClear={() => this.setTextInputShouldClear(false)} + isDisabled={isBlockedFromConcierge || this.props.disabled} + selection={this.state.selection} + onSelectionChange={this.onSelectionChange} + isFullComposerAvailable={isFullComposerAvailable} + setIsFullComposerAvailable={this.setIsFullComposerAvailable} + isComposerFullSize={this.props.isComposerFullSize} + value={this.state.value} + numberOfLines={this.props.numberOfLines} + onNumberOfLinesChange={this.updateNumberOfLines} + shouldCalculateCaretPosition + onLayout={(e) => { + const composerHeight = e.nativeEvent.layout.height; + if (this.state.composerHeight === composerHeight) { return; } - - const file = lodashGet(e, ['dataTransfer', 'files', 0]); - - displayFileInModal(file); - - this.setState({isDraggingOver: false}); + this.setState({composerHeight}); }} - isDisabled={this.props.disabled} - > - this.checkComposerVisibility()} - autoFocus={this.shouldAutoFocus} - multiline - ref={this.setTextInputRef} - textAlignVertical="top" - placeholder={inputPlaceholder} - placeholderTextColor={themeColors.placeholderText} - onChangeText={(comment) => this.updateComment(comment, true)} - onKeyPress={this.triggerHotkeyActions} - style={[styles.textInputCompose, this.props.isComposerFullSize ? styles.textInputFullCompose : styles.flex4]} - maxLines={maxComposerLines} - onFocus={() => this.setIsFocused(true)} - onBlur={() => { - this.setIsFocused(false); - this.resetSuggestions(); - }} - onClick={() => { - this.shouldBlockEmojiCalc = false; - this.shouldBlockMentionCalc = false; - }} - onPasteFile={displayFileInModal} - shouldClear={this.state.textInputShouldClear} - onClear={() => this.setTextInputShouldClear(false)} - isDisabled={isBlockedFromConcierge || this.props.disabled} - selection={this.state.selection} - onSelectionChange={this.onSelectionChange} - isFullComposerAvailable={isFullComposerAvailable} - setIsFullComposerAvailable={this.setIsFullComposerAvailable} - isComposerFullSize={this.props.isComposerFullSize} - value={this.state.value} - numberOfLines={this.props.numberOfLines} - onNumberOfLinesChange={this.updateNumberOfLines} - shouldCalculateCaretPosition - onLayout={(e) => { - const composerHeight = e.nativeEvent.layout.height; - if (this.state.composerHeight === composerHeight) { - return; - } - this.setState({composerHeight}); - }} - onScroll={() => this.setShouldShowSuggestionMenuToFalse()} - /> - + onScroll={() => this.setShouldShowSuggestionMenuToFalse()} + /> + {/* { */} + {/* if (this.state.isAttachmentPreviewActive) { */} + {/* return; */} + {/* } */} + {/* const file = lodashGet(e, ['dataTransfer', 'files', 0]); */} + {/* displayFileInModal(file); */} + {/* }} */} + {/* /> */} )} @@ -1236,7 +1219,6 @@ class ReportActionCompose extends React.Component { /> - {this.state.isDraggingOver && } {!_.isEmpty(this.state.suggestedEmojis) && this.state.shouldShowEmojiSuggestionMenu && ( - {props.translate('reportActionCompose.dropToUpload')} - + {translate('reportActionCompose.dropToUpload')} + ); } ReportDropUI.displayName = 'ReportDropUI'; ReportDropUI.propTypes = propTypes; -export default withLocalize(ReportDropUI); +export default ReportDropUI; diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index f37c0247fada..471e47cb2b0a 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -45,7 +45,7 @@ const propTypes = { shouldDisableCompose: PropTypes.bool, /** Unique id for nativeId in DragAndDrop */ - dragAndDropId: PropTypes.string.isRequired, + dragAndDropID: PropTypes.string.isRequired, ...windowDimensionsPropTypes, }; @@ -95,7 +95,7 @@ function ReportFooter(props) { pendingAction={props.pendingAction} isComposerFullSize={props.isComposerFullSize} disabled={props.shouldDisableCompose} - dragAndDropId={props.dragAndDropId} + dragAndDropID={props.dragAndDropID} /> From 6be2fe0de46f536251f531ee363e02ed79c6b373 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 26 Jul 2023 23:24:40 -0700 Subject: [PATCH 10/37] Create useThrottledEffect hook --- src/components/DragAndDrop/Provider/index.js | 8 +++++--- src/hooks/useThrottledEffect.js | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/hooks/useThrottledEffect.js diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index bfdcb98eeaf4..c824a14bf83a 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -4,6 +4,7 @@ import {View} from 'react-native'; import {useIsFocused} from '@react-navigation/native'; import {PortalHost} from '@gorhom/portal'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; +import useThrottledEffect from '../../../hooks/useThrottledEffect'; import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; import DNDUtils from '../Utils'; @@ -48,8 +49,8 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled }, [isFocused, isDisabled]); // Measure the position of the drop zone - useEffect( - _.throttle(() => { + useThrottledEffect( + () => { if (!dropZone.current) { return; } @@ -61,7 +62,8 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled bottom: y + height, }); }); - }, 100), + }, + 100, [windowWidth, windowHeight], ); diff --git a/src/hooks/useThrottledEffect.js b/src/hooks/useThrottledEffect.js new file mode 100644 index 000000000000..099b4b02dcc0 --- /dev/null +++ b/src/hooks/useThrottledEffect.js @@ -0,0 +1,15 @@ +import _ from 'underscore'; +import {useEffect, useRef, useCallback} from 'react'; + +export default function useThrottledEffect(effect, rateLimit, dependencies) { + const callback = useRef(effect); + callback.current = effect; + + // eslint-disable-next-line react-hooks/exhaustive-deps + const throttledEffect = useCallback( + _.throttle(() => effect.current(), rateLimit), + [rateLimit], + ); + + useEffect(throttledEffect, [throttledEffect, ...dependencies]); +} From 23f2d4fd17bb51b3f5d4b957beece11e7c7d0338 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 26 Jul 2023 23:26:29 -0700 Subject: [PATCH 11/37] remove unused import --- src/pages/home/ReportScreen.js | 1 - src/pages/home/report/ReportActionCompose.js | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 28649a291115..4bea13035ae5 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import {View} from 'react-native'; import lodashGet from 'lodash/get'; import _ from 'underscore'; -import {PortalHost} from '@gorhom/portal'; import styles from '../../styles/styles'; import ScreenWrapper from '../../components/ScreenWrapper'; import HeaderView from './HeaderView'; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 11395b091169..84c512249812 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -1160,16 +1160,16 @@ class ReportActionCompose extends React.Component { onScroll={() => this.setShouldShowSuggestionMenuToFalse()} /> - {/* { */} - {/* if (this.state.isAttachmentPreviewActive) { */} - {/* return; */} - {/* } */} - {/* const file = lodashGet(e, ['dataTransfer', 'files', 0]); */} - {/* displayFileInModal(file); */} - {/* }} */} - {/* /> */} + { + if (this.state.isAttachmentPreviewActive) { + return; + } + const file = lodashGet(e, ['dataTransfer', 'files', 0]); + displayFileInModal(file); + }} + /> )} From 5949c3e28b3d931ffb010916eef97ac4eecf52ff Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 26 Jul 2023 23:31:24 -0700 Subject: [PATCH 12/37] Add flex 1 style to fix display --- src/components/DragAndDrop/Provider/index.js | 8 +++++++- src/hooks/useThrottledEffect.js | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index c824a14bf83a..0c07b4b33501 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -7,6 +7,7 @@ import useWindowDimensions from '../../../hooks/useWindowDimensions'; import useThrottledEffect from '../../../hooks/useThrottledEffect'; import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; import DNDUtils from '../Utils'; +import styles from '../../../styles/styles'; const COPY_DROP_EFFECT = 'copy'; const NONE_DROP_EFFECT = 'none'; @@ -135,7 +136,12 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled return ( - (dropZone.current = e)}>{children} + (dropZone.current = e)} + style={styles.flex1} + > + {children} + ); diff --git a/src/hooks/useThrottledEffect.js b/src/hooks/useThrottledEffect.js index 099b4b02dcc0..3032beec325f 100644 --- a/src/hooks/useThrottledEffect.js +++ b/src/hooks/useThrottledEffect.js @@ -7,7 +7,12 @@ export default function useThrottledEffect(effect, rateLimit, dependencies) { // eslint-disable-next-line react-hooks/exhaustive-deps const throttledEffect = useCallback( - _.throttle(() => effect.current(), rateLimit), + _.throttle(() => { + if (!_.isFunction(effect.current)) { + return; + } + return effect.current(); + }, rateLimit), [rateLimit], ); From 2094ca0907d64a1cbe9c56525046939edf7381b3 Mon Sep 17 00:00:00 2001 From: rory Date: Wed, 26 Jul 2023 23:55:37 -0700 Subject: [PATCH 13/37] Fix overlay style --- src/components/DragAndDrop/Consumer/index.js | 8 ++++++- src/components/DragAndDrop/Provider/index.js | 4 ++++ src/pages/home/report/ReportDropUI.js | 16 +++++++------- src/styles/styles.js | 22 +++++++------------- src/styles/themes/default.js | 2 -- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index c9c7712de8a8..4d4a1806b567 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -1,8 +1,10 @@ import _ from 'underscore'; import React, {useContext, useEffect, useRef} from 'react'; +import {View} from 'react-native'; import {Portal} from '@gorhom/portal'; import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes'; import DNDUtils from '../Utils'; +import styles from '../../../styles/styles'; function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) { const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID); @@ -27,7 +29,11 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) { return null; } - return {_.isFunction(children) ? children(dropZoneRect) : children}; + return ( + + {_.isFunction(children) ? children(dropZoneRect) : children} + + ); } DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes; diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index 0c07b4b33501..9ee5f69a8e00 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -87,6 +87,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled // Nothing needed here, just needed to preventDefault in order for the drop event to fire later break; case DRAG_ENTER_EVENT: + console.log('RORY_DEBUG drag enter'); if (isDraggingOver) { return; } @@ -95,6 +96,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled setIsDraggingOver(true); break; case DRAG_LEAVE_EVENT: + console.log('RORY_DEBUG drag leave'); if ( !isDraggingOver || !(event.clientY <= dropZoneRect.top || event.clientY >= dropZoneRect.bottom || event.clientX <= dropZoneRect.left || event.clientX >= dropZoneRect.right) @@ -134,6 +136,8 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled }; }, [dropZoneDragHandler]); + console.log('RORY_DEBUG DragAndDropProvider rendering w/ context:', {dropZoneID, isDraggingOver, dropZoneRect}); + return ( - - + + + + + {translate('reportActionCompose.dropToUpload')} - {translate('reportActionCompose.dropToUpload')} ); } diff --git a/src/styles/styles.js b/src/styles/styles.js index 0c6c0f4cde78..b02ed8cd8077 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3156,30 +3156,24 @@ const styles = { marginLeft: 6, }, - fullScreenTransparentOverlay: { + fullScreen: { position: 'absolute', - width: '100%', - height: '100%', top: 0, left: 0, right: 0, bottom: 0, - backgroundColor: themeColors.dropUIBG, - zIndex: 2, }, - dropZoneTopInvisibleOverlay: { - position: 'absolute', - width: '100%', - height: '100%', - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: themeColors.dropTransparentOverlay, + invisibleOverlay: { + backgroundColor: themeColors.transparent, zIndex: 1000, }, + reportDropOverlay: { + backgroundColor: themeColors.dropUIBG, + zIndex: 2, + }, + cardSection: { backgroundColor: themeColors.cardBG, borderRadius: variables.componentBorderRadiusCard, diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index 43f1ff083d94..884a31a03301 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -64,7 +64,6 @@ const darkTheme = { heroCard: colors.blue, uploadPreviewActivityIndicator: colors.greenHighlightBackground, dropUIBG: 'rgba(6,27,9,0.92)', - dropTransparentOverlay: 'rgba(255,255,255,0)', checkBox: colors.green, pickerOptionsTextColor: colors.white, imageCropBackgroundColor: colors.greenIcons, @@ -121,7 +120,6 @@ const oldTheme = { heroCard: colors.blue, uploadPreviewActivityIndicator: colors.gray1, dropUIBG: 'rgba(6,27,9,0.92)', - dropTransparentOverlay: 'rgba(255,255,255,0)', cardBG: colors.gray1, cardBorder: colors.gray1, checkBox: colors.blue, From f5afe54f81578caa67a78be448f06e6d537edc2b Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 01:21:00 -0700 Subject: [PATCH 14/37] Remove throttled effect hook so measure can happen onLayout --- src/components/DragAndDrop/Consumer/index.js | 2 +- src/components/DragAndDrop/Provider/index.js | 17 +++++++++-------- src/hooks/useThrottledEffect.js | 20 -------------------- 3 files changed, 10 insertions(+), 29 deletions(-) delete mode 100644 src/hooks/useThrottledEffect.js diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index 4d4a1806b567..d22e4b2dee65 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -31,7 +31,7 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) { return ( - {_.isFunction(children) ? children(dropZoneRect) : children} + {_.isFunction(children) ? children(dropZoneRect) : children} ); } diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index 9ee5f69a8e00..deca707f9c5a 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -4,7 +4,6 @@ import {View} from 'react-native'; import {useIsFocused} from '@react-navigation/native'; import {PortalHost} from '@gorhom/portal'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; -import useThrottledEffect from '../../../hooks/useThrottledEffect'; import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; import DNDUtils from '../Utils'; import styles from '../../../styles/styles'; @@ -49,9 +48,9 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled setIsDraggingOver(false); }, [isFocused, isDisabled]); - // Measure the position of the drop zone - useThrottledEffect( - () => { + // eslint-disable-next-line react-hooks/exhaustive-deps + const measureDropZone = useCallback( + _.throttle(() => { if (!dropZone.current) { return; } @@ -63,11 +62,13 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled bottom: y + height, }); }); - }, - 100, - [windowWidth, windowHeight], + }, 100), + [], ); + // Remeasure the position of the drop zone when the window resizes + useEffect(measureDropZone, [windowWidth, windowHeight, measureDropZone]); + /** * Handles all types of drag-N-drop events on the drop zone associated with composer * @@ -87,7 +88,6 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled // Nothing needed here, just needed to preventDefault in order for the drop event to fire later break; case DRAG_ENTER_EVENT: - console.log('RORY_DEBUG drag enter'); if (isDraggingOver) { return; } @@ -142,6 +142,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled (dropZone.current = e)} + onLayout={measureDropZone} style={styles.flex1} > {children} diff --git a/src/hooks/useThrottledEffect.js b/src/hooks/useThrottledEffect.js deleted file mode 100644 index 3032beec325f..000000000000 --- a/src/hooks/useThrottledEffect.js +++ /dev/null @@ -1,20 +0,0 @@ -import _ from 'underscore'; -import {useEffect, useRef, useCallback} from 'react'; - -export default function useThrottledEffect(effect, rateLimit, dependencies) { - const callback = useRef(effect); - callback.current = effect; - - // eslint-disable-next-line react-hooks/exhaustive-deps - const throttledEffect = useCallback( - _.throttle(() => { - if (!_.isFunction(effect.current)) { - return; - } - return effect.current(); - }, rateLimit), - [rateLimit], - ); - - useEffect(throttledEffect, [throttledEffect, ...dependencies]); -} From 989f5fa0dfce7d4eba184afa187209371fae250b Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 03:04:31 -0700 Subject: [PATCH 15/37] Fix drag state by nesting portal inside transparent overlay and implementing counter --- src/components/DragAndDrop/Provider/index.js | 45 ++++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index deca707f9c5a..d4469b373281 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -36,6 +36,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled const {windowWidth, windowHeight} = useWindowDimensions(); const dropZone = useRef(null); + const dragCounter = useRef(0); const [isDraggingOver, setIsDraggingOver] = useState(false); const [dropZoneRect, setDropZoneRect] = useState({}); @@ -45,6 +46,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled if (isFocused && !isDisabled) { return; } + dragCounter.current = 0; setIsDraggingOver(false); }, [isFocused, isDisabled]); @@ -82,12 +84,13 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled event.preventDefault(); - if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) { + if (shouldAcceptDrop(event)) { switch (event.type) { case DRAG_OVER_EVENT: // Nothing needed here, just needed to preventDefault in order for the drop event to fire later break; case DRAG_ENTER_EVENT: + dragCounter.current++; if (isDraggingOver) { return; } @@ -96,17 +99,15 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled setIsDraggingOver(true); break; case DRAG_LEAVE_EVENT: - console.log('RORY_DEBUG drag leave'); - if ( - !isDraggingOver || - !(event.clientY <= dropZoneRect.top || event.clientY >= dropZoneRect.bottom || event.clientX <= dropZoneRect.left || event.clientX >= dropZoneRect.right) - ) { + dragCounter.current--; + if (!isDraggingOver || dragCounter.current > 0) { return; } setIsDraggingOver(false); break; case DROP_EVENT: + dragCounter.current = 0; setIsDraggingOver(false); DNDUtils.executeOnDropCallbacks(event, dropZoneID); break; @@ -118,26 +119,32 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled event.dataTransfer.dropEffect = NONE_DROP_EFFECT; } }, - [isFocused, isDisabled, isDraggingOver, dropZoneRect.top, dropZoneRect.bottom, dropZoneRect.left, dropZoneRect.right, dropZoneID], + [isFocused, isDisabled, isDraggingOver, dropZoneID], ); useEffect(() => { + if (!dropZone.current) { + return; + } + // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired: // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome - document.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - document.addEventListener(DROP_EVENT, dropZoneDragHandler); + dropZone.current.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + dropZone.current.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + dropZone.current.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + dropZone.current.addEventListener(DROP_EVENT, dropZoneDragHandler); return () => { - document.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - document.removeEventListener(DROP_EVENT, dropZoneDragHandler); + if (!dropZone.current) { + return; + } + + dropZone.current.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + dropZone.current.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + dropZone.current.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + dropZone.current.removeEventListener(DROP_EVENT, dropZoneDragHandler); }; }, [dropZoneDragHandler]); - console.log('RORY_DEBUG DragAndDropProvider rendering w/ context:', {dropZoneID, isDraggingOver, dropZoneRect}); - return ( + + + {children} - ); } From c9a16801051feec2a0acb60942a8636070fe4d8b Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 03:08:52 -0700 Subject: [PATCH 16/37] Simplify by removing measurement --- src/components/DragAndDrop/Consumer/index.js | 11 ++------ src/components/DragAndDrop/Provider/index.js | 27 +------------------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index d22e4b2dee65..a27c09d495a4 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -1,14 +1,11 @@ -import _ from 'underscore'; import React, {useContext, useEffect, useRef} from 'react'; -import {View} from 'react-native'; import {Portal} from '@gorhom/portal'; import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes'; import DNDUtils from '../Utils'; -import styles from '../../../styles/styles'; function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) { const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID); - const {isDraggingOver, dropZoneRect} = useContext(DragAndDropContext); + const {isDraggingOver} = useContext(DragAndDropContext); const onDropRef = useRef(onDrop); onDropRef.current = onDrop; @@ -29,11 +26,7 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) { return null; } - return ( - - {_.isFunction(children) ? children(dropZoneRect) : children} - - ); + return {children}; } DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes; diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index d4469b373281..f8b7bba536bf 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -3,7 +3,6 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import {useIsFocused} from '@react-navigation/native'; import {PortalHost} from '@gorhom/portal'; -import useWindowDimensions from '../../../hooks/useWindowDimensions'; import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; import DNDUtils from '../Utils'; import styles from '../../../styles/styles'; @@ -33,13 +32,11 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled ); const isFocused = useIsFocused(); - const {windowWidth, windowHeight} = useWindowDimensions(); const dropZone = useRef(null); const dragCounter = useRef(0); const [isDraggingOver, setIsDraggingOver] = useState(false); - const [dropZoneRect, setDropZoneRect] = useState({}); // If this component is out of focus or disabled, reset the drag state back to the default useEffect(() => { @@ -50,27 +47,6 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled setIsDraggingOver(false); }, [isFocused, isDisabled]); - // eslint-disable-next-line react-hooks/exhaustive-deps - const measureDropZone = useCallback( - _.throttle(() => { - if (!dropZone.current) { - return; - } - dropZone.current.measureInWindow((x, y, width, height) => { - setDropZoneRect({ - left: x, - top: y, - right: x + width, - bottom: y + height, - }); - }); - }, 100), - [], - ); - - // Remeasure the position of the drop zone when the window resizes - useEffect(measureDropZone, [windowWidth, windowHeight, measureDropZone]); - /** * Handles all types of drag-N-drop events on the drop zone associated with composer * @@ -146,10 +122,9 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled }, [dropZoneDragHandler]); return ( - + (dropZone.current = e)} - onLayout={measureDropZone} style={styles.flex1} > From 58b31c9aa9918e4122dd54f6cff5f8144bab8b8f Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 03:11:17 -0700 Subject: [PATCH 17/37] Remove unused useEffectOnPageLoad hook --- src/hooks/useEffectOnPageLoad/index.js | 20 ------------------- src/hooks/useEffectOnPageLoad/index.native.js | 3 --- 2 files changed, 23 deletions(-) delete mode 100644 src/hooks/useEffectOnPageLoad/index.js delete mode 100644 src/hooks/useEffectOnPageLoad/index.native.js diff --git a/src/hooks/useEffectOnPageLoad/index.js b/src/hooks/useEffectOnPageLoad/index.js deleted file mode 100644 index a7338fe55a6b..000000000000 --- a/src/hooks/useEffectOnPageLoad/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import {useEffect, useRef} from 'react'; - -export default function useEffectOnPageLoad(onPageLoad, dependencies = []) { - const onPageLoadRef = useRef(onPageLoad); - onPageLoadRef.current = onPageLoad; - - useEffect(() => { - function onPageLoadCallback() { - onPageLoadRef.current(); - } - - if (document.readyState === 'complete') { - onPageLoadCallback(); - } else { - window.addEventListener('load', onPageLoadCallback); - return () => window.removeEventListener('load', onPageLoadCallback); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, dependencies); -} diff --git a/src/hooks/useEffectOnPageLoad/index.native.js b/src/hooks/useEffectOnPageLoad/index.native.js deleted file mode 100644 index 0c55bf66d3e6..000000000000 --- a/src/hooks/useEffectOnPageLoad/index.native.js +++ /dev/null @@ -1,3 +0,0 @@ -import {useEffect} from 'react'; - -export default useEffect; From a0f6afa34a2b051e163a92a708103177df4dc4fb Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 08:52:56 -0700 Subject: [PATCH 18/37] Remove now-unused DropZone component --- src/components/DragAndDrop/DropZone/index.js | 34 ------------------- .../DragAndDrop/DropZone/index.native.js | 5 --- 2 files changed, 39 deletions(-) delete mode 100644 src/components/DragAndDrop/DropZone/index.js delete mode 100644 src/components/DragAndDrop/DropZone/index.native.js diff --git a/src/components/DragAndDrop/DropZone/index.js b/src/components/DragAndDrop/DropZone/index.js deleted file mode 100644 index a073009e22d5..000000000000 --- a/src/components/DragAndDrop/DropZone/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import {Portal} from '@gorhom/portal'; -import PropTypes from 'prop-types'; -import styles from '../../../styles/styles'; - -const propTypes = { - /** Name for a drop zone view holder which gives us the flexibility to mount drop zone wherever we want. The holder view can be implemented as PortalHost */ - dropZoneViewHolderName: PropTypes.string.isRequired, - - /** Drop zone content */ - children: PropTypes.node.isRequired, - - /** Required for drag and drop to properly detect dropzone */ - dropZoneID: PropTypes.string.isRequired, -}; - -function DropZone(props) { - return ( - - {props.children} - {/* Necessary for blocking events on content which can publish unwanted dragleave even if we are inside dropzone */} - - - ); -} - -DropZone.displayName = 'DropZone'; -DropZone.propTypes = propTypes; - -export default DropZone; diff --git a/src/components/DragAndDrop/DropZone/index.native.js b/src/components/DragAndDrop/DropZone/index.native.js deleted file mode 100644 index 4e76267469b0..000000000000 --- a/src/components/DragAndDrop/DropZone/index.native.js +++ /dev/null @@ -1,5 +0,0 @@ -const DropZone = (props) => props.children; - -DropZone.displayName = 'DropZone'; - -export default DropZone; From 807d81934c9aeaed2098ce4cb5bc3426f0b5a114 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 10:38:14 -0700 Subject: [PATCH 19/37] Correctly pass event to subscriber --- src/components/DragAndDrop/Consumer/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index a27c09d495a4..041995661b68 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -12,11 +12,11 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) { useEffect(() => { // Internal function ensures that we only register the onDrop listener once for this consumer, // even if the onDrop function passed in changes - const onDropCallback = () => { + const onDropCallback = (e) => { if (!onDropRef.current) { return; } - onDropRef.current(); + onDropRef.current(e); }; DNDUtils.registerOnDropCallback(dropZoneID, onDropCallback); return () => DNDUtils.deregisterOnDropCallback(dropZoneID, onDropCallback); From 48ce5e4c70d1ced8d9a176bf203e07852b67b14b Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 11:54:58 -0700 Subject: [PATCH 20/37] Fix storybook --- .storybook/preview.js | 3 +- .storybook/webpack.config.js | 1 + __mocks__/@react-navigation/native/index.js | 7 ++ src/components/DragAndDrop/Provider/index.js | 7 +- src/stories/DragAndDrop.stories.js | 95 +++++++++----------- 5 files changed, 57 insertions(+), 56 deletions(-) create mode 100644 __mocks__/@react-navigation/native/index.js diff --git a/.storybook/preview.js b/.storybook/preview.js index 2926e8f8c818..7ccfd74e0e45 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,6 +1,7 @@ import React from 'react'; import Onyx from 'react-native-onyx'; import {SafeAreaProvider} from 'react-native-safe-area-context'; +import {PortalProvider} from '@gorhom/portal'; import './fonts.css'; import ComposeProviders from '../src/components/ComposeProviders'; import HTMLEngineProvider from '../src/components/HTMLEngineProvider'; @@ -14,7 +15,7 @@ Onyx.init({ const decorators = [ (Story) => ( - + ), diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 6c39661c76f9..3c5dbb41ea2c 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -27,6 +27,7 @@ module.exports = ({config}) => { 'react-native$': '@expensify/react-native-web', 'react-native-web': '@expensify/react-native-web', '@react-native-community/netinfo': path.resolve(__dirname, '../__mocks__/@react-native-community/netinfo.js'), + '@react-navigation/native': path.resolve(__dirname, '../__mocks__/@react-navigation/native'), }; // Necessary to overwrite the values in the existing DefinePlugin hardcoded to the Config staging values diff --git a/__mocks__/@react-navigation/native/index.js b/__mocks__/@react-navigation/native/index.js new file mode 100644 index 000000000000..7d26028217af --- /dev/null +++ b/__mocks__/@react-navigation/native/index.js @@ -0,0 +1,7 @@ +function useIsFocused() { + return true; +} + +export * from '@react-navigation/core'; +export * from '@react-navigation/native'; +export {useIsFocused}; diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index f8b7bba536bf..0bcc53788b1b 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -125,9 +125,12 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled (dropZone.current = e)} - style={styles.flex1} + style={[styles.flex1, styles.w100, styles.h100]} > - + {children} diff --git a/src/stories/DragAndDrop.stories.js b/src/stories/DragAndDrop.stories.js index ece5394f49e8..ef5b4fbdf1de 100644 --- a/src/stories/DragAndDrop.stories.js +++ b/src/stories/DragAndDrop.stories.js @@ -1,10 +1,9 @@ import React, {useState} from 'react'; import {View, Image} from 'react-native'; -import {PortalHost, PortalProvider} from '@gorhom/portal'; import lodashGet from 'lodash/get'; import Text from '../components/Text'; -import DragAndDrop from '../components/DragAndDrop'; -import DropZone from '../components/DragAndDrop/DropZone'; +import DragAndDropProvider from '../components/DragAndDrop/Provider'; +import DragAndDropConsumer from '../components/DragAndDrop/Consumer'; import styles from '../styles/styles'; /** @@ -14,52 +13,34 @@ import styles from '../styles/styles'; */ const story = { title: 'Components/DragAndDrop', - component: DragAndDrop, + component: DragAndDropConsumer, }; -function Default() { - const [draggingOver, setDraggingOver] = useState(false); - - const [fileUrl, setFileUrl] = useState(''); +const DROP_ZONE_ID = 'dropID'; +const DROP_ZONE_HOST_NAME = 'dropZoneHostName'; +function Default() { + const [fileURL, setFileURL] = useState(''); return ( - - {/* DragAndDrop does not need to render drop area as children since it is connected to it via id, which gives us flexibility to bring DragAndDrop where your - draggingOver state is located */} - { - setDraggingOver(true); - }} - onDragLeave={() => { - setDraggingOver(false); - }} - onDrop={(e) => { - const file = lodashGet(e, ['dataTransfer', 'files', 0]); - if (file && file.type.includes('image')) { - setFileUrl(URL.createObjectURL(file)); - } - setDraggingOver(false); - }} + + - - {fileUrl ? ( + + {fileURL ? ( Drop a picture here! )} - {/* Portals give us flexibility to render active drag overlay regardless of your react component structure */} - - - {draggingOver && ( - - )} - + { + const file = lodashGet(e, ['dataTransfer', 'files', 0]); + if (file && file.type.includes('image')) { + const reader = new FileReader(); + reader.addEventListener('load', () => setFileURL(reader.result)); + reader.readAsDataURL(file); + } + }} + > + + Release to upload file + + + + ); } From 0a09b5b0e953f872ea82cfb97189188c8465a220 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 12:01:08 -0700 Subject: [PATCH 21/37] Get rid of unnecessary hostName --- src/CONST.js | 3 +-- .../DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js | 3 --- src/components/DragAndDrop/Consumer/index.js | 4 ++-- .../DragAndDrop/Provider/dragAndDropProviderPropTypes.js | 3 --- src/components/DragAndDrop/Provider/index.js | 6 +++--- src/pages/home/ReportScreen.js | 8 +------- src/pages/home/report/ReportActionCompose.js | 4 ---- src/pages/home/report/ReportDropUI.js | 8 ++------ src/pages/home/report/ReportFooter.js | 4 ---- src/stories/DragAndDrop.stories.js | 7 +------ 10 files changed, 10 insertions(+), 40 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index af25f43d47c0..796a95ce3716 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -480,8 +480,7 @@ const CONST = { PERSONAL_DETAIL: 'personalDetail', }, REPORT: { - DROP_HOST_NAME: 'ReportDropZone', - DROP_NATIVE_ID: 'report-dropzone', + DROP_ZONE_ID: 'report-dropzone', ACTIVE_DROP_NATIVE_ID: 'report-dropzone', MAXIMUM_PARTICIPANTS: 8, SPLIT_REPORTID: '-2', diff --git a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js index 4351a1f9a8b7..d039c766b10a 100644 --- a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js +++ b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js @@ -7,9 +7,6 @@ export default { /** String ID to identify the dropZone. It should match the dropZoneID of the associated provider. */ dropZoneID: PropTypes.string.isRequired, - /** String name to identify the dropZone Portal. Multiple dropZoneIDs may share a single dropZoneHostName. */ - dropZoneHostName: PropTypes.string.isRequired, - /** Function to execute when an item is dropped in the drop zone. */ onDrop: PropTypes.func.isRequired, }; diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index 041995661b68..886d10b37fc7 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -3,7 +3,7 @@ import {Portal} from '@gorhom/portal'; import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes'; import DNDUtils from '../Utils'; -function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) { +function DragAndDropConsumer({children, dropZoneID, onDrop}) { const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID); const {isDraggingOver} = useContext(DragAndDropContext); @@ -26,7 +26,7 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) { return null; } - return {children}; + return {children}; } DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes; diff --git a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js index f1461a098f67..e33a47474517 100644 --- a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js +++ b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js @@ -7,9 +7,6 @@ export default { /** ID for the drop zone. */ dropZoneID: PropTypes.string.isRequired, - /** ID for the Portal host where content can be rendered by consumers. */ - dropZoneHostName: PropTypes.string.isRequired, - /** Should this dropZone be disabled? */ isDisabled: PropTypes.bool, }; diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index 0bcc53788b1b..40b2b17d021a 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -22,7 +22,7 @@ function shouldAcceptDrop(event) { return _.some(event.dataTransfer.types, (type) => type === 'Files'); } -function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled = false}) { +function DragAndDropProvider({children, dropZoneID, isDisabled = false}) { const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID, true); useEffect( () => () => { @@ -129,9 +129,9 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled > - + {children} diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 4bea13035ae5..829629fbc106 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -137,9 +137,6 @@ class ReportScreen extends React.Component { this.flatListRef = React.createRef(); this.reactionListRef = React.createRef(); - - // We need unique ID for drag and drop to work properly with stack navigator. - this.dragAndDropID = CONST.REPORT.DROP_NATIVE_ID + ReportUtils.generateReportID(); } componentDidMount() { @@ -350,8 +347,7 @@ class ReportScreen extends React.Component { /> )} )} @@ -408,7 +403,6 @@ class ReportScreen extends React.Component { )} diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 84c512249812..f5affeb60882 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -117,9 +117,6 @@ const propTypes = { /** The type of action that's pending */ pendingAction: PropTypes.oneOf(['add', 'update', 'delete']), - /** Unique id for nativeId in DragAndDrop */ - dragAndDropID: PropTypes.string.isRequired, - ...windowDimensionsPropTypes, ...withLocalizePropTypes, ...withCurrentUserPersonalDetailsPropTypes, @@ -1161,7 +1158,6 @@ class ReportActionCompose extends React.Component { /> { if (this.state.isAttachmentPreviewActive) { return; diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js index f9edf5577d8b..201ecadbc6e2 100644 --- a/src/pages/home/report/ReportDropUI.js +++ b/src/pages/home/report/ReportDropUI.js @@ -10,19 +10,15 @@ import * as Expensicons from '../../../components/Icon/Expensicons'; import useLocalize from '../../../hooks/useLocalize'; const propTypes = { - /** DropZoneID for the current report. */ - dropZoneID: PropTypes.string.isRequired, - /** Callback to execute when a file is dropped. */ onDrop: PropTypes.func.isRequired, }; -function ReportDropUI({dropZoneID, onDrop}) { +function ReportDropUI({onDrop}) { const {translate} = useLocalize(); return ( diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 471e47cb2b0a..c03e47dd531e 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -44,9 +44,6 @@ const propTypes = { /** Whether user interactions should be disabled */ shouldDisableCompose: PropTypes.bool, - /** Unique id for nativeId in DragAndDrop */ - dragAndDropID: PropTypes.string.isRequired, - ...windowDimensionsPropTypes, }; @@ -95,7 +92,6 @@ function ReportFooter(props) { pendingAction={props.pendingAction} isComposerFullSize={props.isComposerFullSize} disabled={props.shouldDisableCompose} - dragAndDropID={props.dragAndDropID} /> diff --git a/src/stories/DragAndDrop.stories.js b/src/stories/DragAndDrop.stories.js index ef5b4fbdf1de..e57c349a6fe5 100644 --- a/src/stories/DragAndDrop.stories.js +++ b/src/stories/DragAndDrop.stories.js @@ -17,7 +17,6 @@ const story = { }; const DROP_ZONE_ID = 'dropID'; -const DROP_ZONE_HOST_NAME = 'dropZoneHostName'; function Default() { const [fileURL, setFileURL] = useState(''); @@ -33,10 +32,7 @@ function Default() { styles.justifyContentCenter, ]} > - + {fileURL ? ( { const file = lodashGet(e, ['dataTransfer', 'files', 0]); if (file && file.type.includes('image')) { From da885c4d85941d631536f5120c35ab5b34923016 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 13:48:29 -0700 Subject: [PATCH 22/37] Create useDragAndDrop hook to DRY things up --- src/components/DragAndDrop/Provider/index.js | 105 ++--------------- src/hooks/useDragAndDrop.js | 117 +++++++++++++++++++ 2 files changed, 126 insertions(+), 96 deletions(-) create mode 100644 src/hooks/useDragAndDrop.js diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index 40b2b17d021a..850c45342a27 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -1,18 +1,11 @@ import _ from 'underscore'; -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; -import {useIsFocused} from '@react-navigation/native'; import {PortalHost} from '@gorhom/portal'; import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; import DNDUtils from '../Utils'; import styles from '../../../styles/styles'; - -const COPY_DROP_EFFECT = 'copy'; -const NONE_DROP_EFFECT = 'none'; -const DRAG_ENTER_EVENT = 'dragenter'; -const DRAG_OVER_EVENT = 'dragover'; -const DRAG_LEAVE_EVENT = 'dragleave'; -const DROP_EVENT = 'drop'; +import useDragAndDrop from '../../../hooks/useDragAndDrop'; /** * @param {Event} event – drag event @@ -31,95 +24,15 @@ function DragAndDropProvider({children, dropZoneID, isDisabled = false}) { [dropZoneID], ); - const isFocused = useIsFocused(); - const dropZone = useRef(null); - const dragCounter = useRef(0); - - const [isDraggingOver, setIsDraggingOver] = useState(false); - - // If this component is out of focus or disabled, reset the drag state back to the default - useEffect(() => { - if (isFocused && !isDisabled) { - return; - } - dragCounter.current = 0; - setIsDraggingOver(false); - }, [isFocused, isDisabled]); - - /** - * Handles all types of drag-N-drop events on the drop zone associated with composer - * - * @param {Object} event native Event - */ - const dropZoneDragHandler = useCallback( - (event) => { - if (!isFocused || isDisabled) { - return; - } - - event.preventDefault(); - - if (shouldAcceptDrop(event)) { - switch (event.type) { - case DRAG_OVER_EVENT: - // Nothing needed here, just needed to preventDefault in order for the drop event to fire later - break; - case DRAG_ENTER_EVENT: - dragCounter.current++; - if (isDraggingOver) { - return; - } - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = COPY_DROP_EFFECT; - setIsDraggingOver(true); - break; - case DRAG_LEAVE_EVENT: - dragCounter.current--; - if (!isDraggingOver || dragCounter.current > 0) { - return; - } - - setIsDraggingOver(false); - break; - case DROP_EVENT: - dragCounter.current = 0; - setIsDraggingOver(false); - DNDUtils.executeOnDropCallbacks(event, dropZoneID); - break; - default: - break; - } - } else { - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = NONE_DROP_EFFECT; - } + const {isDraggingOver} = useDragAndDrop({ + dropZoneElement: dropZone.current, + onDrop: (event) => { + DNDUtils.executeOnDropCallbacks(event, dropZoneID); }, - [isFocused, isDisabled, isDraggingOver, dropZoneID], - ); - - useEffect(() => { - if (!dropZone.current) { - return; - } - - // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired: - // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome - dropZone.current.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - dropZone.current.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - dropZone.current.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - dropZone.current.addEventListener(DROP_EVENT, dropZoneDragHandler); - return () => { - if (!dropZone.current) { - return; - } - - dropZone.current.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - dropZone.current.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - dropZone.current.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - dropZone.current.removeEventListener(DROP_EVENT, dropZoneDragHandler); - }; - }, [dropZoneDragHandler]); + shouldAcceptDrop, + isDisabled, + }); return ( diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js new file mode 100644 index 000000000000..50179a6fe885 --- /dev/null +++ b/src/hooks/useDragAndDrop.js @@ -0,0 +1,117 @@ +import _ from 'underscore'; +import {useEffect, useRef, useState, useCallback} from 'react'; +import {useIsFocused} from '@react-navigation/native'; + +const COPY_DROP_EFFECT = 'copy'; +const NONE_DROP_EFFECT = 'none'; +const DRAG_ENTER_EVENT = 'dragenter'; +const DRAG_OVER_EVENT = 'dragover'; +const DRAG_LEAVE_EVENT = 'dragleave'; +const DROP_EVENT = 'drop'; + +export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}) { + const isFocused = useIsFocused(); + const [isDraggingOver, setIsDraggingOver] = useState(false); + + // This solution is borrowed from this SO: https://stackoverflow.com/questions/7110353/html5-dragleave-fired-when-hovering-a-child-element + // This is necessary because dragging over children will cause dragleave to execute on the parent. + // You can think of this counter as a stack. When a child is hovered over we push an element onto the stack. + // Then we only process the dragleave event if the count is 0, because it means that the last element (the parent) has been popped off the stack. + const dragCounter = useRef(0); + + // If this component is out of focus or disabled, reset the drag state back to the default + useEffect(() => { + if (isFocused && !isDisabled) { + return; + } + dragCounter.current = 0; + setIsDraggingOver(false); + }, [isFocused, isDisabled]); + + const onDropRef = useRef(onDrop); + onDropRef.current = onDrop; + const onDropCallback = useCallback((event) => { + if (!_.isFunction(onDropRef.current)) { + return; + } + onDropRef.current(event); + }, []); + + const setDropEffect = useCallback( + (event) => { + // eslint-disable-next-line no-param-reassign + event.dataTransfer.dropEffect = shouldAllowDrop && shouldAcceptDrop(event) ? COPY_DROP_EFFECT : NONE_DROP_EFFECT; + }, + [shouldAllowDrop, shouldAcceptDrop], + ); + + /** + * Handles all types of drag-N-drop events on the drop zone associated with composer + * + * @param {Object} event native Event + */ + const dropZoneDragHandler = useCallback( + (event) => { + if (!isFocused || isDisabled) { + return; + } + + event.preventDefault(); + + switch (event.type) { + case DRAG_OVER_EVENT: + setDropEffect(event); + break; + case DRAG_ENTER_EVENT: + dragCounter.current++; + setDropEffect(event); + if (isDraggingOver) { + return; + } + setIsDraggingOver(true); + break; + case DRAG_LEAVE_EVENT: + dragCounter.current--; + if (!isDraggingOver || dragCounter.current > 0) { + return; + } + + setIsDraggingOver(false); + break; + case DROP_EVENT: + dragCounter.current = 0; + setIsDraggingOver(false); + onDropCallback(event); + break; + default: + break; + } + }, + [isFocused, isDisabled, setDropEffect, isDraggingOver, onDropCallback], + ); + + useEffect(() => { + if (!dropZoneElement) { + return; + } + + // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired: + // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome + dropZoneElement.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + dropZoneElement.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + dropZoneElement.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + dropZoneElement.addEventListener(DROP_EVENT, dropZoneDragHandler); + return () => { + if (!dropZoneElement) { + return; + } + + dropZoneElement.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + dropZoneElement.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + dropZoneElement.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + dropZoneElement.removeEventListener(DROP_EVENT, dropZoneDragHandler); + }; + }, [dropZoneElement, dropZoneDragHandler]); + + return {isDraggingOver}; +} From ebfb46ce2eecadb0eade20ebe30bd9fc504c4540 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 13:49:44 -0700 Subject: [PATCH 23/37] Add missing JSDoc --- src/hooks/useDragAndDrop.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js index 50179a6fe885..01777347b3c8 100644 --- a/src/hooks/useDragAndDrop.js +++ b/src/hooks/useDragAndDrop.js @@ -9,6 +9,14 @@ const DRAG_OVER_EVENT = 'dragover'; const DRAG_LEAVE_EVENT = 'dragleave'; const DROP_EVENT = 'drop'; +/** + * @param {Element} dropZoneElement + * @param {Function} [onDrop] + * @param {Boolean} [shouldAllowDrop] + * @param {Boolean} [isDisabled] + * @param {Function} [shouldAcceptDrop] + * @returns {{isDraggingOver: Boolean}} + */ export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}) { const isFocused = useIsFocused(); const [isDraggingOver, setIsDraggingOver] = useState(false); From d510e1eb5a1ca8c96741981bc7e52c369469d8bb Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 14:18:21 -0700 Subject: [PATCH 24/37] useDragAndDrop in NoDropZone --- .../DragAndDrop/NoDropZone/index.js | 37 +++++++++---------- src/hooks/useDragAndDrop.js | 5 ++- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/components/DragAndDrop/NoDropZone/index.js b/src/components/DragAndDrop/NoDropZone/index.js index f562796ce09a..22d36199ed18 100644 --- a/src/components/DragAndDrop/NoDropZone/index.js +++ b/src/components/DragAndDrop/NoDropZone/index.js @@ -1,29 +1,28 @@ -import {useEffect} from 'react'; +import React, {useRef} from 'react'; +import {View} from 'react-native'; import PropTypes from 'prop-types'; +import styles from '../../../styles/styles'; +import useDragAndDrop from '../../../hooks/useDragAndDrop'; const propTypes = { /** Content */ children: PropTypes.node.isRequired, }; -function NoDropZone(props) { - function handleDrag(event) { - event.preventDefault(); - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = 'none'; - } - - useEffect(() => { - document.addEventListener('dragenter', handleDrag); - document.addEventListener('dragover', handleDrag); - - return () => { - document.removeEventListener('dragenter', handleDrag); - document.removeEventListener('dragover', handleDrag); - }; - }, []); - - return props.children; +function NoDropZone({children}) { + const noDropZone = useRef(null); + useDragAndDrop({ + dropZoneElement: noDropZone.current, + shouldAllowDrop: false, + }); + return ( + (noDropZone.current = e)} + style={[styles.fullScreen]} + > + {children} + + ); } NoDropZone.displayName = 'NoDropZone'; diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js index 01777347b3c8..3459a7985e20 100644 --- a/src/hooks/useDragAndDrop.js +++ b/src/hooks/useDragAndDrop.js @@ -47,8 +47,11 @@ export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shou const setDropEffect = useCallback( (event) => { + const effect = shouldAllowDrop && shouldAcceptDrop(event) ? COPY_DROP_EFFECT : NONE_DROP_EFFECT; // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = shouldAllowDrop && shouldAcceptDrop(event) ? COPY_DROP_EFFECT : NONE_DROP_EFFECT; + event.dataTransfer.dropEffect = effect; + // eslint-disable-next-line no-param-reassign + event.dataTransfer.effectAllowed = effect; }, [shouldAllowDrop, shouldAcceptDrop], ); From d140024fff4ecfaedd75d811a9447d1ddf7a1de8 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 15:21:27 -0700 Subject: [PATCH 25/37] Fix ref inconsistencies which were making listeners get unsubscribed --- .../DragAndDrop/NoDropZone/index.js | 2 +- src/components/DragAndDrop/Provider/index.js | 2 +- src/hooks/useDragAndDrop.js | 26 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/DragAndDrop/NoDropZone/index.js b/src/components/DragAndDrop/NoDropZone/index.js index 22d36199ed18..5573136a3bc7 100644 --- a/src/components/DragAndDrop/NoDropZone/index.js +++ b/src/components/DragAndDrop/NoDropZone/index.js @@ -12,7 +12,7 @@ const propTypes = { function NoDropZone({children}) { const noDropZone = useRef(null); useDragAndDrop({ - dropZoneElement: noDropZone.current, + dropZone: noDropZone, shouldAllowDrop: false, }); return ( diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index 850c45342a27..ee2f4a797f76 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -26,7 +26,7 @@ function DragAndDropProvider({children, dropZoneID, isDisabled = false}) { const dropZone = useRef(null); const {isDraggingOver} = useDragAndDrop({ - dropZoneElement: dropZone.current, + dropZone, onDrop: (event) => { DNDUtils.executeOnDropCallbacks(event, dropZoneID); }, diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js index 3459a7985e20..323f230cc66f 100644 --- a/src/hooks/useDragAndDrop.js +++ b/src/hooks/useDragAndDrop.js @@ -10,14 +10,14 @@ const DRAG_LEAVE_EVENT = 'dragleave'; const DROP_EVENT = 'drop'; /** - * @param {Element} dropZoneElement + * @param {Object} dropZone – ref to the dropZone component * @param {Function} [onDrop] * @param {Boolean} [shouldAllowDrop] * @param {Boolean} [isDisabled] * @param {Function} [shouldAcceptDrop] * @returns {{isDraggingOver: Boolean}} */ -export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}) { +export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}) { const isFocused = useIsFocused(); const [isDraggingOver, setIsDraggingOver] = useState(false); @@ -102,27 +102,27 @@ export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shou ); useEffect(() => { - if (!dropZoneElement) { + if (!dropZone.current) { return; } // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired: // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome - dropZoneElement.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - dropZoneElement.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - dropZoneElement.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - dropZoneElement.addEventListener(DROP_EVENT, dropZoneDragHandler); + dropZone.current.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + dropZone.current.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + dropZone.current.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + dropZone.current.addEventListener(DROP_EVENT, dropZoneDragHandler); return () => { - if (!dropZoneElement) { + if (!dropZone.current) { return; } - dropZoneElement.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - dropZoneElement.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - dropZoneElement.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - dropZoneElement.removeEventListener(DROP_EVENT, dropZoneDragHandler); + dropZone.current.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + dropZone.current.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + dropZone.current.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + dropZone.current.removeEventListener(DROP_EVENT, dropZoneDragHandler); }; - }, [dropZoneElement, dropZoneDragHandler]); + }, [dropZone, dropZoneDragHandler]); return {isDraggingOver}; } From be377dbdf33708160a6b67e0c5ab6a4196b5d555 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 15:34:15 -0700 Subject: [PATCH 26/37] Fix click events in the drop zone --- src/components/DragAndDrop/Provider/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index ee2f4a797f76..fa710000b4d6 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -40,12 +40,14 @@ function DragAndDropProvider({children, dropZoneID, isDisabled = false}) { ref={(e) => (dropZone.current = e)} style={[styles.flex1, styles.w100, styles.h100]} > - - - + {isDraggingOver && ( + + + + )} {children} From f0685c3b594da20fa86c02cbac343cfb1aa55011 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 15:39:01 -0700 Subject: [PATCH 27/37] Fix lint --- src/hooks/useDragAndDrop.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js index 323f230cc66f..a8744e89199c 100644 --- a/src/hooks/useDragAndDrop.js +++ b/src/hooks/useDragAndDrop.js @@ -106,21 +106,23 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow return; } + const dropZoneRef = dropZone.current; + // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired: // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome - dropZone.current.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - dropZone.current.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - dropZone.current.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - dropZone.current.addEventListener(DROP_EVENT, dropZoneDragHandler); + dropZoneRef.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + dropZoneRef.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + dropZoneRef.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + dropZoneRef.addEventListener(DROP_EVENT, dropZoneDragHandler); return () => { - if (!dropZone.current) { + if (!dropZoneRef) { return; } - dropZone.current.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); - dropZone.current.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); - dropZone.current.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); - dropZone.current.removeEventListener(DROP_EVENT, dropZoneDragHandler); + dropZoneRef.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler); + dropZoneRef.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler); + dropZoneRef.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler); + dropZoneRef.removeEventListener(DROP_EVENT, dropZoneDragHandler); }; }, [dropZone, dropZoneDragHandler]); From 4373de1518ab9ded86596152f21399571ba58074 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 16:03:28 -0700 Subject: [PATCH 28/37] Fix tests --- __mocks__/@react-navigation/native/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__mocks__/@react-navigation/native/index.js b/__mocks__/@react-navigation/native/index.js index 7d26028217af..2f00886c30e5 100644 --- a/__mocks__/@react-navigation/native/index.js +++ b/__mocks__/@react-navigation/native/index.js @@ -1,6 +1,6 @@ -function useIsFocused() { - return true; -} +import {useIsFocused as realUseIsFocused} from '@react-navigation/native'; + +const useIsFocused = process.env.NODE_ENV === 'test' ? realUseIsFocused : () => true; export * from '@react-navigation/core'; export * from '@react-navigation/native'; From 8a4dcf5b7a383c364c28c5df1ecd2104279c0e3e Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 16:17:32 -0700 Subject: [PATCH 29/37] Add comment in mock --- __mocks__/@react-navigation/native/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/__mocks__/@react-navigation/native/index.js b/__mocks__/@react-navigation/native/index.js index 2f00886c30e5..09abd0d02bf9 100644 --- a/__mocks__/@react-navigation/native/index.js +++ b/__mocks__/@react-navigation/native/index.js @@ -1,5 +1,6 @@ import {useIsFocused as realUseIsFocused} from '@react-navigation/native'; +// We only want this mocked for storybook, not jest const useIsFocused = process.env.NODE_ENV === 'test' ? realUseIsFocused : () => true; export * from '@react-navigation/core'; From 834791ea06b1c82677bcedaa9555fbd8af0b56f4 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 16:19:25 -0700 Subject: [PATCH 30/37] Rename DNDUtils to DragAndDropUtils --- src/components/DragAndDrop/Consumer/index.js | 8 ++++---- src/components/DragAndDrop/Provider/index.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index 886d10b37fc7..b1dee44e34e0 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -1,10 +1,10 @@ import React, {useContext, useEffect, useRef} from 'react'; import {Portal} from '@gorhom/portal'; import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes'; -import DNDUtils from '../Utils'; +import DragAndDropUtils from '../Utils'; function DragAndDropConsumer({children, dropZoneID, onDrop}) { - const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID); + const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID); const {isDraggingOver} = useContext(DragAndDropContext); const onDropRef = useRef(onDrop); @@ -18,8 +18,8 @@ function DragAndDropConsumer({children, dropZoneID, onDrop}) { } onDropRef.current(e); }; - DNDUtils.registerOnDropCallback(dropZoneID, onDropCallback); - return () => DNDUtils.deregisterOnDropCallback(dropZoneID, onDropCallback); + DragAndDropUtils.registerOnDropCallback(dropZoneID, onDropCallback); + return () => DragAndDropUtils.deregisterOnDropCallback(dropZoneID, onDropCallback); }, [dropZoneID]); if (!isDraggingOver) { diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index fa710000b4d6..d8ca80a17d3d 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -3,7 +3,7 @@ import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; import {PortalHost} from '@gorhom/portal'; import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; -import DNDUtils from '../Utils'; +import DragAndDropUtils from '../Utils'; import styles from '../../../styles/styles'; import useDragAndDrop from '../../../hooks/useDragAndDrop'; @@ -16,10 +16,10 @@ function shouldAcceptDrop(event) { } function DragAndDropProvider({children, dropZoneID, isDisabled = false}) { - const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID, true); + const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID, true); useEffect( () => () => { - DNDUtils.deleteDragAndDropContext(dropZoneID); + DragAndDropUtils.deleteDragAndDropContext(dropZoneID); }, [dropZoneID], ); @@ -28,7 +28,7 @@ function DragAndDropProvider({children, dropZoneID, isDisabled = false}) { const {isDraggingOver} = useDragAndDrop({ dropZone, onDrop: (event) => { - DNDUtils.executeOnDropCallbacks(event, dropZoneID); + DragAndDropUtils.executeOnDropCallbacks(event, dropZoneID); }, shouldAcceptDrop, isDisabled, From 578c22b43a8fbee1d1942f769ea22f71c6353941 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 17:05:51 -0700 Subject: [PATCH 31/37] Remove subscriber optimization --- src/components/DragAndDrop/Consumer/index.js | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index b1dee44e34e0..2cd9d11820d0 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -7,20 +7,10 @@ function DragAndDropConsumer({children, dropZoneID, onDrop}) { const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID); const {isDraggingOver} = useContext(DragAndDropContext); - const onDropRef = useRef(onDrop); - onDropRef.current = onDrop; useEffect(() => { - // Internal function ensures that we only register the onDrop listener once for this consumer, - // even if the onDrop function passed in changes - const onDropCallback = (e) => { - if (!onDropRef.current) { - return; - } - onDropRef.current(e); - }; - DragAndDropUtils.registerOnDropCallback(dropZoneID, onDropCallback); - return () => DragAndDropUtils.deregisterOnDropCallback(dropZoneID, onDropCallback); - }, [dropZoneID]); + DragAndDropUtils.registerOnDropCallback(dropZoneID, onDrop); + return () => DragAndDropUtils.deregisterOnDropCallback(dropZoneID, onDrop); + }, [dropZoneID, onDrop]); if (!isDraggingOver) { return null; From ca842680093e056b09eb789de5c1cd7e71965de8 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 17:09:44 -0700 Subject: [PATCH 32/37] Fix lint --- src/components/DragAndDrop/Consumer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index 2cd9d11820d0..2a4161b92630 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -1,4 +1,4 @@ -import React, {useContext, useEffect, useRef} from 'react'; +import React, {useContext, useEffect} from 'react'; import {Portal} from '@gorhom/portal'; import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes'; import DragAndDropUtils from '../Utils'; From 8a028bb91aaff4b46e742e5e29e88542b694db90 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 19:59:45 -0700 Subject: [PATCH 33/37] Simplify context implementation --- .../Consumer/dragAndDropConsumerPropTypes.js | 3 -- src/components/DragAndDrop/Consumer/index.js | 12 +++--- .../Provider/dragAndDropProviderPropTypes.js | 3 -- src/components/DragAndDrop/Provider/index.js | 37 +++++++++---------- .../DragAndDrop/Provider/index.native.js | 3 ++ src/hooks/useDragAndDrop.js | 14 +------ 6 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js index d039c766b10a..66f8ed996e2e 100644 --- a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js +++ b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js @@ -4,9 +4,6 @@ export default { /** Children to render inside this component. */ children: PropTypes.node.isRequired, - /** String ID to identify the dropZone. It should match the dropZoneID of the associated provider. */ - dropZoneID: PropTypes.string.isRequired, - /** Function to execute when an item is dropped in the drop zone. */ onDrop: PropTypes.func.isRequired, }; diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index 2a4161b92630..ce3ac92c7885 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -1,16 +1,14 @@ import React, {useContext, useEffect} from 'react'; import {Portal} from '@gorhom/portal'; import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes'; -import DragAndDropUtils from '../Utils'; +import {DragAndDropContext} from '../Provider'; -function DragAndDropConsumer({children, dropZoneID, onDrop}) { - const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID); - const {isDraggingOver} = useContext(DragAndDropContext); +function DragAndDropConsumer({children, onDrop}) { + const {isDraggingOver, setOnDropListener, dropZoneID} = useContext(DragAndDropContext); useEffect(() => { - DragAndDropUtils.registerOnDropCallback(dropZoneID, onDrop); - return () => DragAndDropUtils.deregisterOnDropCallback(dropZoneID, onDrop); - }, [dropZoneID, onDrop]); + setOnDropListener(onDrop); + }, [onDrop, setOnDropListener]); if (!isDraggingOver) { return null; diff --git a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js index e33a47474517..d9cc806e9012 100644 --- a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js +++ b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js @@ -4,9 +4,6 @@ export default { /** Children to render inside this component. */ children: PropTypes.node.isRequired, - /** ID for the drop zone. */ - dropZoneID: PropTypes.string.isRequired, - /** Should this dropZone be disabled? */ isDisabled: PropTypes.bool, }; diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index d8ca80a17d3d..18374593a3c7 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -1,12 +1,14 @@ import _ from 'underscore'; -import React, {useEffect, useRef} from 'react'; +import React, {useRef, useCallback} from 'react'; import {View} from 'react-native'; import {PortalHost} from '@gorhom/portal'; +import Str from 'expensify-common/lib/str'; import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; -import DragAndDropUtils from '../Utils'; import styles from '../../../styles/styles'; import useDragAndDrop from '../../../hooks/useDragAndDrop'; +const DragAndDropContext = React.createContext({}); + /** * @param {Event} event – drag event * @returns {Boolean} @@ -15,37 +17,31 @@ function shouldAcceptDrop(event) { return _.some(event.dataTransfer.types, (type) => type === 'Files'); } -function DragAndDropProvider({children, dropZoneID, isDisabled = false}) { - const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID, true); - useEffect( - () => () => { - DragAndDropUtils.deleteDragAndDropContext(dropZoneID); - }, - [dropZoneID], - ); - +function DragAndDropProvider({children, isDisabled = false}) { const dropZone = useRef(null); + const dropZoneID = useRef(Str.guid('drag-n-drop')); + + const onDropListener = useRef(() => {}); + const setOnDropListener = useCallback((callback) => { + onDropListener.current = callback; + }, []); + const {isDraggingOver} = useDragAndDrop({ dropZone, - onDrop: (event) => { - DragAndDropUtils.executeOnDropCallbacks(event, dropZoneID); - }, + onDrop: onDropListener.current, shouldAcceptDrop, isDisabled, }); return ( - + (dropZone.current = e)} style={[styles.flex1, styles.w100, styles.h100]} > {isDraggingOver && ( - - + + )} {children} @@ -58,3 +54,4 @@ DragAndDropProvider.propTypes = dragAndDropProviderPropTypes; DragAndDropProvider.displayName = 'DragAndDropProvider'; export default DragAndDropProvider; +export {DragAndDropContext}; diff --git a/src/components/DragAndDrop/Provider/index.native.js b/src/components/DragAndDrop/Provider/index.native.js index f2539e37dc8d..4dac66912531 100644 --- a/src/components/DragAndDrop/Provider/index.native.js +++ b/src/components/DragAndDrop/Provider/index.native.js @@ -1,5 +1,7 @@ import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes'; +const DragAndDropContext = {}; + function DragAndDropProvider({children}) { return children; } @@ -8,3 +10,4 @@ DragAndDropProvider.propTypes = dragAndDropProviderPropTypes; DragAndDropProvider.displayName = 'DragAndDropProvider'; export default DragAndDropProvider; +export {DragAndDropContext}; diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js index a8744e89199c..98df70085a72 100644 --- a/src/hooks/useDragAndDrop.js +++ b/src/hooks/useDragAndDrop.js @@ -1,4 +1,3 @@ -import _ from 'underscore'; import {useEffect, useRef, useState, useCallback} from 'react'; import {useIsFocused} from '@react-navigation/native'; @@ -36,15 +35,6 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow setIsDraggingOver(false); }, [isFocused, isDisabled]); - const onDropRef = useRef(onDrop); - onDropRef.current = onDrop; - const onDropCallback = useCallback((event) => { - if (!_.isFunction(onDropRef.current)) { - return; - } - onDropRef.current(event); - }, []); - const setDropEffect = useCallback( (event) => { const effect = shouldAllowDrop && shouldAcceptDrop(event) ? COPY_DROP_EFFECT : NONE_DROP_EFFECT; @@ -92,13 +82,13 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow case DROP_EVENT: dragCounter.current = 0; setIsDraggingOver(false); - onDropCallback(event); + onDrop(event); break; default: break; } }, - [isFocused, isDisabled, setDropEffect, isDraggingOver, onDropCallback], + [isFocused, isDisabled, setDropEffect, isDraggingOver, onDrop], ); useEffect(() => { From 0e90a133ca340739bab1019e77f331a27616d60a Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 20:05:19 -0700 Subject: [PATCH 34/37] Remove unused DragAndDropUtils --- src/CONST.js | 1 - src/components/DragAndDrop/Utils/index.js | 71 ------------------- .../DragAndDrop/Utils/index.native.js | 7 -- src/pages/home/ReportScreen.js | 5 +- src/pages/home/report/ReportDropUI.js | 5 +- src/stories/DragAndDrop.stories.js | 5 +- 6 files changed, 3 insertions(+), 91 deletions(-) delete mode 100644 src/components/DragAndDrop/Utils/index.js delete mode 100644 src/components/DragAndDrop/Utils/index.native.js diff --git a/src/CONST.js b/src/CONST.js index 82a66c53e716..18637d582c31 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -480,7 +480,6 @@ const CONST = { PERSONAL_DETAIL: 'personalDetail', }, REPORT: { - DROP_ZONE_ID: 'report-dropzone', ACTIVE_DROP_NATIVE_ID: 'report-dropzone', MAXIMUM_PARTICIPANTS: 8, SPLIT_REPORTID: '-2', diff --git a/src/components/DragAndDrop/Utils/index.js b/src/components/DragAndDrop/Utils/index.js deleted file mode 100644 index cc9c34c5abbc..000000000000 --- a/src/components/DragAndDrop/Utils/index.js +++ /dev/null @@ -1,71 +0,0 @@ -import {createContext} from 'react'; - -const dragAndDropContexts = {}; -const onDropCallbacks = {}; - -/** - * @param {String} dropZoneID - * @param {Boolean} [shouldCreateNewContext] – should this create a new context for the dropZoneID if one does not already exist? - * @returns {Object} - */ -function getDragAndDropContext(dropZoneID, shouldCreateNewContext = false) { - if (dropZoneID in dragAndDropContexts) { - return dragAndDropContexts[dropZoneID]; - } - - if (!shouldCreateNewContext) { - throw new Error(`Could not find DragAndDrop context with dropZoneID: ${dropZoneID}`); - } - - dragAndDropContexts[dropZoneID] = createContext({}); - return dragAndDropContexts[dropZoneID]; -} - -function deleteDragAndDropContext(dropZoneID) { - delete dragAndDropContexts[dropZoneID]; -} - -/** - * @param {String} dropZoneID - * @param {Function} callback - */ -function registerOnDropCallback(dropZoneID, callback) { - if (!(dropZoneID in onDropCallbacks)) { - onDropCallbacks[dropZoneID] = []; - } - onDropCallbacks[dropZoneID].push(callback); -} - -/** - * @param {String} dropZoneID - * @param {Function} callback - */ -function deregisterOnDropCallback(dropZoneID, callback) { - if (!(dropZoneID in onDropCallbacks)) { - return; - } - const index = onDropCallbacks[dropZoneID].indexOf(callback); - if (index < 0) { - return; - } - onDropCallbacks[dropZoneID] = onDropCallbacks[dropZoneID].splice(index, 1); -} - -/** - * @param {Event} event – onDrop event - * @param {String} dropZoneID – dropZoneID that triggered the event - */ -function executeOnDropCallbacks(event, dropZoneID) { - if (!(dropZoneID in onDropCallbacks)) { - return; - } - onDropCallbacks[dropZoneID].forEach((callback) => callback(event)); -} - -export default { - getDragAndDropContext, - deleteDragAndDropContext, - registerOnDropCallback, - deregisterOnDropCallback, - executeOnDropCallbacks, -}; diff --git a/src/components/DragAndDrop/Utils/index.native.js b/src/components/DragAndDrop/Utils/index.native.js deleted file mode 100644 index 24ca8384d5c4..000000000000 --- a/src/components/DragAndDrop/Utils/index.native.js +++ /dev/null @@ -1,7 +0,0 @@ -export default { - getDragAndDropContext: () => {}, - deleteDragAndDropContext: () => {}, - registerOnDropCallback: () => {}, - deregisterOnDropCallback: () => {}, - executeOnDropCallbacks: () => {}, -}; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 55d8822e4274..66c46da11306 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -347,10 +347,7 @@ class ReportScreen extends React.Component { shouldShowCloseButton /> )} - + { diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js index 201ecadbc6e2..4df6a5958d9a 100644 --- a/src/pages/home/report/ReportDropUI.js +++ b/src/pages/home/report/ReportDropUI.js @@ -17,10 +17,7 @@ const propTypes = { function ReportDropUI({onDrop}) { const {translate} = useLocalize(); return ( - + - + {fileURL ? ( { const file = lodashGet(e, ['dataTransfer', 'files', 0]); if (file && file.type.includes('image')) { From 8972845569f3ddd8ece9a3453eea8b6d0f149335 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 20:06:15 -0700 Subject: [PATCH 35/37] Remove unused constant --- src/CONST.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CONST.js b/src/CONST.js index 18637d582c31..06bc1873431f 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -480,7 +480,6 @@ const CONST = { PERSONAL_DETAIL: 'personalDetail', }, REPORT: { - ACTIVE_DROP_NATIVE_ID: 'report-dropzone', MAXIMUM_PARTICIPANTS: 8, SPLIT_REPORTID: '-2', ACTIONS: { From 8711b494de73bf6921ad6cdfa2722b95d933579a Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 20:06:43 -0700 Subject: [PATCH 36/37] fix lint --- src/pages/home/report/ReportDropUI.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js index 4df6a5958d9a..d742346cd769 100644 --- a/src/pages/home/report/ReportDropUI.js +++ b/src/pages/home/report/ReportDropUI.js @@ -3,7 +3,6 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../../../styles/styles'; import Text from '../../../components/Text'; -import CONST from '../../../CONST'; import DragAndDropConsumer from '../../../components/DragAndDrop/Consumer'; import Icon from '../../../components/Icon'; import * as Expensicons from '../../../components/Icon/Expensicons'; From cce276f4c40ab9f66716b8d8b731684ed5c27eec Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 27 Jul 2023 20:10:05 -0700 Subject: [PATCH 37/37] onDropHandler not onDropListener --- src/components/DragAndDrop/Consumer/index.js | 6 +++--- src/components/DragAndDrop/Provider/index.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js index ce3ac92c7885..cf21afe627f4 100644 --- a/src/components/DragAndDrop/Consumer/index.js +++ b/src/components/DragAndDrop/Consumer/index.js @@ -4,11 +4,11 @@ import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes'; import {DragAndDropContext} from '../Provider'; function DragAndDropConsumer({children, onDrop}) { - const {isDraggingOver, setOnDropListener, dropZoneID} = useContext(DragAndDropContext); + const {isDraggingOver, setOnDropHandler, dropZoneID} = useContext(DragAndDropContext); useEffect(() => { - setOnDropListener(onDrop); - }, [onDrop, setOnDropListener]); + setOnDropHandler(onDrop); + }, [onDrop, setOnDropHandler]); if (!isDraggingOver) { return null; diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index 18374593a3c7..89b0f47a830d 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -21,20 +21,20 @@ function DragAndDropProvider({children, isDisabled = false}) { const dropZone = useRef(null); const dropZoneID = useRef(Str.guid('drag-n-drop')); - const onDropListener = useRef(() => {}); - const setOnDropListener = useCallback((callback) => { - onDropListener.current = callback; + const onDropHandler = useRef(() => {}); + const setOnDropHandler = useCallback((callback) => { + onDropHandler.current = callback; }, []); const {isDraggingOver} = useDragAndDrop({ dropZone, - onDrop: onDropListener.current, + onDrop: onDropHandler.current, shouldAcceptDrop, isDisabled, }); return ( - + (dropZone.current = e)} style={[styles.flex1, styles.w100, styles.h100]}