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.

242 lines 7.81 kB
export class TimerService { constructor() { this.intervalId = null; this.callbacks = []; this.timerDisplay = null; this.timerContainer = null; this.warningThreshold = 60; this.timerToggle = null; this.timerState = { isRunning: false, timeRemaining: 0, totalTime: 0, isWarning: false, isExpired: false, }; this.initializeDOM(); } static getInstance() { if (!TimerService.instance) { TimerService.instance = new TimerService(); } return TimerService.instance; } initializeDOM() { this.timerDisplay = document.querySelector('#timer-display'); this.timerContainer = document.querySelector('#quiz-timer'); this.timerToggle = document.querySelector('#timer-toggle'); if (this.timerToggle) { this.timerToggle.addEventListener('change', e => { const enabled = e.target.checked; this.toggleTimer(enabled); }); } } subscribe(callback) { this.callbacks.push(callback); return () => { const index = this.callbacks.indexOf(callback); if (index > -1) { this.callbacks.splice(index, 1); } }; } notifySubscribers() { this.callbacks.forEach(callback => callback(this.timerState)); } startTimer(durationInSeconds = 300, warningThreshold = 60) { this.stopTimer(); this.warningThreshold = warningThreshold; this.timerState = { isRunning: true, timeRemaining: durationInSeconds, totalTime: durationInSeconds, isWarning: false, isExpired: false, }; this.showTimer(); this.updateDisplay(); this.intervalId = setInterval(() => { this.tick(); }, 1000); this.notifySubscribers(); } stopTimer() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } this.timerState.isRunning = false; this.hideTimer(); this.notifySubscribers(); } autoStartIfEnabled(settingsService) { const settings = settingsService.getSettings(); if (settings.quiz.useTimer) { console.log('🚀 Auto-starting timer (modernized single setting)'); const timerDuration = settings.quiz.timerDuration || 900; this.startTimer(timerDuration, this.warningThreshold); } } pauseTimer() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } this.timerState.isRunning = false; this.notifySubscribers(); } resumeTimer() { if (!this.timerState.isExpired && this.timerState.timeRemaining > 0) { this.timerState.isRunning = true; this.intervalId = setInterval(() => { this.tick(); }, 1000); this.notifySubscribers(); } } tick() { this.timerState.timeRemaining -= 1; this.timerState.isWarning = this.timerState.timeRemaining <= this.warningThreshold; if (this.timerState.timeRemaining <= 0) { this.timerState.timeRemaining = 0; this.timerState.isExpired = true; this.timerState.isRunning = false; this.stopTimer(); this.handleTimeExpiry(); } this.updateDisplay(); this.notifySubscribers(); } handleTimeExpiry() { let autoSubmit = false; try { const settingsStr = localStorage.getItem('timerSettings'); if (settingsStr) { const settings = JSON.parse(settingsStr); autoSubmit = settings.autoSubmit; } } catch (e) { autoSubmit = false; } const submitButton = document.querySelector('#submit-button'); if (submitButton) { this.showTimeExpiredMessage(); if (autoSubmit) { setTimeout(() => { submitButton.click(); }, 2000); } } } showTimeExpiredMessage() { const message = document.createElement('div'); message.className = 'fixed top-20 left-1/2 transform -translate-x-1/2 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50 animate-bounce'; message.textContent = "⏰ Time's up! " + (this.getAutoSubmitSetting() ? 'Submitting quiz...' : 'Please submit your quiz.'); document.body.appendChild(message); setTimeout(() => { if (document.body.contains(message)) { document.body.removeChild(message); } }, 3000); } getAutoSubmitSetting() { try { const settingsStr = localStorage.getItem('timerSettings'); if (settingsStr) { const settings = JSON.parse(settingsStr); return settings.autoSubmit; } } catch (e) { } return false; } updateDisplay() { if (this.timerDisplay) { this.timerDisplay.textContent = this.formatTime(this.timerState.timeRemaining); if (this.timerState.isWarning) { this.timerDisplay.classList.add('text-red-500', 'font-bold', 'animate-pulse'); } else { this.timerDisplay.classList.remove('text-red-500', 'font-bold', 'animate-pulse'); } } } showTimer() { if (this.timerContainer) { this.timerContainer.classList.remove('hidden'); this.timerContainer.classList.add('flex'); } } hideTimer() { if (this.timerContainer) { this.timerContainer.classList.add('hidden'); this.timerContainer.classList.remove('flex'); } } toggleTimer(enabled) { if (enabled) { this.startTimer(300, this.warningThreshold); } else { this.stopTimer(); } } formatTime(seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; } getState() { return { ...this.timerState }; } isTimerEnabled() { return this.timerToggle?.checked || false; } getTimeRemaining() { return this.timerState.timeRemaining; } getProgressPercentage() { if (this.timerState.totalTime === 0) return 0; return (((this.timerState.totalTime - this.timerState.timeRemaining) / this.timerState.totalTime) * 100); } setWarningThreshold(seconds) { this.warningThreshold = seconds; } getWarningThreshold() { return this.warningThreshold; } saveTimerSettings(settings) { try { localStorage.setItem('timerSettings', JSON.stringify(settings)); } catch (e) { console.warn('Failed to save timer settings:', e); } } loadTimerSettings() { try { const settingsStr = localStorage.getItem('timerSettings'); if (settingsStr) { return JSON.parse(settingsStr); } } catch (e) { console.warn('Failed to load timer settings:', e); } return { enabled: false, duration: 300, warning: 60, autoSubmit: false, }; } } //# sourceMappingURL=TimerService.js.map