/* Batch upload handling — multi-file selection, upload, per-file status tracking */ let batchFiles = []; let currentBatchId = null; let batchPollInterval = null; function switchUploadMode(mode) { const tabSingle = document.getElementById('tabSingle'); const tabBatch = document.getElementById('tabBatch'); const singleArea = document.getElementById('singleUploadArea'); const batchArea = document.getElementById('batchUploadArea'); if (mode === 'batch') { tabSingle.classList.remove('active'); tabSingle.setAttribute('aria-selected', 'false'); tabBatch.classList.add('active'); tabBatch.setAttribute('aria-selected', 'true'); singleArea.style.display = 'none'; batchArea.style.display = 'block'; batchArea.setAttribute('tabindex', '0'); singleArea.setAttribute('tabindex', '-1'); } else { tabBatch.classList.remove('active'); tabBatch.setAttribute('aria-selected', 'false'); tabSingle.classList.add('active'); tabSingle.setAttribute('aria-selected', 'true'); batchArea.style.display = 'none'; singleArea.style.display = 'block'; singleArea.setAttribute('tabindex', '0'); batchArea.setAttribute('tabindex', '-1'); } } function initBatchUpload() { const batchDrop = document.getElementById('batchDropArea'); const batchInput = document.getElementById('batchFileInput'); if (!batchDrop || !batchInput) return; batchDrop.addEventListener('click', () => batchInput.click()); batchDrop.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); batchInput.click(); } }); batchDrop.addEventListener('dragover', (e) => { e.preventDefault(); batchDrop.classList.add('dragover'); }); batchDrop.addEventListener('dragleave', () => { batchDrop.classList.remove('dragover'); }); batchDrop.addEventListener('drop', (e) => { e.preventDefault(); batchDrop.classList.remove('dragover'); addBatchFiles(e.dataTransfer.files); }); batchInput.addEventListener('change', (e) => { addBatchFiles(e.target.files); }); } function addBatchFiles(fileList) { for (let i = 0; i < fileList.length; i++) { const file = fileList[i]; if (!file.name.toLowerCase().endsWith('.pdf')) continue; if (file.size > 50 * 1024 * 1024) continue; if (batchFiles.length >= 10) break; // Avoid duplicates if (batchFiles.some(f => f.name === file.name && f.size === file.size)) continue; batchFiles.push(file); } renderBatchFileList(); } function renderBatchFileList() { const listEl = document.getElementById('batchFileList'); const actionsEl = document.getElementById('batchActions'); if (batchFiles.length === 0) { listEl.style.display = 'none'; actionsEl.style.display = 'none'; return; } listEl.style.display = 'block'; actionsEl.style.display = 'flex'; let html = '
' + batchFiles.length + ' file(s) selected:
'; batchFiles.forEach((file, idx) => { const sizeMB = (file.size / 1024 / 1024).toFixed(2); html += '
'; html += '' + escapeHtml(file.name) + ' (' + sizeMB + ' MB)'; html += ''; html += '
'; }); listEl.innerHTML = html; } function removeBatchFile(index) { batchFiles.splice(index, 1); renderBatchFileList(); } function clearBatchFiles() { batchFiles = []; document.getElementById('batchFileInput').value = ''; renderBatchFileList(); document.getElementById('batchProgress').style.display = 'none'; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } async function startBatchUpload() { if (batchFiles.length === 0) return; const btn = document.getElementById('batchUploadBtn'); btn.disabled = true; btn.textContent = 'Uploading...'; const progressEl = document.getElementById('batchProgress'); progressEl.style.display = 'block'; progressEl.innerHTML = '
Uploading ' + batchFiles.length + ' files...
'; const quickMode = document.getElementById('quickMode').checked; try { const result = await uploadBatch(batchFiles); if (result.success) { currentBatchId = result.data.batch_id; const uploaded = result.data.uploaded || []; const errors = result.data.errors || []; let html = '
'; html += '
Batch: ' + currentBatchId + '
'; if (uploaded.length > 0) { html += '
' + uploaded.length + ' file(s) uploaded successfully
'; } if (errors.length > 0) { html += '
' + errors.length + ' file(s) failed:
'; errors.forEach(e => { html += '
' + escapeHtml(e.filename) + ': ' + escapeHtml(e.error) + '
'; }); } html += '
'; // Per-file status rows html += '
'; uploaded.forEach(f => { html += '
'; html += '
' + escapeHtml(f.filename) + '
'; html += '
'; html += 'Queued'; html += ''; html += ''; html += '
'; }); html += '
'; // Overall progress bar html += '
'; html += '
Processing...0%
'; html += '
'; html += '
'; progressEl.innerHTML = html; // Start each check for (const f of uploaded) { startCheck(f.job_id, quickMode).catch(() => {}); } // Poll batch status pollBatchStatus(uploaded.map(f => f.job_id)); } else { progressEl.innerHTML = '
Batch upload failed: ' + escapeHtml(result.error) + '
'; } } catch (error) { progressEl.innerHTML = '
Error: ' + escapeHtml(error.message) + '
'; } btn.disabled = false; btn.textContent = 'Upload & Check All'; } function pollBatchStatus(jobIds) { const total = jobIds.length; let completedSet = new Set(); batchPollInterval = setInterval(async () => { for (const jobId of jobIds) { if (completedSet.has(jobId)) continue; try { const result = await checkStatus(jobId); if (!result.success) continue; const data = result.data; const statusEl = document.getElementById('batch-status-' + jobId); const scoreEl = document.getElementById('batch-score-' + jobId); const linkEl = document.getElementById('batch-link-' + jobId); const rowEl = document.getElementById('batch-row-' + jobId); if (!statusEl) continue; if (data.status === 'completed') { completedSet.add(jobId); statusEl.textContent = 'Completed'; statusEl.style.color = 'var(--success)'; if (rowEl) rowEl.style.borderColor = 'var(--success)'; // Fetch score try { const res = await getResult(jobId); if (res.success && res.data.accessibility_score !== undefined) { const score = res.data.accessibility_score; let color = 'var(--success)'; if (score < 50) color = 'var(--error)'; else if (score < 80) color = 'var(--warning)'; scoreEl.innerHTML = '' + score + '/100'; } } catch (_) {} linkEl.style.display = 'inline'; linkEl.href = '#'; linkEl.onclick = (e) => { e.preventDefault(); viewBatchResult(jobId); }; } else if (data.status === 'failed' || data.status === 'error') { completedSet.add(jobId); statusEl.textContent = 'Failed'; statusEl.style.color = 'var(--error)'; if (rowEl) rowEl.style.borderColor = 'var(--error)'; } else if (data.status === 'processing') { const pct = data.progress || 0; statusEl.textContent = 'Processing' + (pct > 0 ? ' (' + pct + '%)' : '...'); statusEl.style.color = 'var(--info)'; } } catch (_) {} } // Update overall progress const done = completedSet.size; const pct = Math.round((done / total) * 100); const fillEl = document.getElementById('batchOverallFill'); const pctEl = document.getElementById('batchOverallPct'); const txtEl = document.getElementById('batchOverallText'); if (fillEl) fillEl.style.width = pct + '%'; if (pctEl) pctEl.textContent = pct + '%'; if (txtEl) txtEl.textContent = done + ' of ' + total + ' complete'; if (done >= total) { clearInterval(batchPollInterval); batchPollInterval = null; if (txtEl) txtEl.textContent = 'All ' + total + ' files processed'; } }, 3000); } async function viewBatchResult(jobId) { try { const result = await getResult(jobId); if (result.success) { currentJobId = jobId; document.getElementById('uploadSection').style.display = 'none'; displayResults(result.data); } } catch (error) { alert('Failed to load result: ' + error.message); } } async function exportReport(format) { if (!currentJobId) return; const hasAdjustments = (typeof overriddenChecks !== 'undefined' && overriddenChecks.size > 0) || (typeof dismissedIndices !== 'undefined' && dismissedIndices.size > 0); // Open the window synchronously first to avoid popup-blocker blocking an async call const win = window.open('about:blank', '_blank'); if (hasAdjustments) { try { await fetch('api.php?action=save_adjusted_result', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ job_id: currentJobId }) }); } catch (e) { console.warn('Could not save adjusted result before export:', e); } } const url = getExportUrl(currentJobId, format); if (win) { win.location.href = url; } else { window.open(url, '_blank'); } }