Merge pull request #71 from Pouzor/feature/double-click-to-edit

feat: double-click node to open edit modal
This commit is contained in:
Remy
2026-04-18 21:55:01 +02:00
committed by GitHub
4 changed files with 33 additions and 4 deletions
+3 -3
View File
@@ -9,7 +9,7 @@ pydantic-settings==2.5.2
python-jose[cryptography]==3.5.0 python-jose[cryptography]==3.5.0
passlib[bcrypt]==1.7.4 passlib[bcrypt]==1.7.4
bcrypt==4.0.1 bcrypt==4.0.1
python-multipart==0.0.22 python-multipart==0.0.26
apscheduler==3.10.4 apscheduler==3.10.4
python-nmap==0.7.1 python-nmap==0.7.1
pyyaml==6.0.2 pyyaml==6.0.2
@@ -21,6 +21,6 @@ zeroconf==0.131.0
# Dev # Dev
ruff==0.6.9 ruff==0.6.9
mypy==1.11.2 mypy==1.11.2
pytest==8.3.3 pytest==9.0.3
pytest-asyncio==0.24.0 pytest-asyncio==1.3.0
pytest-cov==5.0.0 pytest-cov==5.0.0
+5
View File
@@ -343,6 +343,10 @@ export default function App() {
setEditEdgeId(edge.id) setEditEdgeId(edge.id)
}, []) }, [])
const handleNodeDoubleClick = useCallback((node: Node<NodeData>) => {
handleEditNode(node.id)
}, [handleEditNode])
const handleEdgeUpdate = useCallback((data: EdgeData) => { const handleEdgeUpdate = useCallback((data: EdgeData) => {
if (!editEdgeId) return if (!editEdgeId) return
snapshotHistory() snapshotHistory()
@@ -400,6 +404,7 @@ export default function App() {
<CanvasContainer <CanvasContainer
onConnect={handleEdgeConnect} onConnect={handleEdgeConnect}
onEdgeDoubleClick={handleEdgeDoubleClick} onEdgeDoubleClick={handleEdgeDoubleClick}
onNodeDoubleClick={handleNodeDoubleClick}
onNodeDragStart={snapshotHistory} onNodeDragStart={snapshotHistory}
onOpenPending={(deviceId) => { onOpenPending={(deviceId) => {
setHighlightPendingId(undefined) setHighlightPendingId(undefined)
@@ -25,11 +25,12 @@ import type { NodeData, EdgeData } from '@/types'
interface CanvasContainerProps { interface CanvasContainerProps {
onConnect?: (connection: Connection) => void onConnect?: (connection: Connection) => void
onEdgeDoubleClick?: (edge: Edge<EdgeData>) => void onEdgeDoubleClick?: (edge: Edge<EdgeData>) => void
onNodeDoubleClick?: (node: Node<NodeData>) => void
onNodeDragStart?: () => void onNodeDragStart?: () => void
onOpenPending?: (deviceId: string) => void onOpenPending?: (deviceId: string) => void
} }
export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, onNodeDragStart, onOpenPending }: CanvasContainerProps) { export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, onNodeDoubleClick, onNodeDragStart, onOpenPending }: CanvasContainerProps) {
const [lassoMode, setLassoMode] = useState(true) const [lassoMode, setLassoMode] = useState(true)
const { const {
nodes, edges, nodes, edges,
@@ -68,6 +69,10 @@ export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, o
onEdgeDoubleClick?.(edge) onEdgeDoubleClick?.(edge)
}, [onEdgeDoubleClick]) }, [onEdgeDoubleClick])
const handleNodeDoubleClick = useCallback((_: React.MouseEvent, node: Node<NodeData>) => {
onNodeDoubleClick?.(node)
}, [onNodeDoubleClick])
return ( return (
<div className="w-full h-full" style={{ background: theme.colors.canvasBackground }}> <div className="w-full h-full" style={{ background: theme.colors.canvasBackground }}>
<ReactFlow <ReactFlow
@@ -79,6 +84,7 @@ export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, o
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
onPaneClick={onPaneClick} onPaneClick={onPaneClick}
onEdgeDoubleClick={handleEdgeDoubleClick} onEdgeDoubleClick={handleEdgeDoubleClick}
onNodeDoubleClick={handleNodeDoubleClick}
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
edgeTypes={edgeTypes} edgeTypes={edgeTypes}
@@ -104,6 +104,24 @@ describe('CanvasContainer', () => {
}).not.toThrow() }).not.toThrow()
}) })
// ── Node double-click ─────────────────────────────────────────────────────
it('calls onNodeDoubleClick prop when a node is double-clicked', () => {
const onNodeDoubleClick = vi.fn()
const node = makeNode('n1')
render(<CanvasContainer onNodeDoubleClick={onNodeDoubleClick} />)
;(rfProps.onNodeDoubleClick as (...args: unknown[]) => unknown)({} as MouseEvent, node)
expect(onNodeDoubleClick).toHaveBeenCalledWith(node)
})
it('does not throw when onNodeDoubleClick is not provided', () => {
const node = makeNode('n1')
render(<CanvasContainer />)
expect(() => {
;(rfProps.onNodeDoubleClick as (...args: unknown[]) => unknown)({} as MouseEvent, node)
}).not.toThrow()
})
// ── Connection validation ───────────────────────────────────────────────── // ── Connection validation ─────────────────────────────────────────────────
it('isValidConnection returns false for self-connections', () => { it('isValidConnection returns false for self-connections', () => {