119 lines
4 KiB
JavaScript
119 lines
4 KiB
JavaScript
/**
|
|
* 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;
|
|
});
|
|
}
|