/** * Stepper Navigation - Handles multi-step workflow */ import { showConfirmModal } from './modal.js'; import { saveGradingResults, prepareEmbryoData, isFirebaseReady } from '../utils/firebaseService.js'; import { appState } from '../state.js'; import { showToast } from './toast.js'; let currentStep = 1; const totalSteps = 3; /** * Initialize stepper navigation */ export function initializeStepper() { updateStepperUI(); attachStepperEventListeners(); } /** * Navigate to a specific step */ export function goToStep(stepNumber) { if (stepNumber < 1 || stepNumber > totalSteps) return; currentStep = stepNumber; updateStepperUI(); scrollToTop(); } /** * Go to next step */ export function nextStep() { if (currentStep < totalSteps) { currentStep++; updateStepperUI(); scrollToTop(); } } /** * Go to previous step */ export function prevStep() { if (currentStep > 1) { currentStep--; updateStepperUI(); scrollToTop(); } } /** * Mark a step as completed */ export function markStepCompleted(stepNumber) { const step = document.querySelector(`.step[data-step="${stepNumber}"]`); if (step) { step.classList.add('completed'); } // Update step line const stepLines = document.querySelectorAll('.step-line'); if (stepLines[stepNumber - 1]) { stepLines[stepNumber - 1].classList.add('completed'); } } /** * Update stepper UI */ function updateStepperUI() { // Update step indicators document.querySelectorAll('.step').forEach((step, index) => { const stepNum = index + 1; step.classList.remove('active'); if (stepNum === currentStep) { step.classList.add('active'); } }); // Update step content visibility document.querySelectorAll('.step-content').forEach((content, index) => { const stepNum = index + 1; content.classList.remove('active'); if (stepNum === currentStep) { content.classList.add('active'); } }); } /** * Reset stepper to first step */ export function resetStepper() { currentStep = 1; // Remove completed status from all steps document.querySelectorAll('.step').forEach(step => { step.classList.remove('completed', 'active'); }); document.querySelectorAll('.step-line').forEach(line => { line.classList.remove('completed'); }); updateStepperUI(); } /** * Get current step number */ export function getCurrentStep() { return currentStep; } /** * Scroll to top of page */ function scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); } /** * Attach event listeners for step navigation */ function attachStepperEventListeners() { // Step 1 navigation const nextStep1 = document.getElementById('nextStep1'); if (nextStep1) { nextStep1.addEventListener('click', () => { markStepCompleted(1); goToStep(2); initializeStep2(); }); } const startOverBtn = document.getElementById('startOverBtn'); if (startOverBtn) { startOverBtn.addEventListener('click', async () => { const confirmed = await showConfirmModal( 'Are you sure you want to start over? All progress will be lost.', 'Start Over' ); if (confirmed) { resetStepper(); resetWorkflow(); } }); } // Step 2 navigation const prevStep2 = document.getElementById('prevStep2'); if (prevStep2) { prevStep2.addEventListener('click', () => goToStep(1)); } const nextStep2 = document.getElementById('nextStep2'); if (nextStep2) { nextStep2.addEventListener('click', () => { markStepCompleted(2); goToStep(3); initializeStep3(); }); } // Step 3 navigation const prevStep3 = document.getElementById('prevStep3'); if (prevStep3) { prevStep3.addEventListener('click', () => goToStep(2)); } const analyzeAnother = document.getElementById('analyzeAnother'); if (analyzeAnother) { analyzeAnother.addEventListener('click', async () => { // Save results first, then reset await handleSaveAndReset(); }); } } /** * Initialize Step 2 - Show embryo image and prepare for detection */ function initializeStep2() { // Show the embryo image first, then trigger detection after UI is ready setTimeout(() => { import('./workflow.js').then(module => { module.showEmbryoImageAndDetect(); }); }, 150); } /** * Initialize Step 3 - Evaluate all embryos in background */ function initializeStep3() { // Trigger evaluation of all embryos in background import('./workflow.js').then(module => { module.evaluateAllEmbryos(); }); } /** * Reset workflow (to be called from workflow.js) */ export function resetWorkflow() { // Reset file input to allow re-uploading the same file const imageInput = document.getElementById('imageInput'); if (imageInput) { imageInput.value = ''; } // Clear images const mainImage = document.getElementById('mainImage'); const mainImagePreview = document.getElementById('mainImagePreview'); const croppedImage = document.getElementById('croppedImage'); const cropCanvas = document.getElementById('cropCanvas'); if (mainImage) { mainImage.style.display = 'none'; mainImage.src = ''; } if (croppedImage) { croppedImage.style.display = 'none'; croppedImage.src = ''; } if (cropCanvas) { cropCanvas.style.display = 'none'; } if (mainImagePreview) { const placeholder = mainImagePreview.querySelector('.placeholder'); if (placeholder) placeholder.style.display = 'flex'; } // Reset buttons const nextStep1 = document.getElementById('nextStep1'); if (nextStep1) nextStep1.disabled = true; const nextStep2 = document.getElementById('nextStep2'); if (nextStep2) nextStep2.disabled = true; // Clear classification status const classificationStatus = document.getElementById('classificationStatus'); if (classificationStatus) { classificationStatus.innerHTML = `
Upload an image to check if it contains an embryo.
The system will automatically classify the image upon upload.
`; } // Clear results const finalResults = document.getElementById('finalResults'); if (finalResults) finalResults.innerHTML = 'Processing all embryos...
'; const finalPredictions = document.getElementById('finalPredictions'); if (finalPredictions) finalPredictions.innerHTML = ''; const selectionInfo = document.getElementById('selectionInfo'); if (selectionInfo) { selectionInfo.innerHTML = 'Select an embryo to continue.
'; } // Clear embryo thumbnails const embryoThumbnails = document.getElementById('embryoThumbnails'); if (embryoThumbnails) { embryoThumbnails.innerHTML = ''; } // Disable zoom controls const zoomIn = document.getElementById('zoomIn'); const zoomOut = document.getElementById('zoomOut'); const zoomReset = document.getElementById('zoomReset'); if (zoomIn) zoomIn.disabled = true; if (zoomOut) zoomOut.disabled = true; if (zoomReset) zoomReset.disabled = true; // Hide crop editor const cropControls = document.getElementById('cropControls'); const editCropBtn = document.getElementById('editCropBtn'); if (cropControls) cropControls.style.display = 'none'; if (editCropBtn) editCropBtn.style.display = 'none'; // Reset appState import('../state.js').then(module => { module.appState.croppedEmbryos = []; module.appState.embryoResults = []; module.appState.embryoRemarks = {}; // Clear all remarks module.appState.currentEmbryoIndex = 0; module.appState.currentImage = null; module.appState.zoomLevel = 1; module.appState.finalResult = null; }); } /** * Enable next button for a step */ export function enableNextButton(stepNumber) { const button = document.getElementById(`nextStep${stepNumber}`); if (button) { button.disabled = false; } } /** * Disable next button for a step */ export function disableNextButton(stepNumber) { const button = document.getElementById(`nextStep${stepNumber}`); if (button) { button.disabled = true; } } /** * Handle saving results to Firebase */ async function handleSaveResults() { if (!isFirebaseReady()) { console.warn('Firebase is not configured.'); return false; } if (!appState.croppedEmbryos || appState.croppedEmbryos.length === 0) { console.warn('No embryos to save'); return false; } try { // Prepare the data with results and remarks const embryoData = prepareEmbryoData( appState.croppedEmbryos, appState.currentImage, appState.embryoResults, appState.embryoRemarks ); // Save to Firebase silently const docId = await saveGradingResults(embryoData); // Log to console for debugging console.log('Results saved successfully! Document ID:', docId); return true; } catch (error) { console.error('Error saving results:', error); return false; } } /** * Handle saving results and then resetting for a new analysis */ async function handleSaveAndReset() { // Save silently in background await handleSaveResults(); // Reset immediately resetStepper(); resetWorkflow(); }