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