mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
Added block height to state and made loop relative to block height and position
This commit is contained in:
@@ -30,6 +30,7 @@ export function WorkflowBlock({ id, data, selected }: NodeProps<WorkflowBlockPro
|
||||
|
||||
// Refs
|
||||
const blockRef = useRef<HTMLDivElement>(null)
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const updateNodeInternals = useUpdateNodeInternals()
|
||||
|
||||
// Store selectors
|
||||
@@ -38,16 +39,65 @@ export function WorkflowBlock({ id, data, selected }: NodeProps<WorkflowBlockPro
|
||||
(state) => 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<WorkflowBlockPro
|
||||
</div>
|
||||
|
||||
{/* Block Content */}
|
||||
<div className="px-4 pt-3 pb-4 space-y-4 cursor-pointer">
|
||||
<div ref={contentRef} className="px-4 pt-3 pb-4 space-y-4 cursor-pointer">
|
||||
{subBlockRows.map((row, rowIndex) => (
|
||||
<div key={`row-${rowIndex}`} className="flex gap-4">
|
||||
{row.map((subBlock, blockIndex) => (
|
||||
|
||||
@@ -34,22 +34,33 @@ function calculateLoopBounds(loop: Loop, blocks: Record<string, any>) {
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+31
-16
@@ -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<WorkflowStoreWithHistory>()(
|
||||
outputs,
|
||||
enabled: true,
|
||||
horizontalHandles: true,
|
||||
isWide: false,
|
||||
height: 0,
|
||||
},
|
||||
},
|
||||
edges: [...get().edges],
|
||||
@@ -186,7 +188,7 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
|
||||
// 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<WorkflowStoreWithHistory>()(
|
||||
sourceHandle: edge.sourceHandle,
|
||||
targetHandle: edge.targetHandle,
|
||||
}
|
||||
|
||||
|
||||
const newEdges = [...get().edges, newEdge]
|
||||
|
||||
|
||||
// Recalculate all loops after adding the edge
|
||||
const newLoops: Record<string, Loop> = {}
|
||||
const processedPaths = new Set<string>()
|
||||
|
||||
|
||||
// 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<WorkflowStoreWithHistory>()(
|
||||
|
||||
removeEdge: (edgeId: string) => {
|
||||
const newEdges = get().edges.filter((edge) => edge.id !== edgeId)
|
||||
|
||||
|
||||
// Recalculate all loops after edge removal
|
||||
const newLoops: Record<string, Loop> = {}
|
||||
const processedPaths = new Set<string>()
|
||||
|
||||
|
||||
// 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<WorkflowStoreWithHistory>()(
|
||||
loops: { ...get().loops },
|
||||
}))
|
||||
},
|
||||
|
||||
updateBlockHeight: (id: string, height: number) => {
|
||||
set((state) => ({
|
||||
blocks: {
|
||||
...state.blocks,
|
||||
[id]: {
|
||||
...state.blocks[id],
|
||||
height,
|
||||
},
|
||||
},
|
||||
edges: [...state.edges],
|
||||
}))
|
||||
},
|
||||
})),
|
||||
{ name: 'workflow-store' }
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user