Skip to content

Commit fef1af5

Browse files
committed
fit paste in viewport bounds
1 parent 4267fe8 commit fef1af5

File tree

2 files changed

+70
-5
lines changed

2 files changed

+70
-5
lines changed

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

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,60 @@ const LazyOAuthRequiredModal = lazy(() =>
7777

7878
const logger = createLogger('Workflow')
7979

80+
const DEFAULT_PASTE_OFFSET = { x: 50, y: 50 }
81+
82+
/**
83+
* Calculates the offset to paste blocks at viewport center
84+
*/
85+
function calculatePasteOffset(
86+
clipboard: {
87+
blocks: Record<string, { position: { x: number; y: number }; type: string; height?: number }>
88+
} | null,
89+
screenToFlowPosition: (pos: { x: number; y: number }) => { x: number; y: number }
90+
): { x: number; y: number } {
91+
if (!clipboard) return DEFAULT_PASTE_OFFSET
92+
93+
const clipboardBlocks = Object.values(clipboard.blocks)
94+
if (clipboardBlocks.length === 0) return DEFAULT_PASTE_OFFSET
95+
96+
// Calculate bounding box using proper dimensions
97+
const minX = Math.min(...clipboardBlocks.map((b) => b.position.x))
98+
const maxX = Math.max(
99+
...clipboardBlocks.map((b) => {
100+
const width =
101+
b.type === 'loop' || b.type === 'parallel'
102+
? CONTAINER_DIMENSIONS.DEFAULT_WIDTH
103+
: BLOCK_DIMENSIONS.FIXED_WIDTH
104+
return b.position.x + width
105+
})
106+
)
107+
const minY = Math.min(...clipboardBlocks.map((b) => b.position.y))
108+
const maxY = Math.max(
109+
...clipboardBlocks.map((b) => {
110+
const height =
111+
b.type === 'loop' || b.type === 'parallel'
112+
? CONTAINER_DIMENSIONS.DEFAULT_HEIGHT
113+
: Math.max(b.height || BLOCK_DIMENSIONS.MIN_HEIGHT, BLOCK_DIMENSIONS.MIN_HEIGHT)
114+
return b.position.y + height
115+
})
116+
)
117+
const clipboardCenter = { x: (minX + maxX) / 2, y: (minY + maxY) / 2 }
118+
119+
const flowContainer = document.querySelector('.react-flow')
120+
if (!flowContainer) return DEFAULT_PASTE_OFFSET
121+
122+
const rect = flowContainer.getBoundingClientRect()
123+
const viewportCenter = screenToFlowPosition({
124+
x: rect.width / 2,
125+
y: rect.height / 2,
126+
})
127+
128+
return {
129+
x: viewportCenter.x - clipboardCenter.x,
130+
y: viewportCenter.y - clipboardCenter.y,
131+
}
132+
}
133+
80134
/** Custom node types for ReactFlow. */
81135
const nodeTypes: NodeTypes = {
82136
workflowBlock: WorkflowBlock,
@@ -155,6 +209,7 @@ const WorkflowContent = React.memo(() => {
155209
copyBlocks,
156210
preparePasteData,
157211
hasClipboard,
212+
clipboard,
158213
} = useWorkflowRegistry(
159214
useShallow((state) => ({
160215
workflows: state.workflows,
@@ -164,6 +219,7 @@ const WorkflowContent = React.memo(() => {
164219
copyBlocks: state.copyBlocks,
165220
preparePasteData: state.preparePasteData,
166221
hasClipboard: state.hasClipboard,
222+
clipboard: state.clipboard,
167223
}))
168224
)
169225

@@ -557,7 +613,11 @@ const WorkflowContent = React.memo(() => {
557613
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
558614
if (effectivePermissions.canEdit && hasClipboard()) {
559615
event.preventDefault()
560-
const pasteData = preparePasteData()
616+
617+
// Calculate offset to paste blocks at viewport center
618+
const pasteOffset = calculatePasteOffset(clipboard, screenToFlowPosition)
619+
620+
const pasteData = preparePasteData(pasteOffset)
561621
if (pasteData) {
562622
const pastedBlocks = Object.values(pasteData.blocks)
563623
const hasTriggerInPaste = pastedBlocks.some((block) =>
@@ -608,6 +668,8 @@ const WorkflowContent = React.memo(() => {
608668
blocks,
609669
addNotification,
610670
activeWorkflowId,
671+
clipboard,
672+
screenToFlowPosition,
611673
])
612674

613675
/**

apps/sim/stores/workflows/utils.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,14 +541,17 @@ export function regenerateBlockIds(
541541
const newNormalizedName = normalizeName(newName)
542542
nameMap.set(oldNormalizedName, newNormalizedName)
543543

544+
const isNested = !!block.data?.parentId
544545
const newBlock: BlockState = {
545546
...block,
546547
id: newId,
547548
name: newName,
548-
position: {
549-
x: block.position.x + positionOffset.x,
550-
y: block.position.y + positionOffset.y,
551-
},
549+
position: isNested
550+
? block.position
551+
: {
552+
x: block.position.x + positionOffset.x,
553+
y: block.position.y + positionOffset.y,
554+
},
552555
}
553556

554557
newBlocks[newId] = newBlock

0 commit comments

Comments
 (0)