/** * IRL Classroom Session JavaScript with WebSocket */ jQuery(document).ready(function($) { let socket = null; let quizData = null; // Connect to WebSocket connectWebSocket(); /** * Connect to WebSocket server */ function connectWebSocket() { socket = io('https://frenchirl.com/ws', { path: '/socket.io/', transports: ['websocket'], reconnection: true, reconnectionDelay: 1000, reconnectionAttempts: 5 }); socket.on('connect', function() { console.log('WebSocket connected'); if (classroomData.isTeacher) { // Join as teacher socket.emit('join_as_teacher', { code: classroomData.sessionCode }); } else { // Students will join after entering name (for now, auto-join) socket.emit('join_session', { code: classroomData.sessionCode, student_name: 'Student-' + Math.floor(Math.random() * 1000) }); } }); socket.on('disconnect', function() { console.log('WebSocket disconnected'); }); socket.on('error', function(data) { console.error('Socket error:', data.message); alert(data.message); }); // Teacher-specific events if (classroomData.isTeacher) { socket.on('teacher_joined', function(data) { console.log('Teacher joined, students:', data.student_count); updateStudentCount(data.student_count); loadQuizData(); }); socket.on('student_joined', function(data) { console.log('Student joined:', data.student_name); updateStudentCount(data.student_count); }); socket.on('student_left', function(data) { console.log('Student left'); updateStudentCount(data.student_count); }); socket.on('blank_completed', function(data) { console.log('Blank completed:', data.blank_id, data.word); if (classroomData.mode === 'collab') { fillBlank(data.blank_id, data.word); updateCollabProgress(); } }); socket.on('leaderboard_update', function(data) { console.log('Leaderboard update:', data.leaderboard); if (classroomData.mode === 'race') { updateLeaderboard(data.leaderboard); } }); } // Student-specific events if (!classroomData.isTeacher) { socket.on('joined_session', function(data) { console.log('Joined session as:', data.student_name); loadQuizData(); }); socket.on('answer_feedback', function(data) { handleAnswerFeedback(data.blank_id, data.is_correct); }); } } /** * Load quiz data via REST API */ function loadQuizData() { $.get(classroomData.restUrl + '/quiz/' + classroomData.quizId + '?level=' + classroomData.level) .done(function(quiz) { console.log('Quiz loaded:', quiz); quizData = quiz; if (classroomData.isTeacher) { initTeacherView(quiz); } else { initStudentView(quiz); } }) .fail(function() { alert('Failed to load quiz'); }); } /** * Initialize teacher broadcast view */ function initTeacherView(quiz) { $('#quiz-title').text(quiz.title); $('#quiz-audio').attr('src', quiz.audio_url); // Render quiz text with blanks let html = quiz.text; quiz.blanks.forEach((blank, i) => { const regex = new RegExp('\\b' + blank.word + '\\b', 'i'); html = html.replace(regex, `___`); }); $('#quiz-text').html(html); // Audio controls const audio = document.getElementById('quiz-audio'); $('#play-btn').on('click', function() { audio.play(); $(this).hide(); $('#pause-btn').show(); }); $('#pause-btn').on('click', function() { audio.pause(); $(this).hide(); $('#play-btn').show(); }); // Update time display audio.addEventListener('timeupdate', function() { const current = formatTime(audio.currentTime); const duration = formatTime(audio.duration); $('#audio-time').text(current + ' / ' + duration); }); // Show leaderboard/progress based on mode if (classroomData.mode === 'race') { $('#leaderboard').show(); } else { $('#collab-progress').show(); $('#progress-text').text('0 / ' + quiz.blanks.length + ' complete'); } } /** * Initialize student view */ function initStudentView(quiz) { $('#waiting-screen').hide(); $('#quiz-screen').show(); // Create input boxes quiz.blanks.forEach((blank, i) => { const $input = $(` `); // Debounced answer submission let timeout; $input.on('input', function() { const answer = $(this).val().trim(); clearTimeout(timeout); if (answer.length > 0) { timeout = setTimeout(function() { submitAnswer(i, answer); }, 500); } }); $('#input-area').append($input); }); $('#student-score').text('0 / ' + quiz.blanks.length); $('#input-0').focus(); } /** * Submit answer via WebSocket */ function submitAnswer(blankId, answer) { socket.emit('submit_answer', { code: classroomData.sessionCode, blank_id: blankId, answer: answer }); } /** * Handle answer feedback from WebSocket */ function handleAnswerFeedback(blankId, isCorrect) { const $input = $('#input-' + blankId); $input.removeClass('correct incorrect'); if (isCorrect) { $input.addClass('correct'); // Haptic feedback if (navigator.vibrate) { navigator.vibrate([30, 10, 30]); } // Disable input $input.prop('disabled', true); // Update score updateStudentScore(); // Focus next input $('#input-' + (blankId + 1)).focus(); } else { $input.addClass('incorrect'); setTimeout(function() { $input.removeClass('incorrect'); }, 500); } } /** * Update student score display */ function updateStudentScore() { const total = $('.input-box').length; const correct = $('.input-box.correct').length; $('#student-score').text(correct + ' / ' + total); } /** * Update student count (teacher view) */ function updateStudentCount(count) { $('#student-count').text(count + ' student' + (count !== 1 ? 's' : '') + ' connected'); } /** * Fill blank on teacher screen (collab mode) */ function fillBlank(blankId, word) { const $blank = $('#blank-' + blankId); $blank.text(word).addClass('completed'); } /** * Update collaboration progress */ function updateCollabProgress() { if (!quizData) return; const total = quizData.blanks.length; const completed = $('.quiz-blank.completed').length; const percentage = (completed / total) * 100; $('#progress-bar').css('width', percentage + '%'); $('#progress-text').text(completed + ' / ' + total + ' complete'); if (completed === total) { $('#collab-progress h3').text('🎉 Quiz Complete! 🎉'); } } /** * Update leaderboard (race mode) */ function updateLeaderboard(leaderboard) { const $list = $('#leaderboard-list'); $list.empty(); leaderboard.forEach(function(student, index) { const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : ''; const $item = $(`
${medal || (index + 1)} ${escapeHtml(student.name)} ${student.score}
`); $list.append($item); }); } /** * Format time for audio player */ function formatTime(seconds) { if (!isFinite(seconds)) return '0:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return mins + ':' + (secs < 10 ? '0' : '') + secs; } /** * Escape HTML */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } });