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