2024-11-15 06:00:35 -05:00
|
|
|
import React, { useCallback, useRef, useEffect } from "react";
|
2024-11-15 04:44:20 -05:00
|
|
|
import ReactFlow, {
|
|
|
|
|
Background,
|
|
|
|
|
Controls,
|
|
|
|
|
ReactFlowProvider,
|
|
|
|
|
addEdge,
|
|
|
|
|
Connection,
|
|
|
|
|
useReactFlow,
|
|
|
|
|
applyNodeChanges,
|
|
|
|
|
applyEdgeChanges,
|
|
|
|
|
Panel,
|
2024-11-15 06:00:35 -05:00
|
|
|
} 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";
|
2024-11-15 04:44:20 -05:00
|
|
|
|
|
|
|
|
const nodeTypes = {
|
|
|
|
|
auth: CustomNode,
|
|
|
|
|
url: CustomNode,
|
|
|
|
|
output: CustomNode,
|
|
|
|
|
logic: CustomNode,
|
|
|
|
|
variable: CustomNode,
|
2024-11-15 06:00:35 -05:00
|
|
|
"db-find": CustomNode,
|
|
|
|
|
"db-insert": CustomNode,
|
|
|
|
|
"db-update": CustomNode,
|
|
|
|
|
"db-delete": CustomNode,
|
|
|
|
|
"db-query": CustomNode,
|
2024-11-15 04:44:20 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 {
|
2024-11-15 06:00:35 -05:00
|
|
|
// Create default URL node for new routes
|
|
|
|
|
const defaultNode = {
|
|
|
|
|
id: `node_${Date.now()}`,
|
|
|
|
|
type: "url",
|
|
|
|
|
position: { x: 100, y: 100 },
|
|
|
|
|
data: {
|
|
|
|
|
label: "URL",
|
|
|
|
|
path: route.url,
|
|
|
|
|
method: route.method,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
setNodes([defaultNode]);
|
2024-11-15 04:44:20 -05:00
|
|
|
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();
|
2024-11-15 06:00:35 -05:00
|
|
|
event.dataTransfer.dropEffect = "move";
|
2024-11-15 04:44:20 -05:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const onDrop = useCallback(
|
|
|
|
|
(event: React.DragEvent) => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
2024-11-15 06:00:35 -05:00
|
|
|
const type = event.dataTransfer.getData("application/reactflow");
|
2024-11-15 04:44:20 -05:00
|
|
|
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,
|
2024-11-15 06:00:35 -05:00
|
|
|
data: {
|
|
|
|
|
label: type.charAt(0).toUpperCase() + type.slice(1).replace("-", " "),
|
|
|
|
|
},
|
2024-11-15 04:44:20 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
);
|
2024-11-15 06:00:35 -05:00
|
|
|
}
|