/** * Exam Application * Main entry point for the exam functionality */ // Import services and utilities import * as ExamApi from './components/exam-api.js'; import * as TerminalService from './components/terminal-service.js'; import * as RemoteDesktopService from './components/remote-desktop-service.js'; import * as QuestionService from './components/question-service.js'; import * as TimerService from './components/timer-service.js'; import * as UiUtils from './components/ui-utils.js'; import * as WakeLockService from './components/wake-lock-service.js'; import * as ClipboardService from './components/clipboard-service.js'; document.addEventListener('DOMContentLoaded', function() { // DOM Elements const pageLoader = document.getElementById('pageLoader'); const endExamBtnDropdown = document.getElementById('endExamBtnDropdown'); const confirmEndBtn = document.getElementById('confirmEndBtn'); const terminateSessionBtn = document.getElementById('terminateSessionBtn'); const confirmTerminateBtn = document.getElementById('confirmTerminateBtn'); const prevBtn = document.getElementById('prevBtn'); const nextBtn = document.getElementById('nextBtn'); const questionDropdown = document.getElementById('questionDropdown'); const questionDropdownMenu = document.getElementById('questionDropdownMenu'); const questionContent = document.getElementById('questionContent'); const examTimer = document.getElementById('examTimer'); const vncFrame = document.getElementById('vnc-frame'); const connectionStatus = document.getElementById('connectionStatus'); const startExamBtn = document.getElementById('startExamBtn'); const toggleViewBtn = document.getElementById('toggleViewBtn'); const sshTerminalContainer = document.getElementById('sshTerminalContainer'); const sshConnectionStatus = document.getElementById('sshConnectionStatus'); const viewResultsBtn = document.getElementById('viewResultsBtn'); // Modals const confirmModal = new bootstrap.Modal(document.getElementById('confirmModal')); const terminateModal = new bootstrap.Modal(document.getElementById('terminateModal')); const startExamModal = new bootstrap.Modal(document.getElementById('startExamModal')); const examEndModal = new bootstrap.Modal(document.getElementById('examEndModal')); // State variables let examInfo = {}; // Store exam information let currentQuestionId = 1; let questions = []; let isTerminalActive = false; let isCompletedExamMode = false; // Add event listener for page unload to clean up resources window.addEventListener('beforeunload', cleanupResources); // Initialize by fetching questions fetchExamQuestions(); // Listen for examCompletedSession event and handle connecting to a finished exam session document.addEventListener('examCompletedSession', function(event) { const { examId } = event.detail; console.log('Connecting to completed exam session for exam:', examId); // Get DOM elements const pageLoader = document.getElementById('pageLoader'); const vncFrame = document.getElementById('vnc-frame'); const connectionStatus = document.getElementById('connectionStatus'); const examTimer = document.getElementById('examTimer'); // Set completed exam mode isCompletedExamMode = true; // Show loader pageLoader.style.display = 'flex'; // Create a promises array for parallel fetching const promises = [ // Fetch exam info ExamApi.fetchCurrentExamInfo(), // Fetch exam questions ExamApi.fetchExamData(examId) ]; // Execute all promises in parallel Promise.all(promises) .then(([examInfoData, questionsData]) => { // Store exam info for later use examInfo = examInfoData; // Hide timer for completed exam examTimer.style.display = 'none'; // Add Review Mode badge next to title const headerTitle = document.querySelector('.header-title'); if (headerTitle) { // Create the badge const reviewBadge = document.createElement('span'); reviewBadge.className = 'review-mode-badge'; reviewBadge.textContent = 'Review Mode'; // Add badge next to title headerTitle.appendChild(reviewBadge); } // Hide end exam button if it exists const endExamItem = document.querySelector('.dropdown-item[href="#"][id="endExamBtnDropdown"]'); if (endExamItem) { endExamItem.style.display = 'none'; } if (questionsData) { // Transform the questions questions = QuestionService.transformQuestionsFromApi(questionsData); // Initialize the exam UI with questions initExamUI(); console.log('Loaded', questions.length, 'questions for completed exam'); // Add "View Results" button to header const headerControls = document.querySelector('.header-controls'); if (headerControls) { const viewResultsBtn = document.createElement('button'); viewResultsBtn.className = 'btn-custom'; viewResultsBtn.textContent = 'View Results'; viewResultsBtn.addEventListener('click', () => { window.location.href = `/results?id=${examId}`; }); // Add button to beginning of controls headerControls.prepend(viewResultsBtn); } } // Connect to Remote Desktop RemoteDesktopService.connectToRemoteDesktop(vncFrame, showVncConnectionStatus); // Hide loader after a short delay setTimeout(() => { pageLoader.style.display = 'none'; }, 1500); }) .catch(error => { console.error('Error loading exam environment:', error); alert('Failed to connect to exam environment. Please try again.'); pageLoader.style.display = 'none'; }); }); // Function declarations // Function to show VNC connection status function showVncConnectionStatus(message, type) { UiUtils.showConnectionStatus(connectionStatus, message, type); } // Function to show SSH connection status function showSshConnectionStatus(message, type) { UiUtils.showConnectionStatus(sshConnectionStatus, message, type); } // Setup callbacks for terminal service TerminalService.setCallbacks({ showConnectionStatus: showSshConnectionStatus }); // Load exam environment for completed exams function loadExamEnvironment(examId) { // Show loader pageLoader.style.display = 'flex'; // Fetch exam info for connection details only ExamApi.fetchCurrentExamInfo() .then(data => { examInfo = data; // Connect to environment connectToExamSession(); // Hide loader after a short delay setTimeout(() => { pageLoader.style.display = 'none'; }, 1500); }) .catch(error => { console.error('Error loading exam environment:', error); alert('Failed to connect to exam environment. Please try again.'); pageLoader.style.display = 'none'; }); } // Connect to exam session for review function connectToExamSession() { console.log('Connecting to exam session in completed exam mode'); // Connect to Remote Desktop RemoteDesktopService.connectToRemoteDesktop(vncFrame, showVncConnectionStatus); // Setup Remote Desktop frame handlers RemoteDesktopService.setupRemoteDesktopFrameHandlers(vncFrame, showVncConnectionStatus); // Enter fullscreen mode for better visibility UiUtils.requestFullscreen(document.documentElement); } // Fetch questions from API function fetchExamQuestions() { // Show loader while fetching questions pageLoader.style.display = 'flex'; const examId = ExamApi.getExamId(); if (!examId) return; // First check exam status to handle completed exams ExamApi.checkExamStatus(examId) .then(status => { if (status === 'EVALUATED' || status === 'EVALUATING') { // Show option to view results for completed exams UiUtils.showExamCompletedModal(examId, status); pageLoader.style.display = 'none'; return null; // Skip loading questions } // First fetch current exam info return ExamApi.fetchCurrentExamInfo() .then(data => { examInfo = data; // Set timer based on examDurationInMinutes and examStartTime if available if (data.info) { if (data.info.events?.examStartTime && data.info.examDurationInMinutes) { // Calculate remaining time for in-progress exams const remainingMinutes = TimerService.calculateRemainingTime(data); console.log(`Setting timer to ${remainingMinutes} minutes (based on start time)`); TimerService.setTimerDuration(remainingMinutes); } else if (data.info.examDurationInMinutes) { // For new exams, use the full duration console.log(`Setting timer to ${data.info.examDurationInMinutes} minutes`); TimerService.setTimerDuration(data.info.examDurationInMinutes); } else { console.warn('examDurationInMinutes not found in API response, using default duration'); } } return ExamApi.fetchExamData(examId); }) .then(data => { if (!data) return; // Store exam info for later use examInfo = data.info || examInfo; // Transform the questions questions = QuestionService.transformQuestionsFromApi(data); // Initialize the exam UI after loading questions initExamUI(); // Show the start exam modal showStartExamModal(); // Hide loader pageLoader.style.display = 'none'; }) .catch(error => { console.error('Error loading exam:', error); // Show an error message to the user alert('Failed to load exam data. Please refresh the page or contact support.'); pageLoader.style.display = 'none'; }); }) .catch(error => { console.error('Error loading exam:', error); // Show an error message to the user alert('Failed to load exam data. Please refresh the page or contact support.'); pageLoader.style.display = 'none'; }); } // Show start exam modal function showStartExamModal() { // Check if the exam has already been started or completed if (examInfo.info?.events?.examStartTime) { setupContinueExamButton(); } else { setupNewExamButton(); } // Always show the modal regardless of exam state startExamModal.show(); } // Setup button for continuing an existing exam function setupContinueExamButton() { const modalTitle = document.getElementById('startExamModalLabel'); // Hide default content document.getElementById('newExamContent').style.display = 'none'; if (examInfo.info.events.examEndTime) { // Exam is finished - show completed content document.getElementById('examCompletedContent').style.display = 'block'; document.getElementById('examInProgressContent').style.display = 'none'; // Update modal title modalTitle.textContent = 'Exam Completed'; startExamBtn.textContent = 'Continue to Session'; } else { // Exam is in progress - show in-progress content document.getElementById('examInProgressContent').style.display = 'block'; document.getElementById('examCompletedContent').style.display = 'none'; // Update modal title modalTitle.textContent = 'Exam in Progress'; startExamBtn.textContent = 'Continue Session'; } // Remove countdown behavior and just continue to session startExamBtn.addEventListener('click', function() { startExamModal.hide(); startExam(); }, { once: true }); } // Setup button for starting a new exam function setupNewExamButton() { // Show default content, hide others document.getElementById('newExamContent').style.display = 'block'; document.getElementById('examInProgressContent').style.display = 'none'; document.getElementById('examCompletedContent').style.display = 'none'; // Reset modal title document.getElementById('startExamModalLabel').textContent = 'Ready to Begin Your Exam'; // Initialize counter let countDown = 3; startExamBtn.innerHTML = `Start Exam (${countDown})`; // Remove any existing click handlers startExamBtn.replaceWith(startExamBtn.cloneNode(true)); // Get the fresh reference after cloning const freshStartExamBtn = document.getElementById('startExamBtn'); // Add event listener to start exam button if (freshStartExamBtn) { freshStartExamBtn.addEventListener('click', function handleStartClick() { // Decrease counter on each click countDown--; if (countDown > 0) { // Update button text with new counter freshStartExamBtn.innerHTML = `Start Exam (${countDown})`; } else { // Start exam when counter reaches 0 freshStartExamBtn.removeEventListener('click', handleStartClick); startExamModal.hide(); startExam(); } }); } } // Track exam start event function trackExamStartEvent() { // Track exam start event only if this is a new exam (not a continuation) const examId = ExamApi.getExamId(); if (examId && !examInfo.info?.events?.examStartTime) { const currentTime = Date.now(); console.log('Setting exam start time:', currentTime); ExamApi.trackExamEvent(examId, { examStartTime: currentTime, userAgent: navigator.userAgent, screenResolution: `${window.screen.width}x${window.screen.height}` }); } } // Track exam end event function trackExamEndEvent() { const examId = ExamApi.getExamId(); if (!examId) return; const currentEndTime = Date.now(); console.log('Setting exam end time:', currentEndTime); return ExamApi.trackExamEvent(examId, { examEndTime: currentEndTime }).catch(error => { console.error('Error tracking exam end event:', error); // Continue with exam evaluation regardless of tracking error }); } // Setup timer threshold notifications function setupTimerNotifications() { TimerService.registerTimeThresholdCallbacks({ // 30 minutes remaining 30: (minutes) => { UiUtils.showToast('30 minutes left in your exam', { bgColor: 'bg-warning', textColor: 'text-dark', delay: 7000 }); }, // 10 minutes remaining 10: (minutes) => { UiUtils.showToast('Only 10 minutes remaining! ', { bgColor: 'bg-danger', textColor: 'text-white', delay: 10000 }); }, // Time's up 0: (minutes) => { handleExamEnd(); } }); } // Start exam functionality function startExam() { // Show loader while starting exam pageLoader.style.display = 'flex'; // Enter fullscreen mode UiUtils.requestFullscreen(document.documentElement); // Acquire wake lock to prevent device from sleeping WakeLockService.acquireWakeLock() .then(success => { if (success) { console.log('Wake lock acquired successfully'); } else { console.warn('Wake lock not acquired, device may sleep during exam'); } }); // Connect to Remote Desktop RemoteDesktopService.connectToRemoteDesktop(vncFrame, showVncConnectionStatus); // Calculate remaining time const remainingTime = TimerService.calculateRemainingTime(examInfo); // Handle timer visibility and initialization if (remainingTime <= 0 || examInfo.info?.events?.examEndTime) { // Hide timer if time is up or exam has ended examTimer.style.display = 'none'; } else { // Show and initialize timer with remaining time examTimer.style.display = 'block'; // Initialize timer with DOM element TimerService.initTimer(examTimer, remainingTime, { onTimerEnd: () => { handleExamEnd(); } }); // Set up timer notifications setupTimerNotifications(); // Start the timer TimerService.startTimer(); } // Track exam start trackExamStartEvent(); // Hide loader after a short delay setTimeout(() => { pageLoader.style.display = 'none'; }, 1500); } // Handle exam end when timer reaches zero function handleExamEnd() { // Release wake lock as exam is ending WakeLockService.releaseWakeLock() .then(success => { if (success) { console.log('Wake lock released successfully'); } }); // Show the exam end modal examEndModal.show(); // Get exam ID const examId = ExamApi.getExamId(); if (!examId) return; // Track exam end event trackExamEndEvent() .then(() => { // Make API call to end and evaluate the exam return ExamApi.evaluateExam(examId); }) .then(data => { console.log('Exam evaluation started:', data); // Set up the View Results button if (viewResultsBtn) { viewResultsBtn.addEventListener('click', () => { window.location.href = `/results?id=${examId}`; }); } }) .catch(error => { console.error('Error ending exam:', error); alert('There was an error evaluating your exam. Please try manually ending the exam.'); }); } // Update question content function updateQuestionContent(questionId) { const question = questions.find(q => q.id === questionId || q.id === questionId.toString()); if (!question) { console.error(`Question with ID ${questionId} not found`); questionContent.innerHTML = '