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 && ( )}