Merge pull request #71 from Pouzor/feature/double-click-to-edit
feat: double-click node to open edit modal
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user