Shivam311's picture
feat: CodeAtlas Enterprise - IBM Bob Engineering Intelligence Platform
3a7842d
Raw
History Blame Contribute Delete
6.27 kB
import { useCallback, useEffect, useRef, useState } from 'react';
import { Background, Controls, Panel, ReactFlow, addEdge, useEdgesState, useNodesState } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { MarkerType } from '@xyflow/react';
import WorkflowEdge from './WorkflowEdge';
import WorkflowMinimap from './WorkflowMinimap';
import WorkflowNode from './WorkflowNode';
import WorkflowToolbar from './WorkflowToolbar';
const nodeTypes = { workflowNode: WorkflowNode };
const edgeTypes = { workflowEdge: WorkflowEdge, default: WorkflowEdge };
export const NODE_TEMPLATES = [
{ type: 'start', label: 'Start / Trigger', iconName: 'Play', color: '#10B981', category: 'Flow' },
{ type: 'process', label: 'Process', iconName: 'Cpu', color: '#3B82F6', category: 'Flow' },
{ type: 'decision', label: 'Decision', iconName: 'GitBranch', color: '#F59E0B', category: 'Flow' },
{ type: 'database', label: 'Database', iconName: 'Database', color: '#10B981', category: 'Infrastructure' },
{ type: 'api', label: 'API Call', iconName: 'Globe', color: '#06B6D4', category: 'Integration' },
{ type: 'auth', label: 'Auth Check', iconName: 'Lock', color: '#EF4444', category: 'Security' },
{ type: 'notification', label: 'Notification', iconName: 'Bell', color: '#8B5CF6', category: 'Output' },
{ type: 'code', label: 'Code Module', iconName: 'Code', color: '#84CC16', category: 'Code' },
{ type: 'service', label: 'Microservice', iconName: 'Box', color: '#EC4899', category: 'Infrastructure' },
{ type: 'queue', label: 'Message Queue', iconName: 'Layers', color: '#F97316', category: 'Infrastructure' },
{ type: 'cloud', label: 'Cloud Service', iconName: 'Cloud', color: '#14B8A6', category: 'Infrastructure' },
];
let nodeIdCounter = 1;
export default function WorkflowDesigner({ initialNodes = [], initialEdges = [] }) {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const [isDraggingOver, setIsDraggingOver] = useState(false);
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const wrapperRef = useRef(null);
useEffect(() => {
setNodes(initialNodes);
setEdges(initialEdges);
}, [initialEdges, initialNodes, setEdges, setNodes]);
const onConnect = useCallback(
(params) =>
setEdges((current) =>
addEdge(
{
...params,
type: 'workflowEdge',
markerEnd: { type: MarkerType.ArrowClosed, color: '#38BDF8' },
animated: true,
},
current,
),
),
[setEdges],
);
const onDragStart = (event, template) => {
event.dataTransfer.setData('application/workflow-node', JSON.stringify(template));
event.dataTransfer.effectAllowed = 'move';
};
const onDragOver = useCallback((event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
setIsDraggingOver(true);
}, []);
const onDrop = useCallback(
(event) => {
event.preventDefault();
setIsDraggingOver(false);
const raw = event.dataTransfer.getData('application/workflow-node');
if (!raw || !reactFlowInstance) return;
const template = JSON.parse(raw);
const position = reactFlowInstance.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
setNodes((current) =>
current.concat({
id: `node_${nodeIdCounter++}`,
type: 'workflowNode',
position,
data: { ...template, nodeType: template.type, description: '' },
}),
);
},
[reactFlowInstance, setNodes],
);
const addNode = (template = NODE_TEMPLATES[0]) => {
setNodes((current) =>
current.concat({
id: `node_${nodeIdCounter++}`,
type: 'workflowNode',
position: { x: 120 + current.length * 35, y: 120 + current.length * 24 },
data: { ...template, nodeType: template.type, description: '' },
}),
);
};
const deleteSelected = () => {
setNodes((current) => current.filter((node) => !node.selected));
setEdges((current) => current.filter((edge) => !edge.selected));
};
const exportWorkflow = () => {
const blob = new Blob([JSON.stringify({ nodes, edges, timestamp: new Date().toISOString() }, null, 2)], {
type: 'application/json',
});
const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = 'codeatlas-workflow.json';
anchor.click();
URL.revokeObjectURL(url);
};
return (
<div className="flex h-full min-h-[720px] overflow-hidden rounded-lg border border-white/10">
<WorkflowToolbar templates={NODE_TEMPLATES} onDragStart={onDragStart} />
<div className="relative flex-1" ref={wrapperRef}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onInit={setReactFlowInstance}
onDrop={onDrop}
onDragOver={onDragOver}
onDragLeave={() => setIsDraggingOver(false)}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
fitView
deleteKeyCode="Delete"
style={{ background: isDraggingOver ? 'rgba(56,189,248,0.05)' : '#080b14' }}
>
<Background color="#263144" gap={28} size={1} />
<Controls />
<WorkflowMinimap />
<Panel position="top-right">
<div className="flex gap-2">
<button onClick={() => addNode()} className="rounded-lg border border-cyan-400/25 bg-cyan-500/12 px-3 py-2 text-xs font-medium text-cyan-200">
Add Node
</button>
<button onClick={deleteSelected} className="rounded-lg border border-red-400/25 bg-red-500/12 px-3 py-2 text-xs font-medium text-red-200">
Delete
</button>
<button onClick={exportWorkflow} className="rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-xs font-medium text-slate-200">
Export
</button>
</div>
</Panel>
</ReactFlow>
</div>
</div>
);
}