UNPKG

react-movable-block-editor

Version:

React component for creating layouts and content via drag-and-drop blocks.

1,530 lines (1,363 loc) 80.5 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib'), require('react'), require('react-dnd'), require('react-resizable'), require('lodash-es'), require('react-dnd-html5-backend'), require('react-dnd-touch-backend'), require('react-markdown'), require('react-color')) : typeof define === 'function' && define.amd ? define(['exports', 'tslib', 'react', 'react-dnd', 'react-resizable', 'lodash-es', 'react-dnd-html5-backend', 'react-dnd-touch-backend', 'react-markdown', 'react-color'], factory) : (global = global || self, factory(global['react-movable-block-editor'] = {}, global.tslib_1, global.React, global.reactDnd, global.reactResizable, global.lodashEs, global.Html5Backend, global.TouchBackend, global.MarkDown, global.reactColor)); }(this, function (exports, tslib_1, React, reactDnd, reactResizable, lodashEs, Html5Backend, TouchBackend, MarkDown, reactColor) { 'use strict'; var React__default = 'default' in React ? React['default'] : React; Html5Backend = Html5Backend && Html5Backend.hasOwnProperty('default') ? Html5Backend['default'] : Html5Backend; TouchBackend = TouchBackend && TouchBackend.hasOwnProperty('default') ? TouchBackend['default'] : TouchBackend; MarkDown = MarkDown && MarkDown.hasOwnProperty('default') ? MarkDown['default'] : MarkDown; function updateNode(byId, newNode) { var _a; return tslib_1.__assign({}, byId, (_a = {}, _a[newNode.id] = newNode, _a)); } function removeNode(byId, nodeId) { var _a; var node = byId[nodeId]; if (!node) return byId; if (node && node.parentId) { var parent_1 = byId[node.parentId]; if (parent_1) { var childIdx = parent_1.childrenIds.findIndex(function (id) { return id === node.id; }); if (childIdx >= 0) { var childrenIds = parent_1.childrenIds.filter(function (id) { return id !== node.id; }); byId = updateNode(byId, tslib_1.__assign({}, parent_1, { childrenIds: childrenIds })); } } } return tslib_1.__assign({}, byId, (_a = {}, _a[nodeId] = undefined, _a)); } function hasDescendent(byId, nodeId, descendentId, checked) { if (checked === void 0) { checked = {}; } if (checked[nodeId]) return true; //CYCLE checked[nodeId] = true; var node = byId[nodeId]; if (!node) { return false; //node not found? bad case } if (node.childrenIds.indexOf(descendentId) >= 0) { return true; } for (var _i = 0, _a = node.childrenIds; _i < _a.length; _i++) { var childId = _a[_i]; var descendentOfChild = hasDescendent(byId, childId, descendentId, checked); if (descendentOfChild) return true; } return false; } function placeNodeInParent(byId, node, newParentId, opts) { if (!node) { return byId; } // Prevent placing a parent under a current child (or descendent) to avoid cycles if (hasDescendent(byId, node.id, newParentId)) { console.warn('node ', node.id, ' has ', newParentId, ' as child, so cannot make it into', node.id, "'s parent OR there's a cycle"); return byId; } // Disallow placing within itself if (node.id === newParentId) { console.warn('Cannot place into self: ', { id: node.id, newParentId: newParentId }); return byId; } // if parent changed, remove node from old parent if (node && node.parentId && node.parentId !== newParentId) { var parent_2 = byId[node.parentId]; if (parent_2) { var childrenIds = parent_2.childrenIds.filter(function (id) { return id !== node.id; }); byId = updateNode(byId, tslib_1.__assign({}, parent_2, { childrenIds: childrenIds })); } } var newParent = byId[newParentId]; if (newParent) { var childrenIds = reinsertIntoList(newParent.childrenIds, node.id, opts); byId = updateNode(byId, tslib_1.__assign({}, newParent, { childrenIds: childrenIds })); } return updateNode(byId, tslib_1.__assign({}, node, { parentId: newParentId })); } function reinsertIntoList(ids, itemId, opts) { if (opts === void 0) { opts = {}; } // Disallow placing before/after itself if (itemId === opts.beforeItemId || itemId === opts.afterItemId) { console.warn('Cannot place relative to self: ', tslib_1.__assign({ itemId: itemId }, opts)); return ids; } // remove the item if it's in the list ids = ids.filter(function (id) { return id !== itemId; }); var beforeItemId = opts.beforeItemId, afterItemId = opts.afterItemId; var refItemIdx = -1; if (beforeItemId) { refItemIdx = ids.findIndex(function (id) { return id === beforeItemId; }); } else if (afterItemId) { refItemIdx = ids.findIndex(function (id) { return id === afterItemId; }) + 1; } if (refItemIdx >= 0) { var newItems = ids.slice(); newItems.splice(refItemIdx, 0, itemId); return newItems; } return ids.concat([itemId]); } var cloneDeep = /*#__PURE__*/ require("lodash-es").cloneDeep; function deepCopy(byId, node) { if (!node) return node; return tslib_1.__assign({}, node, { value: cloneDeep(node.value), childrenIds: node.childrenIds.slice(), children: node.childrenIds.map(function (id) { var childNode = byId[id]; return deepCopy(byId, childNode); }) }); } function paste(value) { var byId = value.byId; var copiedNode = (value ? value : { copiedNode: null }).copiedNode; if (!copiedNode) return value; // support pasting same node multiple times - ensure childrenIds in each copy point to different arrays copiedNode = cloneDeep(copiedNode); var newById = pasteChild(byId, copiedNode).byId; // add copied node to parent if (copiedNode.parentId) { var parentNode = byId[copiedNode.parentId]; if (parentNode) { return tslib_1.__assign({}, value, { byId: placeNodeInParent(newById, copiedNode, copiedNode.parentId, { afterItemId: parentNode.childrenIds[parentNode.childrenIds.length - 1] }) }); } } return value; } function pasteChild(byId, copiedNode) { var _a; copiedNode.id = newBlockId(byId, copiedNode.type); var children = copiedNode.children; copiedNode.children = undefined; byId = tslib_1.__assign({}, byId, (_a = {}, _a[copiedNode.id] = copiedNode, _a)); if (children) { var idx = 0; for (var _i = 0, children_1 = children; _i < children_1.length; _i++) { var child = children_1[_i]; child.parentId = copiedNode.id; var _b = pasteChild(byId, child), newById = _b.byId, newChildId = _b.newId; copiedNode.childrenIds[idx] = newChildId; byId = newById; ++idx; } } return { byId: byId, newId: copiedNode.id }; } function newBlockId(byId, prefix) { if (prefix === void 0) { prefix = 'node'; } //TODO better way to create uid var nextId = Object.keys(byId).length; while (!!byId[nextId]) nextId++; return prefix + '_' + nextId; } function create(value, props) { var byId = value.byId; var nodeId = newBlockId(byId); var node = tslib_1.__assign({}, byId[nodeId], props); if (!node) return value; return tslib_1.__assign({}, value, { byId: updateNode(byId, node) }); } function update(value, nodeId, propsToUpdate) { var byId = value.byId; var node = tslib_1.__assign({}, byId[nodeId], propsToUpdate); if (!node) return value; return tslib_1.__assign({}, value, { byId: updateNode(byId, node) }); } function destroy(value, nodeId) { var byId = value.byId, rootNodeId = value.rootNodeId, focusedNodeId = value.focusedNodeId; if (rootNodeId === nodeId) { alert('Cannot destroy root node'); return tslib_1.__assign({}, value, { byId: byId, focusedNodeId: focusedNodeId }); } return tslib_1.__assign({}, value, { byId: removeNode(byId, nodeId), focusedNodeId: focusedNodeId === nodeId ? null : focusedNodeId }); } function move(value, nodeId, targetParentId, opts) { if (opts === void 0) { opts = {}; } if (!targetParentId) return value; var byId = value.byId; var node = byId[nodeId]; return tslib_1.__assign({}, value, { byId: placeNodeInParent(byId, tslib_1.__assign({}, node, { isPlaceHolder: opts.isPlaceHolder }, opts.absolutePos ? { top: opts.absolutePos.top, left: opts.absolutePos.left } : {}), targetParentId, opts) }); } function moveInDirection(value, nodeId, moveOpts) { if (moveOpts === void 0) { moveOpts = {}; } var byId = value.byId; var node = byId[nodeId]; if (!node) return value; var targetParentId = node.parentId; if (!targetParentId) return value; // sanity check var parentNode = byId[targetParentId]; if (!parentNode || !parentNode.parentId) return value; // sanity check var grandParentNode = byId[parentNode.parentId]; // if (!parentNode) return value; // sanity check var curPositionInParent = parentNode.childrenIds.indexOf(nodeId); if (curPositionInParent < 0) return value; // sanity check var opts = {}; if (parentNode.type === 'row') { if (moveOpts.direction === 'left') { if (curPositionInParent === 0) return value; opts = { beforeItemId: parentNode.childrenIds[curPositionInParent - 1] }; } else if (moveOpts.direction === 'right') { if (curPositionInParent >= parentNode.childrenIds.length - 1) return value; opts = { afterItemId: parentNode.childrenIds[curPositionInParent + 1] }; } else { if (grandParentNode && grandParentNode.type === 'col') { var parentPositionInGrandparent = grandParentNode.childrenIds.indexOf(parentNode.id); if (parentPositionInGrandparent > 0 && moveOpts.direction === 'up') { // move to uncle (grandparent's other child) return move(value, nodeId, grandParentNode.childrenIds[parentPositionInGrandparent - 1]); } else if (parentPositionInGrandparent < grandParentNode.childrenIds.length - 1 && moveOpts.direction === 'down') { return move(value, nodeId, grandParentNode.childrenIds[parentPositionInGrandparent + 1]); } else { return value; } } else { return value; } } } else if (parentNode.type === 'col') { if (moveOpts.direction === 'up') { if (curPositionInParent === 0) return value; opts = { beforeItemId: parentNode.childrenIds[curPositionInParent - 1] }; } else if (moveOpts.direction === 'down') { if (curPositionInParent >= parentNode.childrenIds.length - 1) return value; opts = { afterItemId: parentNode.childrenIds[curPositionInParent + 1] }; } else { if (grandParentNode && grandParentNode.type === 'row') { var parentPositionInGrandparent = grandParentNode.childrenIds.indexOf(parentNode.id); if (parentPositionInGrandparent > 0 && moveOpts.direction === 'left') { return move(value, nodeId, grandParentNode.childrenIds[parentPositionInGrandparent - 1]); } else if (parentPositionInGrandparent < grandParentNode.childrenIds.length - 1 && moveOpts.direction === 'right') { // move to uncle (grandparent's other child) return move(value, nodeId, grandParentNode.childrenIds[parentPositionInGrandparent + 1]); } else { return value; } } else { return value; } } } else if (parentNode.type === 'layer') { // move by stepPx if inside layer var topDelta = (moveOpts.direction === 'up' ? -1 : moveOpts.direction === 'down' ? 1 : 0) * (moveOpts.stepPx || 1); var leftDelta = (moveOpts.direction === 'left' ? -1 : moveOpts.direction === 'right' ? 1 : 0) * (moveOpts.stepPx || 1); opts = { absolutePos: { top: (node.top || 0) + topDelta, left: (node.left || 0) + leftDelta } }; } return tslib_1.__assign({}, value, { byId: placeNodeInParent(byId, tslib_1.__assign({}, node, { isPlaceHolder: opts.isPlaceHolder }, opts.absolutePos ? { top: opts.absolutePos.top, left: opts.absolutePos.left } : {}), targetParentId, opts) }); } function focusNode(value, node, focus) { if (focus === void 0) { focus = true; } if (!node) return value; var focusedNodeId = value.focusedNodeId; return tslib_1.__assign({}, value, { focusedNodeId: focus ? node.id : focusedNodeId === node.id ? null : focusedNodeId }); } function parseTypes(types) { var draggedNodeIds = types.filter(function (t) { return t.indexOf('draggednodeid:') === 0; }); var draggedNodeTypes = types.filter(function (t) { return t.indexOf('draggednodetype:') === 0; }); if (draggedNodeIds.length !== 1 || draggedNodeTypes.length !== 1) { return { draggedNodeId: 'notanode', draggedNodeType: null }; } var draggedNodeId = draggedNodeIds[0].slice('draggednodeid:'.length); var draggedNodeType = draggedNodeTypes[0].slice('draggednodetype:'.length); return { draggedNodeId: draggedNodeId, draggedNodeType: draggedNodeType }; } function getDragPositionRelativeToTarget(e, targetRect) { if (!targetRect) return null; return { left: e.clientX - targetRect.left, top: e.clientY - targetRect.top, width: targetRect.width, height: targetRect.height }; } function getDragPositionRelativeToTarget2(_a, targetRect) { var clientX = _a.clientX, clientY = _a.clientY; if (!targetRect) return null; return { left: clientX - targetRect.left, top: clientY - targetRect.top, width: targetRect.width, height: targetRect.height }; } function onDragStart(e, draggedNode, getBoundingRect) { e.stopPropagation(); var relativePos = getDragPositionRelativeToTarget(e, getBoundingRect()); if (relativePos) { e.dataTransfer.setData('text/plain', JSON.stringify({ startLeft: relativePos.left, startTop: relativePos.top })); } e.dataTransfer.setData("draggednodeid:" + draggedNode.id, ''); e.dataTransfer.setData("draggednodetype:" + draggedNode.type, ''); } function onDropped(types, targetNodeId, opts) { if (opts === void 0) { opts = {}; } var draggedNodeId = parseTypes(types).draggedNodeId; return function (value) { return move(value, draggedNodeId, targetNodeId, opts); }; } function onDropped2(draggedNodeId, targetNodeId, opts) { if (opts === void 0) { opts = {}; } return function (value) { return move(value, draggedNodeId, targetNodeId, opts); }; } var LayerBlock = /*#__PURE__*/ /** @class */ function (_super) { tslib_1.__extends(LayerBlock, _super); function LayerBlock() { return _super !== null && _super.apply(this, arguments) || this; } LayerBlock.prototype.renderChild = function (nodeId) { var _a = this.props, changeBlocks = _a.changeBlocks, getNode = _a.getNode, undoRedoVersion = _a.undoRedoVersion, focusedNodeId = _a.focusedNodeId, renderEditBlock = _a.renderEditBlock; var node = getNode(nodeId); if (!node) return null; return renderEditBlock({ node: node, renderEditBlock: renderEditBlock, undoRedoVersion: undoRedoVersion, changeBlocks: changeBlocks, getNode: getNode, focusedNodeId: focusedNodeId }); }; LayerBlock.prototype.render = function () { var _this = this; var _a = this.props, node = _a.node, getNode = _a.getNode; var childrenIds = node.childrenIds; var paddingLeft = (node.paddingLeftPercentWidth !== undefined ? node.paddingLeftPercentWidth : 0.05) * node.width; var paddingTop = (node.paddingTopPercentHeight !== undefined ? node.paddingTopPercentHeight : 0.05) * node.height; return React.createElement("div", { style: { position: 'relative', opacity: this.props.isDragging ? 0.5 : 1, width: node.width, height: node.height, backgroundColor: node.backgroundColor || '#f5f5f5a3', // padding: 5, borderRadius: 3 } }, childrenIds.map(function (childId) { var node = getNode(childId); if (!node) return null; var res = React.createElement("div", { // className="drag-node" key: 'node_' + childId, style: { position: 'absolute', width: node.width, height: node.height, top: paddingTop, left: paddingLeft, transform: "translate(" + node.left + "px," + node.top + "px)" } }, _this.renderChild(childId)); return res; })); }; return LayerBlock; }(React.Component); var AbsoluteLayerBlock = function (props) { var selfRef = React.useRef(null); var _a = reactDnd.useDrag({ item: { type: 'col', id: props.node.id }, collect: function (monitor) { return { isDragging: monitor.isDragging() }; } }), isDragging = _a[0].isDragging, drag = _a[1]; var _b = reactDnd.useDrop({ accept: ['col', 'row', 'layer', 'custom', 'image', 'markdown'], canDrop: function (item) { var draggedNodeType = item.type; return draggedNodeType === 'row' || draggedNodeType === 'col' || draggedNodeType === 'markdown' || draggedNodeType === 'image' || draggedNodeType === 'layer' || draggedNodeType === 'custom'; }, drop: function (item, monitor) { var isOver = monitor.isOver({ shallow: true }); var clientOffset = monitor.getClientOffset(); var sourceClientOffset = monitor.getSourceClientOffset(); var initialClientOffset = monitor.getInitialClientOffset(); var initialSourceClientOffset = monitor.getInitialSourceClientOffset(); // ?TODO check if already dropped in nested target... instead of shallow isOver check? if (!isOver || !item || !sourceClientOffset || !clientOffset || !initialClientOffset || !initialSourceClientOffset) return null; // geometry: figure out whether the dragged element should go after us or before us var relativeDraggedPosition = getDragPositionRelativeToTarget2({ clientX: clientOffset.x, clientY: clientOffset.y }, getBoundingRect()); var lastChildId = props.node.childrenIds.length ? props.node.childrenIds[props.node.childrenIds.length - 1] : undefined; props.changeBlocks(onDropped2(item.id, props.node.id, relativeDraggedPosition ? { afterItemId: lastChildId, absolutePos: { top: relativeDraggedPosition.top - (initialClientOffset.y - initialSourceClientOffset.y), left: relativeDraggedPosition.left - (initialClientOffset.x - initialSourceClientOffset.x) } } : undefined)); return { type: 'layer', id: props.node.id }; }, collect: function (monitor) { var dragSourceItem = monitor.getItem(); return { isOver: monitor.isOver({ shallow: true }), initialClientOffset: monitor.getInitialClientOffset(), clientOffset: monitor.getClientOffset(), dragSourceItemType: dragSourceItem ? dragSourceItem.type : '', dragSourceItemId: dragSourceItem ? dragSourceItem.id : '', sourceClientOffset: monitor.getSourceClientOffset(), initialSourceClientOffset: monitor.getInitialSourceClientOffset() }; } }), _c = _b[0], drop = _b[1]; var getBoundingRect = function () { return selfRef.current && selfRef.current.getBoundingClientRect ? selfRef.current.getBoundingClientRect() : null; }; return React.createElement("div", { ref: selfRef, style: { width: '100%', height: '100%' } }, React.createElement("div", { ref: drop, style: { width: '100%', height: '100%' } }, React.createElement("div", { ref: drag, style: { width: '100%', height: '100%' } }, React.createElement(LayerBlock, tslib_1.__assign({}, props, { isDragging: isDragging }))))); }; var ColBlock = function (props) { var wantToPlaceNext = props.wantToPlaceNext; var renderChild = function (nodeId) { var getNode = props.getNode, changeBlocks = props.changeBlocks, undoRedoVersion = props.undoRedoVersion, focusedNodeId = props.focusedNodeId, renderEditBlock = props.renderEditBlock; var node = getNode(nodeId); if (!node) return null; return renderEditBlock({ node: node, undoRedoVersion: undoRedoVersion, renderEditBlock: renderEditBlock, focusedNodeId: focusedNodeId, changeBlocks: changeBlocks, getNode: getNode }); }; var reactChildren = props.children; var node = props.node, getNode = props.getNode; var childrenIds = node.childrenIds; var firstChildPlaceholderHeight = 3; var paddingLeft = (node.paddingLeftPercentWidth !== undefined ? node.paddingLeftPercentWidth : 0.05) * node.width; var paddingTop = (node.paddingTopPercentHeight !== undefined ? node.paddingTopPercentHeight : 0.05) * node.height; var runningHeight = wantToPlaceNext === 'firstChild' ? firstChildPlaceholderHeight : 0; return React__default.createElement("div", { key: 'col_' + node.id, style: { display: 'flex', opacity: props.isDragging ? 0.5 : 1, flexDirection: 'row', width: '100%', height: '100%' } }, wantToPlaceNext === 'left' && React__default.createElement("div", { style: { height: '100%', width: 3, backgroundColor: 'green' } }), React__default.createElement("div", { style: { width: '100%', height: '100%', position: 'relative', borderRadius: 3, backgroundColor: props.node.isPlaceHolder ? 'grey' : props.node.backgroundColor || '#ffffffa8', borderStyle: 'dashed', borderWidth: 1 } }, wantToPlaceNext === 'firstChild' && React__default.createElement("div", { style: { height: 10, width: '100%', position: 'absolute', top: paddingTop, left: paddingLeft, transform: "translate(0," + runningHeight + "px)", backgroundColor: 'blue' } }), React__default.Children.count(reactChildren) ? reactChildren : childrenIds.map(function (childId) { var row = getNode(childId); if (!row) return null; var res = React__default.createElement("div", { className: "drag-node", key: 'node_' + childId, style: { position: 'absolute', width: row.width, height: row.height, top: paddingTop, left: paddingLeft, transform: "translate(0," + runningHeight + "px)" } }, renderChild(childId)); runningHeight += row.height; return res; }), wantToPlaceNext === 'lastChild' && React__default.createElement("div", { style: { top: paddingLeft, left: paddingTop, transform: "translate(0," + runningHeight + "px)", position: 'absolute', height: 10, width: '100%', backgroundColor: 'orange' } })), wantToPlaceNext === 'right' && React__default.createElement("div", { style: { height: '100%', width: 3, backgroundColor: 'white' } })); }; var DraggableColBlock = function (props) { var selfRef = React.useRef(null); var _a = reactDnd.useDrag({ item: { type: 'col', id: props.node.id }, collect: function (monitor) { return { isDragging: monitor.isDragging() }; } }), isDragging = _a[0].isDragging, drag = _a[1]; var _b = reactDnd.useDrop({ accept: ['col', 'row', 'layer', 'custom', 'image', 'markdown'], canDrop: function (item) { var getNode = props.getNode, node = props.node; var parentNode = node.parentId ? getNode(node.parentId) : null; var draggedNodeType = item.type; return (// if our parent is a row, allow to place a sibling(col) before/after us draggedNodeType === 'col' && parentNode && parentNode.type === 'row' || // if we have children, we don't know if they can handle drops !props.children && (draggedNodeType === 'row' || draggedNodeType === 'markdown' || draggedNodeType === 'image' || draggedNodeType === 'layer' || draggedNodeType === 'custom') ); }, drop: function (item, monitor) { var isOver = monitor.isOver({ shallow: true }); // ?TODO check if already dropped in nested target... instead of shallow isOver check? if (!isOver || !item) return null; if (item.type === 'col') { // geometry: figure out whether the dragged element should go after us or before us in parent container var placeBefore = shouldPlaceBefore('x'); var anchorOpts = placeBefore ? { beforeItemId: props.node.id } : { afterItemId: props.node.id }; props.changeBlocks(onDropped2(item.id, props.node.parentId || '', tslib_1.__assign({}, anchorOpts))); } else { var placeBefore = shouldPlaceBefore('y'); var childrenIds = props.node.childrenIds; var numChildren = childrenIds.length; var anchorOpts = numChildren ? placeBefore ? { beforeItemId: childrenIds[0] } : { afterItemId: childrenIds[numChildren - 1] } : null; props.changeBlocks(onDropped2(item.id, props.node.id, tslib_1.__assign({}, anchorOpts))); } return { type: 'col', id: props.node.id }; }, collect: function (monitor) { var dragSourceItem = monitor.getItem(); return { isOver: monitor.isOver({ shallow: true }), initialClientOffset: monitor.getInitialClientOffset(), clientOffset: monitor.getClientOffset(), dragSourceItemType: dragSourceItem ? dragSourceItem.type : '', dragSourceItemId: dragSourceItem ? dragSourceItem.id : '', initialSourceClientOffset: monitor.getInitialSourceClientOffset() }; } }), _c = _b[0], clientOffset = _c.clientOffset, dragSourceItemType = _c.dragSourceItemType, isOver = _c.isOver, drop = _b[1]; var getBoundingRect = function () { return selfRef.current && selfRef.current.getBoundingClientRect ? selfRef.current.getBoundingClientRect() : null; }; var shouldPlaceBefore = function (axis) { if (!clientOffset || !isOver) { return false; } // geometry: figure out whether the dragged element should go after us or before us var relativeDraggedPosition = getDragPositionRelativeToTarget2({ clientX: clientOffset.x, clientY: clientOffset.y }, getBoundingRect()); var placeBefore = relativeDraggedPosition && (axis === 'y' ? relativeDraggedPosition.top / relativeDraggedPosition.height < 0.45 : relativeDraggedPosition.left / relativeDraggedPosition.width < 0.45); return placeBefore; }; var calcWantToPlaceNext = function (dragSourceItemType) { if (!dragSourceItemType || !isOver) return null; if (dragSourceItemType === 'col') { var placeBefore_1 = shouldPlaceBefore('x'); return placeBefore_1 ? 'left' : 'right'; } var placeBefore = shouldPlaceBefore('y'); return placeBefore ? 'firstChild' : 'lastChild'; }; var wantToPlaceNext = calcWantToPlaceNext(dragSourceItemType); return React__default.createElement("div", { ref: selfRef, style: { width: '100%', height: '100%' } }, React__default.createElement("div", { ref: drop, style: { width: '100%', height: '100%' } }, React__default.createElement("div", { ref: drag, style: { width: '100%', height: '100%' } }, React__default.createElement(ColBlock, tslib_1.__assign({}, props, { wantToPlaceNext: wantToPlaceNext, isDragging: isDragging }))))); }; var RowBlock = /*#__PURE__*/ /** @class */ function (_super) { tslib_1.__extends(RowBlock, _super); function RowBlock() { return _super !== null && _super.apply(this, arguments) || this; } RowBlock.prototype.render = function () { var reactChildren = this.props.children; var _a = this.props, wantToPlaceNext = _a.wantToPlaceNext, node = _a.node, getNode = _a.getNode, undoRedoVersion = _a.undoRedoVersion, changeBlocks = _a.changeBlocks, renderEditBlock = _a.renderEditBlock, focusedNodeId = _a.focusedNodeId; var firstChildPlaceholderWidth = 3; var paddingLeft = (node.paddingLeftPercentWidth !== undefined ? node.paddingLeftPercentWidth : 0.05) * node.width; var paddingTop = (node.paddingTopPercentHeight !== undefined ? node.paddingTopPercentHeight : 0.05) * node.height; var runningWidth = wantToPlaceNext === 'firstChild' ? firstChildPlaceholderWidth : 0; return React.createElement("div", { key: 'row_' + node.id, style: { opacity: this.props.isDragging ? 0.5 : 1 } }, wantToPlaceNext === 'top' && React.createElement("div", { style: { height: 3, width: '100%', backgroundColor: 'black' } }), React.createElement("div", { style: { position: 'relative', height: node.height, width: node.width, // padding: 20, borderStyle: 'dashed', borderWidth: 0, borderBottomWidth: 1, backgroundColor: node.isPlaceHolder ? 'darkgrey' : node.backgroundColor || '#b5bbb9b0' } }, wantToPlaceNext === 'firstChild' && React.createElement("div", { style: { height: '100%', width: firstChildPlaceholderWidth, backgroundColor: 'blue', position: 'absolute', top: paddingTop, left: paddingLeft, transform: "translate(" + runningWidth + "px,0)" } }), React.Children.count(reactChildren) ? reactChildren : node.childrenIds.map(function (id) { var node = getNode(id); if (!node) return null; var res = React.createElement("div", { className: "drag-node", key: 'node_' + node.id, style: { position: 'absolute', width: node.width, height: node.height, top: paddingTop, left: paddingLeft, transform: "translate(" + runningWidth + "px,0)" } }, renderEditBlock({ node: node, getNode: getNode, undoRedoVersion: undoRedoVersion, focusedNodeId: focusedNodeId, renderEditBlock: renderEditBlock, changeBlocks: changeBlocks })); runningWidth += node.width; return res; }), wantToPlaceNext === 'lastChild' && React.createElement("div", { style: { position: 'absolute', top: paddingTop, left: paddingLeft, transform: "translate(" + runningWidth + "px,0)", height: '100%', width: firstChildPlaceholderWidth, backgroundColor: 'orange' } })), wantToPlaceNext === 'bottom' && React.createElement("div", { style: { height: 3, width: '100%', backgroundColor: 'green' } })); }; return RowBlock; }(React.Component); var DraggableRowBlock = function (props) { var selfRef = React.useRef(null); var _a = reactDnd.useDrag({ item: { type: 'row', id: props.node.id }, collect: function (monitor) { return { isDragging: monitor.isDragging() }; } }), isDragging = _a[0].isDragging, drag = _a[1]; var _b = reactDnd.useDrop({ accept: ['col', 'row', 'layer', 'custom', 'image', 'markdown'], canDrop: function (item) { var getNode = props.getNode, node = props.node; var parentNode = node.parentId ? getNode(node.parentId) : null; var draggedNodeType = item.type; return (// if our parent is a col, allow to place a sibling (row) before/after us draggedNodeType === 'row' && parentNode && parentNode.type === 'col' || // if we have children, we don't know if they can handle drops !props.children && (draggedNodeType === 'col' || draggedNodeType === 'markdown' || draggedNodeType === 'image' || draggedNodeType === 'layer' || draggedNodeType === 'custom') ); }, drop: function (item, monitor) { var isOver = monitor.isOver({ shallow: true }); // ?TODO check if already dropped in nested target... instead of shallow isOver check? if (!isOver || !item) return null; if (item.type === 'row') { // geometry: figure out whether the dragged element should go after us or before us in parent container var placeBefore = shouldPlaceBefore('y'); var anchorOpts = placeBefore ? { beforeItemId: props.node.id } : { afterItemId: props.node.id }; props.changeBlocks(onDropped2(item.id, props.node.parentId || '', tslib_1.__assign({}, anchorOpts))); } else { var placeBefore = shouldPlaceBefore('x'); var childrenIds = props.node.childrenIds; var numChildren = childrenIds.length; var anchorOpts = numChildren ? placeBefore ? { beforeItemId: childrenIds[0] } : { afterItemId: childrenIds[numChildren - 1] } : null; props.changeBlocks(onDropped2(item.id, props.node.id, tslib_1.__assign({}, anchorOpts))); } return { type: 'row', id: props.node.id }; }, collect: function (monitor) { var dragSourceItem = monitor.getItem(); return { isOver: monitor.isOver({ shallow: true }), initialClientOffset: monitor.getInitialClientOffset(), clientOffset: monitor.getClientOffset(), dragSourceItemType: dragSourceItem ? dragSourceItem.type : '', dragSourceItemId: dragSourceItem ? dragSourceItem.id : '', initialSourceClientOffset: monitor.getInitialSourceClientOffset() }; } }), _c = _b[0], clientOffset = _c.clientOffset, dragSourceItemType = _c.dragSourceItemType, isOver = _c.isOver, drop = _b[1]; var getBoundingRect = function () { return selfRef.current && selfRef.current.getBoundingClientRect ? selfRef.current.getBoundingClientRect() : null; }; var shouldPlaceBefore = function (axis) { if (!clientOffset || !isOver) { return false; } // geometry: figure out whether the dragged element should go after us or before us var relativeDraggedPosition = getDragPositionRelativeToTarget2({ clientX: clientOffset.x, clientY: clientOffset.y }, getBoundingRect()); var placeBefore = relativeDraggedPosition && (axis === 'y' ? relativeDraggedPosition.top / relativeDraggedPosition.height < 0.3 : relativeDraggedPosition.left / relativeDraggedPosition.width < 0.3); return placeBefore; }; var calcWantToPlaceNext = function (dragSourceItemType) { if (!dragSourceItemType || !isOver) return null; if (dragSourceItemType === 'row') { var placeBefore_1 = shouldPlaceBefore('y'); return placeBefore_1 ? 'top' : 'bottom'; } var placeBefore = shouldPlaceBefore('x'); return placeBefore ? 'firstChild' : 'lastChild'; }; var wantToPlaceNext = calcWantToPlaceNext(dragSourceItemType); return React.createElement("div", { ref: selfRef, style: { width: '100%', height: '100%' } }, React.createElement("div", { ref: drop, style: { width: '100%', height: '100%' } }, React.createElement("div", { ref: drag, style: { width: '100%', height: '100%' } }, React.createElement(RowBlock, tslib_1.__assign({}, props, { wantToPlaceNext: wantToPlaceNext, isDragging: isDragging }))))); }; var ImageBlockComp = /*#__PURE__*/ /** @class */ function (_super) { tslib_1.__extends(ImageBlockComp, _super); function ImageBlockComp() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.selfRef = null; _this.getBoundingRect = function () { return _this.selfRef && _this.selfRef.getBoundingClientRect ? _this.selfRef.getBoundingClientRect() : null; }; return _this; } ImageBlockComp.prototype.render = function () { var _this = this; var node = this.props.node; return React.createElement("div", { ref: function (el) { return _this.selfRef = el; }, // draggable // onDragStart={e => onDragStart(e, this.props.node, this.getBoundingRect)} style: { opacity: this.props.isDragging ? 0.5 : 1, width: '100%', height: '100%', backgroundColor: 'violet', padding: 5, borderRadius: 3 } }, React.createElement("img", { style: { width: '100%', height: '100%' }, src: node.value })); }; return ImageBlockComp; }(React.Component); var ImageBlock = function (props) { var _a = reactDnd.useDrag({ item: { type: 'image', id: props.node.id }, collect: function (monitor) { return { isDragging: monitor.isDragging() }; } }), isDragging = _a[0].isDragging, drag = _a[1]; return React.createElement("div", { ref: drag, style: { width: '100%', height: '100%' } }, React.createElement(ImageBlockComp, tslib_1.__assign({}, props, { isDragging: isDragging }))); }; var MarkdownBlockComp = /*#__PURE__*/ /** @class */ function (_super) { tslib_1.__extends(MarkdownBlockComp, _super); function MarkdownBlockComp() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.selfRef = null; _this.getBoundingRect = function () { return _this.selfRef && _this.selfRef.getBoundingClientRect ? _this.selfRef.getBoundingClientRect() : null; }; return _this; } MarkdownBlockComp.prototype.render = function () { var _this = this; var _a = this.props, node = _a.node, update = _a.update; var value = node.value; return React.createElement("div", { ref: function (el) { return _this.selfRef = el; }, // draggable // onDragStart={e => onDragStart(e, this.props.node, this.getBoundingRect)} style: { opacity: this.props.isDragging ? 0.5 : 1, width: '100%', height: '100%', backgroundColor: '#ffc0cb75', padding: 5, borderRadius: 3 } }, React.createElement("textarea", { className: "form-control", name: "value", placeholder: "Type here...", value: value, style: { width: '95%', height: '95%', outline: 'none' }, onChange: function (e) { return update(node.id, { value: e.target.value }); } })); }; return MarkdownBlockComp; }(React.Component); var MarkdownBlock = function (props) { var _a = reactDnd.useDrag({ item: { type: 'markdown', id: props.node.id }, collect: function (monitor) { return { isDragging: monitor.isDragging() }; } }), isDragging = _a[0].isDragging, drag = _a[1]; return React.createElement("div", { ref: drag, style: { width: '100%', height: '100%' } }, React.createElement(MarkdownBlockComp, tslib_1.__assign({}, props, { isDragging: isDragging }))); }; var ResizableBlock = function (props) { var node = props.node, renderEditBlock = props.renderEditBlock, getNode = props.getNode, undoRedoVersion = props.undoRedoVersion, changeBlocks = props.changeBlocks, focusedNodeId = props.focusedNodeId, width = props.width, height = props.height; return React.createElement("div", { onClick: function (e) { e.stopPropagation(); changeBlocks(function (value) { return focusNode(value, node); }); }, style: node.id === focusedNodeId ? { borderStyle: 'dashed', borderWidth: 1, borderColor: 'orange' } : {} }, React.createElement(reactResizable.ResizableBox, { className: "box", width: width, height: height, onResizeStart: function (event) { event.preventDefault(); event.stopPropagation(); }, onResize: function (_event, _a) { var size = _a.size; var width = size.width, height = size.height; changeBlocks(function (value) { return update(value, node.id, { width: width, height: height }); }); } }, node.type === 'col' ? React.createElement(DraggableColBlock, { key: 'col_' + node.id, node: node, renderEditBlock: renderEditBlock, getNode: getNode, changeBlocks: changeBlocks, focusedNodeId: focusedNodeId, undoRedoVersion: undoRedoVersion }) : node.type === 'row' ? React.createElement(DraggableRowBlock, { key: 'row_' + node.id, node: node, renderEditBlock: renderEditBlock, getNode: getNode, changeBlocks: changeBlocks, focusedNodeId: focusedNodeId, undoRedoVersion: undoRedoVersion }) : node.type === 'markdown' ? React.createElement(MarkdownBlock, { node: node, update: function (nodeId, props) { return changeBlocks(function (value) { return update(value, nodeId, props); }); } }) : node.type === 'layer' ? React.createElement(AbsoluteLayerBlock, { key: 'layer_' + node.id, node: node, renderEditBlock: renderEditBlock, getNode: getNode, changeBlocks: changeBlocks, focusedNodeId: focusedNodeId, undoRedoVersion: undoRedoVersion }) : React.createElement(ImageBlock, { key: 'image_' + node.id, node: node, renderEditBlock: renderEditBlock, getNode: getNode, changeBlocks: changeBlocks, focusedNodeId: focusedNodeId, undoRedoVersion: undoRedoVersion }))); }; var defaultRenderEditBlock = function (_a) { var node = _a.node, renderEditBlock = _a.renderEditBlock, getNode = _a.getNode, changeBlocks = _a.changeBlocks, undoRedoVersion = _a.undoRedoVersion, focusedNodeId = _a.focusedNodeId; return React.createElement(ResizableBlock, { width: node.width, height: node.height, renderEditBlock: renderEditBlock, node: node, getNode: getNode, changeBlocks: changeBlocks, focusedNodeId: focusedNodeId, undoRedoVersion: undoRedoVersion }); }; var maxUndoStack = 10; var BlockEditor = /*#__PURE__*/ /** @class */ function (_super) { tslib_1.__extends(BlockEditor, _super); function BlockEditor() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.haveHtml5DndAPI = haveDraggableHtml5API(); _this.state = { value: _this.props.value }; _this.getNode = function (id) { return _this.state.value.byId[id]; }; _this.onSendOp = function (op) { var value = op(_this.state.value); _this.setState({ value: value }); }; return _this; } BlockEditor.prototype.componentDidMount = function () { if (this.props.onChange) { // Notify parent of change var value = this.state.value; this.props.onChange(value); } }; BlockEditor.prototype.componentDidUpdate = function (prevProps, prevState) { if (this.props.onChange && this.state.value !== prevState.value && this.state.value !== this.props.value) { var value = this.state.value; this.props.onChange(value); } else if (this.props.value && prevProps.value !== this.props.value && this.props.value !== this.state.value) { this.setState({ value: this.props.value }); } // make the last byId reachable through undo if (this.state.value.byId !== prevState.value.byId && this.state.value.undoStack === prevState.value.undoStack) { this.setState({ value: tslib_1.__assign({}, this.state.value, { undoStack: [lodashEs.cloneDeep(prevState.value.byId)].concat(this.state.value.undoStack).slice(0, maxUndoStack) }) }); } }; BlockEditor.prototype.render = function () { var renderEditBlock = this.props.renderEditBlock; var _a = this.state.value, byId = _a.byId, rootNodeId = _a.rootNodeId, undoRedoVersion = _a.undoRedoVersion; var rootNode = byId[rootNodeId]; var Backend = this.haveHtml5DndAPI ? Html5Backend : TouchBackend; // console.log('LAYOUT:', JSON.stringify(byId), JSON.stringify(rootNode)); return React.createElement("div", { style: { position: 'relative', width: rootNode.width, height: rootNode.height } }, React.createElement(reactDnd.DndProvider, { backend: Backend }, React.createElement(DraggableColBlock, { key: 'col_' + rootNode.id, undoRedoVersion: undoRedoVersion, node: rootNode, getNode: this.getNode, changeBlocks: this.onSendOp, focusedNodeId: this.state.value.focusedNodeId, renderEditBlock: renderEditBlock }))); }; BlockEditor.defaultProps = { renderEditBlock: defaultRenderEditBlock, onChange: function (_v) {} }; return BlockEditor; }(React.Component); function haveDraggableHtml5API() { var iOS = !!navigator.userAgent.match('iPhone OS') || !!navigator.userAgent.match('iPad'); if (iOS) return false; var div = window.document.createElement('div'); return 'draggable' in div || 'ondragstart' in div && 'ondrop' in div; } function hasBorder(node) { return !!node.borderWidth && node.borderWidth > 0 || !!node.borderTopWidth && node.borderTopWidth > 0 || !!node.borderBottomWidth && node.borderBottomWidth > 0 || !!node.borderLeftWidth && node.borderLeftWidth > 0 || !!node.borderRightWidth && node.borderRightWidth > 0; } var copyBasicProps = function (node) { return { color: node.color || undefined, display: node.display || undefined, flexDirection: node.flexDirection || undefined, justifyContent: node.justifyContent || undefined, alignItems: node.alignItems || undefined, backgroundColor: node.backgroundColor || undefined, borderWidth: node.borderWidth !== undefined ? node.borderWidth : 0, borderTopWidth: node.borderTopWidth !== undefined ? node.borderTopWidth : undefined, borderBottomWidth: node.borderBottomWidth !== undefined ? node.borderBottomWidth : undefined, borderLeftWidth: node.borderLeftWidth !== undefined ? node.borderLeftWidth : undefined, borderRightWidth: node.borderRightWidth !== undefined ? node.borderRightWidth : undefined, borderStyle: !node.borderStyle ? hasBorder(node) ? 'solid' : undefined : node.borderStyle || undefined }; }; var Preview = function (_a) { var byId = _a.byId, node = _a.node, _b = _a.renderPreviewBlock, renderPreviewBlock = _b === void 0 ? defaultRenderPreviewBlock : _b; if (!node) return null; switch (node.type) { case 'markdown': return React.createElement("div", { key: node.id, style: tslib_1.__assign({}, copyBasicProps(node), { height: node.height, width: node.width }) }, React.createElement(MarkDown, { source: node.value })); case 'layer': return React.createElement("div", { key: node.id, style: tslib_1.__assign({