feat(launcher): migrate cfg menu to 7-tab sidebar layout (v3.1)
Replaces the 3-tab horizontal bar (Setup / Device / Dev) with a vertical
sidebar navigation matching the v3.1 design spec. New tab structure:
General — App Modes, Screen Saver (operator-facing)
Connectivity — Remote Controller & WebSocket
Sync & Health — Sync Timers, System Health
Native OS — OS controls (native or edit_mode preview)
Wallpaper — Desktop wallpaper settings
Advanced — Launch Timing, Updates (edit_mode only)
Maintenance — Local Resets, Debug Panel (edit_mode only)
Layout changes:
- Sidebar nav (w-48) + scrollable main content area replace inline tab bar
- Tab header shows label + description subtitle
- Technical Mode toggle is now a labeled button (not hidden icon)
- Footer shows Account/Device context; Reload moved to header
- {#key active_tab} wrapper ensures clean component remount on tab switch
- Remove unused icons (SlidersHorizontal, HeartPulse, Timer, CloudDownload)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,19 +37,74 @@ import Launcher_Cfg_Wallpaper from './cfg_components/launcher_cfg_wallpaper.svel
|
|||||||
import {
|
import {
|
||||||
Bug,
|
Bug,
|
||||||
Code,
|
Code,
|
||||||
|
Gamepad2,
|
||||||
|
Image,
|
||||||
|
LayoutGrid,
|
||||||
Monitor,
|
Monitor,
|
||||||
Pencil,
|
Pencil,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Settings,
|
Settings,
|
||||||
SlidersHorizontal,
|
Wrench,
|
||||||
X
|
X
|
||||||
} from '@lucide/svelte';
|
} from '@lucide/svelte';
|
||||||
|
|
||||||
// UI Tab State
|
// UI Tab State
|
||||||
// Tabs are audience-oriented:
|
type TabId =
|
||||||
// setup — what every onsite operator needs (mode preset, display, WS, screen saver)
|
| 'general'
|
||||||
// device — sync engine (all devices) + native/Electron OS controls (native or edit_mode)
|
| 'connectivity'
|
||||||
// dev — developer/debug tools; only useful when edit_mode is on
|
| 'sync'
|
||||||
let active_tab: 'setup' | 'device' | 'dev' = $state('setup');
|
| 'native'
|
||||||
|
| 'wallpaper'
|
||||||
|
| 'advanced'
|
||||||
|
| 'maintenance';
|
||||||
|
let active_tab: TabId = $state('general');
|
||||||
|
|
||||||
|
const TABS = [
|
||||||
|
{
|
||||||
|
id: 'general' as TabId,
|
||||||
|
label: 'General',
|
||||||
|
icon: LayoutGrid,
|
||||||
|
title: 'App Modes, UI & Screen Saver'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'connectivity' as TabId,
|
||||||
|
label: 'Connectivity',
|
||||||
|
icon: Gamepad2,
|
||||||
|
title: 'Remote Controller & WebSocket'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sync' as TabId,
|
||||||
|
label: 'Sync & Health',
|
||||||
|
icon: RefreshCw,
|
||||||
|
title: 'Sync Engine & System Status'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'native' as TabId,
|
||||||
|
label: 'Native OS',
|
||||||
|
icon: Monitor,
|
||||||
|
title: 'Folders, Displays & Native Apps'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'wallpaper' as TabId,
|
||||||
|
label: 'Wallpaper',
|
||||||
|
icon: Image,
|
||||||
|
title: 'Desktop Wallpaper Settings'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'advanced' as TabId,
|
||||||
|
label: 'Advanced',
|
||||||
|
icon: Code,
|
||||||
|
title: 'Launch Timing & Updates',
|
||||||
|
edit_only: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'maintenance' as TabId,
|
||||||
|
label: 'Maintenance',
|
||||||
|
icon: Wrench,
|
||||||
|
title: 'Local Resets & Debug Tools',
|
||||||
|
edit_only: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-Collapse Coordinator
|
* Auto-Collapse Coordinator
|
||||||
@@ -70,185 +125,210 @@ function handle_section_expand(current_key: string) {
|
|||||||
});
|
});
|
||||||
$events_loc.launcher = launcher; // Trigger store update
|
$events_loc.launcher = launcher; // Trigger store update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const current_tab_info = $derived(TABS.find((t) => t.id === active_tab));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="
|
class="bg-surface-100-900 flex h-full min-h-[500px] w-full flex-row overflow-hidden">
|
||||||
flex w-full
|
<!-- Sidebar Navigation -->
|
||||||
max-w-full flex-col items-center justify-start gap-4
|
|
||||||
">
|
|
||||||
<div
|
<div
|
||||||
class="border-surface-500/20 flex w-full flex-row items-center justify-between border-b pb-2">
|
class="bg-surface-500/5 flex w-48 flex-shrink-0 flex-col border-r border-surface-500/20 p-2">
|
||||||
<h2
|
<div class="mb-4 flex items-center gap-2 px-2 py-3">
|
||||||
class="text-center text-lg font-bold text-gray-700 dark:text-gray-200">
|
<Settings size="1.2em" class="text-primary-500" />
|
||||||
<Settings size="1em" class="mr-2 opacity-50" />
|
<span class="text-xs font-bold tracking-widest uppercase opacity-80"
|
||||||
Launcher Configuration
|
>Launcher</span>
|
||||||
</h2>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-1">
|
<nav class="flex flex-grow flex-col gap-1">
|
||||||
<!-- Subtle Edit Mode toggle — intentionally low-key so kiosk operators
|
{#each TABS as tab}
|
||||||
don't stumble on it, but accessible for setup/admin use.
|
{#if !tab.edit_only || $ae_loc.edit_mode}
|
||||||
Glows primary when active; nearly invisible when off. -->
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => (active_tab = tab.id)}
|
||||||
|
class="btn btn-sm justify-start gap-3 px-3 py-2 text-[11px] font-medium transition-all"
|
||||||
|
class:preset-filled-primary={active_tab === tab.id}
|
||||||
|
class:preset-tonal-surface={active_tab !== tab.id}
|
||||||
|
class:hover:preset-tonal-primary={active_tab !==
|
||||||
|
tab.id}>
|
||||||
|
<tab.icon size="1.1em" class="shrink-0" />
|
||||||
|
{tab.label}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="mt-auto flex flex-col gap-2 p-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => ($ae_loc.edit_mode = !$ae_loc.edit_mode)}
|
onclick={() => ($ae_loc.edit_mode = !$ae_loc.edit_mode)}
|
||||||
class="btn btn-icon transition-all duration-300"
|
class="btn btn-sm justify-start gap-3 px-3 py-2 text-[10px] font-medium transition-all"
|
||||||
class:text-primary-500={$ae_loc.edit_mode}
|
class:text-primary-500={$ae_loc.edit_mode}
|
||||||
class:opacity-20={!$ae_loc.edit_mode}
|
class:opacity-50={!$ae_loc.edit_mode}
|
||||||
class:hover:opacity-60={!$ae_loc.edit_mode}
|
title="{$ae_loc.edit_mode ? 'Disable' : 'Enable'} Technical Mode">
|
||||||
title="{$ae_loc.edit_mode ? 'Disable' : 'Enable'} Edit Mode">
|
<Pencil size="1em" />
|
||||||
<Pencil size="0.75em" />
|
<span>Technical Mode</span>
|
||||||
<span class="sr-only">Toggle Edit Mode</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
|
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
|
||||||
class="btn btn-icon hover:bg-surface-500/10 transition-colors dark:text-white">
|
class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 justify-start gap-3 px-3 py-2 text-[10px] transition-all">
|
||||||
<X size="1em" />
|
<X size="1em" />
|
||||||
<span class="sr-only">Close Config</span>
|
<span>Close Settings</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Category Tabs -->
|
<!-- Main Content Area -->
|
||||||
<!-- Dev tab is only shown when Edit Mode is active — keeps the UI uncluttered
|
<div class="flex flex-grow flex-col overflow-hidden">
|
||||||
for onsite operators who never need those tools. Edit Mode is toggled via
|
<!-- Tab Header -->
|
||||||
the pencil icon in the header above. -->
|
<header
|
||||||
<div
|
class="border-surface-500/10 flex flex-shrink-0 items-center justify-between border-b px-6 py-4">
|
||||||
class="bg-surface-500/10 w-full gap-1 rounded-lg p-1"
|
<div class="flex flex-col">
|
||||||
class:grid={true}
|
<h2 class="text-lg font-bold text-gray-800 dark:text-gray-100">
|
||||||
class:grid-cols-2={!$ae_loc.edit_mode}
|
{current_tab_info?.label}
|
||||||
class:grid-cols-3={$ae_loc.edit_mode}>
|
</h2>
|
||||||
<button
|
<p class="text-[10px] opacity-50">{current_tab_info?.title}</p>
|
||||||
type="button"
|
|
||||||
onclick={() => (active_tab = 'setup')}
|
|
||||||
class="btn btn-sm text-[10px] font-bold uppercase transition-all"
|
|
||||||
class:preset-filled-primary={active_tab === 'setup'}
|
|
||||||
class:preset-tonal-surface={active_tab !== 'setup'}
|
|
||||||
title="Display presets, interface toggles, WS controller, screen saver">
|
|
||||||
<SlidersHorizontal size="0.85em" class="mr-1" /> Setup
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={() => (active_tab = 'device')}
|
|
||||||
class="btn btn-sm text-[10px] font-bold uppercase transition-all"
|
|
||||||
class:preset-filled-primary={active_tab === 'device'}
|
|
||||||
class:preset-tonal-surface={active_tab !== 'device'}
|
|
||||||
title="Sync engine, device health & native OS controls">
|
|
||||||
<Monitor size="0.85em" class="mr-1" /> Device
|
|
||||||
</button>
|
|
||||||
{#if $ae_loc.edit_mode}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={() => (active_tab = 'dev')}
|
|
||||||
class="btn btn-sm text-[10px] font-bold uppercase transition-all"
|
|
||||||
class:preset-filled-warning={active_tab === 'dev'}
|
|
||||||
class:preset-tonal-surface={active_tab !== 'dev'}
|
|
||||||
title="Developer & debug tools">
|
|
||||||
<Code size="0.85em" class="mr-1" /> Dev
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tab Content -->
|
|
||||||
<div class="flex min-h-100 w-full flex-col gap-2">
|
|
||||||
<!-- SETUP: everything onsite operators need day-to-day -->
|
|
||||||
{#if active_tab === 'setup'}
|
|
||||||
<div
|
|
||||||
class="animate-in fade-in slide-in-from-left-2 flex flex-col gap-2 duration-300">
|
|
||||||
<!-- Mode preset is the #1 onsite action — give it prominent placement -->
|
|
||||||
<Launcher_Cfg_App_Modes
|
|
||||||
on_expand={() => handle_section_expand('app_modes')} />
|
|
||||||
<Launcher_Cfg_Controller
|
|
||||||
on_expand={() => handle_section_expand('controller')} />
|
|
||||||
<Launcher_Cfg_Screen_Saver
|
|
||||||
on_expand={() => handle_section_expand('screen_saver')} />
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- DEVICE: sync engine first (all devices) + native OS controls (native or edit_mode preview) -->
|
<div class="flex items-center gap-2">
|
||||||
{#if active_tab === 'device'}
|
<button
|
||||||
<div
|
type="button"
|
||||||
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-2 duration-300">
|
onclick={() => location.reload()}
|
||||||
<!-- Sync pause/timers — relevant to every device, not just native -->
|
class="btn btn-sm preset-tonal-surface hover:preset-tonal-secondary text-[10px] font-bold"
|
||||||
<Launcher_Cfg_Sync_Timers
|
title="Reload Application">
|
||||||
on_expand={() => handle_section_expand('sync_timers')} />
|
<RefreshCw size="1em" class="mr-1" /> Reload
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<!-- Native sections: always in Electron; visible in edit_mode for dev preview.
|
<div class="flex-grow overflow-y-auto p-6">
|
||||||
electron_relay.ts guards all calls — safe to import/render without Electron. -->
|
{#key active_tab}
|
||||||
{#if $ae_loc.is_native || $ae_loc.edit_mode}
|
<div class="mx-auto flex w-full max-w-2xl flex-col gap-6">
|
||||||
<Launcher_Cfg_Health
|
{#if active_tab === 'general'}
|
||||||
on_expand={() => handle_section_expand('health')} />
|
<div
|
||||||
<Launcher_Cfg_Native_OS
|
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-6 duration-300">
|
||||||
on_expand={() => handle_section_expand('native_os')} />
|
<Launcher_Cfg_App_Modes
|
||||||
<Launcher_Cfg_Wallpaper
|
on_expand={() =>
|
||||||
on_expand={() => handle_section_expand('wallpaper')} />
|
handle_section_expand('app_modes')} />
|
||||||
<Launcher_Cfg_Launch_Timing
|
<Launcher_Cfg_Screen_Saver
|
||||||
on_expand={() => handle_section_expand('launch_timing')} />
|
on_expand={() =>
|
||||||
{#if $ae_loc.is_native}
|
handle_section_expand('screen_saver')} />
|
||||||
<Launcher_Cfg_Updates
|
</div>
|
||||||
on_expand={() =>
|
|
||||||
handle_section_expand('updates')} />
|
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
|
||||||
<div
|
|
||||||
class="flex flex-col items-center gap-1 py-3 text-center text-xs italic opacity-40">
|
|
||||||
<Monitor size="1.2em" class="opacity-30" />
|
|
||||||
<p>Native OS controls available in Aether Desktop.</p>
|
|
||||||
<p class="text-[9px]">Enable Edit Mode to preview.</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- DEV: developer/debug tools — only reachable when Edit Mode is on -->
|
{#if active_tab === 'connectivity'}
|
||||||
{#if active_tab === 'dev' && $ae_loc.edit_mode}
|
<div
|
||||||
<div
|
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-6 duration-300">
|
||||||
class="animate-in fade-in slide-in-from-right-2 flex flex-col gap-2 duration-300">
|
<Launcher_Cfg_Controller
|
||||||
<Launcher_Cfg_Local_Actions
|
on_expand={() =>
|
||||||
on_expand={() => handle_section_expand('local_actions')} />
|
handle_section_expand('controller')} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Global Actions Footer -->
|
{#if active_tab === 'sync'}
|
||||||
<div
|
<div
|
||||||
class="border-surface-500/20 mt-auto flex w-full flex-col gap-2 border-t pt-4">
|
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-6 duration-300">
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<Launcher_Cfg_Sync_Timers
|
||||||
<!-- Close button — always visible in lower-left as a second dismissal point.
|
on_expand={() =>
|
||||||
Useful in kiosk/iframe mode where the top-right close btn may scroll out of view. -->
|
handle_section_expand('sync_timers')} />
|
||||||
<button
|
<Launcher_Cfg_Health
|
||||||
type="button"
|
on_expand={() =>
|
||||||
onclick={() => ($events_loc.launcher.hide_drawer__cfg = true)}
|
handle_section_expand('health')} />
|
||||||
class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 transition-all">
|
</div>
|
||||||
<X size="0.85em" class="mr-1" />
|
{/if}
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
{#if active_tab === 'native'}
|
||||||
type="button"
|
<div
|
||||||
onclick={() => location.reload()}
|
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-6 duration-300">
|
||||||
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition-all">
|
{#if $ae_loc.is_native || $ae_loc.edit_mode}
|
||||||
<RefreshCw size="0.85em" class="mr-1" />
|
<Launcher_Cfg_Native_OS
|
||||||
Reload
|
on_expand={() =>
|
||||||
</button>
|
handle_section_expand('native_os')} />
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="flex flex-col items-center gap-3 py-12 text-center text-sm italic opacity-40">
|
||||||
|
<Monitor size="3em" class="opacity-20" />
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<p>
|
||||||
|
Native OS controls are only
|
||||||
|
available when running in Aether
|
||||||
|
Desktop.
|
||||||
|
</p>
|
||||||
|
<p class="text-[10px]">
|
||||||
|
Enable Technical Mode to preview
|
||||||
|
layout.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if active_tab === 'wallpaper'}
|
||||||
|
<div
|
||||||
|
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-6 duration-300">
|
||||||
|
<Launcher_Cfg_Wallpaper
|
||||||
|
on_expand={() =>
|
||||||
|
handle_section_expand('wallpaper')} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if active_tab === 'advanced' && $ae_loc.edit_mode}
|
||||||
|
<div
|
||||||
|
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-6 duration-300">
|
||||||
|
<Launcher_Cfg_Launch_Timing
|
||||||
|
on_expand={() =>
|
||||||
|
handle_section_expand('launch_timing')} />
|
||||||
|
{#if $ae_loc.is_native || $ae_loc.edit_mode}
|
||||||
|
<Launcher_Cfg_Updates
|
||||||
|
on_expand={() =>
|
||||||
|
handle_section_expand('updates')} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if active_tab === 'maintenance' && $ae_loc.edit_mode}
|
||||||
|
<div
|
||||||
|
class="animate-in fade-in slide-in-from-bottom-2 flex flex-col gap-6 duration-300">
|
||||||
|
<Launcher_Cfg_Local_Actions
|
||||||
|
on_expand={() =>
|
||||||
|
handle_section_expand('local_actions')} />
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p
|
||||||
|
class="ml-1 text-[9px] font-bold uppercase opacity-50">
|
||||||
|
Debug Access
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() =>
|
||||||
|
($events_loc.launcher.hide_drawer__debug = false)}
|
||||||
|
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 w-full transition-all">
|
||||||
|
<Bug size="1.2em" class="mr-2" />
|
||||||
|
Open Debug Panel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $ae_loc.edit_mode}
|
<!-- Content Footer -->
|
||||||
<button
|
<footer
|
||||||
type="button"
|
class="border-surface-500/10 flex flex-shrink-0 items-center justify-between border-t px-6 py-3">
|
||||||
onclick={() =>
|
<p class="text-[9px] font-bold tracking-widest uppercase opacity-30">
|
||||||
($events_loc.launcher.hide_drawer__debug = false)}
|
Aether Platform • Events Launcher v3.1
|
||||||
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500 w-full transition-all">
|
</p>
|
||||||
<Bug size="0.85em" class="mr-1" />
|
<div class="flex items-center gap-4 text-[9px] opacity-40">
|
||||||
Debug Panel
|
<span>Account: {$ae_loc.account_id || 'None'}</span>
|
||||||
</button>
|
<span
|
||||||
{/if}
|
>Device: {$ae_loc.native_device?.event_device_id ||
|
||||||
|
'Web'}</span
|
||||||
<p
|
>
|
||||||
class="mt-2 text-center text-[9px] font-bold tracking-widest uppercase opacity-40">
|
</div>
|
||||||
Aether Platform • Events Launcher v3.0
|
</footer>
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user