UNPKG

@lyra/form-builder

Version:
323 lines (275 loc) 11.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _sortBy2 = require('lodash/sortBy'); var _sortBy3 = _interopRequireDefault(_sortBy2); 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; }; exports.createUploadTarget = createUploadTarget; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _default = require('part:@lyra/components/snackbar/default'); var _default2 = _interopRequireDefault(_default); var _default3 = require('part:@lyra/components/buttons/default'); var _default4 = _interopRequireDefault(_default3); var _default5 = require('part:@lyra/components/dialogs/default'); var _default6 = _interopRequireDefault(_default5); var _UploadTarget = require('../../../styles/UploadTarget.css'); var _UploadTarget2 = _interopRequireDefault(_UploadTarget); var _humanizeList = require('humanize-list'); var _humanizeList2 = _interopRequireDefault(_humanizeList); var _extractFiles = require('./extractFiles'); var _imageUrlToBlob = require('./imageUrlToBlob'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 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; } /*:: import type {Path} from '../../../typedefs/path'*/ /*:: import type {Uploader} from '../../../lyra/uploads/typedefs'*/ /*:: import type {Type} from '../../../typedefs/index'*/ /*:: type Props = { type: Type, children: () => {}, className?: string, onFocus: ?(Path) => void, getUploadOptions: (type: Type, file: File) => UploadOption, onUpload?: (type: Type, file: File) => Uploader }*/ /*:: type UploadTask = { file: File, uploaderCandidates: Array<UploadOption> }*/ // this is a hack for Safari that reads pasted image(s) from an ContentEditable div instead of the onpaste event /*:: type State = { rejected: Array<UploadTask>, ambiguous: Array<UploadTask>, isMoving: ?boolean }*/ function convertImagesToFilesAndClearContentEditable(element /*: HTMLDivElement*/, targetFormat = 'image/jpeg') /*: Promise<Array<File>>*/ { if (!element.isContentEditable) { throw new Error(`Expected element to be contentEditable="true". Instead found a non contenteditable ${element.tagName}`); } return new Promise(resolve => setTimeout(resolve, 10)) // add a delay so the paste event can finish .then(() => Array.from(element.querySelectorAll('img'))).then(imageElements => { element.innerHTML = ''; // clear return imageElements; }).then(images => Promise.all(images.map(img => (0, _imageUrlToBlob.imageUrlToBlob)(img.src)))).then(imageBlobs => imageBlobs.map(blob => new File([blob], 'pasted-image.jpg', { type: targetFormat }))); } // needed by Edge function select(el) { const range = document.createRange(); range.selectNodeContents(el); const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } function createUploadTarget(Component) { var _class, _temp2; return _temp2 = _class = class UploadTargetFieldset extends _react2.default.Component /*:: <Props, State>*/ { constructor(...args) { var _temp; return _temp = super(...args), this.dragEnteredEls = [], this.state = { isDraggingOver: false, showPasteInput: false, rejected: [], ambiguous: [] }, this.handleFocus = (event /*: SyntheticEvent<HTMLDivElement>*/) => { const onFocus = this.props.onFocus; event.stopPropagation(); if (onFocus) { onFocus(['$']); } }, this.handleKeyPress = (event /*: SyntheticKeyboardEvent<*>*/) => { if (event.target === _reactDom2.default.findDOMNode(this) && (event.ctrlKey || event.metaKey) && event.key === 'v') { this.setState({ showPasteInput: true }); } }, this.handlePaste = (event /*: SyntheticClipboardEvent<*>*/) => { (0, _extractFiles.extractPastedFiles)(event.clipboardData).then(files => { return files.length > 0 ? files : // Invoke Safari hack convertImagesToFilesAndClearContentEditable(this._pasteInput, 'image/jpeg'); }).then(files => { this.uploadFiles(files); this.setState({ showPasteInput: false }); }); }, this.handleDrop = (event /*: SyntheticDragEvent<*>*/) => { this.setState({ isDraggingOver: false }); event.preventDefault(); event.stopPropagation(); if (this.props.onUpload) { (0, _extractFiles.extractDroppedFiles)(event.nativeEvent.dataTransfer).then(files => { if (files) { this.uploadFiles(files); } }); } }, this.handleDragOver = (event /*: SyntheticDragEvent<*>*/) => { if (this.props.onUpload) { event.preventDefault(); event.stopPropagation(); } }, this.handleDragEnter = (event /*: SyntheticDragEvent<*>*/) => { event.stopPropagation(); this.dragEnteredEls.push(event.target); this.setState({ isDraggingOver: true }); }, this.handleDragLeave = (event /*: SyntheticDragEvent<*>*/) => { event.stopPropagation(); const idx = this.dragEnteredEls.indexOf(event.target); if (idx > -1) { this.dragEnteredEls.splice(idx, 1); } if (this.dragEnteredEls.length === 0) { this.setState({ isDraggingOver: false }); } }, this.setPasteInput = (element /*: ?HTMLInputElement*/) => { // Only care about focus events from children this._pasteInput = element; }, this.setElement = (element /*: ?HTMLDivElement*/) => { // Only care about focus events from children this._element = element; }, _temp; } uploadFiles(files /*: Array<File>*/) { const tasks = files.map(file => ({ file, uploaderCandidates: this.props.getUploadOptions(file) })); const ready = tasks.filter(task => task.uploaderCandidates.length > 0); const rejected = tasks.filter(task => task.uploaderCandidates.length === 0); this.setState({ rejected }); // todo: consider if we need to ask the user // the list of candidates is sorted by their priority and the first one is selected // const ambiguous = tasks // .filter(task => task.uploaderCandidates.length > 1) ready.forEach(task => { this.uploadFile(task.file, (0, _sortBy3.default)(task.uploaderCandidates, cand => cand.uploader.priority)[0]); }); } uploadFile(file /*: File*/, uploadOption /*: UploadOption*/) { const onUpload = this.props.onUpload; const type = uploadOption.type, uploader = uploadOption.uploader; onUpload({ file, type, uploader }); } componentDidUpdate(_, prevState) { if (!prevState.showPasteInput && this.state.showPasteInput) { this._pasteInput.focus(); select(this._pasteInput); // Needed by Edge } else if (prevState.showPasteInput && !this.state.showPasteInput) { this.focus(); } } focus() { if (this._element) { this._element.focus(); } } renderSnacks() { var _state = this.state; const rejected = _state.rejected, ambiguous = _state.ambiguous; return _react2.default.createElement( 'div', null, ambiguous.length > 0 && // not in use right now as we just pick the first uploader _react2.default.createElement( _default6.default, { isOpen: true, title: 'Select how to represent', actions: [{ title: 'Cancel' }], onAction: () => this.setState({ ambiguous: [] }) }, ambiguous.map(task => _react2.default.createElement( 'div', { key: task.file.name }, 'The file ', task.file.name, ' can be converted to several types of content. Please select how you want to represent it:', _react2.default.createElement( 'ul', null, task.uploaderCandidates.map(uploaderCandidate => _react2.default.createElement( 'li', { key: uploaderCandidate.type.name }, _react2.default.createElement( _default4.default, { onClick: () => { this.uploadFile(task.file, uploaderCandidate); this.setState({ ambiguous: ambiguous.filter(t => t !== task) }); } }, 'Represent as ', uploaderCandidate.type.name ) )) ) )) ), rejected.length > 0 && _react2.default.createElement( _default2.default, { kind: 'warning', action: { title: 'OK' }, onAction: () => this.setState({ rejected: [] }) }, 'File(s) not accepted:', (0, _humanizeList2.default)(rejected.map(task => task.file.name)) ) ); } render() { var _props = this.props; const children = _props.children, type = _props.type, onUpload = _props.onUpload, getUploadOptions = _props.getUploadOptions, rest = _objectWithoutProperties(_props, ['children', 'type', 'onUpload', 'getUploadOptions']); var _state2 = this.state; const isDraggingOver = _state2.isDraggingOver, showPasteInput = _state2.showPasteInput; return _react2.default.createElement( Component, _extends({}, rest, { ref: this.setElement, onFocus: this.handleFocus, onKeyDown: this.handleKeyPress, onDragOver: this.handleDragOver, onDragEnter: this.handleDragEnter, onDragLeave: this.handleDragLeave, onDrop: this.handleDrop }), isDraggingOver && _react2.default.createElement( 'div', { className: _UploadTarget2.default.dragStatus }, _react2.default.createElement( 'h2', { className: _UploadTarget2.default.dragStatusInner }, 'Drop to upload' ) ), showPasteInput && _react2.default.createElement( 'div', { className: _UploadTarget2.default.dragStatus }, _react2.default.createElement('div', { contentEditable: true, onPaste: this.handlePaste, className: _UploadTarget2.default.pasteInput, ref: this.setPasteInput }), _react2.default.createElement( 'h2', { className: _UploadTarget2.default.dragStatusInner }, 'Paste (Ctrl+V or \u2318+V) to upload' ) ), children, this.renderSnacks() ); } }, _class.defaultProps = { tabIndex: 0 }, _temp2; }