agent-orchestrator/ui-electrobun/src/mainview/SettingsDrawer.svelte
Hibryda 2b1194c809 refactor(electrobun): centralize all shared state into global stores
New stores:
- ui-store.svelte.ts: settingsOpen, paletteOpen, searchOpen, notifDrawerOpen,
  showWizard, settingsCategory, projectToDelete, showAddGroup, newGroupName
- project-tabs-store.svelte.ts: per-project activeTab + activatedTabs via Map

Wired:
- App.svelte: 8 inline $state removed, reads/writes via ui-store
- ProjectCard: activeTab/activatedTabs from project-tabs-store
- SettingsDrawer: activeCategory from ui-store
- CommandPalette: 4 commands call ui-store directly (no CustomEvent dispatch)

Components are now pure view layers reading from stores.
2026-03-23 20:26:07 +01:00

261 lines
7.3 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
import AppearanceSettings from './settings/AppearanceSettings.svelte';
import AgentSettings from './settings/AgentSettings.svelte';
import SecuritySettings from './settings/SecuritySettings.svelte';
import ProjectSettings from './settings/ProjectSettings.svelte';
import OrchestrationSettings from './settings/OrchestrationSettings.svelte';
import AdvancedSettings from './settings/AdvancedSettings.svelte';
import MarketplaceTab from './settings/MarketplaceTab.svelte';
import KeyboardSettings from './settings/KeyboardSettings.svelte';
import RemoteMachinesSettings from './settings/RemoteMachinesSettings.svelte';
import DiagnosticsTab from './settings/DiagnosticsTab.svelte';
import { t } from './i18n.svelte.ts';
import {
getSettingsCategory, setSettingsCategory,
type SettingsCategory,
} from './ui-store.svelte.ts';
interface Props {
open: boolean;
onClose: () => void;
}
let { open, onClose }: Props = $props();
type CategoryId = SettingsCategory;
interface Category {
id: CategoryId;
label: string;
icon: string;
}
const CATEGORY_DEFS: Array<{ id: CategoryId; icon: string; key: string }> = [
{ id: 'appearance', icon: '🎨', key: 'settings.appearance' },
{ id: 'agents', icon: '🤖', key: 'settings.agents' },
{ id: 'security', icon: '🔒', key: 'settings.security' },
{ id: 'projects', icon: '📁', key: 'settings.projects' },
{ id: 'orchestration', icon: '⚙', key: 'settings.orchestration' },
{ id: 'machines', icon: '🖥', key: 'settings.machines' },
{ id: 'keyboard', icon: '⌨', key: 'settings.keyboard' },
{ id: 'advanced', icon: '🔧', key: 'settings.advanced' },
{ id: 'marketplace', icon: '🛒', key: 'settings.marketplace' },
{ id: 'diagnostics', icon: '📊', key: 'settings.diagnostics' },
];
let CATEGORIES = $derived<Category[]>(
CATEGORY_DEFS.map(d => ({ id: d.id, label: t(d.key as any), icon: d.icon }))
);
let activeCategory = $derived(getSettingsCategory());
function handleBackdropClick(e: MouseEvent) {
if (e.target === e.currentTarget) onClose();
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') onClose();
}
</script>
<!-- Fix #11: display toggle instead of {#if} -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="drawer-backdrop"
style:display={open ? 'flex' : 'none'}
role="dialog"
aria-modal="true"
aria-label="Settings"
tabindex="-1"
onclick={handleBackdropClick}
onkeydown={handleKeydown}
>
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<aside class="drawer-panel" onclick={e => e.stopPropagation()} onkeydown={e => e.stopPropagation()}>
<!-- Header -->
<header class="drawer-header">
<h2 class="drawer-title">{t('settings.title')}</h2>
<button class="drawer-close" onclick={onClose} aria-label={t('settings.close')}>×</button>
</header>
<!-- Body: sidebar + content -->
<div class="drawer-body">
<!-- Category nav -->
<nav class="cat-nav" aria-label="Settings categories">
{#each CATEGORIES as cat}
<button
class="cat-btn"
class:active={activeCategory === cat.id}
onclick={() => setSettingsCategory(cat.id)}
aria-current={activeCategory === cat.id ? 'page' : undefined}
>
<span class="cat-icon" aria-hidden="true">{cat.icon}</span>
<span class="cat-label">{cat.label}</span>
</button>
{/each}
</nav>
<!-- Category content -->
<div class="cat-content">
{#if activeCategory === 'appearance'}
<AppearanceSettings />
{:else if activeCategory === 'agents'}
<AgentSettings />
{:else if activeCategory === 'security'}
<SecuritySettings />
{:else if activeCategory === 'projects'}
<ProjectSettings />
{:else if activeCategory === 'orchestration'}
<OrchestrationSettings />
{:else if activeCategory === 'machines'}
<RemoteMachinesSettings />
{:else if activeCategory === 'advanced'}
<AdvancedSettings />
{:else if activeCategory === 'keyboard'}
<KeyboardSettings />
{:else if activeCategory === 'marketplace'}
<MarketplaceTab />
{:else if activeCategory === 'diagnostics'}
<DiagnosticsTab />
{/if}
</div>
</div>
</aside>
</div>
<style>
.drawer-backdrop {
position: fixed;
inset: 0;
z-index: 200;
background: color-mix(in srgb, var(--ctp-crust) 60%, transparent);
display: flex;
align-items: stretch;
}
.drawer-panel {
width: 30rem;
max-width: 95vw;
background: var(--ctp-mantle);
border-right: 1px solid var(--ctp-surface0);
display: flex;
flex-direction: column;
overflow: hidden;
animation: slide-in 0.18s ease-out;
}
@keyframes slide-in {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.drawer-header {
height: 3rem;
display: flex;
align-items: center;
padding: 0 0.875rem;
border-bottom: 1px solid var(--ctp-surface0);
flex-shrink: 0;
}
.drawer-title {
flex: 1;
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
color: var(--ctp-text);
}
.drawer-close {
width: 1.75rem;
height: 1.75rem;
background: transparent;
border: none;
border-radius: 0.3rem;
color: var(--ctp-overlay1);
font-size: 1.125rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.12s, color 0.12s;
}
.drawer-close:hover {
background: var(--ctp-surface0);
color: var(--ctp-text);
}
.drawer-body {
flex: 1;
min-height: 0;
display: flex;
overflow: hidden;
}
.cat-nav {
width: 8.5rem;
flex-shrink: 0;
background: var(--ctp-crust);
border-right: 1px solid var(--ctp-surface0);
display: flex;
flex-direction: column;
padding: 0.375rem 0;
gap: 0.0625rem;
overflow-y: auto;
}
.cat-nav::-webkit-scrollbar { display: none; }
.cat-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: transparent;
border: none;
color: var(--ctp-subtext0);
font-family: var(--ui-font-family);
font-size: 0.8125rem;
cursor: pointer;
text-align: left;
border-radius: 0;
transition: background 0.1s, color 0.1s;
}
.cat-btn:hover {
background: var(--ctp-surface0);
color: var(--ctp-text);
}
.cat-btn.active {
background: var(--ctp-surface0);
color: var(--ctp-text);
border-left: 2px solid var(--ctp-mauve);
padding-left: calc(0.75rem - 2px);
}
.cat-icon {
font-size: 0.875rem;
flex-shrink: 0;
}
.cat-label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.cat-content {
flex: 1;
min-width: 0;
overflow-y: auto;
padding: 0.875rem;
}
.cat-content::-webkit-scrollbar { width: 0.375rem; }
.cat-content::-webkit-scrollbar-track { background: transparent; }
.cat-content::-webkit-scrollbar-thumb { background: var(--ctp-surface1); border-radius: 0.25rem; }
</style>