/** * state.js * -------- * Single shared application state object and all DOM element references. * * Centralising these here means every module imports the same live object — * mutations made in one module are immediately visible to all others without * any event bus or pub/sub layer. * * Also exports announce(), which every module uses to push messages to the * ARIA live region for screen-reader users. */ // ─── Shared mutable state ──────────────────────────────────────────────────── export const state = { /** Files returned by the last /api/scan call. */ scannedFiles: [], /** Set of file paths the user has checked for compression. */ selectedPaths: new Set(), /** job_id of the currently active or most-recently-seen compression job. */ currentJobId: null, /** Active EventSource for the SSE progress stream. */ eventSource: null, /** Per-file result objects accumulated during a compression run. */ compressionResults: [], /** Current path shown in the server-side directory browser modal. */ browserPath: '/', /** * Index of the last SSE event we have processed. * Passed as ?from=N when reconnecting so the server skips events * we already applied to the UI. */ seenEventCount: 0, /** Handle returned by setTimeout for the auto-reconnect retry. */ reconnectTimer: null, }; // ─── DOM element references ─────────────────────────────────────────────────── const $ = id => document.getElementById(id); export const els = { // Step 1 — Configure source dirInput: $('dir-input'), browseBtn: $('browse-btn'), minSizeInput: $('min-size-input'), suffixInput: $('suffix-input'), scanBtn: $('scan-btn'), scanStatus: $('scan-status'), // Directory browser modal browserModal: $('browser-modal'), browserList: $('browser-list'), browserPath: $('browser-current-path'), closeBrowser: $('close-browser'), browserCancel: $('browser-cancel'), browserSelect: $('browser-select'), // Step 2 — File selection sectionFiles: $('section-files'), selectAllBtn: $('select-all-btn'), deselectAllBtn: $('deselect-all-btn'), selectionSummary: $('selection-summary'), fileTbody: $('file-tbody'), compressBtn: $('compress-btn'), // Email notification opt-in notifyChk: $('notify-chk'), notifyEmailRow: $('notify-email-row'), notifyEmail: $('notify-email'), // Step 3 — Compression progress sectionProgress: $('section-progress'), progTotal: $('prog-total'), progDone: $('prog-done'), progStatus: $('prog-status'), overallBar: $('overall-bar'), overallBarFill: $('overall-bar-fill'), overallPct: $('overall-pct'), fileProgressList: $('file-progress-list'), cancelBtn: $('cancel-btn'), notifyStatus: $('notify-status'), reconnectBtn: $('reconnect-btn'), reconnectBtnBanner: $('reconnect-btn-banner'), streamLostBanner: $('stream-lost-banner'), // Step 4 — Results sectionResults: $('section-results'), resultsContent: $('results-content'), restartBtn: $('restart-btn'), // Header themeToggle: $('theme-toggle'), themeIcon: $('theme-icon'), settingsBtn: $('settings-btn'), // Accessibility live region srAnnounce: $('sr-announce'), }; // ─── Screen-reader announcements ───────────────────────────────────────────── /** * Push a message to the ARIA assertive live region. * Clears first so repeated identical messages are still announced. * @param {string} msg */ export function announce(msg) { els.srAnnounce.textContent = ''; requestAnimationFrame(() => { els.srAnnounce.textContent = msg; }); }