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.

216 lines โ€ข 8.72 kB
import { AppStore } from '../store/AppStore'; export class QuizProgressManager { constructor(quiz, settings, navigationController, feedbackManager) { this.quiz = quiz; this.settings = settings; this.navigationController = navigationController; this.feedbackManager = feedbackManager; this.answers = new Map(); this.quizStartTime = Date.now(); this.currentQuestionIndex = 0; this.autoAdvanceTimeouts = new Set(); this.recoverProgress(); } handleAnswerSelection(questionId, answer) { console.log(`๐Ÿ“ Answer selected: ${questionId} = ${answer}`); this.answers.set(questionId, answer); this.persistProgress(); if (this.settings.quiz.showImmediateFeedback) { this.showFeedback(questionId, answer); } if (this.settings.quiz.autoAdvanceEnabled && this.isInSingleQuestionMode()) { this.scheduleAutoAdvance(questionId); } } showFeedback(questionId, answer) { const question = this.quiz.questions.find(q => q.id === questionId); if (!question) return; const isCorrect = answer === question.correctAnswer; let feedbackContainer = document.querySelector(`#feedback-container-${questionId}`) || document.querySelector('#feedback-container') || document.querySelector(`[data-question-id="${questionId}"]`); if (feedbackContainer) { if (feedbackContainer.hasAttribute('data-question-id')) { let innerContainer = feedbackContainer.querySelector('.feedback-area'); if (!innerContainer) { innerContainer = document.createElement('div'); innerContainer.className = 'feedback-area mt-4'; feedbackContainer.appendChild(innerContainer); } feedbackContainer = innerContainer; } const feedbackHtml = ` <div class="feedback-display p-4 rounded-lg border-2 mt-4 transition-all duration-300 ${isCorrect ? 'bg-green-50 border-green-200' : 'bg-red-50 border-red-200'}"> <div class="feedback-status flex items-center mb-2"> <span class="text-lg mr-2">${isCorrect ? 'โœ…' : 'โŒ'}</span> <span class="font-medium ${isCorrect ? 'text-green-700' : 'text-red-700'}"> ${isCorrect ? 'Correct!' : 'Incorrect'} </span> </div> ${this.settings.quiz.showExplanationsAfterAnswer && question.explanation ? ` <div class="explanation text-gray-700 mt-2"> <strong>Explanation:</strong> ${question.explanation} </div> ` : ''} </div> `; feedbackContainer.innerHTML = feedbackHtml; const feedbackElement = feedbackContainer.querySelector('.feedback-display'); if (feedbackElement) { feedbackElement.classList.add('animate-fadeIn'); } } else { console.warn(`โš ๏ธ No feedback container found for question ${questionId}`); console.log(`${isCorrect ? 'โœ… Correct!' : 'โŒ Incorrect'} Answer: ${answer}`); } } scheduleAutoAdvance(questionId) { const isLastQuestion = this.isLastQuestion(); const delay = isLastQuestion ? this.settings.quiz.autoAdvanceDelay + 1000 : this.settings.quiz.autoAdvanceDelay; console.log(`โฑ๏ธ Scheduling auto-advance in ${delay}ms (last question: ${isLastQuestion})`); const timeoutId = setTimeout(() => { if (isLastQuestion) { this.submitCompleteQuiz(); } else { this.advanceToNextQuestion(); } this.autoAdvanceTimeouts.delete(timeoutId); }, delay); this.autoAdvanceTimeouts.add(timeoutId); } advanceToNextQuestion() { console.log('โžก๏ธ Auto-advancing to next question'); const feedbackContainer = document.querySelector('#feedback-container'); if (feedbackContainer) { feedbackContainer.innerHTML = ''; } if (this.navigationController && typeof this.navigationController.nextQuestion === 'function') { this.navigationController.nextQuestion(); } else { document.dispatchEvent(new CustomEvent('quiz:next-question')); } this.currentQuestionIndex++; } async submitCompleteQuiz() { console.log('๐Ÿš€ Submitting complete quiz with all answers'); try { const response = await fetch('/quiz/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ quizId: this.quiz.id, answers: Object.fromEntries(this.answers), completionTime: Date.now() - this.quizStartTime, progressData: { questionsAnswered: this.answers.size, totalQuestions: this.quiz.questions.length, autoAdvanceUsed: this.settings.quiz.autoAdvanceEnabled, }, }), }); if (response.ok) { const result = await response.json(); this.clearProgress(); document.dispatchEvent(new CustomEvent('quiz:completed', { detail: { result, answers: Object.fromEntries(this.answers) }, })); return result; } else { throw new Error(`Submission failed: ${response.statusText}`); } } catch (error) { console.error('โŒ Quiz submission failed:', error); throw error; } } persistProgress() { const progress = { answers: [...this.answers], timestamp: Date.now(), quizId: this.quiz.id, currentQuestionIndex: this.currentQuestionIndex, timeSpent: Date.now() - this.quizStartTime, }; try { localStorage.setItem('quiz-progress', JSON.stringify(progress)); console.log(`๐Ÿ’พ Progress saved: ${this.answers.size} answers`); } catch (error) { console.warn('Failed to save progress to localStorage:', error); } } recoverProgress() { try { const progressStr = localStorage.getItem('quiz-progress'); if (!progressStr) return false; const progress = JSON.parse(progressStr); if (progress.quizId !== this.quiz.id) { this.clearProgress(); return false; } this.answers = new Map(progress.answers); this.currentQuestionIndex = progress.currentQuestionIndex || 0; this.quizStartTime = Date.now() - (progress.timeSpent || 0); console.log(`๐Ÿ”„ Progress recovered: ${this.answers.size} answers, question ${this.currentQuestionIndex}`); return true; } catch (error) { console.warn('Failed to recover progress:', error); this.clearProgress(); return false; } } clearProgress() { try { localStorage.removeItem('quiz-progress'); console.log('๐Ÿ—‘๏ธ Progress cleared from localStorage'); } catch (error) { console.warn('Failed to clear progress:', error); } } cancelAutoAdvance() { this.autoAdvanceTimeouts.forEach(timeoutId => { clearTimeout(timeoutId); }); this.autoAdvanceTimeouts.clear(); console.log('โน๏ธ Auto-advance cancelled'); } getAnswers() { return new Map(this.answers); } getCompletionStats() { const answered = this.answers.size; const total = this.quiz.questions.length; return { answered, total, percentage: total > 0 ? Math.round((answered / total) * 100) : 0, }; } isInSingleQuestionMode() { const appStore = AppStore.getInstance(); const state = appStore.getState(); return state.ui.viewMode === 'single'; } isLastQuestion() { return this.currentQuestionIndex >= this.quiz.questions.length - 1; } destroy() { this.cancelAutoAdvance(); this.persistProgress(); console.log('๐Ÿงน QuizProgressManager cleanup completed'); } } //# sourceMappingURL=QuizProgressManager.js.map