fix: context menu dismisses on pane click, ISP in toolbar
Context menu now closes when clicking anywhere on the canvas via onPaneClick prop. ISP device added as built-in toolbar item under Internet section so it's always available without a database entry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ interface NetworkCanvasProps {
|
|||||||
isDragOver?: boolean
|
isDragOver?: boolean
|
||||||
onNodeContextMenu?: (event: React.MouseEvent, node: Node) => void
|
onNodeContextMenu?: (event: React.MouseEvent, node: Node) => void
|
||||||
onPaneContextMenu?: (event: MouseEvent | React.MouseEvent) => void
|
onPaneContextMenu?: (event: MouseEvent | React.MouseEvent) => void
|
||||||
|
onPaneClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NetworkCanvas({
|
export function NetworkCanvas({
|
||||||
@@ -44,6 +45,7 @@ export function NetworkCanvas({
|
|||||||
isDragOver,
|
isDragOver,
|
||||||
onNodeContextMenu,
|
onNodeContextMenu,
|
||||||
onPaneContextMenu,
|
onPaneContextMenu,
|
||||||
|
onPaneClick: onPaneClickProp,
|
||||||
}: NetworkCanvasProps) {
|
}: NetworkCanvasProps) {
|
||||||
const handleSelectionChange = useCallback(({ nodes: selectedNodes, edges: selectedEdges }: { nodes: Node[]; edges: Edge[] }) => {
|
const handleSelectionChange = useCallback(({ nodes: selectedNodes, edges: selectedEdges }: { nodes: Node[]; edges: Edge[] }) => {
|
||||||
if (selectedNodes.length === 1) {
|
if (selectedNodes.length === 1) {
|
||||||
@@ -61,7 +63,8 @@ export function NetworkCanvas({
|
|||||||
const handlePaneClick = useCallback(() => {
|
const handlePaneClick = useCallback(() => {
|
||||||
onNodeSelect(null)
|
onNodeSelect(null)
|
||||||
onEdgeSelect(null)
|
onEdgeSelect(null)
|
||||||
}, [onNodeSelect, onEdgeSelect])
|
onPaneClickProp?.()
|
||||||
|
}, [onNodeSelect, onEdgeSelect, onPaneClickProp])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full w-full" onDragLeave={onDragLeave}>
|
<div className="relative h-full w-full" onDragLeave={onDragLeave}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useMemo, useCallback } from 'react'
|
import { useState, useMemo, useCallback } from 'react'
|
||||||
import { Search, Plus, ChevronDown, ChevronRight, X, LayoutGrid, GripVertical } from 'lucide-react'
|
import { Search, Plus, ChevronDown, ChevronRight, X, LayoutGrid, GripVertical, Globe } from 'lucide-react'
|
||||||
import { getDeviceRenderConfig, CATEGORY_LABELS, CATEGORY_ORDER } from '../nodes/deviceRegistry'
|
import { getDeviceRenderConfig, CATEGORY_LABELS, CATEGORY_ORDER } from '../nodes/deviceRegistry'
|
||||||
import type { DeviceTypeResponse, DeviceTypeCreate } from '@/types'
|
import type { DeviceTypeResponse, DeviceTypeCreate } from '@/types'
|
||||||
import { deviceTypesApi } from '@/api'
|
import { deviceTypesApi } from '@/api'
|
||||||
@@ -122,6 +122,31 @@ export function DeviceToolbar({ deviceTypes, onDeviceTypesChange }: DeviceToolba
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Internet section */}
|
||||||
|
<div className="mb-1 mt-2 border-t border-default pt-2">
|
||||||
|
<div className="flex items-center gap-1 px-1 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
Internet
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<div
|
||||||
|
draggable
|
||||||
|
onDragStart={e => {
|
||||||
|
e.dataTransfer.setData('application/reactflow-device', JSON.stringify({
|
||||||
|
slug: 'isp',
|
||||||
|
label: 'ISP',
|
||||||
|
category: 'cloud',
|
||||||
|
}))
|
||||||
|
e.dataTransfer.effectAllowed = 'move'
|
||||||
|
}}
|
||||||
|
className="flex cursor-grab items-center gap-2 rounded px-2 py-1.5 text-xs text-primary hover:bg-elevated active:cursor-grabbing active:scale-[0.98] transition-transform"
|
||||||
|
>
|
||||||
|
<GripVertical size={12} className="shrink-0 text-muted-foreground/50" />
|
||||||
|
<Globe size={14} style={{ color: 'var(--color-accent)' }} />
|
||||||
|
<span>ISP</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Grouping section */}
|
{/* Grouping section */}
|
||||||
<div className="mb-1 mt-2 border-t border-default pt-2">
|
<div className="mb-1 mt-2 border-t border-default pt-2">
|
||||||
<div className="flex items-center gap-1 px-1 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
<div className="flex items-center gap-1 px-1 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
|||||||
@@ -495,6 +495,7 @@ function DiagramEditorInner() {
|
|||||||
isDragOver={isDragOver}
|
isDragOver={isDragOver}
|
||||||
onNodeContextMenu={handleNodeContextMenu}
|
onNodeContextMenu={handleNodeContextMenu}
|
||||||
onPaneContextMenu={handlePaneContextMenu}
|
onPaneContextMenu={handlePaneContextMenu}
|
||||||
|
onPaneClick={closeContextMenu}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AIAssistPanel
|
<AIAssistPanel
|
||||||
|
|||||||
Reference in New Issue
Block a user