@flowgram.ai/free-layout-core 0.1.0-alpha.10 → 0.1.0-alpha.12

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
@@ -72,6 +72,34 @@ import { Rectangle as Rectangle6, SizeSchema } from "@flowgram.ai/utils";
72
72
  import { bindConfigEntity } from "@flowgram.ai/core";
73
73
  import { delay } from "@flowgram.ai/utils";
74
74
 
75
+ // src/utils/build-group-json.ts
76
+ import { FlowNodeBaseType } from "@flowgram.ai/document";
77
+ var buildGroupJSON = (json) => {
78
+ const { nodes, edges } = json;
79
+ const groupJSONs = nodes.filter(
80
+ (nodeJSON) => nodeJSON.type === FlowNodeBaseType.GROUP
81
+ );
82
+ const nodeJSONMap = new Map(nodes.map((n) => [n.id, n]));
83
+ const groupNodeJSONs = groupJSONs.map((groupJSON) => {
84
+ const groupBlocks = (groupJSON.data.blockIDs ?? []).map((blockID) => nodeJSONMap.get(blockID)).filter(Boolean);
85
+ const groupEdges = edges?.filter(
86
+ (edge) => groupBlocks.some((block) => block.id === edge.sourceNodeID || block.id === edge.targetNodeID)
87
+ );
88
+ const groupNodeJSON = {
89
+ ...groupJSON,
90
+ blocks: groupBlocks,
91
+ edges: groupEdges
92
+ };
93
+ return groupNodeJSON;
94
+ });
95
+ const groupBlockSet = new Set(groupJSONs.map((groupJSON) => groupJSON.data.blockIDs).flat());
96
+ const processedNodes = nodes.filter((nodeJSON) => !groupBlockSet.has(nodeJSON.id)).concat(groupNodeJSONs);
97
+ return {
98
+ nodes: processedNodes,
99
+ edges
100
+ };
101
+ };
102
+
75
103
  // src/utils/nanoid.ts
76
104
  import { nanoid as nanoidOrigin } from "nanoid";
77
105
  function nanoid(n) {
@@ -173,22 +201,23 @@ var WorkflowPortEntity = class extends Entity {
173
201
  }
174
202
  // 设置连线的错误态,外部应使用 validate 进行更新
175
203
  set hasError(hasError) {
176
- this._hasError = hasError;
177
- this._onErrorChangedEmitter.fire();
204
+ if (hasError !== this._hasError) {
205
+ this._hasError = hasError;
206
+ this._onErrorChangedEmitter.fire();
207
+ }
178
208
  }
179
209
  validate() {
180
210
  const anyLineHasError = this.allLines.some((line) => {
181
211
  if (line.disposed || line.isHidden) {
182
212
  return false;
183
213
  }
184
- line.validateSelf();
185
214
  return line.hasError;
186
215
  });
187
216
  const isPortHasError = this.node.document.isErrorPort(this);
188
217
  this.hasError = anyLineHasError || isPortHasError;
189
218
  }
190
219
  isErrorPort() {
191
- return this.node.document.isErrorPort(this);
220
+ return this.node.document.isErrorPort(this, this.hasError);
192
221
  }
193
222
  get point() {
194
223
  const { targetElement } = this;
@@ -375,7 +404,6 @@ var WorkflowNodePortsData = class extends EntityData {
375
404
  port.allLines.forEach((line) => {
376
405
  line.validate();
377
406
  });
378
- port.validate();
379
407
  });
380
408
  }
381
409
  /**
@@ -506,6 +534,9 @@ var _WorkflowNodeLinesData = class _WorkflowNodeLinesData extends EntityData2 {
506
534
  get outputLines() {
507
535
  return this.data.outputLines;
508
536
  }
537
+ get allLines() {
538
+ return this.data.inputLines.concat(this.data.outputLines);
539
+ }
509
540
  /**
510
541
  * 输入节点
511
542
  */
@@ -692,8 +723,16 @@ var POINT_RADIUS = 10;
692
723
  var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
693
724
  constructor(opts) {
694
725
  super(opts);
695
- this._processing = false;
696
- this._hasError = false;
726
+ this._uiState = {
727
+ hasError: false,
728
+ flowing: false,
729
+ disabled: false,
730
+ vertical: false,
731
+ hideArrow: false,
732
+ reverse: false,
733
+ highlightColor: "",
734
+ lockedColor: ""
735
+ };
697
736
  this.stackIndex = 0;
698
737
  /**
699
738
  * 线条数据
@@ -713,6 +752,14 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
713
752
  if (opts.drawingTo) {
714
753
  this.isDrawing = true;
715
754
  }
755
+ this.onEntityChange(() => {
756
+ this.fromPort?.validate();
757
+ this.toPort?.validate();
758
+ });
759
+ this.onDispose(() => {
760
+ this.fromPort?.validate();
761
+ this.toPort?.validate();
762
+ });
716
763
  }
717
764
  /**
718
765
  * 转成线条 id
@@ -722,6 +769,43 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
722
769
  const { from, to, fromPort, toPort } = info;
723
770
  return `${from}_${fromPort || ""}-${to || ""}_${toPort || ""}`;
724
771
  }
772
+ /**
773
+ * 线条的 UI 状态
774
+ */
775
+ get uiState() {
776
+ return this._uiState;
777
+ }
778
+ /**
779
+ * 更新线条的 ui 状态
780
+ * @param newState
781
+ */
782
+ updateUIState(newState) {
783
+ let changed = false;
784
+ Object.keys(newState).forEach((key) => {
785
+ const value = newState[key];
786
+ if (this._uiState[key] !== value) {
787
+ this._uiState[key] = value;
788
+ changed = true;
789
+ }
790
+ });
791
+ if (changed) {
792
+ this.fireChange();
793
+ }
794
+ }
795
+ /**
796
+ * 线条的扩展数据
797
+ */
798
+ get lineData() {
799
+ return this._lineData;
800
+ }
801
+ /**
802
+ * 更新线条扩展数据
803
+ * @param data
804
+ */
805
+ set lineData(data) {
806
+ this._lineData = data;
807
+ this.fireChange();
808
+ }
725
809
  /**
726
810
  * 获取线条的前置节点
727
811
  */
@@ -743,29 +827,30 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
743
827
  }
744
828
  /**
745
829
  * 获取是否 testrun processing
830
+ * @deprecated use `uiState.flowing` instead
746
831
  */
747
832
  get processing() {
748
- return this._processing;
833
+ return this._uiState.flowing;
749
834
  }
750
835
  /**
751
836
  * 设置 testrun processing 状态
837
+ * @deprecated use `uiState.flowing` instead
752
838
  */
753
839
  set processing(status) {
754
- if (this._processing !== status) {
755
- this._processing = status;
840
+ if (this._uiState.flowing !== status) {
841
+ this._uiState.flowing = status;
756
842
  this.fireChange();
757
843
  }
758
844
  }
759
845
  // 获取连线是否为错误态
760
846
  get hasError() {
761
- return this._hasError;
847
+ return this.uiState.hasError;
762
848
  }
763
849
  // 设置连线的错误态
764
850
  set hasError(hasError) {
765
- if (this._hasError !== hasError) {
766
- this._hasError = hasError;
767
- this.fireChange();
768
- }
851
+ this.updateUIState({
852
+ hasError
853
+ });
769
854
  if (this._node) {
770
855
  this._node.dataset.hasError = this.hasError ? "true" : "false";
771
856
  }
@@ -780,6 +865,7 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
780
865
  if (this.toPort === toPort) {
781
866
  return;
782
867
  }
868
+ const prePort = this.toPort;
783
869
  if (toPort && toPort.portType === "input" && this.linesManager.canAddLine(this.fromPort, toPort, true)) {
784
870
  const { node, portID } = toPort;
785
871
  this._to = node;
@@ -792,6 +878,9 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
792
878
  this.info.to = void 0;
793
879
  this.info.toPort = "";
794
880
  }
881
+ if (prePort) {
882
+ prePort.validate();
883
+ }
795
884
  this.fireChange();
796
885
  }
797
886
  /**
@@ -818,13 +907,20 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
818
907
  return this.info.drawingTo;
819
908
  }
820
909
  get highlightColor() {
821
- return this.info.highlightColor || "";
910
+ return this.uiState.highlightColor || "";
822
911
  }
823
- set highlightColor(color) {
824
- if (this.info.highlightColor !== color) {
825
- this.info.highlightColor = color;
826
- this.fireChange();
827
- }
912
+ set highlightColor(highlightColor) {
913
+ this.updateUIState({
914
+ highlightColor
915
+ });
916
+ }
917
+ get lockedColor() {
918
+ return this.uiState.lockedColor;
919
+ }
920
+ set lockedColor(lockedColor) {
921
+ this.updateUIState({
922
+ lockedColor
923
+ });
828
924
  }
829
925
  /**
830
926
  * 获取线条的边框位置大小
@@ -855,23 +951,23 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
855
951
  }
856
952
  /** 是否反转箭头 */
857
953
  get reverse() {
858
- return this.linesManager.isReverseLine(this);
954
+ return this.linesManager.isReverseLine(this, this.uiState.reverse);
859
955
  }
860
956
  /** 是否隐藏箭头 */
861
957
  get hideArrow() {
862
- return this.linesManager.isHideArrowLine(this);
958
+ return this.linesManager.isHideArrowLine(this, this.uiState.hideArrow);
863
959
  }
864
960
  /** 是否流动 */
865
961
  get flowing() {
866
- return this.linesManager.isFlowingLine(this);
962
+ return this.linesManager.isFlowingLine(this, this.uiState.flowing);
867
963
  }
868
964
  /** 是否禁用 */
869
965
  get disabled() {
870
- return this.linesManager.isDisabledLine(this);
966
+ return this.linesManager.isDisabledLine(this, this.uiState.disabled);
871
967
  }
872
968
  /** 是否竖向 */
873
969
  get vertical() {
874
- return this.linesManager.isVerticalLine(this);
970
+ return this.linesManager.isVerticalLine(this, this.uiState.vertical);
875
971
  }
876
972
  /** 获取线条渲染器类型 */
877
973
  get renderType() {
@@ -898,15 +994,16 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
898
994
  }
899
995
  // 校验连线是否为错误态
900
996
  validate() {
901
- const { fromPort, toPort } = this;
902
997
  this.validateSelf();
903
- fromPort?.validate();
904
- toPort?.validate();
905
998
  }
999
+ /**
1000
+ * use `validate` instead
1001
+ * @deprecated
1002
+ */
906
1003
  validateSelf() {
907
1004
  const { fromPort, toPort } = this;
908
1005
  if (fromPort) {
909
- this.hasError = this.linesManager.isErrorLine(fromPort, toPort);
1006
+ this.hasError = this.linesManager.isErrorLine(fromPort, toPort, this.uiState.hasError);
910
1007
  }
911
1008
  }
912
1009
  is(line) {
@@ -937,6 +1034,9 @@ var _WorkflowLineEntity = class _WorkflowLineEntity extends Entity2 {
937
1034
  sourcePortID: this.info.fromPort,
938
1035
  targetPortID: this.info.toPort
939
1036
  };
1037
+ if (this._lineData !== void 0) {
1038
+ json.data = this._lineData;
1039
+ }
940
1040
  if (!json.sourcePortID) {
941
1041
  delete json.sourcePortID;
942
1042
  }
@@ -1129,7 +1229,7 @@ import {
1129
1229
  FlowNodeTransformData as FlowNodeTransformData6,
1130
1230
  FlowOperationBaseService
1131
1231
  } from "@flowgram.ai/document";
1132
- import { FlowNodeBaseType as FlowNodeBaseType2 } from "@flowgram.ai/document";
1232
+ import { FlowNodeBaseType as FlowNodeBaseType3 } from "@flowgram.ai/document";
1133
1233
  import {
1134
1234
  CommandService,
1135
1235
  MouseTouchEvent,
@@ -1339,7 +1439,6 @@ var WorkflowLinesManager = class {
1339
1439
  }
1340
1440
  fromNode.removeLine(line);
1341
1441
  toNode?.removeLine(line);
1342
- line.validate();
1343
1442
  });
1344
1443
  line.onDispose(() => {
1345
1444
  if (available) {
@@ -1383,41 +1482,41 @@ var WorkflowLinesManager = class {
1383
1482
  get disposed() {
1384
1483
  return this.toDispose.disposed;
1385
1484
  }
1386
- isErrorLine(fromPort, toPort) {
1485
+ isErrorLine(fromPort, toPort, defaultValue) {
1387
1486
  if (this.options.isErrorLine) {
1388
1487
  return this.options.isErrorLine(fromPort, toPort, this);
1389
1488
  }
1390
- return false;
1489
+ return !!defaultValue;
1391
1490
  }
1392
- isReverseLine(line) {
1491
+ isReverseLine(line, defaultValue = false) {
1393
1492
  if (this.options.isReverseLine) {
1394
1493
  return this.options.isReverseLine(line);
1395
1494
  }
1396
- return false;
1495
+ return defaultValue;
1397
1496
  }
1398
- isHideArrowLine(line) {
1497
+ isHideArrowLine(line, defaultValue = false) {
1399
1498
  if (this.options.isHideArrowLine) {
1400
1499
  return this.options.isHideArrowLine(line);
1401
1500
  }
1402
- return false;
1501
+ return defaultValue;
1403
1502
  }
1404
- isFlowingLine(line) {
1503
+ isFlowingLine(line, defaultValue = false) {
1405
1504
  if (this.options.isFlowingLine) {
1406
1505
  return this.options.isFlowingLine(line);
1407
1506
  }
1408
- return false;
1507
+ return defaultValue;
1409
1508
  }
1410
- isDisabledLine(line) {
1509
+ isDisabledLine(line, defaultValue = false) {
1411
1510
  if (this.options.isDisabledLine) {
1412
1511
  return this.options.isDisabledLine(line);
1413
1512
  }
1414
- return false;
1513
+ return defaultValue;
1415
1514
  }
1416
- isVerticalLine(line) {
1515
+ isVerticalLine(line, defaultValue = false) {
1417
1516
  if (this.options.isVerticalLine) {
1418
1517
  return this.options.isVerticalLine(line);
1419
1518
  }
1420
- return false;
1519
+ return defaultValue;
1421
1520
  }
1422
1521
  setLineRenderType(line) {
1423
1522
  if (this.options.setLineRenderType) {
@@ -1435,6 +1534,9 @@ var WorkflowLinesManager = class {
1435
1534
  if (line.isHidden) {
1436
1535
  return this.lineColor.hidden;
1437
1536
  }
1537
+ if (line.lockedColor) {
1538
+ return line.lockedColor;
1539
+ }
1438
1540
  if (line.hasError) {
1439
1541
  return this.lineColor.error;
1440
1542
  }
@@ -1566,7 +1668,7 @@ import { Emitter as Emitter4 } from "@flowgram.ai/utils";
1566
1668
  import { NodeEngineContext } from "@flowgram.ai/form-core";
1567
1669
  import {
1568
1670
  FlowDocument,
1569
- FlowNodeBaseType,
1671
+ FlowNodeBaseType as FlowNodeBaseType2,
1570
1672
  FlowNodeTransformData as FlowNodeTransformData5
1571
1673
  } from "@flowgram.ai/document";
1572
1674
  import {
@@ -1859,7 +1961,7 @@ var WorkflowDocument = class extends FlowDocument {
1859
1961
  toJSON: () => this.toNodeJSON(node)
1860
1962
  });
1861
1963
  node.onDispose(() => {
1862
- if (!node.parent || node.parent.flowNodeType === FlowNodeBaseType.ROOT) {
1964
+ if (!node.parent || node.parent.flowNodeType === FlowNodeBaseType2.ROOT) {
1863
1965
  return;
1864
1966
  }
1865
1967
  const parentTransform = node.parent.getData(FlowNodeTransformData5);
@@ -2027,10 +2129,10 @@ var WorkflowDocument = class extends FlowDocument {
2027
2129
  );
2028
2130
  }
2029
2131
  getAllNodes() {
2030
- return this.entityManager.getEntities(WorkflowNodeEntity).filter((n) => n.id !== FlowNodeBaseType.ROOT);
2132
+ return this.entityManager.getEntities(WorkflowNodeEntity).filter((n) => n.id !== FlowNodeBaseType2.ROOT);
2031
2133
  }
2032
2134
  getAllPorts() {
2033
- return this.entityManager.getEntities(WorkflowPortEntity).filter((p) => p.node.id !== FlowNodeBaseType.ROOT);
2135
+ return this.entityManager.getEntities(WorkflowPortEntity).filter((p) => p.node.id !== FlowNodeBaseType2.ROOT);
2034
2136
  }
2035
2137
  /**
2036
2138
  * 获取画布中的非游离节点
@@ -2174,21 +2276,22 @@ var WorkflowDocument = class extends FlowDocument {
2174
2276
  /**
2175
2277
  * 判断端口是否为错误态
2176
2278
  */
2177
- isErrorPort(port) {
2279
+ isErrorPort(port, defaultValue = false) {
2178
2280
  if (typeof this.options.isErrorPort === "function") {
2179
2281
  return this.options.isErrorPort(port);
2180
2282
  }
2181
- return false;
2283
+ return defaultValue;
2182
2284
  }
2183
2285
  /**
2184
2286
  * 导出数据
2185
2287
  */
2186
2288
  toJSON() {
2187
2289
  const rootJSON = this.toNodeJSON(this.root);
2188
- return {
2290
+ const json = {
2189
2291
  nodes: rootJSON.blocks ?? [],
2190
2292
  edges: rootJSON.edges ?? []
2191
2293
  };
2294
+ return json;
2192
2295
  }
2193
2296
  dispose() {
2194
2297
  super.dispose();
@@ -2200,10 +2303,11 @@ var WorkflowDocument = class extends FlowDocument {
2200
2303
  renderJSON(json, options) {
2201
2304
  const { parent = this.root, isClone = false } = options ?? {};
2202
2305
  const containerID = this.getNodeSubCanvas(parent)?.canvasNode.id ?? parent.id;
2203
- const nodes = json.nodes.map(
2306
+ const processedJSON = buildGroupJSON(json);
2307
+ const nodes = processedJSON.nodes.map(
2204
2308
  (nodeJSON) => this.createWorkflowNode(nodeJSON, isClone, containerID)
2205
2309
  );
2206
- const edges = json.edges.map((edge) => this.createWorkflowLine(edge, containerID)).filter(Boolean);
2310
+ const edges = processedJSON.edges.map((edge) => this.createWorkflowLine(edge, containerID)).filter(Boolean);
2207
2311
  return { nodes, edges };
2208
2312
  }
2209
2313
  getNodeSubCanvas(node) {
@@ -2213,13 +2317,19 @@ var WorkflowDocument = class extends FlowDocument {
2213
2317
  return subCanvas;
2214
2318
  }
2215
2319
  getNodeChildren(node) {
2216
- if (!node) return [];
2320
+ if (!node || node.flowNodeType === FlowNodeBaseType2.GROUP) return [];
2217
2321
  const subCanvas = this.getNodeSubCanvas(node);
2218
- const childrenWithCanvas = subCanvas ? subCanvas.canvasNode.collapsedChildren : node.collapsedChildren;
2219
- const children = childrenWithCanvas.filter((child) => {
2322
+ const realChildren = subCanvas ? subCanvas.canvasNode.blocks : node.blocks;
2323
+ const childrenWithoutSubCanvas = realChildren.filter((child) => {
2220
2324
  const childMeta = child.getNodeMeta();
2221
2325
  return !childMeta.subCanvas?.(node)?.isCanvas;
2222
2326
  }).filter(Boolean);
2327
+ const children = childrenWithoutSubCanvas.map((child) => {
2328
+ if (child.flowNodeType === FlowNodeBaseType2.GROUP) {
2329
+ return [child, ...child.blocks];
2330
+ }
2331
+ return child;
2332
+ }).flat();
2223
2333
  return children;
2224
2334
  }
2225
2335
  toLineJSON(line) {
@@ -2351,7 +2461,7 @@ var WorkflowDragService = class {
2351
2461
  }
2352
2462
  this.isDragging = true;
2353
2463
  const sameParent = this.childrenOfContainer(selectedNodes);
2354
- if (sameParent && sameParent.flowNodeType !== FlowNodeBaseType2.ROOT) {
2464
+ if (sameParent && sameParent.flowNodeType !== FlowNodeBaseType3.ROOT) {
2355
2465
  selectedNodes = [sameParent];
2356
2466
  }
2357
2467
  let startPosition = this.getNodesPosition(selectedNodes);
@@ -2521,7 +2631,7 @@ var WorkflowDragService = class {
2521
2631
  if (!mousePos) {
2522
2632
  return { x: 0, y: 0 };
2523
2633
  }
2524
- if (!subNodeType || !containerNode || containerNode.flowNodeType === FlowNodeBaseType2.ROOT) {
2634
+ if (!subNodeType || !containerNode || containerNode.flowNodeType === FlowNodeBaseType3.ROOT) {
2525
2635
  return mousePos;
2526
2636
  }
2527
2637
  const isParentEmpty = !containerNode.children || containerNode.children.length === 0;
@@ -2732,7 +2842,7 @@ var WorkflowDragService = class {
2732
2842
  return;
2733
2843
  }
2734
2844
  config.updateCursor("grab");
2735
- line.highlightColor = this.linesManager.lineColor.drawing;
2845
+ line.highlightColor = originLine?.lockedColor || this.linesManager.lineColor.drawing;
2736
2846
  this.hoverService.updateHoveredKey("");
2737
2847
  }
2738
2848
  if (!line) {
@@ -2754,7 +2864,7 @@ var WorkflowDragService = class {
2754
2864
  this._onDragLineEventEmitter.fire({
2755
2865
  type: "onDrag"
2756
2866
  });
2757
- this.setLineColor(line, this.linesManager.lineColor.drawing);
2867
+ this.setLineColor(line, originLine?.lockedColor || this.linesManager.lineColor.drawing);
2758
2868
  if (toNode && this.canBuildContainerLine(toNode, dragPos)) {
2759
2869
  toPort = this.getNearestPort(toNode, dragPos);
2760
2870
  const { hasError } = this.handleDragOnNode(toNode, fromPort, line, toPort, originLine);
@@ -3443,6 +3553,7 @@ export {
3443
3553
  WorkflowSelectService,
3444
3554
  WorkflowSimpleLineContribution,
3445
3555
  bindConfigEntity,
3556
+ buildGroupJSON,
3446
3557
  compose,
3447
3558
  composeAsync,
3448
3559
  delay,