UNPKG

jjb-lc-designable

Version:

基于alibaba-designable源码二次封装的表单设计器。

685 lines (684 loc) 20.2 kB
import { action, define, observable, toJS } from 'jjb-lc-formily/reactive'; import { uid, isFn, each } from 'jjb-lc-designable/shared'; import { InsertBeforeEvent, InsertAfterEvent, InsertChildrenEvent, PrependNodeEvent, AppendNodeEvent, WrapNodeEvent, UpdateChildrenEvent, RemoveNodeEvent, UpdateNodePropsEvent, CloneNodeEvent, FromNodeEvent } from '../events'; import { GlobalRegistry } from '../registry'; import { mergeLocales } from '../internals'; const TreeNodes = new Map(); const CommonDesignerPropsMap = new Map(); const removeNode = node => { if (node.parent) { node.parent.children = node.parent.children.filter(child => child !== node); } }; const resetNodesParent = (nodes, parent) => { const resetDepth = node => { node.depth = node.parent ? node.parent.depth + 1 : 0; node.children.forEach(resetDepth); }; const shallowReset = node => { node.parent = parent; node.root = parent.root; resetDepth(node); }; const deepReset = node => { shallowReset(node); resetNodesParent(node.children, node); }; return nodes.map(node => { if (node === parent) return node; if (!parent.isSourceNode) { if (node.isSourceNode) { node = node.clone(parent); resetDepth(node); } else if (!node.isRoot && node.isInOperation) { node.operation?.selection.remove(node); removeNode(node); shallowReset(node); } else { deepReset(node); } } else { deepReset(node); } if (!TreeNodes.has(node.id)) { TreeNodes.set(node.id, node); CommonDesignerPropsMap.set(node.componentName, node.designerProps); } return node; }); }; const resetParent = (node, parent) => { return resetNodesParent([node], parent)[0]; }; const resolveDesignerProps = (node, props) => { if (isFn(props)) return props(node); return props; }; export class TreeNode { depth = 0; hidden = false; componentName = 'NO_NAME_COMPONENT'; sourceName = ''; props = {}; children = []; constructor(node, parent) { if (node instanceof TreeNode) { return node; } this.id = node.id || uid(); if (parent) { this.parent = parent; this.depth = parent.depth + 1; this.root = parent.root; TreeNodes.set(this.id, this); } else { this.root = this; this.rootOperation = node.operation; this.isSelfSourceNode = node.isSourceNode || false; TreeNodes.set(this.id, this); } if (node) { this.from(node); } this.makeObservable(); } makeObservable() { define(this, { componentName: observable.ref, props: observable, hidden: observable.ref, children: observable.shallow, designerProps: observable.computed, designerLocales: observable.computed, wrap: action, prepend: action, append: action, insertAfter: action, insertBefore: action, remove: action, setProps: action, setChildren: action, setComponentName: action }); } get designerProps() { const behaviors = GlobalRegistry.getDesignerBehaviors(this); const designerProps = behaviors.reduce((buf, pattern) => { if (!pattern.designerProps) return buf; Object.assign(buf, resolveDesignerProps(this, pattern.designerProps)); return buf; }, {}); return designerProps; } get designerLocales() { const behaviors = GlobalRegistry.getDesignerBehaviors(this); const designerLocales = behaviors.reduce((buf, pattern) => { if (!pattern.designerLocales) return buf; mergeLocales(buf, pattern.designerLocales); return buf; }, {}); return designerLocales; } get previous() { if (this.parent === this || !this.parent) return; return this.parent.children[this.index - 1]; } get next() { if (this.parent === this || !this.parent) return; return this.parent.children[this.index + 1]; } get siblings() { if (this.parent) { return this.parent.children.filter(node => node !== this); } return []; } get index() { if (this.parent === this || !this.parent) return 0; return this.parent.children.indexOf(this); } get descendants() { return this.children.reduce((buf, node) => { return buf.concat(node).concat(node.descendants); }, []); } get isRoot() { return this === this.root; } get isInOperation() { return !!this.operation; } get lastChild() { return this.children[this.children.length - 1]; } get firstChild() { return this.children[0]; } get isSourceNode() { return this.root.isSelfSourceNode; } get operation() { return this.root?.rootOperation; } get viewport() { return this.operation?.workspace?.viewport; } get outline() { return this.operation?.workspace?.outline; } get moveLayout() { return this.viewport?.getValidNodeLayout(this); } getElement(area = 'viewport') { return this[area]?.findElementById(this.id); } getValidElement(area = 'viewport') { return this[area]?.getValidNodeElement(this); } getElementRect(area = 'viewport') { return this[area]?.getElementRect(this.getElement(area)); } getValidElementRect(area = 'viewport') { return this[area]?.getValidNodeRect(this); } getElementOffsetRect(area = 'viewport') { return this[area]?.getElementOffsetRect(this.getElement(area)); } getValidElementOffsetRect(area = 'viewport') { return this[area]?.getValidNodeOffsetRect(this); } getPrevious(step = 1) { return this.parent.children[this.index - step]; } getAfter(step = 1) { return this.parent.children[this.index + step]; } getSibling(index = 0) { return this.parent.children[index]; } getParents(node) { const _node = node || this; return _node?.parent ? [_node.parent].concat(this.getParents(_node.parent)) : []; } getParentByDepth(depth = 0) { let parent = this.parent; if (parent?.depth === depth) { return parent; } else { return parent?.getParentByDepth(depth); } } getMessage(token) { return GlobalRegistry.getDesignerMessage(token, this.designerLocales); } isMyAncestor(node) { if (node === this || this.parent === node) return false; return node.contains(this); } isMyParent(node) { return this.parent === node; } isMyParents(node) { if (node === this) return false; return this.isMyParent(node) || this.isMyAncestor(node); } isMyChild(node) { return node.isMyParent(this); } isMyChildren(node) { return node.isMyParents(this); } takeSnapshot(type) { this.operation?.snapshot(type); } triggerMutation(event, callback, defaults) { if (this.operation) { // @ts-ignore const result = this.operation.dispatch(event, callback) || defaults; this.takeSnapshot(event?.type); return result; } else if (isFn(callback)) { return callback(); } } find(finder) { if (finder(this)) { return this; } else { let result = undefined; this.eachChildren(node => { if (finder(node)) { result = node; return false; } }); return result; } } findAll(finder) { const results = []; if (finder(this)) { results.push(this); } this.eachChildren(node => { if (finder(node)) { results.push(node); } }); return results; } distanceTo(node) { if (this.root !== node.root) { return Infinity; } if (this.parent !== node.parent) { return Infinity; } return Math.abs(this.index - node.index); } crossSiblings(node) { if (this.parent !== node.parent) return []; const minIndex = Math.min(this.index, node.index); const maxIndex = Math.max(this.index, node.index); const results = []; for (let i = minIndex + 1; i < maxIndex; i++) { results.push(this.parent.children[i]); } return results; } allowSibling(nodes) { if (this.designerProps?.allowSiblings?.(this, nodes) === false) return false; return this.parent?.allowAppend(nodes); } allowDrop(parent) { if (!isFn(this.designerProps.allowDrop)) return true; return this.designerProps.allowDrop(parent); } allowAppend(nodes) { if (!this.designerProps?.droppable) return false; if (this.designerProps?.allowAppend?.(this, nodes) === false) return false; if (nodes.some(node => !node.allowDrop(this))) return false; if (this.root === this) return true; return true; } allowClone() { if (this === this.root) return false; return this.designerProps.cloneable ?? true; } allowDrag() { if (this === this.root && !this.isSourceNode) return false; return this.designerProps.draggable ?? true; } allowResize() { if (this === this.root && !this.isSourceNode) return false; const { resizable } = this.designerProps; if (!resizable) return false; if (resizable.width && resizable.height) return ['x', 'y']; if (resizable.width) return ['x']; return ['y']; } allowRotate() {} allowRound() {} allowScale() {} allowTranslate() { if (this === this.root && !this.isSourceNode) return false; const { translatable } = this.designerProps; if (translatable?.x && translatable?.y) return true; return false; } allowDelete() { if (this === this.root) return false; return this.designerProps.deletable ?? true; } findById(id) { if (!id) return; if (this.id === id) return this; if (this.children?.length > 0) { return TreeNodes.get(id); } } contains(...nodes) { return nodes.every(node => { if (node === this || node?.parent === this || node?.getParentByDepth(this.depth) === this) { return true; } return false; }); } eachTree(callback) { if (isFn(callback)) { callback(this.root); this.root?.eachChildren(callback); } } eachChildren(callback) { if (isFn(callback)) { for (let i = 0; i < this.children.length; i++) { const node = this.children[i]; if (callback(node) === false) return; node.eachChildren(callback); } } } resetNodesParent(nodes, parent) { return resetNodesParent(nodes.filter(node => node !== this), parent); } setProps(props) { return this.triggerMutation(new UpdateNodePropsEvent({ target: this, source: null }), () => { Object.assign(this.props, props); }); } setComponentName(componentName) { this.componentName = componentName; } prepend(...nodes) { if (nodes.some(node => node.contains(this))) return []; const originSourceParents = nodes.map(node => node.parent); const newNodes = this.resetNodesParent(nodes, this); if (!newNodes.length) return []; return this.triggerMutation(new PrependNodeEvent({ originSourceParents, target: this, source: newNodes }), () => { this.children = newNodes.concat(this.children); return newNodes; }, []); } append(...nodes) { if (nodes.some(node => node.contains(this))) return []; const originSourceParents = nodes.map(node => node.parent); const newNodes = this.resetNodesParent(nodes, this); if (!newNodes.length) return []; return this.triggerMutation(new AppendNodeEvent({ originSourceParents, target: this, source: newNodes }), () => { this.children = this.children.concat(newNodes); return newNodes; }, []); } wrap(wrapper) { if (wrapper === this) return; const parent = this.parent; return this.triggerMutation(new WrapNodeEvent({ target: this, source: wrapper }), () => { resetParent(this, wrapper); resetParent(wrapper, parent); return wrapper; }); } insertAfter(...nodes) { const parent = this.parent; if (nodes.some(node => node.contains(this))) return []; if (parent?.children?.length) { const originSourceParents = nodes.map(node => node.parent); const newNodes = this.resetNodesParent(nodes, parent); if (!newNodes.length) return []; return this.triggerMutation(new InsertAfterEvent({ originSourceParents, target: this, source: newNodes }), () => { parent.children = parent.children.reduce((buf, node) => { if (node === this) { return buf.concat([node]).concat(newNodes); } else { return buf.concat([node]); } }, []); return newNodes; }, []); } return []; } insertBefore(...nodes) { const parent = this.parent; if (nodes.some(node => node.contains(this))) return []; if (parent?.children?.length) { const originSourceParents = nodes.map(node => node.parent); const newNodes = this.resetNodesParent(nodes, parent); if (!newNodes.length) return []; return this.triggerMutation(new InsertBeforeEvent({ originSourceParents, target: this, source: newNodes }), () => { parent.children = parent.children.reduce((buf, node) => { if (node === this) { return buf.concat(newNodes).concat([node]); } else { return buf.concat([node]); } }, []); return newNodes; }, []); } return []; } insertChildren(start, ...nodes) { if (nodes.some(node => node.contains(this))) return []; if (this.children?.length) { const originSourceParents = nodes.map(node => node.parent); const newNodes = this.resetNodesParent(nodes, this); if (!newNodes.length) return []; return this.triggerMutation(new InsertChildrenEvent({ originSourceParents, target: this, source: newNodes }), () => { this.children = this.children.reduce((buf, node, index) => { if (index === start) { return buf.concat(newNodes).concat([node]); } return buf.concat([node]); }, []); return newNodes; }, []); } return []; } setChildren(...nodes) { const originSourceParents = nodes.map(node => node.parent); const newNodes = this.resetNodesParent(nodes, this); return this.triggerMutation(new UpdateChildrenEvent({ originSourceParents, target: this, source: newNodes }), () => { this.children = newNodes; return newNodes; }, []); } /** * @deprecated * please use `setChildren` */ setNodeChildren(...nodes) { return this.setChildren(...nodes); } remove() { return this.triggerMutation(new RemoveNodeEvent({ target: this, source: null }), () => { removeNode(this); TreeNodes.delete(this.id); }); } clone(parent) { const newNode = new TreeNode({ id: uid(), componentName: this.componentName, sourceName: this.sourceName, props: toJS(this.props), children: [] }, parent ? parent : this.parent); newNode.children = resetNodesParent(this.children.map(child => { return child.clone(newNode); }), newNode); return this.triggerMutation(new CloneNodeEvent({ target: this, source: newNode }), () => newNode); } from(node) { if (!node) return; return this.triggerMutation(new FromNodeEvent({ target: this, source: node }), () => { if (node.id && node.id !== this.id) { TreeNodes.delete(this.id); TreeNodes.set(node.id, this); this.id = node.id; } if (node.componentName) { this.componentName = node.componentName; } this.props = node.props ?? {}; if (node.hidden) { this.hidden = node.hidden; } if (node.children) { this.children = node.children?.map?.(node => { return new TreeNode(node, this); }) || []; } }); } serialize() { return { id: this.id, componentName: this.componentName, sourceName: this.sourceName, props: toJS(this.props), hidden: this.hidden, children: this.children.map(treeNode => { return treeNode.serialize(); }) }; } static create(node, parent) { return new TreeNode(node, parent); } static findById(id) { return TreeNodes.get(id); } static remove(nodes = []) { for (let i = nodes.length - 1; i >= 0; i--) { const node = nodes[i]; if (node.allowDelete()) { const previous = node.previous; const next = node.next; node.remove(); node.operation?.selection.select(previous ? previous : next ? next : node.parent); node.operation?.hover.clear(); } } } static sort(nodes = []) { return nodes.sort((before, after) => { if (before.depth !== after.depth) return 0; return before.index - after.index >= 0 ? 1 : -1; }); } static clone(nodes = []) { const groups = {}; const lastGroupNode = {}; const filterNestedNode = TreeNode.sort(nodes).filter(node => { return !nodes.some(parent => { return node.isMyParents(parent); }); }); each(filterNestedNode, node => { if (node === node.root) return; if (!node.allowClone()) return; if (!node?.operation) return; groups[node?.parent?.id] = groups[node?.parent?.id] || []; groups[node?.parent?.id].push(node); if (lastGroupNode[node?.parent?.id]) { if (node.index > lastGroupNode[node?.parent?.id].index) { lastGroupNode[node?.parent?.id] = node; } } else { lastGroupNode[node?.parent?.id] = node; } }); const parents = new Map(); each(groups, (nodes, parentId) => { const lastNode = lastGroupNode[parentId]; let insertPoint = lastNode; each(nodes, node => { const cloned = node.clone(); if (!cloned) return; if (node.operation?.selection.has(node) && insertPoint.parent.allowAppend([cloned])) { insertPoint.insertAfter(cloned); insertPoint = insertPoint.next; } else if (node.operation.selection.length === 1) { const targetNode = node.operation?.tree.findById(node.operation.selection.first); let cloneNodes = parents.get(targetNode); if (!cloneNodes) { cloneNodes = []; parents.set(targetNode, cloneNodes); } if (targetNode && targetNode.allowAppend([cloned])) { cloneNodes.push(cloned); } } }); }); parents.forEach((nodes, target) => { if (!nodes.length) return; target.append(...nodes); }); } static filterResizable(nodes = []) { return nodes.filter(node => node.allowResize()); } static filterRotatable(nodes = []) { return nodes.filter(node => node.allowRotate()); } static filterScalable(nodes = []) { return nodes.filter(node => node.allowScale()); } static filterRoundable(nodes = []) { return nodes.filter(node => node.allowRound()); } static filterTranslatable(nodes = []) { return nodes.filter(node => node.allowTranslate()); } static filterDraggable(nodes = []) { return nodes.reduce((buf, node) => { if (!node.allowDrag()) return buf; if (isFn(node?.designerProps?.getDragNodes)) { const transformed = node.designerProps.getDragNodes(node); return transformed ? buf.concat(transformed) : buf; } if (node.componentName === '$$ResourceNode$$') return buf.concat(node.children); return buf.concat([node]); }, []); } static filterDroppable(nodes = [], parent) { return nodes.reduce((buf, node) => { if (!node.allowDrop(parent)) return buf; if (isFn(node.designerProps?.getDropNodes)) { const cloned = node.isSourceNode ? node.clone(node.parent) : node; const transformed = node.designerProps.getDropNodes(cloned, parent); return transformed ? buf.concat(transformed) : buf; } if (node.componentName === '$$ResourceNode$$') return buf.concat(node.children); return buf.concat([node]); }, []); } }