Egendefinerte tema-slots: lagre opptil 6 temaer for gjenbruk

Rad 1: 8 forhåndsdefinerte presets (faste)
Rad 2: opptil 6 bruker-slots — klikk + for å lagre gjeldende tema,
klikk for å bruke, høyreklikk for å slette.
Lagres i workspace-metadata.savedThemes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-19 07:11:09 +00:00
parent ebcacf4847
commit a2fc8609d4
2 changed files with 66 additions and 0 deletions

View file

@ -42,6 +42,10 @@
theme?: ThemeConfig;
/** Callback when theme changes */
onThemeChange?: (theme: ThemeConfig) => void;
/** User's saved theme slots (up to 6) */
savedThemes?: ThemeConfig[];
/** Callback when saved themes change */
onSavedThemesChange?: (themes: ThemeConfig[]) => void;
}
let {
@ -57,6 +61,8 @@
activeTraits,
theme = DEFAULT_THEME,
onThemeChange,
savedThemes = [],
onSavedThemesChange,
}: Props = $props();
const isPersonalWorkspace = $derived(!collectionId);
@ -266,6 +272,19 @@
onThemeChange?.(preset);
}
const MAX_SAVED = 6;
function saveCurrentTheme() {
if (savedThemes.length >= MAX_SAVED) return;
const newSaved = [...savedThemes, { ...theme }];
onSavedThemesChange?.(newSaved);
}
function deleteSavedTheme(index: number) {
const newSaved = savedThemes.filter((_, i) => i !== index);
onSavedThemesChange?.(newSaved);
}
// =========================================================================
// Click outside
// =========================================================================
@ -498,6 +517,29 @@
</button>
{/each}
</div>
<div class="settings-presets settings-user-themes">
{#each savedThemes as saved, i (i)}
<button
class="settings-preset settings-saved-preset"
title="Lagret tema {i + 1} (høyreklikk for å slette)"
onclick={() => applyPreset(saved)}
oncontextmenu={(e) => { e.preventDefault(); deleteSavedTheme(i); }}
>
<span class="settings-preset-swatch"
style:background={presetAccentCSS(saved)}
>{i + 1}</span>
</button>
{/each}
{#if savedThemes.length < MAX_SAVED}
<button
class="settings-preset settings-save-btn"
title="Lagre gjeldende tema"
onclick={saveCurrentTheme}
>
<span class="settings-preset-swatch settings-save-swatch">+</span>
</button>
{/if}
</div>
<!-- Surface selector + sliders -->
<div class="settings-color-group">
@ -811,6 +853,18 @@
}
.settings-preset:hover { border-color: var(--color-accent, #6366f1); }
.settings-preset-swatch { width: 28px; height: 28px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; }
.settings-user-themes { margin-top: 4px; }
.settings-saved-preset .settings-preset-swatch { font-size: 11px; font-weight: 600; color: white; text-shadow: 0 1px 2px rgba(0,0,0,0.5); }
.settings-save-swatch {
background: transparent !important;
border: 2px dashed var(--color-border, #2a2a2e);
color: var(--color-text-dim, #5a5a66);
font-size: 16px;
}
.settings-save-btn:hover .settings-save-swatch {
border-color: var(--color-accent, #6366f1);
color: var(--color-text-muted, #8a8a96);
}
/* Per-color controls */
.settings-color-group { margin-bottom: 8px; }

View file

@ -87,6 +87,9 @@
if (typeof meta.gridEnabled === 'boolean') {
gridEnabled = meta.gridEnabled;
}
if (Array.isArray(meta.savedThemes)) {
savedThemes = meta.savedThemes as ThemeConfig[];
}
}
})
.catch((err) => {
@ -110,6 +113,7 @@
let layoutInitialized = $state(false);
let savedCamera = $state<Camera>({ x: 0, y: 0, zoom: 1.0 });
let gridEnabled = $state(false);
let savedThemes = $state<ThemeConfig[]>([]);
// When workspace node appears in store (after creation), load its layout
$effect(() => {
@ -156,6 +160,7 @@
workspace_layout: layout,
camera: savedCamera,
gridEnabled,
savedThemes,
preferences: {
...(currentMeta.preferences ?? {}),
theme: themeToMetadata(currentTheme),
@ -246,6 +251,11 @@
persistMetadata();
}
function handleSavedThemesChange(themes: ThemeConfig[]) {
savedThemes = themes;
persistMetadata();
}
function loadThemeFromMeta(meta: Record<string, unknown>) {
const loaded = loadThemeFromMetadata(meta);
if (loaded) {
@ -347,6 +357,8 @@
activeTraits={activeLayoutTraits}
theme={currentTheme}
onThemeChange={handleThemeChange}
{savedThemes}
onSavedThemesChange={handleSavedThemesChange}
/>
<!-- Main content -->