react-movable-block-editor
Version:
React component for creating layouts and content via drag-and-drop blocks.
1,654 lines (1,479 loc) • 75.3 kB
JavaScript
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var tslib_1 = require('tslib');
var React = require('react');
var React__default = _interopDefault(React);
var reactDnd = require('react-dnd');
var reactResizable = require('react-resizable');
var lodash = require('lodash');
var Html5Backend = _interopDefault(require('react-dnd-html5-backend'));
var TouchBackend = _interopDefault(require('react-dnd-touch-backend'));
var MarkDown = _interopDefault(require('react-markdown'));
var reactColor = require('react-color');
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').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: [lodash.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({
// flex: 1,
position: 'relative'
}, copyBasicProps(node), {
height: node.height,
width: node.width
})
}, node.childrenIds.map(function (id) {
var childNode = byId[id];
if (!childNode) return React.createElement("div", null, "Error in layer ", node.id, ": child node ", id, " not found");
return React.createElement("div", {
key: childNode.id,
style: {
position: 'absolute',
top: childNode.top || 0,
left: childNode.left || 0
}
}, renderPreviewBlock({
byId: byId,
node: childNode,
renderPreviewBlock: renderPreviewBlock
}));
}));
case 'image':
return React.createElement("div", {
key: node.id,
style: {
height: '100%',
width: '100%'
}
}, React.createElement("img", {
src: node.value
}));
case 'col':
return React.createElement("div", {
key: node.id,
style: tslib_1.__assign({}, copyBasicProps(node), {
width: node.width,
height: node.height,
display: 'flex',
flexDirection: 'column'
})
}, node.childrenIds.map(function (id) {
var childNode = byId[id];
if (!childNode) return React.createElement("div", null, "Error in col ", node.id, ": child node ", id, " not found");
return renderPreviewBlock({
byId: byId,
node: childNode,
renderPreviewBlock: renderPreviewBlock
});
}));
case 'row':
return React.createElement("div", {
key: node.id,
style: tslib_1.__assign({}, copyBasicProps(node), {
width: node.width,
height: node.height,
display: 'flex',
flexDirection: 'row'
})
}, node.childrenIds.map(function (id) {
var childNode = byId[id];
if (!childNode) return React.createElement("div", null, "Error in row ", node.id, ": child node ", id, " not found");
return renderPreviewBlock({
byId: byId,
node: childNode,
renderPreviewBlock: renderPreviewBlock
});
}));
case 'custom':
return null;
}
};
var defaultRenderPreviewBlock = function (_a) {
var byId = _a.byId,
node = _a.node,
renderPreviewBlock = _a.renderPreviewBlock;
return React.createElement(Preview, {
key: 'node_' + node.id,
node: node,
byId: byId,
renderPreviewBlock: renderPreviewBlock
});
};
var BlockBreadCrumbs = function (props) {
var byId = props.byId,
node = props.node,
onSelect = props.onSelect,
_a = props.navClassName,
navClassName = _a === void 0 ? 'breadcrumb' : _a,
_b = props.itemClassName,
itemClassName = _b === void 0 ? 'breadcrumb-item btn btn-link active' : _b;
if (!node) return null;
var crumbs = [];
var id = node.id;
while (id) {
var node_1 = byId[id];
if (!node_1) break;
crumbs.unshift({
label: node_1.type + "-" + node_1.id,
id: node_1.id
});
id = node_1.parentId || '';
}
return React.createElement("nav", {
"aria-label": "breadcrumb"
}, React.createElement("ol", {
className: navClassName
}, crumbs.map(function (crumb) {
return React.createElement("li", {
onClick: function () {
return onSelect(crumb.id);
},
key: 'crumb_' + crumb.id,