@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,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { FlowDocumentConfigEnum, FlowNodeBaseType, FlowNodeSplitType } from './typings';
|
|
7
|
+
import { FlowVirtualTree } from './flow-virtual-tree';
|
|
8
|
+
import type { FlowDocument } from './flow-document';
|
|
9
|
+
import type { FlowNodeEntity } from './entities';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Render Tree 会只读模式,不具备操作 tree 结构元素
|
|
13
|
+
*/
|
|
14
|
+
export class FlowRenderTree<T extends FlowNodeEntity> extends FlowVirtualTree<T> {
|
|
15
|
+
protected originTree: FlowVirtualTree<T>;
|
|
16
|
+
|
|
17
|
+
protected document: FlowDocument;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 折叠的节点
|
|
21
|
+
* @protected
|
|
22
|
+
*/
|
|
23
|
+
protected nodesCollapsed = new Set<T>();
|
|
24
|
+
|
|
25
|
+
constructor(readonly root: T, originTree: FlowVirtualTree<T>, document: FlowDocument) {
|
|
26
|
+
super(root);
|
|
27
|
+
this.originTree = originTree;
|
|
28
|
+
this.onTreeChange = this.originTree.onTreeChange;
|
|
29
|
+
this.document = document;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isCollapsed(node: T): boolean {
|
|
33
|
+
return this.nodesCollapsed.has(node);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get collapsedNodeList(): T[] {
|
|
37
|
+
return Array.from(this.nodesCollapsed);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 折叠元素
|
|
42
|
+
* @param node
|
|
43
|
+
* @param collapsed
|
|
44
|
+
*/
|
|
45
|
+
setCollapsed(node: T, collapsed: boolean): void {
|
|
46
|
+
if (collapsed) {
|
|
47
|
+
this.nodesCollapsed.add(node);
|
|
48
|
+
} else {
|
|
49
|
+
this.nodesCollapsed.delete(node);
|
|
50
|
+
}
|
|
51
|
+
this.originTree.fireTreeChange();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
openNodeInsideCollapsed(node: T) {
|
|
58
|
+
// 找到所有的originTree上的parent
|
|
59
|
+
let curr: T | undefined = this.originTree.getInfo(node)?.parent;
|
|
60
|
+
|
|
61
|
+
while (curr) {
|
|
62
|
+
if (this.nodesCollapsed.has(curr)) {
|
|
63
|
+
this.nodesCollapsed.delete(curr);
|
|
64
|
+
}
|
|
65
|
+
const { parent } = this.originTree.getInfo(curr) || {};
|
|
66
|
+
curr = parent;
|
|
67
|
+
}
|
|
68
|
+
this.originTree.fireTreeChange();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 更新结束节点等位置信息,分支里如果全是结束节点则要做相应的偏移
|
|
73
|
+
*/
|
|
74
|
+
updateRenderStruct(): void {
|
|
75
|
+
this.map = this.originTree.cloneMap();
|
|
76
|
+
|
|
77
|
+
// 结束节点位置更新逻辑开关
|
|
78
|
+
if (this.document.config.get(FlowDocumentConfigEnum.END_NODES_REFINE_BRANCH)) {
|
|
79
|
+
this.refineBranch(this.root);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 节点展开收起
|
|
83
|
+
this.hideCollapsed();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 隐藏收起节点
|
|
88
|
+
*/
|
|
89
|
+
protected hideCollapsed() {
|
|
90
|
+
this.nodesCollapsed.forEach(collapsedNode => {
|
|
91
|
+
const collapsedNodeInfo = this.getInfo(collapsedNode);
|
|
92
|
+
if (!collapsedNodeInfo) {
|
|
93
|
+
// 自动回收节点数据
|
|
94
|
+
this.nodesCollapsed.delete(collapsedNode);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const iconChild = collapsedNodeInfo.children.find(
|
|
99
|
+
_child =>
|
|
100
|
+
_child.flowNodeType === FlowNodeBaseType.BLOCK_ICON ||
|
|
101
|
+
_child.flowNodeType === FlowNodeBaseType.BLOCK_ORDER_ICON,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// ⚠️注意:BLOCK_ICON 和 BLOCK_ORDER_ICON 作为一个 block 的标识节点,收起时需要保留
|
|
105
|
+
if (iconChild) {
|
|
106
|
+
const iconInfo = this.getInfo(iconChild);
|
|
107
|
+
iconInfo.next = undefined;
|
|
108
|
+
iconInfo.pre = undefined;
|
|
109
|
+
collapsedNodeInfo.children = [iconChild];
|
|
110
|
+
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 收起节点children置为空
|
|
115
|
+
collapsedNodeInfo.children = [];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 节点是否为结束节点
|
|
120
|
+
isNodeEnd(node: T): boolean {
|
|
121
|
+
if (node.getNodeMeta().isNodeEnd) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const { children } = this.getInfo(node);
|
|
125
|
+
|
|
126
|
+
if (children.length > 0 && node.isInlineBlocks) {
|
|
127
|
+
return children.every(child => this.isNodeEnd(child));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (node.isInlineBlock) {
|
|
131
|
+
return this.isNodeEnd(children[children.length - 1]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 优化精简分支线
|
|
139
|
+
* - 结束节点拉直分支线
|
|
140
|
+
*/
|
|
141
|
+
protected refineBranch(block: T) {
|
|
142
|
+
let curr: T | undefined = this.getInfo(block).children[0];
|
|
143
|
+
|
|
144
|
+
while (curr) {
|
|
145
|
+
if (
|
|
146
|
+
curr.flowNodeType === FlowNodeSplitType.DYNAMIC_SPLIT ||
|
|
147
|
+
curr.flowNodeType === FlowNodeSplitType.STATIC_SPLIT
|
|
148
|
+
) {
|
|
149
|
+
const { next, children: branchChildren } = this.getInfo(curr);
|
|
150
|
+
const { children } = this.getInfo(branchChildren[1]);
|
|
151
|
+
|
|
152
|
+
const passBlocks = (children || []).filter(child => !this.isNodeEnd(child));
|
|
153
|
+
|
|
154
|
+
const shouldDragAllNextNodes = passBlocks.length === 1;
|
|
155
|
+
if (shouldDragAllNextNodes && next) {
|
|
156
|
+
this.dragNextNodesToBlock(passBlocks[0], next);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 对每一个分支再进行refineBranch
|
|
160
|
+
children?.forEach(child => {
|
|
161
|
+
this.refineBranch(child);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (shouldDragAllNextNodes) {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
curr = curr.next as T;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 结束节点拽分支,将后续节点拽到对应分支内
|
|
174
|
+
protected dragNextNodesToBlock(toBlock: T, next: T) {
|
|
175
|
+
const toBlockInfo = this.getInfo(toBlock);
|
|
176
|
+
const nextInfo = this.getInfo(next);
|
|
177
|
+
const toBlockLastChild = toBlockInfo.children[toBlock.children.length - 1];
|
|
178
|
+
|
|
179
|
+
if (nextInfo.parent) {
|
|
180
|
+
const nextParentInfo = this.getInfo(nextInfo.parent);
|
|
181
|
+
|
|
182
|
+
// 1. next的节点和之前的节点断连
|
|
183
|
+
if (nextInfo.pre) {
|
|
184
|
+
this.getInfo(nextInfo.pre).next = undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 2. next连接到before上
|
|
188
|
+
if (toBlockLastChild) {
|
|
189
|
+
const lastChildInfo = this.getInfo(toBlockLastChild);
|
|
190
|
+
lastChildInfo.next = next;
|
|
191
|
+
nextInfo.pre = toBlockLastChild;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 3. 获取所有后续节点, 将所有后续节点重新设置parent
|
|
195
|
+
const nextNodeIndex = nextParentInfo.children.indexOf(next);
|
|
196
|
+
const allNextNodes = nextParentInfo.children.slice(nextNodeIndex);
|
|
197
|
+
nextParentInfo.children = nextParentInfo.children.slice(0, nextNodeIndex);
|
|
198
|
+
for (const node of allNextNodes) {
|
|
199
|
+
const nodeInfo = this.getInfo(node);
|
|
200
|
+
toBlockInfo.children.push(node);
|
|
201
|
+
nodeInfo.parent = toBlock;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getInfo(node: T): FlowVirtualTree.NodeInfo<T> {
|
|
207
|
+
const info = this.map.get(node) || this.originTree.getInfo(node);
|
|
208
|
+
return info;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 或者originTree节点的信息
|
|
212
|
+
getOriginInfo(node: T): FlowVirtualTree.NodeInfo<T> {
|
|
213
|
+
return this.originTree.getInfo(node);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 获取收起的隐藏节点
|
|
217
|
+
getCollapsedChildren(node: T): T[] {
|
|
218
|
+
return this.getOriginInfo(node).children || [];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
remove(): void {
|
|
222
|
+
throw new Error('Render Tree cannot use remove node');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
addChild(): T {
|
|
226
|
+
throw new Error('Render tree cannot use add child');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
insertAfter(): void {
|
|
230
|
+
throw new Error('Render tree cannot use insert after');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
removeParent(): void {
|
|
234
|
+
throw new Error('Render tree cannot use remove parent');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { type Disposable, Emitter } from '@flowgram-vue/utils';
|
|
7
|
+
|
|
8
|
+
import { type FlowNodeType } from './typings';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 存储节点的 tree 结构信息
|
|
12
|
+
* 策略是 "重修改轻查询",即修改时候做的事情更多,查询都通过指针来操作
|
|
13
|
+
*/
|
|
14
|
+
export class FlowVirtualTree<T extends { id: string; flowNodeType?: FlowNodeType }>
|
|
15
|
+
implements Disposable
|
|
16
|
+
{
|
|
17
|
+
protected onTreeChangeEmitter = new Emitter<void>();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* tree 结构变化时候触发
|
|
21
|
+
*/
|
|
22
|
+
onTreeChange = this.onTreeChangeEmitter.event;
|
|
23
|
+
|
|
24
|
+
protected map: Map<T, FlowVirtualTree.NodeInfo<T>> = new Map();
|
|
25
|
+
|
|
26
|
+
constructor(readonly root: T) {}
|
|
27
|
+
|
|
28
|
+
dispose() {
|
|
29
|
+
this.map.clear();
|
|
30
|
+
this.onTreeChangeEmitter.dispose();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getInfo(node: T): FlowVirtualTree.NodeInfo<T> {
|
|
34
|
+
let res: FlowVirtualTree.NodeInfo<T> | undefined = this.map.get(node);
|
|
35
|
+
if (!res) {
|
|
36
|
+
res = { children: [] };
|
|
37
|
+
this.map.set(node, res);
|
|
38
|
+
}
|
|
39
|
+
return res;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
clear(): void {
|
|
43
|
+
this.map.clear();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
cloneMap(): Map<T, FlowVirtualTree.NodeInfo<T>> {
|
|
47
|
+
const newMap: Map<T, FlowVirtualTree.NodeInfo<T>> = new Map();
|
|
48
|
+
for (const [key, value] of this.map) {
|
|
49
|
+
newMap.set(key, {
|
|
50
|
+
...value,
|
|
51
|
+
children: value.children.slice(),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return newMap;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clone(): FlowVirtualTree<T> {
|
|
59
|
+
const newTree = new FlowVirtualTree<T>(this.root);
|
|
60
|
+
newTree.map = this.cloneMap();
|
|
61
|
+
return newTree;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
remove(node: T, withChildren = true): void {
|
|
65
|
+
this.removeParent(node);
|
|
66
|
+
if (withChildren) {
|
|
67
|
+
this._removeChildren(node);
|
|
68
|
+
}
|
|
69
|
+
this.map.delete(node);
|
|
70
|
+
this.fireTreeChange();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
addChild(parent: T, child: T, index?: number): T {
|
|
74
|
+
const parentInfo = this.getInfo(parent);
|
|
75
|
+
const childInfo = this.getInfo(child);
|
|
76
|
+
if (childInfo.parent) {
|
|
77
|
+
if (childInfo.parent === parent) return child;
|
|
78
|
+
if (childInfo.parent !== parent) {
|
|
79
|
+
this.removeParent(child);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const len = parentInfo.children.length;
|
|
84
|
+
const idx = typeof index === 'undefined' ? len - 1 : index - 1;
|
|
85
|
+
const lastChild = parentInfo.children[idx];
|
|
86
|
+
const nextChild = parentInfo.children[idx + 1];
|
|
87
|
+
if (lastChild) this.getInfo(lastChild).next = child;
|
|
88
|
+
if (nextChild) this.getInfo(nextChild).pre = child;
|
|
89
|
+
childInfo.pre = lastChild;
|
|
90
|
+
childInfo.next = nextChild;
|
|
91
|
+
parentInfo.children.splice(idx + 1, 0, child);
|
|
92
|
+
childInfo.parent = parent;
|
|
93
|
+
this.fireTreeChange();
|
|
94
|
+
return child;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
moveChilds(parent: T, childs: T[], index?: number): T[] {
|
|
98
|
+
const parentInfo = this.getInfo(parent);
|
|
99
|
+
const len = parentInfo.children.length;
|
|
100
|
+
let childIndex: number = index ?? len;
|
|
101
|
+
|
|
102
|
+
childs.forEach((child) => {
|
|
103
|
+
const childInfo = this.getInfo(child);
|
|
104
|
+
if (childInfo.parent) {
|
|
105
|
+
this.removeParent(child);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
childs.forEach((child) => {
|
|
110
|
+
const childInfo = this.getInfo(child);
|
|
111
|
+
let lastChild: T | undefined = parentInfo.children[childIndex - 1];
|
|
112
|
+
let nextChild: T | undefined = parentInfo.children[childIndex];
|
|
113
|
+
|
|
114
|
+
if (lastChild) this.getInfo(lastChild).next = child;
|
|
115
|
+
if (nextChild) this.getInfo(nextChild).pre = child;
|
|
116
|
+
childInfo.pre = lastChild;
|
|
117
|
+
childInfo.next = nextChild;
|
|
118
|
+
parentInfo.children.splice(childIndex, 0, child);
|
|
119
|
+
childInfo.parent = parent;
|
|
120
|
+
childIndex++;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.fireTreeChange();
|
|
124
|
+
return childs;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getById(id: string): T | undefined {
|
|
128
|
+
for (const node of this.map.keys()) {
|
|
129
|
+
if (node.id === id) return node;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 插入节点到后边
|
|
135
|
+
* @param before
|
|
136
|
+
* @param after
|
|
137
|
+
*/
|
|
138
|
+
insertAfter(before: T, after: T) {
|
|
139
|
+
const beforeInfo = this.getInfo(before);
|
|
140
|
+
const afterInfo = this.getInfo(after);
|
|
141
|
+
this.removeParent(after);
|
|
142
|
+
if (beforeInfo.parent) {
|
|
143
|
+
const parentInfo = this.getInfo(beforeInfo.parent);
|
|
144
|
+
parentInfo.children.splice(parentInfo.children.indexOf(before) + 1, 0, after);
|
|
145
|
+
const { next } = beforeInfo;
|
|
146
|
+
if (next) {
|
|
147
|
+
this.getInfo(next).pre = after;
|
|
148
|
+
}
|
|
149
|
+
afterInfo.next = next;
|
|
150
|
+
beforeInfo.next = after;
|
|
151
|
+
afterInfo.pre = before;
|
|
152
|
+
afterInfo.parent = beforeInfo.parent;
|
|
153
|
+
}
|
|
154
|
+
this.fireTreeChange();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
removeParent(node: T): void {
|
|
158
|
+
const info = this.getInfo(node);
|
|
159
|
+
if (!info.parent) return;
|
|
160
|
+
const parentInfo = this.getInfo(info.parent);
|
|
161
|
+
const index = parentInfo.children.indexOf(node);
|
|
162
|
+
parentInfo.children.splice(index, 1);
|
|
163
|
+
const { pre, next } = info;
|
|
164
|
+
if (pre) this.getInfo(pre).next = next;
|
|
165
|
+
if (next) this.getInfo(next).pre = pre;
|
|
166
|
+
this.fireTreeChange();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private _removeChildren(node: T): void {
|
|
170
|
+
// 删除子节点
|
|
171
|
+
const children = this.getChildren(node);
|
|
172
|
+
if (children.length > 0) {
|
|
173
|
+
children.forEach((child) => {
|
|
174
|
+
this._removeChildren(child);
|
|
175
|
+
this.map.delete(child);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getParent(node: T): T | undefined {
|
|
181
|
+
return this.getInfo(node).parent;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getPre(node: T): T | undefined {
|
|
185
|
+
return this.getInfo(node).pre;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getNext(node: T): T | undefined {
|
|
189
|
+
return this.getInfo(node).next;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getChildren(node: T): T[] {
|
|
193
|
+
return this.getInfo(node).children;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
traverse(
|
|
197
|
+
fn: (node: T, depth: number, index: number) => boolean | void,
|
|
198
|
+
node = this.root,
|
|
199
|
+
depth = 0,
|
|
200
|
+
index = 0
|
|
201
|
+
): boolean | void {
|
|
202
|
+
const breaked = fn(node, depth, index);
|
|
203
|
+
if (breaked) return true;
|
|
204
|
+
const info = this.getInfo(node);
|
|
205
|
+
const shouldBreak = info.children.find((child, i) => this.traverse(fn, child, depth + 1, i));
|
|
206
|
+
if (shouldBreak) return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 通知文档树结构更新
|
|
211
|
+
*/
|
|
212
|
+
fireTreeChange(): void {
|
|
213
|
+
this.onTreeChangeEmitter.fire();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
get size(): number {
|
|
217
|
+
return this.map.size;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
toString(showType?: boolean): string {
|
|
221
|
+
const ret: string[] = [];
|
|
222
|
+
this.traverse((node, depth) => {
|
|
223
|
+
if (depth === 0) {
|
|
224
|
+
ret.push(node.id);
|
|
225
|
+
} else {
|
|
226
|
+
ret.push(
|
|
227
|
+
`|${new Array(depth).fill('--').join('')} ${
|
|
228
|
+
showType ? `${node.flowNodeType}(${node.id})` : node.id
|
|
229
|
+
}`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
return `${ret.join('\n')}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export namespace FlowVirtualTree {
|
|
238
|
+
export interface NodeInfo<T> {
|
|
239
|
+
parent?: T;
|
|
240
|
+
next?: T;
|
|
241
|
+
pre?: T;
|
|
242
|
+
children: T[];
|
|
243
|
+
}
|
|
244
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './typings';
|
|
7
|
+
export * from './entities';
|
|
8
|
+
export * from './datas';
|
|
9
|
+
export * from './flow-document';
|
|
10
|
+
export * from './flow-virtual-tree';
|
|
11
|
+
export * from './flow-document-contribution';
|
|
12
|
+
export * from './flow-document-container-module';
|
|
13
|
+
export * from './flow-document-config';
|
|
14
|
+
export * from './flow-document-options';
|
|
15
|
+
export * from './services';
|
|
16
|
+
export * from './utils';
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { injectable, inject, multiInject, optional } from 'inversify';
|
|
7
|
+
import { IPoint, OriginSchema, PaddingSchema, ScrollSchema, SizeSchema } from '@flowgram-vue/utils';
|
|
8
|
+
|
|
9
|
+
import { type FlowLayout, FlowLayoutDefault, FlowLayoutContribution } from '../typings';
|
|
10
|
+
import { type FlowDocument, FlowDocumentProvider } from '../flow-document';
|
|
11
|
+
import { FlowNodeEntity } from '../entities';
|
|
12
|
+
import { FlowNodeTransformData } from '../datas';
|
|
13
|
+
|
|
14
|
+
// 开始节点距离上边 36 像素
|
|
15
|
+
const DEFAULT_SCROLL = -36;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 用于描述节点的结构特征
|
|
19
|
+
*/
|
|
20
|
+
interface FlowNodeTransformStructData {
|
|
21
|
+
childrenLength: number;
|
|
22
|
+
index: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 用于描述节点的结构特征
|
|
26
|
+
*/
|
|
27
|
+
interface FlowNodeTransformStructData {
|
|
28
|
+
childrenLength: number;
|
|
29
|
+
index: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isStructDataEqual(
|
|
33
|
+
struct1: FlowNodeTransformStructData,
|
|
34
|
+
struct2: FlowNodeTransformStructData
|
|
35
|
+
): boolean {
|
|
36
|
+
return struct1.childrenLength === struct2.childrenLength && struct1.index === struct2.index;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@injectable()
|
|
40
|
+
export class HorizontalFixedLayout implements FlowLayout {
|
|
41
|
+
name = FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT;
|
|
42
|
+
|
|
43
|
+
protected structDataMap = new WeakMap<FlowNodeEntity, FlowNodeTransformStructData>();
|
|
44
|
+
|
|
45
|
+
@inject(FlowDocumentProvider) protected documentProvider: FlowDocumentProvider;
|
|
46
|
+
|
|
47
|
+
@multiInject(FlowLayoutContribution)
|
|
48
|
+
@optional()
|
|
49
|
+
contribs?: FlowLayoutContribution[];
|
|
50
|
+
|
|
51
|
+
get document(): FlowDocument {
|
|
52
|
+
return this.documentProvider();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
reload() {
|
|
56
|
+
this.structDataMap = new WeakMap();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 更新布局
|
|
61
|
+
*/
|
|
62
|
+
update(): void {
|
|
63
|
+
this.updateLocalTransform(this.document.root);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 更新节点的偏移
|
|
68
|
+
* @param node
|
|
69
|
+
* @param forceChange
|
|
70
|
+
*/
|
|
71
|
+
updateLocalTransform(node: FlowNodeEntity, forceChange = false): boolean {
|
|
72
|
+
const { children, parent, isInlineBlock } = node;
|
|
73
|
+
|
|
74
|
+
const transform = node.getData<FlowNodeTransformData>(FlowNodeTransformData);
|
|
75
|
+
const { getDelta, getOrigin } = node.getNodeRegistry();
|
|
76
|
+
const lastStructData = this.structDataMap.get(node) || {
|
|
77
|
+
childrenLength: 0,
|
|
78
|
+
index: -1,
|
|
79
|
+
};
|
|
80
|
+
// 重新计算都要清空 bounds 缓存,因为 bounds 依赖所有
|
|
81
|
+
node.clearMemoGlobal();
|
|
82
|
+
let localDirty = transform.localDirty || forceChange;
|
|
83
|
+
const newStructData: FlowNodeTransformStructData = {
|
|
84
|
+
index: node.index,
|
|
85
|
+
childrenLength: node.children.length,
|
|
86
|
+
};
|
|
87
|
+
// index 变化也要重新计算
|
|
88
|
+
if (!isStructDataEqual(lastStructData, newStructData)) {
|
|
89
|
+
localDirty = true;
|
|
90
|
+
this.structDataMap.set(node, newStructData);
|
|
91
|
+
}
|
|
92
|
+
// Step1: 计算子节点
|
|
93
|
+
let siblingDirty = false;
|
|
94
|
+
if (children.length > 0) {
|
|
95
|
+
for (const child of children) {
|
|
96
|
+
const childDirty = this.updateLocalTransform(child, siblingDirty);
|
|
97
|
+
// 子节点变更则父节点跟着变更
|
|
98
|
+
if (childDirty) {
|
|
99
|
+
siblingDirty = true;
|
|
100
|
+
localDirty = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// 如果没有变更则不执行
|
|
105
|
+
if (!localDirty) return false;
|
|
106
|
+
// Step2: 计算节点的 position 偏移量
|
|
107
|
+
node.clearMemoLocal();
|
|
108
|
+
transform.transform.update({
|
|
109
|
+
origin: getOrigin ? getOrigin(transform, this) : this.getDefaultNodeOrigin(),
|
|
110
|
+
});
|
|
111
|
+
const preTransform = transform.pre;
|
|
112
|
+
const delta = getDelta?.(transform, this) || { x: 0, y: 0 };
|
|
113
|
+
const inlineSpacingPre =
|
|
114
|
+
isInlineBlock && transform.parent?.inlineSpacingPre ? transform.parent?.inlineSpacingPre : 0;
|
|
115
|
+
const fromParentDelta = parent?.getNodeRegistry().getChildDelta?.(transform, this) || {
|
|
116
|
+
x: 0,
|
|
117
|
+
y: 0,
|
|
118
|
+
};
|
|
119
|
+
delta.x += fromParentDelta.x;
|
|
120
|
+
delta.y += fromParentDelta.y;
|
|
121
|
+
|
|
122
|
+
// Step3:根据上一个节点的相对偏移算当前偏移
|
|
123
|
+
const position = { x: delta.x, y: delta.y };
|
|
124
|
+
// 水平布局
|
|
125
|
+
if (isInlineBlock) {
|
|
126
|
+
position.x += inlineSpacingPre;
|
|
127
|
+
} else {
|
|
128
|
+
position.x += preTransform?.localBounds.right || 0;
|
|
129
|
+
position.x += preTransform?.spacing || 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
transform.transform.update({
|
|
133
|
+
size: transform.data.size,
|
|
134
|
+
position,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// 布局结束后可执行额外逻辑
|
|
138
|
+
this.onAfterUpdateLocalTransform(transform);
|
|
139
|
+
|
|
140
|
+
transform.localDirty = false;
|
|
141
|
+
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
onAfterUpdateLocalTransform(transform: FlowNodeTransformData) {
|
|
146
|
+
// 执行 register 上的 onAfterUpdateLocalTransform
|
|
147
|
+
const { onAfterUpdateLocalTransform } = transform.entity.getNodeRegistry();
|
|
148
|
+
onAfterUpdateLocalTransform?.(transform, this);
|
|
149
|
+
|
|
150
|
+
// 执行 contribution 上的 onAfterUpdateLocalTransform
|
|
151
|
+
this.contribs?.forEach((_contrib) => {
|
|
152
|
+
_contrib?.onAfterUpdateLocalTransform?.(transform, this);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getNodeTransform(node: FlowNodeEntity): FlowNodeTransformData {
|
|
157
|
+
return node.getData(FlowNodeTransformData)!;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getPadding(node: FlowNodeEntity): PaddingSchema {
|
|
161
|
+
const { inlineSpacingPre, inlineSpacingAfter, padding } = node.getNodeMeta();
|
|
162
|
+
const transform = this.getNodeTransform(node);
|
|
163
|
+
if (padding) {
|
|
164
|
+
return typeof padding === 'function' ? padding(transform) : padding;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const paddingPre =
|
|
168
|
+
typeof inlineSpacingPre === 'function' ? inlineSpacingPre(transform) : inlineSpacingPre;
|
|
169
|
+
const paddingAfter =
|
|
170
|
+
typeof inlineSpacingAfter === 'function' ? inlineSpacingAfter(transform) : inlineSpacingAfter;
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
left: paddingPre,
|
|
174
|
+
top: 0,
|
|
175
|
+
right: paddingAfter,
|
|
176
|
+
bottom: 0,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getInitScroll(contentSize: SizeSchema): ScrollSchema {
|
|
181
|
+
return {
|
|
182
|
+
scrollX: DEFAULT_SCROLL,
|
|
183
|
+
scrollY: -contentSize.height / 2,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getDefaultInputPoint(node: FlowNodeEntity): IPoint {
|
|
188
|
+
return this.getNodeTransform(node).bounds.leftCenter;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getDefaultOutputPoint(node: FlowNodeEntity): IPoint {
|
|
192
|
+
return this.getNodeTransform(node).bounds.rightCenter;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
getDefaultNodeOrigin(): OriginSchema {
|
|
196
|
+
return { x: 0, y: 0.5 };
|
|
197
|
+
}
|
|
198
|
+
}
|