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

331 lines
7.8 KiB
TypeScript

import { Editor } from '@alilc/lowcode-editor-core';
import {
DocumentModel as InnerDocumentModel,
Node as InnerNode,
IOnChangeOptions as InnerIOnChangeOptions,
DragObject as InnerDragObject,
DragNodeObject,
DragNodeDataObject,
isDragNodeObject,
} from '@alilc/lowcode-designer';
import {
TransformStage,
RootSchema,
GlobalEvent,
} from '@alilc/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 Canvas from './canvas';
import ModalNodesManager from './modal-nodes-manager';
import { documentSymbol, editorSymbol, nodeSymbol } from './symbols';
type IOnChangeOptions = {
type: string;
node: Node;
};
type PropChangeOptions = {
key?: string | number;
prop?: Prop;
node: Node;
newValue: any;
oldValue: any;
};
const Events = {
IMPORT_SCHEMA: 'shell.document.importSchema',
};
const shellDocSymbol = Symbol('shellDocSymbol');
export default class DocumentModel {
private readonly [documentSymbol]: InnerDocumentModel;
private readonly [editorSymbol]: Editor;
private _focusNode: Node;
public selection: Selection;
public detecting: Detecting;
public history: History;
public canvas: Canvas;
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);
this.canvas = new Canvas(document.designer);
this._focusNode = Node.create(this[documentSymbol].focusNode);
}
static create(document: InnerDocumentModel | undefined | null) {
if (!document) return null;
// @ts-ignore 直接返回已挂载的 shell doc 实例
if (document[shellDocSymbol]) return document[shellDocSymbol];
const shellDoc = new DocumentModel(document);
// @ts-ignore 直接返回已挂载的 shell doc 实例
document[shellDocSymbol] = shellDoc;
return shellDoc;
}
/**
* id
*/
get id() {
return this[documentSymbol].id;
}
set id(id) {
this[documentSymbol].id = id;
}
/**
* 获取当前文档所属的 project
* @returns
*/
get project() {
return Project.create(this[documentSymbol].project);
}
/**
* 获取文档的根节点
* @returns
*/
get root(): Node | null {
return Node.create(this[documentSymbol].getRoot());
}
get focusNode(): Node {
return this._focusNode || this.root;
}
set focusNode(node: Node) {
this._focusNode = node;
}
/**
* 获取文档下所有节点
* @returns
*/
get nodesMap() {
const map = new Map<string, Node>();
for (let id of this[documentSymbol].nodesMap.keys()) {
map.set(id, this.getNodeById(id)!);
}
return map;
}
/**
* 模态节点管理
*/
get modalNodesManager() {
return ModalNodesManager.create(this[documentSymbol].modalNodesManager);
}
// @TODO: 不能直接暴露
get dropLocation() {
return this[documentSymbol].dropLocation;
}
/**
* 根据 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);
this[editorSymbol].emit(Events.IMPORT_SCHEMA, schema);
}
/**
* 导出 schema
* @param stage
* @returns
*/
exportSchema(stage: TransformStage = TransformStage.Render) {
return this[documentSymbol].export(stage);
}
/**
* 插入节点
* @param parent
* @param thing
* @param at
* @param copy
* @returns
*/
insertNode(
parent: Node,
thing: Node,
at?: number | null | undefined,
copy?: boolean | undefined,
) {
const node = this[documentSymbol].insertNode(
parent[nodeSymbol] ? parent[nodeSymbol] : parent,
thing?.[nodeSymbol] ? thing[nodeSymbol] : thing,
at,
copy,
);
return Node.create(node);
}
/**
* 创建一个节点
* @param data
* @returns
*/
createNode(data: any) {
return Node.create(this[documentSymbol].createNode(data));
}
/**
* 移除指定节点/节点id
* @param idOrNode
*/
removeNode(idOrNode: string | Node) {
this[documentSymbol].removeNode(idOrNode as any);
}
/**
* componentsMap of documentModel
* @param extraComps
* @returns
*/
getComponentsMap(extraComps?: string[]) {
return this[documentSymbol].getComponentsMap(extraComps);
}
/**
* 检查拖拽放置的目标节点是否可以放置该拖拽对象
* @param dropTarget 拖拽放置的目标节点
* @param dragObject 拖拽的对象
* @returns boolean 是否可以放置
*/
checkNesting(dropTarget: Node, dragObject: DragNodeObject | DragNodeDataObject): boolean {
let innerDragObject: InnerDragObject = dragObject;
if (isDragNodeObject(dragObject)) {
innerDragObject.nodes = innerDragObject.nodes.map((node: Node) => (node[nodeSymbol] || node));
}
return this[documentSymbol].checkNesting(
(dropTarget[nodeSymbol] || dropTarget) as any,
innerDragObject as any,
);
}
/**
* 当前 document 新增节点事件
*/
onAddNode(fn: (node: Node) => void) {
return this[documentSymbol].onNodeCreate((node: InnerNode) => {
fn(Node.create(node)!);
});
}
/**
* 当前 document 新增节点事件,此时节点已经挂载到 document 上
*/
onMountNode(fn: (payload: { node: Node }) => void) {
this[editorSymbol].on('node.add', fn as any);
return () => {
this[editorSymbol].off('node.add', fn as any);
};
}
/**
* 当前 document 删除节点事件
*/
onRemoveNode(fn: (node: Node) => void) {
return this[documentSymbol].onNodeDestroy((node: InnerNode) => {
fn(Node.create(node)!);
});
}
/**
* 当前 document 的 hover 变更事件
*/
onChangeDetecting(fn: (node: Node) => void) {
return this[documentSymbol].designer.detecting.onDetectingChange((node: InnerNode) => {
fn(Node.create(node)!);
});
}
/**
* 当前 document 的选中变更事件
*/
onChangeSelection(fn: (ids: string[]) => void) {
return this[documentSymbol].selection.onSelectionChange((ids: string[]) => {
fn(ids);
});
}
/**
* 当前 document 的节点显隐状态变更事件
* @param fn
*/
onChangeNodeVisible(fn: (node: Node, visible: boolean) => void) {
// TODO: history 变化时需要重新绑定
this[documentSymbol].nodesMap.forEach((node) => {
node.onVisibleChange((flag: boolean) => {
fn(Node.create(node)!, flag);
});
});
}
/**
* 当前 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();
});
});
}
/**
* 当前 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)!,
});
},
);
}
/**
* import schema event
* @param fn
*/
onImportSchema(fn: (schema: RootSchema) => void) {
this[editorSymbol].on(Events.IMPORT_SCHEMA, fn as any);
}
}