"use client"; import { memo, useMemo, useEffect, useState, useCallback } from "react"; import { Handle, Position, useUpdateNodeInternals, useReactFlow, NodeProps } from "@xyflow/react"; import { BaseNode } from "./BaseNode"; import { useWorkflowStore } from "@/store/workflowStore"; import type { WorkflowNode, SwitchNodeData, HandleType } from "@/types"; const HANDLE_COLORS: Record = { image: "#10b981", // emerald — matches globals.css text: "#3b82f6", // blue — matches globals.css video: "#ffffff", // white — default handle style audio: "rgb(167, 139, 250)", // violet — matches GenerateAudioNode/OutputNode "3d": "#f97316", // orange — matches globals.css easeCurve: "#ffffff", // white — default handle style }; export const SwitchNode = memo(({ id, data, selected }: NodeProps) => { const nodeData = data as SwitchNodeData; const edges = useWorkflowStore((state) => state.edges); const updateNodeData = useWorkflowStore((state) => state.updateNodeData); const updateNodeInternals = useUpdateNodeInternals(); const { setNodes, setEdges } = useReactFlow(); const [editingId, setEditingId] = useState(null); // Derive inputType from incoming edge connection const derivedInputType = useMemo(() => { const inputEdge = edges.find((e) => e.target === id); const handle = inputEdge?.targetHandle; // "generic-input" means the edge hasn't been resolved to a real type yet if (!handle || handle === "generic-input") return undefined; return handle as HandleType; }, [edges, id]); // Update stored inputType when derived type changes useEffect(() => { const newType = derivedInputType || null; if (newType !== nodeData.inputType) { updateNodeData(id, { inputType: newType }); } }, [derivedInputType, id, nodeData.inputType, updateNodeData]); // Calculate handle positioning const handleSpacing = 32; const baseOffset = 38; // Clear the header bar // Dynamic height based on switch count and whether we have input const switchCount = nodeData.switches.length; const showOutputs = nodeData.inputType !== null; const lastHandleTop = baseOffset + (showOutputs ? switchCount * handleSpacing : handleSpacing); const minHeight = lastHandleTop + 40; // Extra space for add button // Resize node and notify React Flow when switch count or inputType changes useEffect(() => { setNodes((nodes) => nodes.map((node) => { if (node.id === id) { const currentHeight = (node.style?.height as number) || 0; if (currentHeight < minHeight) { return { ...node, style: { ...node.style, height: minHeight } }; } } return node; }) ); updateNodeInternals(id); }, [switchCount, showOutputs, id, minHeight, setNodes, updateNodeInternals]); // Handle toggle change const handleToggle = useCallback( (switchId: string) => { const updatedSwitches = nodeData.switches.map((sw) => sw.id === switchId ? { ...sw, enabled: !sw.enabled } : sw ); updateNodeData(id, { switches: updatedSwitches }); }, [id, nodeData.switches, updateNodeData] ); // Handle name edit const handleNameEdit = useCallback( (switchId: string, newName: string) => { const updatedSwitches = nodeData.switches.map((sw) => sw.id === switchId ? { ...sw, name: newName } : sw ); updateNodeData(id, { switches: updatedSwitches }); setEditingId(null); }, [id, nodeData.switches, updateNodeData] ); // Handle delete switch const handleDelete = useCallback( (switchId: string) => { if (nodeData.switches.length <= 1) return; const updatedSwitches = nodeData.switches.filter((sw) => sw.id !== switchId); updateNodeData(id, { switches: updatedSwitches }); // Remove edges connected to this handle setEdges((edges) => edges.filter((e) => !(e.source === id && e.sourceHandle === switchId))); }, [id, nodeData.switches, updateNodeData, setEdges] ); // Handle add switch const handleAddSwitch = useCallback(() => { const newSwitch = { id: Math.random().toString(36).slice(2, 9), name: `Output ${nodeData.switches.length + 1}`, enabled: true, }; updateNodeData(id, { switches: [...nodeData.switches, newSwitch] }); }, [id, nodeData.switches, updateNodeData]); return ( updateNodeData(id, { customTitle })} onCommentChange={(comment) => updateNodeData(id, { comment })} selected={selected} minWidth={220} minHeight={minHeight} className="bg-violet-950/80 border-violet-600" > {/* Input handle (left) */} {nodeData.inputType ? ( ) : ( )} {/* Output handles (right) - only when input connected */} {showOutputs && nodeData.switches.map((sw, index) => ( ))} {/* Body content */}
{!showOutputs ? (
Connect input to enable outputs
) : ( <> {nodeData.switches.map((sw, index) => (
{/* Toggle */}
); }); SwitchNode.displayName = "SwitchNode";