From 4463007736b5431cd37702dbe4adbc37ca0bf155 Mon Sep 17 00:00:00 2001 From: stroblme Date: Mon, 12 Oct 2026 22:05:39 +0200 Subject: [PATCH] =?UTF-8?q?Dashboard=20palette:=20switch=20dashboard,=20pu?= =?UTF-8?q?blish,=20settings;=20=E2=8C=98S=20publishes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Dashboard/DashboardEditor.tsx | 39 ++++-- .../components/Dashboard/DashboardPalette.tsx | 131 ++++++++++++++++++ .../components/Dashboard/WidgetPalette.tsx | 71 ---------- 3 files changed, 156 insertions(+), 85 deletions(-) create mode 100644 frontend/src/components/Dashboard/DashboardPalette.tsx delete mode 100644 frontend/src/components/Dashboard/WidgetPalette.tsx diff --git a/frontend/src/components/Dashboard/DashboardEditor.tsx b/frontend/src/components/Dashboard/DashboardEditor.tsx index fbdf360..fd4e78c 100644 --- a/frontend/src/components/Dashboard/DashboardEditor.tsx +++ b/frontend/src/components/Dashboard/DashboardEditor.tsx @@ -39,6 +39,7 @@ import { appPath } from "@/lib/portal" import { useShortcuts } from "@/lib/shortcuts" import { cn } from "@/lib/utils" import { handleError } from "@/utils" +import { DashboardPalette } from "./DashboardPalette" import { areaOf, CanvasSurface, @@ -66,7 +67,6 @@ import { PaletteProvider } from "./settings" import { useUi } from "./ui" import { showTitle } from "./ui/core/config" import { LookProvider, useCanvasRoot } from "./ui/core/look" -import { WidgetPalette } from "./WidgetPalette" import { WIDGET_LABELS, WIDGET_SIZES, @@ -335,11 +335,25 @@ export function DashboardEditor({ setSelected(id) } - // ⌘P, as on the flow canvas. Reachable while typing, so a settings field - // having focus does not hide it. + const openSettings = () => { + setSelected(null) + setSettingsOpen(true) + } + + // As on the flow canvas, and reachable while typing for the same reasons. + // Outside edit mode both keys stay the browser's. useShortcuts( - { "mod+p": () => (edit ? setPaletteOpen((open) => !open) : false) }, - ["mod+p"], + { + "mod+p": () => (edit ? setPaletteOpen((open) => !open) : false), + // An edit still waiting for its autosave counts: publishing flushes it. + "mod+s": () => { + if (!edit) return false + if (!publish.isPending && (hasDraft || timer.current)) { + void publishDashboard() + } + }, + }, + ["mod+p", "mod+s"], ) const patch = (id: string, changes: Partial) => @@ -639,14 +653,9 @@ export function DashboardEditor({ "text-muted-foreground", settingsOpen && "text-primary", )} - onClick={() => { - if (settingsOpen) { - setSettingsOpen(false) - return - } - setSelected(null) - setSettingsOpen(true) - }} + onClick={() => + settingsOpen ? setSettingsOpen(false) : openSettings() + } aria-pressed={settingsOpen} aria-label="Dashboard settings" data-testid="edit-dashboard" @@ -718,10 +727,12 @@ export function DashboardEditor({ {edit ? ( <> - void publishDashboard() : undefined} /> !INPUT_WIDGETS.has(kind))], + ["Control something", KINDS.filter((kind) => INPUT_WIDGETS.has(kind))], +] as const + +/** + * ⌘P on a dashboard being edited: add a widget, jump to another dashboard, or + * publish — the flow canvas's palette, for this editor. + */ +export function DashboardPalette({ + open, + onOpenChange, + onAdd, + onSettings, + onPublish, +}: { + open: boolean + onOpenChange: (open: boolean) => void + onAdd: (kind: WidgetKind) => void + onSettings: () => void + /** Absent while there is nothing to publish. */ + onPublish?: () => void +}) { + const navigate = useNavigate() + const { data } = useQuery({ ...dashboardsQueryOptions(), enabled: open }) + + // Unmounted rather than faded, for the reason the flow palette gives. + if (!open) return null + + const dashboards = data?.data ?? [] + + const close = (action: () => void) => { + onOpenChange(false) + action() + } + + return ( + + + + Nothing matches that. + + {GROUPS.map(([heading, kinds]) => ( + + {kinds.map((kind) => ( + close(() => onAdd(kind))} + > + + {WIDGET_LABELS[kind]} + + {WIDGET_DESCRIPTIONS[kind]} + + + + ))} + + ))} + + {dashboards.length > 0 ? ( + + {dashboards.map((dashboard) => ( + + close(() => + // Editing a neighbour is the same job, as on the rail. + navigate({ + to: "/dashboards/$name", + params: { name: dashboard.name }, + search: { edit: true }, + }), + ) + } + > + {dashboard.title || dashboard.name} + + ))} + + ) : null} + + + {onPublish ? ( + close(onPublish)} + > + Publish this dashboard + + ) : null} + close(onSettings)} + > + Dashboard settings + + + + + ) +} diff --git a/frontend/src/components/Dashboard/WidgetPalette.tsx b/frontend/src/components/Dashboard/WidgetPalette.tsx deleted file mode 100644 index f1498b1..0000000 --- a/frontend/src/components/Dashboard/WidgetPalette.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { - CommandDialog, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command" -import { - INPUT_WIDGETS, - WIDGET_DESCRIPTIONS, - WIDGET_LABELS, - type WidgetKind, -} from "./widgets" - -const KINDS = Object.keys(WIDGET_LABELS) as WidgetKind[] - -const GROUPS = [ - ["Show a value", KINDS.filter((kind) => !INPUT_WIDGETS.has(kind))], - ["Control something", KINDS.filter((kind) => INPUT_WIDGETS.has(kind))], -] as const - -/** ⌘P on a dashboard being edited: the flow canvas's add-a-node palette, for widgets. */ -export function WidgetPalette({ - open, - onOpenChange, - onAdd, -}: { - open: boolean - onOpenChange: (open: boolean) => void - onAdd: (kind: WidgetKind) => void -}) { - // Unmounted rather than faded, for the reason the flow palette gives. - if (!open) return null - - return ( - - - - Nothing matches that. - {GROUPS.map(([heading, kinds]) => ( - - {kinds.map((kind) => ( - { - onOpenChange(false) - onAdd(kind) - }} - > - - {WIDGET_LABELS[kind]} - - {WIDGET_DESCRIPTIONS[kind]} - - - - ))} - - ))} - - - ) -}