diff --git a/frontend/src/components/network/CanvasEmptyPrompt.tsx b/frontend/src/components/network/CanvasEmptyPrompt.tsx index 1f532dfe..cbf6a4f5 100644 --- a/frontend/src/components/network/CanvasEmptyPrompt.tsx +++ b/frontend/src/components/network/CanvasEmptyPrompt.tsx @@ -63,7 +63,7 @@ export function CanvasEmptyPrompt({ onGenerate }: CanvasEmptyPromptProps) { if (mode === 'manual') { return (
-
+
@@ -94,7 +94,7 @@ export function CanvasEmptyPrompt({ onGenerate }: CanvasEmptyPromptProps) { } }} > -
+
- ) : ( <> @@ -186,7 +179,7 @@ export function CanvasEmptyPrompt({ onGenerate }: CanvasEmptyPromptProps) { autoFocus className="w-full resize-none rounded-lg border border-default bg-input px-4 py-3 pb-7 text-sm text-primary placeholder:text-muted-foreground focus:border-accent focus:outline-none disabled:opacity-50" /> - + ⌘↵ to generate
diff --git a/frontend/src/components/network/ContextMenu.tsx b/frontend/src/components/network/ContextMenu.tsx index ff798b65..5597ba16 100644 --- a/frontend/src/components/network/ContextMenu.tsx +++ b/frontend/src/components/network/ContextMenu.tsx @@ -55,7 +55,7 @@ export function ContextMenu({ position, actions, onClose }: ContextMenuProps) { return (
{actions.map((action) => ( diff --git a/frontend/src/components/network/DiagramHeader.tsx b/frontend/src/components/network/DiagramHeader.tsx index 4cd3b0cf..8551f506 100644 --- a/frontend/src/components/network/DiagramHeader.tsx +++ b/frontend/src/components/network/DiagramHeader.tsx @@ -5,6 +5,7 @@ import { ChevronLeft, Save, Download, FileJson, Image, FileText } from 'lucide-r interface DiagramHeaderProps { name: string clientName: string | null + isDirty: boolean isSaving: boolean lastSavedAt: Date | null diagramId: string | null @@ -18,6 +19,7 @@ interface DiagramHeaderProps { export function DiagramHeader({ name, clientName, + isDirty, isSaving, lastSavedAt, diagramId, @@ -111,9 +113,11 @@ export function DiagramHeader({
- {lastSavedAt && ( + {isDirty && !isSaving ? ( + Unsaved changes + ) : lastSavedAt ? ( {formatLastSaved()} - )} + ) : null}
@@ -68,7 +81,7 @@ export function AIAssistPanel({ onGenerate, getExistingBounds, hasNodes }: AIAss
- {mode === 'replace' && hasNodes && ( + {needsReplaceConfirm && (

@@ -113,8 +126,32 @@ export function AIAssistPanel({ onGenerate, getExistingBounds, hasNodes }: AIAss {loading ? (

-
- Generating your network diagram... +
+ Generating your network diagram… +
+ ) : needsReplaceConfirm && !replaceConfirm ? ( + + ) : needsReplaceConfirm && replaceConfirm ? ( +
+ +
) : (
- + {deleteConfirm ? ( +
+

Delete this connection?

+
+ + +
+
+ ) : ( + + )}
) @@ -326,13 +351,33 @@ export function PropertiesPanel({
- + {deleteConfirm ? ( +
+

Delete this device?

+
+ + +
+
+ ) : ( + + )}
) diff --git a/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx b/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx index 8635ee4a..f06a646b 100644 --- a/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx +++ b/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx @@ -65,6 +65,7 @@ function DiagramEditorInner() { const canvasRef = useRef(null) const [contextMenu, setContextMenu] = useState(null) + const [pendingDeleteNodeId, setPendingDeleteNodeId] = useState(null) useEffect(() => { isDirtyRef.current = isDirty }, [isDirty]) useEffect(() => { diagramIdRef.current = diagramId }, [diagramId]) @@ -525,6 +526,7 @@ function DiagramEditorInner() { { + const nodeId = contextMenu.nodeId + setContextMenu(null) + if (nodeId) setPendingDeleteNodeId(nodeId) + else deleteSelected() + }, }) : getCanvasMenuActions({ onPaste: pasteNodes, @@ -596,6 +603,25 @@ function DiagramEditorInner() { onClose={closeContextMenu} /> )} + {pendingDeleteNodeId && ( +
+
+ Delete this device? + + +
+
+ )}
) } diff --git a/frontend/src/pages/NetworkDiagrams/index.tsx b/frontend/src/pages/NetworkDiagrams/index.tsx index f9dfc9c6..11a71128 100644 --- a/frontend/src/pages/NetworkDiagrams/index.tsx +++ b/frontend/src/pages/NetworkDiagrams/index.tsx @@ -12,12 +12,12 @@ const OTHER_COLOR = '#4f5666' function TopologyBar({ categoryCounts, nodeCount }: { categoryCounts: Record; nodeCount: number }) { if (nodeCount === 0) return null const sorted = Object.entries(categoryCounts).sort(([, a], [, b]) => b - a) + const tooltipLabel = sorted.map(([cat, count]) => `${count} ${cat}`).join(' · ') return ( -
+
{sorted.map(([cat, count]) => (
(null) + const [confirmArchiveId, setConfirmArchiveId] = useState(null) const clientDropdownRef = useRef(null) useEffect(() => { @@ -101,6 +102,7 @@ export default function NetworkDiagramsPage() { toast.error('Failed to archive') } setMenuOpenId(null) + setConfirmArchiveId(null) }, [loadDiagrams]) const handleImport = useCallback(async () => { @@ -269,25 +271,47 @@ export default function NetworkDiagramsPage() {
{menuOpenId === d.id && ( -
- - - +
+ {confirmArchiveId === d.id ? ( + <> +

Archive this diagram?

+
+ + +
+ + ) : ( + <> + + + + + )}
)}