mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
747 lines
19 KiB
TypeScript
747 lines
19 KiB
TypeScript
import { omit } from 'lodash';
|
|
import { inject, injectable, multiInject, optional, postConstruct } from 'inversify';
|
|
import { type Disposable, Emitter } from '@flowgram.ai/utils';
|
|
import { type EntityData, type EntityDataRegistry, EntityManager } from '@flowgram.ai/core';
|
|
|
|
import {
|
|
AddNodeData,
|
|
DEFAULT_FLOW_NODE_META,
|
|
type FlowDocumentJSON,
|
|
FlowLayout,
|
|
FlowLayoutDefault,
|
|
FlowNodeBaseType,
|
|
type FlowNodeJSON,
|
|
FlowNodeRegistry,
|
|
FlowNodeType,
|
|
} from './typings';
|
|
import { FlowVirtualTree } from './flow-virtual-tree';
|
|
import { FlowRenderTree } from './flow-render-tree';
|
|
import {
|
|
ConstantKeys,
|
|
FlowDocumentOptions,
|
|
FlowDocumentOptionsDefault,
|
|
} from './flow-document-options';
|
|
import { FlowDocumentContribution } from './flow-document-contribution';
|
|
import { FlowDocumentConfig } from './flow-document-config';
|
|
import { FlowDocumentTransformerEntity, FlowNodeEntity, FlowRendererStateEntity } from './entities';
|
|
|
|
export type FlowDocumentProvider = () => FlowDocument;
|
|
export const FlowDocumentProvider = Symbol('FlowDocumentProvider');
|
|
/**
|
|
* 流程整个文档数据
|
|
*/
|
|
@injectable()
|
|
export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
|
|
@inject(EntityManager) protected entityManager: EntityManager;
|
|
|
|
@inject(FlowDocumentConfig) readonly config: FlowDocumentConfig;
|
|
|
|
/**
|
|
* 流程画布配置项
|
|
*/
|
|
@inject(FlowDocumentOptions) @optional() public options: FlowDocumentOptions;
|
|
|
|
@multiInject(FlowDocumentContribution)
|
|
@optional()
|
|
protected contributions: FlowDocumentContribution[] = [];
|
|
|
|
protected registers = new Map<FlowNodeType, FlowNodeRegistry>();
|
|
|
|
private nodeRegistryCache = new Map<string, any>();
|
|
|
|
protected nodeDataRegistries: EntityDataRegistry[] = [];
|
|
|
|
protected layouts: FlowLayout[] = [];
|
|
|
|
protected currentLayoutKey: string = '';
|
|
|
|
protected onNodeUpdateEmitter = new Emitter<{
|
|
node: FlowNodeEntity;
|
|
/**
|
|
* use 'json' instead
|
|
* @deprecated
|
|
*/
|
|
data: FlowNodeJSON;
|
|
json: FlowNodeJSON;
|
|
}>();
|
|
|
|
protected onNodeCreateEmitter = new Emitter<{
|
|
node: FlowNodeEntity;
|
|
/**
|
|
* use 'json' instead
|
|
* @deprecated
|
|
*/
|
|
data: FlowNodeJSON;
|
|
json: FlowNodeJSON;
|
|
}>();
|
|
|
|
protected onNodeDisposeEmitter = new Emitter<{
|
|
node: FlowNodeEntity;
|
|
}>();
|
|
|
|
protected onLayoutChangeEmitter = new Emitter<FlowLayout>();
|
|
|
|
readonly onNodeUpdate = this.onNodeUpdateEmitter.event;
|
|
|
|
readonly onNodeCreate = this.onNodeCreateEmitter.event;
|
|
|
|
readonly onNodeDispose = this.onNodeDisposeEmitter.event;
|
|
|
|
readonly onLayoutChange = this.onLayoutChangeEmitter.event;
|
|
|
|
private _disposed = false;
|
|
|
|
root: FlowNodeEntity;
|
|
|
|
/**
|
|
* 原始的 tree 结构
|
|
*/
|
|
originTree: FlowVirtualTree<FlowNodeEntity>;
|
|
|
|
transformer: FlowDocumentTransformerEntity;
|
|
|
|
/**
|
|
* 渲染相关的全局轧辊台
|
|
*/
|
|
renderState: FlowRendererStateEntity;
|
|
|
|
/**
|
|
* 渲染后的 tree 结构
|
|
*/
|
|
renderTree: FlowRenderTree<FlowNodeEntity>;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get disposed(): boolean {
|
|
return this._disposed;
|
|
}
|
|
|
|
@postConstruct()
|
|
init(): void {
|
|
if (!this.options) this.options = FlowDocumentOptionsDefault;
|
|
this.currentLayoutKey = this.options.defaultLayout || FlowLayoutDefault.VERTICAL_FIXED_LAYOUT;
|
|
this.contributions.forEach((contrib) => contrib.registerDocument?.(this));
|
|
this.root = this.addNode({ id: 'root', type: FlowNodeBaseType.ROOT });
|
|
this.originTree = new FlowVirtualTree<FlowNodeEntity>(this.root);
|
|
this.transformer = this.entityManager.createEntity<FlowDocumentTransformerEntity>(
|
|
FlowDocumentTransformerEntity,
|
|
{ document: this }
|
|
);
|
|
this.renderState =
|
|
this.entityManager.createEntity<FlowRendererStateEntity>(FlowRendererStateEntity);
|
|
this.renderTree = new FlowRenderTree<FlowNodeEntity>(this.root, this.originTree, this);
|
|
// 布局第一次加载时候触发一次
|
|
this.layout.reload?.();
|
|
}
|
|
|
|
/**
|
|
* 从数据初始化 O(n)
|
|
* @param json
|
|
*/
|
|
/**
|
|
* 加载数据,可以被重载
|
|
* @param json 文档数据更新
|
|
* @param fireRender 是否要触发渲染,默认 true
|
|
*/
|
|
fromJSON(json: FlowDocumentJSON | any, fireRender = true): void {
|
|
if (this._disposed) return;
|
|
// 清空 tree 数据 重新计算
|
|
this.originTree.clear();
|
|
this.renderTree.clear();
|
|
// 暂停触发画布更新
|
|
this.entityManager.changeEntityLocked = true;
|
|
// 添加前的节点
|
|
const oldNodes = this.entityManager.getEntities<FlowNodeEntity>(FlowNodeEntity);
|
|
// 添加后的节点
|
|
const newNodes: FlowNodeEntity[] = [this.root];
|
|
this.addBlocksAsChildren(this.root, json.nodes || [], newNodes);
|
|
// 删除无效的节点
|
|
oldNodes.forEach((node) => {
|
|
if (!newNodes.includes(node)) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
this.entityManager.changeEntityLocked = false;
|
|
this.transformer.loading = false;
|
|
if (fireRender) this.fireRender();
|
|
}
|
|
|
|
get layout(): FlowLayout {
|
|
const layout = this.layouts.find((layout) => layout.name == this.currentLayoutKey);
|
|
if (!layout) {
|
|
throw new Error(`Unknown flow layout: ${this.currentLayoutKey}`);
|
|
}
|
|
return layout;
|
|
}
|
|
|
|
async load(): Promise<void> {
|
|
await Promise.all(this.contributions.map((c) => c.loadDocument?.(this)));
|
|
}
|
|
|
|
get loading(): boolean {
|
|
return this.transformer.loading;
|
|
}
|
|
|
|
/**
|
|
* 触发 render
|
|
*/
|
|
fireRender(): void {
|
|
if (this.transformer.isTreeDirty()) {
|
|
this.entityManager.fireEntityChanged(FlowNodeEntity.type);
|
|
this.entityManager.fireEntityChanged(FlowDocumentTransformerEntity.type);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 从指定节点的下一个节点新增
|
|
* @param fromNode
|
|
* @param json
|
|
*/
|
|
addFromNode(fromNode: FlowNodeEntity | string, json: FlowNodeJSON): FlowNodeEntity {
|
|
const node = typeof fromNode === 'string' ? this.getNode(fromNode)! : fromNode;
|
|
this.entityManager.changeEntityLocked = true;
|
|
const { parent } = node;
|
|
const result = this.addNode({
|
|
...json,
|
|
parent,
|
|
// originParent,
|
|
});
|
|
this.originTree.insertAfter(node, result);
|
|
this.entityManager.changeEntityLocked = false;
|
|
this.entityManager.fireEntityChanged(FlowNodeEntity.type);
|
|
return result;
|
|
}
|
|
|
|
removeNode(node: FlowNodeEntity | string) {
|
|
if (typeof node === 'string') {
|
|
this.getNode(node)?.dispose();
|
|
} else {
|
|
node.dispose();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 添加节点,如果节点已经存在则不会重复创建
|
|
* @param data
|
|
* @param addedNodes
|
|
*/
|
|
addNode(data: AddNodeData, addedNodes?: FlowNodeEntity[]): FlowNodeEntity {
|
|
const { id, type = 'block', originParent, parent, meta, hidden, index } = data;
|
|
let node = this.getNode(id);
|
|
let isNew = false;
|
|
const register = this.getNodeRegistry(type, data.originParent);
|
|
// node 类型变化则全部删除重新来
|
|
if (node && node.flowNodeType !== data.type) {
|
|
node.dispose();
|
|
node = undefined;
|
|
}
|
|
if (!node) {
|
|
const { dataRegistries } = register;
|
|
node = this.entityManager.createEntity<FlowNodeEntity>(FlowNodeEntity, {
|
|
id,
|
|
document: this,
|
|
flowNodeType: type,
|
|
originParent,
|
|
meta,
|
|
});
|
|
const datas = dataRegistries
|
|
? this.nodeDataRegistries.concat(...dataRegistries)
|
|
: this.nodeDataRegistries;
|
|
node.addInitializeData(datas);
|
|
node.onDispose(() => this.onNodeDisposeEmitter.fire({ node: node! }));
|
|
this.options.fromNodeJSON?.(node, data, true);
|
|
isNew = true;
|
|
} else {
|
|
this.options.fromNodeJSON?.(node, data, false);
|
|
}
|
|
// 初始化数据重制
|
|
node.initData({
|
|
originParent,
|
|
parent,
|
|
meta,
|
|
hidden,
|
|
index,
|
|
});
|
|
// 开始节点加到 root 里边
|
|
if (node.isStart) {
|
|
this.root.addChild(node);
|
|
}
|
|
addedNodes?.push(node);
|
|
// 自定义创建逻辑
|
|
if (register.onCreate) {
|
|
const extendNodes = register.onCreate(node, data);
|
|
if (extendNodes && addedNodes) {
|
|
addedNodes.push(...extendNodes);
|
|
}
|
|
} else if (data.blocks && data.blocks.length > 0) {
|
|
// 兼容老的写法
|
|
if (!data.blocks[0].type) {
|
|
this.addInlineBlocks(node, data.blocks, addedNodes);
|
|
} else {
|
|
this.addBlocksAsChildren(node, data.blocks as FlowNodeJSON[], addedNodes);
|
|
}
|
|
}
|
|
|
|
if (isNew) {
|
|
this.onNodeCreateEmitter.fire({
|
|
node,
|
|
data,
|
|
json: data,
|
|
});
|
|
} else {
|
|
this.onNodeUpdateEmitter.fire({ node, data, json: data });
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
addBlocksAsChildren(
|
|
parent: FlowNodeEntity,
|
|
blocks: FlowNodeJSON[],
|
|
addedNodes?: FlowNodeEntity[]
|
|
): void {
|
|
for (const block of blocks) {
|
|
this.addNode(
|
|
{
|
|
...block,
|
|
parent,
|
|
},
|
|
addedNodes
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* block 格式:
|
|
* node: (最原始的 id)
|
|
* blockIcon
|
|
* inlineBlocks
|
|
* block
|
|
* blockOrderIcon
|
|
* block
|
|
* blockOrderIcon
|
|
* @param node
|
|
* @param blocks
|
|
* @param addedNodes
|
|
*/
|
|
addInlineBlocks(
|
|
node: FlowNodeEntity,
|
|
blocks: FlowNodeJSON[],
|
|
addedNodes: FlowNodeEntity[] = []
|
|
): FlowNodeEntity[] {
|
|
// 块列表开始节点,用来展示块的按钮
|
|
const blockIconNode = this.addNode({
|
|
id: `$blockIcon$${node.id}`,
|
|
type: FlowNodeBaseType.BLOCK_ICON,
|
|
originParent: node,
|
|
parent: node,
|
|
});
|
|
addedNodes.push(blockIconNode);
|
|
// 水平布局
|
|
const inlineBlocksNode = this.addNode({
|
|
id: `$inlineBlocks$${node.id}`,
|
|
type: FlowNodeBaseType.INLINE_BLOCKS,
|
|
originParent: node,
|
|
parent: node,
|
|
});
|
|
addedNodes.push(inlineBlocksNode);
|
|
blocks.forEach((blockData) => {
|
|
this.addBlock(node, blockData, addedNodes);
|
|
});
|
|
return addedNodes;
|
|
}
|
|
|
|
/**
|
|
* 添加单个 block
|
|
* @param target
|
|
* @param blockData
|
|
* @param addedNodes
|
|
* @param parent 默认去找 $inlineBlocks$
|
|
*/
|
|
addBlock(
|
|
target: FlowNodeEntity | string,
|
|
blockData: FlowNodeJSON,
|
|
addedNodes?: FlowNodeEntity[],
|
|
parent?: FlowNodeEntity,
|
|
index?: number
|
|
): FlowNodeEntity {
|
|
const node: FlowNodeEntity = typeof target === 'string' ? this.getNode(target)! : target;
|
|
const { onBlockChildCreate } = node.getNodeRegistry();
|
|
if (onBlockChildCreate) {
|
|
return onBlockChildCreate(node, blockData, addedNodes);
|
|
}
|
|
parent = parent || this.getNode(`$inlineBlocks$${node.id}`);
|
|
// 块节点会生成一个空的 Block 节点用来切割 Block
|
|
const block = this.addNode({
|
|
...omit(blockData, 'blocks'),
|
|
type: blockData.type || FlowNodeBaseType.BLOCK,
|
|
originParent: node,
|
|
parent,
|
|
index,
|
|
});
|
|
|
|
if (blockData.meta?.defaultCollapsed) {
|
|
block.collapsed = true;
|
|
}
|
|
|
|
// 块开始节点
|
|
const blockOrderIcon = this.addNode({
|
|
id: `$blockOrderIcon$${blockData.id}`,
|
|
type: FlowNodeBaseType.BLOCK_ORDER_ICON,
|
|
originParent: node,
|
|
meta: blockData.meta,
|
|
data: blockData.data,
|
|
parent: block,
|
|
});
|
|
addedNodes?.push(block, blockOrderIcon);
|
|
if (blockData.blocks) {
|
|
this.addBlocksAsChildren(block, blockData.blocks as FlowNodeJSON[], addedNodes);
|
|
}
|
|
return block;
|
|
}
|
|
|
|
/**
|
|
* 根据 id 获取节点
|
|
* @param id
|
|
*/
|
|
getNode(id: string): FlowNodeEntity | undefined {
|
|
if (!id) return undefined;
|
|
return this.entityManager.getEntityById<FlowNodeEntity>(id);
|
|
}
|
|
|
|
/**
|
|
* 注册节点
|
|
* @param registries
|
|
*/
|
|
registerFlowNodes<T extends FlowNodeRegistry<any>>(...registries: T[]): void {
|
|
registries.forEach((newRegistry) => {
|
|
if (!newRegistry) {
|
|
throw new Error('[FlowDocument] registerFlowNodes parameters get undefined registry.');
|
|
}
|
|
const preRegistry = this.registers.get(newRegistry.type);
|
|
this.registers.set(newRegistry.type, {
|
|
...preRegistry,
|
|
...newRegistry,
|
|
meta: {
|
|
...preRegistry?.meta,
|
|
...newRegistry?.meta,
|
|
},
|
|
extendChildRegistries: FlowNodeRegistry.mergeChildRegistries(
|
|
preRegistry?.extendChildRegistries,
|
|
newRegistry?.extendChildRegistries
|
|
),
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check node extend
|
|
* @param currentType
|
|
* @param parentType
|
|
*/
|
|
isExtend(currentType: FlowNodeType, parentType: FlowNodeType): boolean {
|
|
return (this.getNodeRegistry(currentType).__extends__ || []).includes(parentType);
|
|
}
|
|
|
|
/**
|
|
* 导出数据,可以重载
|
|
*/
|
|
toJSON(): T | any {
|
|
return {
|
|
nodes: this.root.toJSON().blocks,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @deprecated
|
|
* use `getNodeRegistry` instead
|
|
*/
|
|
getNodeRegister<T extends FlowNodeRegistry = FlowNodeRegistry>(
|
|
type: FlowNodeType,
|
|
originParent?: FlowNodeEntity
|
|
): T {
|
|
return this.getNodeRegistry<T>(type, originParent);
|
|
}
|
|
|
|
getNodeRegistry<T extends FlowNodeRegistry = FlowNodeRegistry>(
|
|
type: FlowNodeType,
|
|
originParent?: FlowNodeEntity
|
|
): T {
|
|
const typeKey = `${type}_${originParent?.flowNodeType || ''}`;
|
|
if (this.nodeRegistryCache.has(typeKey)) {
|
|
return this.nodeRegistryCache.get(typeKey) as T;
|
|
}
|
|
const customDefaultRegistry = this.options.getNodeDefaultRegistry?.(type);
|
|
let register = this.registers.get(type) || { type };
|
|
const extendRegisters: FlowNodeRegistry[] = [];
|
|
const extendKey = register.extend;
|
|
// 继承重载
|
|
if (register.extend && this.registers.has(register.extend)) {
|
|
register = FlowNodeRegistry.merge(
|
|
this.getNodeRegistry(register.extend),
|
|
register,
|
|
register.type
|
|
);
|
|
}
|
|
// 父节点覆盖
|
|
if (originParent) {
|
|
const extendRegister = this.getNodeRegistry(
|
|
originParent.flowNodeType
|
|
).extendChildRegistries?.find((r) => r.type === type);
|
|
if (extendRegister) {
|
|
if (extendRegister.extend && this.registers.has(extendRegister.extend)) {
|
|
extendRegisters.push(this.registers.get(extendRegister.extend)!);
|
|
}
|
|
extendRegisters.push(extendRegister);
|
|
}
|
|
}
|
|
register = FlowNodeRegistry.extend(register, extendRegisters);
|
|
const defaultNodeMeta = DEFAULT_FLOW_NODE_META(type, this);
|
|
defaultNodeMeta.spacing =
|
|
this.options?.constants?.[ConstantKeys.NODE_SPACING] || defaultNodeMeta.spacing;
|
|
|
|
const res = {
|
|
...customDefaultRegistry,
|
|
...register,
|
|
meta: {
|
|
...defaultNodeMeta,
|
|
...customDefaultRegistry?.meta,
|
|
...register.meta,
|
|
},
|
|
} as T;
|
|
// Save the "extend" attribute
|
|
if (extendKey) {
|
|
res.extend = extendKey;
|
|
}
|
|
this.nodeRegistryCache.set(typeKey, res);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* 节点注入数据
|
|
* @param nodeDatas
|
|
*/
|
|
registerNodeDatas(...nodeDatas: EntityDataRegistry[]): void {
|
|
this.nodeDataRegistries.push(...nodeDatas);
|
|
}
|
|
|
|
/**
|
|
* traverse all nodes, O(n)
|
|
* R
|
|
* |
|
|
* +---1
|
|
* | |
|
|
* | +---1.1
|
|
* | |
|
|
* | +---1.2
|
|
* | |
|
|
* | +---1.3
|
|
* | | |
|
|
* | | +---1.3.1
|
|
* | | |
|
|
* | | +---1.3.2
|
|
* | |
|
|
* | +---1.4
|
|
* |
|
|
* +---2
|
|
* |
|
|
* +---2.1
|
|
*
|
|
* sort: [1, 1.1, 1.2, 1.3, 1.3.1, 1.3.2, 1.4, 2, 2.1]
|
|
* @param fn
|
|
* @param node
|
|
* @param depth
|
|
* @return isBreak
|
|
*/
|
|
traverse(
|
|
fn: (node: FlowNodeEntity, depth: number, index: number) => boolean | void,
|
|
node = this.root,
|
|
depth = 0
|
|
): boolean | void {
|
|
return this.originTree.traverse(fn, node, depth);
|
|
}
|
|
|
|
get size(): number {
|
|
return this.getAllNodes().length;
|
|
}
|
|
|
|
hasNode(nodeId: string): boolean {
|
|
return !!this.entityManager.getEntityById(nodeId);
|
|
}
|
|
|
|
getAllNodes(): FlowNodeEntity[] {
|
|
return this.entityManager.getEntities(FlowNodeEntity);
|
|
}
|
|
|
|
toString(showType?: boolean): string {
|
|
return this.originTree.toString(showType);
|
|
}
|
|
|
|
/**
|
|
* 返回需要渲染的数据
|
|
*/
|
|
getRenderDatas<T extends EntityData>(
|
|
dataRegistry: EntityDataRegistry<T>,
|
|
containHiddenNodes = true
|
|
): T[] {
|
|
const result: T[] = [];
|
|
this.renderTree.traverse((node) => {
|
|
if (!containHiddenNodes && node.hidden) return;
|
|
result.push(node.getData<T>(dataRegistry)!);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
toNodeJSON(node: FlowNodeEntity): FlowNodeJSON {
|
|
if (this.options.toNodeJSON) {
|
|
return this.options.toNodeJSON(node);
|
|
}
|
|
const nodesMap: Record<string, FlowNodeJSON> = {};
|
|
let startNodeJSON: FlowNodeJSON;
|
|
this.traverse((node) => {
|
|
const isSystemNode = node.id.startsWith('$');
|
|
if (isSystemNode) return;
|
|
const nodeJSONData = node.getJSONData();
|
|
const nodeJSON: FlowNodeJSON = {
|
|
id: node.id,
|
|
type: node.flowNodeType,
|
|
};
|
|
if (nodeJSONData !== undefined) {
|
|
nodeJSON.data = nodeJSONData;
|
|
}
|
|
if (!startNodeJSON) startNodeJSON = nodeJSON;
|
|
let { parent } = node;
|
|
if (parent && parent.id.startsWith('$')) {
|
|
parent = parent.originParent;
|
|
}
|
|
const parentJSON = parent ? nodesMap[parent.id] : undefined;
|
|
if (parentJSON) {
|
|
if (!parentJSON.blocks) {
|
|
parentJSON.blocks = [];
|
|
}
|
|
parentJSON.blocks.push(nodeJSON);
|
|
}
|
|
nodesMap[node.id] = nodeJSON;
|
|
}, node);
|
|
return startNodeJSON!;
|
|
}
|
|
|
|
/**
|
|
* 移动节点
|
|
* @param param0
|
|
* @returns
|
|
*/
|
|
moveNodes({
|
|
dropNodeId,
|
|
sortNodeIds,
|
|
inside = false,
|
|
}: {
|
|
dropNodeId: string;
|
|
sortNodeIds: string[];
|
|
inside?: boolean;
|
|
}) {
|
|
const dropEntity = this.getNode(dropNodeId);
|
|
if (!dropEntity) {
|
|
return;
|
|
}
|
|
|
|
const sortNodes = sortNodeIds.map((id) => this.getNode(id)!);
|
|
|
|
// 按照顺序一个个移动到目标节点下
|
|
this.entityManager.changeEntityLocked = true;
|
|
for (const node of sortNodes.reverse()) {
|
|
if (inside) {
|
|
this.originTree.addChild(dropEntity, node, 0);
|
|
} else {
|
|
this.originTree.insertAfter(dropEntity, node);
|
|
}
|
|
}
|
|
|
|
this.entityManager.changeEntityLocked = false;
|
|
this.fireRender();
|
|
}
|
|
|
|
/**
|
|
* 移动子节点
|
|
* @param param0
|
|
* @returns
|
|
*/
|
|
moveChildNodes({
|
|
toParentId,
|
|
toIndex,
|
|
nodeIds,
|
|
}: {
|
|
toParentId: string;
|
|
nodeIds: string[];
|
|
toIndex: number;
|
|
}) {
|
|
if (nodeIds.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const toParent = this.getNode(toParentId);
|
|
if (!toParent) {
|
|
return;
|
|
}
|
|
|
|
this.entityManager.changeEntityLocked = true;
|
|
|
|
this.originTree.moveChilds(
|
|
toParent,
|
|
nodeIds.map((nodeId) => this.getNode(nodeId) as FlowNodeEntity),
|
|
toIndex
|
|
);
|
|
|
|
this.entityManager.changeEntityLocked = false;
|
|
this.fireRender();
|
|
}
|
|
|
|
/**
|
|
* 注册布局
|
|
* @param layout
|
|
*/
|
|
registerLayout(layout: FlowLayout) {
|
|
this.layouts.push(layout);
|
|
}
|
|
|
|
/**
|
|
* 更新布局
|
|
* @param layoutKey
|
|
*/
|
|
setLayout(layoutKey: string) {
|
|
if (this.currentLayoutKey === layoutKey) return;
|
|
const layout = this.layouts.find((layout) => layout.name === layoutKey);
|
|
if (!layout) return;
|
|
this.currentLayoutKey = layoutKey;
|
|
this.transformer.clear();
|
|
layout.reload?.();
|
|
this.fireRender();
|
|
this.onLayoutChangeEmitter.fire(this.layout);
|
|
}
|
|
|
|
/**
|
|
* 切换垂直或水平布局
|
|
*/
|
|
toggleFixedLayout() {
|
|
this.setLayout(
|
|
this.layout.name === FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT
|
|
? FlowLayoutDefault.VERTICAL_FIXED_LAYOUT
|
|
: FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT
|
|
);
|
|
}
|
|
|
|
dispose() {
|
|
if (this._disposed) return;
|
|
this.registers.clear();
|
|
this.nodeRegistryCache.clear();
|
|
this.originTree.dispose();
|
|
this.renderTree.dispose();
|
|
this.onNodeUpdateEmitter.dispose();
|
|
this.onNodeCreateEmitter.dispose();
|
|
this.onNodeDisposeEmitter.dispose();
|
|
this.onLayoutChangeEmitter.dispose();
|
|
this._disposed = true;
|
|
}
|
|
}
|