diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 6117a9334..348a34ae4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -99,19 +99,33 @@ const logger = createLogger('Workflow') const DEFAULT_PASTE_OFFSET = { x: 50, y: 50 } /** - * Calculates the offset to paste blocks at viewport center + * Calculates the offset to paste blocks at viewport center, or simple offset for nested blocks */ function calculatePasteOffset( clipboard: { - blocks: Record + blocks: Record< + string, + { + position: { x: number; y: number } + type: string + height?: number + data?: { parentId?: string } + } + > } | null, - viewportCenter: { x: number; y: number } + viewportCenter: { x: number; y: number }, + existingBlocks: Record = {} ): { x: number; y: number } { if (!clipboard) return DEFAULT_PASTE_OFFSET const clipboardBlocks = Object.values(clipboard.blocks) if (clipboardBlocks.length === 0) return DEFAULT_PASTE_OFFSET + const allBlocksNested = clipboardBlocks.every( + (b) => b.data?.parentId && existingBlocks[b.data.parentId] + ) + if (allBlocksNested) return DEFAULT_PASTE_OFFSET + const minX = Math.min(...clipboardBlocks.map((b) => b.position.x)) const maxX = Math.max( ...clipboardBlocks.map((b) => { @@ -449,7 +463,6 @@ const WorkflowContent = React.memo(() => { /** Re-applies diff markers when blocks change after socket rehydration. */ const diffBlocksRef = useRef(blocks) useEffect(() => { - // Track if blocks actually changed (vs other deps triggering this effect) const blocksChanged = blocks !== diffBlocksRef.current diffBlocksRef.current = blocks @@ -1024,7 +1037,7 @@ const WorkflowContent = React.memo(() => { executePasteOperation( 'paste', - calculatePasteOffset(clipboard, getViewportCenter()), + calculatePasteOffset(clipboard, getViewportCenter(), blocks), targetContainer, flowPosition // Pass the click position so blocks are centered at where user right-clicked ) @@ -1036,6 +1049,7 @@ const WorkflowContent = React.memo(() => { screenToFlowPosition, contextMenuPosition, isPointInLoopNode, + blocks, ]) const handleContextDuplicate = useCallback(() => { @@ -1146,7 +1160,10 @@ const WorkflowContent = React.memo(() => { } else if ((event.ctrlKey || event.metaKey) && event.key === 'v') { if (effectivePermissions.canEdit && hasClipboard()) { event.preventDefault() - executePasteOperation('paste', calculatePasteOffset(clipboard, getViewportCenter())) + executePasteOperation( + 'paste', + calculatePasteOffset(clipboard, getViewportCenter(), blocks) + ) } } } @@ -1168,6 +1185,7 @@ const WorkflowContent = React.memo(() => { clipboard, getViewportCenter, executePasteOperation, + blocks, ]) /** diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index 06025b885..98048c7ab 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger' import type { Edge } from 'reactflow' import { create } from 'zustand' import { devtools } from 'zustand/middleware' +import { CONTAINER_DIMENSIONS } from '@/lib/workflows/blocks/block-dimensions' import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs' import { getBlock } from '@/blocks' import type { SubBlockConfig } from '@/blocks/types' @@ -446,7 +447,10 @@ export const useWorkflowStore = create()( // Clean up orphaned nodes - blocks whose parent was removed but weren't descendants // This can happen in edge cases (e.g., data inconsistency, external modifications) const remainingBlockIds = new Set(Object.keys(newBlocks)) - const CONTAINER_OFFSET = { x: 16, y: 50 + 16 } // leftPadding, headerHeight + topPadding + const CONTAINER_OFFSET = { + x: CONTAINER_DIMENSIONS.LEFT_PADDING, + y: CONTAINER_DIMENSIONS.HEADER_HEIGHT + CONTAINER_DIMENSIONS.TOP_PADDING, + } Object.entries(newBlocks).forEach(([blockId, block]) => { const parentId = block.data?.parentId