2026-02-07 22:54:48 +08:00

218 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useCallback, ReactNode } from 'react';
import {
ReactFlow,
applyNodeChanges,
applyEdgeChanges,
addEdge,
Background,
Controls,
ReactFlowProvider,
NodeTypes,
NodeChange,
EdgeChange,
MarkerType,
NodeMouseHandler,
OnConnect,
MiniMap,
IsValidConnection,
EdgeMouseHandler
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { AppNode, AppEdge, NodeData, EdgeData, Settings } from './types/graph';
import CustomNode from './components/CustomNode';
import NodeEditor from './components/NodeEditor';
import EdgeEditor from './components/EdgeEditor'
import Toggle from "./components/Toggle"
import { generateWireGuardPrivateKey } from './utils/wireguardConfig';
import './App.css';
const initialNodes: AppNode[] = [];
const initialEdges: AppEdge[] = [];
const initialSettings : Settings = {
listenPort: 38894,
mtu: 1420,
};
const nodeTypes : NodeTypes = {
custom: CustomNode,
};
function generateNodeData(count: number) : NodeData | null {
const privateKey = generateWireGuardPrivateKey();
const node : NodeData = {
id: `n-${crypto.randomUUID()}`,
label: `Node-${count + 1}`,
privateKey: privateKey
}
return node
}
function FlowContent(): ReactNode {
const [nodes, setNodes] = useState<AppNode[]>(initialNodes);
const [edges, setEdges] = useState<AppEdge[]>(initialEdges);
const [settings, setSettings] = useState<Settings>(initialSettings);
const [editingNode, setEditingNode] = useState<NodeData | undefined>(undefined);
const [editingEdge, setEditingEdge] = useState<EdgeData | undefined>(undefined);
const [enableTwoWay, setEnableTwoWay] = useState(false);
const onNodesChange = useCallback(
(changes: NodeChange<AppNode>[]) => setNodes((nds) => applyNodeChanges<AppNode>(changes, nds)),
[]);
const onEdgesChange = useCallback(
(changes : EdgeChange<AppEdge>[]) => setEdges((eds) => applyEdgeChanges<AppEdge>(changes, eds)),
[]);
const onConnect = useCallback<OnConnect>(
(params) => {
const id = `e-${crypto.randomUUID()}`
const newEdge : AppEdge = {
...params,
id: id,
animated: !enableTwoWay,
markerEnd: enableTwoWay ? undefined : { type: MarkerType.ArrowClosed },
data : {
id: id,
isTwoWayEdge: enableTwoWay
}
}
return setEdges((eds) => addEdge<AppEdge>(newEdge, eds));
},
[enableTwoWay]);
const onNodeClick = useCallback<NodeMouseHandler<AppNode>>(
(_event, node) => setEditingNode(node.data),
[]);
const onEdgeClick = useCallback<EdgeMouseHandler<AppEdge>>(
(_event, edge) => setEditingEdge(edge.data),
[]
)
const validateConnection = useCallback<IsValidConnection>(
(connection) => {
if (connection.source === connection.target) {
return false;
}
const isDuplicate = edges.some(
(edge) => (
(edge.source === connection.source && edge.target === connection.target) ||
(edge.source === connection.target && edge.target === connection.source)
)
);
return !isDuplicate;
},
[edges]);
const handleAddNode = (): void => {
const result = generateNodeData(nodes.length);
if(result == null) return;
const newNode: AppNode = {
id: result.id,
position: { x: 0, y: 0 },
data: result,
type: 'custom',
selected: true
};
setNodes((prev) => [...prev.map(node => ({ ...node, selected: false })), newNode]);
};
const handleUpdateNode = (updatedData: NodeData): void => {
setNodes((prev) =>
prev.map((node) => {
if (node.data.id === editingNode?.id) {
return { ...node, data: updatedData };
}
return node;
})
);
setEditingNode(undefined);
};
const handleUpdateEdge = (updatedData: EdgeData): void => {
setEdges((prev) =>
prev.map((edge) => {
if (edge.data && edge.data.id === editingEdge?.id) {
return { ...edge, data: updatedData };
}
return edge;
})
);
setEditingNode(undefined);
};
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeDoubleClick={onNodeClick}
onEdgeDoubleClick={onEdgeClick}
nodeTypes={nodeTypes}
deleteKeyCode={["Delete"]}
fitView
isValidConnection={validateConnection}
>
<Background />
<Controls />
<MiniMap
nodeColor={(n) => {
if (n.type === 'input') return 'blue';
return '#eee';
}}
maskColor="rgba(0, 0, 0, 0.1)"
position="bottom-right" // 也可以是 top-right 等
/>
</ReactFlow>
<div className="toolbar">
<div className="toolbar-group">
<Toggle
className="toolbar-item"
checked = {enableTwoWay}
onChange={checked => setEnableTwoWay(checked)} ></Toggle>
<button className="toolbar-btn" onClick={handleAddNode} title="添加新节点">
</button>
<button className="toolbar-btn" onClick={() => {}} title="设置">
📋
</button>
</div>
</div>
{editingNode && (
<NodeEditor
node={editingNode}
onUpdate={handleUpdateNode}
onClose={() => setEditingNode(undefined)}
settings={settings}
/>
)}
{editingEdge && (
<EdgeEditor
edge={editingEdge}
onUpdate={handleUpdateEdge}
onClose={() => setEditingEdge(undefined)}
/>
)}
</div>
);
}
export default function App(): ReactNode {
return (
<ReactFlowProvider>
<FlowContent />
</ReactFlowProvider>
);
}