/* 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 += '
View';
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');
}
}