@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.
Files changed (42) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +3233 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.ts +1728 -0
  5. package/dist/index.js +3199 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +61 -0
  8. package/src/datas/flow-node-render-data.ts +227 -0
  9. package/src/datas/flow-node-transform-data.ts +337 -0
  10. package/src/datas/flow-node-transition-data.ts +182 -0
  11. package/src/datas/index.ts +8 -0
  12. package/src/entities/flow-document-transformer-entity.ts +125 -0
  13. package/src/entities/flow-node-entity.ts +415 -0
  14. package/src/entities/flow-renderer-state-entity.ts +127 -0
  15. package/src/entities/index.ts +8 -0
  16. package/src/flow-document-config.ts +42 -0
  17. package/src/flow-document-container-module.ts +33 -0
  18. package/src/flow-document-contribution.ts +22 -0
  19. package/src/flow-document-options.ts +79 -0
  20. package/src/flow-document.ts +766 -0
  21. package/src/flow-render-tree.ts +236 -0
  22. package/src/flow-virtual-tree.ts +244 -0
  23. package/src/index.ts +16 -0
  24. package/src/layout/horizontal-fixed-layout.ts +198 -0
  25. package/src/layout/index.ts +7 -0
  26. package/src/layout/vertical-fixed-layout.ts +198 -0
  27. package/src/services/flow-drag-service.ts +211 -0
  28. package/src/services/flow-group-service/flow-group-controller.ts +120 -0
  29. package/src/services/flow-group-service/flow-group-service.ts +107 -0
  30. package/src/services/flow-group-service/flow-group-utils.ts +104 -0
  31. package/src/services/flow-group-service/index.ts +7 -0
  32. package/src/services/flow-operation-base-service.ts +333 -0
  33. package/src/services/index.ts +8 -0
  34. package/src/typings/flow-group.ts +8 -0
  35. package/src/typings/flow-layout.ts +72 -0
  36. package/src/typings/flow-node-register.ts +356 -0
  37. package/src/typings/flow-operation.ts +312 -0
  38. package/src/typings/flow-transition.ts +104 -0
  39. package/src/typings/flow.ts +70 -0
  40. package/src/typings/index.ts +11 -0
  41. package/src/utils/get-default-spacing.ts +21 -0
  42. 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
+ }