/** * progress.js * ----------- * Progress section DOM management: per-file bars, overall bar, and * the final results summary. * * These functions are called by both stream.js (live SSE updates) and * session.js (snapshot restore on reconnect / page reload). * * Exports * ------- * setupProgressSection(files) * setOverallProgress(pct) * updateFileProgress(idx, pct, statusClass, statusText, detail, detailClass) * showStreamLost() * hideStreamLost() * showResults(finalStatus) */ import { state, els, announce } from './state.js'; import { esc } from './utils.js'; // ─── Progress section setup ─────────────────────────────────────────────────── /** * Render the initial per-file progress items and reset counters. * Called when a new compression job starts or when the DOM needs to be * rebuilt after a full page reload. * @param {Array} files — file objects from the job (name, path, …) */ export function setupProgressSection(files) { els.progTotal.textContent = files.length; els.progDone.textContent = '0'; els.progStatus.textContent = 'Running'; setOverallProgress(0); let html = ''; files.forEach((f, idx) => { html += `
${esc(f.name)} Waiting
`; }); els.fileProgressList.innerHTML = html; } // ─── Bar helpers ───────────────────────────────────────────────────────────── /** * Update the overall progress bar. * @param {number} pct 0–100 */ export function setOverallProgress(pct) { const p = Math.min(100, Math.round(pct)); els.overallBarFill.style.width = `${p}%`; els.overallBar.setAttribute('aria-valuenow', p); els.overallPct.textContent = `${p}%`; } /** * Update a single file's progress bar, status badge, and detail text. * * @param {number} idx — file index (0-based) * @param {number} pct — 0–100 * @param {string} statusClass — 'waiting' | 'running' | 'done' | 'error' * @param {string} statusText — visible badge text * @param {string} [detail] — optional sub-text (elapsed time, size saved…) * @param {string} [detailClass]— optional class applied to the detail element */ export function updateFileProgress(idx, pct, statusClass, statusText, detail, detailClass) { const fill = document.getElementById(`fpfill-${idx}`); const bar = document.getElementById(`fpbar-${idx}`); const pctEl = document.getElementById(`fppct-${idx}`); const status = document.getElementById(`fps-${idx}`); const item = document.getElementById(`fpi-${idx}`); const det = document.getElementById(`fpdetail-${idx}`); if (!fill) return; const p = Math.min(100, Math.round(pct)); fill.style.width = `${p}%`; bar.setAttribute('aria-valuenow', p); pctEl.textContent = `${p}%`; status.className = `fp-status ${statusClass}`; status.textContent = statusText; item.className = `file-progress-item ${statusClass}`; fill.classList.toggle('active', statusClass === 'running'); if (detail !== undefined) { det.textContent = detail; det.className = `fp-detail ${detailClass || ''}`; } } // ─── Stream-lost banner ─────────────────────────────────────────────────────── /** Show the disconnection warning banner and reveal the Reconnect button. */ export function showStreamLost() { els.streamLostBanner.hidden = false; els.reconnectBtn.hidden = false; els.progStatus.textContent = 'Disconnected'; announce('Live progress stream disconnected. Use Reconnect to resume.'); } /** Hide the disconnection warning banner and Reconnect button. */ export function hideStreamLost() { els.streamLostBanner.hidden = true; els.reconnectBtn.hidden = true; } // ─── Results summary ───────────────────────────────────────────────────────── /** * Render the Step 4 results card and scroll it into view. * @param {'done'|'cancelled'} finalStatus */ export function showResults(finalStatus) { const results = state.compressionResults; let html = ''; if (finalStatus === 'cancelled') { html += `

Compression was cancelled. Completed files are listed below.

`; } if (!results.length && finalStatus === 'cancelled') { html += '

No files were completed before cancellation.

'; } results.forEach(r => { if (r.status === 'done') { html += `
${esc(r.filename)}
→ ${esc(r.output || '')}
-${r.reduction_pct}%
`; } else if (r.status === 'error') { html += `
${esc(r.filename)}
${esc(r.message)}
`; } }); if (!html) html = '

No results to display.

'; els.resultsContent.innerHTML = html; els.sectionResults.hidden = false; els.sectionResults.scrollIntoView({ behavior: 'smooth', block: 'start' }); }