mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-24 01:56:42 +02:00
♻️ refactor: create createAppSettingsStore factory and migrate 3 apps
- Add createAppSettingsStore<T>() factory to @manacore/shared-stores - Migrate todo, contacts, calendar settings stores to use factory - Factory provides: localStorage persistence, type-safe set/update/reset - Optional onSettingsChange callback for cloud sync (used by calendar) - Reduces boilerplate by ~323 LOC across 3 apps Savings: - todo: 259 → 159 LOC (100 LOC) - contacts: 278 → 173 LOC (105 LOC) - calendar: 433 → 315 LOC (118 LOC)
This commit is contained in:
parent
9f4713117c
commit
4681ba8c36
9 changed files with 361 additions and 544 deletions
|
|
@ -10,3 +10,8 @@ export {
|
|||
type NavigationStore,
|
||||
} from './navigation.svelte';
|
||||
export { createThemeStore, type ThemeStore, type ThemeMode } from './theme.svelte';
|
||||
export {
|
||||
createAppSettingsStore,
|
||||
type AppSettingsStore,
|
||||
type AppSettingsStoreOptions,
|
||||
} from './settings.svelte';
|
||||
|
|
|
|||
149
packages/shared-stores/src/settings.svelte.ts
Normal file
149
packages/shared-stores/src/settings.svelte.ts
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* App Settings Store Factory
|
||||
*
|
||||
* Creates a type-safe settings store with localStorage persistence.
|
||||
* Reduces ~200 LOC boilerplate per app to ~20 LOC.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* interface MyAppSettings {
|
||||
* theme: 'light' | 'dark';
|
||||
* sidebarCollapsed: boolean;
|
||||
* immersiveModeEnabled: boolean;
|
||||
* }
|
||||
*
|
||||
* const DEFAULT_SETTINGS: MyAppSettings = {
|
||||
* theme: 'light',
|
||||
* sidebarCollapsed: false,
|
||||
* immersiveModeEnabled: false,
|
||||
* };
|
||||
*
|
||||
* export const settingsStore = createAppSettingsStore('my-app-settings', DEFAULT_SETTINGS);
|
||||
*
|
||||
* // Usage:
|
||||
* settingsStore.settings.theme // 'light'
|
||||
* settingsStore.set('theme', 'dark');
|
||||
* settingsStore.update({ sidebarCollapsed: true });
|
||||
* settingsStore.toggleImmersiveMode(); // If immersiveModeEnabled exists
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export interface AppSettingsStoreOptions<T> {
|
||||
/**
|
||||
* Callback invoked after each settings change.
|
||||
* Use for cloud sync or other side effects.
|
||||
*/
|
||||
onSettingsChange?: (settings: T) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface AppSettingsStore<T extends Record<string, unknown>> {
|
||||
/** Current settings state (reactive) */
|
||||
readonly settings: T;
|
||||
|
||||
/** Get the default settings */
|
||||
getDefaults(): T;
|
||||
|
||||
/** Initialize settings from localStorage (call on mount) */
|
||||
initialize(): void;
|
||||
|
||||
/** Set a single setting value */
|
||||
set<K extends keyof T>(key: K, value: T[K]): void;
|
||||
|
||||
/** Update multiple settings at once */
|
||||
update(updates: Partial<T>): void;
|
||||
|
||||
/** Reset all settings to defaults */
|
||||
reset(): void;
|
||||
|
||||
/** Toggle immersive mode (if immersiveModeEnabled exists in settings) */
|
||||
toggleImmersiveMode(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a settings store with localStorage persistence.
|
||||
*
|
||||
* @param storageKey - localStorage key for persistence
|
||||
* @param defaultSettings - Default settings object
|
||||
* @param options - Optional configuration (onSettingsChange callback)
|
||||
* @returns AppSettingsStore instance
|
||||
*/
|
||||
export function createAppSettingsStore<T extends Record<string, unknown>>(
|
||||
storageKey: string,
|
||||
defaultSettings: T,
|
||||
options?: AppSettingsStoreOptions<T>
|
||||
): AppSettingsStore<T> {
|
||||
// Load settings from localStorage
|
||||
function loadSettings(): T {
|
||||
if (!browser) return { ...defaultSettings };
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
// Merge with defaults to handle new settings added after initial save
|
||||
return { ...defaultSettings, ...parsed };
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to load settings from ${storageKey}:`, e);
|
||||
}
|
||||
|
||||
return { ...defaultSettings };
|
||||
}
|
||||
|
||||
// Save settings to localStorage
|
||||
function saveSettings(newSettings: T): void {
|
||||
if (!browser) return;
|
||||
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify(newSettings));
|
||||
} catch (e) {
|
||||
console.error(`Failed to save settings to ${storageKey}:`, e);
|
||||
}
|
||||
|
||||
// Invoke callback if provided
|
||||
options?.onSettingsChange?.(newSettings);
|
||||
}
|
||||
|
||||
// Reactive state using Svelte 5 runes
|
||||
let settings = $state<T>(loadSettings());
|
||||
|
||||
return {
|
||||
get settings() {
|
||||
return settings;
|
||||
},
|
||||
|
||||
getDefaults() {
|
||||
return { ...defaultSettings };
|
||||
},
|
||||
|
||||
initialize() {
|
||||
if (!browser) return;
|
||||
settings = loadSettings();
|
||||
},
|
||||
|
||||
set<K extends keyof T>(key: K, value: T[K]) {
|
||||
settings = { ...settings, [key]: value };
|
||||
saveSettings(settings);
|
||||
},
|
||||
|
||||
update(updates: Partial<T>) {
|
||||
settings = { ...settings, ...updates };
|
||||
saveSettings(settings);
|
||||
},
|
||||
|
||||
reset() {
|
||||
settings = { ...defaultSettings };
|
||||
saveSettings(settings);
|
||||
},
|
||||
|
||||
toggleImmersiveMode() {
|
||||
if ('immersiveModeEnabled' in settings) {
|
||||
const current = settings.immersiveModeEnabled as boolean;
|
||||
settings = { ...settings, immersiveModeEnabled: !current };
|
||||
saveSettings(settings);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue