@flowgram-vue/free-layout-core 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 +0 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +0 -0
- package/dist/index.js.map +1 -0
- package/dist/typings.cjs +46 -0
- package/dist/typings.cjs.map +1 -0
- package/dist/typings.d.ts +1 -0
- package/dist/typings.js +40 -0
- package/dist/typings.js.map +1 -0
- package/package.json +78 -0
- package/src/composables/index.ts +24 -0
- package/src/composables/typings.ts +97 -0
- package/src/composables/use-current-dom-node.ts +18 -0
- package/src/composables/use-current-entity.ts +15 -0
- package/src/composables/use-node-render-context.ts +18 -0
- package/src/composables/use-node-render.ts +192 -0
- package/src/composables/use-playground-readonly-state.ts +25 -0
- package/src/composables/use-workflow-document.ts +12 -0
- package/src/constants.ts +17 -0
- package/src/entities/index.ts +8 -0
- package/src/entities/workflow-line-entity.ts +640 -0
- package/src/entities/workflow-node-entity.ts +17 -0
- package/src/entities/workflow-port-entity.ts +340 -0
- package/src/entity-datas/index.ts +8 -0
- package/src/entity-datas/workflow-line-render-data.ts +136 -0
- package/src/entity-datas/workflow-node-lines-data.ts +166 -0
- package/src/entity-datas/workflow-node-ports-data.ts +253 -0
- package/src/env.d.ts +10 -0
- package/src/index.ts +18 -0
- package/src/layout/free-layout.ts +166 -0
- package/src/layout/index.ts +6 -0
- package/src/service/index.ts +10 -0
- package/src/service/workflow-drag-service.ts +969 -0
- package/src/service/workflow-hover-service.ts +106 -0
- package/src/service/workflow-operation-base-service.ts +118 -0
- package/src/service/workflow-reset-layout-service.ts +87 -0
- package/src/service/workflow-select-service.ts +128 -0
- package/src/typings/index.ts +19 -0
- package/src/typings/workflow-drag.ts +51 -0
- package/src/typings/workflow-edge.ts +15 -0
- package/src/typings/workflow-json.ts +64 -0
- package/src/typings/workflow-line.ts +71 -0
- package/src/typings/workflow-node.ts +52 -0
- package/src/typings/workflow-operation.ts +40 -0
- package/src/typings/workflow-registry.ts +34 -0
- package/src/typings/workflow-sub-canvas.ts +15 -0
- package/src/utils/build-group-json.ts +47 -0
- package/src/utils/compose.ts +6 -0
- package/src/utils/fit-view.ts +21 -0
- package/src/utils/flow-node-form-data.ts +45 -0
- package/src/utils/get-anti-overlap-position.ts +44 -0
- package/src/utils/get-line-center.ts +22 -0
- package/src/utils/get-url-params.ts +50 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/layout-to-positions.ts +65 -0
- package/src/utils/location-config-to-point.ts +40 -0
- package/src/utils/nanoid.ts +10 -0
- package/src/utils/statics.ts +27 -0
- package/src/vue/index.ts +6 -0
- package/src/vue/node-render-provider.vue +16 -0
- package/src/workflow-commands.ts +14 -0
- package/src/workflow-document-container-module.ts +50 -0
- package/src/workflow-document-contribution.ts +31 -0
- package/src/workflow-document-option.ts +148 -0
- package/src/workflow-document.ts +873 -0
- package/src/workflow-lines-manager.ts +596 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isEqual } from 'lodash-es';
|
|
7
|
+
import { FlowNodeRenderData } from '@flowgram-vue/document';
|
|
8
|
+
import { EntityData, SizeData } from '@flowgram-vue/core';
|
|
9
|
+
|
|
10
|
+
import { type WorkflowPortType, getPortEntityId } from '../utils/statics';
|
|
11
|
+
import { type LinePoint, LinePointLocation, type WorkflowNodeMeta } from '../typings';
|
|
12
|
+
import { WorkflowPortEntity } from '../entities/workflow-port-entity';
|
|
13
|
+
import { type WorkflowNodeEntity, type WorkflowPort, type WorkflowPorts } from '../entities';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 节点的点位信息
|
|
17
|
+
* portsData 只监听点位的数目和类型,不监听点位的 position 变化
|
|
18
|
+
*/
|
|
19
|
+
export class WorkflowNodePortsData extends EntityData {
|
|
20
|
+
public static readonly type = 'WorkflowNodePortsData';
|
|
21
|
+
|
|
22
|
+
public readonly entity: WorkflowNodeEntity;
|
|
23
|
+
|
|
24
|
+
/** 静态的 ports 数据 */
|
|
25
|
+
protected _staticPorts: WorkflowPorts = [];
|
|
26
|
+
|
|
27
|
+
/** 存储 port 实体的 id,用于判断 port 是否存在 */
|
|
28
|
+
protected _portIDSet = new Set<string>();
|
|
29
|
+
|
|
30
|
+
/** 上一次的 ports 数据,用于判断 ports 是否发生变化 */
|
|
31
|
+
protected _prePorts: WorkflowPorts;
|
|
32
|
+
|
|
33
|
+
constructor(entity: WorkflowNodeEntity) {
|
|
34
|
+
super(entity);
|
|
35
|
+
this.entity = entity;
|
|
36
|
+
const meta = entity.getNodeMeta<WorkflowNodeMeta>();
|
|
37
|
+
// 动态模式默认为空, 非动态模式默认左右两个点位
|
|
38
|
+
const defaultPorts: WorkflowPorts = meta.useDynamicPort
|
|
39
|
+
? []
|
|
40
|
+
: [{ type: 'input' }, { type: 'output' }];
|
|
41
|
+
this._staticPorts = meta.defaultPorts?.slice() || defaultPorts;
|
|
42
|
+
this.updatePorts(this._staticPorts);
|
|
43
|
+
if (meta.useDynamicPort) {
|
|
44
|
+
this.toDispose.push(
|
|
45
|
+
// 只需要监听节点的大小,因为算的是相对位置
|
|
46
|
+
entity.getData!(SizeData)!.onDataChange(() => {
|
|
47
|
+
// 有可能节点被销毁了
|
|
48
|
+
if (entity.getData!(SizeData).width && entity.getData!(SizeData).height) {
|
|
49
|
+
this.updateDynamicPorts();
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
this.onDispose(() => {
|
|
55
|
+
this.allPorts.forEach((port) => port.dispose());
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public getDefaultData(): any {
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Update all ports data, includes static ports and dynamic ports
|
|
65
|
+
* @param ports
|
|
66
|
+
*/
|
|
67
|
+
public updateAllPorts(ports?: WorkflowPorts) {
|
|
68
|
+
const meta = this.entity.getNodeMeta<WorkflowNodeMeta>();
|
|
69
|
+
if (ports) {
|
|
70
|
+
this._staticPorts = ports;
|
|
71
|
+
}
|
|
72
|
+
if (meta.useDynamicPort) {
|
|
73
|
+
this.updateDynamicPorts();
|
|
74
|
+
} else {
|
|
75
|
+
this.updatePorts(this._staticPorts);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @deprecated use `updateAllPorts` instead
|
|
81
|
+
*/
|
|
82
|
+
public updateStaticPorts(ports: WorkflowPorts): void {
|
|
83
|
+
this.updateAllPorts(ports);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 动态计算点位,通过 dom 的 data-port-key
|
|
88
|
+
*/
|
|
89
|
+
public updateDynamicPorts(): void {
|
|
90
|
+
const domNode = this.entity.getData(FlowNodeRenderData)!.node;
|
|
91
|
+
const elements = domNode.querySelectorAll<HTMLDivElement>('[data-port-id]');
|
|
92
|
+
const staticPorts: WorkflowPorts = this._staticPorts;
|
|
93
|
+
const dynamicPorts: WorkflowPorts = [];
|
|
94
|
+
if (elements.length > 0) {
|
|
95
|
+
dynamicPorts.push(
|
|
96
|
+
...Array.from(elements).map((element) => ({
|
|
97
|
+
portID: element.getAttribute('data-port-id')!,
|
|
98
|
+
type: element.getAttribute('data-port-type')! as WorkflowPortType,
|
|
99
|
+
location: element.getAttribute('data-port-location')! as LinePointLocation,
|
|
100
|
+
targetElement: element,
|
|
101
|
+
}))
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
this.updatePorts(staticPorts.concat(dynamicPorts));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 根据 key 获取 port 实体
|
|
109
|
+
*/
|
|
110
|
+
public getPortEntityByKey(
|
|
111
|
+
portType: WorkflowPortType,
|
|
112
|
+
portKey?: string | number
|
|
113
|
+
): WorkflowPortEntity {
|
|
114
|
+
const entity = this.getOrCreatePortEntity({
|
|
115
|
+
type: portType,
|
|
116
|
+
portID: portKey,
|
|
117
|
+
});
|
|
118
|
+
return entity;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 更新 ports 数据
|
|
123
|
+
*/
|
|
124
|
+
protected updatePorts(ports: WorkflowPorts): void {
|
|
125
|
+
if (!isEqual(this._prePorts, ports)) {
|
|
126
|
+
const portKeys = ports.map((port) => this.getPortId(port.type, port.portID));
|
|
127
|
+
this._portIDSet.forEach((portId) => {
|
|
128
|
+
if (!portKeys.includes(portId)) {
|
|
129
|
+
this.getPortEntity(portId)?.dispose();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
ports.forEach((port) => this.updatePortEntity(port));
|
|
133
|
+
this._prePorts = ports;
|
|
134
|
+
this.fireChange();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Note: 为什么调用 port.validate 不够,需要调用 line.validate
|
|
138
|
+
// 原因:假设有这样的连线:dynamic port → end 节点。
|
|
139
|
+
// line.validate 时,line.fromPort 可能为 undefined(未创建实体),导致 end 节点上的 port 未正确校验
|
|
140
|
+
// 所以需要在所有 port entities 准备完成后,通过再次调用 line.validate 来触发连线另一端的 port 更新
|
|
141
|
+
this.allPorts.forEach((port) => {
|
|
142
|
+
port.allLines.forEach((line) => {
|
|
143
|
+
line.validate();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 获取所有 port entities
|
|
150
|
+
*/
|
|
151
|
+
public get allPorts(): WorkflowPortEntity[] {
|
|
152
|
+
return Array.from(this._portIDSet)
|
|
153
|
+
.map((portId) => this.getPortEntity(portId)!)
|
|
154
|
+
.filter(Boolean); // dispose 时,会获取不到 port
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 获取输入点位
|
|
159
|
+
*/
|
|
160
|
+
public get inputPorts(): WorkflowPortEntity[] {
|
|
161
|
+
return this.allPorts.filter((port) => port.portType === 'input');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 获取输出点位
|
|
166
|
+
*/
|
|
167
|
+
public get outputPorts(): WorkflowPortEntity[] {
|
|
168
|
+
return this.allPorts.filter((port) => port.portType === 'output');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 获取输入点位置
|
|
173
|
+
*/
|
|
174
|
+
public get inputPoints(): LinePoint[] {
|
|
175
|
+
return this.inputPorts.map((port) => port.point);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 获取输出点位置
|
|
180
|
+
*/
|
|
181
|
+
public get outputPoints(): LinePoint[] {
|
|
182
|
+
return this.inputPorts.map((port) => port.point);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 根据 key 获取 输入点位置
|
|
187
|
+
*/
|
|
188
|
+
public getInputPoint(key?: string | number): LinePoint {
|
|
189
|
+
return this.getPortEntityByKey('input', key).point;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 根据 key 获取输出点位置
|
|
194
|
+
*/
|
|
195
|
+
public getOutputPoint(key?: string | number): LinePoint {
|
|
196
|
+
return this.getPortEntityByKey('output', key).point;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 获取 port 实体
|
|
201
|
+
*/
|
|
202
|
+
protected getPortEntity(portId: string): WorkflowPortEntity | undefined {
|
|
203
|
+
if (!this._portIDSet.has(portId)) {
|
|
204
|
+
// 如果不是自身创建的 port,则返回 undefined
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
return this.entity.entityManager.getEntityById<WorkflowPortEntity>(portId)!;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 拼接 port 实体的 id
|
|
212
|
+
*/
|
|
213
|
+
protected getPortId(portType: WorkflowPortType, portKey: string | number = ''): string {
|
|
214
|
+
return getPortEntityId(this.entity, portType, portKey);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 创建 port 实体
|
|
219
|
+
*/
|
|
220
|
+
protected createPortEntity(portInfo: WorkflowPort): WorkflowPortEntity {
|
|
221
|
+
const id = this.getPortId(portInfo.type, portInfo.portID);
|
|
222
|
+
let portEntity = this.entity.entityManager.getEntityById<WorkflowPortEntity>(id);
|
|
223
|
+
if (!portEntity) {
|
|
224
|
+
portEntity = this.entity.entityManager.createEntity<WorkflowPortEntity>(WorkflowPortEntity, {
|
|
225
|
+
id,
|
|
226
|
+
node: this.entity,
|
|
227
|
+
...portInfo,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
portEntity.onDispose(() => {
|
|
231
|
+
this._portIDSet.delete(id);
|
|
232
|
+
});
|
|
233
|
+
this._portIDSet.add(id);
|
|
234
|
+
return portEntity;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 获取或创建 port 实体
|
|
239
|
+
*/
|
|
240
|
+
protected getOrCreatePortEntity(portInfo: WorkflowPort): WorkflowPortEntity {
|
|
241
|
+
const id = this.getPortId(portInfo.type, portInfo.portID);
|
|
242
|
+
return this.getPortEntity(id) ?? this.createPortEntity(portInfo);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 更新 port 实体
|
|
247
|
+
*/
|
|
248
|
+
protected updatePortEntity(portInfo: WorkflowPort): WorkflowPortEntity {
|
|
249
|
+
const portEntity = this.getOrCreatePortEntity(portInfo);
|
|
250
|
+
portEntity.update(portInfo);
|
|
251
|
+
return portEntity;
|
|
252
|
+
}
|
|
253
|
+
}
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare module '*.vue' {
|
|
7
|
+
import type { DefineComponent } from 'vue';
|
|
8
|
+
const component: DefineComponent<object, object, unknown>;
|
|
9
|
+
export default component;
|
|
10
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './workflow-commands';
|
|
7
|
+
export * from './composables';
|
|
8
|
+
export * from './vue';
|
|
9
|
+
export * from './utils';
|
|
10
|
+
export * from './typings';
|
|
11
|
+
export * from './entities';
|
|
12
|
+
export * from './constants';
|
|
13
|
+
export * from './entity-datas';
|
|
14
|
+
export * from './service';
|
|
15
|
+
export * from './workflow-document';
|
|
16
|
+
export * from './workflow-document-container-module';
|
|
17
|
+
export * from './workflow-lines-manager';
|
|
18
|
+
export * from './workflow-document-option';
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { inject, injectable } from 'inversify';
|
|
7
|
+
import {
|
|
8
|
+
type IPoint,
|
|
9
|
+
PaddingSchema,
|
|
10
|
+
Rectangle,
|
|
11
|
+
type ScrollSchema,
|
|
12
|
+
SizeSchema,
|
|
13
|
+
} from '@flowgram-vue/utils';
|
|
14
|
+
import {
|
|
15
|
+
type FlowDocument,
|
|
16
|
+
type FlowLayout,
|
|
17
|
+
type FlowNodeEntity,
|
|
18
|
+
FlowDocumentProvider,
|
|
19
|
+
FlowNodeTransformData,
|
|
20
|
+
} from '@flowgram-vue/document';
|
|
21
|
+
import { PlaygroundConfigEntity, TransformData } from '@flowgram-vue/core';
|
|
22
|
+
|
|
23
|
+
export const FREE_LAYOUT_KEY = 'free-layout';
|
|
24
|
+
/**
|
|
25
|
+
* 自由画布布局
|
|
26
|
+
*/
|
|
27
|
+
@injectable()
|
|
28
|
+
export class FreeLayout implements FlowLayout {
|
|
29
|
+
name = FREE_LAYOUT_KEY;
|
|
30
|
+
|
|
31
|
+
@inject(PlaygroundConfigEntity) playgroundConfig: PlaygroundConfigEntity;
|
|
32
|
+
|
|
33
|
+
@inject(FlowDocumentProvider)
|
|
34
|
+
protected documentProvider: FlowDocumentProvider;
|
|
35
|
+
|
|
36
|
+
get document(): FlowDocument {
|
|
37
|
+
return this.documentProvider();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 更新布局
|
|
42
|
+
*/
|
|
43
|
+
update(): void {
|
|
44
|
+
if (this.document.root.getData(FlowNodeTransformData)?.localDirty) {
|
|
45
|
+
this.document.root.clearMemoGlobal();
|
|
46
|
+
// this.document.root.getData(FlowNodeTransformData)!.localDirty = false
|
|
47
|
+
}
|
|
48
|
+
// 自由画布同步同步大小, TODO 这个移动到 createWorkflowNode
|
|
49
|
+
// this.document.root.allChildren.forEach(this.syncTransform.bind(this))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
syncTransform(node: FlowNodeEntity): void {
|
|
53
|
+
const transform = node.getData<FlowNodeTransformData>(FlowNodeTransformData)!;
|
|
54
|
+
if (!transform.localDirty) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
node.clearMemoGlobal();
|
|
58
|
+
node.clearMemoLocal();
|
|
59
|
+
// 同步 size 给原始的 transform
|
|
60
|
+
transform.transform.update({
|
|
61
|
+
size: transform.data.size,
|
|
62
|
+
});
|
|
63
|
+
if (!node.parent) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
node.parent.clearMemoGlobal();
|
|
67
|
+
node.parent.clearMemoLocal();
|
|
68
|
+
const parentTransform = node.parent.getData<FlowNodeTransformData>(FlowNodeTransformData);
|
|
69
|
+
parentTransform.transform.fireChange();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 更新所有受影响的上下游节点
|
|
74
|
+
*/
|
|
75
|
+
updateAffectedTransform(node: FlowNodeEntity): void {
|
|
76
|
+
const transformData = node.transform;
|
|
77
|
+
if (!transformData.localDirty) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const allParents = this.getAllParents(node);
|
|
81
|
+
const allBlocks = this.getAllBlocks(node).reverse();
|
|
82
|
+
const affectedNodes = [...allBlocks, ...allParents];
|
|
83
|
+
affectedNodes.forEach((node) => {
|
|
84
|
+
this.fireChange(node);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 获取节点的 padding 数据
|
|
90
|
+
* @param node
|
|
91
|
+
*/
|
|
92
|
+
getPadding(node: FlowNodeEntity): PaddingSchema {
|
|
93
|
+
const { padding } = node.getNodeMeta();
|
|
94
|
+
const transform = node.getData<FlowNodeTransformData>(FlowNodeTransformData);
|
|
95
|
+
if (padding) {
|
|
96
|
+
return typeof padding === 'function' ? padding(transform) : padding;
|
|
97
|
+
}
|
|
98
|
+
return PaddingSchema.empty();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 默认滚动到 fitview 区域
|
|
103
|
+
* @param contentSize
|
|
104
|
+
*/
|
|
105
|
+
getInitScroll(contentSize: SizeSchema): ScrollSchema {
|
|
106
|
+
const bounds = Rectangle.enlarge(
|
|
107
|
+
this.document.getAllNodes().map((node) => node.getData<TransformData>(TransformData).bounds)
|
|
108
|
+
).pad(30, 30); // 留出 30 像素的边界
|
|
109
|
+
const viewport = this.playgroundConfig.getViewport(false);
|
|
110
|
+
const zoom = SizeSchema.fixSize(bounds, viewport);
|
|
111
|
+
return {
|
|
112
|
+
scrollX: (bounds.x + bounds.width / 2) * zoom - this.playgroundConfig.config.width / 2,
|
|
113
|
+
scrollY: (bounds.y + bounds.height / 2) * zoom - this.playgroundConfig.config.height / 2,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 获取默认输入点
|
|
119
|
+
*/
|
|
120
|
+
getDefaultInputPoint(node: FlowNodeEntity): IPoint {
|
|
121
|
+
return node.getData<TransformData>(TransformData)!.bounds.leftCenter;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 获取默认输出点
|
|
126
|
+
*/
|
|
127
|
+
getDefaultOutputPoint(node: FlowNodeEntity): IPoint {
|
|
128
|
+
return node.getData<TransformData>(TransformData)!.bounds.rightCenter;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 水平中心点
|
|
133
|
+
*/
|
|
134
|
+
getDefaultNodeOrigin(): IPoint {
|
|
135
|
+
return { x: 0.5, y: 0 };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private getAllParents(node: FlowNodeEntity): FlowNodeEntity[] {
|
|
139
|
+
const parents: FlowNodeEntity[] = [];
|
|
140
|
+
let current = node.parent;
|
|
141
|
+
|
|
142
|
+
while (current) {
|
|
143
|
+
parents.push(current);
|
|
144
|
+
current = current.parent;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return parents;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private getAllBlocks(node: FlowNodeEntity): FlowNodeEntity[] {
|
|
151
|
+
return node.blocks.reduce<FlowNodeEntity[]>(
|
|
152
|
+
(acc, child) => [...acc, ...this.getAllBlocks(child)],
|
|
153
|
+
[node]
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private fireChange(node?: FlowNodeEntity): void {
|
|
158
|
+
const transformData = node?.transform;
|
|
159
|
+
if (!node || !transformData?.localDirty) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
node.clearMemoGlobal();
|
|
163
|
+
node.clearMemoLocal();
|
|
164
|
+
transformData.transform.fireChange();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './workflow-select-service';
|
|
7
|
+
export * from './workflow-hover-service';
|
|
8
|
+
export * from './workflow-drag-service';
|
|
9
|
+
export * from './workflow-reset-layout-service';
|
|
10
|
+
export * from './workflow-operation-base-service';
|