Implemented state with logger

This commit is contained in:
Emir Karabeg
2025-01-16 12:03:13 -08:00
parent 6d7e8a3c9b
commit ee6cd2841e
4 changed files with 235 additions and 30 deletions

View File

@@ -1,6 +1,6 @@
'use client'
import { useCallback } from 'react'
import { useCallback, useEffect } from 'react'
import ReactFlow, {
Background,
NodeProps,
@@ -22,6 +22,8 @@ import { getBlock } from '../../../blocks/configs'
import { WorkflowBlock } from '../components/workflow-block/workflow-block'
import { BlockConfig } from '../../../blocks/types/block'
import { BlockType } from '../../../blocks/types/block'
import { useWorkflowStore } from '@/stores/workflow/workflow-store'
import { initializeStateLogger } from '@/stores/workflow/state-logger'
/**
* Represents the data structure for a workflow node
@@ -84,35 +86,69 @@ const edgeTypes: EdgeTypes = { custom: CustomEdge }
* Main canvas component for the workflow editor
*/
function WorkflowCanvas() {
// Flow state management
const [nodes, setNodes, onNodesChange] = useNodesState([])
const [edges, setEdges, onEdgesChange] = useEdgesState([])
// Replace useNodesState and useEdgesState with our store
const { blocks, edges, addBlock, updateBlockPosition, addEdge, removeEdge } =
useWorkflowStore()
// Convert blocks to ReactFlow nodes
const nodes = Object.values(blocks).map((block) => ({
id: block.id,
type: 'workflowBlock',
position: block.position,
data: {
type: block.type,
config: getBlock(block.type),
name: block.name,
},
}))
const { project } = useReactFlow()
/**
* Handles new edge connections between nodes
*/
const onConnect = useCallback(
(connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
const onNodesChange = useCallback(
(changes: any) => {
changes.forEach((change: any) => {
if (change.type === 'position' && change.position) {
updateBlockPosition(change.id, change.position)
}
})
},
[updateBlockPosition]
)
/**
* Handles dropping new blocks onto the canvas
*/
const onEdgesChange = useCallback(
(changes: any) => {
changes.forEach((change: any) => {
if (change.type === 'remove') {
removeEdge(change.id)
}
})
},
[removeEdge]
)
const onConnect = useCallback(
(connection: any) => {
addEdge({
...connection,
id: crypto.randomUUID(),
type: 'custom',
})
},
[addEdge]
)
// Update onDrop to use our store
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault()
try {
// Calculate drop position
const reactFlowBounds = event.currentTarget.getBoundingClientRect()
const position = project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
})
// Get block configuration
const { type } = JSON.parse(
event.dataTransfer.getData('application/json')
)
@@ -123,26 +159,17 @@ function WorkflowCanvas() {
return
}
// Create new node
const newNode = {
id: crypto.randomUUID(),
type: 'workflowBlock',
position,
data: {
type,
config: blockConfig,
name: `${blockConfig.toolbar.title} ${
nodes.filter((n) => n.data.type === type).length + 1
}`,
},
}
const id = crypto.randomUUID()
const name = `${blockConfig.toolbar.title} ${
Object.values(blocks).filter((b) => b.type === type).length + 1
}`
setNodes((nds) => [...nds, newNode])
addBlock(id, type, name, position)
} catch (err) {
console.error('Error dropping block:', err)
}
},
[project, nodes, setNodes]
[project, blocks, addBlock]
)
// Keyframe animation styles
@@ -153,6 +180,10 @@ function WorkflowCanvas() {
}
`
useEffect(() => {
initializeStateLogger()
}, [])
return (
<div className="w-full h-[calc(100vh-56px)]">
<style>{keyframeStyles}</style>

View File

@@ -0,0 +1,11 @@
import { useWorkflowStore } from './workflow-store'
export function initializeStateLogger() {
useWorkflowStore.subscribe((state) => {
console.log('Workflow State Updated:', {
blocks: state.blocks,
edges: state.edges,
selectedBlockId: state.selectedBlockId,
})
})
}

46
stores/workflow/types.ts Normal file
View File

@@ -0,0 +1,46 @@
import { Node, Edge } from 'reactflow'
import { BlockType, OutputType, SubBlockType } from '@/blocks/types/block'
export interface Position {
x: number
y: number
}
export interface BlockState {
id: string
type: BlockType
name: string
position: Position
inputs: Record<string, BlockInput>
outputType: OutputType
}
export interface BlockInput {
id: string
type: SubBlockType
value: string | number | string[][] | null
}
export interface WorkflowState {
blocks: Record<string, BlockState>
edges: Edge[]
selectedBlockId: string | null
}
export interface WorkflowActions {
addBlock: (
id: string,
type: BlockType,
name: string,
position: Position
) => void
updateBlockPosition: (id: string, position: Position) => void
updateBlockInput: (blockId: string, inputId: string, value: any) => void
removeBlock: (id: string) => void
addEdge: (edge: Edge) => void
removeEdge: (edgeId: string) => void
setSelectedBlock: (id: string | null) => void
clear: () => void
}
export type WorkflowStore = WorkflowState & WorkflowActions

View File

@@ -0,0 +1,117 @@
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { Edge } from 'reactflow'
import { BlockType } from '@/blocks/types/block'
import { Position } from '@/stores/workflow/types'
import { WorkflowStore } from './types'
import { getBlock } from '@/blocks/configs'
const initialState = {
blocks: {},
edges: [],
selectedBlockId: null,
}
export const useWorkflowStore = create<WorkflowStore>()(
devtools(
(set, get) => ({
...initialState,
addBlock: (id: string, type: BlockType, name: string, position: Position) => {
const blockConfig = getBlock(type)
if (!blockConfig) return
const inputs: Record<string, any> = {}
blockConfig.workflow.subBlocks.forEach((subBlock) => {
inputs[subBlock.id || crypto.randomUUID()] = {
id: subBlock.id || crypto.randomUUID(),
type: subBlock.type,
value: null,
}
})
set((state) => ({
blocks: {
...state.blocks,
[id]: {
id,
type,
name,
position,
inputs,
outputType:
typeof blockConfig.workflow.outputType === 'string'
? blockConfig.workflow.outputType
: blockConfig.workflow.outputType.default,
},
},
}))
},
updateBlockPosition: (id: string, position: Position) => {
set((state) => ({
blocks: {
...state.blocks,
[id]: {
...state.blocks[id],
position,
},
},
}))
},
updateBlockInput: (blockId: string, inputId: string, value: any) => {
set((state) => ({
blocks: {
...state.blocks,
[blockId]: {
...state.blocks[blockId],
inputs: {
...state.blocks[blockId].inputs,
[inputId]: {
...state.blocks[blockId].inputs[inputId],
value,
},
},
},
},
}))
},
removeBlock: (id: string) => {
set((state) => {
const { [id]: _, ...remainingBlocks } = state.blocks
const remainingEdges = state.edges.filter(
(edge) => edge.source !== id && edge.target !== id
)
return {
blocks: remainingBlocks,
edges: remainingEdges,
selectedBlockId: state.selectedBlockId === id ? null : state.selectedBlockId,
}
})
},
addEdge: (edge: Edge) => {
set((state) => ({
edges: [...state.edges, edge],
}))
},
removeEdge: (edgeId: string) => {
set((state) => ({
edges: state.edges.filter((edge) => edge.id !== edgeId),
}))
},
setSelectedBlock: (id: string | null) => {
set({ selectedBlockId: id })
},
clear: () => {
set(initialState)
},
}),
{ name: 'workflow-store' }
)
)