diff --git a/frontend/src/components/network/DiagramHeader.tsx b/frontend/src/components/network/DiagramHeader.tsx
index f4f3b397..9cc59332 100644
--- a/frontend/src/components/network/DiagramHeader.tsx
+++ b/frontend/src/components/network/DiagramHeader.tsx
@@ -1,9 +1,9 @@
import { useState, useCallback, useRef, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
-import { ChevronLeft, Save, Download, FileJson, FileCode, Image, FileText, Undo2, Redo2, MousePointer2, Hand, Share2, Upload } from 'lucide-react'
+import { ChevronLeft, Save, Download, FileJson, FileCode, Image, FileText, Undo2, Redo2, MousePointer2, Hand, Share2, Upload, Cable } from 'lucide-react'
import { cn } from '@/lib/utils'
-export type InteractionMode = 'select' | 'pan'
+export type InteractionMode = 'select' | 'pan' | 'connect'
interface DiagramHeaderProps {
name: string
@@ -156,6 +156,18 @@ export function DiagramHeader({
>
+
diff --git a/frontend/src/components/network/NetworkCanvas.tsx b/frontend/src/components/network/NetworkCanvas.tsx
index ddd34f80..5dc18919 100644
--- a/frontend/src/components/network/NetworkCanvas.tsx
+++ b/frontend/src/components/network/NetworkCanvas.tsx
@@ -99,17 +99,22 @@ export function NetworkCanvas({
edgeTypes={edgeTypes}
defaultEdgeOptions={{ type: 'connection' }}
edgesReconnectable
+ connectOnClick={interactionMode === 'connect'}
reconnectRadius={20}
connectionRadius={24}
deleteKeyCode={['Backspace', 'Delete']}
multiSelectionKeyCode="Shift"
- panOnDrag={interactionMode === 'pan'}
+ panOnDrag={interactionMode === 'pan' ? [0, 1] : [1]}
selectionOnDrag={interactionMode === 'select'}
panActivationKeyCode="Space"
snapToGrid={true}
snapGrid={[20, 20]}
fitView
- className={interactionMode === 'pan' ? 'bg-page cursor-grab active:cursor-grabbing' : 'bg-page'}
+ className={cn(
+ 'bg-page',
+ interactionMode === 'pan' && 'cursor-grab active:cursor-grabbing',
+ interactionMode === 'connect' && 'rf-connect-mode cursor-crosshair',
+ )}
>
diff --git a/frontend/src/components/network/hooks/useCanvasShortcuts.ts b/frontend/src/components/network/hooks/useCanvasShortcuts.ts
index 955ce0be..da33bfb8 100644
--- a/frontend/src/components/network/hooks/useCanvasShortcuts.ts
+++ b/frontend/src/components/network/hooks/useCanvasShortcuts.ts
@@ -47,7 +47,7 @@ export function useCanvasShortcuts({
onUndo: () => void
onRedo: () => void
onNudge: (dx: number, dy: number) => void
- onSetMode: (mode: 'select' | 'pan') => void
+ onSetMode: (mode: 'select' | 'pan' | 'connect') => void
}) {
const { getNodes, fitView, screenToFlowPosition, setNodes: rfSetNodes } = useReactFlow()
const clipboardRef = useRef(null)
@@ -244,7 +244,7 @@ export function useCanvasShortcuts({
return
}
- // Mode shortcuts: V = select, H = pan
+ // Mode shortcuts: V = select, H = pan, C = connect
if (!ctrl && e.key === 'v') {
onSetMode('select')
return
@@ -253,6 +253,10 @@ export function useCanvasShortcuts({
onSetMode('pan')
return
}
+ if (!ctrl && e.key === 'c') {
+ onSetMode('connect')
+ return
+ }
if (ctrl && e.key === 'c') {
e.preventDefault()
diff --git a/frontend/src/components/network/ui/base-handle.tsx b/frontend/src/components/network/ui/base-handle.tsx
index 913d8abb..4f57ca45 100644
--- a/frontend/src/components/network/ui/base-handle.tsx
+++ b/frontend/src/components/network/ui/base-handle.tsx
@@ -11,6 +11,7 @@ export function BaseHandle({ className, children, ...props }: ComponentProps
diff --git a/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx b/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx
index 6b2eab67..689a9039 100644
--- a/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx
+++ b/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx
@@ -842,6 +842,11 @@ function DiagramEditorInner() {
onPaneClick={closeContextMenu}
interactionMode={interactionMode}
/>
+ {interactionMode === 'connect' && (
+
+ Connect mode: drag between device handles. Middle-click and drag to pan.
+
+ )}
{nodes.length === 0 && !loading && (
)}