Skip to content

Commit 3a4f130

Browse files
authored
improvement(subflow): remove all associated edges when moving a block into a subflow (#2145)
* improvement(subflow): remove all associated edges when moving a block into a subflow * ack PR comments
1 parent e80feee commit 3a4f130

File tree

1 file changed

+51
-13
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]

1 file changed

+51
-13
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,33 @@ const WorkflowContent = React.memo(() => {
472472
}
473473
}, [debouncedAutoLayout, undo, redo])
474474

475+
/**
476+
* Removes all edges connected to a block, skipping individual edge recording for undo/redo.
477+
* Used when moving nodes between containers where edges would violate boundary constraints.
478+
*/
479+
const removeEdgesForNode = useCallback(
480+
(blockId: string, edgesToRemove: Edge[]): void => {
481+
if (edgesToRemove.length === 0) return
482+
483+
// Skip individual edge recording - parent update will record as batch
484+
window.dispatchEvent(new CustomEvent('skip-edge-recording', { detail: { skip: true } }))
485+
486+
try {
487+
edgesToRemove.forEach((edge) => {
488+
removeEdge(edge.id)
489+
})
490+
491+
logger.debug('Removed edges for node', {
492+
blockId,
493+
edgeCount: edgesToRemove.length,
494+
})
495+
} finally {
496+
window.dispatchEvent(new CustomEvent('skip-edge-recording', { detail: { skip: false } }))
497+
}
498+
},
499+
[removeEdge]
500+
)
501+
475502
// Listen for explicit remove-from-subflow actions from ActionBar
476503
useEffect(() => {
477504
const handleRemoveFromSubflow = (event: Event) => {
@@ -490,18 +517,11 @@ const WorkflowContent = React.memo(() => {
490517
(e) => e.source === blockId || e.target === blockId
491518
)
492519

493-
// Set flag to skip individual edge recording for undo/redo
494-
window.dispatchEvent(new CustomEvent('skip-edge-recording', { detail: { skip: true } }))
495-
496-
// Remove edges first
497-
edgesToRemove.forEach((edge) => {
498-
removeEdge(edge.id)
499-
})
520+
// Remove edges using shared helper
521+
removeEdgesForNode(blockId, edgesToRemove)
500522

501-
// Then update parent relationship
523+
// Update parent relationship (null = remove from parent)
502524
updateNodeParent(blockId, null, edgesToRemove)
503-
504-
window.dispatchEvent(new CustomEvent('skip-edge-recording', { detail: { skip: false } }))
505525
} catch (err) {
506526
logger.error('Failed to remove from subflow', { err })
507527
}
@@ -510,7 +530,7 @@ const WorkflowContent = React.memo(() => {
510530
window.addEventListener('remove-from-subflow', handleRemoveFromSubflow as EventListener)
511531
return () =>
512532
window.removeEventListener('remove-from-subflow', handleRemoveFromSubflow as EventListener)
513-
}, [getNodes, updateNodeParent, removeEdge, edgesForDisplay])
533+
}, [blocks, edgesForDisplay, removeEdgesForNode, updateNodeParent])
514534

515535
// Handle drops
516536
const findClosestOutput = useCallback(
@@ -1881,6 +1901,21 @@ const WorkflowContent = React.memo(() => {
18811901

18821902
// Update the node's parent relationship
18831903
if (potentialParentId) {
1904+
// Remove existing edges before moving into container
1905+
const edgesToRemove = edgesForDisplay.filter(
1906+
(e) => e.source === node.id || e.target === node.id
1907+
)
1908+
1909+
if (edgesToRemove.length > 0) {
1910+
removeEdgesForNode(node.id, edgesToRemove)
1911+
1912+
logger.info('Removed edges when moving node into subflow', {
1913+
blockId: node.id,
1914+
targetParentId: potentialParentId,
1915+
edgeCount: edgesToRemove.length,
1916+
})
1917+
}
1918+
18841919
// Compute relative position BEFORE updating parent to avoid stale state
18851920
// Account for header (50px), left padding (16px), and top padding (16px)
18861921
const containerAbsPosBefore = getNodeAbsolutePosition(potentialParentId)
@@ -1954,8 +1989,9 @@ const WorkflowContent = React.memo(() => {
19541989
// Skip recording these edges separately since they're part of the parent update
19551990
window.dispatchEvent(new CustomEvent('skip-edge-recording', { detail: { skip: true } }))
19561991

1957-
// Moving to a new parent container - pass the edges that will be added
1958-
updateNodeParent(node.id, potentialParentId, edgesToAdd)
1992+
// Moving to a new parent container - pass both removed and added edges for undo/redo
1993+
const affectedEdges = [...edgesToRemove, ...edgesToAdd]
1994+
updateNodeParent(node.id, potentialParentId, affectedEdges)
19591995

19601996
// Now add the edges after parent update
19611997
edgesToAdd.forEach((edge) => addEdge(edge))
@@ -1976,6 +2012,8 @@ const WorkflowContent = React.memo(() => {
19762012
addEdge,
19772013
determineSourceHandle,
19782014
blocks,
2015+
edgesForDisplay,
2016+
removeEdgesForNode,
19792017
getNodeAbsolutePosition,
19802018
getDragStartPosition,
19812019
setDragStartPosition,

0 commit comments

Comments
 (0)