import { EventEmitter } from '$lib/utils/EventEmitter'; import { get, writable, type Writable } from 'svelte/store'; export interface Settings { accessibility: { textToSpeech: boolean; highContrast: boolean; fontSize: number; lineSpacing: number; speechRate: number; speechPitch: number; speechVolume: number; interruptSpeechOnEnter: boolean; speakAllProfiles: boolean; }; ui: { isDarkMode: boolean; showTimestamps: boolean; showSidebar: boolean; splitViewDirection: 'horizontal' | 'vertical'; inputHistorySize: number; outputBufferSize: number; ansiColor: boolean; font: string; debugGmcp: boolean; globalVolume: number; }; } export class SettingsManager extends EventEmitter { private settings: Settings; private readonly STORAGE_KEY = 'svelte-mud-settings'; private initialized = false; // Create our own stores rather than depending on the ones from mudStore public accessibilitySettings: Writable; public uiSettings: Writable; constructor() { super(); // Initialize with default settings this.settings = { accessibility: { textToSpeech: false, highContrast: false, fontSize: 16, lineSpacing: 1.2, speechRate: 1, speechPitch: 1, speechVolume: 1, interruptSpeechOnEnter: true, speakAllProfiles: true }, ui: { isDarkMode: true, showTimestamps: true, showSidebar: true, splitViewDirection: 'horizontal', inputHistorySize: 100, outputBufferSize: 1000, ansiColor: true, font: 'monospace', debugGmcp: false, globalVolume: 0.7 } }; // Create the stores with default values this.accessibilitySettings = writable(this.settings.accessibility); this.uiSettings = writable(this.settings.ui); // Set up subscriptions to save settings when they change this.accessibilitySettings.subscribe(value => { // Skip during initialization if (!this.initialized) return; if (typeof window !== 'undefined') { // Use a small timeout to batch multiple rapid changes setTimeout(() => this.saveSettings(), 100); } }); this.uiSettings.subscribe(value => { // Skip during initialization if (!this.initialized) return; if (typeof window !== 'undefined') { // Use a small timeout to batch multiple rapid changes setTimeout(() => this.saveSettings(), 100); } }); // Load settings from storage this.loadSettings(); } /** * Load settings from localStorage */ private loadSettings(): void { // Skip during SSR if (typeof window === 'undefined' || typeof localStorage === 'undefined') { console.log('SSR environment detected, skipping settings load'); return; } try { const storedSettings = localStorage.getItem(this.STORAGE_KEY); if (storedSettings) { console.log('Retrieved settings from localStorage'); const parsedSettings = JSON.parse(storedSettings); // Merge with defaults to ensure all properties exist if (parsedSettings && typeof parsedSettings === 'object') { // Update internal settings this.settings = { accessibility: { ...this.settings.accessibility, ...(parsedSettings.accessibility || {}) }, ui: { ...this.settings.ui, ...(parsedSettings.ui || {}) } }; console.log('Loaded settings from localStorage:', this.settings); // Set the stores without triggering the save callback this.accessibilitySettings.set(this.settings.accessibility); this.uiSettings.set(this.settings.ui); } else { console.warn('Invalid settings format found in localStorage'); } } else { console.log('No settings found in localStorage, using defaults'); } // Now that settings are loaded, mark as initialized this.initialized = true; // Notify listeners this.emit('settingsLoaded', this.settings); } catch (error) { console.error('Failed to load settings from localStorage:', error); // Mark as initialized even on error this.initialized = true; } } // Save settings to localStorage public saveSettings(): void { if (typeof window === 'undefined' || !this.initialized) return; try { // Get current values from stores this.settings.accessibility = get(this.accessibilitySettings); this.settings.ui = get(this.uiSettings); localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.settings)); console.log('Saved settings to localStorage'); this.emit('settingsSaved', this.settings); } catch (error) { console.error('Failed to save settings:', error); } } // Update methods public updateAccessibilitySettings(newSettings: Partial): void { this.accessibilitySettings.update(current => ({...current, ...newSettings})); } public updateUiSettings(newSettings: Partial): void { this.uiSettings.update(current => ({...current, ...newSettings})); } // Reset settings to defaults public resetSettings(): void { const defaults = { accessibility: { textToSpeech: false, highContrast: false, fontSize: 16, lineSpacing: 1.2, speechRate: 1, speechPitch: 1, speechVolume: 1, interruptSpeechOnEnter: true, speakAllProfiles: true }, ui: { isDarkMode: true, showTimestamps: true, showSidebar: true, splitViewDirection: 'horizontal', inputHistorySize: 100, outputBufferSize: 1000, ansiColor: true, font: 'monospace', debugGmcp: false, globalVolume: 0.7 } }; this.accessibilitySettings.set(defaults.accessibility); this.uiSettings.set(defaults.ui); this.emit('settingsReset', defaults); } // Import/export public importSettings(json: string): void { try { const imported = JSON.parse(json); if (typeof imported === 'object' && imported !== null) { // Create a valid settings object with defaults for missing properties const newSettings = { accessibility: { ...this.settings.accessibility, ...(imported.accessibility || {}) }, ui: { ...this.settings.ui, ...(imported.ui || {}) } }; // Update stores this.accessibilitySettings.set(newSettings.accessibility); this.uiSettings.set(newSettings.ui); // Update internal settings this.settings = newSettings; this.emit('settingsImported', newSettings); } } catch (error) { console.error('Failed to import settings:', error); throw new Error('Failed to import settings. Invalid format.'); } } public exportSettings(): string { // Get current values from stores this.settings.accessibility = get(this.accessibilitySettings); this.settings.ui = get(this.uiSettings); return JSON.stringify(this.settings, null, 2); } } // Create a singleton instance export const settingsManager = new SettingsManager();