diff --git a/src/CONST.js b/src/CONST.js index 39ef3103d848..0d84a53b6b7b 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -120,6 +120,7 @@ const CONST = { PERSONAL_DETAIL: 'personalDetail', }, REPORT: { + DROP_NATIVE_ID: 'report-dropzone', MAXIMUM_PARTICIPANTS: 8, ACTIONS: { LIMIT: 50, diff --git a/src/components/TextInputFocusable/index.js b/src/components/TextInputFocusable/index.js index ae979787c020..2004ed232e28 100755 --- a/src/components/TextInputFocusable/index.js +++ b/src/components/TextInputFocusable/index.js @@ -5,6 +5,7 @@ import _ from 'underscore'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import Growl from '../../libs/Growl'; import themeColors from '../../styles/themes/default'; +import CONST from '../../CONST'; const propTypes = { /** Maximum number of lines in the text input */ @@ -32,13 +33,16 @@ const propTypes = { /** When the input has cleared whoever owns this input should know about it */ onClear: PropTypes.func, - /** Callback to fire when a file has been dragged into the text input */ + /** 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, - /** Callback to fire when the user is no longer dragging over the text input */ + /** Callback to fire when the user is no longer dragging over the text input & report body */ onDragLeave: PropTypes.func, - /** Callback to fire when a file is dropped on the text input */ + /** Callback to fire when a file is dropped on the text input & report body */ onDrop: PropTypes.func, /** Whether or not this TextInput is disabled. */ @@ -69,12 +73,13 @@ const defaultProps = { onClear: () => {}, style: null, onDragEnter: () => {}, + onDragOver: () => {}, onDragLeave: () => {}, onDrop: () => {}, isDisabled: false, autoFocus: false, forwardedRef: null, - onSelectionChange: () => { }, + onSelectionChange: () => {}, selection: { start: 0, end: 0, @@ -114,6 +119,7 @@ class TextInputFocusable extends React.Component { end: initialValue.length, }; this.saveSelection = this.saveSelection.bind(this); + this.dragNDropListener = this.dragNDropListener.bind(this); } componentDidMount() { @@ -131,10 +137,11 @@ class TextInputFocusable extends React.Component { // listeners here and unbind when the component unmounts if (this.textInput) { // Firefox will not allow dropping unless we call preventDefault on the dragover event - this.textInput.addEventListener('dragover', e => e.preventDefault()); - this.textInput.addEventListener('dragenter', this.props.onDragEnter); - this.textInput.addEventListener('dragleave', this.props.onDragLeave); - this.textInput.addEventListener('drop', this.props.onDrop); + // We listen on document to extend the Drop area beyond Composer + document.addEventListener('dragover', this.dragNDropListener); + document.addEventListener('dragenter', this.dragNDropListener); + document.addEventListener('dragleave', this.dragNDropListener); + document.addEventListener('drop', this.dragNDropListener); this.textInput.addEventListener('paste', this.checkForAttachment.bind(this)); } } @@ -158,10 +165,10 @@ class TextInputFocusable extends React.Component { componentWillUnmount() { if (this.textInput) { - this.textInput.addEventListener('dragover', e => e.preventDefault()); - this.textInput.removeEventListener('dragenter', this.props.onDragEnter); - this.textInput.removeEventListener('dragleave', this.props.onDragLeave); - this.textInput.removeEventListener('drop', this.props.onDrop); + document.removeEventListener('dragover', this.dragNDropListener); + document.removeEventListener('dragenter', this.dragNDropListener); + document.removeEventListener('dragleave', this.dragNDropListener); + document.removeEventListener('drop', this.dragNDropListener); this.textInput.removeEventListener('paste', this.checkForAttachment.bind(this)); } } @@ -182,6 +189,46 @@ class TextInputFocusable extends React.Component { return newNumberOfLines; } + /** + * Handles all types of drag-N-drop events on the composer + * + * @param {Object} e native Event + * @memberof TextInputFocusable + */ + dragNDropListener(e) { + let isOriginComposer = false; + const handler = () => { + switch (e.type) { + case 'dragover': + e.preventDefault(); + this.props.onDragOver(e, isOriginComposer); + break; + case 'dragenter': + e.dataTransfer.dropEffect = 'copy'; + this.props.onDragEnter(e, isOriginComposer); + break; + case 'dragleave': + this.props.onDragLeave(e, isOriginComposer); + break; + case 'drop': + this.props.onDrop(e, isOriginComposer); + break; + default: break; + } + }; + + // We first check if drop target is composer so that it can be highlighted + if (this.textInput.contains(e.target)) { + isOriginComposer = true; + handler(); + return; + } + + if (document.getElementById(CONST.REPORT.DROP_NATIVE_ID).contains(e.target)) { + handler(); + } + } + /** * Keeps track of user cursor position on the Composer * diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 48943c9bffac..d1449ed486aa 100755 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -501,7 +501,16 @@ class ReportActionCompose extends React.Component { placeholderTextColor={themeColors.placeholderText} onChangeText={this.updateComment} onKeyPress={this.triggerHotkeyActions} - onDragEnter={() => this.setState({isDraggingOver: true})} + onDragEnter={(e, isOriginComposer) => { + if (isOriginComposer) { + this.setState({isDraggingOver: true}); + } + }} + onDragOver={(e, isOriginComposer) => { + if (isOriginComposer) { + this.setState({isDraggingOver: true}); + } + }} onDragLeave={() => this.setState({isDraggingOver: false})} onDrop={(e) => { e.preventDefault(); diff --git a/src/pages/home/report/ReportView.js b/src/pages/home/report/ReportView.js index 724452f50b86..8481be201ed8 100644 --- a/src/pages/home/report/ReportView.js +++ b/src/pages/home/report/ReportView.js @@ -9,6 +9,7 @@ import KeyboardSpacer from '../../../components/KeyboardSpacer'; import styles from '../../../styles/styles'; import SwipeableView from '../../../components/SwipeableView'; import ONYXKEYS from '../../../ONYXKEYS'; +import CONST from '../../../CONST'; const propTypes = { /** The ID of the report the selected report */ @@ -29,7 +30,7 @@ const defaultProps = { }; const ReportView = ({reportID, session}) => ( - + {session.shouldShowComposeInput && (