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
text/typescript
/**
* @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,
},
});
}
}