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.

327 lines โ€ข 13.6 kB
export class ProgressTracker { constructor(store, settingsService) { this.progressBar = null; this.breadcrumbs = null; this.statusText = null; this.timerDisplay = null; this.store = store; this.settingsService = settingsService; this.initializeElements(); } initializeElements() { this.progressBar = document.querySelector('#quiz-progress-bar'); this.breadcrumbs = document.querySelector('.breadcrumbs-container'); this.statusText = document.querySelector('#quiz-progress-text'); this.timerDisplay = document.querySelector('#timer-display'); console.log('๐Ÿ“Š ProgressTracker initialized with specific selectors'); } updateProgress(currentQuestionIndex, totalQuestions, answeredCount) { console.log(`๐Ÿ“ˆ ProgressTracker.updateProgress DEBUG:`, { currentQuestionIndex, totalQuestions, answeredCount, timestamp: new Date().toISOString(), }); this.updateProgressBar(currentQuestionIndex, totalQuestions); this.updateStatusText(currentQuestionIndex, totalQuestions, answeredCount); this.updateBreadcrumbs(currentQuestionIndex, totalQuestions, answeredCount); this.updateCompletionPercentage(answeredCount, totalQuestions); } updateProgressBar(currentIndex, total) { if (!this.progressBar) return; const percentage = ((currentIndex + 1) / total) * 100; this.progressBar.style.transition = 'width 0.3s ease-out'; this.progressBar.style.width = `${percentage}%`; this.progressBar.classList.add('animate-pulse'); setTimeout(() => { this.progressBar?.classList.remove('animate-pulse'); }, 300); console.log(`๐Ÿ“Š Progress bar updated: ${percentage.toFixed(1)}%`); } updateStatusText(currentIndex, total, answeredCount) { if (!this.statusText) return; const state = this.store.getState(); if (state.ui.viewMode === 'single') { this.statusText.textContent = `Question ${currentIndex + 1} of ${total}`; } else { this.statusText.textContent = `Quiz Progress: ${answeredCount}/${total} answered`; } } updateBreadcrumbs(currentIndex, total, answeredCount) { const state = this.store.getState(); if (state.ui.viewMode === 'single' && total > 10) { return; } if (!this.breadcrumbs) { this.createBreadcrumbsContainer(); } if (!this.breadcrumbs) return; const breadcrumbsHtml = this.generateBreadcrumbsHtml(currentIndex, total, answeredCount); this.breadcrumbs.innerHTML = breadcrumbsHtml; console.log('๐Ÿž Breadcrumbs updated'); } createBreadcrumbsContainer() { const container = document.createElement('div'); container.className = 'breadcrumbs-container mt-2 flex items-center justify-center space-x-2'; const insertPoint = this.statusText?.parentNode || document.querySelector('#quiz-container'); if (insertPoint) { if (this.statusText) { insertPoint.insertBefore(container, this.statusText.nextSibling); } else { insertPoint.appendChild(container); } this.breadcrumbs = container; } } generateBreadcrumbsHtml(currentIndex, total, answeredCount) { const state = this.store.getState(); const userAnswers = state.userAnswers; const quiz = state.currentQuiz; if (!quiz) return ''; console.log(`๐Ÿž Breadcrumbs DEBUG:`, { currentIndex, total, answeredCount, userAnswersCount: Object.keys(userAnswers).length, }); const items = []; for (let i = 0; i < total; i++) { const question = quiz.questions[i]; const isAnswered = userAnswers[question.id] !== undefined; const isCurrent = i === currentIndex; let className = 'w-3 h-3 rounded-full transition-all duration-200 '; let clickable = false; console.log(`๐Ÿ”ต Circle ${i + 1}:`, { isCurrent, isAnswered, questionId: question.id, userAnswer: userAnswers[question.id], }); if (isCurrent) { className += 'bg-blue-600 ring-2 ring-blue-300 scale-125'; } else if (isAnswered) { className += 'bg-green-500 hover:bg-green-600 cursor-pointer'; clickable = true; } else { className += 'bg-gray-300 dark:bg-gray-600'; } const clickHandler = clickable ? `onclick="window.quizNavigation?.goToQuestion(${i})"` : ''; items.push(` <div class="${className}" ${clickHandler} title="Question ${i + 1}${isAnswered ? ' (Answered)' : ''}${isCurrent ? ' (Current)' : ''}" aria-label="Question ${i + 1}${isAnswered ? ' answered' : ''}${isCurrent ? ' current' : ''}"> </div> `); } return items.join(''); } updateCompletionPercentage(answeredCount, totalQuestions) { const percentage = Math.round((answeredCount / totalQuestions) * 100); const percentageElements = document.querySelectorAll('.completion-percentage'); percentageElements.forEach(element => { element.textContent = `${percentage}%`; }); if (!this.isAnyModalOpen()) { document.title = `Quiz Progress: ${percentage}% - MCP Quiz Server`; } } isAnyModalOpen() { const settingsModal = document.querySelector('#settings-dropdown'); if (settingsModal && !settingsModal.classList.contains('hidden')) { return true; } const commonModalSelectors = [ '#quiz-start-overlay', '#quiz-results-overlay', '#tour-modal', '.modal-overlay:not(.hidden)', '[data-modal]:not(.hidden)', ]; return commonModalSelectors.some(selector => { const modal = document.querySelector(selector); return (modal && !modal.classList.contains('hidden') && getComputedStyle(modal).display !== 'none'); }); } showCompletionIndicator(score, totalQuestions) { const percentage = Math.round((score / totalQuestions) * 100); console.log(`๐ŸŽฏ Showing completion: ${score}/${totalQuestions} (${percentage}%)`); const overlay = document.createElement('div'); overlay.className = ` fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 animate-fadeIn `; overlay.innerHTML = ` <div class="bg-white dark:bg-gray-800 rounded-lg p-8 max-w-md mx-4 text-center animate-slideInUp"> <div class="text-6xl mb-4"> ${this.getCompletionEmoji(percentage)} </div> <h3 class="text-2xl font-bold mb-2 text-gray-900 dark:text-gray-100"> ${this.getCompletionTitle(percentage)} </h3> <p class="text-lg text-gray-600 dark:text-gray-300 mb-4"> You scored ${score} out of ${totalQuestions} questions </p> <div class="w-full bg-gray-200 rounded-full h-4 mb-4"> <div class="bg-gradient-to-r from-blue-500 to-green-500 h-4 rounded-full transition-all duration-1000 ease-out" style="width: 0%" data-final-width="${percentage}%"> </div> </div> <p class="text-2xl font-bold ${this.getPercentageColor(percentage)}"> ${percentage}% </p> </div> `; document.body.appendChild(overlay); setTimeout(() => { const progressBar = overlay.querySelector('[data-final-width]'); if (progressBar) { progressBar.style.width = progressBar.dataset.finalWidth || '0%'; } }, 500); setTimeout(() => { overlay.classList.add('animate-fadeOut'); setTimeout(() => { overlay.remove(); }, 300); }, 5000); overlay.addEventListener('click', () => { overlay.remove(); }); } showMilestone(milestone, currentProgress) { console.log(`๐ŸŽ–๏ธ Milestone reached: ${milestone}`); const toast = document.createElement('div'); toast.className = ` fixed top-4 right-4 bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg z-40 animate-slideInRight `; toast.innerHTML = ` <div class="flex items-center"> <svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path> </svg> <div> <div class="font-medium">${milestone}</div> <div class="text-sm opacity-90">${currentProgress}% complete</div> </div> </div> `; document.body.appendChild(toast); setTimeout(() => { toast.classList.add('animate-slideOutRight'); setTimeout(() => { toast.remove(); }, 300); }, 3000); } updateTimer(timeLeft, totalTime) { if (!this.timerDisplay) return; const minutes = Math.floor(timeLeft / 60); const seconds = timeLeft % 60; const timeString = `${minutes}:${seconds.toString().padStart(2, '0')}`; this.timerDisplay.textContent = timeString; const percentage = (timeLeft / totalTime) * 100; this.timerDisplay.className = this.timerDisplay.className.replace(/text-(red|yellow|green)-\d+/g, ''); if (percentage < 10) { this.timerDisplay.classList.add('text-red-600', 'animate-pulse'); } else if (percentage < 25) { this.timerDisplay.classList.add('text-yellow-600'); this.timerDisplay.classList.remove('animate-pulse'); } else { this.timerDisplay.classList.add('text-green-600'); this.timerDisplay.classList.remove('animate-pulse'); } } getCompletionEmoji(percentage) { if (percentage >= 90) return '๐Ÿ†'; if (percentage >= 80) return '๐ŸŽ‰'; if (percentage >= 70) return '๐Ÿ‘'; if (percentage >= 60) return '๐Ÿ‘Œ'; return '๐Ÿ’ช'; } getCompletionTitle(percentage) { if (percentage >= 90) return 'Outstanding!'; if (percentage >= 80) return 'Great Job!'; if (percentage >= 70) return 'Well Done!'; if (percentage >= 60) return 'Good Work!'; return 'Keep Practicing!'; } getPercentageColor(percentage) { if (percentage >= 80) return 'text-green-600'; if (percentage >= 60) return 'text-yellow-600'; return 'text-red-600'; } reset() { if (this.progressBar) { this.progressBar.style.width = '0%'; } if (this.statusText) { this.statusText.textContent = 'Ready to start'; } if (this.breadcrumbs) { this.breadcrumbs.innerHTML = ''; } if (this.timerDisplay) { this.timerDisplay.textContent = ''; this.timerDisplay.className = this.timerDisplay.className.replace(/text-(red|yellow|green)-\d+|animate-pulse/g, ''); } if (!this.isAnyModalOpen()) { document.title = 'MCP Quiz Server'; } console.log('๐Ÿ”„ Progress tracker reset'); } refreshDocumentTitle() { const state = this.store.getState(); if (state.currentQuiz && state.currentQuiz.questions && state.quiz.currentQuestionIndex !== -1) { const totalQuestions = state.currentQuiz.questions.length; const answeredCount = Object.keys(state.userAnswers).length; const percentage = Math.round((answeredCount / totalQuestions) * 100); if (!this.isAnyModalOpen()) { document.title = `Quiz Progress: ${percentage}% - MCP Quiz Server`; } } else { if (!this.isAnyModalOpen()) { document.title = 'MCP Quiz Server'; } } } cleanup() { const overlays = document.querySelectorAll('.fixed.inset-0'); overlays.forEach(overlay => { if (overlay.querySelector('.completion-percentage, .animate-slideInUp')) { overlay.remove(); } }); const toasts = document.querySelectorAll('.fixed.top-4.right-4'); toasts.forEach(toast => toast.remove()); this.reset(); console.log('๐Ÿงน ProgressTracker cleanup completed'); } } //# sourceMappingURL=ProgressTracker.js.map