@lyra/form-builder
Version:
Lyra form builder
323 lines (275 loc) • 11.4 kB
JavaScript
'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;
}