From 3e1d7e718ce8a5c2563d24ba3f96cff1c55aeab5 Mon Sep 17 00:00:00 2001 From: Adam Gough Date: Wed, 28 May 2025 13:15:36 -0700 Subject: [PATCH] fix: added preview for subgroup nodes, changed position of child blocks in state --- .../loop-node/components/loop-badges.tsx | 247 +++++++++------- .../w/[id]/components/loop-node/loop-node.tsx | 59 ++-- .../components/parallel-badges.tsx | 275 ++++++++++-------- .../parallel-node/parallel-node.tsx | 61 ++-- .../workflow-preview/workflow-preview.tsx | 162 +++++++---- 5 files changed, 472 insertions(+), 332 deletions(-) diff --git a/apps/sim/app/w/[id]/components/loop-node/components/loop-badges.tsx b/apps/sim/app/w/[id]/components/loop-node/components/loop-badges.tsx index 0bb160a90a..123a70a14c 100644 --- a/apps/sim/app/w/[id]/components/loop-node/components/loop-badges.tsx +++ b/apps/sim/app/w/[id]/components/loop-node/components/loop-badges.tsx @@ -21,6 +21,7 @@ interface LoopNodeData { loopType?: 'for' | 'forEach' count?: number collection?: string | any[] | Record + isPreview?: boolean executionState?: { currentIteration: number isExecuting: boolean @@ -35,6 +36,9 @@ interface LoopBadgesProps { } export function LoopBadges({ nodeId, data }: LoopBadgesProps) { + // Check if this is preview mode + const isPreview = data?.isPreview || false + // State const [loopType, setLoopType] = useState(data?.loopType || 'for') const [iterations, setIterations] = useState(data?.count || 5) @@ -50,6 +54,8 @@ export function LoopBadges({ nodeId, data }: LoopBadgesProps) { // Get store methods const updateNodeData = useCallback( (updates: Partial) => { + if (isPreview) return // Don't update in preview mode + useWorkflowStore.setState((state) => ({ blocks: { ...state.blocks, @@ -63,7 +69,7 @@ export function LoopBadges({ nodeId, data }: LoopBadgesProps) { }, })) }, - [nodeId] + [nodeId, isPreview] ) const updateLoopType = useWorkflowStore((state) => state.updateLoopType) @@ -94,27 +100,36 @@ export function LoopBadges({ nodeId, data }: LoopBadgesProps) { // Handle loop type change const handleLoopTypeChange = useCallback( (newType: 'for' | 'forEach') => { + if (isPreview) return // Don't allow changes in preview mode + setLoopType(newType) updateLoopType(nodeId, newType) setTypePopoverOpen(false) }, - [nodeId, updateLoopType] + [nodeId, updateLoopType, isPreview] ) // Handle iterations input change - const handleIterationsChange = useCallback((e: React.ChangeEvent) => { - const sanitizedValue = e.target.value.replace(/[^0-9]/g, '') - const numValue = Number.parseInt(sanitizedValue) + const handleIterationsChange = useCallback( + (e: React.ChangeEvent) => { + if (isPreview) return // Don't allow changes in preview mode - if (!Number.isNaN(numValue)) { - setInputValue(Math.min(100, numValue).toString()) - } else { - setInputValue(sanitizedValue) - } - }, []) + const sanitizedValue = e.target.value.replace(/[^0-9]/g, '') + const numValue = Number.parseInt(sanitizedValue) + + if (!Number.isNaN(numValue)) { + setInputValue(Math.min(100, numValue).toString()) + } else { + setInputValue(sanitizedValue) + } + }, + [isPreview] + ) // Handle iterations save const handleIterationsSave = useCallback(() => { + if (isPreview) return // Don't allow changes in preview mode + const value = Number.parseInt(inputValue) if (!Number.isNaN(value)) { @@ -126,11 +141,13 @@ export function LoopBadges({ nodeId, data }: LoopBadgesProps) { setInputValue(iterations.toString()) } setConfigPopoverOpen(false) - }, [inputValue, iterations, nodeId, updateLoopCount]) + }, [inputValue, iterations, nodeId, updateLoopCount, isPreview]) // Handle editor change with tag dropdown support const handleEditorChange = useCallback( (value: string) => { + if (isPreview) return // Don't allow changes in preview mode + setEditorValue(value) updateLoopCollection(nodeId, value) @@ -146,12 +163,14 @@ export function LoopBadges({ nodeId, data }: LoopBadgesProps) { setShowTagDropdown(triggerCheck.show) } }, - [nodeId, updateLoopCollection] + [nodeId, updateLoopCollection, isPreview] ) // Handle tag selection const handleTagSelect = useCallback( (newValue: string) => { + if (isPreview) return // Don't allow changes in preview mode + setEditorValue(newValue) updateLoopCollection(nodeId, newValue) setShowTagDropdown(false) @@ -164,137 +183,149 @@ export function LoopBadges({ nodeId, data }: LoopBadgesProps) { } }, 0) }, - [nodeId, updateLoopCollection] + [nodeId, updateLoopCollection, isPreview] ) return (
{/* Loop Type Badge */} - + e.stopPropagation()}> {loopType === 'for' ? 'For Loop' : 'For Each'} - + {!isPreview && } - e.stopPropagation()}> -
-
Loop Type
-
-
handleLoopTypeChange('for')} - > - For Loop -
-
handleLoopTypeChange('forEach')} - > - For Each + {!isPreview && ( + e.stopPropagation()}> +
+
Loop Type
+
+
handleLoopTypeChange('for')} + > + For Loop +
+
handleLoopTypeChange('forEach')} + > + For Each +
-
- + + )} {/* Iterations/Collection Badge */} - + e.stopPropagation()}> {loopType === 'for' ? `Iterations: ${iterations}` : 'Items'} - + {!isPreview && } - e.stopPropagation()} - > -
-
- {loopType === 'for' ? 'Loop Iterations' : 'Collection Items'} -
- - {loopType === 'for' ? ( - // Number input for 'for' loops -
- e.key === 'Enter' && handleIterationsSave()} - className='h-8 text-sm' - autoFocus - /> + {!isPreview && ( + e.stopPropagation()} + > +
+
+ {loopType === 'for' ? 'Loop Iterations' : 'Collection Items'}
- ) : ( - // Code editor for 'forEach' loops -
-
- {editorValue === '' && ( -
- ["item1", "item2", "item3"] -
+ + {loopType === 'for' ? ( + // Number input for 'for' loops +
+ e.key === 'Enter' && handleIterationsSave()} + className='h-8 text-sm' + autoFocus + /> +
+ ) : ( + // Code editor for 'forEach' loops +
+
+ {editorValue === '' && ( +
+ ["item1", "item2", "item3"] +
+ )} + highlight(code, languages.javascript, 'javascript')} + padding={0} + style={{ + fontFamily: 'monospace', + lineHeight: '21px', + }} + className='w-full focus:outline-none' + textareaClassName='focus:outline-none focus:ring-0 bg-transparent resize-none w-full overflow-hidden whitespace-pre-wrap' + /> +
+
+ Array or object to iterate over. Type "{'<'}" to reference other blocks. +
+ {showTagDropdown && ( + setShowTagDropdown(false)} + /> )} - highlight(code, languages.javascript, 'javascript')} - padding={0} - style={{ - fontFamily: 'monospace', - lineHeight: '21px', - }} - className='w-full focus:outline-none' - textareaClassName='focus:outline-none focus:ring-0 bg-transparent resize-none w-full overflow-hidden whitespace-pre-wrap' - />
-
- Array or object to iterate over. Type "{'<'}" to reference other blocks. -
- {showTagDropdown && ( - setShowTagDropdown(false)} - /> - )} -
- )} + )} - {loopType === 'for' && ( -
- Enter a number between 1 and 100 -
- )} -
- + {loopType === 'for' && ( +
+ Enter a number between 1 and 100 +
+ )} +
+
+ )}
) diff --git a/apps/sim/app/w/[id]/components/loop-node/loop-node.tsx b/apps/sim/app/w/[id]/components/loop-node/loop-node.tsx index 9190c1a33f..ae0bb86741 100644 --- a/apps/sim/app/w/[id]/components/loop-node/loop-node.tsx +++ b/apps/sim/app/w/[id]/components/loop-node/loop-node.tsx @@ -72,6 +72,9 @@ export const LoopNodeComponent = memo(({ data, selected, id }: NodeProps) => { const removeBlock = useWorkflowStore((state) => state.removeBlock) const blockRef = useRef(null) + // Check if this is preview mode + const isPreview = data?.isPreview || false + // Determine nesting level by counting parents const nestingLevel = useMemo(() => { let level = 0 @@ -91,7 +94,7 @@ export const LoopNodeComponent = memo(({ data, selected, id }: NodeProps) => { const getNestedStyles = () => { // Base styles const styles: Record = { - backgroundColor: data?.state === 'valid' ? 'rgba(34,197,94,0.05)' : 'transparent', + backgroundColor: 'transparent', } // Apply nested styles @@ -118,7 +121,7 @@ export const LoopNodeComponent = memo(({ data, selected, id }: NodeProps) => { ' relative cursor-default select-none', 'transition-block-bg transition-ring', 'z-[20]', - data?.state === 'valid' && 'bg-[rgba(34,197,94,0.05)] ring-2 ring-[#2FB3FF]', + data?.state === 'valid', nestingLevel > 0 && `border border-[0.5px] ${nestingLevel % 2 === 0 ? 'border-slate-300/60' : 'border-slate-400/60'}` )} @@ -128,23 +131,27 @@ export const LoopNodeComponent = memo(({ data, selected, id }: NodeProps) => { position: 'relative', overflow: 'visible', ...nestedStyles, - pointerEvents: 'all', + pointerEvents: isPreview ? 'none' : 'all', }} data-node-id={id} data-type='loopNode' data-nesting-level={nestingLevel} > {/* Critical drag handle that controls only the loop node movement */} -
+ {!isPreview && ( +
+ )} {/* Custom visible resize handle */} -
+ {!isPreview && ( +
+ )} {/* Child nodes container - Enable pointer events to allow dragging of children */}
{ style={{ position: 'relative', minHeight: '100%', - pointerEvents: 'auto', + pointerEvents: isPreview ? 'none' : 'auto', }} > {/* Delete button - styled like in action-bar.tsx */} - + {!isPreview && ( + + )} {/* Loop Start Block */}
+ isPreview?: boolean executionState?: { currentExecution: number isExecuting: boolean @@ -35,6 +36,9 @@ interface ParallelBadgesProps { } export function ParallelBadges({ nodeId, data }: ParallelBadgesProps) { + // Check if this is preview mode + const isPreview = data?.isPreview || false + // State const [parallelType, setParallelType] = useState<'count' | 'collection'>( data?.parallelType || 'collection' @@ -56,6 +60,8 @@ export function ParallelBadges({ nodeId, data }: ParallelBadgesProps) { // Update node data to include parallel type const updateNodeData = useCallback( (updates: Partial) => { + if (isPreview) return // Don't update in preview mode + useWorkflowStore.setState((state) => ({ blocks: { ...state.blocks, @@ -69,7 +75,7 @@ export function ParallelBadges({ nodeId, data }: ParallelBadgesProps) { }, })) }, - [nodeId] + [nodeId, isPreview] ) // Initialize state from data when it changes @@ -94,6 +100,8 @@ export function ParallelBadges({ nodeId, data }: ParallelBadgesProps) { // Handle parallel type change const handleParallelTypeChange = useCallback( (newType: 'count' | 'collection') => { + if (isPreview) return // Don't allow changes in preview mode + setParallelType(newType) updateNodeData({ parallelType: newType }) @@ -108,23 +116,38 @@ export function ParallelBadges({ nodeId, data }: ParallelBadgesProps) { setTypePopoverOpen(false) }, - [nodeId, iterations, editorValue, updateNodeData, updateParallelCount, updateParallelCollection] + [ + nodeId, + iterations, + editorValue, + updateNodeData, + updateParallelCount, + updateParallelCollection, + isPreview, + ] ) // Handle iterations input change - const handleIterationsChange = useCallback((e: React.ChangeEvent) => { - const sanitizedValue = e.target.value.replace(/[^0-9]/g, '') - const numValue = Number.parseInt(sanitizedValue) + const handleIterationsChange = useCallback( + (e: React.ChangeEvent) => { + if (isPreview) return // Don't allow changes in preview mode - if (!Number.isNaN(numValue)) { - setInputValue(Math.min(20, numValue).toString()) - } else { - setInputValue(sanitizedValue) - } - }, []) + const sanitizedValue = e.target.value.replace(/[^0-9]/g, '') + const numValue = Number.parseInt(sanitizedValue) + + if (!Number.isNaN(numValue)) { + setInputValue(Math.min(20, numValue).toString()) + } else { + setInputValue(sanitizedValue) + } + }, + [isPreview] + ) // Handle iterations save const handleIterationsSave = useCallback(() => { + if (isPreview) return // Don't allow changes in preview mode + const value = Number.parseInt(inputValue) if (!Number.isNaN(value)) { @@ -136,11 +159,13 @@ export function ParallelBadges({ nodeId, data }: ParallelBadgesProps) { setInputValue(iterations.toString()) } setConfigPopoverOpen(false) - }, [inputValue, iterations, nodeId, updateParallelCount]) + }, [inputValue, iterations, nodeId, updateParallelCount, isPreview]) // Handle editor change and check for tag trigger const handleEditorChange = useCallback( (value: string) => { + if (isPreview) return // Don't allow changes in preview mode + setEditorValue(value) updateParallelCollection(nodeId, value) @@ -156,12 +181,14 @@ export function ParallelBadges({ nodeId, data }: ParallelBadgesProps) { setShowTagDropdown(tagTrigger.show) } }, - [nodeId, updateParallelCollection] + [nodeId, updateParallelCollection, isPreview] ) // Handle tag selection const handleTagSelect = useCallback( (newValue: string) => { + if (isPreview) return // Don't allow changes in preview mode + setEditorValue(newValue) updateParallelCollection(nodeId, newValue) setShowTagDropdown(false) @@ -174,7 +201,7 @@ export function ParallelBadges({ nodeId, data }: ParallelBadgesProps) { } }, 0) }, - [nodeId, updateParallelCollection] + [nodeId, updateParallelCollection, isPreview] ) // Handle key events @@ -187,141 +214,153 @@ export function ParallelBadges({ nodeId, data }: ParallelBadgesProps) { return (
{/* Parallel Type Badge */} - + e.stopPropagation()}> {parallelType === 'count' ? 'Parallel Count' : 'Parallel Each'} - + {!isPreview && } - e.stopPropagation()}> -
-
Parallel Type
-
-
handleParallelTypeChange('count')} - > - Parallel Count -
-
handleParallelTypeChange('collection')} - > - Parallel Each + {!isPreview && ( + e.stopPropagation()}> +
+
Parallel Type
+
+
handleParallelTypeChange('count')} + > + Parallel Count +
+
handleParallelTypeChange('collection')} + > + Parallel Each +
-
- + + )} {/* Iterations/Collection Badge */} - + e.stopPropagation()}> {parallelType === 'count' ? `Iterations: ${iterations}` : 'Items'} - + {!isPreview && } - e.stopPropagation()} - onKeyDown={handleKeyDown} - > -
-
- {parallelType === 'count' ? 'Parallel Iterations' : 'Parallel Items'} -
- - {parallelType === 'count' ? ( - // Number input for count-based parallel -
- e.key === 'Enter' && handleIterationsSave()} - className='h-8 text-sm' - autoFocus - /> + {!isPreview && ( + e.stopPropagation()} + onKeyDown={handleKeyDown} + > +
+
+ {parallelType === 'count' ? 'Parallel Iterations' : 'Parallel Items'}
- ) : ( - // Code editor for collection-based parallel -
-
- {editorValue === '' && ( -
- ['item1', 'item2', 'item3'] -
+ + {parallelType === 'count' ? ( + // Number input for count-based parallel +
+ e.key === 'Enter' && handleIterationsSave()} + className='h-8 text-sm' + autoFocus + /> +
+ ) : ( + // Code editor for collection-based parallel +
+
+ {editorValue === '' && ( +
+ ['item1', 'item2', 'item3'] +
+ )} + highlight(code, languages.javascript, 'javascript')} + padding={0} + style={{ + fontFamily: 'monospace', + lineHeight: '21px', + }} + className='w-full focus:outline-none' + textareaClassName='focus:outline-none focus:ring-0 bg-transparent resize-none w-full overflow-hidden whitespace-pre-wrap' + onKeyDown={(e) => { + if (e.key === 'Escape') { + setShowTagDropdown(false) + } + }} + /> +
+
+ Array or object to use for parallel execution. Type "{'<'}" to reference other + blocks. +
+ {showTagDropdown && ( + setShowTagDropdown(false)} + /> )} - highlight(code, languages.javascript, 'javascript')} - padding={0} - style={{ - fontFamily: 'monospace', - lineHeight: '21px', - }} - className='w-full focus:outline-none' - textareaClassName='focus:outline-none focus:ring-0 bg-transparent resize-none w-full overflow-hidden whitespace-pre-wrap' - onKeyDown={(e) => { - if (e.key === 'Escape') { - setShowTagDropdown(false) - } - }} - />
-
- Array or object to use for parallel execution. Type "{'<'}" to reference other - blocks. -
- {showTagDropdown && ( - setShowTagDropdown(false)} - /> - )} -
- )} + )} - {parallelType === 'count' && ( -
- Enter a number between 1 and 20 -
- )} -
- + {parallelType === 'count' && ( +
+ Enter a number between 1 and 20 +
+ )} +
+
+ )}
) diff --git a/apps/sim/app/w/[id]/components/parallel-node/parallel-node.tsx b/apps/sim/app/w/[id]/components/parallel-node/parallel-node.tsx index b1fd043867..d5b1cbd4e9 100644 --- a/apps/sim/app/w/[id]/components/parallel-node/parallel-node.tsx +++ b/apps/sim/app/w/[id]/components/parallel-node/parallel-node.tsx @@ -88,6 +88,9 @@ export const ParallelNodeComponent = memo(({ data, selected, id }: NodeProps) => const { getNodes } = useReactFlow() const blockRef = useRef(null) + // Check if this is preview mode + const isPreview = data?.isPreview || false + // Determine nesting level by counting parents const nestingLevel = useMemo(() => { const maxDepth = 100 // Prevent infinite loops @@ -108,7 +111,7 @@ export const ParallelNodeComponent = memo(({ data, selected, id }: NodeProps) => const getNestedStyles = () => { // Base styles const styles: Record = { - backgroundColor: data?.state === 'valid' ? 'rgba(139, 195, 74, 0.05)' : 'transparent', + backgroundColor: 'transparent', } // Apply nested styles @@ -135,7 +138,7 @@ export const ParallelNodeComponent = memo(({ data, selected, id }: NodeProps) => 'relative cursor-default select-none', 'transition-block-bg transition-ring', 'z-[20]', - data?.state === 'valid' && 'bg-[rgba(139,195,74,0.05)] ring-2 ring-[#8BC34A]', + data?.state === 'valid', nestingLevel > 0 && `border border-[0.5px] ${nestingLevel % 2 === 0 ? 'border-slate-300/60' : 'border-slate-400/60'}` )} @@ -145,23 +148,27 @@ export const ParallelNodeComponent = memo(({ data, selected, id }: NodeProps) => position: 'relative', overflow: 'visible', ...nestedStyles, - pointerEvents: 'all', + pointerEvents: isPreview ? 'none' : 'all', }} data-node-id={id} data-type='parallelNode' data-nesting-level={nestingLevel} > {/* Critical drag handle that controls only the parallel node movement */} -
+ {!isPreview && ( +
+ )} {/* Custom visible resize handle */} -
+ {!isPreview && ( +
+ )} {/* Child nodes container - Set pointerEvents to allow dragging of children */}
style={{ position: 'relative', minHeight: '100%', - pointerEvents: 'auto', + pointerEvents: isPreview ? 'none' : 'auto', }} > {/* Delete button - styled like in action-bar.tsx */} - + {!isPreview && ( + + )} {/* Parallel Start Block */}
+ ): { x: number; y: number } => { + // If no parent, use the block's position as-is + if (!block.data?.parentId) { + return block.position + } + + // Find the parent block + const parentBlock = blocks[block.data.parentId] + if (!parentBlock) { + logger.warn(`Parent block not found for child block: ${block.id}`) + return block.position + } + + // Recursively calculate parent's absolute position (for nested containers) + const parentAbsolutePosition = calculateAbsolutePosition(parentBlock, blocks) + + // Add parent's absolute position to child's relative position + return { + x: parentAbsolutePosition.x + block.position.x, + y: parentAbsolutePosition.y + block.position.y, + } + } + // Transform blocks and loops into ReactFlow nodes const nodes: Node[] = useMemo(() => { const nodeArray: Node[] = [] - // First, get all blocks with parent-child relationships - const blocksWithParents: Record = {} - const topLevelBlocks: Record = {} - - // Categorize blocks as top-level or child blocks - Object.entries(workflowState.blocks).forEach(([blockId, block]) => { - if (block.data?.parentId) { - // This is a child block - blocksWithParents[blockId] = block - } else { - // This is a top-level block - topLevelBlocks[blockId] = block - } - }) - // Add block nodes using the same approach as workflow.tsx Object.entries(workflowState.blocks).forEach(([blockId, block]) => { if (!block || !block.type) { @@ -118,6 +131,49 @@ export function WorkflowPreview({ return } + // Calculate absolute position for proper preview positioning + const absolutePosition = calculateAbsolutePosition(block, workflowState.blocks) + + // Handle container nodes (loop and parallel) differently + if (block.type === 'loop') { + nodeArray.push({ + id: block.id, + type: 'loopNode', + position: absolutePosition, + parentId: block.data?.parentId, + extent: block.data?.extent || undefined, + draggable: false, + data: { + ...block.data, + width: block.data?.width || 500, + height: block.data?.height || 300, + state: 'valid', + isPreview: true, + }, + }) + return + } + + if (block.type === 'parallel') { + nodeArray.push({ + id: block.id, + type: 'parallelNode', + position: absolutePosition, + parentId: block.data?.parentId, + extent: block.data?.extent || undefined, + draggable: false, + data: { + ...block.data, + width: block.data?.width || 500, + height: block.data?.height || 300, + state: 'valid', + isPreview: true, + }, + }) + return + } + + // Handle regular blocks const blockConfig = getBlock(block.type) if (!blockConfig) { logger.error(`No configuration found for block type: ${block.type}`, { blockId }) @@ -130,23 +186,23 @@ export function WorkflowPreview({ nodeArray.push({ id: blockId, type: 'workflowBlock', - position: block.position, + position: absolutePosition, draggable: false, data: { type: block.type, - config: blockConfig || (block.type === 'loop' ? LoopTool : null), + config: blockConfig, name: block.name, blockState: block, - isReadOnly: true, // Set read-only mode for preview - isPreview: true, // Indicate this is a preview - subBlockValues: subBlocksClone, // Use the deep clone to avoid reference issues + isReadOnly: true, + isPreview: true, + subBlockValues: subBlocksClone, }, }) - // Add children of this block if it's a loop + // Add children of this block if it's a loop (for nested blocks) if (block.type === 'loop') { // Find all children of this loop - const childBlocks = Object.entries(blocksWithParents).filter( + const childBlocks = Object.entries(workflowState.blocks).filter( ([_, childBlock]) => childBlock.data?.parentId === blockId ) @@ -154,39 +210,35 @@ export function WorkflowPreview({ childBlocks.forEach(([childId, childBlock]) => { const childConfig = getBlock(childBlock.type) - nodeArray.push({ - id: childId, - type: 'workflowBlock', - // Position child blocks relative to the parent - position: { - x: block.position.x + 50, // Offset children to the right - y: block.position.y + (childBlock.position?.y || 100), // Preserve vertical positioning - }, - data: { - type: childBlock.type, - config: childConfig, - name: childBlock.name, - blockState: childBlock, - showSubBlocks, - isChild: true, - parentId: blockId, - }, - draggable: false, - }) + if (childConfig) { + nodeArray.push({ + id: childId, + type: 'workflowBlock', + // Position child blocks relative to the parent + position: { + x: block.position.x + 50, // Offset children to the right + y: block.position.y + (childBlock.position?.y || 100), // Preserve vertical positioning + }, + data: { + type: childBlock.type, + config: childConfig, + name: childBlock.name, + blockState: childBlock, + showSubBlocks, + isChild: true, + parentId: blockId, + isReadOnly: true, + isPreview: true, + }, + draggable: false, + }) + } }) } }) return nodeArray - }, [ - blocksStructure, - loopsStructure, - parallelsStructure, - showSubBlocks, - workflowState.blocks, - workflowState.loops, - workflowState.parallels, - ]) + }, [blocksStructure, loopsStructure, parallelsStructure, showSubBlocks, workflowState.blocks]) // Transform edges const edges: Edge[] = useMemo(() => { @@ -198,7 +250,7 @@ export function WorkflowPreview({ targetHandle: edge.targetHandle, type: 'workflowEdge', })) - }, [edgesStructure, workflowState.edges, workflowState.parallels]) + }, [edgesStructure, workflowState.edges]) return (