UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

222 lines (195 loc) 7.55 kB
// Copyright (c) 2020 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /** * Copied from https://github.com/sarink/react-file-drop * For React 16.8 compatibility */ import PropTypes from 'prop-types'; import React from 'react'; import window from 'global/window'; class FileDrop extends React.PureComponent { static isIE = () => window && window.navigator && ((window.navigator.userAgent || []).includes('MSIE') || (window.navigator.appVersion || []).includes('Trident/')); static eventHasFiles = event => { // In most browsers this is an array, but in IE11 it's an Object :( let hasFiles = false; if (event.dataTransfer) { const types = event.dataTransfer.types; for (const keyOrIndex in types) { if (types[keyOrIndex] === 'Files') { hasFiles = true; break; } } } return hasFiles; }; static propTypes = { className: PropTypes.string, targetClassName: PropTypes.string, draggingOverFrameClassName: PropTypes.string, draggingOverTargetClassName: PropTypes.string, onDragOver: PropTypes.func, onDragLeave: PropTypes.func, onDrop: PropTypes.func, dropEffect: PropTypes.oneOf(['copy', 'move', 'link', 'none']), frame: (props, propName, componentName) => { const prop = props[propName]; if (prop === null) { return new Error( `Warning: Required prop \`${propName}\` was not specified in \`${componentName}\`` ); } if (prop !== document && prop !== window && !(prop instanceof HTMLElement)) { return new Error( `Warning: Prop \`${propName}\` must be one of the following: document, HTMLElement!` ); } }, onFrameDragEnter: PropTypes.func, onFrameDragLeave: PropTypes.func, onFrameDrop: PropTypes.func }; static defaultProps = { dropEffect: 'copy', frame: window ? window.document : undefined, className: 'file-drop', targetClassName: 'file-drop-target', draggingOverFrameClassName: 'file-drop-dragging-over-frame', draggingOverTargetClassName: 'file-drop-dragging-over-target' }; constructor(props) { super(props); this.frameDragCounter = 0; this.state = {draggingOverFrame: false, draggingOverTarget: false}; } componentDidMount() { this.startFrameListeners(this.props.frame); this.resetDragging(); window.addEventListener('dragover', this.handleWindowDragOverOrDrop); window.addEventListener('drop', this.handleWindowDragOverOrDrop); } componentDidUpdate(prevProps) { if (prevProps.frame !== this.props.frame) { this.resetDragging(); this.stopFrameListeners(prevProps.frame); this.startFrameListeners(this.props.frame); } } componentWillUnmount() { this.stopFrameListeners(this.props.frame); window.removeEventListener('dragover', this.handleWindowDragOverOrDrop); window.removeEventListener('drop', this.handleWindowDragOverOrDrop); } resetDragging = () => { this.frameDragCounter = 0; this.setState({draggingOverFrame: false, draggingOverTarget: false}); }; handleWindowDragOverOrDrop = event => { // This prevents the browser from trying to load whatever file the user dropped on the window event.preventDefault(); }; handleFrameDrag = event => { // Only allow dragging of files if (!FileDrop.eventHasFiles(event)) return; // We are listening for events on the 'frame', so every time the user drags over any element in the frame's tree, // the event bubbles up to the frame. By keeping count of how many "dragenters" we get, we can tell if they are still // "draggingOverFrame" (b/c you get one "dragenter" initially, and one "dragenter"/one "dragleave" for every bubble) // This is far better than a "dragover" handler, which would be calling `setState` continuously. this.frameDragCounter += event.type === 'dragenter' ? 1 : -1; if (this.frameDragCounter === 1) { this.setState({draggingOverFrame: true}); if (this.props.onFrameDragEnter) this.props.onFrameDragEnter(event); return; } if (this.frameDragCounter === 0) { this.setState({draggingOverFrame: false}); if (this.props.onFrameDragLeave) this.props.onFrameDragLeave(event); return; } }; handleFrameDrop = event => { event.preventDefault(); if (!this.state.draggingOverTarget) { this.resetDragging(); if (this.props.onFrameDrop) this.props.onFrameDrop(event); } }; handleDragOver = event => { if (FileDrop.eventHasFiles(event)) { this.setState({draggingOverTarget: true}); if (!FileDrop.isIE() && this.props.dropEffect) event.dataTransfer.dropEffect = this.props.dropEffect; if (this.props.onDragOver) this.props.onDragOver(event); } }; handleDragLeave = event => { this.setState({draggingOverTarget: false}); if (this.props.onDragLeave) this.props.onDragLeave(event); }; handleDrop = event => { if (this.props.onDrop && FileDrop.eventHasFiles(event)) { const files = event.dataTransfer ? event.dataTransfer.files : null; this.props.onDrop(files, event); } this.resetDragging(); }; stopFrameListeners = frame => { if (frame) { frame.removeEventListener('dragenter', this.handleFrameDrag); frame.removeEventListener('dragleave', this.handleFrameDrag); frame.removeEventListener('drop', this.handleFrameDrop); } }; startFrameListeners = frame => { if (frame) { frame.addEventListener('dragenter', this.handleFrameDrag); frame.addEventListener('dragleave', this.handleFrameDrag); frame.addEventListener('drop', this.handleFrameDrop); } }; render() { const { children, className, targetClassName, draggingOverFrameClassName, draggingOverTargetClassName } = this.props; const {draggingOverTarget, draggingOverFrame} = this.state; let fileDropTargetClassName = targetClassName; if (draggingOverFrame) fileDropTargetClassName += ` ${draggingOverFrameClassName}`; if (draggingOverTarget) fileDropTargetClassName += ` ${draggingOverTargetClassName}`; return ( <div className={className} onDragOver={this.handleDragOver} onDragLeave={this.handleDragLeave} onDrop={this.handleDrop} > <div className={fileDropTargetClassName}>{children}</div> </div> ); } } export default FileDrop;