UNPKG

mcp-quiz-server

Version:

🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.

366 lines (331 loc) 9.31 kB
/** * @moduleName: SettingsManager Service * @version: 1.0.0 * @since: 2025-07-28 * @lastUpdated: 2025-07-28 * @requirementsTraceability: [Add relevant requirements] * @briefDescription: [Add description of functionality] * @contributors: Claude Code Agent */ import { AccessibilitySettings, AppSettings, PerformanceSettings, QuizSettings, UISettings, } from '../types/index'; /** * Singleton service for managing application settings * Handles persistence, validation, and change notifications */ export class SettingsManager { private static instance: SettingsManager; private settings: AppSettings; private changeListeners: ((settings: AppSettings) => void)[] = []; private readonly STORAGE_KEY = 'quiz-platform-settings'; private constructor() { this.settings = this.getDefaultSettings(); this.loadSettings(); } /** * Get singleton instance */ static getInstance(): SettingsManager { if (!SettingsManager.instance) { SettingsManager.instance = new SettingsManager(); } return SettingsManager.instance; } /** * Get default settings configuration */ private getDefaultSettings(): AppSettings { return { ui: { theme: 'system', fontSize: 'medium', navigationVisible: true, reducedMotion: false, animations: true, celebration: true, announceChanges: false, }, quiz: { viewMode: 'single', autoAdvance: { enabled: false, delaySeconds: 3, showCountdown: true, }, showProgress: true, largeButtons: false, preloadQuestions: true, minimalUI: false, }, accessibility: { screenReaderMode: false, keyboardNavigation: true, skipLinks: false, highContrast: false, }, performance: { lazyLoading: true, compactMode: false, disableAnalytics: false, cacheOptimized: true, }, }; } /** * Load settings from localStorage */ private loadSettings(): void { try { const stored = localStorage.getItem(this.STORAGE_KEY); if (stored) { const parsedSettings = JSON.parse(stored); this.settings = this.mergeWithDefaults(parsedSettings); console.log('⚙️ Settings loaded from localStorage:', this.settings); } } catch (error) { console.warn('Failed to load settings from localStorage:', error); this.settings = this.getDefaultSettings(); } } /** * Merge stored settings with defaults to handle new settings */ private mergeWithDefaults(stored: Partial<AppSettings>): AppSettings { const defaults = this.getDefaultSettings(); return { ui: { ...defaults.ui, ...stored.ui }, quiz: { ...defaults.quiz, ...stored.quiz, autoAdvance: { ...defaults.quiz.autoAdvance, ...stored.quiz?.autoAdvance }, }, accessibility: { ...defaults.accessibility, ...stored.accessibility }, performance: { ...defaults.performance, ...stored.performance }, }; } /** * Save settings to localStorage */ private saveSettings(): void { try { localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.settings)); console.log('💾 Settings saved to localStorage'); } catch (error) { console.error('Failed to save settings to localStorage:', error); } } /** * Get current settings */ getSettings(): AppSettings { return { ...this.settings }; } /** * Get specific setting value using dot notation */ getSetting<T>(path: string): T | undefined { return this.getNestedValue(this.settings, path); } /** * Update a specific setting */ updateSetting(path: string, value: any): void { const oldSettings = { ...this.settings }; this.setNestedValue(this.settings, path, value); this.saveSettings(); this.notifyChangeListeners(); console.log(`⚙️ Setting updated: ${path} =`, value); } /** * Apply bulk settings (used for demo configurations) */ applyBulkSettings(newSettings: Partial<AppSettings>): void { const oldSettings = { ...this.settings }; this.settings = this.mergeWithDefaults(newSettings); this.saveSettings(); this.notifyChangeListeners(); console.log('⚙️ Bulk settings applied:', newSettings); } /** * Reset settings to defaults */ resetToDefaults(): void { this.settings = this.getDefaultSettings(); this.saveSettings(); this.notifyChangeListeners(); console.log('🔄 Settings reset to defaults'); } /** * Subscribe to settings changes */ subscribe(listener: (settings: AppSettings) => void): () => void { this.changeListeners.push(listener); // Return unsubscribe function return () => { const index = this.changeListeners.indexOf(listener); if (index > -1) { this.changeListeners.splice(index, 1); } }; } /** * Notify all change listeners */ private notifyChangeListeners(): void { this.changeListeners.forEach(listener => { try { listener(this.getSettings()); } catch (error) { console.error('Error in settings change listener:', error); } }); } /** * Export settings for sharing/backup */ exportSettings(): string { return JSON.stringify(this.settings, null, 2); } /** * Import settings from JSON string */ importSettings(jsonString: string): boolean { try { const importedSettings = JSON.parse(jsonString); this.applyBulkSettings(importedSettings); return true; } catch (error) { console.error('Failed to import settings:', error); return false; } } /** * Validate settings structure */ validateSettings(settings: any): boolean { try { // Basic structure validation return ( typeof settings === 'object' && settings.ui && typeof settings.ui === 'object' && settings.quiz && typeof settings.quiz === 'object' && settings.accessibility && typeof settings.accessibility === 'object' && settings.performance && typeof settings.performance === 'object' ); } catch { return false; } } /** * Get nested object value using dot notation */ private getNestedValue(obj: any, path: string): any { return path.split('.').reduce((current, key) => current?.[key], obj); } /** * Set nested object value using dot notation */ private setNestedValue(obj: any, path: string, value: any): void { const keys = path.split('.'); const lastKey = keys.pop()!; const target = keys.reduce((current, key) => { if (!current[key] || typeof current[key] !== 'object') { current[key] = {}; } return current[key]; }, obj); target[lastKey] = value; } /** * Get settings for a specific category */ getUISettings(): UISettings { return { ...this.settings.ui }; } getQuizSettings(): QuizSettings { return { ...this.settings.quiz }; } getAccessibilitySettings(): AccessibilitySettings { return { ...this.settings.accessibility }; } getPerformanceSettings(): PerformanceSettings { return { ...this.settings.performance }; } /** * Apply accessibility-optimized settings */ applyAccessibilityMode(): void { this.applyBulkSettings({ ui: { theme: 'high-contrast', fontSize: 'large', navigationVisible: true, reducedMotion: true, animations: false, celebration: false, announceChanges: true, }, accessibility: { screenReaderMode: true, keyboardNavigation: true, skipLinks: true, highContrast: true, }, quiz: { viewMode: 'single', autoAdvance: { enabled: false, delaySeconds: 5, showCountdown: true, }, showProgress: true, largeButtons: true, preloadQuestions: true, minimalUI: false, }, }); } /** * Apply performance-optimized settings */ applyPerformanceMode(): void { this.applyBulkSettings({ ui: { theme: 'light', fontSize: 'medium', navigationVisible: false, reducedMotion: true, animations: false, celebration: false, announceChanges: false, }, quiz: { viewMode: 'single', autoAdvance: { enabled: true, delaySeconds: 1, showCountdown: false, }, showProgress: false, largeButtons: false, preloadQuestions: false, minimalUI: true, }, performance: { lazyLoading: true, compactMode: true, disableAnalytics: true, cacheOptimized: true, }, }); } }