@@ -2,107 +2,15 @@ import { useCallback } from 'react'
22import { createLogger } from '@sim/logger'
33import { useReactFlow } from 'reactflow'
44import { BLOCK_DIMENSIONS , CONTAINER_DIMENSIONS } from '@/lib/workflows/blocks/block-dimensions'
5- import { getBlock } from '@/blocks/registry'
5+ import {
6+ calculateContainerDimensions ,
7+ clampPositionToContainer ,
8+ estimateBlockDimensions ,
9+ } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/node-position-utils'
610import { useWorkflowStore } from '@/stores/workflows/workflow/store'
711
812const logger = createLogger ( 'NodeUtilities' )
913
10- /**
11- * Estimates block dimensions based on block type.
12- * Uses subblock count to estimate height for blocks that haven't been measured yet.
13- *
14- * @param blockType - The type of block (e.g., 'condition', 'agent')
15- * @returns Estimated width and height for the block
16- */
17- export function estimateBlockDimensions ( blockType : string ) : { width : number ; height : number } {
18- const blockConfig = getBlock ( blockType )
19- const subBlockCount = blockConfig ?. subBlocks ?. length ?? 3
20- // Many subblocks are conditionally rendered (advanced mode, provider-specific, etc.)
21- // Use roughly half the config count as a reasonable estimate, capped between 3-7 rows
22- const estimatedRows = Math . max ( 3 , Math . min ( Math . ceil ( subBlockCount / 2 ) , 7 ) )
23- const hasErrorRow = blockType !== 'starter' && blockType !== 'response' ? 1 : 0
24-
25- const height =
26- BLOCK_DIMENSIONS . HEADER_HEIGHT +
27- BLOCK_DIMENSIONS . WORKFLOW_CONTENT_PADDING +
28- ( estimatedRows + hasErrorRow ) * BLOCK_DIMENSIONS . WORKFLOW_ROW_HEIGHT
29-
30- return {
31- width : BLOCK_DIMENSIONS . FIXED_WIDTH ,
32- height : Math . max ( height , BLOCK_DIMENSIONS . MIN_HEIGHT ) ,
33- }
34- }
35-
36- /**
37- * Clamps a position to keep a block fully inside a container's content area.
38- * Content area starts after the header and padding, and ends before the right/bottom padding.
39- *
40- * @param position - Raw position relative to container origin
41- * @param containerDimensions - Container width and height
42- * @param blockDimensions - Block width and height
43- * @returns Clamped position that keeps block inside content area
44- */
45- export function clampPositionToContainer (
46- position : { x : number ; y : number } ,
47- containerDimensions : { width : number ; height : number } ,
48- blockDimensions : { width : number ; height : number }
49- ) : { x : number ; y : number } {
50- const { width : containerWidth , height : containerHeight } = containerDimensions
51- const { width : blockWidth , height : blockHeight } = blockDimensions
52-
53- // Content area bounds (where blocks can be placed)
54- const minX = CONTAINER_DIMENSIONS . LEFT_PADDING
55- const minY = CONTAINER_DIMENSIONS . HEADER_HEIGHT + CONTAINER_DIMENSIONS . TOP_PADDING
56- const maxX = containerWidth - CONTAINER_DIMENSIONS . RIGHT_PADDING - blockWidth
57- const maxY = containerHeight - CONTAINER_DIMENSIONS . BOTTOM_PADDING - blockHeight
58-
59- return {
60- x : Math . max ( minX , Math . min ( position . x , Math . max ( minX , maxX ) ) ) ,
61- y : Math . max ( minY , Math . min ( position . y , Math . max ( minY , maxY ) ) ) ,
62- }
63- }
64-
65- /**
66- * Calculates container dimensions based on child block positions.
67- * Single source of truth for container sizing - ensures consistency between
68- * live drag updates and final dimension calculations.
69- *
70- * @param childPositions - Array of child positions with their dimensions
71- * @returns Calculated width and height for the container
72- */
73- export function calculateContainerDimensions (
74- childPositions : Array < { x : number ; y : number ; width : number ; height : number } >
75- ) : { width : number ; height : number } {
76- if ( childPositions . length === 0 ) {
77- return {
78- width : CONTAINER_DIMENSIONS . DEFAULT_WIDTH ,
79- height : CONTAINER_DIMENSIONS . DEFAULT_HEIGHT ,
80- }
81- }
82-
83- let maxRight = 0
84- let maxBottom = 0
85-
86- for ( const child of childPositions ) {
87- maxRight = Math . max ( maxRight , child . x + child . width )
88- maxBottom = Math . max ( maxBottom , child . y + child . height )
89- }
90-
91- const width = Math . max (
92- CONTAINER_DIMENSIONS . DEFAULT_WIDTH ,
93- CONTAINER_DIMENSIONS . LEFT_PADDING + maxRight + CONTAINER_DIMENSIONS . RIGHT_PADDING
94- )
95- const height = Math . max (
96- CONTAINER_DIMENSIONS . DEFAULT_HEIGHT ,
97- CONTAINER_DIMENSIONS . HEADER_HEIGHT +
98- CONTAINER_DIMENSIONS . TOP_PADDING +
99- maxBottom +
100- CONTAINER_DIMENSIONS . BOTTOM_PADDING
101- )
102-
103- return { width, height }
104- }
105-
10614/**
10715 * Hook providing utilities for node position, hierarchy, and dimension calculations
10816 */
@@ -138,15 +46,13 @@ export function useNodeUtilities(blocks: Record<string, any>) {
13846 }
13947 }
14048
141- // Prefer deterministic height published by the block component; fallback to estimate
14249 if ( block . height ) {
14350 return {
14451 width : BLOCK_DIMENSIONS . FIXED_WIDTH ,
14552 height : Math . max ( block . height , BLOCK_DIMENSIONS . MIN_HEIGHT ) ,
14653 }
14754 }
14855
149- // Use shared estimation utility for blocks without measured height
15056 return estimateBlockDimensions ( block . type )
15157 } ,
15258 [ blocks , isContainerType ]
@@ -230,8 +136,6 @@ export function useNodeUtilities(blocks: Record<string, any>) {
230136
231137 const parentPos = getNodeAbsolutePosition ( parentId )
232138
233- // Child positions are stored relative to the content area (after header and padding)
234- // Add these offsets when calculating absolute position
235139 const headerHeight = 50
236140 const leftPadding = 16
237141 const topPadding = 16
@@ -314,7 +218,6 @@ export function useNodeUtilities(blocks: Record<string, any>) {
314218 } )
315219 . map ( ( n ) => ( {
316220 loopId : n . id ,
317- // Return absolute position so callers can compute relative placement correctly
318221 loopPosition : getNodeAbsolutePosition ( n . id ) ,
319222 dimensions : {
320223 width : n . data ?. width || CONTAINER_DIMENSIONS . DEFAULT_WIDTH ,
@@ -449,7 +352,6 @@ export function useNodeUtilities(blocks: Record<string, any>) {
449352 return absPos
450353 }
451354
452- // Use known defaults per node type without type casting
453355 const isSubflow = node . type === 'subflowNode'
454356 const width = isSubflow
455357 ? typeof node . data ?. width === 'number'
0 commit comments