const state = { titles: [], activeId: "", query: "" }; const els = { libraryBadge: document.querySelector("#libraryBadge"), libraryPath: document.querySelector("#libraryPath"), rescanBtn: document.querySelector("#rescanBtn"), searchInput: document.querySelector("#searchInput"), titleList: document.querySelector("#titleList"), details: document.querySelector("#details"), titleButtonTemplate: document.querySelector("#titleButtonTemplate") }; function bytes(value) { const units = ["B", "KB", "MB", "GB", "TB"]; let size = value; let unit = 0; while (size >= 1024 && unit < units.length - 1) { size /= 1024; unit += 1; } return `${size.toFixed(size >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`; } async function api(path, options = {}) { const response = await fetch(path, { headers: { "content-type": "application/json" }, ...options }); const data = await response.json(); if (!response.ok) throw new Error(data.error || "Request failed"); return data; } async function loadLibrary() { const data = await api("/api/library"); state.titles = data.titles; els.libraryPath.textContent = data.libraryDir; els.libraryBadge.textContent = `${state.titles.length} title${state.titles.length === 1 ? "" : "s"}`; if (!state.activeId && state.titles.length) state.activeId = state.titles[0].id; if (!state.titles.some((title) => title.id === state.activeId)) { state.activeId = state.titles[0]?.id || ""; } render(); } function filteredTitles() { const query = state.query.toLowerCase(); if (!query) return state.titles; return state.titles.filter((title) => { return [ title.title, title.folder, title.description, ...title.videos.map((video) => video.relativePath) ].join(" ").toLowerCase().includes(query); }); } function render() { renderTitleList(); renderDetails(); } function renderTitleList() { els.titleList.innerHTML = ""; for (const title of filteredTitles()) { const node = els.titleButtonTemplate.content.firstElementChild.cloneNode(true); node.classList.toggle("is-active", title.id === state.activeId); node.querySelector(".title-button__name").textContent = title.title; node.querySelector(".title-button__meta").textContent = `${title.count} file${title.count === 1 ? "" : "s"} · ${bytes(title.size)}`; node.addEventListener("click", () => { state.activeId = title.id; render(); }); els.titleList.append(node); } } function renderDetails() { const title = state.titles.find((item) => item.id === state.activeId); if (!title) { els.details.innerHTML = `
Expected: Library/<Movie or Series>/filename.mp4 or Library/<Series>/Season 01/filename.mp4
${escapeHtml(title.folder)} · ${title.count} file${title.count === 1 ? "" : "s"} · ${bytes(title.size)}
${escapeHtml(err.message)}