UNPKG

@lyra/form-builder

Version:
370 lines (304 loc) 12.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _uniq2 = require('lodash/uniq'); var _uniq3 = _interopRequireDefault(_uniq2); 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 _class, _temp, _initialiseProps; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _generateHelpUrl = require('@lyra/generate-help-url'); var _generateHelpUrl2 = _interopRequireDefault(_generateHelpUrl); var _default = require('part:@lyra/components/formfields/default'); var _default2 = _interopRequireDefault(_default); var _withPatchSubscriber = require('../../utils/withPatchSubscriber'); var _withPatchSubscriber2 = _interopRequireDefault(_withPatchSubscriber); var _PatchEvent = require('../../PatchEvent'); var _InvalidValueInput = require('../InvalidValueInput'); var _InvalidValueInput2 = _interopRequireDefault(_InvalidValueInput); var _resolveTypeName = require('../../utils/resolveTypeName'); var _Input = require('./Input'); var _Input2 = _interopRequireDefault(_Input); var _createSelectionOperation = require('./utils/createSelectionOperation'); var _createSelectionOperation2 = _interopRequireDefault(_createSelectionOperation); var _changeToPatches = require('./utils/changeToPatches'); var _changeToPatches2 = _interopRequireDefault(_changeToPatches); var _deserialize = require('./utils/deserialize'); var _deserialize2 = _interopRequireDefault(_deserialize); var _patchesToChange = require('./utils/patchesToChange'); var _patchesToChange2 = _interopRequireDefault(_patchesToChange); var _SyncWrapper = require('./styles/SyncWrapper.css'); var _SyncWrapper2 = _interopRequireDefault(_SyncWrapper); 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 {Block, BlockArrayType, SlateValue, Marker} from './typeDefs'*/ function findBlockType(type) { return type.of.find(ofType => ofType.name === 'block'); } function isDeprecatedBlockSchema(type) { const blockType = findBlockType(type); if (blockType.span !== undefined) { return 'deprecatedSpan'; } if (type.of.find(memberType => memberType.options && memberType.options.inline)) { return 'deprecatedInline'; } return false; } function isDeprecatedBlockValue(value) { if (!value || !Array.isArray(value)) { return false; } const block = value.find(item => item._type === 'block'); if (block && Object.keys(block).includes('spans')) { return true; } return false; } function isInvalidBlockValue(value) { if (Array.isArray(value)) { return false; } if (typeof value === 'undefined') { return false; } return true; } /*:: type Props = { focusPath: [], markers: Marker[], onBlur: (nextPath: []) => void, onChange: PatchEvent => void, onFocus: (nextPath: []) => void, onPaste?: ( event: SyntheticEvent, path: [], type: Type, value: ?Value ) => {insert?: Value, path?: []}, onPatch: (event: PatchEvent) => void, readOnly?: boolean, renderBlockActions?: (block: Block) => React.Node, renderCustomMarkers?: (Marker[]) => React.Node, schema: Schema, subscribe: (() => void) => void, type: BlockArrayType, value: Block[] }*/ /*:: type State = { deprecatedSchema: boolean, deprecatedBlockValue: boolean, invalidBlockValue: boolean, editorValue: SlateValue, initialFocusPath?: [] }*/ exports.default = (0, _withPatchSubscriber2.default)((_temp = _class = class SyncWrapper extends _react2.default.PureComponent /*:: <Props, State>*/ { constructor(props) { super(props); _initialiseProps.call(this); const value = props.value, type = props.type; const deprecatedSchema = isDeprecatedBlockSchema(type); const deprecatedBlockValue = isDeprecatedBlockValue(value); const invalidBlockValue = isInvalidBlockValue(value); this.state = { deprecatedSchema, deprecatedBlockValue, invalidBlockValue, initialFocusPath: props.focusPath, editorValue: deprecatedSchema || deprecatedBlockValue || invalidBlockValue ? (0, _deserialize2.default)([], type) : (0, _deserialize2.default)(value, type) }; this.unsubscribe = props.subscribe(this.handleDocumentPatches); } // Keep track of what the editor value is (as seen in the editor) before it is changed by something. componentDidMount() { if (this.state.initialFocusPath) { setTimeout(() => { this.props.onFocus(this.state.initialFocusPath); }, 0); } } focus() { if (this._input) { this._input.focus(); } } // eslint-disable-next-line complexity componentWillUnmount() { this.unsubscribe(); } // eslint-disable-next-line complexity render() { var _state = this.state; const editorValue = _state.editorValue, deprecatedSchema = _state.deprecatedSchema, deprecatedBlockValue = _state.deprecatedBlockValue, invalidBlockValue = _state.invalidBlockValue; var _props = this.props; const onChange = _props.onChange, rest = _objectWithoutProperties(_props, ['onChange']); var _props2 = this.props; const type = _props2.type, value = _props2.value; const isDeprecated = deprecatedSchema || deprecatedBlockValue; return _react2.default.createElement( 'div', { className: _SyncWrapper2.default.root }, !isDeprecated && !invalidBlockValue && _react2.default.createElement(_Input2.default, _extends({ editorValue: editorValue, onChange: this.handleEditorChange, onPatch: this.handleFormBuilderPatch, undoRedoStack: this._undoRedoStack, ref: this.refInput }, rest)), invalidBlockValue && _react2.default.createElement(_InvalidValueInput2.default, { validTypes: type.of.map(mType => mType.name), actualType: (0, _resolveTypeName.resolveTypeName)(value), value: value, onChange: this.handleInvalidValue }), isDeprecated && _react2.default.createElement( _default2.default, { label: type.title }, _react2.default.createElement( 'div', { className: _SyncWrapper2.default.disabledEditor }, _react2.default.createElement( 'strong', null, 'Heads up!' ), _react2.default.createElement( 'p', null, 'You\'re using a new version of the Studio with', deprecatedSchema && " a block schema that hasn't been updated.", deprecatedSchema && deprecatedBlockValue && ' Also block text needs to be updated.', deprecatedBlockValue && !deprecatedSchema && " block text that hasn't been updated." ), deprecatedSchema === 'deprecatedInline' && _react2.default.createElement( 'p', null, _react2.default.createElement( 'a', { href: (0, _generateHelpUrl2.default)('migrate-to-block-inline-types'), rel: 'noopener noreferrer', target: '_blank' }, 'Migrate schema to block.children inline types' ) ), deprecatedSchema === 'deprecatedSpan' && _react2.default.createElement( 'p', null, _react2.default.createElement( 'a', { href: (0, _generateHelpUrl2.default)('migrate-to-block-children'), rel: 'noopener noreferrer', target: '_blank' }, 'Migrate schema to block.children' ) ) ) ) ); } }, _class.defaultProps = { markers: [] }, _initialiseProps = function _initialiseProps() { this._input = null; this._select = null; this._undoRedoStack = { undo: [], redo: [] }; this._beforeChangeEditorValue = null; this.handleEditorChange = (change /*: SlateChange*/, callback /*: void => void*/) => { var _props3 = this.props; const value = _props3.value, onChange = _props3.onChange, type = _props3.type; this._beforeChangeEditorValue = this.state.editorValue; this.setState({ editorValue: change.value }); const patches = (0, _changeToPatches2.default)(this._beforeChangeEditorValue, change, value, type); this._select = (0, _createSelectionOperation2.default)(change); // Do the change if (patches.length) { onChange(_PatchEvent.PatchEvent.from(patches)); } if (callback) { callback(change); return change; } return change; }; this.handleFormBuilderPatch = (event /*: PatchEvent*/) => { var _props4 = this.props; const onChange = _props4.onChange, type = _props4.type; const editorValue = this.state.editorValue; const change = (0, _patchesToChange2.default)(event.patches, editorValue, null, type); this.setState({ editorValue: change.value }); return onChange(event); }; this.handleDocumentPatches = ({ patches, shouldReset, snapshot }) => { var _props5 = this.props; const type = _props5.type, focusPath = _props5.focusPath; const hasRemotePatches = patches.some(patch => patch.origin === 'remote'); const hasInsertUnsetPatches = patches.some(patch => ['insert', 'unset'].includes(patch.type)); const hasMultipleDestinations = (0, _uniq3.default)(patches.map(patch => patch.path[0] && patch.path[0]._key).filter(Boolean)).length > 1; const hasComplexity = patches.length > 3; // Some heuristics for when we should set a new state or just trust that the editor // state is in sync with the formbuilder value. As setting a new state may be a performance // hog, we don't want to do it for the most basic changes (like entering a new character). // TODO: force sync the state every now and then just to be 100% sure we are in sync. const shouldSetNewState = hasRemotePatches || hasInsertUnsetPatches || hasMultipleDestinations || hasComplexity || shouldReset; const localPatches = patches.filter(patch => patch.origin === 'local'); // Handle undo/redo if (localPatches.length) { const lastPatch = localPatches.slice(-1)[0]; // Until the FormBuilder can support some kind of patch tagging, // we create a void patch with key 'undoRedoVoidPatch' in changesToPatches // to know if this is undo/redo operation or not. const isUndoRedoPatch = lastPatch && lastPatch.path[0] && lastPatch.path[0]._key === 'undoRedoVoidPatch'; if (!isUndoRedoPatch) { this._undoRedoStack.undo.push({ patches: localPatches, // Use the _beforeChangeEditorValue here, because at this point we could be // in the middle of changes, and the state.editorValue may be in a flux state editorValue: this._beforeChangeEditorValue, select: this._select }); // Redo stack must be reset here this._undoRedoStack.redo = []; } } // Set a new editorValue from the snapshot, // and restore the user's selection if (snapshot && shouldSetNewState) { const editorValue = (0, _deserialize2.default)(snapshot, type); const change = editorValue.change(); if (this._select) { // eslint-disable-next-line max-depth try { change.applyOperations([this._select]); } catch (err) { // eslint-disable-next-line max-depth if (!err.message.match('Could not find a descendant')) { console.error(err); // eslint-disable-line no-console } } } // Keep the editor focused as we insert the new value if ((focusPath || []).length === 1) { change.focus(); } this.setState({ editorValue: change.value }); } }; this.refInput = (input /*: Input*/) => { this._input = input; }; this.handleInvalidValue = () => {}; }, _temp));