124 lines
3.3 KiB
TypeScript
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>
|
|
)
|
|
}
|