Files
svelte-mud/src/lib/settings/SettingsManager.ts

257 lines
7.4 KiB
TypeScript
Raw Normal View History

2025-04-21 18:22:08 +02:00
import { EventEmitter } from '$lib/utils/EventEmitter';
2025-04-22 13:54:57 +02:00
import { get, writable, type Writable } from 'svelte/store';
2025-04-21 18:22:08 +02:00
export interface Settings {
accessibility: {
textToSpeech: boolean;
highContrast: boolean;
fontSize: number;
lineSpacing: number;
speechRate: number;
speechPitch: number;
speechVolume: number;
interruptSpeechOnEnter: boolean;
2025-04-22 18:31:25 +02:00
speakAllProfiles: boolean;
2025-04-21 18:22:08 +02:00
};
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';
2025-04-22 13:54:57 +02:00
private initialized = false;
// Create our own stores rather than depending on the ones from mudStore
public accessibilitySettings: Writable<Settings['accessibility']>;
public uiSettings: Writable<Settings['ui']>;
2025-04-21 18:22:08 +02:00
constructor() {
super();
// Initialize with default settings
this.settings = {
accessibility: {
textToSpeech: false,
highContrast: false,
fontSize: 16,
lineSpacing: 1.2,
speechRate: 1,
speechPitch: 1,
speechVolume: 1,
2025-04-22 18:31:25 +02:00
interruptSpeechOnEnter: true,
speakAllProfiles: true
2025-04-21 18:22:08 +02:00
},
ui: {
isDarkMode: true,
showTimestamps: true,
showSidebar: true,
splitViewDirection: 'horizontal',
inputHistorySize: 100,
outputBufferSize: 1000,
ansiColor: true,
font: 'monospace',
debugGmcp: false,
globalVolume: 0.7
}
};
2025-04-22 13:54:57 +02:00
// 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);
}
});
2025-04-21 18:22:08 +02:00
// Load settings from storage
this.loadSettings();
}
/**
* Load settings from localStorage
*/
private loadSettings(): void {
2025-04-22 13:54:57 +02:00
// Skip during SSR
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
console.log('SSR environment detected, skipping settings load');
return;
2025-04-21 18:22:08 +02:00
}
2025-04-22 13:54:57 +02:00
2025-04-21 18:22:08 +02:00
try {
const storedSettings = localStorage.getItem(this.STORAGE_KEY);
if (storedSettings) {
2025-04-22 13:54:57 +02:00
console.log('Retrieved settings from localStorage');
2025-04-21 18:22:08 +02:00
const parsedSettings = JSON.parse(storedSettings);
// Merge with defaults to ensure all properties exist
2025-04-22 13:54:57 +02:00
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');
}
2025-04-21 18:22:08 +02:00
} else {
console.log('No settings found in localStorage, using defaults');
}
2025-04-22 13:54:57 +02:00
// Now that settings are loaded, mark as initialized
this.initialized = true;
// Notify listeners
this.emit('settingsLoaded', this.settings);
2025-04-21 18:22:08 +02:00
} catch (error) {
console.error('Failed to load settings from localStorage:', error);
2025-04-22 13:54:57 +02:00
// Mark as initialized even on error
this.initialized = true;
2025-04-21 18:22:08 +02:00
}
}
2025-04-22 13:54:57 +02:00
// Save settings to localStorage
2025-04-21 18:22:08 +02:00
public saveSettings(): void {
2025-04-22 13:54:57 +02:00
if (typeof window === 'undefined' || !this.initialized) return;
2025-04-21 18:22:08 +02:00
try {
// Get current values from stores
2025-04-22 13:54:57 +02:00
this.settings.accessibility = get(this.accessibilitySettings);
this.settings.ui = get(this.uiSettings);
2025-04-21 18:22:08 +02:00
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.settings));
2025-04-22 13:54:57 +02:00
console.log('Saved settings to localStorage');
2025-04-21 18:22:08 +02:00
this.emit('settingsSaved', this.settings);
} catch (error) {
2025-04-22 13:54:57 +02:00
console.error('Failed to save settings:', error);
2025-04-21 18:22:08 +02:00
}
}
2025-04-22 13:54:57 +02:00
// Update methods
2025-04-21 18:22:08 +02:00
public updateAccessibilitySettings(newSettings: Partial<Settings['accessibility']>): void {
2025-04-22 13:54:57 +02:00
this.accessibilitySettings.update(current => ({...current, ...newSettings}));
2025-04-21 18:22:08 +02:00
}
public updateUiSettings(newSettings: Partial<Settings['ui']>): void {
2025-04-22 13:54:57 +02:00
this.uiSettings.update(current => ({...current, ...newSettings}));
2025-04-21 18:22:08 +02:00
}
2025-04-22 13:54:57 +02:00
// Reset settings to defaults
2025-04-21 18:22:08 +02:00
public resetSettings(): void {
2025-04-22 13:54:57 +02:00
const defaults = {
2025-04-21 18:22:08 +02:00
accessibility: {
textToSpeech: false,
highContrast: false,
fontSize: 16,
lineSpacing: 1.2,
speechRate: 1,
speechPitch: 1,
speechVolume: 1,
2025-04-22 18:31:25 +02:00
interruptSpeechOnEnter: true,
speakAllProfiles: true
2025-04-21 18:22:08 +02:00
},
ui: {
isDarkMode: true,
showTimestamps: true,
showSidebar: true,
splitViewDirection: 'horizontal',
inputHistorySize: 100,
outputBufferSize: 1000,
ansiColor: true,
font: 'monospace',
debugGmcp: false,
globalVolume: 0.7
}
};
2025-04-22 13:54:57 +02:00
this.accessibilitySettings.set(defaults.accessibility);
this.uiSettings.set(defaults.ui);
this.emit('settingsReset', defaults);
2025-04-21 18:22:08 +02:00
}
2025-04-22 13:54:57 +02:00
// Import/export
2025-04-21 18:22:08 +02:00
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
2025-04-22 13:54:57 +02:00
const newSettings = {
2025-04-21 18:22:08 +02:00
accessibility: {
...this.settings.accessibility,
...(imported.accessibility || {})
},
ui: {
...this.settings.ui,
...(imported.ui || {})
}
};
// Update stores
2025-04-22 13:54:57 +02:00
this.accessibilitySettings.set(newSettings.accessibility);
this.uiSettings.set(newSettings.ui);
2025-04-21 18:22:08 +02:00
2025-04-22 13:54:57 +02:00
// Update internal settings
2025-04-21 18:22:08 +02:00
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
2025-04-22 13:54:57 +02:00
this.settings.accessibility = get(this.accessibilitySettings);
this.settings.ui = get(this.uiSettings);
2025-04-21 18:22:08 +02:00
return JSON.stringify(this.settings, null, 2);
}
}
// Create a singleton instance
export const settingsManager = new SettingsManager();