From ed97047bdf4190a2952a6122a9e59a7460ed6c73 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sat, 27 Dec 2025 17:38:58 +0000 Subject: [PATCH] refactor: modularize workflow editor --- .../components/workflow/WorkflowEditor.tsx | 539 +++--------------- .../workflow/editor/WorkflowDetailsPanel.tsx | 23 + .../workflow/editor/WorkflowNodeCard.tsx | 138 +++++ .../workflow/editor/WorkflowNodesPanel.tsx | 61 ++ .../workflow/editor/WorkflowSidebar.tsx | 67 +++ .../workflow/editor/WorkflowTester.tsx | 76 +++ .../components/workflow/editor/constants.ts | 35 ++ .../workflow/editor/createActionHandlers.ts | 133 +++++ .../src/components/workflow/editor/types.ts | 69 +++ .../workflow/editor/useWorkflowState.ts | 33 ++ 10 files changed, 703 insertions(+), 471 deletions(-) create mode 100644 frontends/nextjs/src/components/workflow/editor/WorkflowDetailsPanel.tsx create mode 100644 frontends/nextjs/src/components/workflow/editor/WorkflowNodeCard.tsx create mode 100644 frontends/nextjs/src/components/workflow/editor/WorkflowNodesPanel.tsx create mode 100644 frontends/nextjs/src/components/workflow/editor/WorkflowSidebar.tsx create mode 100644 frontends/nextjs/src/components/workflow/editor/WorkflowTester.tsx create mode 100644 frontends/nextjs/src/components/workflow/editor/constants.ts create mode 100644 frontends/nextjs/src/components/workflow/editor/createActionHandlers.ts create mode 100644 frontends/nextjs/src/components/workflow/editor/types.ts create mode 100644 frontends/nextjs/src/components/workflow/editor/useWorkflowState.ts diff --git a/frontends/nextjs/src/components/workflow/WorkflowEditor.tsx b/frontends/nextjs/src/components/workflow/WorkflowEditor.tsx index 702cef278..e355792b6 100644 --- a/frontends/nextjs/src/components/workflow/WorkflowEditor.tsx +++ b/frontends/nextjs/src/components/workflow/WorkflowEditor.tsx @@ -1,243 +1,56 @@ -import { useState } from 'react' -import { - Box, - Card, - CardContent, - CardHeader, - TextField, - Typography, - Chip, - MenuItem, - IconButton, -} from '@mui/material' -import { - Add as AddIcon, - Delete as DeleteIcon, - FlashOn as LightningIcon, - Code as CodeIcon, - AccountTree as GitBranchIcon, - ArrowForward as ArrowRightIcon, - PlayArrow as PlayIcon, - CheckCircle as CheckCircleIcon, - Cancel as XCircleIcon, -} from '@mui/icons-material' -import { toast } from 'sonner' -import { createWorkflowEngine, type WorkflowExecutionResult as WFExecResult } from '@/lib/workflow/engine/workflow-engine' -import type { Workflow, WorkflowNode, WorkflowEdge, LuaScript } from '@/lib/level-types' -import { Input, Label, Badge, Button, Textarea, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@/components/ui' -import { CardTitle, CardDescription } from '@/components/ui' - -interface WorkflowEditorProps { - workflows: Workflow[] - onWorkflowsChange: (workflows: Workflow[]) => void - scripts?: LuaScript[] -} +import { Card, CardContent, CardHeader } from '@mui/material' +import { PlayArrow as PlayIcon } from '@mui/icons-material' +import { CardDescription, CardTitle } from '@/components/ui' +import { WorkflowSidebar } from './editor/WorkflowSidebar' +import { WorkflowDetailsPanel } from './editor/WorkflowDetailsPanel' +import { WorkflowNodesPanel } from './editor/WorkflowNodesPanel' +import { WorkflowTester } from './editor/WorkflowTester' +import { useWorkflowState } from './editor/useWorkflowState' +import { createActionHandlers } from './editor/createActionHandlers' +import type { WorkflowEditorProps } from './editor/types' export function WorkflowEditor({ workflows, onWorkflowsChange, scripts = [] }: WorkflowEditorProps) { - const [selectedWorkflow, setSelectedWorkflow] = useState( - workflows.length > 0 ? workflows[0].id : null - ) - const [testData, setTestData] = useState('{"example": "data"}') - const [testOutput, setTestOutput] = useState(null) - const [isExecuting, setIsExecuting] = useState(false) + const state = useWorkflowState(workflows) + const { + currentWorkflow, + selectedWorkflowId, + setSelectedWorkflowId, + testData, + setTestData, + testOutput, + setTestOutput, + isExecuting, + setIsExecuting, + } = state - const currentWorkflow = workflows.find(w => w.id === selectedWorkflow) - - const handleAddWorkflow = () => { - const newWorkflow: Workflow = { - id: `workflow_${Date.now()}`, - name: 'New Workflow', - nodes: [], - edges: [], - enabled: true, - } - onWorkflowsChange([...workflows, newWorkflow]) - setSelectedWorkflow(newWorkflow.id) - toast.success('Workflow created') - } - - const handleDeleteWorkflow = (workflowId: string) => { - onWorkflowsChange(workflows.filter(w => w.id !== workflowId)) - if (selectedWorkflow === workflowId) { - setSelectedWorkflow(workflows.length > 1 ? workflows[0].id : null) - } - toast.success('Workflow deleted') - } - - const handleUpdateWorkflow = (updates: Partial) => { - if (!currentWorkflow) return - - onWorkflowsChange( - workflows.map(w => w.id === selectedWorkflow ? { ...w, ...updates } : w) - ) - } - - const handleAddNode = (type: WorkflowNode['type']) => { - if (!currentWorkflow) return - - const newNode: WorkflowNode = { - id: `node_${Date.now()}`, - type, - label: `${type.charAt(0).toUpperCase() + type.slice(1)} Node`, - config: {}, - position: { x: 100, y: currentWorkflow.nodes.length * 100 + 100 }, - } - - handleUpdateWorkflow({ - nodes: [...currentWorkflow.nodes, newNode], - }) - toast.success('Node added') - } - - const handleDeleteNode = (nodeId: string) => { - if (!currentWorkflow) return - - handleUpdateWorkflow({ - nodes: currentWorkflow.nodes.filter(n => n.id !== nodeId), - edges: currentWorkflow.edges.filter(e => e.source !== nodeId && e.target !== nodeId), - }) - toast.success('Node deleted') - } - - const handleUpdateNode = (nodeId: string, updates: Partial) => { - if (!currentWorkflow) return - - handleUpdateWorkflow({ - nodes: currentWorkflow.nodes.map(n => n.id === nodeId ? { ...n, ...updates } : n), - }) - } - - const handleTestWorkflow = async () => { - if (!currentWorkflow) return - - setIsExecuting(true) - setTestOutput(null) - - try { - let parsedData: any - try { - parsedData = JSON.parse(testData) - } catch { - parsedData = testData - } - - const engine = createWorkflowEngine() - const result = await engine.executeWorkflow(currentWorkflow, { - data: parsedData, - user: { username: 'test_user', role: 'god' }, - scripts, - }) - - setTestOutput(result) - - if (result.success) { - toast.success('Workflow executed successfully') - } else { - toast.error('Workflow execution failed') - } - } catch (error) { - toast.error('Execution error: ' + (error instanceof Error ? error.message : String(error))) - setTestOutput({ - success: false, - outputs: {}, - logs: [], - error: error instanceof Error ? error.message : String(error), - }) - } finally { - setIsExecuting(false) - } - } - - const getNodeIcon = (type: WorkflowNode['type']) => { - switch (type) { - case 'trigger': - return - case 'action': - return - case 'condition': - return - case 'lua': - return - case 'transform': - return - default: - return - } - } - - const getNodeColor = (type: WorkflowNode['type']): string => { - switch (type) { - case 'trigger': - return 'success.main' - case 'action': - return 'primary.main' - case 'condition': - return 'warning.main' - case 'lua': - return 'secondary.main' - case 'transform': - return 'info.main' - default: - return 'grey.500' - } - } + const { + handleAddWorkflow, + handleDeleteWorkflow, + handleUpdateWorkflow, + handleAddNode, + handleDeleteNode, + handleUpdateNode, + handleTestWorkflow, + } = createActionHandlers({ + workflows, + currentWorkflow, + onWorkflowsChange, + setSelectedWorkflowId, + setTestOutput, + setIsExecuting, + scripts, + testData, + }) return (
- - -
- Workflows - -
- Automation workflows -
- -
- {workflows.length === 0 ? ( -

- No workflows yet. Create one to start. -

- ) : ( - workflows.map((workflow) => ( -
setSelectedWorkflow(workflow.id)} - > -
-
{workflow.name}
-
- {workflow.nodes.length} nodes -
-
-
- - {workflow.enabled ? 'On' : 'Off'} - - -
-
- )) - )} -
-
-
+ {!currentWorkflow ? ( @@ -254,250 +67,34 @@ export function WorkflowEditor({ workflows, onWorkflowsChange, scripts = [] }: W Edit Workflow: {currentWorkflow.name} Configure workflow nodes and connections
- + -
-
- - handleUpdateWorkflow({ name: e.target.value })} - placeholder="My Workflow" - /> -
-
- - handleUpdateWorkflow({ description: e.target.value })} - placeholder="What this workflow does..." - /> -
-
+ -
-
- -
- - - - -
-
- -
- {currentWorkflow.nodes.length === 0 ? ( -

- No nodes yet. Add nodes to build your workflow. -

- ) : ( - currentWorkflow.nodes.map((node, index) => ( - - -
-
- {getNodeIcon(node.type)} -
-
-
-
- - - handleUpdateNode(node.id, { label: e.target.value }) - } - placeholder="Node name" - /> -
-
- - -
-
- - {node.type === 'lua' && scripts.length > 0 && ( -
- - -
- )} - - {node.type === 'condition' && ( -
- - - handleUpdateNode(node.id, { - config: { ...node.config, condition: e.target.value } - }) - } - placeholder="data.value > 10" - className="font-mono text-xs" - /> -
- )} - - {node.type === 'transform' && ( -
- - - handleUpdateNode(node.id, { - config: { ...node.config, transform: e.target.value } - }) - } - placeholder="{ result: data.value * 2 }" - className="font-mono text-xs" - /> -
- )} - -
- - Step {index + 1} - - {index < currentWorkflow.nodes.length - 1 && ( -
- - Next -
- )} -
-
- -
-
-
- )) - )} -
-
+ {currentWorkflow.nodes.length > 0 && ( - <> -
- -