jjb-lc-designable
Version:
基于alibaba-designable源码二次封装的表单设计器。
685 lines (684 loc) • 20.2 kB
JavaScript
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]);
}, []);
}
}