lowcode-engine/packages/shell/src/model/document-model.ts

381 lines
10 KiB
TypeScript

import {
IDocumentModel as InnerDocumentModel,
INode as InnerNode,
} from '@alilc/lowcode-designer';
import {
IPublicEnumTransformStage,
IPublicTypeRootSchema,
GlobalEvent,
IPublicModelDocumentModel,
IPublicTypeOnChangeOptions,
IPublicTypeDragNodeObject,
IPublicTypeDragNodeDataObject,
IPublicModelNode,
IPublicModelSelection,
IPublicModelDetecting,
IPublicModelHistory,
IPublicApiProject,
IPublicModelModalNodesManager,
IPublicTypePropChangeOptions,
IPublicModelDropLocation,
IPublicApiCanvas,
IPublicTypeDisposable,
IPublicModelEditor,
IPublicTypeNodeSchema,
} from '@alilc/lowcode-types';
import { isDragNodeObject } from '@alilc/lowcode-utils';
import { Node as ShellNode } from './node';
import { Selection as ShellSelection } from './selection';
import { Detecting as ShellDetecting } from './detecting';
import { History as ShellHistory } from './history';
import { DropLocation as ShellDropLocation } from './drop-location';
import { Project as ShellProject, Canvas as ShellCanvas } from '../api';
import { Prop as ShellProp } from './prop';
import { ModalNodesManager } from './modal-nodes-manager';
import { documentSymbol, editorSymbol, nodeSymbol } from '../symbols';
const shellDocSymbol = Symbol('shellDocSymbol');
export class DocumentModel implements IPublicModelDocumentModel {
private readonly [documentSymbol]: InnerDocumentModel;
private readonly [editorSymbol]: IPublicModelEditor;
private _focusNode: IPublicModelNode | null;
selection: IPublicModelSelection;
detecting: IPublicModelDetecting;
history: IPublicModelHistory;
/**
* @deprecated use canvas API instead
*/
canvas: IPublicApiCanvas;
constructor(document: InnerDocumentModel) {
this[documentSymbol] = document;
this[editorSymbol] = document.designer?.editor as IPublicModelEditor;
this.selection = new ShellSelection(document);
this.detecting = new ShellDetecting(document);
this.history = new ShellHistory(document);
this.canvas = new ShellCanvas(this[editorSymbol]);
this._focusNode = ShellNode.create(this[documentSymbol].focusNode);
}
static create(document: InnerDocumentModel | undefined | null): IPublicModelDocumentModel | null {
if (!document) {
return null;
}
// @ts-ignore 直接返回已挂载的 shell doc 实例
if (document[shellDocSymbol]) {
return (document as any)[shellDocSymbol];
}
const shellDoc = new DocumentModel(document);
// @ts-ignore 直接返回已挂载的 shell doc 实例
document[shellDocSymbol] = shellDoc;
return shellDoc;
}
/**
* id
*/
get id(): string {
return this[documentSymbol].id;
}
set id(id) {
this[documentSymbol].id = id;
}
/**
* 获取当前文档所属的 project
* @returns
*/
get project(): IPublicApiProject {
return ShellProject.create(this[documentSymbol].project);
}
/**
* 获取文档的根节点
* root node of this documentModel
* @returns
*/
get root(): IPublicModelNode | null {
return ShellNode.create(this[documentSymbol].rootNode);
}
get focusNode(): IPublicModelNode | null {
return this._focusNode || this.root;
}
set focusNode(node: IPublicModelNode | null) {
this._focusNode = node;
this[editorSymbol].eventBus.emit(
'shell.document.focusNodeChanged',
{ document: this, focusNode: node },
);
}
/**
* 获取文档下所有节点 Map, key 为 nodeId
* get map of all nodes , using node.id as key
*/
get nodesMap(): Map<string, IPublicModelNode> {
const map = new Map<string, IPublicModelNode>();
for (let id of this[documentSymbol].nodesMap.keys()) {
map.set(id, this.getNodeById(id)!);
}
return map;
}
/**
* 模态节点管理
*/
get modalNodesManager(): IPublicModelModalNodesManager | null {
return ModalNodesManager.create(this[documentSymbol].modalNodesManager);
}
get dropLocation(): IPublicModelDropLocation | null {
return ShellDropLocation.create(this[documentSymbol].dropLocation);
}
set dropLocation(loc: IPublicModelDropLocation | null) {
this[documentSymbol].dropLocation = loc;
}
/**
* 根据 nodeId 返回 Node 实例
* get node instance by nodeId
* @param {string} nodeId
*/
getNodeById(nodeId: string): IPublicModelNode | null {
return ShellNode.create(this[documentSymbol].getNode(nodeId));
}
/**
* 导入 schema
* @param schema
*/
importSchema(schema: IPublicTypeRootSchema): void {
this[documentSymbol].import(schema);
this[editorSymbol].eventBus.emit('shell.document.importSchema', schema);
}
/**
* 导出 schema
* @param stage
* @returns
*/
exportSchema(stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Render): any {
return this[documentSymbol].export(stage);
}
/**
* 插入节点
* @param parent
* @param thing
* @param at
* @param copy
* @returns
*/
insertNode(
parent: IPublicModelNode,
thing: IPublicModelNode,
at?: number | null | undefined,
copy?: boolean | undefined,
): IPublicModelNode | null {
const node = this[documentSymbol].insertNode(
(parent as any)[nodeSymbol] ? (parent as any)[nodeSymbol] : parent,
(thing as any)?.[nodeSymbol] ? (thing as any)[nodeSymbol] : thing,
at,
copy,
);
return ShellNode.create(node);
}
/**
* 创建一个节点
* @param data
* @returns
*/
createNode(data: IPublicTypeNodeSchema): IPublicModelNode | null {
return ShellNode.create(this[documentSymbol].createNode(data));
}
/**
* 移除指定节点/节点id
* @param idOrNode
*/
removeNode(idOrNode: string | IPublicModelNode): void {
this[documentSymbol].removeNode(idOrNode as any);
}
/**
* componentsMap of documentModel
* @param extraComps
* @returns
*/
getComponentsMap(extraComps?: string[]): any {
return this[documentSymbol].getComponentsMap(extraComps);
}
/**
* 检查拖拽放置的目标节点是否可以放置该拖拽对象
* @param dropTarget 拖拽放置的目标节点
* @param dragObject 拖拽的对象
* @returns boolean 是否可以放置
*/
checkNesting(
dropTarget: IPublicModelNode,
dragObject: IPublicTypeDragNodeObject | IPublicTypeDragNodeDataObject,
): boolean {
let innerDragObject = dragObject;
if (isDragNodeObject(dragObject)) {
innerDragObject.nodes = innerDragObject.nodes?.map(
(node: IPublicModelNode) => ((node as any)[nodeSymbol] || node),
);
}
return this[documentSymbol].checkNesting(
((dropTarget as any)[nodeSymbol] || dropTarget) as any,
innerDragObject as any,
);
}
/**
* 当前 document 新增节点事件
*/
onAddNode(fn: (node: IPublicModelNode) => void): IPublicTypeDisposable {
return this[documentSymbol].onNodeCreate((node: InnerNode) => {
fn(ShellNode.create(node)!);
});
}
/**
* 当前 document 新增节点事件,此时节点已经挂载到 document 上
*/
onMountNode(fn: (payload: { node: IPublicModelNode }) => void): IPublicTypeDisposable {
this[editorSymbol].eventBus.on('node.add', fn as any);
return () => {
this[editorSymbol].eventBus.off('node.add', fn as any);
};
}
/**
* 当前 document 删除节点事件
*/
onRemoveNode(fn: (node: IPublicModelNode) => void): IPublicTypeDisposable {
return this[documentSymbol].onNodeDestroy((node: InnerNode) => {
fn(ShellNode.create(node)!);
});
}
/**
* 当前 document 的 hover 变更事件
*/
onChangeDetecting(fn: (node: IPublicModelNode) => void): IPublicTypeDisposable {
return this[documentSymbol].designer.detecting.onDetectingChange((node: InnerNode) => {
fn(ShellNode.create(node)!);
});
}
/**
* 当前 document 的选中变更事件
*/
onChangeSelection(fn: (ids: string[]) => void): IPublicTypeDisposable {
return this[documentSymbol].selection.onSelectionChange((ids: string[]) => {
fn(ids);
});
}
/**
* 当前 document 的节点显隐状态变更事件
* @param fn
*/
onChangeNodeVisible(fn: (node: IPublicModelNode, visible: boolean) => void): IPublicTypeDisposable {
return this[documentSymbol].onChangeNodeVisible((node: InnerNode, visible: boolean) => {
fn(ShellNode.create(node)!, visible);
});
}
/**
* 当前 document 的节点 children 变更事件
* @param fn
*/
onChangeNodeChildren(fn: (info: IPublicTypeOnChangeOptions) => void): IPublicTypeDisposable {
return this[documentSymbol].onChangeNodeChildren((info?: IPublicTypeOnChangeOptions<InnerNode>) => {
if (!info) {
return;
}
fn({
type: info.type,
node: ShellNode.create(info.node)!,
});
});
}
/**
* 当前 document 节点属性修改事件
* @param fn
*/
onChangeNodeProp(fn: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable {
const callback = (info: GlobalEvent.Node.Prop.ChangeOptions) => {
fn({
key: info.key,
oldValue: info.oldValue,
newValue: info.newValue,
prop: ShellProp.create(info.prop)!,
node: ShellNode.create(info.node as any)!,
});
};
this[editorSymbol].on(
GlobalEvent.Node.Prop.InnerChange,
callback,
);
return () => {
this[editorSymbol].off(
GlobalEvent.Node.Prop.InnerChange,
callback,
);
};
}
/**
* import schema event
* @param fn
*/
onImportSchema(fn: (schema: IPublicTypeRootSchema) => void): IPublicTypeDisposable {
return this[editorSymbol].eventBus.on('shell.document.importSchema', fn as any);
}
isDetectingNode(node: IPublicModelNode): boolean {
return this.detecting.current === node;
}
onFocusNodeChanged(
fn: (doc: IPublicModelDocumentModel, focusNode: IPublicModelNode) => void,
): IPublicTypeDisposable {
if (!fn) {
return () => {};
}
return this[editorSymbol].eventBus.on(
'shell.document.focusNodeChanged',
(payload) => {
const { document, focusNode } = payload;
fn(document, focusNode);
},
);
}
onDropLocationChanged(fn: (doc: IPublicModelDocumentModel) => void): IPublicTypeDisposable {
if (!fn) {
return () => {};
}
return this[editorSymbol].eventBus.on(
'document.dropLocation.changed',
(payload) => {
const { document } = payload;
fn(document);
},
);
}
}