Initial commit
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import React, { useCallback, useRef, useEffect } from 'react';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Controls,
|
||||
ReactFlowProvider,
|
||||
addEdge,
|
||||
Connection,
|
||||
useReactFlow,
|
||||
applyNodeChanges,
|
||||
applyEdgeChanges,
|
||||
Panel,
|
||||
} from 'reactflow';
|
||||
import { ArrowLeft, Save } from 'lucide-react';
|
||||
import { useFlowStore } from '../store/flowStore';
|
||||
import { ConfigPanel } from './ConfigPanel';
|
||||
import { ComponentsPanel } from './ComponentsPanel';
|
||||
import CustomNode from './CustomNode';
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
const nodeTypes = {
|
||||
auth: CustomNode,
|
||||
url: CustomNode,
|
||||
output: CustomNode,
|
||||
logic: CustomNode,
|
||||
variable: CustomNode,
|
||||
'db-find': CustomNode,
|
||||
'db-insert': CustomNode,
|
||||
'db-update': CustomNode,
|
||||
'db-delete': CustomNode,
|
||||
'db-query': CustomNode,
|
||||
};
|
||||
|
||||
interface FlowEditorContentProps {
|
||||
route: any;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function FlowEditorContent({ route, onClose }: FlowEditorContentProps) {
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||
const { project } = useReactFlow();
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
setNodes,
|
||||
setEdges,
|
||||
selectedNode,
|
||||
setSelectedNode,
|
||||
updateNodeData,
|
||||
updateRoute,
|
||||
} = useFlowStore();
|
||||
|
||||
useEffect(() => {
|
||||
// Load saved flow data if it exists
|
||||
if (route.flowData) {
|
||||
setNodes(route.flowData.nodes);
|
||||
setEdges(route.flowData.edges);
|
||||
} else {
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
}
|
||||
}, [route, setNodes, setEdges]);
|
||||
|
||||
const onNodesChange = useCallback(
|
||||
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||
[setNodes]
|
||||
);
|
||||
|
||||
const onEdgesChange = useCallback(
|
||||
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
const onDragOver = useCallback((event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
}, []);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const type = event.dataTransfer.getData('application/reactflow');
|
||||
if (!type || !reactFlowWrapper.current) return;
|
||||
|
||||
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const position = project({
|
||||
x: event.clientX - reactFlowBounds.left - 75,
|
||||
y: event.clientY - reactFlowBounds.top - 25,
|
||||
});
|
||||
|
||||
const newNode = {
|
||||
id: `node_${Date.now()}`,
|
||||
type,
|
||||
position,
|
||||
data: { label: type.charAt(0).toUpperCase() + type.slice(1).replace('-', ' ') },
|
||||
};
|
||||
|
||||
setNodes((nds) => [...nds, newNode]);
|
||||
},
|
||||
[project, setNodes]
|
||||
);
|
||||
|
||||
const onNodeClick = useCallback(
|
||||
(_: React.MouseEvent, node: any) => {
|
||||
setSelectedNode(node);
|
||||
},
|
||||
[setSelectedNode]
|
||||
);
|
||||
|
||||
const onPaneClick = useCallback(() => {
|
||||
setSelectedNode(null);
|
||||
}, [setSelectedNode]);
|
||||
|
||||
const handleSave = () => {
|
||||
updateRoute({
|
||||
...route,
|
||||
flowData: {
|
||||
nodes,
|
||||
edges,
|
||||
},
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="border-b border-gray-200 p-4 flex items-center justify-between bg-white">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
Back to Routes
|
||||
</button>
|
||||
<h2 className="ml-4 text-lg font-semibold">
|
||||
{route.name} ({route.method} {route.url})
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
>
|
||||
<Save className="w-4 h-4" />
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<ComponentsPanel />
|
||||
<div className="flex-1 h-full" ref={reactFlowWrapper}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
onNodeClick={onNodeClick}
|
||||
onPaneClick={onPaneClick}
|
||||
nodeTypes={nodeTypes}
|
||||
deleteKeyCode="Delete"
|
||||
multiSelectionKeyCode="Control"
|
||||
selectionKeyCode="Shift"
|
||||
snapToGrid={true}
|
||||
snapGrid={[15, 15]}
|
||||
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
|
||||
fitView
|
||||
>
|
||||
<Background />
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
{selectedNode && (
|
||||
<ConfigPanel
|
||||
node={selectedNode}
|
||||
onClose={() => setSelectedNode(null)}
|
||||
onUpdateNode={updateNodeData}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RouteFlowEditor({ route, onClose }: FlowEditorContentProps) {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<FlowEditorContent route={route} onClose={onClose} />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user