feat(phase3): PWA, auto-discovery, bookmarklet, multi-tab sync
- PWA: manifest, service worker (cache-first static, network-first API), offline page, install prompt banner - Auto-discovery: Docker socket + Traefik API scanning, approval UI - Quick-add bookmarklet: popup-based add page, favicon auto-detect - Multi-tab sync: BroadcastChannel for theme + data changes - i18n translations for all new strings (EN/RU)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { broadcastThemeChange } from '$lib/utils/broadcastSync.js';
|
||||
|
||||
const THEME_STORAGE_KEY = 'wal-theme-mode';
|
||||
const PRIMARY_HUE_KEY = 'wal-primary-hue';
|
||||
const PRIMARY_SAT_KEY = 'wal-primary-sat';
|
||||
@@ -36,6 +38,7 @@ class ThemeStore {
|
||||
backgroundType = $state<BackgroundType>('mesh');
|
||||
|
||||
#systemPreference: 'dark' | 'light' = 'dark';
|
||||
#suppressBroadcast = false;
|
||||
|
||||
resolvedMode = $derived<'dark' | 'light'>(
|
||||
this.mode === 'system' ? this.#systemPreference : this.mode
|
||||
@@ -98,6 +101,20 @@ class ThemeStore {
|
||||
html.style.setProperty('--primary-h', String(this.primaryHue));
|
||||
html.style.setProperty('--primary-s', `${this.primarySaturation}%`);
|
||||
});
|
||||
|
||||
// Broadcast theme changes to other tabs
|
||||
$effect(() => {
|
||||
// Read all reactive values to track them
|
||||
const snapshot = {
|
||||
mode: this.mode,
|
||||
primaryHue: this.primaryHue,
|
||||
primarySaturation: this.primarySaturation,
|
||||
backgroundType: this.backgroundType
|
||||
};
|
||||
if (typeof window === 'undefined') return;
|
||||
if (this.#suppressBroadcast) return;
|
||||
broadcastThemeChange(snapshot);
|
||||
});
|
||||
}
|
||||
|
||||
cycleMode() {
|
||||
@@ -119,6 +136,27 @@ class ThemeStore {
|
||||
this.primarySaturation = Math.max(0, Math.min(100, saturation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply theme values received from another tab via BroadcastChannel.
|
||||
* Suppresses re-broadcasting to avoid echo loops.
|
||||
*/
|
||||
applyFromBroadcast(values: {
|
||||
mode: ThemeMode;
|
||||
primaryHue: number;
|
||||
primarySaturation: number;
|
||||
backgroundType: BackgroundType;
|
||||
}) {
|
||||
this.#suppressBroadcast = true;
|
||||
this.mode = values.mode;
|
||||
this.primaryHue = values.primaryHue;
|
||||
this.primarySaturation = values.primarySaturation;
|
||||
this.backgroundType = values.backgroundType;
|
||||
// Re-enable on next microtask so the effect reads suppressBroadcast=true
|
||||
queueMicrotask(() => {
|
||||
this.#suppressBroadcast = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply non-null server-stored user preferences over localStorage defaults.
|
||||
* Call from +layout.svelte when user data is available.
|
||||
|
||||
Reference in New Issue
Block a user