feat: add grouping toolbar items and traffic flow toggle
DeviceToolbar gets Subnet/VLAN/Site/DMZ grouping section with drag-drop. PropertiesPanel gets Show Traffic toggle that switches edges between connection and animated types. DiagramEditor handles both device and group node drops. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -200,10 +200,7 @@ function DiagramEditorInner() {
|
||||
|
||||
const onDrop = useCallback((event: React.DragEvent) => {
|
||||
event.preventDefault()
|
||||
const raw = event.dataTransfer.getData('application/reactflow-device')
|
||||
if (!raw) return
|
||||
|
||||
const { slug, label, category } = JSON.parse(raw) as { slug: string; label: string; category: string }
|
||||
const reactFlowBounds = (event.target as HTMLElement).closest('.react-flow')?.getBoundingClientRect()
|
||||
if (!reactFlowBounds) return
|
||||
|
||||
@@ -212,29 +209,53 @@ function DiagramEditorInner() {
|
||||
y: event.clientY - reactFlowBounds.top,
|
||||
}
|
||||
|
||||
const newNode: Node = {
|
||||
id: `device-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
type: 'device',
|
||||
position,
|
||||
data: {
|
||||
label,
|
||||
deviceType: slug,
|
||||
category,
|
||||
properties: {
|
||||
hostname: null,
|
||||
ip: null,
|
||||
subnet: null,
|
||||
vendor: null,
|
||||
model: null,
|
||||
role: null,
|
||||
vlan: null,
|
||||
notes: null,
|
||||
status: 'unknown',
|
||||
} satisfies DeviceProperties,
|
||||
} satisfies DeviceNodeData,
|
||||
// Handle device drops
|
||||
const deviceRaw = event.dataTransfer.getData('application/reactflow-device')
|
||||
if (deviceRaw) {
|
||||
const { slug, label, category } = JSON.parse(deviceRaw) as { slug: string; label: string; category: string }
|
||||
const newNode: Node = {
|
||||
id: `device-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
type: 'device',
|
||||
position,
|
||||
data: {
|
||||
label,
|
||||
deviceType: slug,
|
||||
category,
|
||||
properties: {
|
||||
hostname: null,
|
||||
ip: null,
|
||||
subnet: null,
|
||||
vendor: null,
|
||||
model: null,
|
||||
role: null,
|
||||
vlan: null,
|
||||
notes: null,
|
||||
status: 'unknown',
|
||||
} satisfies DeviceProperties,
|
||||
} satisfies DeviceNodeData,
|
||||
}
|
||||
setNodes(nds => [...nds, newNode])
|
||||
setIsDirty(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle group drops
|
||||
const groupRaw = event.dataTransfer.getData('application/reactflow-group')
|
||||
if (groupRaw) {
|
||||
const { slug, label } = JSON.parse(groupRaw) as { slug: string; label: string }
|
||||
const newNode: Node = {
|
||||
id: `group-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
type: 'group',
|
||||
position,
|
||||
style: { width: 300, height: 200 },
|
||||
data: {
|
||||
label,
|
||||
groupType: slug,
|
||||
},
|
||||
}
|
||||
setNodes(nds => [...nds, newNode])
|
||||
setIsDirty(true)
|
||||
}
|
||||
setNodes(nds => [...nds, newNode])
|
||||
setIsDirty(true)
|
||||
}, [setNodes])
|
||||
|
||||
const handleNodeUpdate = useCallback((nodeId: string, updates: Partial<DeviceNodeData>) => {
|
||||
@@ -262,6 +283,14 @@ function DiagramEditorInner() {
|
||||
setIsDirty(true)
|
||||
}, [setEdges])
|
||||
|
||||
const handleEdgeTypeChange = useCallback((edgeId: string, edgeType: string) => {
|
||||
setEdges(eds => eds.map(e => {
|
||||
if (e.id !== edgeId) return e
|
||||
return { ...e, type: edgeType }
|
||||
}))
|
||||
setIsDirty(true)
|
||||
}, [setEdges])
|
||||
|
||||
const handleDeleteNode = useCallback((nodeId: string) => {
|
||||
setNodes(nds => nds.filter(n => n.id !== nodeId))
|
||||
setEdges(eds => eds.filter(e => e.source !== nodeId && e.target !== nodeId))
|
||||
@@ -405,6 +434,7 @@ function DiagramEditorInner() {
|
||||
selectedEdge={selectedEdge}
|
||||
onNodeUpdate={handleNodeUpdate}
|
||||
onEdgeUpdate={handleEdgeUpdate}
|
||||
onEdgeTypeChange={handleEdgeTypeChange}
|
||||
onDeleteNode={handleDeleteNode}
|
||||
onDeleteEdge={handleDeleteEdge}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user