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