mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
Added loop block to workflow canvas; need to fix height calculation
This commit is contained in:
@@ -20,13 +20,14 @@ export const CustomEdge = (props: EdgeProps) => {
|
||||
const isSelected = props.id === props.data?.selectedEdgeId
|
||||
|
||||
return (
|
||||
<g>
|
||||
<g style={{ zIndex: 1 }}>
|
||||
<path
|
||||
d={edgePath}
|
||||
strokeWidth={20}
|
||||
stroke="transparent"
|
||||
fill="none"
|
||||
className="react-flow__edge-interaction"
|
||||
style={{ pointerEvents: 'all' }}
|
||||
/>
|
||||
<path
|
||||
d={edgePath}
|
||||
@@ -51,9 +52,10 @@ export const CustomEdge = (props: EdgeProps) => {
|
||||
x={labelX - 12}
|
||||
y={labelY - 12}
|
||||
className="overflow-visible"
|
||||
style={{ zIndex: 999 }}
|
||||
>
|
||||
<div
|
||||
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-full bg-[#FAFBFC]"
|
||||
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-full bg-[#FAFBFC] relative z-[9999]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
props.data?.onDelete?.(props.id)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
ConnectionLineType,
|
||||
@@ -16,6 +16,7 @@ import { NotificationList } from '@/app/w/components/notifications/notifications
|
||||
import { getBlock } from '../../../../../blocks'
|
||||
import { useWorkflowExecution } from '../../../hooks/use-workflow-execution'
|
||||
import { CustomEdge } from '../custom-edge/custom-edge'
|
||||
import { createLoopNode, getRelativeLoopPosition } from '../workflow-loop/workflow-loop'
|
||||
import { WorkflowNode } from '../workflow-node/workflow-node'
|
||||
|
||||
// Define custom node and edge types for ReactFlow
|
||||
@@ -45,31 +46,90 @@ export function WorkflowCanvas() {
|
||||
} = useWorkflowStore()
|
||||
const { addNotification } = useNotificationStore()
|
||||
const { project, setViewport } = useReactFlow()
|
||||
const { loops } = useWorkflowStore()
|
||||
|
||||
// Transform blocks into ReactFlow node format
|
||||
const nodes = Object.values(blocks).map((block) => ({
|
||||
id: block.id,
|
||||
type: 'workflowBlock',
|
||||
position: block.position,
|
||||
selected: block.id === selectedBlockId,
|
||||
dragHandle: '.workflow-drag-handle',
|
||||
data: {
|
||||
type: block.type,
|
||||
config: getBlock(block.type),
|
||||
name: block.name,
|
||||
},
|
||||
}))
|
||||
// Transform blocks and loops into ReactFlow node format
|
||||
const nodes = useMemo(() => {
|
||||
const nodeArray: any[] = []
|
||||
|
||||
// Handle node position updates during drag operations
|
||||
// Add loop group nodes first
|
||||
Object.entries(loops).forEach(([loopId, loop]) => {
|
||||
const loopNode = createLoopNode({ loopId, loop, blocks })
|
||||
if (loopNode) {
|
||||
nodeArray.push(loopNode)
|
||||
}
|
||||
})
|
||||
|
||||
// Add block nodes with relative positions if they're in a loop
|
||||
Object.entries(blocks).forEach(([blockId, block]) => {
|
||||
// Skip loop position entries that don't have proper block structure
|
||||
if (!block.type || !block.name) {
|
||||
console.log('Skipping invalid block:', blockId, block)
|
||||
return
|
||||
}
|
||||
|
||||
// Get block configuration
|
||||
const blockConfig = getBlock(block.type)
|
||||
if (!blockConfig) {
|
||||
console.error(`No configuration found for block type: ${block.type}`)
|
||||
return
|
||||
}
|
||||
|
||||
// Find if block belongs to any loop
|
||||
const parentLoop = Object.entries(loops).find(([_, loop]) => loop.nodes.includes(block.id))
|
||||
|
||||
let position = block.position
|
||||
if (parentLoop) {
|
||||
const [loopId] = parentLoop
|
||||
const loopNode = nodeArray.find((node) => node.id === `loop-${loopId}`)
|
||||
if (loopNode) {
|
||||
position = getRelativeLoopPosition(block.position, loopNode.position)
|
||||
}
|
||||
}
|
||||
|
||||
nodeArray.push({
|
||||
id: block.id,
|
||||
type: 'workflowBlock',
|
||||
position,
|
||||
parentId: parentLoop ? `loop-${parentLoop[0]}` : undefined,
|
||||
dragHandle: '.workflow-drag-handle',
|
||||
selected: block.id === selectedBlockId,
|
||||
data: {
|
||||
type: block.type,
|
||||
config: blockConfig,
|
||||
name: block.name,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
return nodeArray
|
||||
}, [blocks, loops, selectedBlockId])
|
||||
|
||||
// Update node position handler
|
||||
const onNodesChange = useCallback(
|
||||
(changes: any) => {
|
||||
changes.forEach((change: any) => {
|
||||
if (change.type === 'position' && change.position) {
|
||||
updateBlockPosition(change.id, change.position)
|
||||
const node = nodes.find((n) => n.id === change.id)
|
||||
if (!node) return
|
||||
|
||||
// If node is part of a loop, convert position back to absolute
|
||||
if (node.parentId) {
|
||||
const loopNode = nodes.find((n) => n.id === node.parentId)
|
||||
if (loopNode) {
|
||||
const absolutePosition = {
|
||||
x: change.position.x + loopNode.position.x,
|
||||
y: change.position.y + loopNode.position.y,
|
||||
}
|
||||
updateBlockPosition(change.id, absolutePosition)
|
||||
}
|
||||
} else {
|
||||
updateBlockPosition(change.id, change.position)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
[updateBlockPosition]
|
||||
[nodes, updateBlockPosition]
|
||||
)
|
||||
|
||||
// Handle edge removal and updates
|
||||
|
||||
71
app/w/[id]/components/workflow-loop/workflow-loop.tsx
Normal file
71
app/w/[id]/components/workflow-loop/workflow-loop.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Loop } from '@/stores/workflow/types'
|
||||
|
||||
interface WorkflowLoopProps {
|
||||
loopId: string
|
||||
loop: Loop
|
||||
blocks: Record<string, any>
|
||||
}
|
||||
|
||||
// Pure calculation function - no hooks
|
||||
function calculateLoopBounds(loop: Loop, blocks: Record<string, any>) {
|
||||
// Get all blocks in this loop
|
||||
const loopBlocks = loop.nodes.map((id) => blocks[id])
|
||||
if (!loopBlocks.length) return null
|
||||
|
||||
// Calculate bounds of all blocks in loop
|
||||
const bound = loopBlocks.reduce(
|
||||
(acc, block) => {
|
||||
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)
|
||||
return acc
|
||||
},
|
||||
{ minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }
|
||||
)
|
||||
|
||||
// Add padding around the group
|
||||
const PADDING = 50
|
||||
return {
|
||||
x: bound.minX - PADDING,
|
||||
y: bound.minY - PADDING,
|
||||
width: bound.maxX - bound.minX + PADDING * 2,
|
||||
height: bound.maxY - bound.minY + PADDING * 2,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create loop node
|
||||
export function createLoopNode({ loopId, loop, blocks }: WorkflowLoopProps) {
|
||||
const loopBounds = calculateLoopBounds(loop, blocks)
|
||||
if (!loopBounds) return null
|
||||
|
||||
return {
|
||||
id: `loop-${loopId}`,
|
||||
type: 'group',
|
||||
position: { x: loopBounds.x, y: loopBounds.y },
|
||||
style: {
|
||||
backgroundColor: 'rgb(247, 247, 248)',
|
||||
border: '1px solid rgb(203, 213, 225)',
|
||||
borderRadius: '12px',
|
||||
width: loopBounds.width,
|
||||
height: loopBounds.height,
|
||||
pointerEvents: 'none',
|
||||
zIndex: -1,
|
||||
isolation: 'isolate',
|
||||
},
|
||||
data: {
|
||||
label: 'Loop',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to calculate relative position for child blocks
|
||||
export function getRelativeLoopPosition(
|
||||
blockPosition: { x: number; y: number },
|
||||
loopBounds: { x: number; y: number }
|
||||
) {
|
||||
return {
|
||||
x: blockPosition.x - loopBounds.x,
|
||||
y: blockPosition.y - loopBounds.y,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user