mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-28 00:08:21 -05:00
fix(workflow): preserve parent and position when duplicating/pasting nested blocks
Three related fixes for blocks inside containers (loop/parallel): 1. regenerateBlockIds now preserves parentId when the parent exists in the current workflow, not just when it's in the copy set. This keeps duplicated blocks inside their container. 2. calculatePasteOffset now uses simple offset for nested blocks instead of viewport-center calculation. Since nested blocks use relative positioning, the viewport-center offset would place them incorrectly. 3. Use CONTAINER_DIMENSIONS constants instead of hardcoded magic numbers in orphan cleanup position calculation.
This commit is contained in:
@@ -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<string, { position: { x: number; y: number }; type: string; height?: number }>
|
||||
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<string, { id: string }> = {}
|
||||
): { 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,
|
||||
])
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<WorkflowStore>()(
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user