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; theme?: ThemeConfig;
/** Callback when theme changes */ /** Callback when theme changes */
onThemeChange?: (theme: ThemeConfig) => void; onThemeChange?: (theme: ThemeConfig) => void;
/** User's saved theme slots (up to 6) */
savedThemes?: ThemeConfig[];
/** Callback when saved themes change */
onSavedThemesChange?: (themes: ThemeConfig[]) => void;
} }
let { let {
@ -57,6 +61,8 @@
activeTraits, activeTraits,
theme = DEFAULT_THEME, theme = DEFAULT_THEME,
onThemeChange, onThemeChange,
savedThemes = [],
onSavedThemesChange,
}: Props = $props(); }: Props = $props();
const isPersonalWorkspace = $derived(!collectionId); const isPersonalWorkspace = $derived(!collectionId);
@ -266,6 +272,19 @@
onThemeChange?.(preset); 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 // Click outside
// ========================================================================= // =========================================================================
@ -498,6 +517,29 @@
</button> </button>
{/each} {/each}
</div> </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 --> <!-- Surface selector + sliders -->
<div class="settings-color-group"> <div class="settings-color-group">
@ -811,6 +853,18 @@
} }
.settings-preset:hover { border-color: var(--color-accent, #6366f1); } .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-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 */ /* Per-color controls */
.settings-color-group { margin-bottom: 8px; } .settings-color-group { margin-bottom: 8px; }

View file

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