619 lines
20 KiB
JavaScript
619 lines
20 KiB
JavaScript
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
|
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
|
|
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
|
|
|
|
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
|
|
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
|
|
/* eslint prefer-template: 0 */
|
|
|
|
import React from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { supportMultiple, fileAccepted, allFilesAccepted, fileMatchSize, onDocumentDragOver, getDataTransferItems } from './utils';
|
|
import styles from './utils/styles';
|
|
|
|
var Dropzone = function (_React$Component) {
|
|
_inherits(Dropzone, _React$Component);
|
|
|
|
function Dropzone(props, context) {
|
|
_classCallCheck(this, Dropzone);
|
|
|
|
var _this = _possibleConstructorReturn(this, (Dropzone.__proto__ || Object.getPrototypeOf(Dropzone)).call(this, props, context));
|
|
|
|
_this.renderChildren = function (children, isDragActive, isDragAccept, isDragReject) {
|
|
if (typeof children === 'function') {
|
|
return children(_extends({}, _this.state, {
|
|
isDragActive: isDragActive,
|
|
isDragAccept: isDragAccept,
|
|
isDragReject: isDragReject
|
|
}));
|
|
}
|
|
return children;
|
|
};
|
|
|
|
_this.composeHandlers = _this.composeHandlers.bind(_this);
|
|
_this.onClick = _this.onClick.bind(_this);
|
|
_this.onDocumentDrop = _this.onDocumentDrop.bind(_this);
|
|
_this.onDragEnter = _this.onDragEnter.bind(_this);
|
|
_this.onDragLeave = _this.onDragLeave.bind(_this);
|
|
_this.onDragOver = _this.onDragOver.bind(_this);
|
|
_this.onDragStart = _this.onDragStart.bind(_this);
|
|
_this.onDrop = _this.onDrop.bind(_this);
|
|
_this.onFileDialogCancel = _this.onFileDialogCancel.bind(_this);
|
|
_this.onInputElementClick = _this.onInputElementClick.bind(_this);
|
|
|
|
_this.setRef = _this.setRef.bind(_this);
|
|
_this.setRefs = _this.setRefs.bind(_this);
|
|
|
|
_this.isFileDialogActive = false;
|
|
|
|
_this.state = {
|
|
draggedFiles: [],
|
|
acceptedFiles: [],
|
|
rejectedFiles: []
|
|
};
|
|
return _this;
|
|
}
|
|
|
|
_createClass(Dropzone, [{
|
|
key: 'componentDidMount',
|
|
value: function componentDidMount() {
|
|
var preventDropOnDocument = this.props.preventDropOnDocument;
|
|
|
|
this.dragTargets = [];
|
|
|
|
if (preventDropOnDocument) {
|
|
document.addEventListener('dragover', onDocumentDragOver, false);
|
|
document.addEventListener('drop', this.onDocumentDrop, false);
|
|
}
|
|
this.fileInputEl.addEventListener('click', this.onInputElementClick, false);
|
|
// Tried implementing addEventListener, but didn't work out
|
|
document.body.onfocus = this.onFileDialogCancel;
|
|
}
|
|
}, {
|
|
key: 'componentWillUnmount',
|
|
value: function componentWillUnmount() {
|
|
var preventDropOnDocument = this.props.preventDropOnDocument;
|
|
|
|
if (preventDropOnDocument) {
|
|
document.removeEventListener('dragover', onDocumentDragOver);
|
|
document.removeEventListener('drop', this.onDocumentDrop);
|
|
}
|
|
if (this.fileInputEl != null) {
|
|
this.fileInputEl.removeEventListener('click', this.onInputElementClick, false);
|
|
}
|
|
// Can be replaced with removeEventListener, if addEventListener works
|
|
if (document != null) {
|
|
document.body.onfocus = null;
|
|
}
|
|
}
|
|
}, {
|
|
key: 'composeHandlers',
|
|
value: function composeHandlers(handler) {
|
|
if (this.props.disabled) {
|
|
return null;
|
|
}
|
|
|
|
return handler;
|
|
}
|
|
}, {
|
|
key: 'onDocumentDrop',
|
|
value: function onDocumentDrop(evt) {
|
|
if (this.node && this.node.contains(evt.target)) {
|
|
// if we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
|
|
return;
|
|
}
|
|
evt.preventDefault();
|
|
this.dragTargets = [];
|
|
}
|
|
}, {
|
|
key: 'onDragStart',
|
|
value: function onDragStart(evt) {
|
|
if (this.props.onDragStart) {
|
|
this.props.onDragStart.call(this, evt);
|
|
}
|
|
}
|
|
}, {
|
|
key: 'onDragEnter',
|
|
value: function onDragEnter(evt) {
|
|
evt.preventDefault();
|
|
|
|
// Count the dropzone and any children that are entered.
|
|
if (this.dragTargets.indexOf(evt.target) === -1) {
|
|
this.dragTargets.push(evt.target);
|
|
}
|
|
|
|
this.setState({
|
|
isDragActive: true, // Do not rely on files for the drag state. It doesn't work in Safari.
|
|
draggedFiles: getDataTransferItems(evt)
|
|
});
|
|
|
|
if (this.props.onDragEnter) {
|
|
this.props.onDragEnter.call(this, evt);
|
|
}
|
|
}
|
|
}, {
|
|
key: 'onDragOver',
|
|
value: function onDragOver(evt) {
|
|
// eslint-disable-line class-methods-use-this
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
try {
|
|
// The file dialog on Chrome allows users to drag files from the dialog onto
|
|
// the dropzone, causing the browser the crash when the file dialog is closed.
|
|
// A drop effect of 'none' prevents the file from being dropped
|
|
evt.dataTransfer.dropEffect = this.isFileDialogActive ? 'none' : 'copy'; // eslint-disable-line no-param-reassign
|
|
} catch (err) {
|
|
// continue regardless of error
|
|
}
|
|
|
|
if (this.props.onDragOver) {
|
|
this.props.onDragOver.call(this, evt);
|
|
}
|
|
return false;
|
|
}
|
|
}, {
|
|
key: 'onDragLeave',
|
|
value: function onDragLeave(evt) {
|
|
var _this2 = this;
|
|
|
|
evt.preventDefault();
|
|
|
|
// Only deactivate once the dropzone and all children have been left.
|
|
this.dragTargets = this.dragTargets.filter(function (el) {
|
|
return el !== evt.target && _this2.node.contains(el);
|
|
});
|
|
if (this.dragTargets.length > 0) {
|
|
return;
|
|
}
|
|
|
|
// Clear dragging files state
|
|
this.setState({
|
|
isDragActive: false,
|
|
draggedFiles: []
|
|
});
|
|
|
|
if (this.props.onDragLeave) {
|
|
this.props.onDragLeave.call(this, evt);
|
|
}
|
|
}
|
|
}, {
|
|
key: 'onDrop',
|
|
value: function onDrop(evt) {
|
|
var _this3 = this;
|
|
|
|
var _props = this.props,
|
|
onDrop = _props.onDrop,
|
|
onDropAccepted = _props.onDropAccepted,
|
|
onDropRejected = _props.onDropRejected,
|
|
multiple = _props.multiple,
|
|
disablePreview = _props.disablePreview,
|
|
accept = _props.accept;
|
|
|
|
var fileList = getDataTransferItems(evt);
|
|
var acceptedFiles = [];
|
|
var rejectedFiles = [];
|
|
|
|
// Stop default browser behavior
|
|
evt.preventDefault();
|
|
|
|
// Reset the counter along with the drag on a drop.
|
|
this.dragTargets = [];
|
|
this.isFileDialogActive = false;
|
|
|
|
fileList.forEach(function (file) {
|
|
if (!disablePreview) {
|
|
try {
|
|
file.preview = window.URL.createObjectURL(file); // eslint-disable-line no-param-reassign
|
|
} catch (err) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
console.error('Failed to generate preview for file', file, err); // eslint-disable-line no-console
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fileAccepted(file, accept) && fileMatchSize(file, _this3.props.maxSize, _this3.props.minSize)) {
|
|
acceptedFiles.push(file);
|
|
} else {
|
|
rejectedFiles.push(file);
|
|
}
|
|
});
|
|
|
|
if (!multiple) {
|
|
// if not in multi mode add any extra accepted files to rejected.
|
|
// This will allow end users to easily ignore a multi file drop in "single" mode.
|
|
rejectedFiles.push.apply(rejectedFiles, _toConsumableArray(acceptedFiles.splice(1)));
|
|
}
|
|
|
|
if (onDrop) {
|
|
onDrop.call(this, acceptedFiles, rejectedFiles, evt);
|
|
}
|
|
|
|
if (rejectedFiles.length > 0 && onDropRejected) {
|
|
onDropRejected.call(this, rejectedFiles, evt);
|
|
}
|
|
|
|
if (acceptedFiles.length > 0 && onDropAccepted) {
|
|
onDropAccepted.call(this, acceptedFiles, evt);
|
|
}
|
|
|
|
// Clear files value
|
|
this.draggedFiles = null;
|
|
|
|
// Reset drag state
|
|
this.setState({
|
|
isDragActive: false,
|
|
draggedFiles: [],
|
|
acceptedFiles: acceptedFiles,
|
|
rejectedFiles: rejectedFiles
|
|
});
|
|
}
|
|
}, {
|
|
key: 'onClick',
|
|
value: function onClick(evt) {
|
|
var _props2 = this.props,
|
|
onClick = _props2.onClick,
|
|
disableClick = _props2.disableClick;
|
|
|
|
if (!disableClick) {
|
|
evt.stopPropagation();
|
|
|
|
if (onClick) {
|
|
onClick.call(this, evt);
|
|
}
|
|
|
|
// in IE11/Edge the file-browser dialog is blocking, ensure this is behind setTimeout
|
|
// this is so react can handle state changes in the onClick prop above above
|
|
// see: https://github.com/react-dropzone/react-dropzone/issues/450
|
|
setTimeout(this.open.bind(this), 0);
|
|
}
|
|
}
|
|
}, {
|
|
key: 'onInputElementClick',
|
|
value: function onInputElementClick(evt) {
|
|
evt.stopPropagation();
|
|
if (this.props.inputProps && this.props.inputProps.onClick) {
|
|
this.props.inputProps.onClick();
|
|
}
|
|
}
|
|
}, {
|
|
key: 'onFileDialogCancel',
|
|
value: function onFileDialogCancel() {
|
|
// timeout will not recognize context of this method
|
|
var onFileDialogCancel = this.props.onFileDialogCancel;
|
|
var fileInputEl = this.fileInputEl;
|
|
var isFileDialogActive = this.isFileDialogActive;
|
|
// execute the timeout only if the onFileDialogCancel is defined and FileDialog
|
|
// is opened in the browser
|
|
|
|
if (onFileDialogCancel && isFileDialogActive) {
|
|
setTimeout(function () {
|
|
// Returns an object as FileList
|
|
var FileList = fileInputEl.files;
|
|
if (!FileList.length) {
|
|
isFileDialogActive = false;
|
|
onFileDialogCancel();
|
|
}
|
|
}, 300);
|
|
}
|
|
}
|
|
}, {
|
|
key: 'setRef',
|
|
value: function setRef(ref) {
|
|
this.node = ref;
|
|
}
|
|
}, {
|
|
key: 'setRefs',
|
|
value: function setRefs(ref) {
|
|
this.fileInputEl = ref;
|
|
}
|
|
/**
|
|
* Open system file upload dialog.
|
|
*
|
|
* @public
|
|
*/
|
|
|
|
}, {
|
|
key: 'open',
|
|
value: function open() {
|
|
this.isFileDialogActive = true;
|
|
this.fileInputEl.value = null;
|
|
this.fileInputEl.click();
|
|
}
|
|
}, {
|
|
key: 'render',
|
|
value: function render() {
|
|
var _props3 = this.props,
|
|
accept = _props3.accept,
|
|
acceptClassName = _props3.acceptClassName,
|
|
activeClassName = _props3.activeClassName,
|
|
children = _props3.children,
|
|
disabled = _props3.disabled,
|
|
disabledClassName = _props3.disabledClassName,
|
|
inputProps = _props3.inputProps,
|
|
multiple = _props3.multiple,
|
|
name = _props3.name,
|
|
rejectClassName = _props3.rejectClassName,
|
|
rest = _objectWithoutProperties(_props3, ['accept', 'acceptClassName', 'activeClassName', 'children', 'disabled', 'disabledClassName', 'inputProps', 'multiple', 'name', 'rejectClassName']);
|
|
|
|
var acceptStyle = rest.acceptStyle,
|
|
activeStyle = rest.activeStyle,
|
|
_rest$className = rest.className,
|
|
className = _rest$className === undefined ? '' : _rest$className,
|
|
disabledStyle = rest.disabledStyle,
|
|
rejectStyle = rest.rejectStyle,
|
|
style = rest.style,
|
|
props = _objectWithoutProperties(rest, ['acceptStyle', 'activeStyle', 'className', 'disabledStyle', 'rejectStyle', 'style']);
|
|
|
|
var _state = this.state,
|
|
isDragActive = _state.isDragActive,
|
|
draggedFiles = _state.draggedFiles;
|
|
|
|
var filesCount = draggedFiles.length;
|
|
var isMultipleAllowed = multiple || filesCount <= 1;
|
|
var isDragAccept = filesCount > 0 && allFilesAccepted(draggedFiles, this.props.accept);
|
|
var isDragReject = filesCount > 0 && (!isDragAccept || !isMultipleAllowed);
|
|
var noStyles = !className && !style && !activeStyle && !acceptStyle && !rejectStyle && !disabledStyle;
|
|
|
|
if (isDragActive && activeClassName) {
|
|
className += ' ' + activeClassName;
|
|
}
|
|
if (isDragAccept && acceptClassName) {
|
|
className += ' ' + acceptClassName;
|
|
}
|
|
if (isDragReject && rejectClassName) {
|
|
className += ' ' + rejectClassName;
|
|
}
|
|
if (disabled && disabledClassName) {
|
|
className += ' ' + disabledClassName;
|
|
}
|
|
|
|
if (noStyles) {
|
|
style = styles.default;
|
|
activeStyle = styles.active;
|
|
acceptStyle = style.active;
|
|
rejectStyle = styles.rejected;
|
|
disabledStyle = styles.disabled;
|
|
}
|
|
|
|
var appliedStyle = _extends({}, style);
|
|
if (activeStyle && isDragActive) {
|
|
appliedStyle = _extends({}, style, activeStyle);
|
|
}
|
|
if (acceptStyle && isDragAccept) {
|
|
appliedStyle = _extends({}, appliedStyle, acceptStyle);
|
|
}
|
|
if (rejectStyle && isDragReject) {
|
|
appliedStyle = _extends({}, appliedStyle, rejectStyle);
|
|
}
|
|
if (disabledStyle && disabled) {
|
|
appliedStyle = _extends({}, style, disabledStyle);
|
|
}
|
|
|
|
var inputAttributes = {
|
|
accept: accept,
|
|
disabled: disabled,
|
|
type: 'file',
|
|
style: { display: 'none' },
|
|
multiple: supportMultiple && multiple,
|
|
ref: this.setRefs,
|
|
onChange: this.onDrop,
|
|
autoComplete: 'off'
|
|
};
|
|
|
|
if (name && name.length) {
|
|
inputAttributes.name = name;
|
|
}
|
|
|
|
// Destructure custom props away from props used for the div element
|
|
|
|
var acceptedFiles = props.acceptedFiles,
|
|
preventDropOnDocument = props.preventDropOnDocument,
|
|
disablePreview = props.disablePreview,
|
|
disableClick = props.disableClick,
|
|
onDropAccepted = props.onDropAccepted,
|
|
onDropRejected = props.onDropRejected,
|
|
onFileDialogCancel = props.onFileDialogCancel,
|
|
maxSize = props.maxSize,
|
|
minSize = props.minSize,
|
|
divProps = _objectWithoutProperties(props, ['acceptedFiles', 'preventDropOnDocument', 'disablePreview', 'disableClick', 'onDropAccepted', 'onDropRejected', 'onFileDialogCancel', 'maxSize', 'minSize']);
|
|
|
|
return React.createElement(
|
|
'div',
|
|
_extends({
|
|
className: className,
|
|
style: appliedStyle
|
|
}, divProps /* expand user provided props first so event handlers are never overridden */, {
|
|
onClick: this.composeHandlers(this.onClick),
|
|
onDragStart: this.composeHandlers(this.onDragStart),
|
|
onDragEnter: this.composeHandlers(this.onDragEnter),
|
|
onDragOver: this.composeHandlers(this.onDragOver),
|
|
onDragLeave: this.composeHandlers(this.onDragLeave),
|
|
onDrop: this.composeHandlers(this.onDrop),
|
|
ref: this.setRef,
|
|
'aria-disabled': disabled
|
|
}),
|
|
this.renderChildren(children, isDragActive, isDragAccept, isDragReject),
|
|
React.createElement('input', _extends({}, inputProps /* expand user provided inputProps first so inputAttributes override them */, inputAttributes))
|
|
);
|
|
}
|
|
}]);
|
|
|
|
return Dropzone;
|
|
}(React.Component);
|
|
|
|
export default Dropzone;
|
|
|
|
Dropzone.propTypes = {
|
|
/**
|
|
* Allow specific types of files. See https://github.com/okonet/attr-accept for more information.
|
|
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
|
|
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
|
|
* Windows. In some cases there might not be a mime type set at all.
|
|
* See: https://github.com/react-dropzone/react-dropzone/issues/276
|
|
*/
|
|
accept: PropTypes.string,
|
|
|
|
/**
|
|
* Contents of the dropzone
|
|
*/
|
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
|
|
|
|
/**
|
|
* Disallow clicking on the dropzone container to open file dialog
|
|
*/
|
|
disableClick: PropTypes.bool,
|
|
|
|
/**
|
|
* Enable/disable the dropzone entirely
|
|
*/
|
|
disabled: PropTypes.bool,
|
|
|
|
/**
|
|
* Enable/disable preview generation
|
|
*/
|
|
disablePreview: PropTypes.bool,
|
|
|
|
/**
|
|
* If false, allow dropped items to take over the current browser window
|
|
*/
|
|
preventDropOnDocument: PropTypes.bool,
|
|
|
|
/**
|
|
* Pass additional attributes to the `<input type="file"/>` tag
|
|
*/
|
|
inputProps: PropTypes.object,
|
|
|
|
/**
|
|
* Allow dropping multiple files
|
|
*/
|
|
multiple: PropTypes.bool,
|
|
|
|
/**
|
|
* `name` attribute for the input tag
|
|
*/
|
|
name: PropTypes.string,
|
|
|
|
/**
|
|
* Maximum file size
|
|
*/
|
|
maxSize: PropTypes.number,
|
|
|
|
/**
|
|
* Minimum file size
|
|
*/
|
|
minSize: PropTypes.number,
|
|
|
|
/**
|
|
* className
|
|
*/
|
|
className: PropTypes.string,
|
|
|
|
/**
|
|
* className for active state
|
|
*/
|
|
activeClassName: PropTypes.string,
|
|
|
|
/**
|
|
* className for accepted state
|
|
*/
|
|
acceptClassName: PropTypes.string,
|
|
|
|
/**
|
|
* className for rejected state
|
|
*/
|
|
rejectClassName: PropTypes.string,
|
|
|
|
/**
|
|
* className for disabled state
|
|
*/
|
|
disabledClassName: PropTypes.string,
|
|
|
|
/**
|
|
* CSS styles to apply
|
|
*/
|
|
style: PropTypes.object,
|
|
|
|
/**
|
|
* CSS styles to apply when drag is active
|
|
*/
|
|
activeStyle: PropTypes.object,
|
|
|
|
/**
|
|
* CSS styles to apply when drop will be accepted
|
|
*/
|
|
acceptStyle: PropTypes.object,
|
|
|
|
/**
|
|
* CSS styles to apply when drop will be rejected
|
|
*/
|
|
rejectStyle: PropTypes.object,
|
|
|
|
/**
|
|
* CSS styles to apply when dropzone is disabled
|
|
*/
|
|
disabledStyle: PropTypes.object,
|
|
|
|
/**
|
|
* onClick callback
|
|
* @param {Event} event
|
|
*/
|
|
onClick: PropTypes.func,
|
|
|
|
/**
|
|
* onDrop callback
|
|
*/
|
|
onDrop: PropTypes.func,
|
|
|
|
/**
|
|
* onDropAccepted callback
|
|
*/
|
|
onDropAccepted: PropTypes.func,
|
|
|
|
/**
|
|
* onDropRejected callback
|
|
*/
|
|
onDropRejected: PropTypes.func,
|
|
|
|
/**
|
|
* onDragStart callback
|
|
*/
|
|
onDragStart: PropTypes.func,
|
|
|
|
/**
|
|
* onDragEnter callback
|
|
*/
|
|
onDragEnter: PropTypes.func,
|
|
|
|
/**
|
|
* onDragOver callback
|
|
*/
|
|
onDragOver: PropTypes.func,
|
|
|
|
/**
|
|
* onDragLeave callback
|
|
*/
|
|
onDragLeave: PropTypes.func,
|
|
|
|
/**
|
|
* Provide a callback on clicking the cancel button of the file dialog
|
|
*/
|
|
onFileDialogCancel: PropTypes.func
|
|
};
|
|
|
|
Dropzone.defaultProps = {
|
|
preventDropOnDocument: true,
|
|
disabled: false,
|
|
disablePreview: false,
|
|
disableClick: false,
|
|
multiple: true,
|
|
maxSize: Infinity,
|
|
minSize: 0
|
|
}; |