|
|
@@ -1337,11 +1337,31 @@ function WorkflowEditorInner() {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
+ // Track edges that exit from loop bodies to main flow nodes
|
|
|
+ // These need to be represented as loop -> target edges to maintain ordering
|
|
|
+ const loopExitEdges = new Map<string, Set<string>>()
|
|
|
+ loopNodes.forEach(loopId => loopExitEdges.set(loopId, new Set()))
|
|
|
+
|
|
|
+ edges.forEach(e => {
|
|
|
+ const sourceLoop = nodeToLoop.get(e.source)
|
|
|
+ const targetInBody = nodeToLoop.has(e.target)
|
|
|
+
|
|
|
+ // If source is in a loop body and target is NOT in any body and not a loop, track as exit edge
|
|
|
+ if (sourceLoop && !targetInBody && !loopNodes.has(e.target)) {
|
|
|
+ loopExitEdges.get(sourceLoop)!.add(e.target)
|
|
|
+ }
|
|
|
+ // If source is in a loop body and target is another loop (not the same loop), track as exit edge
|
|
|
+ if (sourceLoop && loopNodes.has(e.target) && e.target !== sourceLoop) {
|
|
|
+ loopExitEdges.get(sourceLoop)!.add(e.target)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
// Add edges - for loop nodes, route through spacer
|
|
|
edges.forEach(e => {
|
|
|
const sourceInBody = nodeToLoop.has(e.source)
|
|
|
const targetInBody = nodeToLoop.has(e.target)
|
|
|
|
|
|
+ // Skip edges where source or target is a body node (handled separately)
|
|
|
if (sourceInBody || targetInBody) return
|
|
|
|
|
|
// For loop "done" edges, route through the spacer
|
|
|
@@ -1360,6 +1380,30 @@ function WorkflowEditorInner() {
|
|
|
g.setEdge(e.source, e.target)
|
|
|
})
|
|
|
|
|
|
+ // Connect each loop to its spacer (if it has one)
|
|
|
+ // This ensures the spacer is positioned directly below the loop
|
|
|
+ loopNodes.forEach(loopId => {
|
|
|
+ const spacerId = loopId + '_spacer'
|
|
|
+ if (g.hasNode(spacerId)) {
|
|
|
+ g.setEdge(loopId, spacerId)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // Add edges from loops (via spacers) to nodes that their body exits to
|
|
|
+ // This ensures proper ordering when body nodes connect to main flow nodes
|
|
|
+ loopNodes.forEach(loopId => {
|
|
|
+ const exitTargets = loopExitEdges.get(loopId)!
|
|
|
+ const spacerId = loopId + '_spacer'
|
|
|
+ exitTargets.forEach(targetId => {
|
|
|
+ // Route through spacer if it exists, to place target below the loop body
|
|
|
+ if (g.hasNode(spacerId)) {
|
|
|
+ g.setEdge(spacerId, targetId)
|
|
|
+ } else {
|
|
|
+ g.setEdge(loopId, targetId)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
dagre.layout(g)
|
|
|
|
|
|
const newPositions = new Map<string, { x: number; y: number }>()
|