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
JavaScript
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