From 81731866e150e6fe19109845f4c84a2d38dff3af Mon Sep 17 00:00:00 2001 From: "lihao.ylh" Date: Tue, 21 Dec 2021 20:45:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=85=20simulator-host=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E4=BB=A5=E5=8F=8A=E9=83=A8=E5=88=86=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/document/node/node-children.ts | 51 ++++++--- packages/shell/src/document-model.ts | 95 ++++++++++++---- packages/shell/src/node-children.ts | 22 +++- packages/shell/src/node.ts | 105 +++++++++++++++++- packages/shell/src/project.ts | 75 +++++++++++-- packages/shell/src/simulator-host.ts | 25 +++++ packages/shell/src/symbols.ts | 6 +- 7 files changed, 320 insertions(+), 59 deletions(-) create mode 100644 packages/shell/src/simulator-host.ts diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index e727af3bb..4eefd2ff7 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -19,13 +19,13 @@ export class NodeChildren { constructor(readonly owner: ParentalNode, data: NodeData | NodeData[], options: any = {}) { makeObservable(this); - this.children = (Array.isArray(data) ? data : [data]).map(child => { + this.children = (Array.isArray(data) ? data : [data]).map((child) => { return this.owner.document.createNode(child, options.checkId); }); } internalInitParent() { - this.children.forEach(child => child.internalSetParent(this.owner)); + this.children.forEach((child) => child.internalSetParent(this.owner)); } /** @@ -33,7 +33,7 @@ export class NodeChildren { */ export(stage: TransformStage = TransformStage.Save): NodeData[] { stage = compatStage(stage); - return this.children.map(node => { + return this.children.map((node) => { const data = node.export(stage); if (node.isLeaf() && TransformStage.Save === stage) { // FIXME: filter empty @@ -47,7 +47,7 @@ export class NodeChildren { data = data ? (Array.isArray(data) ? data : [data]) : []; const originChildren = this.children.slice(); - this.children.forEach(child => child.internalSetParent(null)); + this.children.forEach((child) => child.internalSetParent(null)); const children = new Array(data.length); for (let i = 0, l = data.length; i < l; i++) { @@ -134,12 +134,20 @@ export class NodeChildren { delete(node: Node, purge = false, useMutator = true, options: NodeRemoveOptions = {}): boolean { node.internalPurgeStart(); if (node.isParental()) { - foreachReverse(node.children, (subNode: Node) => { - subNode.remove(useMutator, purge, options); - }, (iterable, idx) => (iterable as NodeChildren).get(idx)); - foreachReverse(node.slots, (slotNode: Node) => { - slotNode.remove(useMutator, purge); - }, (iterable, idx) => (iterable as [])[idx]); + foreachReverse( + node.children, + (subNode: Node) => { + subNode.remove(useMutator, purge, options); + }, + (iterable, idx) => (iterable as NodeChildren).get(idx), + ); + foreachReverse( + node.slots, + (slotNode: Node) => { + slotNode.remove(useMutator, purge); + }, + (iterable, idx) => (iterable as [])[idx], + ); } // 需要在从 children 中删除 node 前记录下 index,internalSetParent 中会执行删除(unlink)操作 const i = this.children.indexOf(node); @@ -164,7 +172,13 @@ export class NodeChildren { node, }); if (useMutator) { - this.reportModified(node, this.owner, { type: 'remove', propagated: false, isSubDeleting: this.owner.isPurging, removeIndex: i, removeNode: node }); + this.reportModified(node, this.owner, { + type: 'remove', + propagated: false, + isSubDeleting: this.owner.isPurging, + removeIndex: i, + removeNode: node, + }); } // purge 为 true 时,已在 internalSetParent 中删除了子节点 if (i > -1 && !purge) { @@ -183,10 +197,11 @@ export class NodeChildren { const i = children.indexOf(node); if (node.parent) { - globalContext.has('editor') && globalContext.get('editor').emit('node.remove.topLevel', { - node, - index: node.index, - }); + globalContext.has('editor') && + globalContext.get('editor').emit('node.remove.topLevel', { + node, + index: node.index, + }); } if (i < 0) { @@ -332,7 +347,11 @@ export class NodeChildren { return this.children.find(fn); } - mergeChildren(remover: () => any, adder: (children: Node[]) => NodeData[] | null, sorter: () => any) { + mergeChildren( + remover: (node: Node, idx: number) => boolean, + adder: (children: Node[]) => NodeData[] | null, + sorter: (firstNode: Node, secondNode: Node) => number, + ) { let changed = false; if (remover) { const willRemove = this.children.filter(remover); diff --git a/packages/shell/src/document-model.ts b/packages/shell/src/document-model.ts index 4ac53d311..902296a72 100644 --- a/packages/shell/src/document-model.ts +++ b/packages/shell/src/document-model.ts @@ -13,7 +13,7 @@ import Detecting from './detecting'; import History from './history'; import Project from './project'; import Prop from './prop'; -import { documentSymbol, editorSymbol } from './symbols'; +import { documentSymbol, editorSymbol, nodeSymbol } from './symbols'; type IOnChangeOptions = { type: string; @@ -48,14 +48,26 @@ export default class DocumentModel { return new DocumentModel(document); } + /** + * 获取当前文档所属的 project + * @returns + */ getProject() { return Project.create(this[documentSymbol].project); } + /** + * 获取文档的根节点 + * @returns + */ getRoot() { return Node.create(this[documentSymbol].getRoot()); } + /** + * 获取文档下所有节点 + * @returns + */ getNodesMap() { const map = new Map(); for (let id in this[documentSymbol].nodesMap.keys()) { @@ -64,30 +76,61 @@ export default class DocumentModel { return map; } + /** + * 根据 nodeId 返回 Node 实例 + * @param nodeId + * @returns + */ getNodeById(nodeId: string) { return Node.create(this[documentSymbol].getNode(nodeId)); } + /** + * 导入 schema + * @param schema + */ importSchema(schema: RootSchema) { this[documentSymbol].import(schema); } + /** + * 导出 schema + * @param stage + * @returns + */ exportSchema(stage?: TransformStage) { return this[documentSymbol].export(stage); } + /** + * 插入节点 + * @param parent + * @param thing + * @param at + * @param copy + * @returns + */ insertNode( - parent: ParentalNode, - thing: InnerNode | NodeData, + parent: Node, + thing: Node, at?: number | null | undefined, copy?: boolean | undefined, ) { - const node = this[documentSymbol].insertNode(parent, thing, at, copy); + const node = this[documentSymbol].insertNode( + parent[nodeSymbol] as any, + thing?.[nodeSymbol], + at, + copy, + ); return Node.create(node); } - removeNode(idOrNode: string | InnerNode) { - this[documentSymbol].removeNode(idOrNode); + /** + * 移除指定节点/节点id + * @param idOrNode + */ + removeNode(idOrNode: string | Node) { + this[documentSymbol].removeNode(idOrNode as any); } /** @@ -126,6 +169,10 @@ export default class DocumentModel { }); } + /** + * 当前 document 的节点显隐状态变更事件 + * @param fn + */ onChangeNodeVisible(fn: (node: Node, visible: boolean) => void) { // TODO: history 变化时需要重新绑定 this[documentSymbol].nodesMap.forEach((node) => { @@ -135,30 +182,40 @@ export default class DocumentModel { }); } + /** + * 当前 document 的节点 children 变更事件 + * @param fn + */ onChangeNodeChildren(fn: (info?: IOnChangeOptions) => void) { // TODO: history 变化时需要重新绑定 this[documentSymbol].nodesMap.forEach((node) => { node.onChildrenChange((info?: InnerIOnChangeOptions) => { - return info ? fn({ - type: info.type, - node: Node.create(node)!, - }) : fn(); + return info + ? fn({ + type: info.type, + node: Node.create(node)!, + }) + : fn(); }); }); } /** * 当前 document 节点属性修改事件 + * @param fn */ onChangeNodeProp(fn: (info: PropChangeOptions) => void) { - this[editorSymbol].on(GlobalEvent.Node.Prop.InnerChange, (info: GlobalEvent.Node.Prop.ChangeOptions) => { - fn({ - key: info.key, - oldValue: info.oldValue, - newValue: info.newValue, - prop: Prop.create(info.prop)!, - node: Node.create(info.node as any)!, - }); - }); + this[editorSymbol].on( + GlobalEvent.Node.Prop.InnerChange, + (info: GlobalEvent.Node.Prop.ChangeOptions) => { + fn({ + key: info.key, + oldValue: info.oldValue, + newValue: info.newValue, + prop: Prop.create(info.prop)!, + node: Node.create(info.node as any)!, + }); + }, + ); } } diff --git a/packages/shell/src/node-children.ts b/packages/shell/src/node-children.ts index 1c5c9a9d4..9c1c8f7d0 100644 --- a/packages/shell/src/node-children.ts +++ b/packages/shell/src/node-children.ts @@ -1,7 +1,4 @@ -import { - NodeChildren as InnerNodeChildren, - Node as InnerNode, -} from '@ali/lowcode-designer'; +import { NodeChildren as InnerNodeChildren, Node as InnerNode } from '@ali/lowcode-designer'; import { NodeSchema } from '@ali/lowcode-types'; import Node from './node'; import { nodeSymbol, nodeChildrenSymbol } from './symbols'; @@ -18,6 +15,10 @@ export default class NodeChildren { return new NodeChildren(nodeChldren); } + getOwner() { + return Node.create(this[nodeChildrenSymbol].owner); + } + get size() { return this[nodeChildrenSymbol].size; } @@ -89,4 +90,17 @@ export default class NodeChildren { }), ); } + + mergeChildren( + remover: (node: Node, idx: number) => boolean, + adder: (children: Node[]) => any, + sorter: (firstNode: Node, secondNode: Node) => number, + ) { + this[nodeChildrenSymbol].mergeChildren( + (node: InnerNode, idx: number) => remover(Node.create(node)!, idx), + (children: InnerNode[]) => adder(children.map((node) => Node.create(node)!)), + (firstNode: InnerNode, secondNode: InnerNode) => + sorter(Node.create(firstNode)!, Node.create(secondNode)!), + ); + } } diff --git a/packages/shell/src/node.ts b/packages/shell/src/node.ts index b2ddbcbcd..b456197a6 100644 --- a/packages/shell/src/node.ts +++ b/packages/shell/src/node.ts @@ -23,77 +23,170 @@ export default class Node { return new Node(node); } + /** + * 返回节点 id + */ get id() { return this[nodeSymbol].id; } + /** + * 返回节点 componentName + */ get componentName() { return this[nodeSymbol].componentName; } + /** + * 获取节点所属的文档模型对象 + * @returns + */ getDocumentModel() { return DocumentModel.create(this[documentSymbol]); } + /** + * 获取指定 path 的属性模型实例 + * @param path 属性路径,支持 a / a.b / a.0 等格式 + * @returns + */ getProp(path: string): Prop | null { return Prop.create(this[nodeSymbol].getProp(path)); } + /** + * 获取指定 path 的属性模型实例值 + * @param path 属性路径,支持 a / a.b / a.0 等格式 + * @returns + */ getPropValue(path: string) { return this.getProp(path)?.getValue(); } + /** + * 获取指定 path 的属性模型实例, + * 注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级 + * @param path 属性路径,支持 a / a.b / a.0 等格式 + * @returns + */ getExtraProp(path: string): Prop | null { return Prop.create(this[nodeSymbol].getProp(getConvertedExtraKey(path))); } + /** + * 获取指定 path 的属性模型实例, + * 注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级 + * @param path 属性路径,支持 a / a.b / a.0 等格式 + * @returns + */ getExtraPropValue(path: string) { return this.getExtraProp(path)?.getValue(); } + /** + * 设置指定 path 的属性模型实例值 + * @param path 属性路径,支持 a / a.b / a.0 等格式 + * @param value 值 + * @returns + */ setPropValue(path: string, value: CompositeValue) { return this.getProp(path)?.setValue(value); } + /** + * 设置指定 path 的属性模型实例值 + * @param path 属性路径,支持 a / a.b / a.0 等格式 + * @param value 值 + * @returns + */ setExtraPropValue(path: string, value: CompositeValue) { return this.getExtraProp(path)?.setValue(value); } + /** + * 获取当前节点的前一个兄弟节点 + * @returns + */ getPrevSibling() { return Node.create(this[nodeSymbol].prevSibling); } + + /** + * 获取当前节点的后一个兄弟节点 + * @returns + */ getNextSibling() { return Node.create(this[nodeSymbol].nextSibling); } + /** + * 获取当前节点的父亲节点 + * @returns + */ getParent() { return Node.create(this[nodeSymbol].parent); } + /** + * 获取当前节点的孩子节点模型 + * @returns + */ getChildren() { return NodeChildren.create(this[nodeSymbol].children); } + /** + * 导入节点数据 + * @param data + */ importSchema(data: NodeSchema) { this[nodeSymbol].import(data); } + /** + * 导出节点数据 + * @param stage + * @param options + * @returns + */ exportSchema(stage?: TransformStage, options?: any) { return this[nodeSymbol].export(stage, options); } - insertBefore(node: InnerNode, ref?: InnerNode | undefined, useMutator?: boolean) { - this[nodeSymbol].insertBefore(node, ref, useMutator); + /** + * 在指定位置之前插入一个节点 + * @param node + * @param ref + * @param useMutator + */ + insertBefore(node: Node, ref?: Node | undefined, useMutator?: boolean) { + this[nodeSymbol].insertBefore(node[nodeSymbol], ref?.[nodeSymbol], useMutator); } - insertAfter(node: InnerNode, ref?: InnerNode | undefined, useMutator?: boolean) { - this[nodeSymbol].insertAfter(node, ref, useMutator); + /** + * 在指定位置之后插入一个节点 + * @param node + * @param ref + * @param useMutator + */ + insertAfter(node: Node, ref?: Node | undefined, useMutator?: boolean) { + this[nodeSymbol].insertAfter(node[nodeSymbol], ref?.[nodeSymbol], useMutator); } - replaceChild(node: InnerNode, data: any) { - return Node.create(this[nodeSymbol].replaceChild(node, data)); + /** + * 替换指定节点 + * @param node 待替换的子节点 + * @param data 用作替换的节点对象或者节点描述 + * @returns + */ + replaceChild(node: Node, data: any) { + return Node.create(this[nodeSymbol].replaceChild(node[nodeSymbol], data)); } + /** + * 将当前节点替换成指定节点描述 + * @param schema + */ replaceWith(schema: NodeSchema) { this[nodeSymbol].replaceWith(schema); } diff --git a/packages/shell/src/project.ts b/packages/shell/src/project.ts index ba306cc7a..5f338b7b6 100644 --- a/packages/shell/src/project.ts +++ b/packages/shell/src/project.ts @@ -1,10 +1,18 @@ -import { Project as InnerProject, PropsReducer, TransformStage } from '@ali/lowcode-designer'; +import { + BuiltinSimulatorHost, + Project as InnerProject, + PropsReducer as PropsTransducer, + TransformStage, +} from '@ali/lowcode-designer'; import { RootSchema, ProjectSchema } from '@ali/lowcode-types'; import DocumentModel from './document-model'; -import { projectSymbol } from './symbols'; +import SimulatorHost from './simulator-host'; +import { projectSymbol, simulatorHostSymbol, simulatorRendererSymbol } from './symbols'; export default class Project { private readonly [projectSymbol]: InnerProject; + private [simulatorHostSymbol]: BuiltinSimulatorHost; + private [simulatorRendererSymbol]: any; constructor(project: InnerProject) { this[projectSymbol] = project; @@ -25,37 +33,73 @@ export default class Project { return DocumentModel.create(documentModel); } + /** + * 创建一个 document + * @param data + * @returns + */ createDocument(data?: RootSchema): DocumentModel | null { const doc = this[projectSymbol].createDocument(data); return DocumentModel.create(doc); } + /** + * 根据 fileName 获取 document + * @param fileName + * @returns + */ getDocumentByFileName(fileName: string): DocumentModel | null { return DocumentModel.create(this[projectSymbol].getDocumentByFileName(fileName)); } + /** + * 根据 id 获取 document + * @param id + * @returns + */ getDocumentById(id: string): DocumentModel | null { return DocumentModel.create(this[projectSymbol].getDocument(id)); } + /** + * 获取当前 project 下所有 documents + * @returns + */ getDocuments(): DocumentModel[] { return this[projectSymbol].documents.map((doc) => DocumentModel.create(doc)!); } + /** + * 导出 project + * @returns + */ exportSchema() { return this[projectSymbol].getSchema(); } + /** + * 导入 project + * @param schema 待导入的 project 数据 + */ importSchema(schema?: ProjectSchema) { this[projectSymbol].load(schema, true); } + /** + * 获取当前的 document + * @returns + */ getCurrentDocument(): DocumentModel | null { return DocumentModel.create(this[projectSymbol].currentDocument); } - addPropsTransducer(reducer: PropsReducer, stage: TransformStage) { - this[projectSymbol].designer.addPropsReducer(reducer, stage); + /** + * 增加一个属性的管道处理函数 + * @param transducer + * @param stage + */ + addPropsTransducer(transducer: PropsTransducer, stage: TransformStage) { + this[projectSymbol].designer.addPropsReducer(transducer, stage); } /** @@ -71,21 +115,28 @@ export default class Project { /** * 当前 project 的模拟器 ready 事件 */ - onSimulatorReady(fn: () => void) { - // TODO: 补充 simulator 实例 - // TODO: 思考一下是否要实现补偿触发能力 - return this[projectSymbol].onSimulatorReady(() => { - fn(); + onSimulatorHostReady(fn: (host: SimulatorHost) => void) { + if (this[simulatorHostSymbol]) { + fn(SimulatorHost.create(this[simulatorHostSymbol])!); + return () => {}; + } + return this[projectSymbol].onSimulatorReady((simulator: BuiltinSimulatorHost) => { + this[simulatorHostSymbol] = simulator; + fn(SimulatorHost.create(simulator)!); }); } /** * 当前 project 的渲染器 ready 事件 */ - onRendererReady(fn: () => void) { + onSimulatorRendererReady(fn: () => void) { + if (this[simulatorRendererSymbol]) { + fn(); + return () => {}; + } // TODO: 补充 renderer 实例 - // TODO: 思考一下是否要实现补偿触发能力 - return this[projectSymbol].onRendererReady(() => { + return this[projectSymbol].onRendererReady((renderer: any) => { + this[simulatorRendererSymbol] = renderer; fn(); }); } diff --git a/packages/shell/src/simulator-host.ts b/packages/shell/src/simulator-host.ts new file mode 100644 index 000000000..006321350 --- /dev/null +++ b/packages/shell/src/simulator-host.ts @@ -0,0 +1,25 @@ +import { + BuiltinSimulatorHost, +} from '@ali/lowcode-designer'; +import { simulatorHostSymbol } from './symbols'; + +export default class SimulatorHost { + private readonly [simulatorHostSymbol]: BuiltinSimulatorHost; + + constructor(simulator: BuiltinSimulatorHost) { + this[simulatorHostSymbol] = simulator; + } + + static create(host: BuiltinSimulatorHost) { + if (!host) return null; + return new SimulatorHost(host); + } + + set(key: string, value: any) { + this[simulatorHostSymbol].set(key, value); + } + + get(key: string) { + return this[simulatorHostSymbol].get(key); + } +} diff --git a/packages/shell/src/symbols.ts b/packages/shell/src/symbols.ts index 44c4ee0ca..cb8ddc6a3 100644 --- a/packages/shell/src/symbols.ts +++ b/packages/shell/src/symbols.ts @@ -1,5 +1,5 @@ /** - * 以下 symbol 均用于在 plugin context 对外暴露的模型中存储相应内部模型的 key + * 以下 symbol 均用于在 shell 层对外暴露的模型中存储相应内部模型的 key */ export const projectSymbol = Symbol('project'); export const designerSymbol = Symbol('designer'); @@ -12,4 +12,6 @@ export const propsSymbol = Symbol('props'); export const propSymbol = Symbol('prop'); export const detectingSymbol = Symbol('detecting'); export const selectionSymbol = Symbol('selection'); -export const historySymbol = Symbol('history'); \ No newline at end of file +export const historySymbol = Symbol('history'); +export const simulatorHostSymbol = Symbol('simulatorHost'); +export const simulatorRendererSymbol = Symbol('simulatorRenderer'); \ No newline at end of file