From e69ce4e0baea8fb75dd6f5838204ebc911483989 Mon Sep 17 00:00:00 2001 From: Hugh Kaznowski Date: Sat, 5 Sep 2026 18:17:23 +0100 Subject: [PATCH] Replace the Presets side panel with an inline profile dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Presets is no longer a collapsible NavMenu tab. It's now a compact dropdown in the sidebar listing profiles, with a "+" button that prompts for a name and creates+activates a copy of the current profile, and a "×" button that deletes the active profile (disabled for Default). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o --- webapp/src/components/NavMenu.tsx | 160 +++++++++++------------------- 1 file changed, 59 insertions(+), 101 deletions(-) diff --git a/webapp/src/components/NavMenu.tsx b/webapp/src/components/NavMenu.tsx index 1d0eb7f..7a1aa31 100644 --- a/webapp/src/components/NavMenu.tsx +++ b/webapp/src/components/NavMenu.tsx @@ -7,11 +7,10 @@ import { DocsPanel } from './Docs' import { HelpIcon } from './HelpIcon' import { BUILTIN_THEMES, downloadThemeFile, generateThemeCss, parseThemeCss } from '../lib/themes' -type PanelName = 'settings' | 'presets' | 'docs' | null +type PanelName = 'settings' | 'docs' | null const PANEL_TITLES: Record, string> = { settings: 'Settings', - presets: 'Presets', docs: 'Docs', } @@ -59,17 +58,9 @@ export function NavMenu({ stats }: { stats: Stats | null }) {
ottyl
-
)} @@ -139,6 +128,62 @@ export function NavMenu({ stats }: { stats: Stats | null }) { ) } +/// Replaces the old Presets side-panel tab: a compact dropdown of profiles plus "+" (create a copy of +/// the active profile, prompting for its name) and "×" (delete the active profile — disabled for +/// `Default`, which can never be deleted). +function PresetsDropdown({ panel }: { panel: ReturnType }) { + const activeProfile = panel.settings?.active_profile ?? null + + async function handleCreate() { + const name = window.prompt('New profile name (copies the current profile):')?.trim() + if (!name) return + await panel.addProfile(name) + await panel.switchProfile(name) + } + + async function handleDelete() { + if (!activeProfile || activeProfile === DEFAULT_PROFILE_NAME) return + await panel.removeProfile(activeProfile) + } + + return ( +
+ + + + +
+ ) +} + function SettingsPanel({ panel }: { panel: ReturnType }) { const [filename, setFilename] = useState('') const [content, setContent] = useState('') @@ -510,90 +555,3 @@ function SettingsPanel({ panel }: { panel: ReturnType }
) } - -function PresetsPanel({ panel }: { panel: ReturnType }) { - const [newName, setNewName] = useState('') - - async function handleCreate(e: React.FormEvent) { - e.preventDefault() - if (!newName.trim()) return - await panel.addProfile(newName.trim()) - setNewName('') - } - - return ( -
-
- setNewName(e.target.value)} - className="flex-1 rounded border border-border-soft bg-surface-1 px-2 py-1 text-xs text-text placeholder:text-text-subtle focus:border-border-strong focus:outline-none" - /> - -
- - {panel.profiles.length === 0 ? ( -
No profiles yet.
- ) : ( -
    - {panel.profiles.map((profile) => { - const isActive = panel.settings?.active_profile === profile.name - return ( -
  • -
    - - {profile.name === DEFAULT_PROFILE_NAME ? ( - - — - - ) : ( - - )} -
    -
    - {profile.param_count} params · {profile.metric_count} metrics · {profile.dashboard_count} dashboards -
    -
  • - ) - })} -
- )} -

- A new profile starts as a copy of the currently active one (or empty, if none is active). -

-

- Switching profiles hot-swaps the running engine's declared params/metrics immediately — no restart needed. -

-
- ) -}