feat(network): add connect tool and middle-pan

This commit is contained in:
chihlasm
2026-04-14 03:28:07 +00:00
parent 36721eb5af
commit c063952f12
5 changed files with 33 additions and 6 deletions

View File

@@ -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({
>
<Hand size={15} />
</button>
<button
onClick={() => onModeChange('connect')}
title="Connect (C)"
className={cn(
'p-1.5 transition-colors',
interactionMode === 'connect'
? 'bg-elevated text-primary'
: 'text-muted-foreground hover:text-primary hover:bg-elevated/50',
)}
>
<Cable size={15} />
</button>
</div>
<div className="mx-2 h-5 w-px bg-border-default" />

View File

@@ -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',
)}
>
<Background variant={BackgroundVariant.Dots} color="var(--color-border-default)" gap={20} size={1} />
<Controls className="!border-default !bg-card [&>button]:!border-default [&>button]:!bg-card [&>button]:!fill-text-primary" />

View File

@@ -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<ClipboardData | null>(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()

View File

@@ -11,6 +11,7 @@ export function BaseHandle({ className, children, ...props }: ComponentProps<typ
className={cn(
'h-[14px] w-[14px] rounded-full border border-default bg-elevated transition-opacity',
'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',
'[.rf-connect-mode_&]:opacity-100',
className,
)}
>

View File

@@ -842,6 +842,11 @@ function DiagramEditorInner() {
onPaneClick={closeContextMenu}
interactionMode={interactionMode}
/>
{interactionMode === 'connect' && (
<div className="pointer-events-none absolute left-1/2 top-4 z-10 -translate-x-1/2 rounded-full border border-accent/30 bg-card/95 px-3 py-1.5 text-[11px] text-muted-foreground">
Connect mode: drag between device handles. Middle-click and drag to pan.
</div>
)}
{nodes.length === 0 && !loading && (
<CanvasEmptyPrompt onGenerate={handleAIGenerate} />
)}