From 344c0113a288478fc74964a93f4519cddd114d1f Mon Sep 17 00:00:00 2001 From: "lihao.ylh" Date: Tue, 14 Dec 2021 16:45:12 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/designer/src/designer/detecting.ts | 4 +- packages/designer/src/document/selection.ts | 2 +- .../designer/src/plugin/plugin-context.ts | 89 +++++----- .../designer/src/plugin/plugin-manager.ts | 13 +- packages/designer/src/plugin/plugin-types.ts | 11 +- packages/designer/src/project/project.ts | 4 + packages/editor-core/src/hotkey.ts | 16 ++ packages/engine/src/engine-core.ts | 24 +-- packages/plugin-outline-pane/src/tree-node.ts | 2 +- packages/shell/build.json | 5 + packages/shell/build.test.json | 6 + packages/shell/package.json | 55 ++++++ packages/shell/src/detecting.ts | 27 +++ packages/shell/src/document-model.ts | 164 ++++++++++++++++++ packages/shell/src/dragon.ts | 3 + packages/shell/src/event.ts | 3 + packages/shell/src/history.ts | 44 +++++ packages/shell/src/hotkey.ts | 11 ++ packages/shell/src/index.ts | 30 ++++ packages/shell/src/material.ts | 67 +++++++ packages/shell/src/node.ts | 91 ++++++++++ packages/shell/src/project.ts | 92 ++++++++++ packages/shell/src/prop.ts | 31 ++++ packages/shell/src/selection.ts | 49 ++++++ packages/shell/src/setters.ts | 19 ++ packages/shell/src/skeleton.ts | 55 ++++++ packages/shell/src/symbols.ts | 14 ++ packages/types/src/disposable.ts | 3 + packages/types/src/index.ts | 1 + 29 files changed, 864 insertions(+), 71 deletions(-) create mode 100644 packages/shell/build.json create mode 100644 packages/shell/build.test.json create mode 100644 packages/shell/package.json create mode 100644 packages/shell/src/detecting.ts create mode 100644 packages/shell/src/document-model.ts create mode 100644 packages/shell/src/dragon.ts create mode 100644 packages/shell/src/event.ts create mode 100644 packages/shell/src/history.ts create mode 100644 packages/shell/src/hotkey.ts create mode 100644 packages/shell/src/index.ts create mode 100644 packages/shell/src/material.ts create mode 100644 packages/shell/src/node.ts create mode 100644 packages/shell/src/project.ts create mode 100644 packages/shell/src/prop.ts create mode 100644 packages/shell/src/selection.ts create mode 100644 packages/shell/src/setters.ts create mode 100644 packages/shell/src/skeleton.ts create mode 100644 packages/shell/src/symbols.ts create mode 100644 packages/types/src/disposable.ts diff --git a/packages/designer/src/designer/detecting.ts b/packages/designer/src/designer/detecting.ts index e818d1379..566762233 100644 --- a/packages/designer/src/designer/detecting.ts +++ b/packages/designer/src/designer/detecting.ts @@ -39,7 +39,7 @@ export class Detecting { } } - release(node: Node) { + release(node: Node | null) { if (this._current === node) { this._current = null; this.emitter.emit(DETECTING_CHANGE_EVENT, this.current); @@ -52,7 +52,7 @@ export class Detecting { } } - onDetectingChange(fn: () => void) { + onDetectingChange(fn: (node: Node) => void) { this.emitter.on(DETECTING_CHANGE_EVENT, fn); return () => { this.emitter.off(DETECTING_CHANGE_EVENT, fn); diff --git a/packages/designer/src/document/selection.ts b/packages/designer/src/document/selection.ts index 0a74d3682..4ffb9bcda 100644 --- a/packages/designer/src/document/selection.ts +++ b/packages/designer/src/document/selection.ts @@ -161,7 +161,7 @@ export class Selection { return nodes; } - onSelectionChange(fn: () => void): () => void { + onSelectionChange(fn: (ids: string[]) => void): () => void { this.emitter.on('selectionchange', fn); return () => { this.emitter.removeListener('selectionchange', fn); diff --git a/packages/designer/src/plugin/plugin-context.ts b/packages/designer/src/plugin/plugin-context.ts index 8d7aac778..740e70ba1 100644 --- a/packages/designer/src/plugin/plugin-context.ts +++ b/packages/designer/src/plugin/plugin-context.ts @@ -1,53 +1,50 @@ -import { Editor, Hotkey, hotkey, getSetter, registerSetter, getSettersMap, engineConfig, EngineConfig } from '@ali/lowcode-editor-core'; -import { Skeleton } from '@ali/lowcode-editor-skeleton'; -import { ILowCodePluginConfig, ILowCodePluginManager, ILowCodePluginContext, IDesignerCabin } from './plugin-types'; -import { getLogger, Logger } from '../utils'; +import { Editor, engineConfig } from '@ali/lowcode-editor-core'; +import { Designer } from '@ali/lowcode-designer'; +import { Skeleton as InnerSkeleton } from '@ali/lowcode-editor-skeleton'; import { - registerMetadataTransducer, - addBuiltinComponentAction, - removeBuiltinComponentAction, -} from '../component-meta'; -import { Designer } from '../designer'; -import { Setters, Utils, utils } from '../types'; + Hotkey, + Project, + Skeleton, + Setters, + Material, + editorSymbol, + designerSymbol, + skeletonSymbol, +} from '@ali/lowcode-shell'; +import { getLogger, Logger } from '../utils/logger'; +import { ILowCodePluginContext } from './plugin-types'; +/** + * 一些 API 设计约定: + * 1. 事件的命名格式为:on[Will|Did]VerbNoun?,参考 https://code.visualstudio.com/api/references/vscode-api#events + * 2. 基于 Disposable 模式,对于事件的绑定、快捷键的绑定函数,返回值则是解绑函数 + */ export default class PluginContext implements ILowCodePluginContext { - editor: Editor; - skeleton: Skeleton; - designer: Designer; - hotkey: Hotkey; - logger: Logger; - plugins: ILowCodePluginManager; - designerCabin: IDesignerCabin; - setters: Setters; - utils: Utils; - engineConfig: EngineConfig; + private readonly [editorSymbol]: Editor; + private readonly [designerSymbol]: Designer; + private readonly [skeletonSymbol]: InnerSkeleton; + public hotkey: Hotkey; + public project: Project; + public skeleton: Skeleton; + public logger: Logger; + public setters: Setters; + public material: Material; - constructor(editor: Editor, plugins: ILowCodePluginManager) { - this.editor = editor; - this.designer = editor.get('designer')!; - this.skeleton = editor.get('skeleton')!; - this.hotkey = hotkey; - this.plugins = plugins; - this.designerCabin = this.createDesignerCabin(); - this.setters = { - getSetter, - registerSetter, - getSettersMap, - }; - this.engineConfig = engineConfig; - this.utils = utils; - } + constructor(editor: Editor) { + this[editorSymbol] = editor; + const designer = this[designerSymbol] = editor.get('designer')!; + const skeleton = this[skeletonSymbol] = editor.get('skeleton')!; - private createDesignerCabin(): IDesignerCabin { - return { - registerMetadataTransducer, - addBuiltinComponentAction, - removeBuiltinComponentAction, - }; - } - - setLogger(config: ILowCodePluginConfig): void { - this.logger = getLogger({ level: 'log', bizName: `designer:plugin:${config.name}` }); + // TODO: to be deleted + // this.editor = editor; + const project = designer.project; + this.hotkey = new Hotkey(); + this.project = new Project(project); + this.skeleton = new Skeleton(skeleton); + this.setters = new Setters(); + this.material = new Material(editor); + this.config = engineConfig; + // TODO: pluginName + this.logger = getLogger({ level: 'warn', bizName: 'designer:plugin:' }); } } - diff --git a/packages/designer/src/plugin/plugin-manager.ts b/packages/designer/src/plugin/plugin-manager.ts index 970ce16ec..184083e23 100644 --- a/packages/designer/src/plugin/plugin-manager.ts +++ b/packages/designer/src/plugin/plugin-manager.ts @@ -18,19 +18,24 @@ export class LowCodePluginManager implements ILowCodePluginManager { this.editor = editor; } - private _getLowCodePluginContext() { - return new LowCodePluginContext(this.editor, this); + private _getLowCodePluginContext(config: any) { + return new LowCodePluginContext(this.editor, config); } + // private getNewContext(config: any) { + // return new LowCodePluginContext2(this.editor, config); + // } + async register( pluginConfigCreator: (ctx: ILowCodePluginContext, pluginOptions?: any) => ILowCodePluginConfig, pluginOptions?: any, options?: LowCodeRegisterOptions, ): Promise { - const ctx = this._getLowCodePluginContext(); + const ctx = this._getLowCodePluginContext({ name: pluginConfigCreator.pluginName }); + // ctx.newCtx = this.getNewContext(); const config = pluginConfigCreator(ctx, pluginOptions); invariant(config.name, `${config.name} required`, config); - ctx.setLogger(config); + // ctx.setLogger(config); const allowOverride = options?.override === true; if (this.pluginsMap.has(config.name)) { if (!allowOverride) { diff --git a/packages/designer/src/plugin/plugin-types.ts b/packages/designer/src/plugin/plugin-types.ts index cf76d7b38..a76c83cd5 100644 --- a/packages/designer/src/plugin/plugin-types.ts +++ b/packages/designer/src/plugin/plugin-types.ts @@ -46,15 +46,16 @@ export interface IDesignerCabin { export interface ILowCodePluginContext { skeleton: Skeleton; - designer: Designer; - editor: Editor; + // designer: Designer; + // editor: Editor; hotkey: Hotkey; logger: Logger; - plugins: ILowCodePluginManager; - designerCabin: IDesignerCabin; + // plugins: ILowCodePluginManager; + // designerCabin: IDesignerCabin; setters: Setters; - utils: Utils; + // utils: Utils; engineConfig: EngineConfig; + material: any; } interface ILowCodePluginManagerPluginAccessor { diff --git a/packages/designer/src/project/project.ts b/packages/designer/src/project/project.ts index aaada91c8..8f696867c 100644 --- a/packages/designer/src/project/project.ts +++ b/packages/designer/src/project/project.ts @@ -192,6 +192,10 @@ export class Project { return this.documents.find(doc => doc.id === id) || null; } + getDocumentByFileName(fileName: string): DocumentModel | null { + return this.documents.find(doc => doc.fileName === fileName) || null; + } + @action createDocument(data?: RootSchema): DocumentModel { const doc = new DocumentModel(this, data || this?.data?.componentsTree?.[0]); diff --git a/packages/editor-core/src/hotkey.ts b/packages/editor-core/src/hotkey.ts index 6bf9dda5e..4375c9848 100644 --- a/packages/editor-core/src/hotkey.ts +++ b/packages/editor-core/src/hotkey.ts @@ -1,3 +1,4 @@ +import { isEqual } from 'lodash'; import { globalContext } from './di'; import { Editor } from './editor'; @@ -385,6 +386,21 @@ export class Hotkey { return this; } + unbind(combos: string[] | string, callback: HotkeyCallback, action?: string) { + const combinations = Array.isArray(combos) ? combos : [combos]; + + combinations.forEach(combination => { + const info: KeyInfo = getKeyInfo(combination, action); + const { key, modifiers } = info; + const idx = this.callBacks[key].findIndex(info => { + return isEqual(info.modifiers, modifiers) && info.callback === callback; + }); + if (idx !== -1) { + this.callBacks[key].splice(idx, 1); + } + }); + } + /** * resets all sequence counters except for the ones passed in */ diff --git a/packages/engine/src/engine-core.ts b/packages/engine/src/engine-core.ts index 357546f70..18ded7bd3 100644 --- a/packages/engine/src/engine-core.ts +++ b/packages/engine/src/engine-core.ts @@ -51,14 +51,14 @@ const setters: Setters = { export { editor, editorCabin, - skeleton, + // skeleton, skeletonCabin, designer, designerCabin, plugins, - setters, + // setters, project, - selection, + // selection, /** * 注册一些全局的切面 */ @@ -67,10 +67,10 @@ export { * 全局的一些数据存储 */ // store, - hotkey, + // hotkey, monitor, utils, - engineConfig, + // engineConfig, }; const getSelection = () => designer.currentDocument?.selection; @@ -78,16 +78,16 @@ const getSelection = () => designer.currentDocument?.selection; (window as any).AliLowCodeEngine = { editor, editorCabin, - skeleton, + // skeleton, skeletonCabin, designer, designerCabin, plugins, - setters, + // setters, project, - get selection() { - return getSelection(); - }, + // get selection() { + // return getSelection(); + // }, /** * 注册一些全局的切面 */ @@ -96,11 +96,11 @@ const getSelection = () => designer.currentDocument?.selection; * 全局的一些数据存储 */ // store, - hotkey, + // hotkey, monitor, init, utils, - engineConfig, + // engineConfig, }; // 处理 editor.set('assets'),将组件元数据创建好 diff --git a/packages/plugin-outline-pane/src/tree-node.ts b/packages/plugin-outline-pane/src/tree-node.ts index 07a2debd2..02d042258 100644 --- a/packages/plugin-outline-pane/src/tree-node.ts +++ b/packages/plugin-outline-pane/src/tree-node.ts @@ -11,7 +11,7 @@ export default class TreeNode { /** * 是否可以展开 */ - @computed get expandable(): boolean { + get expandable(): boolean { if (this.locked) return false; return this.hasChildren() || this.hasSlots() || this.dropDetail?.index != null; } diff --git a/packages/shell/build.json b/packages/shell/build.json new file mode 100644 index 000000000..bd5cf18dd --- /dev/null +++ b/packages/shell/build.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "build-plugin-component" + ] +} diff --git a/packages/shell/build.test.json b/packages/shell/build.test.json new file mode 100644 index 000000000..228c07e7f --- /dev/null +++ b/packages/shell/build.test.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "build-plugin-component", + "@ali/lowcode-test-mate/plugin/index.ts" + ] +} diff --git a/packages/shell/package.json b/packages/shell/package.json new file mode 100644 index 000000000..5b11f345f --- /dev/null +++ b/packages/shell/package.json @@ -0,0 +1,55 @@ +{ + "name": "@ali/lowcode-shell", + "version": "1.0.74", + "description": "Shell Layer for AliLowCodeEngine", + "main": "lib/index.js", + "module": "es/index.js", + "files": [ + "lib", + "es" + ], + "scripts": { + "build": "build-scripts build --skip-demo", + "test": "build-scripts test --config build.test.json", + "test:cov": "build-scripts test --config build.test.json --jest-coverage" + }, + "license": "MIT", + "dependencies": { + "@ali/lowcode-editor-core": "1.0.74", + "@ali/lowcode-editor-skeleton": "1.0.74", + "@ali/lowcode-types": "1.0.74", + "@ali/lowcode-utils": "1.0.74", + "classnames": "^2.2.6", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.5", + "react": "^16", + "react-dom": "^16.7.0", + "zen-logger": "^1.1.0" + }, + "devDependencies": { + "@ali/lowcode-test-mate": "^1.0.1", + "@alib/build-scripts": "^0.1.29", + "@testing-library/react": "^11.2.2", + "@types/classnames": "^2.2.7", + "@types/jest": "^26.0.16", + "@types/lodash": "^4.14.165", + "@types/medium-editor": "^5.0.3", + "@types/node": "^13.7.1", + "@types/react": "^16", + "@types/react-dom": "^16", + "babel-jest": "^26.5.2", + "build-plugin-component": "^0.2.10", + "build-scripts-config": "^0.1.8", + "jest": "^26.6.3", + "lodash": "^4.17.20", + "moment": "^2.29.1", + "typescript": "^4.0.3" + }, + "publishConfig": { + "registry": "https://registry.antfin-inc.com" + }, + "resolutions": { + "@builder/babel-preset-ice": "1.0.1" + }, + "gitHead": "19b2119b9f95f8a3da0851b3943774a770975991" +} diff --git a/packages/shell/src/detecting.ts b/packages/shell/src/detecting.ts new file mode 100644 index 000000000..bbcfc7e7b --- /dev/null +++ b/packages/shell/src/detecting.ts @@ -0,0 +1,27 @@ +import { + Detecting as InnerDetecting, + DocumentModel as InnerDocumentModel, +} from '@ali/lowcode-designer'; +import { documentSymbol, detectingSymbol } from './symbols'; + +export default class Detecting { + private readonly [documentSymbol]: InnerDocumentModel; + private readonly [detectingSymbol]: InnerDetecting; + + constructor(document: InnerDocumentModel) { + this[documentSymbol] = document; + this[detectingSymbol] = document.designer.detecting; + } + + capture(id: string) { + this[detectingSymbol].capture(this[documentSymbol].getNode(id)); + } + + release(id: string) { + this[detectingSymbol].release(this[documentSymbol].getNode(id)); + } + + leave() { + this[detectingSymbol].leave(this[documentSymbol]); + } +} \ No newline at end of file diff --git a/packages/shell/src/document-model.ts b/packages/shell/src/document-model.ts new file mode 100644 index 000000000..4ac53d311 --- /dev/null +++ b/packages/shell/src/document-model.ts @@ -0,0 +1,164 @@ +import { Editor } from '@ali/lowcode-editor-core'; +import { + DocumentModel as InnerDocumentModel, + Node as InnerNode, + ParentalNode, + IOnChangeOptions as InnerIOnChangeOptions, + PropChangeOptions as InnerPropChangeOptions, +} from '@ali/lowcode-designer'; +import { TransformStage, RootSchema, NodeSchema, NodeData, GlobalEvent } from '@ali/lowcode-types'; +import Node from './node'; +import Selection from './selection'; +import Detecting from './detecting'; +import History from './history'; +import Project from './project'; +import Prop from './prop'; +import { documentSymbol, editorSymbol } from './symbols'; + +type IOnChangeOptions = { + type: string; + node: Node; +}; + +type PropChangeOptions = { + key?: string | number; + prop?: Prop; + node: Node; + newValue: any; + oldValue: any; +}; + +export default class DocumentModel { + private readonly [documentSymbol]: InnerDocumentModel; + private readonly [editorSymbol]: Editor; + public selection: Selection; + public detecting: Detecting; + public history: History; + + constructor(document: InnerDocumentModel) { + this[documentSymbol] = document; + this[editorSymbol] = document.designer.editor as Editor; + this.selection = new Selection(document); + this.detecting = new Detecting(document); + this.history = new History(document); + } + + static create(document: InnerDocumentModel | undefined | null) { + if (document == undefined) return null; + return new DocumentModel(document); + } + + getProject() { + return Project.create(this[documentSymbol].project); + } + + getRoot() { + return Node.create(this[documentSymbol].getRoot()); + } + + getNodesMap() { + const map = new Map(); + for (let id in this[documentSymbol].nodesMap.keys()) { + map.set(id, this.getNodeById(id)!); + } + return map; + } + + getNodeById(nodeId: string) { + return Node.create(this[documentSymbol].getNode(nodeId)); + } + + importSchema(schema: RootSchema) { + this[documentSymbol].import(schema); + } + + exportSchema(stage?: TransformStage) { + return this[documentSymbol].export(stage); + } + + insertNode( + parent: ParentalNode, + thing: InnerNode | NodeData, + at?: number | null | undefined, + copy?: boolean | undefined, + ) { + const node = this[documentSymbol].insertNode(parent, thing, at, copy); + return Node.create(node); + } + + removeNode(idOrNode: string | InnerNode) { + this[documentSymbol].removeNode(idOrNode); + } + + /** + * 当前 document 新增节点事件 + */ + onAddNode(fn: (node: Node) => void) { + this[documentSymbol].onNodeCreate((node: InnerNode) => { + fn(Node.create(node)!); + }); + } + + /** + * 当前 document 删除节点事件 + */ + onRemoveNode(fn: (node: Node) => void) { + this[documentSymbol].onNodeDestroy((node: InnerNode) => { + fn(Node.create(node)!); + }); + } + + /** + * 当前 document 的 hover 变更事件 + */ + onChangeDetecting(fn: (node: Node) => void) { + this[documentSymbol].designer.detecting.onDetectingChange((node: InnerNode) => { + fn(Node.create(node)!); + }); + } + + /** + * 当前 document 的选中变更事件 + */ + onChangeSelection(fn: (ids: string[]) => void) { + this[documentSymbol].selection.onSelectionChange((ids: string[]) => { + fn(ids); + }); + } + + onChangeNodeVisible(fn: (node: Node, visible: boolean) => void) { + // TODO: history 变化时需要重新绑定 + this[documentSymbol].nodesMap.forEach((node) => { + node.onVisibleChange((flag: boolean) => { + fn(Node.create(node)!, flag); + }); + }); + } + + 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(); + }); + }); + } + + /** + * 当前 document 节点属性修改事件 + */ + 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)!, + }); + }); + } +} diff --git a/packages/shell/src/dragon.ts b/packages/shell/src/dragon.ts new file mode 100644 index 000000000..e11d9c2b9 --- /dev/null +++ b/packages/shell/src/dragon.ts @@ -0,0 +1,3 @@ +export default class Dragon { + +} \ No newline at end of file diff --git a/packages/shell/src/event.ts b/packages/shell/src/event.ts new file mode 100644 index 000000000..44c3a9d20 --- /dev/null +++ b/packages/shell/src/event.ts @@ -0,0 +1,3 @@ +export default class Event { + +} \ No newline at end of file diff --git a/packages/shell/src/history.ts b/packages/shell/src/history.ts new file mode 100644 index 000000000..cbf51d419 --- /dev/null +++ b/packages/shell/src/history.ts @@ -0,0 +1,44 @@ +import { History as InnerHistory, DocumentModel as InnerDocumentModel } from '@ali/lowcode-designer'; +import { documentSymbol, historySymbol } from './symbols'; + +export default class History { + private readonly [documentSymbol]: InnerDocumentModel; + private readonly [historySymbol]: InnerHistory; + + constructor(document: InnerDocumentModel) { + this[documentSymbol] = document; + this[historySymbol] = this[documentSymbol].getHistory(); + } + + go(cursor: number) { + this[historySymbol].go(cursor); + } + + back() { + this[historySymbol].back(); + } + + forward() { + this[historySymbol].forward(); + } + + savePoint() { + this[historySymbol].savePoint(); + } + + isSavePoint() { + return this[historySymbol].isSavePoint(); + } + + getState() { + return this[historySymbol].getState(); + } + + onChangeState(func: () => any) { + return this[historySymbol].onStateChange(func); + } + + onChangeCursor(func: () => any) { + return this[historySymbol].onCursor(func); + } +} diff --git a/packages/shell/src/hotkey.ts b/packages/shell/src/hotkey.ts new file mode 100644 index 000000000..82fbae44a --- /dev/null +++ b/packages/shell/src/hotkey.ts @@ -0,0 +1,11 @@ +import { hotkey, HotkeyCallback } from '@ali/lowcode-editor-core'; +import { Disposable } from '@ali/lowcode-types'; + +export default class Hotkey { + bind(combos: string[] | string, callback: HotkeyCallback, action?: string): Disposable { + hotkey.bind(combos, callback, action); + return () => { + hotkey.unbind(combos, callback, action); + }; + } +} \ No newline at end of file diff --git a/packages/shell/src/index.ts b/packages/shell/src/index.ts new file mode 100644 index 000000000..efea86a04 --- /dev/null +++ b/packages/shell/src/index.ts @@ -0,0 +1,30 @@ +import Detecting from './detecting'; +// import Dragon from './dragon'; +import DocumentModel from './document-model'; +import Event from './event'; +import History from './history'; +import Material from './material'; +import Node from './node'; +import Project from './project'; +import Prop from './prop'; +import Selection from './selection'; +import Setters from './setters'; +import Hotkey from './hotkey'; +import Skeleton from './skeleton'; +export * from './symbols'; + +export { + DocumentModel, + Detecting, + // Dragon, + Event, + History, + Material, + Node, + Project, + Prop, + Selection, + Setters, + Hotkey, + Skeleton, +}; \ No newline at end of file diff --git a/packages/shell/src/material.ts b/packages/shell/src/material.ts new file mode 100644 index 000000000..c50c19ecd --- /dev/null +++ b/packages/shell/src/material.ts @@ -0,0 +1,67 @@ +import { Editor } from '@ali/lowcode-editor-core'; +import { + Designer, + registerMetadataTransducer, + MetadataTransducer, + getRegisteredMetadataTransducers, + addBuiltinComponentAction, + removeBuiltinComponentAction, + modifyBuiltinComponentAction, +} from '@ali/lowcode-designer'; +import { AssetsJson } from '@ali/lowcode-utils'; +import { ComponentAction } from '@ali/lowcode-types'; +import { editorSymbol, designerSymbol } from './symbols'; + +export default class Material { + private readonly [editorSymbol]: Editor; + private readonly [designerSymbol]: Designer; + + constructor(editor: Editor) { + this[editorSymbol] = editor; + this[designerSymbol] = editor.get('designer')!; + } + + setAssets(assets: AssetsJson) { + return this[editorSymbol].setAssets(assets); + } + + getAssets() { + return this[editorSymbol].get('assets'); + } + + loadIncrementalAssets(incrementalAssets: AssetsJson) { + return this[designerSymbol].loadIncrementalAssets(incrementalAssets); + } + + registerMetadataTransducer( + transducer: MetadataTransducer, + level?: number, + id?: string | undefined, + ) { + registerMetadataTransducer(transducer, level, id); + } + + getRegisteredMetadataTransducers() { + return getRegisteredMetadataTransducers(); + } + + getComponentMeta(componentName: string) { + return this[designerSymbol].getComponentMeta(componentName); + } + + getComponentsMap() { + return this[designerSymbol].componentsMap; + } + + addBuiltinComponentAction(action: ComponentAction) { + addBuiltinComponentAction(action); + } + + removeBuiltinComponentAction(name: string) { + removeBuiltinComponentAction(name); + } + + modifyBuiltinComponentAction(actionName: string, handle: (action: ComponentAction) => void) { + modifyBuiltinComponentAction(actionName, handle); + } +} diff --git a/packages/shell/src/node.ts b/packages/shell/src/node.ts new file mode 100644 index 000000000..58f9301ea --- /dev/null +++ b/packages/shell/src/node.ts @@ -0,0 +1,91 @@ +import { + DocumentModel as InnerDocumentModel, + Node as InnerNode, + getConvertedExtraKey, +} from '@ali/lowcode-designer'; +import { CompositeValue, NodeSchema, TransformStage } from '@ali/lowcode-types'; +import Prop from './prop'; +import DocumentModel from './document-model'; +import { documentSymbol, nodeSymbol } from './symbols'; + +export default class Node { + private readonly [documentSymbol]: InnerDocumentModel; + private readonly [nodeSymbol]: InnerNode; + + constructor(node: InnerNode) { + this[nodeSymbol] = node; + this[documentSymbol] = node.document; + } + + static create(node: InnerNode | null) { + if (!node) return null; + return new Node(node); + } + + getDocumentModel() { + return DocumentModel.create(this[documentSymbol]); + } + + getProp(path: string): Prop | null { + return Prop.create(this[nodeSymbol].getProp(path)); + } + + getPropValue(path: string) { + return this.getProp(path)?.getValue(); + } + + getExtraProp(path: string): Prop | null { + return Prop.create(this[nodeSymbol].getProp(getConvertedExtraKey(path))); + } + + getExtraPropValue(path: string) { + return this.getExtraProp(path)?.getValue(); + } + + setPropValue(path: string, value: CompositeValue) { + return this.getProp(path)?.setValue(value); + } + + setExtraPropValue(path: string, value: CompositeValue) { + return this.getExtraProp(path)?.setValue(value); + } + + getPrevSibling() { + return this[nodeSymbol].prevSibling; + } + getNextSibling() { + return this[nodeSymbol].nextSibling; + } + + getParent() { + return this[nodeSymbol].parent; + } + + getChildren() { + return this[nodeSymbol].children; + } + + importSchema(data: NodeSchema) { + this[nodeSymbol].import(data); + } + + 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); + } + + insertAfter(node: InnerNode, ref?: InnerNode | undefined, useMutator?: boolean) { + this[nodeSymbol].insertAfter(node, ref, useMutator); + } + + replaceChild(node: InnerNode, data: any) { + return Node.create(this[nodeSymbol].replaceChild(node, data)); + } + + replaceWith(schema: NodeSchema) { + this[nodeSymbol].replaceWith(schema); + } +} diff --git a/packages/shell/src/project.ts b/packages/shell/src/project.ts new file mode 100644 index 000000000..ba306cc7a --- /dev/null +++ b/packages/shell/src/project.ts @@ -0,0 +1,92 @@ +import { Project as InnerProject, PropsReducer, TransformStage } from '@ali/lowcode-designer'; +import { RootSchema, ProjectSchema } from '@ali/lowcode-types'; +import DocumentModel from './document-model'; +import { projectSymbol } from './symbols'; + +export default class Project { + private readonly [projectSymbol]: InnerProject; + + constructor(project: InnerProject) { + this[projectSymbol] = project; + } + + static create(project: InnerProject) { + return new Project(project); + } + + /** + * 打开一个 document + * @param doc + * @returns + */ + openDocument(doc?: string | RootSchema | undefined) { + const documentModel = this[projectSymbol].open(doc); + if (!documentModel) return null; + return DocumentModel.create(documentModel); + } + + createDocument(data?: RootSchema): DocumentModel | null { + const doc = this[projectSymbol].createDocument(data); + return DocumentModel.create(doc); + } + + getDocumentByFileName(fileName: string): DocumentModel | null { + return DocumentModel.create(this[projectSymbol].getDocumentByFileName(fileName)); + } + + getDocumentById(id: string): DocumentModel | null { + return DocumentModel.create(this[projectSymbol].getDocument(id)); + } + + getDocuments(): DocumentModel[] { + return this[projectSymbol].documents.map((doc) => DocumentModel.create(doc)!); + } + + exportSchema() { + return this[projectSymbol].getSchema(); + } + + importSchema(schema?: ProjectSchema) { + this[projectSymbol].load(schema, true); + } + + getCurrentDocument(): DocumentModel | null { + return DocumentModel.create(this[projectSymbol].currentDocument); + } + + addPropsTransducer(reducer: PropsReducer, stage: TransformStage) { + this[projectSymbol].designer.addPropsReducer(reducer, stage); + } + + /** + * 当前 project 内的 document 变更事件 + */ + onChangeDocument(fn: (doc: DocumentModel) => void) { + // TODO: 思考一下是否要实现补偿触发能力 + return this[projectSymbol].onCurrentDocumentChange((originalDoc) => { + fn(DocumentModel.create(originalDoc)!); + }); + } + + /** + * 当前 project 的模拟器 ready 事件 + */ + onSimulatorReady(fn: () => void) { + // TODO: 补充 simulator 实例 + // TODO: 思考一下是否要实现补偿触发能力 + return this[projectSymbol].onSimulatorReady(() => { + fn(); + }); + } + + /** + * 当前 project 的渲染器 ready 事件 + */ + onRendererReady(fn: () => void) { + // TODO: 补充 renderer 实例 + // TODO: 思考一下是否要实现补偿触发能力 + return this[projectSymbol].onRendererReady(() => { + fn(); + }); + } +} diff --git a/packages/shell/src/prop.ts b/packages/shell/src/prop.ts new file mode 100644 index 000000000..ffe20599c --- /dev/null +++ b/packages/shell/src/prop.ts @@ -0,0 +1,31 @@ +import { Prop as InnerProp } from '@ali/lowcode-designer'; +import { CompositeValue } from '@ali/lowcode-types'; +import { propSymbol } from './symbols'; +import Node from './node'; + +export default class Prop { + private readonly [propSymbol]: InnerProp; + + constructor(prop: InnerProp) { + this[propSymbol] = prop; + } + + static create(prop: InnerProp | undefined | null) { + if (!prop) return null; + return new Prop(prop); + } + + getNode() { + return Node.create(this[propSymbol].getNode()); + } + + setValue(val: CompositeValue) { + this[propSymbol].setValue(val); + } + + getValue() { + return this[propSymbol].getValue(); + } + + exportSchema() {} +} \ No newline at end of file diff --git a/packages/shell/src/selection.ts b/packages/shell/src/selection.ts new file mode 100644 index 000000000..0ce6b0a60 --- /dev/null +++ b/packages/shell/src/selection.ts @@ -0,0 +1,49 @@ +import { + DocumentModel as InnerDocumentModel, + Node as InnerNode, + Selection as InnerSelection, +} from '@ali/lowcode-designer'; +import Node from './node'; +import { documentSymbol, selectionSymbol } from './symbols'; + +export default class Selection { + private readonly [documentSymbol]: InnerDocumentModel; + private readonly [selectionSymbol]: InnerSelection; + + constructor(document: InnerDocumentModel) { + this[documentSymbol] = document; + this[selectionSymbol] = document.selection; + } + + select(id: string) { + this[selectionSymbol].select(id); + } + + selectAll(ids: string[]) { + this[selectionSymbol].selectAll(ids); + } + + getSelected() { + return this[selectionSymbol].selected; + } + + remove(id: string) { + this[selectionSymbol].remove(id); + } + + clear() { + this[selectionSymbol].clear(); + } + + has(id: string) { + return this[selectionSymbol].has(id); + } + + add(id: string) { + this[selectionSymbol].add(id); + } + + getNodes() { + return this[selectionSymbol].getNodes().map((node: InnerNode) => Node.create(node)); + } +} diff --git a/packages/shell/src/setters.ts b/packages/shell/src/setters.ts new file mode 100644 index 000000000..febab29b3 --- /dev/null +++ b/packages/shell/src/setters.ts @@ -0,0 +1,19 @@ +import { getSetter, registerSetter, getSettersMap, RegisteredSetter } from '@ali/lowcode-editor-core'; +import { CustomView } from '@ali/lowcode-types'; + +export default class Setters { + getSetter(type: string) { + return getSetter(type); + } + + getSettersMap() { + return getSettersMap(); + } + + registerSetter( + typeOrMaps: string | { [key: string]: CustomView | RegisteredSetter }, + setter?: CustomView | RegisteredSetter | undefined, + ) { + return registerSetter(typeOrMaps, setter); + } +} diff --git a/packages/shell/src/skeleton.ts b/packages/shell/src/skeleton.ts new file mode 100644 index 000000000..b17a10e41 --- /dev/null +++ b/packages/shell/src/skeleton.ts @@ -0,0 +1,55 @@ +import { + Skeleton as InnerSkeleton, + IWidgetBaseConfig, + IWidgetConfigArea, +} from '@ali/lowcode-editor-skeleton'; +import { skeletonSymbol } from './symbols'; + +export default class Skeleton { + private readonly [skeletonSymbol]: InnerSkeleton; + + constructor(skeleton: InnerSkeleton) { + this[skeletonSymbol] = skeleton; + } + + add(config: IWidgetBaseConfig, extraConfig?: Record) { + return this[skeletonSymbol].add(config, extraConfig); + } + + remove(config: IWidgetBaseConfig) { + const { area, name } = config; + const skeleton = this[skeletonSymbol]; + if (!normalizeArea(area)) return; + skeleton[normalizeArea(area)!].container.remove(name); + } +} + +function normalizeArea(area: IWidgetConfigArea | undefined) { + switch (area) { + case 'leftArea': + case 'left': + return 'leftArea'; + case 'rightArea': + case 'right': + return 'rightArea'; + case 'topArea': + case 'top': + return 'topArea'; + case 'toolbar': + return 'toolbar'; + case 'mainArea': + case 'main': + case 'center': + case 'centerArea': + return 'mainArea'; + case 'bottomArea': + case 'bottom': + return 'bottomArea'; + case 'leftFixedArea': + return 'leftFixedArea'; + case 'leftFloatArea': + return 'leftFloatArea'; + case 'stages': + return 'stages'; + } +} diff --git a/packages/shell/src/symbols.ts b/packages/shell/src/symbols.ts new file mode 100644 index 000000000..efd36c1d0 --- /dev/null +++ b/packages/shell/src/symbols.ts @@ -0,0 +1,14 @@ +/** + * 以下 symbol 均用于在 plugin context 对外暴露的模型中存储相应内部模型的 key + */ +export const projectSymbol = Symbol('project'); +export const designerSymbol = Symbol('designer'); +export const skeletonSymbol = Symbol('skeleton'); +export const documentSymbol = Symbol('document'); +export const editorSymbol = Symbol('editor'); +export const nodeSymbol = Symbol('node'); +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 diff --git a/packages/types/src/disposable.ts b/packages/types/src/disposable.ts new file mode 100644 index 000000000..1743677f1 --- /dev/null +++ b/packages/types/src/disposable.ts @@ -0,0 +1,3 @@ +export interface Disposable { + (): void; +} \ No newline at end of file diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index ca45efdc6..dfd2e64e5 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -20,3 +20,4 @@ export * from './code-intermediate'; export * from './code-result'; export * from './assets'; export * as GlobalEvent from './event'; +export * from './disposable';