feat(electrobun): wire persistence — SQLite, 17 themes, font system
Persistence: - bun:sqlite at ~/.config/agor/settings.db (WAL mode, 500ms busy_timeout) - 4 tables: schema_version, settings, projects, custom_themes - 5 RPC handlers: settings.get/set/getAll, projects get/set Theme system (LIVE switching): - All 17 themes ported from Tauri (4 Catppuccin + 7 Editor + 6 Deep Dark) - applyCssVars() sets 26 --ctp-* vars on document.documentElement - Parallel xterm ITheme mapping per theme - theme-store.svelte.ts: Svelte 5 rune store, persists to SQLite Font system: - font-store.svelte.ts: UI/terminal font family + size - Live CSS var application (--ui-font-family/size, --term-font-family/size) - onTermFontChange() callback registry for terminal instances - Persists all 4 font settings to SQLite AppearanceSettings wired: 17-theme grouped dropdown, font steppers Init on startup: restores saved theme + fonts from SQLite
This commit is contained in:
parent
0b9e8b305a
commit
6002a379e4
13 changed files with 1043 additions and 53 deletions
|
|
@ -1,10 +1,7 @@
|
|||
<script lang="ts">
|
||||
const THEMES = [
|
||||
{ id: 'mocha', label: 'Catppuccin Mocha', group: 'Catppuccin' },
|
||||
{ id: 'macchiato', label: 'Catppuccin Macchiato', group: 'Catppuccin' },
|
||||
{ id: 'frappe', label: 'Catppuccin Frappé', group: 'Catppuccin' },
|
||||
{ id: 'latte', label: 'Catppuccin Latte', group: 'Catppuccin' },
|
||||
];
|
||||
import { THEMES, THEME_GROUPS, type ThemeId } from '../themes.ts';
|
||||
import { themeStore } from '../theme-store.svelte.ts';
|
||||
import { fontStore } from '../font-store.svelte.ts';
|
||||
|
||||
const UI_FONTS = [
|
||||
{ value: '', label: 'System Default' },
|
||||
|
|
@ -25,30 +22,70 @@
|
|||
{ value: 'monospace', label: 'monospace' },
|
||||
];
|
||||
|
||||
let themeId = $state('mocha');
|
||||
let uiFont = $state('');
|
||||
let uiFontSize = $state(14);
|
||||
let termFont = $state('');
|
||||
let termFontSize = $state(13);
|
||||
let cursorStyle = $state('block');
|
||||
let cursorBlink = $state(true);
|
||||
let scrollback = $state(1000);
|
||||
// ── Local reactive state (mirrors store) ───────────────────────────────────
|
||||
let themeId = $state<ThemeId>(themeStore.currentTheme);
|
||||
let uiFont = $state(fontStore.uiFontFamily);
|
||||
let uiFontSize = $state(fontStore.uiFontSize);
|
||||
let termFont = $state(fontStore.termFontFamily);
|
||||
let termFontSize = $state(fontStore.termFontSize);
|
||||
let cursorStyle = $state('block');
|
||||
let cursorBlink = $state(true);
|
||||
let scrollback = $state(1000);
|
||||
|
||||
// Keep local state in sync when store changes (e.g. after initTheme)
|
||||
$effect(() => { themeId = themeStore.currentTheme; });
|
||||
$effect(() => { uiFont = fontStore.uiFontFamily; });
|
||||
$effect(() => { uiFontSize = fontStore.uiFontSize; });
|
||||
$effect(() => { termFont = fontStore.termFontFamily; });
|
||||
$effect(() => { termFontSize = fontStore.termFontSize; });
|
||||
|
||||
// ── Dropdown open state ────────────────────────────────────────────────────
|
||||
let themeOpen = $state(false);
|
||||
let uiFontOpen = $state(false);
|
||||
let termFontOpen = $state(false);
|
||||
|
||||
// ── Derived labels ─────────────────────────────────────────────────────────
|
||||
let themeLabel = $derived(THEMES.find(t => t.id === themeId)?.label ?? 'Catppuccin Mocha');
|
||||
let uiFontLabel = $derived(UI_FONTS.find(f => f.value === uiFont)?.label ?? 'System Default');
|
||||
let termFontLabel = $derived(TERM_FONTS.find(f => f.value === termFont)?.label ?? 'Default (JetBrains Mono)');
|
||||
|
||||
function closeAll() { themeOpen = false; uiFontOpen = false; termFontOpen = false; }
|
||||
// ── Actions ────────────────────────────────────────────────────────────────
|
||||
|
||||
function handleOutsideClick(e: MouseEvent) {
|
||||
function selectTheme(id: ThemeId): void {
|
||||
themeId = id;
|
||||
themeOpen = false;
|
||||
themeStore.setTheme(id);
|
||||
}
|
||||
|
||||
function selectUIFont(value: string): void {
|
||||
uiFont = value;
|
||||
uiFontOpen = false;
|
||||
fontStore.setUIFont(value, uiFontSize);
|
||||
}
|
||||
|
||||
function selectTermFont(value: string): void {
|
||||
termFont = value;
|
||||
termFontOpen = false;
|
||||
fontStore.setTermFont(value, termFontSize);
|
||||
}
|
||||
|
||||
function adjustUISize(delta: number): void {
|
||||
uiFontSize = Math.max(8, Math.min(24, uiFontSize + delta));
|
||||
fontStore.setUIFont(uiFont, uiFontSize);
|
||||
}
|
||||
|
||||
function adjustTermSize(delta: number): void {
|
||||
termFontSize = Math.max(8, Math.min(24, termFontSize + delta));
|
||||
fontStore.setTermFont(termFont, termFontSize);
|
||||
}
|
||||
|
||||
function closeAll(): void { themeOpen = false; uiFontOpen = false; termFontOpen = false; }
|
||||
|
||||
function handleOutsideClick(e: MouseEvent): void {
|
||||
if (!(e.target as HTMLElement).closest('.dd-wrap')) closeAll();
|
||||
}
|
||||
|
||||
function handleKey(e: KeyboardEvent) {
|
||||
function handleKey(e: KeyboardEvent): void {
|
||||
if (e.key === 'Escape') closeAll();
|
||||
}
|
||||
</script>
|
||||
|
|
@ -64,12 +101,16 @@
|
|||
</button>
|
||||
{#if themeOpen}
|
||||
<ul class="dd-list" role="listbox">
|
||||
{#each THEMES as t}
|
||||
<li class="dd-item" class:sel={themeId === t.id} role="option" aria-selected={themeId === t.id}
|
||||
tabindex="0"
|
||||
onclick={() => { themeId = t.id; themeOpen = false; }}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && (themeId = t.id, themeOpen = false)}
|
||||
>{t.label}</li>
|
||||
{#each THEME_GROUPS as group}
|
||||
<li class="dd-group-label" role="presentation">{group}</li>
|
||||
{#each THEMES.filter(t => t.group === group) as t}
|
||||
<li class="dd-item" class:sel={themeId === t.id}
|
||||
role="option" aria-selected={themeId === t.id}
|
||||
tabindex="0"
|
||||
onclick={() => selectTheme(t.id)}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && selectTheme(t.id)}
|
||||
>{t.label}</li>
|
||||
{/each}
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
|
|
@ -86,19 +127,20 @@
|
|||
{#if uiFontOpen}
|
||||
<ul class="dd-list" role="listbox">
|
||||
{#each UI_FONTS as f}
|
||||
<li class="dd-item" class:sel={uiFont === f.value} role="option" aria-selected={uiFont === f.value}
|
||||
<li class="dd-item" class:sel={uiFont === f.value}
|
||||
role="option" aria-selected={uiFont === f.value}
|
||||
tabindex="0"
|
||||
onclick={() => { uiFont = f.value; uiFontOpen = false; }}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && (uiFont = f.value, uiFontOpen = false)}
|
||||
onclick={() => selectUIFont(f.value)}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && selectUIFont(f.value)}
|
||||
>{f.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="stepper">
|
||||
<button onclick={() => uiFontSize = Math.max(8, uiFontSize - 1)} aria-label="Decrease UI font size">−</button>
|
||||
<button onclick={() => adjustUISize(-1)} aria-label="Decrease UI font size">−</button>
|
||||
<span>{uiFontSize}px</span>
|
||||
<button onclick={() => uiFontSize = Math.min(24, uiFontSize + 1)} aria-label="Increase UI font size">+</button>
|
||||
<button onclick={() => adjustUISize(1)} aria-label="Increase UI font size">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -112,19 +154,20 @@
|
|||
{#if termFontOpen}
|
||||
<ul class="dd-list" role="listbox">
|
||||
{#each TERM_FONTS as f}
|
||||
<li class="dd-item" class:sel={termFont === f.value} role="option" aria-selected={termFont === f.value}
|
||||
<li class="dd-item" class:sel={termFont === f.value}
|
||||
role="option" aria-selected={termFont === f.value}
|
||||
tabindex="0"
|
||||
onclick={() => { termFont = f.value; termFontOpen = false; }}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && (termFont = f.value, termFontOpen = false)}
|
||||
onclick={() => selectTermFont(f.value)}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && selectTermFont(f.value)}
|
||||
>{f.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="stepper">
|
||||
<button onclick={() => termFontSize = Math.max(8, termFontSize - 1)} aria-label="Decrease terminal font size">−</button>
|
||||
<button onclick={() => adjustTermSize(-1)} aria-label="Decrease terminal font size">−</button>
|
||||
<span>{termFontSize}px</span>
|
||||
<button onclick={() => termFontSize = Math.min(24, termFontSize + 1)} aria-label="Increase terminal font size">+</button>
|
||||
<button onclick={() => adjustTermSize(1)} aria-label="Increase terminal font size">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -169,9 +212,16 @@
|
|||
position: absolute; top: calc(100% + 0.125rem); left: 0; right: 0; z-index: 50;
|
||||
list-style: none; margin: 0; padding: 0.25rem;
|
||||
background: var(--ctp-mantle); border: 1px solid var(--ctp-surface1); border-radius: 0.3rem;
|
||||
max-height: 12rem; overflow-y: auto;
|
||||
max-height: 14rem; overflow-y: auto;
|
||||
box-shadow: 0 0.5rem 1rem color-mix(in srgb, var(--ctp-crust) 60%, transparent);
|
||||
}
|
||||
.dd-group-label {
|
||||
padding: 0.25rem 0.5rem 0.125rem;
|
||||
font-size: 0.625rem; font-weight: 700; text-transform: uppercase;
|
||||
letter-spacing: 0.06em; color: var(--ctp-overlay0);
|
||||
border-top: 1px solid var(--ctp-surface0);
|
||||
}
|
||||
.dd-group-label:first-child { border-top: none; }
|
||||
.dd-item {
|
||||
padding: 0.3rem 0.5rem; border-radius: 0.2rem; font-size: 0.8125rem; color: var(--ctp-subtext1);
|
||||
cursor: pointer; outline: none; list-style: none;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue