195 lines
6.6 KiB
JavaScript
195 lines
6.6 KiB
JavaScript
/**
|
|
* compress.js
|
|
* -----------
|
|
* Compression job lifecycle: start, notification opt-in, cancel, and restart.
|
|
*
|
|
* Exports
|
|
* -------
|
|
* initCompress() — attach all event listeners; call once at startup
|
|
*/
|
|
|
|
import { state, els, announce } from './state.js';
|
|
import { setupProgressSection, showResults } from './progress.js';
|
|
import { startProgressStream } from './stream.js';
|
|
import { smtpIsConfigured } from './settings.js';
|
|
|
|
// ─── Public init ─────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Attach event listeners for:
|
|
* - Notification checkbox toggle
|
|
* - "Compress Selected Files" button
|
|
* - "Cancel Compression" button
|
|
* - "Start New Session" (restart) button
|
|
*
|
|
* Call once during app initialisation.
|
|
*/
|
|
export function initCompress() {
|
|
_initNotifyToggle();
|
|
_initCompressButton();
|
|
_initCancelButton();
|
|
_initRestartButton();
|
|
}
|
|
|
|
// ─── Notification opt-in ─────────────────────────────────────────────────────
|
|
|
|
function _initNotifyToggle() {
|
|
els.notifyChk.addEventListener('change', () => {
|
|
const show = els.notifyChk.checked;
|
|
els.notifyEmailRow.hidden = !show;
|
|
els.notifyEmail.setAttribute('aria-required', show ? 'true' : 'false');
|
|
const warn = document.getElementById('smtp-not-configured-warn');
|
|
if (show) {
|
|
els.notifyEmail.focus();
|
|
if (warn) warn.hidden = smtpIsConfigured();
|
|
} else {
|
|
els.notifyEmail.value = '';
|
|
if (warn) warn.hidden = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// ─── Start compression ────────────────────────────────────────────────────────
|
|
|
|
function _initCompressButton() {
|
|
els.compressBtn.addEventListener('click', async () => {
|
|
const selectedFiles = state.scannedFiles.filter(
|
|
f => state.selectedPaths.has(f.path),
|
|
);
|
|
if (!selectedFiles.length) return;
|
|
|
|
const suffix = els.suffixInput.value.trim() || '_new';
|
|
const notifyEmail = els.notifyChk.checked ? els.notifyEmail.value.trim() : '';
|
|
|
|
// Client-side email validation
|
|
if (els.notifyChk.checked) {
|
|
if (!notifyEmail) {
|
|
els.notifyEmail.setCustomValidity('Please enter your email address.');
|
|
els.notifyEmail.reportValidity();
|
|
return;
|
|
}
|
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(notifyEmail)) {
|
|
els.notifyEmail.setCustomValidity('Please enter a valid email address.');
|
|
els.notifyEmail.reportValidity();
|
|
return;
|
|
}
|
|
els.notifyEmail.setCustomValidity('');
|
|
}
|
|
|
|
els.compressBtn.disabled = true;
|
|
els.compressBtn.textContent = 'Starting…';
|
|
|
|
try {
|
|
const resp = await fetch('/api/compress/start', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
files: selectedFiles.map(f => ({
|
|
path: f.path,
|
|
size_bytes: f.size_bytes,
|
|
target_bit_rate_bps: f.target_bit_rate_bps || 1_000_000,
|
|
codec: f.codec || 'unknown',
|
|
})),
|
|
suffix,
|
|
notify_email: notifyEmail,
|
|
}),
|
|
});
|
|
const data = await resp.json();
|
|
|
|
if (!resp.ok) {
|
|
alert(`Failed to start compression: ${data.error}`);
|
|
_resetCompressBtn();
|
|
return;
|
|
}
|
|
|
|
state.currentJobId = data.job_id;
|
|
state.seenEventCount = 0;
|
|
state.compressionResults = [];
|
|
sessionStorage.setItem('vp-job-id', data.job_id);
|
|
|
|
setupProgressSection(selectedFiles);
|
|
els.sectionProgress.hidden = false;
|
|
els.sectionProgress.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
announce(`Compression started for ${selectedFiles.length} file(s).`);
|
|
startProgressStream(data.job_id, selectedFiles);
|
|
|
|
} catch (err) {
|
|
alert(`Error: ${err.message}`);
|
|
_resetCompressBtn();
|
|
}
|
|
});
|
|
}
|
|
|
|
function _resetCompressBtn() {
|
|
els.compressBtn.disabled = false;
|
|
els.compressBtn.innerHTML =
|
|
'<span class="btn-icon-prefix" aria-hidden="true">⚡</span> Compress Selected Files';
|
|
}
|
|
|
|
// ─── Cancel ───────────────────────────────────────────────────────────────────
|
|
|
|
function _initCancelButton() {
|
|
els.cancelBtn.addEventListener('click', async () => {
|
|
if (!state.currentJobId) return;
|
|
if (!confirm(
|
|
'Cancel all compression operations? ' +
|
|
'Any file currently being processed will be deleted.',
|
|
)) return;
|
|
|
|
els.cancelBtn.disabled = true;
|
|
els.cancelBtn.textContent = 'Cancelling…';
|
|
|
|
try {
|
|
await fetch(`/api/compress/cancel/${state.currentJobId}`, { method: 'POST' });
|
|
announce('Cancellation requested.');
|
|
} catch (err) {
|
|
console.error('Cancel error:', err);
|
|
}
|
|
});
|
|
}
|
|
|
|
// ─── Restart (new session) ────────────────────────────────────────────────────
|
|
|
|
function _initRestartButton() {
|
|
els.restartBtn.addEventListener('click', () => {
|
|
// Clear state
|
|
state.scannedFiles = [];
|
|
state.selectedPaths.clear();
|
|
state.currentJobId = null;
|
|
state.compressionResults = [];
|
|
state.seenEventCount = 0;
|
|
|
|
if (state.eventSource) {
|
|
state.eventSource.close();
|
|
state.eventSource = null;
|
|
}
|
|
if (state.reconnectTimer) {
|
|
clearTimeout(state.reconnectTimer);
|
|
state.reconnectTimer = null;
|
|
}
|
|
sessionStorage.removeItem('vp-job-id');
|
|
|
|
// Reset UI
|
|
els.sectionFiles.hidden = true;
|
|
els.sectionProgress.hidden = true;
|
|
els.sectionResults.hidden = true;
|
|
els.fileTbody.innerHTML = '';
|
|
els.fileProgressList.innerHTML = '';
|
|
els.scanStatus.textContent = '';
|
|
els.notifyChk.checked = false;
|
|
els.notifyEmailRow.hidden = true;
|
|
els.notifyEmail.value = '';
|
|
els.notifyStatus.hidden = true;
|
|
els.notifyStatus.textContent = '';
|
|
els.streamLostBanner.hidden = true;
|
|
els.reconnectBtn.hidden = true;
|
|
els.cancelBtn.disabled = false;
|
|
els.cancelBtn.textContent = '✕ Cancel Compression';
|
|
_resetCompressBtn();
|
|
|
|
document.getElementById('section-config')
|
|
.scrollIntoView({ behavior: 'smooth' });
|
|
els.dirInput.focus();
|
|
announce('Session reset. Ready to scan again.');
|
|
});
|
|
}
|