UNPKG

react-xhr-uploader-emondo

Version:
547 lines (500 loc) 18.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 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; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _ReactCSSTransitionGroup = require('react/lib/ReactCSSTransitionGroup'); var _ReactCSSTransitionGroup2 = _interopRequireDefault(_ReactCSSTransitionGroup); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 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; } var defaultStyles = { root: { border: '1px solid #CACACA', padding: 20 }, dropTargetStyle: { border: '3px dashed #4A90E2', padding: 10, backgroundColor: '#ffffff', cursor: 'pointer' }, dropTargetActiveStyle: { backgroundColor: '#ccffcc' }, placeHolderStyle: { paddingLeft: '20%', paddingRight: '20%', textAlign: 'center' }, uploadButtonStyle: { width: '100%', marginTop: 10, height: 32, alignSelf: 'center', cursor: 'pointer', backgroundColor: '#D9EBFF', border: '1px solid #5094E3', fontSize: 14 }, fileset: { marginTop: 10, paddingTop: 10, paddingBottom: 10, borderTop: '1px solid #CACACA' }, fileDetails: { paddingTop: 10, display: 'flex', alignItems: 'flex-start' }, fileName: { flexGrow: '8' }, fileSize: { 'float': 'right', flexGrow: '2', alignSelf: 'flex-end' }, removeButton: { alignSelf: 'flex-end' }, progress: { marginTop: 10, width: '100%', height: 16, WebkitAppearance: 'none' } }; var XHRUploader = function (_React$Component) { _inherits(XHRUploader, _React$Component); _createClass(XHRUploader, null, [{ key: 'propTypes', get: function get() { return { url: _react2.default.PropTypes.string.isRequired, auto: _react2.default.PropTypes.bool, fieldName: _react2.default.PropTypes.string, buttonLabel: _react2.default.PropTypes.string, dropzoneLabel: _react2.default.PropTypes.string, maxSize: _react2.default.PropTypes.number, chunks: _react2.default.PropTypes.bool, chunkSize: _react2.default.PropTypes.number, localStore: _react2.default.PropTypes.bool, maxFiles: _react2.default.PropTypes.number, encrypt: _react2.default.PropTypes.bool, clearTimeOut: _react2.default.PropTypes.number, filesetTransitionName: _react2.default.PropTypes.string, styles: _react2.default.PropTypes.object, debug: _react2.default.PropTypes.bool, completionHandler: _react2.default.PropTypes.func }; } }, { key: 'defaultProps', get: function get() { return { auto: false, fieldName: 'datafile', buttonLabel: 'Upload', dropzoneLabel: 'Drag and drop your files here or pick them from your computer', maxSize: 25 * 1024 * 1024, chunks: false, chunkSize: 512 * 1024, localStore: false, maxFiles: 1, encrypt: false, clearTimeOut: 3000, filesetTransitionName: 'fileset', styles: defaultStyles, debug: false }; } }]); function XHRUploader(props) { _classCallCheck(this, XHRUploader); var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(XHRUploader).call(this, props)); _this.state = { items: [] }; _this.activeDrag = 0; _this.xhrs = []; return _this; } _createClass(XHRUploader, [{ key: 'onClick', value: function onClick() { this.refs.fileInput.click(); } }, { key: 'onUploadButtonClick', value: function onUploadButtonClick() { this.upload(); } }, { key: 'onFileSelect', value: function onFileSelect() { var _this2 = this; var items = this.filesToItems(this.refs.fileInput.files); this.setState({ items: items }, function () { if (_this2.props.auto) { _this2.upload(); } }); } }, { key: 'onDragEnter', value: function onDragEnter(e) { e.preventDefault(); this.activeDrag += 1; this.setState({ isActive: this.activeDrag > 0 }); } }, { key: 'onDragOver', value: function onDragOver(e) { e.preventDefault(); e.stopPropagation(); return false; } }, { key: 'onDragLeave', value: function onDragLeave(e) { e.preventDefault(); this.activeDrag -= 1; if (this.activeDrag === 0) { this.setState({ isActive: false }); } } }, { key: 'onDrop', value: function onDrop(e) { var _this3 = this; e.preventDefault(); this.activeDrag = 0; this.setState({ isActive: false }); var droppedFiles = e.dataTransfer ? e.dataTransfer.files : []; var items = this.filesToItems(droppedFiles); this.setState({ items: items }, function () { if (_this3.props.auto) { _this3.upload(); } }); } }, { key: 'clearIfAllCompleted', value: function clearIfAllCompleted() { var _this4 = this; if (this.props.clearTimeOut > 0) { var completed = this.state.items.filter(function (item) { return item.progress === 100; }).length; if (completed === this.state.items.length) { setTimeout(function () { var items = _this4.state.items; for (var itemIndex = 0; itemIndex < items.length; itemIndex++) { _this4.props.completionHandler(items[itemIndex].response); } _this4.setState({ items: [] }); }, this.props.clearTimeOut); } } } }, { key: 'updateFileProgress', value: function updateFileProgress(index, progress) { var newItems = [].concat(_toConsumableArray(this.state.items)); newItems[index] = Object.assign({}, this.state.items[index], { progress: progress }); this.setState({ items: newItems }, this.clearIfAllCompleted); } }, { key: 'updateFileChunkProgress', value: function updateFileChunkProgress(index, chunkIndex, progress) { var newItems = [].concat(_toConsumableArray(this.state.items)); var currentItem = this.state.items[index]; var newProgressArr = [].concat(_toConsumableArray(currentItem.chunkProgress)); var totalProgress = newProgressArr.reduce(function (a, b) { return a + b; }) / newProgressArr.length; newProgressArr[chunkIndex] = progress; newItems[index] = Object.assign({}, currentItem, { chunkProgress: newProgressArr, progress: totalProgress }); this.setState({ items: newItems }, this.clearIfAllCompleted); } }, { key: 'cancelFile', value: function cancelFile(index) { var newItems = [].concat(_toConsumableArray(this.state.items)); newItems[index] = Object.assign({}, this.state.items[index], { cancelled: true }); if (this.xhrs[index]) { this.xhrs[index].upload.removeEventListener('progress'); this.xhrs[index].removeEventListener('load'); this.xhrs[index].abort(); } this.setState({ items: newItems }); } }, { key: 'upload', value: function upload() { var _this5 = this; var items = this.state.items; if (items) { items.filter(function (item) { return !item.cancelled; }).forEach(function (item) { _this5.uploadItem(item); }); } } }, { key: 'uploadItem', value: function uploadItem(item) { var _this6 = this; if (this.props.chunks) { var BYTES_PER_CHUNK = this.props.chunkSize; var SIZE = item.file.size; var start = 0; var end = BYTES_PER_CHUNK; var chunkProgressHandler = function chunkProgressHandler(percentage, chunkIndex) { _this6.updateFileChunkProgress(item.index, chunkIndex, percentage); }; var chunkIndex = 0; while (start < SIZE) { this.uploadChunk(item, start, end, chunkIndex++, item.file.name, chunkProgressHandler); start = end; end = start + BYTES_PER_CHUNK; } } else { this.uploadFile(item, function (progress) { _this6.updateFileProgress(item.index, progress); }); } } }, { key: 'receiveResult', value: function receiveResult(xhr, index) { var _this7 = this; xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { var result = JSON.parse(xhr.responseText); if (result) { var newItems = [].concat(_toConsumableArray(_this7.state.items)); var currentItem = _this7.state.items[index]; newItems[index] = Object.assign({}, currentItem, { response: result }); _this7.setState({ items: newItems }); } } }; } }, { key: 'uploadChunk', value: function uploadChunk(item, start, end, chunkIndex, fileName, progressCallback) { if (item) { var blob = item.file.slice(start, end); var formData = new FormData(); var xhr = new XMLHttpRequest(); formData.append(this.props.fieldName, blob, fileName + '-chunk' + chunkIndex); xhr.onload = function () { progressCallback(100, chunkIndex); }; xhr.upload.onprogress = function (e) { if (e.lengthComputable) { progressCallback(e.loaded / e.total * 100, chunkIndex); } }; this.receiveResult(xhr, item.index); xhr.open('POST', this.props.url, true); xhr.send(formData); } } }, { key: 'uploadFile', value: function uploadFile(item, progressCallback) { if (item) { var formData = new FormData(); var xhr = new XMLHttpRequest(); var file = item.file; formData.append(this.props.fieldName, file, file.name); xhr.onload = function () { progressCallback(100); }; this.receiveResult(xhr, item.index); xhr.upload.onprogress = function (e) { if (e.lengthComputable) { progressCallback(e.loaded / e.total * 100); } }; xhr.open('POST', this.props.url, true); xhr.send(formData); this.xhrs[file.index] = xhr; } } }, { key: 'filesToItems', value: function filesToItems(files) { var _this8 = this; var fileItems = Array.prototype.slice.call(files).slice(0, this.props.maxFiles); var items = fileItems.map(function (f, i) { if (_this8.props.chunks) { var chunkProgress = []; for (var j = 0; j <= f.size / _this8.props.chunkSize; j++) { chunkProgress.push(0); } return { file: f, index: i, progress: 0, cancelled: false, chunkProgress: chunkProgress, response: undefined }; } return { file: f, index: i, progress: 0, cancelled: false, response: undefined }; }); return items; } }, { key: 'renderDropTarget', value: function renderDropTarget() { var _this9 = this; var styles = this.props.styles; var dropTargetStyle = styles.dropTargetStyle; if (this.state.isActive) { dropTargetStyle = Object.assign({}, dropTargetStyle, styles.dropTargetActiveStyle); } return _react2.default.createElement( 'div', { ref: 'dropTarget', style: dropTargetStyle, onClick: function onClick(e) { return _this9.onClick(e); }, onDragEnter: function onDragEnter(e) { return _this9.onDragEnter(e); }, onDragOver: function onDragOver(e) { return _this9.onDragOver(e); }, onDragLeave: function onDragLeave(e) { return _this9.onDragLeave(e); }, onDrop: function onDrop(e) { return _this9.onDrop(e); } }, _react2.default.createElement( 'div', { style: styles.placeHolderStyle }, _react2.default.createElement( 'p', null, this.props.dropzoneLabel ), _react2.default.createElement('center', { className: 'icon-upload icon-large' }) ) ); } }, { key: 'renderFileSet', value: function renderFileSet() { var _this10 = this; var items = this.state.items; var transitionName = this.props.filesetTransitionName; if (items.length > 0) { var _ret = function () { var styles = _this10.props.styles; var progress = _this10.state.progress; return { v: _react2.default.createElement( _ReactCSSTransitionGroup2.default, { component: 'div', transitionName: transitionName, transitionEnterTimeout: 0, transitionLeaveTimeout: 0 }, _react2.default.createElement( 'div', { style: styles.fileset }, items.filter(function (item) { return !item.cancelled; }).map(function (item) { var file = item.file; var sizeInMB = (file.size / (1024 * 1024)).toPrecision(2); var actionButtonClass = item.progress < 100 ? 'icon-cancel-circle icon-button icon-red' : 'icon-checkmark icon-button icon-green'; return _react2.default.createElement( 'div', { key: item.index }, _react2.default.createElement( 'div', { style: styles.fileDetails }, _react2.default.createElement( 'span', { className: 'icon-file icon-large' }, ' ' ), _react2.default.createElement( 'span', { style: styles.fileName }, file.name + ', ' + file.type ), _react2.default.createElement( 'span', { style: styles.fileSize }, sizeInMB + ' Mb' ), _react2.default.createElement('span', { style: styles.removeButton, className: actionButtonClass, onClick: function onClick() { return _this10.cancelFile(item.index); } }) ), _react2.default.createElement( 'div', null, _react2.default.createElement( 'progress', { style: styles.progress, min: '0', max: '100', value: item.progress }, item.progress, '%' ) ) ); }) ) ) }; }(); if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; } return _react2.default.createElement(_ReactCSSTransitionGroup2.default, { component: 'div', transitionName: transitionName, transitionEnterTimeout: 0, transitionLeaveTimeout: 0 }); } }, { key: 'renderButton', value: function renderButton() { var _this11 = this; var styles = this.props.styles; var displayButton = !this.props.auto; if (displayButton) { return _react2.default.createElement( 'button', { style: styles.uploadButtonStyle, onClick: function onClick() { return _this11.upload(); } }, this.props.buttonLabel ); } return null; } }, { key: 'renderInput', value: function renderInput() { var _this12 = this; var maxFiles = this.props.maxFiles; return _react2.default.createElement('input', { style: { display: 'none' }, multiple: maxFiles > 1, type: 'file', ref: 'fileInput', onChange: function onChange() { return _this12.onFileSelect(); } }); } }, { key: 'render', value: function render() { var styles = this.props.styles; return _react2.default.createElement( 'div', { style: styles.root }, this.renderDropTarget(), this.renderFileSet(), this.renderButton(), this.renderInput() ); } }]); return XHRUploader; }(_react2.default.Component); exports.default = XHRUploader;