import { Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState, Position, Handle, ReactFlowProvider } from '@xyflow/react'; import React, { useCallback, memo, useMemo, useRef, useState, useEffect } from 'react'; import { Play } from 'lucide-react'; // Optimized node components with proper prop types and memoization const StartNode = memo(({ data, isConnectable }) => { // Memoize handles configuration const handles = useMemo(() => ([ { id: 'start-right', type: 'source', position: Position.Right } ]), []); return (
{handles.map(handle => ( ))}
Start
); }); const ApiNode = memo(({ data, isConnectable }) => { // Memoize handles and colors const handles = useMemo(() => ([ { id: `${data.id}-left`, type: 'target', position: Position.Left }, { id: `${data.id}-right`, type: 'source', position: Position.Right } ]), [data.id]); const methodColor = useMemo(() => { const colors = { GET: 'bg-green-500', POST: 'bg-blue-500', PUT: 'bg-yellow-500', DELETE: 'bg-red-500', default: 'bg-gray-500' }; return colors[data.method] || colors.default; }, [data.method]); return (
{handles.map(handle => ( ))}
{data.method}
{data.name}
{data.path}
); }); const ConditionNode = memo(({ data, isConnectable }) => { // Memoize handle styles const handleStyles = useMemo(() => ({ true: { top: '30%', backgroundColor: '#22c55e', border: '2px solid #16a34a' }, false: { top: '70%', backgroundColor: '#ef4444', border: '2px solid #dc2626' } }), []); return (
Condition
{data.condition || 'Add condition'}
); }); const DelayNode = memo(({ data, isConnectable }) => { const handles = useMemo(() => ([ { id: `${data.id}-left`, type: 'target', position: Position.Left }, { id: `${data.id}-right`, type: 'source', position: Position.Right } ]), [data.id]); return (
{handles.map(handle => ( ))}
Delay
Delay: {data.duration || '1000'}ms
); }); // Add display names for debugging StartNode.displayName = 'StartNode'; ApiNode.displayName = 'ApiNode'; ConditionNode.displayName = 'ConditionNode'; DelayNode.displayName = 'DelayNode'; // Memoize initial nodes and edges const initialNodes = [ { id: 'start', type: 'startNode', position: { x: 100, y: 50 }, data: { id: 'start' } } ]; const initialEdges = []; // Memoize node types const nodeTypes = { startNode: StartNode, apiNode: ApiNode, conditionNode: ConditionNode, delayNode: DelayNode }; const DiagramArea = () => { const reactFlowWrapper = useRef(null); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const [selectedNodeId, setSelectedNodeId] = useState(null); // Click handlers for node selection const onNodeClick = useCallback((event, node) => { event.stopPropagation(); setSelectedNodeId(node.id); }, []); const onPaneClick = useCallback(() => { setSelectedNodeId(null); }, []); // Apply styles to nodes useEffect(() => { setNodes((nds) => nds.map((node) => ({ ...node, className: node.id === selectedNodeId ? 'border-2 border-primary shadow-md bg-primary/5' : null })) ); }, [selectedNodeId, setNodes]); // Memoize callback functions const onConnect = useCallback((params) => { setEdges(prev => [...prev, { ...params, id: `e${prev.length + 1}` }]); }, []); const onInit = useCallback((instance) => { reactFlowWrapper.current = instance; }, []); const onDragOver = useCallback((event) => { event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; }, []); const onDrop = useCallback((event) => { event.preventDefault(); if (!reactFlowWrapper.current) return; try { const apiData = JSON.parse(event.dataTransfer.getData('application/json')); const position = reactFlowWrapper.current.screenToFlowPosition({ x: event.clientX, y: event.clientY, }); const newNode = { id: `api-${Date.now()}`, type: 'apiNode', position, data: { id: apiData.id, name: apiData.name, method: apiData.method, path: apiData.path, collectionId: apiData.collectionId } }; setNodes(prev => prev.concat(newNode)); } catch (error) { console.error('Error adding new node:', error); } }, [setNodes]); // Default edge options memoized const defaultEdgeOptions = useMemo(() => ({ type: 'smoothstep' }), []); return (
); }; export default memo(DiagramArea);