From ddfa86641bcdb2ffbbb66a5c03dc891e34d2e8ac Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Tue, 11 Feb 2025 21:36:52 -0800 Subject: [PATCH] Added block height to state and made loop relative to block height and position --- .../workflow-block/workflow-block.tsx | 52 ++++++++++++++++++- .../workflow-loop/workflow-loop.tsx | 27 +++++++--- stores/workflow/store.ts | 47 +++++++++++------ stores/workflow/types.ts | 2 + 4 files changed, 103 insertions(+), 25 deletions(-) diff --git a/app/w/[id]/components/workflow-block/workflow-block.tsx b/app/w/[id]/components/workflow-block/workflow-block.tsx index b70a2b1cb7..75be233e06 100644 --- a/app/w/[id]/components/workflow-block/workflow-block.tsx +++ b/app/w/[id]/components/workflow-block/workflow-block.tsx @@ -30,6 +30,7 @@ export function WorkflowBlock({ id, data, selected }: NodeProps(null) + const contentRef = useRef(null) const updateNodeInternals = useUpdateNodeInternals() // Store selectors @@ -38,16 +39,65 @@ export function WorkflowBlock({ id, data, selected }: NodeProps state.blocks[id]?.horizontalHandles ?? false ) const isWide = useWorkflowStore((state) => state.blocks[id]?.isWide ?? false) + const blockHeight = useWorkflowStore((state) => state.blocks[id]?.height ?? 0) // Store actions const updateBlockName = useWorkflowStore((state) => state.updateBlockName) const toggleBlockWide = useWorkflowStore((state) => state.toggleBlockWide) + const updateBlockHeight = useWorkflowStore((state) => state.updateBlockHeight) // Update node internals when handles change useEffect(() => { updateNodeInternals(id) }, [id, horizontalHandles, updateNodeInternals]) + // Add debounce helper + const debounce = (func: Function, wait: number) => { + let timeout: NodeJS.Timeout + return (...args: any[]) => { + clearTimeout(timeout) + timeout = setTimeout(() => func(...args), wait) + } + } + + // Add effect to observe size changes with debounced updates + useEffect(() => { + if (!contentRef.current) return + + let rafId: number + const debouncedUpdate = debounce((height: number) => { + if (height !== blockHeight) { + updateBlockHeight(id, height) + updateNodeInternals(id) + } + }, 100) + + const resizeObserver = new ResizeObserver((entries) => { + // Cancel any pending animation frame + if (rafId) { + cancelAnimationFrame(rafId) + } + + // Schedule the update on the next animation frame + rafId = requestAnimationFrame(() => { + for (const entry of entries) { + const height = + entry.borderBoxSize[0]?.blockSize ?? entry.target.getBoundingClientRect().height + debouncedUpdate(height) + } + }) + }) + + resizeObserver.observe(contentRef.current) + + return () => { + resizeObserver.disconnect() + if (rafId) { + cancelAnimationFrame(rafId) + } + } + }, [id, blockHeight, updateBlockHeight, updateNodeInternals]) + // SubBlock layout management function groupSubBlocks(subBlocks: SubBlockConfig[]) { const visibleSubBlocks = subBlocks.filter((block) => !block.hidden) @@ -185,7 +235,7 @@ export function WorkflowBlock({ id, data, selected }: NodeProps {/* Block Content */} -
+
{subBlockRows.map((row, rowIndex) => (
{row.map((subBlock, blockIndex) => ( diff --git a/app/w/[id]/components/workflow-loop/workflow-loop.tsx b/app/w/[id]/components/workflow-loop/workflow-loop.tsx index 0424069781..2ffad7082f 100644 --- a/app/w/[id]/components/workflow-loop/workflow-loop.tsx +++ b/app/w/[id]/components/workflow-loop/workflow-loop.tsx @@ -34,22 +34,33 @@ function calculateLoopBounds(loop: Loop, blocks: Record) { // Calculate bounds of all blocks in loop const bound = loopBlocks.reduce( (acc, block) => { + // Calculate block dimensions + const blockWidth = block.isWide ? 480 : 320 + const blockHeight = block.height || 200 // Fallback height if not set + + // Update bounds acc.minX = Math.min(acc.minX, block.position.x) acc.minY = Math.min(acc.minY, block.position.y) - acc.maxX = Math.max(acc.maxX, block.position.x + (block.isWide ? 480 : 320)) - acc.maxY = Math.max(acc.maxY, block.position.y + 200) + acc.maxX = Math.max(acc.maxX, block.position.x + blockWidth) + acc.maxY = Math.max(acc.maxY, block.position.y + blockHeight) return acc }, { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity } ) - // Add padding around the group - const PADDING = 50 + // Add padding around the group with extra bottom padding + const PADDING = { + TOP: 50, + RIGHT: 50, + BOTTOM: 110, + LEFT: 50, + } + return { - x: bound.minX - PADDING, - y: bound.minY - PADDING, - width: bound.maxX - bound.minX + PADDING * 2, - height: bound.maxY - bound.minY + PADDING * 2, + x: bound.minX - PADDING.LEFT, + y: bound.minY - PADDING.TOP, + width: bound.maxX - bound.minX + PADDING.LEFT + PADDING.RIGHT, + height: bound.maxY - bound.minY + PADDING.TOP + PADDING.BOTTOM, } } diff --git a/stores/workflow/store.ts b/stores/workflow/store.ts index a018965d62..3c0f03d71d 100644 --- a/stores/workflow/store.ts +++ b/stores/workflow/store.ts @@ -4,7 +4,7 @@ import { devtools } from 'zustand/middleware' import { getBlock } from '@/blocks' import { resolveOutputType } from '@/blocks/utils' import { WorkflowStoreWithHistory, pushHistory, withHistory } from './middleware' -import { Position, SubBlockState, Loop } from './types' +import { Loop, Position, SubBlockState } from './types' import { detectCycle } from './utils' const initialState = { @@ -144,6 +144,8 @@ export const useWorkflowStore = create()( outputs, enabled: true, horizontalHandles: true, + isWide: false, + height: 0, }, }, edges: [...get().edges], @@ -186,7 +188,7 @@ export const useWorkflowStore = create()( // Otherwise, just remove the node from the loop newState.loops[loopId] = { ...loop, - nodes: loop.nodes.filter((nodeId) => nodeId !== id) + nodes: loop.nodes.filter((nodeId) => nodeId !== id), } } } @@ -208,31 +210,31 @@ export const useWorkflowStore = create()( sourceHandle: edge.sourceHandle, targetHandle: edge.targetHandle, } - + const newEdges = [...get().edges, newEdge] - + // Recalculate all loops after adding the edge const newLoops: Record = {} const processedPaths = new Set() - + // Check for cycles from each node - const nodes = new Set(newEdges.map(e => e.source)) - nodes.forEach(node => { + const nodes = new Set(newEdges.map((e) => e.source)) + nodes.forEach((node) => { const { paths } = detectCycle(newEdges, node) - paths.forEach(path => { + paths.forEach((path) => { // Create a canonical path representation for deduplication const canonicalPath = [...path].sort().join(',') if (!processedPaths.has(canonicalPath)) { const loopId = crypto.randomUUID() newLoops[loopId] = { id: loopId, - nodes: path + nodes: path, } processedPaths.add(canonicalPath) } }) }) - + const newState = { blocks: { ...get().blocks }, edges: newEdges, @@ -246,23 +248,23 @@ export const useWorkflowStore = create()( removeEdge: (edgeId: string) => { const newEdges = get().edges.filter((edge) => edge.id !== edgeId) - + // Recalculate all loops after edge removal const newLoops: Record = {} const processedPaths = new Set() - + // Check for cycles from each node - const nodes = new Set(newEdges.map(e => e.source)) - nodes.forEach(node => { + const nodes = new Set(newEdges.map((e) => e.source)) + nodes.forEach((node) => { const { paths } = detectCycle(newEdges, node) - paths.forEach(path => { + paths.forEach((path) => { // Create a canonical path representation for deduplication const canonicalPath = [...path].sort().join(',') if (!processedPaths.has(canonicalPath)) { const loopId = crypto.randomUUID() newLoops[loopId] = { id: loopId, - nodes: path + nodes: path, } processedPaths.add(canonicalPath) } @@ -413,6 +415,19 @@ export const useWorkflowStore = create()( loops: { ...get().loops }, })) }, + + updateBlockHeight: (id: string, height: number) => { + set((state) => ({ + blocks: { + ...state.blocks, + [id]: { + ...state.blocks[id], + height, + }, + }, + edges: [...state.edges], + })) + }, })), { name: 'workflow-store' } ) diff --git a/stores/workflow/types.ts b/stores/workflow/types.ts index 858edc731f..a6d0c4de61 100644 --- a/stores/workflow/types.ts +++ b/stores/workflow/types.ts @@ -16,6 +16,7 @@ export interface BlockState { enabled: boolean horizontalHandles?: boolean isWide?: boolean + height?: number } export interface SubBlockState { @@ -50,6 +51,7 @@ export interface WorkflowActions { toggleBlockHandles: (id: string) => void updateBlockName: (id: string, name: string) => void toggleBlockWide: (id: string) => void + updateBlockHeight: (id: string, height: number) => void } export type WorkflowStore = WorkflowState & WorkflowActions