Added loop block to workflow canvas; need to fix height calculation

This commit is contained in:
Emir Karabeg
2025-02-10 19:42:37 -08:00
parent 7fb47f4363
commit ff4fee0cb8
3 changed files with 152 additions and 19 deletions

View File

@@ -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)

View File

@@ -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

View 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,
}
}