@flowgram.ai/free-layout-core 0.1.6 → 0.1.8

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/dist/esm/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  LineColors,
10
10
  LineType
11
- } from "./chunk-KAL7YCC2.js";
11
+ } from "./chunk-PT4ZVDZZ.js";
12
12
  import {
13
13
  __decorateClass
14
14
  } from "./chunk-EUXUH3YW.js";
@@ -57,321 +57,12 @@ import {
57
57
  SelectionService,
58
58
  TransformData as TransformData4
59
59
  } from "@flowgram.ai/core";
60
- import { Rectangle as Rectangle7, SizeSchema } from "@flowgram.ai/utils";
60
+ import { Rectangle as Rectangle6, SizeSchema } from "@flowgram.ai/utils";
61
61
 
62
62
  // src/utils/index.ts
63
63
  import { bindConfigEntity } from "@flowgram.ai/core";
64
64
  import { delay } from "@flowgram.ai/utils";
65
65
 
66
- // src/utils/fold-line.ts
67
- import { Point, Rectangle } from "@flowgram.ai/utils";
68
- function pointLineDistance(p1, p2, p3) {
69
- let len;
70
- if (p1.x - p2.x === 0) {
71
- len = Math.abs(p3.x - p1.x);
72
- } else {
73
- const A = (p1.y - p2.y) / (p1.x - p2.x);
74
- const B = p1.y - A * p1.x;
75
- len = Math.abs((A * p3.x + B - p3.y) / Math.sqrt(A * A + 1));
76
- }
77
- return len;
78
- }
79
- var FoldLine;
80
- ((FoldLine2) => {
81
- const EDGE_RADIUS = 5;
82
- const OFFSET = 20;
83
- function getEdgeCenter({ source, target }) {
84
- const xOffset = Math.abs(target.x - source.x) / 2;
85
- const centerX = target.x < source.x ? target.x + xOffset : target.x - xOffset;
86
- const yOffset = Math.abs(target.y - source.y) / 2;
87
- const centerY = target.y < source.y ? target.y + yOffset : target.y - yOffset;
88
- return [centerX, centerY];
89
- }
90
- const getDirection = ({ source, target }) => source.x < target.x ? { x: 1, y: 0 } : { x: -1, y: 0 };
91
- function getPoints({ source, target }) {
92
- const sourceDir = { x: 1, y: 0 };
93
- const targetDir = { x: -1, y: 0 };
94
- const sourceGapped = {
95
- x: source.x + sourceDir.x * OFFSET,
96
- y: source.y + sourceDir.y * OFFSET
97
- };
98
- const targetGapped = {
99
- x: target.x + targetDir.x * OFFSET,
100
- y: target.y + targetDir.y * OFFSET
101
- };
102
- const dir = getDirection({
103
- source: sourceGapped,
104
- target: targetGapped
105
- });
106
- const dirAccessor = dir.x !== 0 ? "x" : "y";
107
- const currDir = dir[dirAccessor];
108
- let points = [];
109
- let centerX, centerY;
110
- const [defaultCenterX, defaultCenterY] = getEdgeCenter({
111
- source,
112
- target
113
- });
114
- if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
115
- centerX = defaultCenterX;
116
- centerY = defaultCenterY;
117
- const verticalSplit = [
118
- { x: centerX, y: sourceGapped.y },
119
- { x: centerX, y: targetGapped.y }
120
- ];
121
- const horizontalSplit = [
122
- { x: sourceGapped.x, y: centerY },
123
- { x: targetGapped.x, y: centerY }
124
- ];
125
- if (sourceDir[dirAccessor] === currDir) {
126
- points = dirAccessor === "x" ? verticalSplit : horizontalSplit;
127
- } else {
128
- points = dirAccessor === "x" ? horizontalSplit : verticalSplit;
129
- }
130
- } else {
131
- const sourceTarget = [{ x: sourceGapped.x, y: targetGapped.y }];
132
- const targetSource = [{ x: targetGapped.x, y: sourceGapped.y }];
133
- if (dirAccessor === "x") {
134
- points = sourceDir.x === currDir ? targetSource : sourceTarget;
135
- } else {
136
- points = sourceDir.y === currDir ? sourceTarget : targetSource;
137
- }
138
- const dirAccessorOpposite = dirAccessor === "x" ? "y" : "x";
139
- const isSameDir = sourceDir[dirAccessor] === targetDir[dirAccessorOpposite];
140
- const sourceGtTargetOppo = sourceGapped[dirAccessorOpposite] > targetGapped[dirAccessorOpposite];
141
- const sourceLtTargetOppo = sourceGapped[dirAccessorOpposite] < targetGapped[dirAccessorOpposite];
142
- const flipSourceTarget = sourceDir[dirAccessor] === 1 && (!isSameDir && sourceGtTargetOppo || isSameDir && sourceLtTargetOppo) || sourceDir[dirAccessor] !== 1 && (!isSameDir && sourceLtTargetOppo || isSameDir && sourceGtTargetOppo);
143
- if (flipSourceTarget) {
144
- points = dirAccessor === "x" ? sourceTarget : targetSource;
145
- }
146
- const sourceGapPoint = { x: sourceGapped.x, y: sourceGapped.y };
147
- const targetGapPoint = { x: targetGapped.x, y: targetGapped.y };
148
- const maxXDistance = Math.max(
149
- Math.abs(sourceGapPoint.x - points[0].x),
150
- Math.abs(targetGapPoint.x - points[0].x)
151
- );
152
- const maxYDistance = Math.max(
153
- Math.abs(sourceGapPoint.y - points[0].y),
154
- Math.abs(targetGapPoint.y - points[0].y)
155
- );
156
- if (maxXDistance >= maxYDistance) {
157
- centerX = (sourceGapPoint.x + targetGapPoint.x) / 2;
158
- centerY = points[0].y;
159
- } else {
160
- centerX = points[0].x;
161
- centerY = (sourceGapPoint.y + targetGapPoint.y) / 2;
162
- }
163
- }
164
- const pathPoints = [
165
- source,
166
- { x: sourceGapped.x, y: sourceGapped.y },
167
- ...points,
168
- { x: targetGapped.x, y: targetGapped.y },
169
- target
170
- ];
171
- return pathPoints;
172
- }
173
- FoldLine2.getPoints = getPoints;
174
- function getBend(a, b, c) {
175
- const bendSize = Math.min(
176
- Point.getDistance(a, b) / 2,
177
- Point.getDistance(b, c) / 2,
178
- EDGE_RADIUS
179
- );
180
- const { x, y } = b;
181
- if (a.x === x && x === c.x || a.y === y && y === c.y) {
182
- return `L${x} ${y}`;
183
- }
184
- if (a.y === y) {
185
- const xDir2 = a.x < c.x ? -1 : 1;
186
- const yDir2 = a.y < c.y ? 1 : -1;
187
- return `L ${x + bendSize * xDir2},${y}Q ${x},${y} ${x},${y + bendSize * yDir2}`;
188
- }
189
- const xDir = a.x < c.x ? 1 : -1;
190
- const yDir = a.y < c.y ? -1 : 1;
191
- return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}`;
192
- }
193
- function getSmoothStepPath(points) {
194
- const path = points.reduce((res, p, i) => {
195
- let segment = "";
196
- if (i > 0 && i < points.length - 1) {
197
- segment = getBend(points[i - 1], p, points[i + 1]);
198
- } else {
199
- segment = `${i === 0 ? "M" : "L"}${p.x} ${p.y}`;
200
- }
201
- res += segment;
202
- return res;
203
- }, "");
204
- return path;
205
- }
206
- FoldLine2.getSmoothStepPath = getSmoothStepPath;
207
- function getBounds(points) {
208
- const xList = points.map((p) => p.x);
209
- const yList = points.map((p) => p.y);
210
- const left = Math.min(...xList);
211
- const right = Math.max(...xList);
212
- const top = Math.min(...yList);
213
- const bottom = Math.max(...yList);
214
- return Rectangle.createRectangleWithTwoPoints(
215
- {
216
- x: left,
217
- y: top
218
- },
219
- {
220
- x: right,
221
- y: bottom
222
- }
223
- );
224
- }
225
- FoldLine2.getBounds = getBounds;
226
- function getFoldLineToPointDistance(points, pos) {
227
- const bounds = getBounds(points);
228
- if (bounds.contains(pos.x, pos.y)) {
229
- const lines = points.reduce((res, point, index) => {
230
- if (index === 0) {
231
- return res;
232
- }
233
- res.push([points[index - 1], point]);
234
- return res;
235
- }, []);
236
- return Math.min(...lines.map((l) => pointLineDistance(...l, pos)));
237
- }
238
- return Math.min(...points.map((p) => Point.getDistance(p, pos)));
239
- }
240
- FoldLine2.getFoldLineToPointDistance = getFoldLineToPointDistance;
241
- })(FoldLine || (FoldLine = {}));
242
-
243
- // src/utils/bezier.ts
244
- import { Rectangle as Rectangle2 } from "@flowgram.ai/utils";
245
- var BezierControlType = /* @__PURE__ */ ((BezierControlType2) => {
246
- BezierControlType2[BezierControlType2["RIGHT_TOP"] = 0] = "RIGHT_TOP";
247
- BezierControlType2[BezierControlType2["RIGHT_BOTTOM"] = 1] = "RIGHT_BOTTOM";
248
- BezierControlType2[BezierControlType2["LEFT_TOP"] = 2] = "LEFT_TOP";
249
- BezierControlType2[BezierControlType2["LEFT_BOTTOM"] = 3] = "LEFT_BOTTOM";
250
- return BezierControlType2;
251
- })(BezierControlType || {});
252
- var CONTROL_MAX = 300;
253
- function getBezierHorizontalControlPoints(fromPos, toPos) {
254
- const rect = Rectangle2.createRectangleWithTwoPoints(fromPos, toPos);
255
- let type;
256
- if (fromPos.x <= toPos.x) {
257
- type = fromPos.y <= toPos.y ? 1 /* RIGHT_BOTTOM */ : 0 /* RIGHT_TOP */;
258
- } else {
259
- type = fromPos.y <= toPos.y ? 3 /* LEFT_BOTTOM */ : 2 /* LEFT_TOP */;
260
- }
261
- let controls;
262
- switch (type) {
263
- case 0 /* RIGHT_TOP */:
264
- controls = [
265
- {
266
- x: rect.rightBottom.x - rect.width / 2,
267
- y: rect.rightBottom.y
268
- },
269
- {
270
- x: rect.leftTop.x + rect.width / 2,
271
- y: rect.leftTop.y
272
- }
273
- ];
274
- break;
275
- case 1 /* RIGHT_BOTTOM */:
276
- controls = [
277
- {
278
- x: rect.rightTop.x - rect.width / 2,
279
- y: rect.rightTop.y
280
- },
281
- {
282
- x: rect.leftBottom.x + rect.width / 2,
283
- y: rect.leftBottom.y
284
- }
285
- ];
286
- break;
287
- case 2 /* LEFT_TOP */:
288
- controls = [
289
- {
290
- x: rect.rightBottom.x + Math.min(rect.width, CONTROL_MAX),
291
- y: rect.rightBottom.y
292
- },
293
- {
294
- x: rect.leftTop.x - Math.min(rect.width, CONTROL_MAX),
295
- y: rect.leftTop.y
296
- }
297
- ];
298
- break;
299
- case 3 /* LEFT_BOTTOM */:
300
- controls = [
301
- {
302
- x: rect.rightTop.x + Math.min(rect.width, CONTROL_MAX),
303
- y: rect.rightTop.y
304
- },
305
- {
306
- x: rect.leftBottom.x - Math.min(rect.width, CONTROL_MAX),
307
- y: rect.leftBottom.y
308
- }
309
- ];
310
- }
311
- return controls;
312
- }
313
- function getBezierVerticalControlPoints(fromPos, toPos) {
314
- const rect = Rectangle2.createRectangleWithTwoPoints(fromPos, toPos);
315
- let type;
316
- if (fromPos.y <= toPos.y) {
317
- type = fromPos.x <= toPos.x ? 1 /* RIGHT_BOTTOM */ : 3 /* LEFT_BOTTOM */;
318
- } else {
319
- type = fromPos.x <= toPos.x ? 0 /* RIGHT_TOP */ : 2 /* LEFT_TOP */;
320
- }
321
- let controls;
322
- switch (type) {
323
- case 1 /* RIGHT_BOTTOM */:
324
- controls = [
325
- {
326
- x: rect.leftTop.x,
327
- y: rect.leftTop.y + rect.height / 2
328
- },
329
- {
330
- x: rect.rightBottom.x,
331
- y: rect.rightBottom.y - rect.height / 2
332
- }
333
- ];
334
- break;
335
- case 3 /* LEFT_BOTTOM */:
336
- controls = [
337
- {
338
- x: rect.rightTop.x,
339
- y: rect.rightTop.y + rect.height / 2
340
- },
341
- {
342
- x: rect.leftBottom.x,
343
- y: rect.leftBottom.y - rect.height / 2
344
- }
345
- ];
346
- break;
347
- case 0 /* RIGHT_TOP */:
348
- controls = [
349
- {
350
- x: rect.leftBottom.x,
351
- y: rect.leftBottom.y + Math.min(rect.height, CONTROL_MAX)
352
- },
353
- {
354
- x: rect.rightTop.x,
355
- y: rect.rightTop.y - Math.min(rect.height, CONTROL_MAX)
356
- }
357
- ];
358
- break;
359
- case 2 /* LEFT_TOP */:
360
- controls = [
361
- {
362
- x: rect.rightBottom.x,
363
- y: rect.rightBottom.y + Math.min(rect.height, CONTROL_MAX)
364
- },
365
- {
366
- x: rect.leftTop.x,
367
- y: rect.leftTop.y - Math.min(rect.height, CONTROL_MAX)
368
- }
369
- ];
370
- break;
371
- }
372
- return controls;
373
- }
374
-
375
66
  // src/utils/nanoid.ts
376
67
  import { nanoid as nanoidOrigin } from "nanoid";
377
68
  function nanoid(n) {
@@ -383,9 +74,9 @@ import { compose, composeAsync } from "@flowgram.ai/utils";
383
74
 
384
75
  // src/utils/fit-view.ts
385
76
  import { TransformData } from "@flowgram.ai/core";
386
- import { Rectangle as Rectangle3 } from "@flowgram.ai/utils";
77
+ import { Rectangle } from "@flowgram.ai/utils";
387
78
  var fitView = (doc, playgroundConfig, easing = true) => {
388
- const bounds = Rectangle3.enlarge(
79
+ const bounds = Rectangle.enlarge(
389
80
  doc.getAllNodes().map((node) => node.getData(TransformData).bounds)
390
81
  );
391
82
  return playgroundConfig.fitView(bounds, easing, 30);
@@ -417,11 +108,11 @@ function getAntiOverlapPosition(doc, position, containerNode) {
417
108
  }
418
109
 
419
110
  // src/utils/statics.ts
420
- import { Rectangle as Rectangle4 } from "@flowgram.ai/utils";
111
+ import { Rectangle as Rectangle2 } from "@flowgram.ai/utils";
421
112
  var getPortEntityId = (node, portType, portID = "") => `port_${portType}_${node.id}_${portID}`;
422
113
  var WORKFLOW_LINE_ENTITY = "WorkflowLineEntity";
423
114
  function domReactToBounds(react) {
424
- return new Rectangle4(react.x, react.y, react.width, react.height);
115
+ return new Rectangle2(react.x, react.y, react.width, react.height);
425
116
  }
426
117
 
427
118
  // src/entities/workflow-node-entity.ts
@@ -430,8 +121,7 @@ var WorkflowNodeEntity = FlowNodeEntity;
430
121
 
431
122
  // src/entities/workflow-line-entity.ts
432
123
  import { isEqual as isEqual2 } from "lodash-es";
433
- import { Bezier } from "bezier-js";
434
- import { domUtils, Point as Point2, Rectangle as Rectangle6 } from "@flowgram.ai/utils";
124
+ import { domUtils } from "@flowgram.ai/utils";
435
125
  import { Entity as Entity2 } from "@flowgram.ai/core";
436
126
 
437
127
  // src/entity-datas/workflow-node-ports-data.ts
@@ -440,7 +130,7 @@ import { FlowNodeRenderData } from "@flowgram.ai/document";
440
130
  import { EntityData, SizeData } from "@flowgram.ai/core";
441
131
 
442
132
  // src/entities/workflow-port-entity.ts
443
- import { Rectangle as Rectangle5, Emitter } from "@flowgram.ai/utils";
133
+ import { Rectangle as Rectangle3, Emitter } from "@flowgram.ai/utils";
444
134
  import {
445
135
  Entity,
446
136
  PlaygroundConfigEntity,
@@ -511,7 +201,7 @@ var WorkflowPortEntity = class extends Entity {
511
201
  get bounds() {
512
202
  const { point } = this;
513
203
  const halfSize = PORT_SIZE / 2;
514
- return new Rectangle5(point.x - halfSize, point.y - halfSize, PORT_SIZE, PORT_SIZE);
204
+ return new Rectangle3(point.x - halfSize, point.y - halfSize, PORT_SIZE, PORT_SIZE);
515
205
  }
516
206
  isHovered(x, y) {
517
207
  return this.bounds.contains(x, y);
@@ -767,6 +457,218 @@ var WorkflowNodePortsData = class extends EntityData {
767
457
  };
768
458
  WorkflowNodePortsData.type = "WorkflowNodePortsData";
769
459
 
460
+ // src/entity-datas/workflow-node-lines-data.ts
461
+ import { Disposable } from "@flowgram.ai/utils";
462
+ import { EntityData as EntityData2 } from "@flowgram.ai/core";
463
+ var _WorkflowNodeLinesData = class _WorkflowNodeLinesData extends EntityData2 {
464
+ getDefaultData() {
465
+ return {
466
+ inputLines: [],
467
+ outputLines: []
468
+ };
469
+ }
470
+ constructor(entity) {
471
+ super(entity);
472
+ this.entity = entity;
473
+ this.toDispose.push(
474
+ Disposable.create(() => {
475
+ this.inputLines.slice().forEach((line) => line.dispose());
476
+ this.outputLines.slice().forEach((line) => line.dispose());
477
+ })
478
+ );
479
+ }
480
+ /**
481
+ * 输入线条
482
+ */
483
+ get inputLines() {
484
+ return this.data.inputLines;
485
+ }
486
+ /**
487
+ * 输出线条
488
+ */
489
+ get outputLines() {
490
+ return this.data.outputLines;
491
+ }
492
+ /**
493
+ * 输入节点
494
+ */
495
+ get inputNodes() {
496
+ return this.inputLines.map((l) => l.from).filter(Boolean);
497
+ }
498
+ /**
499
+ * 所有输入节点
500
+ */
501
+ get allInputNodes() {
502
+ const nodeSet = /* @__PURE__ */ new Set();
503
+ const handleNode = (node) => {
504
+ if (nodeSet.has(node)) {
505
+ return;
506
+ }
507
+ nodeSet.add(node);
508
+ const { inputNodes } = node.getData(_WorkflowNodeLinesData);
509
+ if (!inputNodes || !inputNodes.length) {
510
+ return;
511
+ }
512
+ inputNodes.forEach((inputNode) => {
513
+ if (inputNode?.parent === node || node?.parent === inputNode) {
514
+ return;
515
+ }
516
+ handleNode(inputNode);
517
+ });
518
+ };
519
+ handleNode(this.entity);
520
+ nodeSet.delete(this.entity);
521
+ return Array.from(nodeSet);
522
+ }
523
+ /**
524
+ * 输出节点
525
+ */
526
+ get outputNodes() {
527
+ return this.outputLines.map((l) => l.to).filter(Boolean);
528
+ }
529
+ /**
530
+ * 输入输出节点
531
+ */
532
+ get allOutputNodes() {
533
+ const nodeSet = /* @__PURE__ */ new Set();
534
+ const handleNode = (node) => {
535
+ if (nodeSet.has(node)) {
536
+ return;
537
+ }
538
+ nodeSet.add(node);
539
+ const { outputNodes } = node.getData(_WorkflowNodeLinesData);
540
+ if (!outputNodes || !outputNodes.length) {
541
+ return;
542
+ }
543
+ outputNodes.forEach((outputNode) => {
544
+ if (outputNode?.parent === node || node?.parent === outputNode) {
545
+ return;
546
+ }
547
+ handleNode(outputNode);
548
+ });
549
+ };
550
+ handleNode(this.entity);
551
+ nodeSet.delete(this.entity);
552
+ return Array.from(nodeSet);
553
+ }
554
+ addLine(line) {
555
+ if (line.from === this.entity) {
556
+ this.outputLines.push(line);
557
+ } else {
558
+ this.inputLines.push(line);
559
+ }
560
+ this.fireChange();
561
+ }
562
+ removeLine(line) {
563
+ const { inputLines, outputLines } = this;
564
+ const inputIndex = inputLines.indexOf(line);
565
+ const outputIndex = outputLines.indexOf(line);
566
+ if (inputIndex !== -1) {
567
+ inputLines.splice(inputIndex, 1);
568
+ this.fireChange();
569
+ }
570
+ if (outputIndex !== -1) {
571
+ outputLines.splice(outputIndex, 1);
572
+ this.fireChange();
573
+ }
574
+ }
575
+ };
576
+ _WorkflowNodeLinesData.type = "WorkflowNodeLinesData";
577
+ var WorkflowNodeLinesData = _WorkflowNodeLinesData;
578
+
579
+ // src/entity-datas/workflow-line-render-data.ts
580
+ import { Rectangle as Rectangle4 } from "@flowgram.ai/utils";
581
+ import { EntityData as EntityData3 } from "@flowgram.ai/core";
582
+ var WorkflowLineRenderData = class extends EntityData3 {
583
+ constructor(entity) {
584
+ super(entity);
585
+ this.syncContributions();
586
+ }
587
+ getDefaultData() {
588
+ return {
589
+ version: "",
590
+ contributions: /* @__PURE__ */ new Map(),
591
+ position: {
592
+ from: { x: 0, y: 0 },
593
+ to: { x: 0, y: 0 }
594
+ }
595
+ };
596
+ }
597
+ get renderVersion() {
598
+ return this.data.version;
599
+ }
600
+ get position() {
601
+ return this.data.position;
602
+ }
603
+ get path() {
604
+ return this.currentLine?.path ?? "";
605
+ }
606
+ calcDistance(pos) {
607
+ return this.currentLine?.calcDistance(pos) ?? Number.MAX_SAFE_INTEGER;
608
+ }
609
+ get bounds() {
610
+ return this.currentLine?.bounds ?? new Rectangle4();
611
+ }
612
+ /**
613
+ * 更新数据
614
+ * WARNING: 这个方法,必须在 requestAnimationFrame / useLayoutEffect 中调用,否则会引起浏览器强制重排
615
+ */
616
+ update() {
617
+ this.syncContributions();
618
+ const oldVersion = this.data.version;
619
+ this.updatePosition();
620
+ const newVersion = this.data.version;
621
+ if (oldVersion === newVersion) {
622
+ return;
623
+ }
624
+ this.data.version = newVersion;
625
+ this.currentLine?.update({
626
+ fromPos: this.data.position.from,
627
+ toPos: this.data.position.to
628
+ });
629
+ }
630
+ get lineType() {
631
+ return this.entity.renderType ?? this.entity.linesManager.lineType;
632
+ }
633
+ /**
634
+ * 更新版本
635
+ * WARNING: 这个方法,必须在 requestAnimationFrame / useLayoutEffect 中调用,否则会引起浏览器强制重排
636
+ */
637
+ updatePosition() {
638
+ this.data.position.from = this.entity.from.getData(WorkflowNodePortsData).getOutputPoint(this.entity.info.fromPort);
639
+ this.data.position.to = this.entity.info.drawingTo ?? this.entity.to?.getData(WorkflowNodePortsData)?.getInputPoint(this.entity.info.toPort) ?? {
640
+ x: this.data.position.from.x,
641
+ y: this.data.position.from.y
642
+ };
643
+ this.data.version = [
644
+ this.lineType,
645
+ this.data.position.from.x,
646
+ this.data.position.from.y,
647
+ this.data.position.to.x,
648
+ this.data.position.to.y
649
+ ].join("-");
650
+ }
651
+ get currentLine() {
652
+ return this.data.contributions.get(this.lineType);
653
+ }
654
+ syncContributions() {
655
+ if (this.entity.linesManager.contributionFactories.length === this.data.contributions.size) {
656
+ return;
657
+ }
658
+ this.entity.linesManager.contributionFactories.forEach((factory) => {
659
+ this.registerContribution(factory);
660
+ });
661
+ }
662
+ registerContribution(contributionFactory) {
663
+ if (this.data.contributions.has(contributionFactory.type)) {
664
+ return;
665
+ }
666
+ const contribution = new contributionFactory(this.entity);
667
+ this.data.contributions.set(contributionFactory.type, contribution);
668
+ }
669
+ };
670
+ WorkflowLineRenderData.type = "WorkflowLineRenderData";
671
+
770
672
  // src/entities/workflow-line-entity.ts
771
673
  var LINE_HOVER_DISTANCE = 8;
772
674
  var POINT_RADIUS = 10;
@@ -781,10 +683,6 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
781
683
  this.info = {
782
684
  from: ""
783
685
  };
784
- /**
785
- * 贝塞尔线条版本
786
- */
787
- this._bezierVersion = "";
788
686
  this.document = opts.document;
789
687
  this.linesManager = opts.linesManager;
790
688
  this.initInfo({
@@ -910,59 +808,17 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
910
808
  this.fireChange();
911
809
  }
912
810
  }
913
- /**
914
- * 创建贝塞尔线条
915
- * @param fromPos 前置节点位置
916
- * @param toPos 后置节点位置
917
- * @returns 贝塞尔线条数据
918
- */
919
- createBezier(fromPos, toPos) {
920
- const controls = this.vertical ? getBezierVerticalControlPoints(fromPos, toPos) : getBezierHorizontalControlPoints(fromPos, toPos);
921
- const bezier = new Bezier([fromPos, ...controls, toPos]);
922
- const bbox = bezier.bbox();
923
- const bboxBounds = new Rectangle6(
924
- bbox.x.min,
925
- bbox.y.min,
926
- bbox.x.max - bbox.x.min,
927
- bbox.y.max - bbox.y.min
928
- );
929
- const foldPoints = FoldLine.getPoints({
930
- source: {
931
- x: fromPos.x + POINT_RADIUS,
932
- y: fromPos.y
933
- },
934
- target: {
935
- x: toPos.x - POINT_RADIUS,
936
- y: toPos.y
937
- }
938
- });
939
- const foldPath = FoldLine.getSmoothStepPath(foldPoints);
940
- this._bezier = {
941
- fromPos,
942
- toPos,
943
- bezier,
944
- bbox: bboxBounds,
945
- controls,
946
- foldPath,
947
- foldPoints,
948
- foldBounds: FoldLine.getBounds(foldPoints)
949
- };
950
- return this._bezier;
951
- }
952
811
  /**
953
812
  * 获取线条的边框位置大小
954
813
  */
955
814
  get bounds() {
956
- return this.bezier.bbox;
815
+ return this.getData(WorkflowLineRenderData).bounds;
957
816
  }
958
817
  /**
959
818
  * 获取点和线最接近的距离
960
819
  */
961
- getHoverDist(pos, lineType = 0 /* BEZIER */) {
962
- if (lineType === 0 /* BEZIER */) {
963
- return Point2.getDistance(pos, this.bezier.bezier.project(pos));
964
- }
965
- return FoldLine.getFoldLineToPointDistance(this.bezier.foldPoints, pos);
820
+ getHoverDist(pos) {
821
+ return this.getData(WorkflowLineRenderData).calcDistance(pos);
966
822
  }
967
823
  get fromPort() {
968
824
  return this.from.getData(WorkflowNodePortsData).getPortEntityByKey("output", this.info.fromPort);
@@ -977,37 +833,7 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
977
833
  * 获取线条真实的输入输出节点坐标
978
834
  */
979
835
  get position() {
980
- const { bezier } = this;
981
- return {
982
- from: bezier.fromPos,
983
- to: bezier.toPos
984
- };
985
- }
986
- /**
987
- * 获取贝塞尔数据
988
- * 根据两边节点的位置信息创建贝塞尔曲线
989
- */
990
- get bezier() {
991
- if (!this._bezier) {
992
- this.refreshBezier();
993
- }
994
- return this._bezier;
995
- }
996
- refreshBezier() {
997
- const fromPos = this.from.getData(WorkflowNodePortsData).getOutputPoint(this.info.fromPort);
998
- const toPos = this.info.drawingTo || this.to.getData(WorkflowNodePortsData).getInputPoint(this.info.toPort);
999
- const bezierVersion = [fromPos.x, fromPos.y, toPos.x, toPos.y].join("-");
1000
- if (this._bezier && this._bezierVersion === bezierVersion) {
1001
- return;
1002
- }
1003
- this._bezierVersion = bezierVersion;
1004
- this._bezier = this.createBezier(fromPos, toPos);
1005
- }
1006
- /**
1007
- * 可以用于判断线条点位信息是否变化
1008
- */
1009
- get bezierDataVersion() {
1010
- return this._bezierVersion;
836
+ return this.getData(WorkflowLineRenderData).position;
1011
837
  }
1012
838
  /** 是否反转箭头 */
1013
839
  get reverse() {
@@ -1029,17 +855,17 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
1029
855
  get vertical() {
1030
856
  return this.linesManager.isVerticalLine(this);
1031
857
  }
858
+ /** 获取线条渲染器类型 */
859
+ get renderType() {
860
+ return this.linesManager.setLineRenderType(this);
861
+ }
862
+ /** 获取线条样式 */
863
+ get className() {
864
+ return this.linesManager.setLineClassName(this) ?? "";
865
+ }
1032
866
  get color() {
1033
867
  return this.linesManager.getLineColor(this);
1034
868
  }
1035
- // get defaultToPos(): IPoint {
1036
- // const fromPos = this.from.getData(TransformComponent)!.toBounds.center;
1037
- //
1038
- // return {
1039
- // x: fromPos.x + DEFAULT_LINE_LENGTH,
1040
- // y: fromPos.y,
1041
- // };
1042
- // }
1043
869
  /**
1044
870
  * 初始化线条
1045
871
  * @param info 线条信息
@@ -1071,15 +897,6 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
1071
897
  }
1072
898
  return _WorkflowLineEntity.portInfoToLineId(line) === this.id;
1073
899
  }
1074
- /**
1075
- * 框选 贝塞尔曲线
1076
- * TODO 这个方法可能有性能问题,后续看看有没有更好的方式
1077
- * @param rect
1078
- */
1079
- intersectsRectangle(rect) {
1080
- const dots = this.bezier.bezier.getLUT(50);
1081
- return dots.some((dot) => rect.contains(dot.x, dot.y));
1082
- }
1083
900
  canRemove(newLineInfo) {
1084
901
  return this.linesManager.canRemove(this, newLineInfo);
1085
902
  }
@@ -1185,7 +1002,7 @@ var WorkflowSelectService = class {
1185
1002
  entities: [node]
1186
1003
  };
1187
1004
  if (fitView2) {
1188
- const bounds = Rectangle7.enlarge([node.getData(TransformData4).bounds]).pad(
1005
+ const bounds = Rectangle6.enlarge([node.getData(TransformData4).bounds]).pad(
1189
1006
  30,
1190
1007
  30
1191
1008
  );
@@ -1278,7 +1095,7 @@ import {
1278
1095
  PromiseDeferred,
1279
1096
  Emitter as Emitter5,
1280
1097
  DisposableCollection as DisposableCollection2,
1281
- Rectangle as Rectangle9,
1098
+ Rectangle as Rectangle8,
1282
1099
  delay as delay2
1283
1100
  } from "@flowgram.ai/utils";
1284
1101
  import {
@@ -1351,125 +1168,6 @@ var WorkflowDocumentOptionsDefault = {
1351
1168
  }
1352
1169
  };
1353
1170
 
1354
- // src/entity-datas/workflow-node-lines-data.ts
1355
- import { Disposable } from "@flowgram.ai/utils";
1356
- import { EntityData as EntityData2 } from "@flowgram.ai/core";
1357
- var _WorkflowNodeLinesData = class _WorkflowNodeLinesData extends EntityData2 {
1358
- getDefaultData() {
1359
- return {
1360
- inputLines: [],
1361
- outputLines: []
1362
- };
1363
- }
1364
- constructor(entity) {
1365
- super(entity);
1366
- this.entity = entity;
1367
- this.toDispose.push(
1368
- Disposable.create(() => {
1369
- this.inputLines.slice().forEach((line) => line.dispose());
1370
- this.outputLines.slice().forEach((line) => line.dispose());
1371
- })
1372
- );
1373
- }
1374
- /**
1375
- * 输入线条
1376
- */
1377
- get inputLines() {
1378
- return this.data.inputLines;
1379
- }
1380
- /**
1381
- * 输出线条
1382
- */
1383
- get outputLines() {
1384
- return this.data.outputLines;
1385
- }
1386
- /**
1387
- * 输入节点
1388
- */
1389
- get inputNodes() {
1390
- return this.inputLines.map((l) => l.from).filter(Boolean);
1391
- }
1392
- /**
1393
- * 所有输入节点
1394
- */
1395
- get allInputNodes() {
1396
- const nodeSet = /* @__PURE__ */ new Set();
1397
- const handleNode = (node) => {
1398
- if (nodeSet.has(node)) {
1399
- return;
1400
- }
1401
- nodeSet.add(node);
1402
- const { inputNodes } = node.getData(_WorkflowNodeLinesData);
1403
- if (!inputNodes || !inputNodes.length) {
1404
- return;
1405
- }
1406
- inputNodes.forEach((inputNode) => {
1407
- if (inputNode?.parent === node || node?.parent === inputNode) {
1408
- return;
1409
- }
1410
- handleNode(inputNode);
1411
- });
1412
- };
1413
- handleNode(this.entity);
1414
- nodeSet.delete(this.entity);
1415
- return Array.from(nodeSet);
1416
- }
1417
- /**
1418
- * 输出节点
1419
- */
1420
- get outputNodes() {
1421
- return this.outputLines.map((l) => l.to).filter(Boolean);
1422
- }
1423
- /**
1424
- * 输入输出节点
1425
- */
1426
- get allOutputNodes() {
1427
- const nodeSet = /* @__PURE__ */ new Set();
1428
- const handleNode = (node) => {
1429
- if (nodeSet.has(node)) {
1430
- return;
1431
- }
1432
- nodeSet.add(node);
1433
- const { outputNodes } = node.getData(_WorkflowNodeLinesData);
1434
- if (!outputNodes || !outputNodes.length) {
1435
- return;
1436
- }
1437
- outputNodes.forEach((outputNode) => {
1438
- if (outputNode?.parent === node || node?.parent === outputNode) {
1439
- return;
1440
- }
1441
- handleNode(outputNode);
1442
- });
1443
- };
1444
- handleNode(this.entity);
1445
- nodeSet.delete(this.entity);
1446
- return Array.from(nodeSet);
1447
- }
1448
- addLine(line) {
1449
- if (line.from === this.entity) {
1450
- this.outputLines.push(line);
1451
- } else {
1452
- this.inputLines.push(line);
1453
- }
1454
- this.fireChange();
1455
- }
1456
- removeLine(line) {
1457
- const { inputLines, outputLines } = this;
1458
- const inputIndex = inputLines.indexOf(line);
1459
- const outputIndex = outputLines.indexOf(line);
1460
- if (inputIndex !== -1) {
1461
- inputLines.splice(inputIndex, 1);
1462
- this.fireChange();
1463
- }
1464
- if (outputIndex !== -1) {
1465
- outputLines.splice(outputIndex, 1);
1466
- this.fireChange();
1467
- }
1468
- }
1469
- };
1470
- _WorkflowNodeLinesData.type = "WorkflowNodeLinesData";
1471
- var WorkflowNodeLinesData = _WorkflowNodeLinesData;
1472
-
1473
1171
  // src/workflow-lines-manager.ts
1474
1172
  var WorkflowLinesManager = class {
1475
1173
  constructor() {
@@ -1486,6 +1184,7 @@ var WorkflowLinesManager = class {
1486
1184
  * 强制渲染 lines
1487
1185
  */
1488
1186
  this.onForceUpdate = this.onForceUpdateEmitter.event;
1187
+ this.contributionFactories = [];
1489
1188
  /**
1490
1189
  * 是否在调整线条
1491
1190
  */
@@ -1524,7 +1223,12 @@ var WorkflowLinesManager = class {
1524
1223
  }
1525
1224
  if (newType !== this._lineType) {
1526
1225
  this._lineType = newType;
1527
- this.entityManager.fireEntityChanged(WorkflowLineEntity.type);
1226
+ this.getAllLines().forEach((line) => {
1227
+ line.getData(WorkflowLineRenderData).update();
1228
+ });
1229
+ window.requestAnimationFrame(() => {
1230
+ this.entityManager.fireEntityChanged(WorkflowLineEntity.type);
1231
+ });
1528
1232
  }
1529
1233
  return this._lineType;
1530
1234
  }
@@ -1574,6 +1278,7 @@ var WorkflowLinesManager = class {
1574
1278
  to,
1575
1279
  drawingTo
1576
1280
  });
1281
+ this.registerData(line);
1577
1282
  fromNode.addLine(line);
1578
1283
  toNode?.addLine(line);
1579
1284
  line.onDispose(() => {
@@ -1614,7 +1319,7 @@ var WorkflowLinesManager = class {
1614
1319
  getCloseInLineFromMousePos(mousePos, minDistance = LINE_HOVER_DISTANCE) {
1615
1320
  let targetLine, targetLineDist;
1616
1321
  this.getAllLines().forEach((line) => {
1617
- const dist = line.getHoverDist(mousePos, this.lineType);
1322
+ const dist = line.getHoverDist(mousePos);
1618
1323
  if (dist <= minDistance && (!targetLineDist || targetLineDist >= dist)) {
1619
1324
  targetLineDist = dist;
1620
1325
  targetLine = line;
@@ -1664,6 +1369,18 @@ var WorkflowLinesManager = class {
1664
1369
  }
1665
1370
  return false;
1666
1371
  }
1372
+ setLineRenderType(line) {
1373
+ if (this.options.setLineRenderType) {
1374
+ return this.options.setLineRenderType(line);
1375
+ }
1376
+ return void 0;
1377
+ }
1378
+ setLineClassName(line) {
1379
+ if (this.options.setLineClassName) {
1380
+ return this.options.setLineClassName(line);
1381
+ }
1382
+ return void 0;
1383
+ }
1667
1384
  getLineColor(line) {
1668
1385
  if (line.isHidden) {
1669
1386
  return this.lineColor.hidden;
@@ -1752,6 +1469,13 @@ var WorkflowLinesManager = class {
1752
1469
  }
1753
1470
  return last(containNodes);
1754
1471
  }
1472
+ registerContribution(factory) {
1473
+ this.contributionFactories.push(factory);
1474
+ return this;
1475
+ }
1476
+ registerData(line) {
1477
+ line.addData(WorkflowLineRenderData);
1478
+ }
1755
1479
  };
1756
1480
  __decorateClass([
1757
1481
  inject3(WorkflowHoverService)
@@ -1791,7 +1515,7 @@ import {
1791
1515
  import { PlaygroundConfigEntity as PlaygroundConfigEntity3, TransformData as TransformData7 } from "@flowgram.ai/core";
1792
1516
  import {
1793
1517
  PaddingSchema,
1794
- Rectangle as Rectangle8,
1518
+ Rectangle as Rectangle7,
1795
1519
  SizeSchema as SizeSchema2
1796
1520
  } from "@flowgram.ai/utils";
1797
1521
  var FREE_LAYOUT_KEY = "free-layout";
@@ -1845,7 +1569,7 @@ var FreeLayout = class {
1845
1569
  * @param contentSize
1846
1570
  */
1847
1571
  getInitScroll(contentSize) {
1848
- const bounds = Rectangle8.enlarge(
1572
+ const bounds = Rectangle7.enlarge(
1849
1573
  this.document.getAllNodes().map((node) => node.getData(TransformData7).bounds)
1850
1574
  ).pad(30, 30);
1851
1575
  const viewport = this.playgroundConfig.getViewport(false);
@@ -2656,17 +2380,17 @@ var WorkflowDragService = class {
2656
2380
  domNode.style.left = `${left}px`;
2657
2381
  domNode.style.top = `${right}px`;
2658
2382
  const { x, y } = this.playgroundConfig.getPosFromMouseEvent(e);
2659
- const draggingRect = new Rectangle9(x, y, 170, 90);
2383
+ const draggingRect = new Rectangle8(x, y, 170, 90);
2660
2384
  const collisionTransform = this._droppableTransforms.find((transform) => {
2661
2385
  const { bounds, entity } = transform;
2662
2386
  const padding = this.document.layout.getPadding(entity);
2663
- const transformRect = new Rectangle9(
2387
+ const transformRect = new Rectangle8(
2664
2388
  bounds.x + padding.left + padding.right,
2665
2389
  bounds.y,
2666
2390
  bounds.width,
2667
2391
  bounds.height
2668
2392
  );
2669
- return Rectangle9.intersects(draggingRect, transformRect);
2393
+ return Rectangle8.intersects(draggingRect, transformRect);
2670
2394
  });
2671
2395
  this.updateDropNode(collisionTransform?.entity);
2672
2396
  },
@@ -2770,7 +2494,7 @@ var WorkflowDragService = class {
2770
2494
  * 获取节点整体位置
2771
2495
  */
2772
2496
  getNodesPosition(nodes) {
2773
- const selectedBounds = Rectangle9.enlarge(
2497
+ const selectedBounds = Rectangle8.enlarge(
2774
2498
  nodes.map((n) => n.getData(FlowNodeTransformData5).bounds)
2775
2499
  );
2776
2500
  const position = {
@@ -3372,10 +3096,83 @@ var WorkflowDocumentContainerModule = new ContainerModule(
3372
3096
  bind(WorkflowDocumentProvider).toDynamicValue((ctx) => () => ctx.container.get(WorkflowDocument)).inSingletonScope();
3373
3097
  }
3374
3098
  );
3099
+
3100
+ // src/utils/simple-line.ts
3101
+ import { Point, Rectangle as Rectangle9 } from "@flowgram.ai/utils";
3102
+ var LINE_PADDING = 12;
3103
+ var WorkflowSimpleLineContribution = class {
3104
+ constructor(entity) {
3105
+ this.entity = entity;
3106
+ }
3107
+ get path() {
3108
+ return this.data?.path ?? "";
3109
+ }
3110
+ calcDistance(pos) {
3111
+ if (!this.data) {
3112
+ return Number.MAX_SAFE_INTEGER;
3113
+ }
3114
+ const [start, end] = this.data.points;
3115
+ return Point.getDistance(pos, this.projectPointOnLine(pos, start, end));
3116
+ }
3117
+ get bounds() {
3118
+ if (!this.data) {
3119
+ return new Rectangle9();
3120
+ }
3121
+ return this.data.bbox;
3122
+ }
3123
+ update(params) {
3124
+ const { fromPos, toPos } = params;
3125
+ const { vertical } = this.entity;
3126
+ const sourceOffset = {
3127
+ x: vertical ? 0 : POINT_RADIUS,
3128
+ y: vertical ? POINT_RADIUS : 0
3129
+ };
3130
+ const targetOffset = {
3131
+ x: vertical ? 0 : -POINT_RADIUS,
3132
+ y: vertical ? -POINT_RADIUS : 0
3133
+ };
3134
+ const points = [
3135
+ {
3136
+ x: fromPos.x + sourceOffset.x,
3137
+ y: fromPos.y + sourceOffset.y
3138
+ },
3139
+ {
3140
+ x: toPos.x + targetOffset.x,
3141
+ y: toPos.y + targetOffset.y
3142
+ }
3143
+ ];
3144
+ const bbox = Rectangle9.createRectangleWithTwoPoints(points[0], points[1]);
3145
+ const adjustedPoints = points.map((p) => ({
3146
+ x: p.x - bbox.x + LINE_PADDING,
3147
+ y: p.y - bbox.y + LINE_PADDING
3148
+ }));
3149
+ const path = `M ${adjustedPoints[0].x} ${adjustedPoints[0].y} L ${adjustedPoints[1].x} ${adjustedPoints[1].y}`;
3150
+ this.data = {
3151
+ points,
3152
+ path,
3153
+ bbox
3154
+ };
3155
+ }
3156
+ projectPointOnLine(point, lineStart, lineEnd) {
3157
+ const dx = lineEnd.x - lineStart.x;
3158
+ const dy = lineEnd.y - lineStart.y;
3159
+ if (dx === 0) {
3160
+ return { x: lineStart.x, y: point.y };
3161
+ }
3162
+ if (dy === 0) {
3163
+ return { x: point.x, y: lineStart.y };
3164
+ }
3165
+ const t = ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) / (dx * dx + dy * dy);
3166
+ const clampedT = Math.max(0, Math.min(1, t));
3167
+ return {
3168
+ x: lineStart.x + clampedT * dx,
3169
+ y: lineStart.y + clampedT * dy
3170
+ };
3171
+ }
3172
+ };
3173
+ WorkflowSimpleLineContribution.type = "WorkflowSimpleLineContribution";
3375
3174
  export {
3376
- BezierControlType,
3377
3175
  EditorCursorState,
3378
- FoldLine,
3379
3176
  InteractiveType,
3380
3177
  LINE_HOVER_DISTANCE,
3381
3178
  LineColors,
@@ -3394,6 +3191,7 @@ export {
3394
3191
  WorkflowDragService,
3395
3192
  WorkflowHoverService,
3396
3193
  WorkflowLineEntity,
3194
+ WorkflowLineRenderData,
3397
3195
  WorkflowLinesManager,
3398
3196
  WorkflowNodeEntity,
3399
3197
  WorkflowNodeLinesData,
@@ -3401,6 +3199,7 @@ export {
3401
3199
  WorkflowPortEntity,
3402
3200
  WorkflowResetLayoutService,
3403
3201
  WorkflowSelectService,
3202
+ WorkflowSimpleLineContribution,
3404
3203
  bindConfigEntity,
3405
3204
  compose,
3406
3205
  composeAsync,
@@ -3408,8 +3207,6 @@ export {
3408
3207
  domReactToBounds,
3409
3208
  fitView,
3410
3209
  getAntiOverlapPosition,
3411
- getBezierHorizontalControlPoints,
3412
- getBezierVerticalControlPoints,
3413
3210
  getPortEntityId,
3414
3211
  nanoid,
3415
3212
  useConfigEntity,