@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,596 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { last } from 'lodash-es';
|
|
7
|
+
import { inject, injectable } from 'inversify';
|
|
8
|
+
import { DisposableCollection, Emitter, type IPoint } from '@flowgram-vue/utils';
|
|
9
|
+
import { FlowNodeRenderData, FlowNodeTransformData } from '@flowgram-vue/document';
|
|
10
|
+
import { EntityManager, PlaygroundConfigEntity } from '@flowgram-vue/core';
|
|
11
|
+
|
|
12
|
+
import { WorkflowDocumentOptions } from './workflow-document-option';
|
|
13
|
+
import { type WorkflowDocument } from './workflow-document';
|
|
14
|
+
import { getPortEntityIdByNodeId } from './utils/statics';
|
|
15
|
+
import { WorkflowPortType } from './utils';
|
|
16
|
+
import {
|
|
17
|
+
LineColor,
|
|
18
|
+
LineColors,
|
|
19
|
+
LinePoint,
|
|
20
|
+
LineRenderType,
|
|
21
|
+
LineType,
|
|
22
|
+
type WorkflowLineRenderContributionFactory,
|
|
23
|
+
} from './typings/workflow-line';
|
|
24
|
+
import {
|
|
25
|
+
type WorkflowContentChangeEvent,
|
|
26
|
+
WorkflowContentChangeType,
|
|
27
|
+
type WorkflowEdgeJSON,
|
|
28
|
+
WorkflowNodeRegistry,
|
|
29
|
+
} from './typings';
|
|
30
|
+
import { WorkflowHoverService, WorkflowSelectService } from './service';
|
|
31
|
+
import { WorkflowNodeLinesData } from './entity-datas/workflow-node-lines-data';
|
|
32
|
+
import { WorkflowLineRenderData } from './entity-datas';
|
|
33
|
+
import {
|
|
34
|
+
LINE_HOVER_DISTANCE,
|
|
35
|
+
WorkflowLineEntity,
|
|
36
|
+
type WorkflowLineInfo,
|
|
37
|
+
type WorkflowLinePortInfo,
|
|
38
|
+
WorkflowLineUIState,
|
|
39
|
+
type WorkflowNodeEntity,
|
|
40
|
+
WorkflowPortEntity,
|
|
41
|
+
} from './entities';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 线条管理
|
|
45
|
+
*/
|
|
46
|
+
@injectable()
|
|
47
|
+
export class WorkflowLinesManager {
|
|
48
|
+
protected document: WorkflowDocument;
|
|
49
|
+
|
|
50
|
+
protected toDispose = new DisposableCollection();
|
|
51
|
+
|
|
52
|
+
protected readonly portLineMap = new Map<string, Set<WorkflowLineEntity>>();
|
|
53
|
+
// 线条类型
|
|
54
|
+
|
|
55
|
+
protected _lineType: LineRenderType = LineType.BEZIER;
|
|
56
|
+
|
|
57
|
+
protected onAvailableLinesChangeEmitter = new Emitter<WorkflowContentChangeEvent>();
|
|
58
|
+
|
|
59
|
+
protected onForceUpdateEmitter = new Emitter<void>();
|
|
60
|
+
|
|
61
|
+
protected defaultUIState: Partial<WorkflowLineUIState> | undefined = undefined;
|
|
62
|
+
|
|
63
|
+
@inject(WorkflowHoverService) hoverService: WorkflowHoverService;
|
|
64
|
+
|
|
65
|
+
@inject(WorkflowSelectService) selectService: WorkflowSelectService;
|
|
66
|
+
|
|
67
|
+
@inject(EntityManager) protected readonly entityManager: EntityManager;
|
|
68
|
+
|
|
69
|
+
@inject(WorkflowDocumentOptions)
|
|
70
|
+
readonly options: WorkflowDocumentOptions;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 有效的线条被添加或者删除时候触发,未连上的线条不算
|
|
74
|
+
*/
|
|
75
|
+
readonly onAvailableLinesChange = this.onAvailableLinesChangeEmitter.event;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 强制渲染 lines
|
|
79
|
+
*/
|
|
80
|
+
readonly onForceUpdate = this.onForceUpdateEmitter.event;
|
|
81
|
+
|
|
82
|
+
readonly contributionFactories: WorkflowLineRenderContributionFactory[] = [];
|
|
83
|
+
|
|
84
|
+
init(doc: WorkflowDocument): void {
|
|
85
|
+
this.document = doc;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
setDefaultUIState(defaultUIState: Partial<WorkflowLineUIState>): void {
|
|
89
|
+
this.defaultUIState = defaultUIState;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
forceUpdate() {
|
|
93
|
+
this.onForceUpdateEmitter.fire();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get lineType() {
|
|
97
|
+
return this._lineType;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get lineColor(): LineColor {
|
|
101
|
+
const color: LineColor = {
|
|
102
|
+
default: LineColors.DEFUALT,
|
|
103
|
+
error: LineColors.ERROR,
|
|
104
|
+
hidden: LineColors.HIDDEN,
|
|
105
|
+
drawing: LineColors.DRAWING,
|
|
106
|
+
hovered: LineColors.HOVER,
|
|
107
|
+
selected: LineColors.SELECTED,
|
|
108
|
+
flowing: LineColors.FLOWING,
|
|
109
|
+
};
|
|
110
|
+
if (this.options.lineColor) {
|
|
111
|
+
Object.assign(color, this.options.lineColor);
|
|
112
|
+
}
|
|
113
|
+
return color;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
switchLineType(newType?: LineRenderType): LineRenderType {
|
|
117
|
+
if (newType === undefined) {
|
|
118
|
+
if (this._lineType === LineType.BEZIER) {
|
|
119
|
+
newType = LineType.LINE_CHART;
|
|
120
|
+
} else {
|
|
121
|
+
newType = LineType.BEZIER;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (newType !== this._lineType) {
|
|
125
|
+
this._lineType = newType;
|
|
126
|
+
// 更新线条数据
|
|
127
|
+
this.getAllLines().forEach((line) => {
|
|
128
|
+
line.getData(WorkflowLineRenderData).update();
|
|
129
|
+
});
|
|
130
|
+
window.requestAnimationFrame(() => {
|
|
131
|
+
// 触发线条重渲染
|
|
132
|
+
this.entityManager.fireEntityChanged(WorkflowLineEntity.type);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return this._lineType;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getAllLines(): WorkflowLineEntity[] {
|
|
139
|
+
return this.entityManager.getEntities(WorkflowLineEntity);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getLinesByPortId(portId: string): WorkflowLineEntity[] {
|
|
143
|
+
return Array.from(this.portLineMap.get(portId) || []);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
rebindLinePorts(
|
|
147
|
+
line: WorkflowLineEntity,
|
|
148
|
+
prevInfo?: WorkflowLineInfo,
|
|
149
|
+
nextInfo?: WorkflowLineInfo
|
|
150
|
+
): void {
|
|
151
|
+
const prevFromPortId = this.getLinePortId(prevInfo, 'output');
|
|
152
|
+
const prevToPortId = this.getLinePortId(prevInfo, 'input');
|
|
153
|
+
const nextFromPortId = this.getLinePortId(nextInfo, 'output');
|
|
154
|
+
const nextToPortId = this.getLinePortId(nextInfo, 'input');
|
|
155
|
+
|
|
156
|
+
if (prevFromPortId !== nextFromPortId) {
|
|
157
|
+
this.detachLineFromPortId(line, prevFromPortId);
|
|
158
|
+
this.attachLineToPortId(line, nextFromPortId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (prevToPortId !== nextToPortId) {
|
|
162
|
+
this.detachLineFromPortId(line, prevToPortId);
|
|
163
|
+
this.attachLineToPortId(line, nextToPortId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private attachLineToPortId(line: WorkflowLineEntity, portId?: string): void {
|
|
168
|
+
if (!portId) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
let lines = this.portLineMap.get(portId);
|
|
172
|
+
if (!lines) {
|
|
173
|
+
lines = new Set();
|
|
174
|
+
this.portLineMap.set(portId, lines);
|
|
175
|
+
}
|
|
176
|
+
lines.add(line);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private detachLineFromPortId(line: WorkflowLineEntity, portId?: string): void {
|
|
180
|
+
if (!portId) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const lines = this.portLineMap.get(portId);
|
|
184
|
+
if (!lines) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
lines.delete(line);
|
|
188
|
+
if (!lines.size) {
|
|
189
|
+
this.portLineMap.delete(portId);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private getLinePortId(
|
|
194
|
+
info: WorkflowLineInfo | undefined,
|
|
195
|
+
portType: WorkflowPortType
|
|
196
|
+
): string | undefined {
|
|
197
|
+
if (!info) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const nodeId = portType === 'output' ? info.from : info.to;
|
|
201
|
+
if (!nodeId) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
const portId = portType === 'output' ? info.fromPort : info.toPort;
|
|
205
|
+
return getPortEntityIdByNodeId(nodeId, portType, portId);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
getAllAvailableLines(): WorkflowLineEntity[] {
|
|
209
|
+
return this.getAllLines().filter((l) => !l.isDrawing && !l.isHidden);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
hasLine(portInfo: Omit<WorkflowLinePortInfo, 'data'>): boolean {
|
|
213
|
+
return !!this.entityManager.getEntityById<WorkflowLineEntity>(
|
|
214
|
+
WorkflowLineEntity.portInfoToLineId(portInfo)
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getLine(portInfo: Omit<WorkflowLinePortInfo, 'data'>): WorkflowLineEntity | undefined {
|
|
219
|
+
return this.entityManager.getEntityById<WorkflowLineEntity>(
|
|
220
|
+
WorkflowLineEntity.portInfoToLineId(portInfo)
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
getLineById(id: string): WorkflowLineEntity | undefined {
|
|
225
|
+
return this.entityManager.getEntityById<WorkflowLineEntity>(id);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
replaceLine(
|
|
229
|
+
oldPortInfo: Omit<WorkflowLinePortInfo, 'data'>,
|
|
230
|
+
newPortInfo: Omit<WorkflowLinePortInfo, 'data'>
|
|
231
|
+
): WorkflowLineEntity {
|
|
232
|
+
const oldLine = this.getLine(oldPortInfo);
|
|
233
|
+
if (oldLine) {
|
|
234
|
+
oldLine.dispose();
|
|
235
|
+
}
|
|
236
|
+
return this.createLine(newPortInfo)!;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
createLine(
|
|
240
|
+
options: {
|
|
241
|
+
drawingTo?: LinePoint; // 无连接的线条
|
|
242
|
+
drawingFrom?: LinePoint;
|
|
243
|
+
key?: string; // 自定义 key
|
|
244
|
+
} & WorkflowLinePortInfo
|
|
245
|
+
): WorkflowLineEntity | undefined {
|
|
246
|
+
const { from, to, drawingTo, fromPort, drawingFrom, toPort, data } = options;
|
|
247
|
+
const available = Boolean(from && to);
|
|
248
|
+
const key = options.key || WorkflowLineEntity.portInfoToLineId(options);
|
|
249
|
+
let line = this.entityManager.getEntityById<WorkflowLineEntity>(key)!;
|
|
250
|
+
if (line) {
|
|
251
|
+
// 如果之前有线条,则先把颜色去掉
|
|
252
|
+
line.highlightColor = '';
|
|
253
|
+
line.validate();
|
|
254
|
+
return line;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const fromNode = from
|
|
258
|
+
? this.entityManager
|
|
259
|
+
.getEntityById<WorkflowNodeEntity>(from)!
|
|
260
|
+
.getData<WorkflowNodeLinesData>(WorkflowNodeLinesData)
|
|
261
|
+
: undefined;
|
|
262
|
+
const toNode = to
|
|
263
|
+
? this.entityManager
|
|
264
|
+
.getEntityById<WorkflowNodeEntity>(to)!
|
|
265
|
+
.getData<WorkflowNodeLinesData>(WorkflowNodeLinesData)!
|
|
266
|
+
: undefined;
|
|
267
|
+
|
|
268
|
+
if (!fromNode && !toNode) {
|
|
269
|
+
// 非法情况
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this.isDrawing = Boolean(drawingTo || drawingFrom);
|
|
274
|
+
line = this.entityManager.createEntity<WorkflowLineEntity>(WorkflowLineEntity, {
|
|
275
|
+
id: key,
|
|
276
|
+
document: this.document,
|
|
277
|
+
linesManager: this,
|
|
278
|
+
from,
|
|
279
|
+
fromPort,
|
|
280
|
+
toPort,
|
|
281
|
+
to,
|
|
282
|
+
drawingTo,
|
|
283
|
+
drawingFrom,
|
|
284
|
+
data,
|
|
285
|
+
uiState: this.defaultUIState,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
this.registerData(line);
|
|
289
|
+
|
|
290
|
+
fromNode?.addLine(line);
|
|
291
|
+
toNode?.addLine(line);
|
|
292
|
+
line.onDispose(() => {
|
|
293
|
+
this.isDrawing = false;
|
|
294
|
+
fromNode?.removeLine(line);
|
|
295
|
+
toNode?.removeLine(line);
|
|
296
|
+
});
|
|
297
|
+
line.onDispose(() => {
|
|
298
|
+
if (available) {
|
|
299
|
+
this.onAvailableLinesChangeEmitter.fire({
|
|
300
|
+
type: WorkflowContentChangeType.DELETE_LINE,
|
|
301
|
+
toJSON: () => line.toJSON(),
|
|
302
|
+
entity: line,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
line.onLineDataChange(({ oldValue }) => {
|
|
307
|
+
this.onAvailableLinesChangeEmitter.fire({
|
|
308
|
+
type: WorkflowContentChangeType.LINE_DATA_CHANGE,
|
|
309
|
+
toJSON: () => line.toJSON(),
|
|
310
|
+
oldValue,
|
|
311
|
+
entity: line,
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
// 是否为有效的线条
|
|
315
|
+
if (available) {
|
|
316
|
+
this.onAvailableLinesChangeEmitter.fire({
|
|
317
|
+
type: WorkflowContentChangeType.ADD_LINE,
|
|
318
|
+
toJSON: () => line.toJSON(),
|
|
319
|
+
entity: line,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
// 创建时检验 连线错误态 & 端口错误态
|
|
323
|
+
line.validate();
|
|
324
|
+
return line;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 获取线条中距离鼠标位置最近的线条和距离
|
|
329
|
+
* @param mousePos 鼠标位置
|
|
330
|
+
* @param minDistance 最小检测距离
|
|
331
|
+
* @returns 距离鼠标位置最近的线条 以及距离
|
|
332
|
+
*/
|
|
333
|
+
getCloseInLineFromMousePos(
|
|
334
|
+
mousePos: IPoint,
|
|
335
|
+
minDistance: number = LINE_HOVER_DISTANCE
|
|
336
|
+
): WorkflowLineEntity | undefined {
|
|
337
|
+
let targetLine: WorkflowLineEntity | undefined, targetLineDist: number | undefined;
|
|
338
|
+
this.getAllLines().forEach((line) => {
|
|
339
|
+
const dist = line.getHoverDist(mousePos);
|
|
340
|
+
|
|
341
|
+
if (dist <= minDistance && (!targetLineDist || targetLineDist >= dist)) {
|
|
342
|
+
targetLineDist = dist;
|
|
343
|
+
targetLine = line;
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
return targetLine;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 是否在调整线条
|
|
351
|
+
*/
|
|
352
|
+
isDrawing = false;
|
|
353
|
+
|
|
354
|
+
dispose(): void {
|
|
355
|
+
this.portLineMap.clear();
|
|
356
|
+
this.toDispose.dispose();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
get disposed(): boolean {
|
|
360
|
+
return this.toDispose.disposed;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
isErrorLine(fromPort?: WorkflowPortEntity, toPort?: WorkflowPortEntity, defaultValue?: boolean) {
|
|
364
|
+
if (this.options.isErrorLine) {
|
|
365
|
+
return this.options.isErrorLine(fromPort, toPort, this);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return !!defaultValue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
isReverseLine(line: WorkflowLineEntity, defaultValue = false): boolean {
|
|
372
|
+
if (this.options.isReverseLine) {
|
|
373
|
+
return this.options.isReverseLine(line);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return defaultValue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
isHideArrowLine(line: WorkflowLineEntity, defaultValue = false): boolean {
|
|
380
|
+
if (this.options.isHideArrowLine) {
|
|
381
|
+
return this.options.isHideArrowLine(line);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return defaultValue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
isFlowingLine(line: WorkflowLineEntity, defaultValue = false): boolean {
|
|
388
|
+
if (this.options.isFlowingLine) {
|
|
389
|
+
return this.options.isFlowingLine(line);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return defaultValue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
isDisabledLine(line: WorkflowLineEntity, defaultValue = false): boolean {
|
|
396
|
+
if (this.options.isDisabledLine) {
|
|
397
|
+
return this.options.isDisabledLine(line);
|
|
398
|
+
}
|
|
399
|
+
return defaultValue;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
setLineRenderType(line: WorkflowLineEntity): LineRenderType | undefined {
|
|
403
|
+
if (this.options.setLineRenderType) {
|
|
404
|
+
return this.options.setLineRenderType(line);
|
|
405
|
+
}
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
setLineClassName(line: WorkflowLineEntity): string | undefined {
|
|
410
|
+
if (this.options.setLineClassName) {
|
|
411
|
+
return this.options.setLineClassName(line);
|
|
412
|
+
}
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
getLineColor(line: WorkflowLineEntity): string | undefined {
|
|
417
|
+
// 隐藏的优先级比 hasError 高
|
|
418
|
+
if (line.isHidden) {
|
|
419
|
+
return this.lineColor.hidden;
|
|
420
|
+
}
|
|
421
|
+
// 颜色锁定
|
|
422
|
+
if (line.lockedColor) {
|
|
423
|
+
return line.lockedColor;
|
|
424
|
+
}
|
|
425
|
+
if (line.hasError) {
|
|
426
|
+
return this.lineColor.error;
|
|
427
|
+
}
|
|
428
|
+
if (line.highlightColor) {
|
|
429
|
+
return line.highlightColor;
|
|
430
|
+
}
|
|
431
|
+
if (line.drawingTo) {
|
|
432
|
+
return this.lineColor.drawing;
|
|
433
|
+
}
|
|
434
|
+
if (this.hoverService.isHovered(line.id)) {
|
|
435
|
+
return this.lineColor.hovered;
|
|
436
|
+
}
|
|
437
|
+
if (this.selectService.isSelected(line.id)) {
|
|
438
|
+
return this.lineColor.selected;
|
|
439
|
+
}
|
|
440
|
+
// 检查是否为流动线条
|
|
441
|
+
if (this.isFlowingLine(line)) {
|
|
442
|
+
return this.lineColor.flowing;
|
|
443
|
+
}
|
|
444
|
+
return this.lineColor.default;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
canAddLine(fromPort: WorkflowPortEntity, toPort: WorkflowPortEntity, silent?: boolean): boolean {
|
|
448
|
+
if (
|
|
449
|
+
fromPort === toPort ||
|
|
450
|
+
fromPort.node === toPort.node ||
|
|
451
|
+
fromPort.portType !== 'output' ||
|
|
452
|
+
toPort.portType !== 'input' ||
|
|
453
|
+
fromPort.disabled ||
|
|
454
|
+
toPort.disabled
|
|
455
|
+
) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
const fromCanAdd = fromPort.node.getNodeRegistry<WorkflowNodeRegistry>().canAddLine;
|
|
459
|
+
const toCanAdd = toPort.node.getNodeRegistry<WorkflowNodeRegistry>().canAddLine;
|
|
460
|
+
if (fromCanAdd && !fromCanAdd(fromPort, toPort, this, silent)) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
if (toCanAdd && !toCanAdd(fromPort, toPort, this, silent)) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
if (this.options.canAddLine) {
|
|
467
|
+
return this.options.canAddLine(fromPort, toPort, this, silent);
|
|
468
|
+
}
|
|
469
|
+
// 默认不能连接自己
|
|
470
|
+
return fromPort.node !== toPort.node;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
toJSON(): WorkflowEdgeJSON[] {
|
|
474
|
+
return this.getAllLines()
|
|
475
|
+
.filter((l) => !l.isDrawing)
|
|
476
|
+
.map((l) => l.toJSON());
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
getPortById(portId: string): WorkflowPortEntity | undefined {
|
|
480
|
+
return this.entityManager.getEntityById<WorkflowPortEntity>(portId);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
canRemove(
|
|
484
|
+
line: WorkflowLineEntity,
|
|
485
|
+
newLineInfo?: Required<Omit<WorkflowLinePortInfo, 'data'>>,
|
|
486
|
+
silent?: boolean
|
|
487
|
+
): boolean {
|
|
488
|
+
if (
|
|
489
|
+
this.options &&
|
|
490
|
+
this.options.canDeleteLine &&
|
|
491
|
+
!this.options.canDeleteLine(line, newLineInfo, silent)
|
|
492
|
+
) {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
canReset(oldLine: WorkflowLineEntity, newLineInfo: Required<WorkflowLinePortInfo>): boolean {
|
|
499
|
+
if (
|
|
500
|
+
this.options &&
|
|
501
|
+
this.options.canResetLine &&
|
|
502
|
+
!this.options.canResetLine(oldLine, newLineInfo, this)
|
|
503
|
+
) {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* 根据鼠标位置找到 port
|
|
511
|
+
* @param pos
|
|
512
|
+
*/
|
|
513
|
+
getPortFromMousePos(pos: IPoint, portType?: WorkflowPortType): WorkflowPortEntity | undefined {
|
|
514
|
+
const allNodes = this.getSortedNodes().reverse();
|
|
515
|
+
const allPorts = allNodes
|
|
516
|
+
.map((node) => {
|
|
517
|
+
if (!portType) {
|
|
518
|
+
return node.ports.allPorts;
|
|
519
|
+
}
|
|
520
|
+
return portType === 'input' ? node.ports.inputPorts : node.ports.outputPorts;
|
|
521
|
+
})
|
|
522
|
+
.flat();
|
|
523
|
+
const targetPort = allPorts.find((port) => port.isHovered(pos.x, pos.y));
|
|
524
|
+
if (targetPort) {
|
|
525
|
+
const containNodes = this.getContainNodesFromMousePos(pos);
|
|
526
|
+
const targetNode = last(containNodes);
|
|
527
|
+
// 点位可能会被节点覆盖
|
|
528
|
+
if (targetNode && targetNode !== targetPort.node) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return targetPort;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* 根据鼠标位置找到 node
|
|
537
|
+
* @param pos - 鼠标位置
|
|
538
|
+
*/
|
|
539
|
+
getNodeFromMousePos(pos: IPoint): WorkflowNodeEntity | undefined {
|
|
540
|
+
// 先挑选出 bounds 区域符合的 node
|
|
541
|
+
const { selection } = this.selectService;
|
|
542
|
+
const containNodes = this.getContainNodesFromMousePos(pos);
|
|
543
|
+
// 当有元素被选中的时候选中元素在顶层
|
|
544
|
+
if (selection?.length) {
|
|
545
|
+
const filteredNodes = containNodes.filter((node) =>
|
|
546
|
+
selection.some((_node) => node.id === _node.id)
|
|
547
|
+
);
|
|
548
|
+
if (filteredNodes?.length) {
|
|
549
|
+
return last(filteredNodes);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// 默认取最顶层的
|
|
553
|
+
return last(containNodes);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
registerContribution(factory: WorkflowLineRenderContributionFactory): this {
|
|
557
|
+
this.contributionFactories.push(factory);
|
|
558
|
+
return this;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private registerData(line: WorkflowLineEntity) {
|
|
562
|
+
line.addData(WorkflowLineRenderData);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private getSortedNodes() {
|
|
566
|
+
return this.document.getAllNodes().sort((a, b) => this.getNodeIndex(a) - this.getNodeIndex(b));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** 获取鼠标坐标位置的所有节点(stackIndex 从小到大排序) */
|
|
570
|
+
private getContainNodesFromMousePos(pos: IPoint): WorkflowNodeEntity[] {
|
|
571
|
+
const allNodes = this.getSortedNodes();
|
|
572
|
+
const zoom =
|
|
573
|
+
this.entityManager.getEntity<PlaygroundConfigEntity>(PlaygroundConfigEntity)?.config?.zoom ||
|
|
574
|
+
1;
|
|
575
|
+
const containNodes = allNodes
|
|
576
|
+
.map((node) => {
|
|
577
|
+
const { bounds } = node.getData<FlowNodeTransformData>(FlowNodeTransformData);
|
|
578
|
+
// 交互要求,节点边缘 4px 的时候就认为选中节点
|
|
579
|
+
if (
|
|
580
|
+
bounds
|
|
581
|
+
.clone()
|
|
582
|
+
.pad(4 / zoom)
|
|
583
|
+
.contains(pos.x, pos.y)
|
|
584
|
+
) {
|
|
585
|
+
return node;
|
|
586
|
+
}
|
|
587
|
+
})
|
|
588
|
+
.filter(Boolean) as WorkflowNodeEntity[];
|
|
589
|
+
return containNodes;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
private getNodeIndex(node: WorkflowNodeEntity): number {
|
|
593
|
+
const nodeRenderData = node.getData(FlowNodeRenderData);
|
|
594
|
+
return nodeRenderData.stackIndex;
|
|
595
|
+
}
|
|
596
|
+
}
|