diff --git a/packages/designer/src/document/document-model.ts b/packages/designer/src/document/document-model.ts index 3f4805e83..aad2e757e 100644 --- a/packages/designer/src/document/document-model.ts +++ b/packages/designer/src/document/document-model.ts @@ -1,5 +1,6 @@ import { computed, obx } from '@ali/lowcode-editor-core'; import { NodeData, isJSExpression, isDOMText, NodeSchema, isNodeSchema, RootSchema } from '@ali/lowcode-types'; +import { EventEmitter } from 'events'; import { Project } from '../project'; import { ISimulatorHost } from '../simulator'; import { ComponentMeta } from '../component-meta'; @@ -26,7 +27,7 @@ export class DocumentModel { /** * 文档编号 */ - readonly id: string = uniqueId('doc'); + id: string = uniqueId('doc'); /** * 选区控制 */ @@ -40,6 +41,8 @@ export class DocumentModel { @obx.val private nodes = new Set(); private seqId = 0; private _simulator?: ISimulatorHost; + private emitter: EventEmitter; + private rootNodeVisitorMap: { [visitorName: string]: any } = {}; /** * 模拟器 @@ -75,6 +78,7 @@ export class DocumentModel { console.info(this.willPurgeSpace); }, true); */ + this.emitter = new EventEmitter(); if (!schema) { this._blank = true; @@ -194,9 +198,14 @@ export class DocumentModel { this.nodesMap.set(node.id, node); this.nodes.add(node); + this.emitter.emit('nodecreate', node); return node as any; } + public destroyNode(node: Node) { + this.emitter.emit('nodedestroy', node); + } + /** * 插入一个节点 */ @@ -406,7 +415,7 @@ export class DocumentModel { /** * 打开,已载入,默认建立时就打开状态,除非手动关闭 */ - open(): void { + open(): DocumentModel { const originState = this._opened; this._opened = true; if (originState === false) { @@ -417,6 +426,7 @@ export class DocumentModel { } else { this.project.checkExclusive(this); } + return this; } /** @@ -499,6 +509,52 @@ export class DocumentModel { get root() { return this.rootNode; } + + onRendererReady(fn: (args: any) => void): () => void { + this.emitter.on('lowcode_engine_renderer_ready', fn); + return () => { + this.emitter.removeListener('lowcode_engine_renderer_ready', fn); + }; + } + + setRendererReady(renderer) { + this.emitter.emit('lowcode_engine_renderer_ready', renderer); + } + + acceptRootNodeVisitor( + visitorName: string = 'default', + visitorFn: (node: RootNode) => any ) { + let visitorResult = {}; + if (!visitorName) { + /* tslint:disable no-console */ + console.warn('Invalid or empty RootNodeVisitor name.'); + } + try { + visitorResult = visitorFn.call(this, this.rootNode); + this.rootNodeVisitorMap[visitorName] = visitorResult; + } catch (e) { + console.error('RootNodeVisitor is not valid.'); + } + return visitorResult; + } + + getRootNodeVisitor(name: string) { + return this.rootNodeVisitorMap[name]; + } + + onNodeCreate(func: (node: Node) => void) { + this.emitter.on('nodecreate', func); + return () => { + this.emitter.removeListener('nodecreate', func); + }; + } + + onNodeDestroy(func: (node: Node) => void) { + this.emitter.on('nodedestroy', func); + return () => { + this.emitter.removeListener('nodedestroy', func); + }; + } } export function isDocumentModel(obj: any): obj is DocumentModel { diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index 5a0136b65..54fe0c483 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -637,6 +637,8 @@ export class Node { this.autoruns?.forEach((dispose) => dispose()); this.props.purge(); this.document.internalRemoveAndPurgeNode(this); + + this.document.destroyNode(this); } // ======= compatible apis ==== diff --git a/packages/designer/src/project/project.ts b/packages/designer/src/project/project.ts index 8217697be..e010c5476 100644 --- a/packages/designer/src/project/project.ts +++ b/packages/designer/src/project/project.ts @@ -122,7 +122,7 @@ export class Project { if (data) { doc = new DocumentModel(this, data); this.documents.push(doc); - doc.open(); + return doc.open(); } return; @@ -134,7 +134,7 @@ export class Project { doc = new DocumentModel(this, doc); this.documents.push(doc); - doc.open(); + return doc.open(); } checkExclusive(actived: DocumentModel) { diff --git a/packages/designer/src/simulator.ts b/packages/designer/src/simulator.ts index f051bc1f8..e383517e3 100644 --- a/packages/designer/src/simulator.ts +++ b/packages/designer/src/simulator.ts @@ -142,7 +142,7 @@ export interface ISimulatorHost

extends ISensor { computeComponentInstanceRect(instance: ComponentInstance, selector?: string): DOMRect | null; findDOMNodes(instance: ComponentInstance, selector?: string): Array | null; - + /** * 销毁 */ diff --git a/packages/editor-preset-vision/src/editor.ts b/packages/editor-preset-vision/src/editor.ts index d9a3b7189..ff447d36c 100644 --- a/packages/editor-preset-vision/src/editor.ts +++ b/packages/editor-preset-vision/src/editor.ts @@ -5,6 +5,8 @@ import { Designer, LiveEditing, TransformStage, Node } from '@ali/lowcode-design import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane'; import { toCss } from '@ali/vu-css-style'; import logger from '@ali/vu-logger'; +import bus from './bus'; +import { VE_EVENTS } from './base/const'; import DesignerPlugin from '@ali/lowcode-plugin-designer'; import { Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton'; @@ -23,6 +25,12 @@ export const designer = new Designer({ editor: editor }); editor.set(Designer, designer); editor.set('designer', designer); +designer.project.onCurrentDocumentChange((doc) => { + doc.onRendererReady(() => { + bus.emit(VE_EVENTS.VE_PAGE_PAGE_READY); + }); +}); + // 节点 props 初始化 designer.addPropsReducer((props, node) => { // run initials diff --git a/packages/editor-preset-vision/src/pages.ts b/packages/editor-preset-vision/src/pages.ts index 23ad9fe23..bc3ee9d12 100644 --- a/packages/editor-preset-vision/src/pages.ts +++ b/packages/editor-preset-vision/src/pages.ts @@ -1,6 +1,7 @@ import { designer } from './editor'; import { RootSchema } from '@ali/lowcode-types'; import { DocumentModel } from '@ali/lowcode-designer'; +import NodeCacheVisitor from './rootNodeVisitor'; const { project } = designer; @@ -96,4 +97,15 @@ Object.defineProperty(pages, 'currentPage', { } }) +pages.onCurrentPageChange((page: DocumentModel) => { + if (!page) { return; } + page.acceptRootNodeVisitor('NodeCache', (rootNode) => { + const visitor: NodeCacheVisitor = page.getRootNodeVisitor('NodeCache'); + if (visitor) { + visitor.destroy(); + } + return new NodeCacheVisitor(page, rootNode); + }); +}); + export default pages; diff --git a/packages/editor-preset-vision/src/rootNodeVisitor.ts b/packages/editor-preset-vision/src/rootNodeVisitor.ts new file mode 100644 index 000000000..811aa5fd7 --- /dev/null +++ b/packages/editor-preset-vision/src/rootNodeVisitor.ts @@ -0,0 +1,91 @@ +import { findIndex } from 'lodash'; +import { DocumentModel, Node, Root } from '@ali/lowcode-designer'; + +/** + * RootNodeVisitor for VisualEngine Page + * + * - store / cache node + * - quickly find / search or do operations on Node + */ +export default class RootNodeVisitor { + public nodeIdMap: {[id: string]: Node} = {}; + public nodeFieldIdMap: {[fieldId: string]: Node} = {}; + public nodeList: Node[] = []; + + private page: DocumentModel; + private root: RootNode; + private cancelers: Function[] = []; + + constructor(page: DocumentModel, rootNode: RootNode) { + this.page = page; + this.root = rootNode; + + this._findNode(this.root); + this._init(); + } + + public getNodeList() { + return this.nodeList; + } + + public getNodeIdMap() { + return this.nodeIdMap; + } + + public getNodeFieldIdMap() { + return this.nodeFieldIdMap; + } + + public getNodeById(id?: string) { + if (!id) { return this.nodeIdMap; } + return this.nodeIdMap[id]; + } + + public getNodeByFieldId(fieldId?: string) { + if (!fieldId) { return this.nodeFieldIdMap; } + return this.nodeFieldIdMap[fieldId]; + } + + public destroy() { + this.cancelers.forEach((canceler) => canceler()); + } + + private _init() { + this.cancelers.push( + this.page.onNodeCreate((node) => { + this.nodeList.push(node); + this.nodeIdMap[node.id] = node; + if (node.getPropValue('fieldId')) { + this.nodeFieldIdMap[node.getPropValue('fieldId')] = node; + } + }), + ); + + this.cancelers.push( + this.page.onNodeDestroy((node) => { + const idx = findIndex(this.nodeList, (n) => node.id === n.id); + this.nodeList.splice(idx, 1); + delete this.nodeIdMap[node.id]; + if (node.getPropValue('fieldId')) { + delete this.nodeFieldIdMap[node.getPropValue('fieldId')]; + } + }), + ); + } + + private _findNode(node: Node) { + const props = node.getProps(); + const fieldId = props && props.getPropValue('fieldId'); + + this.nodeIdMap[node.getId()] = node; + this.nodeList.push(node); + if (fieldId) { + this.nodeFieldIdMap[fieldId] = node; + } + + const children = node.getChildren(); + if (children) { + children.forEach((child) => this._findNode(child)); + } + } +} diff --git a/packages/react-simulator-renderer/src/renderer.ts b/packages/react-simulator-renderer/src/renderer.ts index c5441de93..df316b5ce 100644 --- a/packages/react-simulator-renderer/src/renderer.ts +++ b/packages/react-simulator-renderer/src/renderer.ts @@ -17,10 +17,12 @@ import Leaf from './builtin-components/leaf'; export class SimulatorRenderer implements BuiltinSimulatorRenderer { readonly isSimulatorRenderer = true; private dispose?: () => void; + constructor() { if (!host) { return; } + this.dispose = host.connect(this, () => { // sync layout config @@ -286,6 +288,7 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer { document.body.classList.add('engine-document'); // important! Stylesheet.invoke depends reactRender(createElement(SimulatorRendererView, { renderer: this }), container); + host.document.setRendererReady(this); } }