flowfi-network/components/sandbox/FlowCanvas.tsx

124 lines
3.3 KiB
TypeScript

'use client'
import { useCallback, useState } from 'react'
import {
ReactFlow,
Controls,
MiniMap,
Background,
useNodesState,
useEdgesState,
addEdge,
type Connection,
type Node,
BackgroundVariant,
} from '@xyflow/react'
import '@xyflow/react/dist/style.css'
import SourceNode from './nodes/SourceNode'
import PipeNode from './nodes/PipeNode'
import SinkNode from './nodes/SinkNode'
import AnimatedPipeEdge from './edges/AnimatedPipeEdge'
import FlowToolbar from './FlowToolbar'
import { presets } from './presets'
const nodeTypes = {
source: SourceNode,
pipe: PipeNode,
sink: SinkNode,
}
const edgeTypes = {
animatedPipe: AnimatedPipeEdge,
}
export default function FlowCanvas() {
const defaultPreset = typeof window !== 'undefined' && window.innerWidth < 768 ? 0 : 0
const [nodes, setNodes, onNodesChange] = useNodesState(presets[defaultPreset].nodes)
const [edges, setEdges, onEdgesChange] = useEdgesState(presets[defaultPreset].edges)
const [isPlaying, setIsPlaying] = useState(true)
const [nodeIdCounter, setNodeIdCounter] = useState(100)
const onConnect = useCallback(
(connection: Connection) => {
setEdges((eds) =>
addEdge(
{ ...connection, type: 'animatedPipe', data: { flowRate: 3 } },
eds
)
)
},
[setEdges]
)
const handleAddNode = useCallback(
(type: 'source' | 'pipe' | 'sink') => {
const id = `n${nodeIdCounter}`
setNodeIdCounter((c) => c + 1)
const labels = { source: 'Source', pipe: 'Pipe', sink: 'Sink' }
const newNode: Node = {
id,
type,
position: { x: 200 + Math.random() * 200, y: 100 + Math.random() * 200 },
data: {
label: labels[type],
...(type === 'source' ? { flowRate: 5 } : {}),
...(type === 'sink' ? { fillLevel: 0 } : {}),
},
}
setNodes((nds) => [...nds, newNode])
},
[nodeIdCounter, setNodes]
)
const handleLoadPreset = useCallback(
(index: number) => {
const preset = presets[index]
setNodes(preset.nodes)
setEdges(preset.edges)
},
[setNodes, setEdges]
)
const handleReset = useCallback(() => {
setNodes(presets[2].nodes)
setEdges(presets[2].edges)
}, [setNodes, setEdges])
const showMiniMap = typeof window !== 'undefined' && window.innerWidth >= 768
return (
<div className="w-full h-[500px] md:h-[600px] rounded-2xl border border-flow/10 overflow-hidden relative bg-deep">
<FlowToolbar
onAddNode={handleAddNode}
onLoadPreset={handleLoadPreset}
onReset={handleReset}
isPlaying={isPlaying}
onTogglePlay={() => setIsPlaying(!isPlaying)}
/>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
defaultEdgeOptions={{ type: 'animatedPipe' }}
fitView
>
<Background variant={BackgroundVariant.Dots} gap={24} size={1} color="#2dd4bf08" />
<Controls position="bottom-right" />
{showMiniMap && (
<MiniMap
position="bottom-left"
nodeColor="#2dd4bf30"
maskColor="#050d1acc"
/>
)}
</ReactFlow>
</div>
)
}