Skip to content

Commit 0429636

Browse files
committed
Consolidate into hook
1 parent 03b9dbb commit 0429636

4 files changed

Lines changed: 107 additions & 89 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
1-
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
1+
import { memo, useCallback, useMemo } from 'react'
22
import ReactMarkdown from 'react-markdown'
3-
import { type NodeProps, useUpdateNodeInternals } from 'reactflow'
3+
import type { NodeProps } from 'reactflow'
44
import remarkGfm from 'remark-gfm'
55
import { cn } from '@/lib/utils'
66
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
77
import { usePanelEditorStore } from '@/stores/panel-new/editor/store'
88
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
99
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
10-
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1110
import { useCurrentWorkflow } from '../../hooks'
11+
import { useBlockDimensions } from '../../hooks/use-block-dimensions'
1212
import { ActionBar } from '../workflow-block/components'
1313
import { useBlockState } from '../workflow-block/hooks'
1414
import type { WorkflowBlockProps } from '../workflow-block/types'
1515

1616
interface NoteBlockNodeData extends WorkflowBlockProps {}
1717

18-
const NOTE_MIN_WIDTH = 220
19-
const NOTE_MIN_HEIGHT = 140
20-
2118
/**
2219
* Extract string value from subblock value object or primitive
2320
*/
@@ -94,10 +91,6 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string }
9491

9592
export const NoteBlock = memo(function NoteBlock({ id, data }: NodeProps<NoteBlockNodeData>) {
9693
const { type, config, name } = data
97-
const containerRef = useRef<HTMLDivElement>(null)
98-
const sizeRef = useRef<{ width: number; height: number } | null>(null)
99-
const updateNodeInternals = useUpdateNodeInternals()
100-
const updateBlockLayoutMetrics = useWorkflowStore((state) => state.updateBlockLayoutMetrics)
10194

10295
const setCurrentBlockId = usePanelEditorStore((state) => state.setCurrentBlockId)
10396
const currentBlockId = usePanelEditorStore((state) => state.currentBlockId)
@@ -146,28 +139,27 @@ export const NoteBlock = memo(function NoteBlock({ id, data }: NodeProps<NoteBlo
146139

147140
const userPermissions = useUserPermissionsContext()
148141

149-
useEffect(() => {
150-
const element = containerRef.current
151-
if (!element) return
152-
153-
const observer = new ResizeObserver((entries) => {
154-
const entry = entries[0]
155-
if (!entry) return
156-
157-
const width = Math.max(Math.round(entry.contentRect.width), NOTE_MIN_WIDTH)
158-
const height = Math.max(Math.round(entry.contentRect.height), NOTE_MIN_HEIGHT)
159-
160-
const previous = sizeRef.current
161-
if (!previous || previous.width !== width || previous.height !== height) {
162-
sizeRef.current = { width, height }
163-
updateBlockLayoutMetrics(id, { width, height })
164-
updateNodeInternals(id)
165-
}
166-
})
167-
168-
observer.observe(element)
169-
return () => observer.disconnect()
170-
}, [id, updateBlockLayoutMetrics, updateNodeInternals])
142+
/**
143+
* Calculate deterministic dimensions based on content structure.
144+
* Uses fixed width and computed height to avoid ResizeObserver jitter.
145+
*/
146+
useBlockDimensions({
147+
blockId: id,
148+
calculateDimensions: () => {
149+
const FIXED_WIDTH = 250
150+
const HEADER_HEIGHT = 40
151+
const CONTENT_PADDING = 14
152+
const MIN_CONTENT_HEIGHT = 20
153+
const BASE_CONTENT_HEIGHT = 60
154+
155+
// Use minimum height for empty notes, base height for content
156+
const contentHeight = isEmpty ? MIN_CONTENT_HEIGHT : BASE_CONTENT_HEIGHT
157+
const calculatedHeight = HEADER_HEIGHT + CONTENT_PADDING + contentHeight
158+
159+
return { width: FIXED_WIDTH, height: calculatedHeight }
160+
},
161+
dependencies: [isEmpty],
162+
})
171163

172164
const hasRing =
173165
isActive || isFocused || diffStatus === 'new' || diffStatus === 'edited' || isDeletedBlock
@@ -183,7 +175,6 @@ export const NoteBlock = memo(function NoteBlock({ id, data }: NodeProps<NoteBlo
183175
return (
184176
<div className='group relative'>
185177
<div
186-
ref={containerRef}
187178
className={cn(
188179
'relative z-[20] w-[250px] cursor-default select-none rounded-[8px] bg-[#232323]'
189180
)}

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

Lines changed: 37 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
import { useParams } from 'next/navigation'
3-
import { Handle, type NodeProps, Position, useUpdateNodeInternals } from 'reactflow'
3+
import { Handle, type NodeProps, Position } from 'reactflow'
44
import { Badge } from '@/components/emcn/components/badge/badge'
55
import { Tooltip } from '@/components/emcn/components/tooltip/tooltip'
66
import { getEnv, isTruthy } from '@/lib/env'
@@ -14,8 +14,7 @@ import { useDisplayName } from '@/hooks/use-display-name'
1414
import { usePanelEditorStore } from '@/stores/panel-new/editor/store'
1515
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
1616
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
17-
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
18-
import { useCurrentWorkflow } from '../../hooks'
17+
import { useBlockDimensions, useCurrentWorkflow } from '../../hooks'
1918
import { ActionBar, Connections } from './components'
2019
import {
2120
useBlockProperties,
@@ -267,7 +266,6 @@ export const WorkflowBlock = memo(function WorkflowBlock({
267266
const { type, config, name, isPending } = data
268267

269268
const contentRef = useRef<HTMLDivElement>(null)
270-
const updateNodeInternals = useUpdateNodeInternals()
271269

272270
const params = useParams()
273271
const currentWorkflowId = params.workflowId as string
@@ -368,7 +366,6 @@ export const WorkflowBlock = memo(function WorkflowBlock({
368366
}
369367
}, [id, collaborativeSetSubblockValue])
370368

371-
const updateBlockLayoutMetrics = useWorkflowStore((state) => state.updateBlockLayoutMetrics)
372369
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
373370
const setCurrentBlockId = usePanelEditorStore((state) => state.setCurrentBlockId)
374371
const currentBlockId = usePanelEditorStore((state) => state.currentBlockId)
@@ -378,14 +375,6 @@ export const WorkflowBlock = memo(function WorkflowBlock({
378375
const isStarterBlock = type === 'starter'
379376
const isWebhookTriggerBlock = type === 'webhook' || type === 'generic_webhook'
380377

381-
/**
382-
* Update node internals when handles change to ensure ReactFlow
383-
* correctly calculates connection points.
384-
*/
385-
useEffect(() => {
386-
updateNodeInternals(id)
387-
}, [id, horizontalHandles, updateNodeInternals])
388-
389378
/**
390379
* Subscribe to this block's subblock values to track changes for conditional rendering
391380
* of subblocks based on their conditions.
@@ -605,51 +594,43 @@ export const WorkflowBlock = memo(function WorkflowBlock({
605594
*
606595
* Width is a fixed 250px for workflow blocks.
607596
*/
608-
useEffect(() => {
609-
// Only workflow blocks (non-subflow) render here, width is constant
610-
const FIXED_WIDTH = 250
611-
const HEADER_HEIGHT = 40
612-
const CONTENT_PADDING = 16
613-
const ROW_HEIGHT = 29
614-
615-
const shouldShowDefaultHandles =
616-
config.category !== 'triggers' && type !== 'starter' && !displayTriggerMode
617-
const hasContentBelowHeader = subBlockRows.length > 0 || shouldShowDefaultHandles
618-
619-
// Count rows based on block type and whether default handles section is shown
620-
const defaultHandlesRow = shouldShowDefaultHandles ? 1 : 0
621-
622-
let rowsCount = 0
623-
if (type === 'condition') {
624-
rowsCount = conditionRows.length + defaultHandlesRow
625-
} else {
626-
const subblockRowCount = subBlockRows.reduce((acc, row) => acc + row.length, 0)
627-
rowsCount = subblockRowCount + defaultHandlesRow
628-
}
629-
630-
const contentHeight = hasContentBelowHeader ? CONTENT_PADDING + rowsCount * ROW_HEIGHT : 0
631-
const calculatedHeight = Math.max(HEADER_HEIGHT + contentHeight, 100)
597+
useBlockDimensions({
598+
blockId: id,
599+
calculateDimensions: () => {
600+
const FIXED_WIDTH = 250
601+
const HEADER_HEIGHT = 40
602+
const CONTENT_PADDING = 16
603+
const ROW_HEIGHT = 29
604+
605+
const shouldShowDefaultHandles =
606+
config.category !== 'triggers' && type !== 'starter' && !displayTriggerMode
607+
const hasContentBelowHeader = subBlockRows.length > 0 || shouldShowDefaultHandles
608+
609+
// Count rows based on block type and whether default handles section is shown
610+
const defaultHandlesRow = shouldShowDefaultHandles ? 1 : 0
611+
612+
let rowsCount = 0
613+
if (type === 'condition') {
614+
rowsCount = conditionRows.length + defaultHandlesRow
615+
} else {
616+
const subblockRowCount = subBlockRows.reduce((acc, row) => acc + row.length, 0)
617+
rowsCount = subblockRowCount + defaultHandlesRow
618+
}
632619

633-
const prevHeight =
634-
typeof currentStoreBlock?.height === 'number' ? currentStoreBlock.height : undefined
635-
const prevWidth = 250 // fixed across the app for workflow blocks
620+
const contentHeight = hasContentBelowHeader ? CONTENT_PADDING + rowsCount * ROW_HEIGHT : 0
621+
const calculatedHeight = Math.max(HEADER_HEIGHT + contentHeight, 100)
636622

637-
if (prevHeight !== calculatedHeight || prevWidth !== FIXED_WIDTH) {
638-
updateBlockLayoutMetrics(id, { width: FIXED_WIDTH, height: calculatedHeight })
639-
updateNodeInternals(id)
640-
}
641-
// eslint-disable-next-line react-hooks/exhaustive-deps
642-
}, [
643-
id,
644-
type,
645-
config.category,
646-
displayTriggerMode,
647-
subBlockRows.length,
648-
conditionRows.length,
649-
currentStoreBlock?.height,
650-
updateBlockLayoutMetrics,
651-
updateNodeInternals,
652-
])
623+
return { width: FIXED_WIDTH, height: calculatedHeight }
624+
},
625+
dependencies: [
626+
type,
627+
config.category,
628+
displayTriggerMode,
629+
subBlockRows.length,
630+
conditionRows.length,
631+
horizontalHandles,
632+
],
633+
})
653634

654635
const showWebhookIndicator = (isStarterBlock || isWebhookTriggerBlock) && isWebhookConfigured
655636
const shouldShowScheduleBadge =

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { useAutoLayout } from './use-auto-layout'
2+
export { useBlockDimensions } from './use-block-dimensions'
23
export { type CurrentWorkflow, useCurrentWorkflow } from './use-current-workflow'
34
export { useNodeUtilities } from './use-node-utilities'
45
export { useScrollManagement } from './use-scroll-management'
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useEffect, useRef } from 'react'
2+
import { useUpdateNodeInternals } from 'reactflow'
3+
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
4+
5+
interface BlockDimensions {
6+
width: number
7+
height: number
8+
}
9+
10+
interface UseBlockDimensionsOptions {
11+
blockId: string
12+
calculateDimensions: () => BlockDimensions
13+
dependencies: React.DependencyList
14+
}
15+
16+
/**
17+
* Hook to manage deterministic block dimensions without ResizeObserver.
18+
* Calculates dimensions based on content structure and updates the store.
19+
*
20+
* @param options - Configuration for dimension calculation
21+
* @param options.blockId - The ID of the block
22+
* @param options.calculateDimensions - Function that returns current dimensions
23+
* @param options.dependencies - Dependencies that trigger recalculation
24+
*/
25+
export function useBlockDimensions({
26+
blockId,
27+
calculateDimensions,
28+
dependencies,
29+
}: UseBlockDimensionsOptions) {
30+
const updateNodeInternals = useUpdateNodeInternals()
31+
const updateBlockLayoutMetrics = useWorkflowStore((state) => state.updateBlockLayoutMetrics)
32+
const previousDimensions = useRef<BlockDimensions | null>(null)
33+
34+
useEffect(() => {
35+
const dimensions = calculateDimensions()
36+
const previous = previousDimensions.current
37+
38+
if (!previous || previous.width !== dimensions.width || previous.height !== dimensions.height) {
39+
previousDimensions.current = dimensions
40+
updateBlockLayoutMetrics(blockId, dimensions)
41+
updateNodeInternals(blockId)
42+
}
43+
// eslint-disable-next-line react-hooks/exhaustive-deps
44+
}, [blockId, updateBlockLayoutMetrics, updateNodeInternals, ...dependencies])
45+
}

0 commit comments

Comments
 (0)