video_press/static/js/modules/browser.js

111 lines
3.7 KiB
JavaScript

/**
* browser.js
* ----------
* Server-side directory browser modal.
*
* Fetches directory listings from /api/browse and renders them inside the
* modal panel. The user navigates the server filesystem and selects a
* directory to populate the scan path input.
*
* Exports
* -------
* initBrowser() — attach all event listeners; call once at startup
*/
import { state, els, announce } from './state.js';
import { esc } from './utils.js';
// ─── Internal helpers ─────────────────────────────────────────────────────────
async function loadBrowserPath(path) {
els.browserList.innerHTML =
'<p class="browser-loading" aria-live="polite">Loading…</p>';
els.browserPath.textContent = path;
try {
const resp = await fetch(`/api/browse?path=${encodeURIComponent(path)}`);
if (!resp.ok) throw new Error((await resp.json()).error || 'Error loading directory');
const data = await resp.json();
state.browserPath = data.current;
els.browserPath.textContent = data.current;
let html = '';
if (data.parent !== null) {
html += `
<button class="browser-item parent-dir" data-path="${esc(data.parent)}">
<span class="item-icon" aria-hidden="true">↑</span>
<span>.. (parent directory)</span>
</button>`;
}
for (const entry of data.entries) {
if (!entry.is_dir) continue;
html += `
<button class="browser-item" data-path="${esc(entry.path)}"
role="option" aria-label="Directory: ${esc(entry.name)}">
<span class="item-icon" aria-hidden="true">📁</span>
<span>${esc(entry.name)}</span>
</button>`;
}
if (!html) html = '<p class="browser-loading">No subdirectories found.</p>';
els.browserList.innerHTML = html;
els.browserList.querySelectorAll('.browser-item').forEach(btn => {
btn.addEventListener('click', () => loadBrowserPath(btn.dataset.path));
btn.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
loadBrowserPath(btn.dataset.path);
}
});
});
} catch (err) {
els.browserList.innerHTML =
`<p class="browser-error" role="alert">Error: ${esc(err.message)}</p>`;
}
}
function openBrowser() {
els.browserModal.hidden = false;
document.body.style.overflow = 'hidden';
loadBrowserPath(els.dirInput.value || '/');
els.closeBrowser.focus();
announce('Directory browser opened');
}
function closeBrowser() {
els.browserModal.hidden = true;
document.body.style.overflow = '';
els.browseBtn.focus();
announce('Directory browser closed');
}
// ─── Public init ─────────────────────────────────────────────────────────────
/**
* Attach all event listeners for the directory browser modal.
* Call once during app initialisation.
*/
export function initBrowser() {
els.browseBtn.addEventListener('click', openBrowser);
els.closeBrowser.addEventListener('click', closeBrowser);
els.browserCancel.addEventListener('click', closeBrowser);
els.browserModal.addEventListener('click', e => {
if (e.target === els.browserModal) closeBrowser();
});
els.browserSelect.addEventListener('click', () => {
els.dirInput.value = state.browserPath;
closeBrowser();
announce(`Directory selected: ${state.browserPath}`);
});
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && !els.browserModal.hidden) closeBrowser();
});
}