@flowgram-vue/document 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/dist/index.cjs +3233 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1728 -0
- package/dist/index.js +3199 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
- package/src/datas/flow-node-render-data.ts +227 -0
- package/src/datas/flow-node-transform-data.ts +337 -0
- package/src/datas/flow-node-transition-data.ts +182 -0
- package/src/datas/index.ts +8 -0
- package/src/entities/flow-document-transformer-entity.ts +125 -0
- package/src/entities/flow-node-entity.ts +415 -0
- package/src/entities/flow-renderer-state-entity.ts +127 -0
- package/src/entities/index.ts +8 -0
- package/src/flow-document-config.ts +42 -0
- package/src/flow-document-container-module.ts +33 -0
- package/src/flow-document-contribution.ts +22 -0
- package/src/flow-document-options.ts +79 -0
- package/src/flow-document.ts +766 -0
- package/src/flow-render-tree.ts +236 -0
- package/src/flow-virtual-tree.ts +244 -0
- package/src/index.ts +16 -0
- package/src/layout/horizontal-fixed-layout.ts +198 -0
- package/src/layout/index.ts +7 -0
- package/src/layout/vertical-fixed-layout.ts +198 -0
- package/src/services/flow-drag-service.ts +211 -0
- package/src/services/flow-group-service/flow-group-controller.ts +120 -0
- package/src/services/flow-group-service/flow-group-service.ts +107 -0
- package/src/services/flow-group-service/flow-group-utils.ts +104 -0
- package/src/services/flow-group-service/index.ts +7 -0
- package/src/services/flow-operation-base-service.ts +333 -0
- package/src/services/index.ts +8 -0
- package/src/typings/flow-group.ts +8 -0
- package/src/typings/flow-layout.ts +72 -0
- package/src/typings/flow-node-register.ts +356 -0
- package/src/typings/flow-operation.ts +312 -0
- package/src/typings/flow-transition.ts +104 -0
- package/src/typings/flow.ts +70 -0
- package/src/typings/index.ts +11 -0
- package/src/utils/get-default-spacing.ts +21 -0
- package/src/utils/index.ts +6 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { omit } from 'lodash-es';
|
|
7
|
+
import { inject, injectable, multiInject, optional, postConstruct } from 'inversify';
|
|
8
|
+
import { type Disposable, Emitter } from '@flowgram-vue/utils';
|
|
9
|
+
import { type EntityData, type EntityDataRegistry, EntityManager } from '@flowgram-vue/core';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
AddNodeData,
|
|
13
|
+
DEFAULT_FLOW_NODE_META,
|
|
14
|
+
type FlowDocumentJSON,
|
|
15
|
+
FlowLayout,
|
|
16
|
+
FlowLayoutDefault,
|
|
17
|
+
FlowNodeBaseType,
|
|
18
|
+
type FlowNodeJSON,
|
|
19
|
+
FlowNodeRegistry,
|
|
20
|
+
FlowNodeType,
|
|
21
|
+
} from './typings';
|
|
22
|
+
import { FlowVirtualTree } from './flow-virtual-tree';
|
|
23
|
+
import { FlowRenderTree } from './flow-render-tree';
|
|
24
|
+
import {
|
|
25
|
+
ConstantKeys,
|
|
26
|
+
FlowDocumentOptions,
|
|
27
|
+
FlowDocumentOptionsDefault,
|
|
28
|
+
} from './flow-document-options';
|
|
29
|
+
import { FlowDocumentContribution } from './flow-document-contribution';
|
|
30
|
+
import { FlowDocumentConfig } from './flow-document-config';
|
|
31
|
+
import { FlowDocumentTransformerEntity, FlowNodeEntity, FlowRendererStateEntity } from './entities';
|
|
32
|
+
|
|
33
|
+
export type FlowDocumentProvider = () => FlowDocument;
|
|
34
|
+
export const FlowDocumentProvider = Symbol('FlowDocumentProvider');
|
|
35
|
+
/**
|
|
36
|
+
* 流程整个文档数据
|
|
37
|
+
*/
|
|
38
|
+
@injectable()
|
|
39
|
+
export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
|
|
40
|
+
@inject(EntityManager) protected entityManager: EntityManager;
|
|
41
|
+
|
|
42
|
+
@inject(FlowDocumentConfig) readonly config: FlowDocumentConfig;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 流程画布配置项
|
|
46
|
+
*/
|
|
47
|
+
@inject(FlowDocumentOptions) @optional() public options: FlowDocumentOptions;
|
|
48
|
+
|
|
49
|
+
@multiInject(FlowDocumentContribution)
|
|
50
|
+
@optional()
|
|
51
|
+
protected contributions: FlowDocumentContribution[] = [];
|
|
52
|
+
|
|
53
|
+
protected registers = new Map<FlowNodeType, FlowNodeRegistry>();
|
|
54
|
+
|
|
55
|
+
private nodeRegistryCache = new Map<string, any>();
|
|
56
|
+
|
|
57
|
+
protected nodeDataRegistries: EntityDataRegistry[] = [];
|
|
58
|
+
|
|
59
|
+
protected layouts: FlowLayout[] = [];
|
|
60
|
+
|
|
61
|
+
protected currentLayoutKey: string = '';
|
|
62
|
+
|
|
63
|
+
protected onNodeUpdateEmitter = new Emitter<{
|
|
64
|
+
node: FlowNodeEntity;
|
|
65
|
+
/**
|
|
66
|
+
* use 'json' instead
|
|
67
|
+
* @deprecated
|
|
68
|
+
*/
|
|
69
|
+
data: FlowNodeJSON;
|
|
70
|
+
json: FlowNodeJSON;
|
|
71
|
+
}>();
|
|
72
|
+
|
|
73
|
+
protected onNodeCreateEmitter = new Emitter<{
|
|
74
|
+
node: FlowNodeEntity;
|
|
75
|
+
/**
|
|
76
|
+
* use 'json' instead
|
|
77
|
+
* @deprecated
|
|
78
|
+
*/
|
|
79
|
+
data: FlowNodeJSON;
|
|
80
|
+
json: FlowNodeJSON;
|
|
81
|
+
}>();
|
|
82
|
+
|
|
83
|
+
protected onNodeDisposeEmitter = new Emitter<{
|
|
84
|
+
node: FlowNodeEntity;
|
|
85
|
+
}>();
|
|
86
|
+
|
|
87
|
+
protected onLayoutChangeEmitter = new Emitter<FlowLayout>();
|
|
88
|
+
|
|
89
|
+
readonly onNodeUpdate = this.onNodeUpdateEmitter.event;
|
|
90
|
+
|
|
91
|
+
readonly onNodeCreate = this.onNodeCreateEmitter.event;
|
|
92
|
+
|
|
93
|
+
readonly onNodeDispose = this.onNodeDisposeEmitter.event;
|
|
94
|
+
|
|
95
|
+
readonly onLayoutChange = this.onLayoutChangeEmitter.event;
|
|
96
|
+
|
|
97
|
+
private _disposed = false;
|
|
98
|
+
|
|
99
|
+
root: FlowNodeEntity;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 原始的 tree 结构
|
|
103
|
+
*/
|
|
104
|
+
originTree: FlowVirtualTree<FlowNodeEntity>;
|
|
105
|
+
|
|
106
|
+
transformer: FlowDocumentTransformerEntity;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 渲染相关的全局轧辊台
|
|
110
|
+
*/
|
|
111
|
+
renderState: FlowRendererStateEntity;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 渲染后的 tree 结构
|
|
115
|
+
*/
|
|
116
|
+
renderTree: FlowRenderTree<FlowNodeEntity>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
*
|
|
120
|
+
*/
|
|
121
|
+
get disposed(): boolean {
|
|
122
|
+
return this._disposed;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@postConstruct()
|
|
126
|
+
init(): void {
|
|
127
|
+
if (!this.options) this.options = FlowDocumentOptionsDefault;
|
|
128
|
+
this.currentLayoutKey = this.options.defaultLayout || FlowLayoutDefault.VERTICAL_FIXED_LAYOUT;
|
|
129
|
+
this.contributions.forEach((contrib) => contrib.registerDocument?.(this));
|
|
130
|
+
this.root = this.addNode({ id: 'root', type: FlowNodeBaseType.ROOT });
|
|
131
|
+
this.originTree = new FlowVirtualTree<FlowNodeEntity>(this.root);
|
|
132
|
+
this.transformer = this.entityManager.createEntity<FlowDocumentTransformerEntity>(
|
|
133
|
+
FlowDocumentTransformerEntity,
|
|
134
|
+
{ document: this }
|
|
135
|
+
);
|
|
136
|
+
this.renderState =
|
|
137
|
+
this.entityManager.createEntity<FlowRendererStateEntity>(FlowRendererStateEntity);
|
|
138
|
+
this.renderTree = new FlowRenderTree<FlowNodeEntity>(this.root, this.originTree, this);
|
|
139
|
+
// 布局第一次加载时候触发一次
|
|
140
|
+
this.layout.reload?.();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 从数据初始化 O(n)
|
|
145
|
+
* @param json
|
|
146
|
+
*/
|
|
147
|
+
/**
|
|
148
|
+
* 加载数据,可以被重载
|
|
149
|
+
* @param json 文档数据更新
|
|
150
|
+
* @param fireRender 是否要触发渲染,默认 true
|
|
151
|
+
*/
|
|
152
|
+
fromJSON(json: FlowDocumentJSON | any, fireRender = true): void {
|
|
153
|
+
if (this._disposed) return;
|
|
154
|
+
// 清空 tree 数据 重新计算
|
|
155
|
+
this.originTree.clear();
|
|
156
|
+
this.renderTree.clear();
|
|
157
|
+
// 暂停触发画布更新
|
|
158
|
+
this.entityManager.changeEntityLocked = true;
|
|
159
|
+
// 添加前的节点
|
|
160
|
+
const oldNodes = this.entityManager.getEntities<FlowNodeEntity>(FlowNodeEntity);
|
|
161
|
+
// 添加后的节点
|
|
162
|
+
const newNodes: FlowNodeEntity[] = [this.root];
|
|
163
|
+
this.addBlocksAsChildren(this.root, json.nodes || [], newNodes);
|
|
164
|
+
// 删除无效的节点
|
|
165
|
+
oldNodes.forEach((node) => {
|
|
166
|
+
if (!newNodes.includes(node)) {
|
|
167
|
+
node.dispose();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
this.entityManager.changeEntityLocked = false;
|
|
171
|
+
this.transformer.loading = false;
|
|
172
|
+
if (fireRender) this.fireRender();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
get layout(): FlowLayout {
|
|
176
|
+
const layout = this.layouts.find((layout) => layout.name == this.currentLayoutKey);
|
|
177
|
+
if (!layout) {
|
|
178
|
+
throw new Error(`Unknown flow layout: ${this.currentLayoutKey}`);
|
|
179
|
+
}
|
|
180
|
+
return layout;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async load(): Promise<void> {
|
|
184
|
+
await Promise.all(this.contributions.map((c) => c.loadDocument?.(this)));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
get loading(): boolean {
|
|
188
|
+
return this.transformer.loading;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 触发 render
|
|
193
|
+
*/
|
|
194
|
+
fireRender(): void {
|
|
195
|
+
if (this.transformer.isTreeDirty()) {
|
|
196
|
+
this.entityManager.fireEntityChanged(FlowNodeEntity.type);
|
|
197
|
+
this.entityManager.fireEntityChanged(FlowDocumentTransformerEntity.type);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 从指定节点的下一个节点新增
|
|
203
|
+
* @param fromNode
|
|
204
|
+
* @param json
|
|
205
|
+
*/
|
|
206
|
+
addFromNode(fromNode: FlowNodeEntity | string, json: FlowNodeJSON): FlowNodeEntity {
|
|
207
|
+
const node = typeof fromNode === 'string' ? this.getNode(fromNode)! : fromNode;
|
|
208
|
+
this.entityManager.changeEntityLocked = true;
|
|
209
|
+
const { parent } = node;
|
|
210
|
+
const result = this.addNode({
|
|
211
|
+
...json,
|
|
212
|
+
parent,
|
|
213
|
+
// originParent,
|
|
214
|
+
});
|
|
215
|
+
this.originTree.insertAfter(node, result);
|
|
216
|
+
this.entityManager.changeEntityLocked = false;
|
|
217
|
+
this.entityManager.fireEntityChanged(FlowNodeEntity.type);
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
removeNode(node: FlowNodeEntity | string) {
|
|
222
|
+
if (typeof node === 'string') {
|
|
223
|
+
this.getNode(node)?.dispose();
|
|
224
|
+
} else {
|
|
225
|
+
node.dispose();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 添加节点,如果节点已经存在则不会重复创建
|
|
231
|
+
* @param data
|
|
232
|
+
* @param addedNodes
|
|
233
|
+
*/
|
|
234
|
+
addNode(data: AddNodeData, addedNodes?: FlowNodeEntity[]): FlowNodeEntity {
|
|
235
|
+
const { id, type = 'block', originParent, parent, meta, hidden, index } = data;
|
|
236
|
+
let node = this.getNode(id);
|
|
237
|
+
let isNew = false;
|
|
238
|
+
const register = this.getNodeRegistry(type, data.originParent);
|
|
239
|
+
// node 类型变化则全部删除重新来
|
|
240
|
+
if (node && node.flowNodeType !== data.type) {
|
|
241
|
+
node.dispose();
|
|
242
|
+
node = undefined;
|
|
243
|
+
}
|
|
244
|
+
if (!node) {
|
|
245
|
+
const { dataRegistries } = register;
|
|
246
|
+
node = this.entityManager.createEntity<FlowNodeEntity>(FlowNodeEntity, {
|
|
247
|
+
id,
|
|
248
|
+
document: this,
|
|
249
|
+
flowNodeType: type,
|
|
250
|
+
originParent,
|
|
251
|
+
meta,
|
|
252
|
+
});
|
|
253
|
+
this.options.preNodeCreate?.(node);
|
|
254
|
+
const datas = dataRegistries
|
|
255
|
+
? this.nodeDataRegistries.concat(...dataRegistries)
|
|
256
|
+
: this.nodeDataRegistries;
|
|
257
|
+
node.addInitializeData(datas);
|
|
258
|
+
node.onDispose(() => this.onNodeDisposeEmitter.fire({ node: node! }));
|
|
259
|
+
this.options.fromNodeJSON?.(node, data, true);
|
|
260
|
+
isNew = true;
|
|
261
|
+
} else {
|
|
262
|
+
this.options.fromNodeJSON?.(node, data, false);
|
|
263
|
+
}
|
|
264
|
+
// 初始化数据重制
|
|
265
|
+
node.initData({
|
|
266
|
+
originParent,
|
|
267
|
+
parent,
|
|
268
|
+
meta,
|
|
269
|
+
hidden,
|
|
270
|
+
index,
|
|
271
|
+
});
|
|
272
|
+
// 开始节点加到 root 里边
|
|
273
|
+
if (node.isStart) {
|
|
274
|
+
this.root.addChild(node);
|
|
275
|
+
}
|
|
276
|
+
addedNodes?.push(node);
|
|
277
|
+
// 自定义创建逻辑
|
|
278
|
+
if (register.onCreate) {
|
|
279
|
+
const extendNodes = register.onCreate(node, data);
|
|
280
|
+
if (extendNodes && addedNodes) {
|
|
281
|
+
addedNodes.push(...extendNodes);
|
|
282
|
+
}
|
|
283
|
+
} else if (data.blocks && data.blocks.length > 0) {
|
|
284
|
+
// 兼容老的写法
|
|
285
|
+
if (!data.blocks[0].type) {
|
|
286
|
+
this.addInlineBlocks(node, data.blocks, addedNodes);
|
|
287
|
+
} else {
|
|
288
|
+
this.addBlocksAsChildren(node, data.blocks as FlowNodeJSON[], addedNodes);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (isNew) {
|
|
293
|
+
this.onNodeCreateEmitter.fire({
|
|
294
|
+
node,
|
|
295
|
+
data,
|
|
296
|
+
json: data,
|
|
297
|
+
});
|
|
298
|
+
} else {
|
|
299
|
+
this.onNodeUpdateEmitter.fire({ node, data, json: data });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return node;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
addBlocksAsChildren(
|
|
306
|
+
parent: FlowNodeEntity,
|
|
307
|
+
blocks: FlowNodeJSON[],
|
|
308
|
+
addedNodes?: FlowNodeEntity[]
|
|
309
|
+
): void {
|
|
310
|
+
for (const block of blocks) {
|
|
311
|
+
this.addNode(
|
|
312
|
+
{
|
|
313
|
+
...block,
|
|
314
|
+
parent,
|
|
315
|
+
},
|
|
316
|
+
addedNodes
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* block 格式:
|
|
323
|
+
* node: (最原始的 id)
|
|
324
|
+
* blockIcon
|
|
325
|
+
* inlineBlocks
|
|
326
|
+
* block
|
|
327
|
+
* blockOrderIcon
|
|
328
|
+
* block
|
|
329
|
+
* blockOrderIcon
|
|
330
|
+
* @param node
|
|
331
|
+
* @param blocks
|
|
332
|
+
* @param addedNodes
|
|
333
|
+
*/
|
|
334
|
+
addInlineBlocks(
|
|
335
|
+
node: FlowNodeEntity,
|
|
336
|
+
blocks: FlowNodeJSON[],
|
|
337
|
+
addedNodes: FlowNodeEntity[] = []
|
|
338
|
+
): FlowNodeEntity[] {
|
|
339
|
+
// 块列表开始节点,用来展示块的按钮
|
|
340
|
+
const blockIconNode = this.addNode({
|
|
341
|
+
id: `$blockIcon$${node.id}`,
|
|
342
|
+
type: FlowNodeBaseType.BLOCK_ICON,
|
|
343
|
+
originParent: node,
|
|
344
|
+
parent: node,
|
|
345
|
+
});
|
|
346
|
+
addedNodes.push(blockIconNode);
|
|
347
|
+
// 水平布局
|
|
348
|
+
const inlineBlocksNode = this.addNode({
|
|
349
|
+
id: `$inlineBlocks$${node.id}`,
|
|
350
|
+
type: FlowNodeBaseType.INLINE_BLOCKS,
|
|
351
|
+
originParent: node,
|
|
352
|
+
parent: node,
|
|
353
|
+
});
|
|
354
|
+
addedNodes.push(inlineBlocksNode);
|
|
355
|
+
blocks.forEach((blockData) => {
|
|
356
|
+
this.addBlock(node, blockData, addedNodes);
|
|
357
|
+
});
|
|
358
|
+
return addedNodes;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 添加单个 block
|
|
363
|
+
* @param target
|
|
364
|
+
* @param blockData
|
|
365
|
+
* @param addedNodes
|
|
366
|
+
* @param parent 默认去找 $inlineBlocks$
|
|
367
|
+
*/
|
|
368
|
+
addBlock(
|
|
369
|
+
target: FlowNodeEntity | string,
|
|
370
|
+
blockData: FlowNodeJSON,
|
|
371
|
+
addedNodes?: FlowNodeEntity[],
|
|
372
|
+
parent?: FlowNodeEntity,
|
|
373
|
+
index?: number
|
|
374
|
+
): FlowNodeEntity {
|
|
375
|
+
const node: FlowNodeEntity = typeof target === 'string' ? this.getNode(target)! : target;
|
|
376
|
+
const { onBlockChildCreate } = node.getNodeRegistry();
|
|
377
|
+
if (onBlockChildCreate) {
|
|
378
|
+
return onBlockChildCreate(node, blockData, addedNodes);
|
|
379
|
+
}
|
|
380
|
+
parent = parent || this.getNode(`$inlineBlocks$${node.id}`);
|
|
381
|
+
// 块节点会生成一个空的 Block 节点用来切割 Block
|
|
382
|
+
const block = this.addNode({
|
|
383
|
+
...omit(blockData, 'blocks'),
|
|
384
|
+
type: blockData.type || FlowNodeBaseType.BLOCK,
|
|
385
|
+
originParent: node,
|
|
386
|
+
parent,
|
|
387
|
+
index,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (blockData.meta?.defaultCollapsed) {
|
|
391
|
+
block.collapsed = true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 块开始节点
|
|
395
|
+
const blockOrderIcon = this.addNode({
|
|
396
|
+
id: `$blockOrderIcon$${blockData.id}`,
|
|
397
|
+
type: FlowNodeBaseType.BLOCK_ORDER_ICON,
|
|
398
|
+
originParent: node,
|
|
399
|
+
meta: blockData.meta,
|
|
400
|
+
data: blockData.data,
|
|
401
|
+
parent: block,
|
|
402
|
+
});
|
|
403
|
+
addedNodes?.push(block, blockOrderIcon);
|
|
404
|
+
if (blockData.blocks) {
|
|
405
|
+
this.addBlocksAsChildren(block, blockData.blocks as FlowNodeJSON[], addedNodes);
|
|
406
|
+
}
|
|
407
|
+
return block;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* 根据 id 获取节点
|
|
412
|
+
* @param id
|
|
413
|
+
*/
|
|
414
|
+
getNode(id: string): FlowNodeEntity | undefined {
|
|
415
|
+
if (!id) return undefined;
|
|
416
|
+
return this.entityManager.getEntityById<FlowNodeEntity>(id);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* 注册节点
|
|
421
|
+
* @param registries
|
|
422
|
+
*/
|
|
423
|
+
registerFlowNodes<T extends FlowNodeRegistry<any>>(...registries: T[]): void {
|
|
424
|
+
registries.forEach((newRegistry) => {
|
|
425
|
+
if (!newRegistry) {
|
|
426
|
+
throw new Error('[FlowDocument] registerFlowNodes parameters get undefined registry.');
|
|
427
|
+
}
|
|
428
|
+
const preRegistry = this.registers.get(newRegistry.type);
|
|
429
|
+
this.registers.set(newRegistry.type, {
|
|
430
|
+
...preRegistry,
|
|
431
|
+
...newRegistry,
|
|
432
|
+
meta: {
|
|
433
|
+
...preRegistry?.meta,
|
|
434
|
+
...newRegistry?.meta,
|
|
435
|
+
},
|
|
436
|
+
extendChildRegistries: FlowNodeRegistry.mergeChildRegistries(
|
|
437
|
+
preRegistry?.extendChildRegistries,
|
|
438
|
+
newRegistry?.extendChildRegistries
|
|
439
|
+
),
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Check node extend
|
|
446
|
+
* @param currentType
|
|
447
|
+
* @param extendType
|
|
448
|
+
*/
|
|
449
|
+
isExtend(currentType: FlowNodeType, extendType: FlowNodeType): boolean {
|
|
450
|
+
return (this.getNodeRegistry(currentType).__extends__ || []).includes(extendType);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Check node type
|
|
455
|
+
* @param currentType
|
|
456
|
+
* @param extendType
|
|
457
|
+
*/
|
|
458
|
+
isTypeOrExtendType(currentType: FlowNodeType, extendType: FlowNodeType): boolean {
|
|
459
|
+
return currentType === extendType || this.isExtend(currentType, extendType);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* 导出数据,可以重载
|
|
464
|
+
*/
|
|
465
|
+
toJSON(): T | any {
|
|
466
|
+
if (this.disposed) {
|
|
467
|
+
throw new Error(
|
|
468
|
+
'The FlowDocument has been disposed and it is no longer possible to call toJSON.'
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
nodes: this.root.toJSON().blocks,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* @deprecated
|
|
478
|
+
* use `getNodeRegistry` instead
|
|
479
|
+
*/
|
|
480
|
+
getNodeRegister<T extends FlowNodeRegistry = FlowNodeRegistry>(
|
|
481
|
+
type: FlowNodeType,
|
|
482
|
+
originParent?: FlowNodeEntity
|
|
483
|
+
): T {
|
|
484
|
+
return this.getNodeRegistry<T>(type, originParent);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
getNodeRegistry<T extends FlowNodeRegistry = FlowNodeRegistry>(
|
|
488
|
+
type: FlowNodeType,
|
|
489
|
+
originParent?: FlowNodeEntity
|
|
490
|
+
): T {
|
|
491
|
+
const typeKey = `${type}_${originParent?.flowNodeType || ''}`;
|
|
492
|
+
if (this.nodeRegistryCache.has(typeKey)) {
|
|
493
|
+
return this.nodeRegistryCache.get(typeKey) as T;
|
|
494
|
+
}
|
|
495
|
+
const customDefaultRegistry = this.options.getNodeDefaultRegistry?.(type);
|
|
496
|
+
let register = this.registers.get(type) || { type };
|
|
497
|
+
const extendRegisters: FlowNodeRegistry[] = [];
|
|
498
|
+
const extendKey = register.extend;
|
|
499
|
+
// 继承重载
|
|
500
|
+
if (register.extend && this.registers.has(register.extend)) {
|
|
501
|
+
register = FlowNodeRegistry.merge(
|
|
502
|
+
this.getNodeRegistry(register.extend),
|
|
503
|
+
register,
|
|
504
|
+
register.type
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
// 父节点覆盖
|
|
508
|
+
if (originParent) {
|
|
509
|
+
const extendRegister = this.getNodeRegistry(
|
|
510
|
+
originParent.flowNodeType
|
|
511
|
+
).extendChildRegistries?.find((r) => r.type === type);
|
|
512
|
+
if (extendRegister) {
|
|
513
|
+
if (extendRegister.extend && this.registers.has(extendRegister.extend)) {
|
|
514
|
+
extendRegisters.push(this.registers.get(extendRegister.extend)!);
|
|
515
|
+
}
|
|
516
|
+
extendRegisters.push(extendRegister);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
register = FlowNodeRegistry.extend(register, extendRegisters);
|
|
520
|
+
const defaultNodeMeta = DEFAULT_FLOW_NODE_META(type, this);
|
|
521
|
+
defaultNodeMeta.spacing =
|
|
522
|
+
this.options?.constants?.[ConstantKeys.NODE_SPACING] || defaultNodeMeta.spacing;
|
|
523
|
+
|
|
524
|
+
const res = {
|
|
525
|
+
...customDefaultRegistry,
|
|
526
|
+
...register,
|
|
527
|
+
meta: {
|
|
528
|
+
...defaultNodeMeta,
|
|
529
|
+
...customDefaultRegistry?.meta,
|
|
530
|
+
...register.meta,
|
|
531
|
+
},
|
|
532
|
+
} as T;
|
|
533
|
+
// Save the "extend" attribute
|
|
534
|
+
if (extendKey) {
|
|
535
|
+
res.extend = extendKey;
|
|
536
|
+
}
|
|
537
|
+
this.nodeRegistryCache.set(typeKey, res);
|
|
538
|
+
return res;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* 节点注入数据
|
|
543
|
+
* @param nodeDatas
|
|
544
|
+
*/
|
|
545
|
+
registerNodeDatas(...nodeDatas: EntityDataRegistry[]): void {
|
|
546
|
+
this.nodeDataRegistries.push(...nodeDatas);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* traverse all nodes, O(n)
|
|
551
|
+
* R
|
|
552
|
+
* |
|
|
553
|
+
* +---1
|
|
554
|
+
* | |
|
|
555
|
+
* | +---1.1
|
|
556
|
+
* | |
|
|
557
|
+
* | +---1.2
|
|
558
|
+
* | |
|
|
559
|
+
* | +---1.3
|
|
560
|
+
* | | |
|
|
561
|
+
* | | +---1.3.1
|
|
562
|
+
* | | |
|
|
563
|
+
* | | +---1.3.2
|
|
564
|
+
* | |
|
|
565
|
+
* | +---1.4
|
|
566
|
+
* |
|
|
567
|
+
* +---2
|
|
568
|
+
* |
|
|
569
|
+
* +---2.1
|
|
570
|
+
*
|
|
571
|
+
* sort: [1, 1.1, 1.2, 1.3, 1.3.1, 1.3.2, 1.4, 2, 2.1]
|
|
572
|
+
* @param fn
|
|
573
|
+
* @param node
|
|
574
|
+
* @param depth
|
|
575
|
+
* @return isBreak
|
|
576
|
+
*/
|
|
577
|
+
traverse(
|
|
578
|
+
fn: (node: FlowNodeEntity, depth: number, index: number) => boolean | void,
|
|
579
|
+
node = this.root,
|
|
580
|
+
depth = 0
|
|
581
|
+
): boolean | void {
|
|
582
|
+
return this.originTree.traverse(fn, node, depth);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
get size(): number {
|
|
586
|
+
return this.getAllNodes().length;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
hasNode(nodeId: string): boolean {
|
|
590
|
+
return !!this.entityManager.getEntityById(nodeId);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
getAllNodes(): FlowNodeEntity[] {
|
|
594
|
+
return this.entityManager.getEntities(FlowNodeEntity);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
toString(showType?: boolean): string {
|
|
598
|
+
return this.originTree.toString(showType);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* 返回需要渲染的数据
|
|
603
|
+
*/
|
|
604
|
+
getRenderDatas<T extends EntityData>(
|
|
605
|
+
dataRegistry: EntityDataRegistry<T>,
|
|
606
|
+
containHiddenNodes = true
|
|
607
|
+
): T[] {
|
|
608
|
+
const result: T[] = [];
|
|
609
|
+
this.renderTree.traverse((node) => {
|
|
610
|
+
if (!containHiddenNodes && node.hidden) return;
|
|
611
|
+
result.push(node.getData<T>(dataRegistry)!);
|
|
612
|
+
});
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
toNodeJSON(node: FlowNodeEntity): FlowNodeJSON {
|
|
617
|
+
if (this.options.toNodeJSON) {
|
|
618
|
+
return this.options.toNodeJSON(node);
|
|
619
|
+
}
|
|
620
|
+
const nodesMap: Record<string, FlowNodeJSON> = {};
|
|
621
|
+
let startNodeJSON: FlowNodeJSON;
|
|
622
|
+
this.traverse((node) => {
|
|
623
|
+
const isSystemNode = node.id.startsWith('$');
|
|
624
|
+
if (isSystemNode) return;
|
|
625
|
+
const nodeJSONData = node.getJSONData();
|
|
626
|
+
const nodeJSON: FlowNodeJSON = {
|
|
627
|
+
id: node.id,
|
|
628
|
+
type: node.flowNodeType,
|
|
629
|
+
};
|
|
630
|
+
if (nodeJSONData !== undefined) {
|
|
631
|
+
nodeJSON.data = nodeJSONData;
|
|
632
|
+
}
|
|
633
|
+
if (!startNodeJSON) startNodeJSON = nodeJSON;
|
|
634
|
+
let { parent } = node;
|
|
635
|
+
if (parent && parent.id.startsWith('$')) {
|
|
636
|
+
parent = parent.originParent;
|
|
637
|
+
}
|
|
638
|
+
const parentJSON = parent ? nodesMap[parent.id] : undefined;
|
|
639
|
+
if (parentJSON) {
|
|
640
|
+
if (!parentJSON.blocks) {
|
|
641
|
+
parentJSON.blocks = [];
|
|
642
|
+
}
|
|
643
|
+
parentJSON.blocks.push(nodeJSON);
|
|
644
|
+
}
|
|
645
|
+
nodesMap[node.id] = nodeJSON;
|
|
646
|
+
}, node);
|
|
647
|
+
return startNodeJSON!;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* 移动节点
|
|
652
|
+
* @param param0
|
|
653
|
+
* @returns
|
|
654
|
+
*/
|
|
655
|
+
moveNodes({
|
|
656
|
+
dropNodeId,
|
|
657
|
+
sortNodeIds,
|
|
658
|
+
inside = false,
|
|
659
|
+
}: {
|
|
660
|
+
dropNodeId: string;
|
|
661
|
+
sortNodeIds: string[];
|
|
662
|
+
inside?: boolean;
|
|
663
|
+
}) {
|
|
664
|
+
const dropEntity = this.getNode(dropNodeId);
|
|
665
|
+
if (!dropEntity) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const sortNodes = sortNodeIds.map((id) => this.getNode(id)!);
|
|
670
|
+
|
|
671
|
+
// 按照顺序一个个移动到目标节点下
|
|
672
|
+
this.entityManager.changeEntityLocked = true;
|
|
673
|
+
for (const node of sortNodes.reverse()) {
|
|
674
|
+
if (inside) {
|
|
675
|
+
this.originTree.addChild(dropEntity, node, 0);
|
|
676
|
+
} else {
|
|
677
|
+
this.originTree.insertAfter(dropEntity, node);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
this.entityManager.changeEntityLocked = false;
|
|
682
|
+
this.fireRender();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* 移动子节点
|
|
687
|
+
* @param param0
|
|
688
|
+
* @returns
|
|
689
|
+
*/
|
|
690
|
+
moveChildNodes({
|
|
691
|
+
toParentId,
|
|
692
|
+
toIndex,
|
|
693
|
+
nodeIds,
|
|
694
|
+
}: {
|
|
695
|
+
toParentId: string;
|
|
696
|
+
nodeIds: string[];
|
|
697
|
+
toIndex: number;
|
|
698
|
+
}) {
|
|
699
|
+
if (nodeIds.length === 0) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const toParent = this.getNode(toParentId);
|
|
704
|
+
if (!toParent) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
this.entityManager.changeEntityLocked = true;
|
|
709
|
+
|
|
710
|
+
this.originTree.moveChilds(
|
|
711
|
+
toParent,
|
|
712
|
+
nodeIds.map((nodeId) => this.getNode(nodeId) as FlowNodeEntity),
|
|
713
|
+
toIndex
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
this.entityManager.changeEntityLocked = false;
|
|
717
|
+
this.fireRender();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* 注册布局
|
|
722
|
+
* @param layout
|
|
723
|
+
*/
|
|
724
|
+
registerLayout(layout: FlowLayout) {
|
|
725
|
+
this.layouts.push(layout);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* 更新布局
|
|
730
|
+
* @param layoutKey
|
|
731
|
+
*/
|
|
732
|
+
setLayout(layoutKey: string) {
|
|
733
|
+
if (this.currentLayoutKey === layoutKey) return;
|
|
734
|
+
const layout = this.layouts.find((layout) => layout.name === layoutKey);
|
|
735
|
+
if (!layout) return;
|
|
736
|
+
this.currentLayoutKey = layoutKey;
|
|
737
|
+
this.transformer.clear();
|
|
738
|
+
layout.reload?.();
|
|
739
|
+
this.fireRender();
|
|
740
|
+
this.onLayoutChangeEmitter.fire(this.layout);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* 切换垂直或水平布局
|
|
745
|
+
*/
|
|
746
|
+
toggleFixedLayout() {
|
|
747
|
+
this.setLayout(
|
|
748
|
+
this.layout.name === FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT
|
|
749
|
+
? FlowLayoutDefault.VERTICAL_FIXED_LAYOUT
|
|
750
|
+
: FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
dispose() {
|
|
755
|
+
if (this._disposed) return;
|
|
756
|
+
this.registers.clear();
|
|
757
|
+
this.nodeRegistryCache.clear();
|
|
758
|
+
this.originTree.dispose();
|
|
759
|
+
this.renderTree.dispose();
|
|
760
|
+
this.onNodeUpdateEmitter.dispose();
|
|
761
|
+
this.onNodeCreateEmitter.dispose();
|
|
762
|
+
this.onNodeDisposeEmitter.dispose();
|
|
763
|
+
this.onLayoutChangeEmitter.dispose();
|
|
764
|
+
this._disposed = true;
|
|
765
|
+
}
|
|
766
|
+
}
|