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.

264 lines โ€ข 11.8 kB
export class FeedbackManager { constructor(settingsService) { this.activeFeedback = new Set(); this.settingsService = settingsService; } showImmediateFeedback(element, isCorrect, questionId) { const settings = this.settingsService.getSettings(); if (!settings.quiz.showImmediateFeedback) { return; } console.log(`๐Ÿ’ก Showing immediate feedback: ${isCorrect ? 'Correct' : 'Incorrect'}`); this.clearFeedback(element); const feedbackClasses = isCorrect ? ['bg-green-100', 'border-green-500', 'dark:bg-green-900/20', 'animate-pulse'] : ['bg-red-100', 'border-red-500', 'dark:bg-red-900/20', 'animate-pulse']; element.classList.add(...feedbackClasses); this.addFeedbackIcon(element, isCorrect); if (settings.quiz.enableCoachingMode) { this.showCoachingFeedback(element, isCorrect, questionId); } if (settings.quiz.showExplanationsAfterAnswer) { setTimeout(() => { this.showExplanation(questionId, isCorrect); }, 800); } this.activeFeedback.add(questionId); setTimeout(() => { element.classList.remove('animate-pulse'); }, 2000); this.announceToScreenReader(isCorrect ? 'Correct answer selected' : 'Incorrect answer selected'); } showExplanation(questionId, wasCorrect) { const settings = this.settingsService.getSettings(); if (!settings.quiz.showExplanationsAfterAnswer) { return; } console.log(`๐Ÿ“– Showing explanation for question: ${questionId}`); const questionContainer = document.querySelector(`[data-question-id="${questionId}"]`); if (!questionContainer) { console.warn('โš ๏ธ Question container not found for explanation'); return; } const existingExplanation = questionContainer.querySelector('.explanation-container'); if (existingExplanation) { return; } const explanationText = this.getExplanationText(questionId); if (!explanationText) { return; } const explanationContainer = document.createElement('div'); explanationContainer.className = ` explanation-container mt-4 p-4 rounded-lg border-l-4 ${wasCorrect ? 'bg-green-50 border-green-400 dark:bg-green-900/10 dark:border-green-500' : 'bg-blue-50 border-blue-400 dark:bg-blue-900/10 dark:border-blue-500'} animate-fadeIn `; explanationContainer.innerHTML = ` <div class="flex items-start"> <div class="flex-shrink-0"> ${wasCorrect ? '<svg class="w-5 h-5 text-green-600 mt-0.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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>' : '<svg class="w-5 h-5 text-blue-600 mt-0.5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>'} </div> <div class="ml-3"> <h4 class="text-sm font-medium ${wasCorrect ? 'text-green-800 dark:text-green-200' : 'text-blue-800 dark:text-blue-200'}"> ${wasCorrect ? 'Great job!' : 'Learn more:'} </h4> <div class="mt-1 text-sm ${wasCorrect ? 'text-green-700 dark:text-green-300' : 'text-blue-700 dark:text-blue-300'}"> ${explanationText} </div> </div> </div> `; questionContainer.appendChild(explanationContainer); setTimeout(() => { explanationContainer.classList.add('opacity-100'); }, 50); } showCoachingFeedback(element, isCorrect, questionId) { const settings = this.settingsService.getSettings(); if (!settings.quiz.enableCoachingMode) { return; } console.log(`๐Ÿƒโ€โ™‚๏ธ Showing coaching feedback for: ${questionId}`); const intensity = settings.quiz.coachingIntensity || 'detailed'; const coachingMessages = this.getCoachingMessages(isCorrect, intensity); const message = coachingMessages[Math.floor(Math.random() * coachingMessages.length)]; this.showCoachingTooltip(element, message, isCorrect); } showCelebration(element, intensity = 'normal') { console.log(`๐ŸŽ‰ Showing ${intensity} celebration`); switch (intensity) { case 'subtle': this.showSubtleCelebration(element); break; case 'enthusiastic': this.showEnthusiasticCelebration(element); break; default: this.showNormalCelebration(element); } } clearFeedback(element) { const feedbackClasses = [ 'bg-green-100', 'border-green-500', 'dark:bg-green-900/20', 'bg-red-100', 'border-red-500', 'dark:bg-red-900/20', 'animate-pulse', ]; element.classList.remove(...feedbackClasses); const feedbackIcon = element.querySelector('.feedback-icon'); if (feedbackIcon) { feedbackIcon.remove(); } } clearAllFeedback() { this.activeFeedback.forEach(questionId => { const questionContainer = document.querySelector(`[data-question-id="${questionId}"]`); if (questionContainer) { const answerElements = questionContainer.querySelectorAll('.quiz-option-card, label'); answerElements.forEach(element => this.clearFeedback(element)); const explanations = questionContainer.querySelectorAll('.explanation-container'); explanations.forEach(explanation => explanation.remove()); } }); this.activeFeedback.clear(); console.log('๐Ÿงน All feedback cleared'); } addFeedbackIcon(element, isCorrect) { const existingIcon = element.querySelector('.feedback-icon'); if (existingIcon) { existingIcon.remove(); } const icon = document.createElement('div'); icon.className = 'feedback-icon absolute top-2 right-2 w-6 h-6 rounded-full flex items-center justify-center'; if (isCorrect) { icon.classList.add('bg-green-500', 'text-white'); icon.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path></svg>'; } else { icon.classList.add('bg-red-500', 'text-white'); icon.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>'; } if (getComputedStyle(element).position === 'static') { element.classList.add('relative'); } element.appendChild(icon); } showCoachingTooltip(element, message, isCorrect) { const tooltip = document.createElement('div'); tooltip.className = ` coaching-tooltip absolute z-50 px-3 py-2 text-sm rounded-lg shadow-lg max-w-xs ${isCorrect ? 'bg-green-600 text-white' : 'bg-blue-600 text-white'} animate-fadeIn `; tooltip.textContent = message; const rect = element.getBoundingClientRect(); tooltip.style.top = `${rect.bottom + 8}px`; tooltip.style.left = `${rect.left}px`; document.body.appendChild(tooltip); setTimeout(() => { tooltip.remove(); }, 3000); } getExplanationText(questionId) { return "This explanation would be loaded from the question's explanation field."; } getCoachingMessages(isCorrect, intensity) { if (isCorrect) { switch (intensity) { case 'comprehensive': return ['Excellent work!', 'Outstanding!', 'Perfect!', 'You nailed it!']; case 'basic': return ['Correct', 'Good', 'Right']; default: return ['Great job!', 'Well done!', 'Correct!', 'Nice work!']; } } else { switch (intensity) { case 'comprehensive': return [ 'Not quite right, but keep trying!', 'Good effort! Try again.', 'Close! Give it another shot.', ]; case 'basic': return ['Try again', 'Not correct']; default: return ['Not quite right', 'Try again!', 'Keep going!']; } } } showSubtleCelebration(element) { element.classList.add('animate-bounce'); setTimeout(() => { element.classList.remove('animate-bounce'); }, 1000); } showNormalCelebration(element) { element.classList.add('animate-bounce'); this.createSuccessParticles(element); setTimeout(() => { element.classList.remove('animate-bounce'); }, 1500); } showEnthusiasticCelebration(element) { element.classList.add('animate-bounce'); for (let i = 0; i < 3; i++) { setTimeout(() => { this.createSuccessParticles(element); }, i * 200); } setTimeout(() => { element.classList.remove('animate-bounce'); }, 2000); } createSuccessParticles(element) { const rect = element.getBoundingClientRect(); const particles = ['๐ŸŽ‰', 'โœจ', '๐ŸŒŸ', '๐Ÿ’ซ']; for (let i = 0; i < 5; i++) { const particle = document.createElement('div'); particle.textContent = particles[Math.floor(Math.random() * particles.length)]; particle.className = 'fixed pointer-events-none z-50 text-2xl animate-bounce'; particle.style.left = `${rect.left + Math.random() * rect.width}px`; particle.style.top = `${rect.top + Math.random() * rect.height}px`; document.body.appendChild(particle); setTimeout(() => { particle.style.transform = `translateY(-50px) scale(0)`; particle.style.opacity = '0'; particle.style.transition = 'all 1s ease-out'; setTimeout(() => { particle.remove(); }, 1000); }, 100); } } announceToScreenReader(message) { const announcement = document.createElement('div'); announcement.setAttribute('aria-live', 'polite'); announcement.setAttribute('aria-atomic', 'true'); announcement.className = 'sr-only'; announcement.textContent = message; document.body.appendChild(announcement); setTimeout(() => { document.body.removeChild(announcement); }, 1000); } cleanup() { this.clearAllFeedback(); const tooltips = document.querySelectorAll('.coaching-tooltip'); tooltips.forEach(tooltip => tooltip.remove()); console.log('๐Ÿงน FeedbackManager cleanup completed'); } } //# sourceMappingURL=FeedbackManager.js.map