feat (ui): series onthouden

This commit is contained in:
kodi
2026-03-09 14:45:24 +01:00
parent c5aaa20ce2
commit 0a294e9bd5
7 changed files with 410 additions and 26 deletions
+112 -24
View File
@@ -20,13 +20,17 @@
settings: {
set_file_date_to_first_aired_date: false,
default_media_root_path: null,
remember_max_series: 10,
},
rememberedSeries: [],
liveSearchItems: [],
};
const el = {
sessionMeta: document.getElementById("sessionMeta"),
outputBox: document.getElementById("outputBox"),
searchInput: document.getElementById("searchInput"),
searchDropdownBtn: document.getElementById("searchDropdownBtn"),
searchBtn: document.getElementById("searchBtn"),
searchResults: document.getElementById("searchResults"),
seriesInfo: document.getElementById("seriesInfo"),
@@ -43,6 +47,8 @@
saveSettingsBtn: document.getElementById("saveSettingsBtn"),
setFileDateToFirstAiredDateInput: document.getElementById("setFileDateToFirstAiredDateInput"),
defaultMediaRootSelect: document.getElementById("defaultMediaRootSelect"),
rememberMaxSeriesInput: document.getElementById("rememberMaxSeriesInput"),
purgeRememberedSeriesBtn: document.getElementById("purgeRememberedSeriesBtn"),
refreshEpisodesBtn: document.getElementById("refreshEpisodesBtn"),
episodesList: document.getElementById("episodesList"),
episodeMeta: document.getElementById("episodeMeta"),
@@ -214,6 +220,9 @@
function applySettingsToForm() {
el.setFileDateToFirstAiredDateInput.checked = !!state.settings.set_file_date_to_first_aired_date;
if (el.rememberMaxSeriesInput) {
el.rememberMaxSeriesInput.value = String(state.settings.remember_max_series || 10);
}
if (el.defaultMediaRootSelect) {
const wanted = state.settings.default_media_root_path || "";
el.defaultMediaRootSelect.value = wanted;
@@ -262,14 +271,20 @@
state.settings = data.settings || {
set_file_date_to_first_aired_date: false,
default_media_root_path: null,
remember_max_series: 10,
};
applySettingsToForm();
}
async function saveSettings() {
const rememberMax = Number(el.rememberMaxSeriesInput.value || "10");
if (!Number.isInteger(rememberMax) || rememberMax < 1 || rememberMax > 100) {
throw new Error("Remember max series must be an integer between 1 and 100");
}
const payload = {
set_file_date_to_first_aired_date: !!el.setFileDateToFirstAiredDateInput.checked,
default_media_root_path: (el.defaultMediaRootSelect.value || "").trim() || null,
remember_max_series: rememberMax,
};
const data = await api("/api/session/settings", {
method: "PUT",
@@ -283,6 +298,89 @@
out("Settings saved", data);
}
async function loadRememberedSeries() {
const data = await api("/api/session/remembered-series");
state.rememberedSeries = data.items || [];
}
async function rememberSeries(item) {
await api("/api/session/remembered-series", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ item }),
});
await loadRememberedSeries();
}
async function purgeRememberedSeries() {
const ok = window.confirm("Are you sure?");
if (!ok) return;
await api("/api/session/remembered-series", { method: "DELETE" });
await loadRememberedSeries();
renderRememberedDropdown();
}
function rememberedForQuery(query) {
const normalized = (query || "").trim().toLowerCase();
const rememberedItems = (state.rememberedSeries || []).map((entry) => entry.series || {});
return rememberedItems.filter((item) => {
if (!normalized) return true;
const text = `${item.display_name || ""} ${item.name || ""}`.toLowerCase();
return text.includes(normalized);
});
}
function renderRememberedDropdown() {
const query = (el.searchInput.value || "").trim();
const items = rememberedForQuery(query);
el.searchResults.innerHTML = "";
items.forEach((item) => {
const li = document.createElement("li");
const left = document.createElement("span");
const label = item.display_name || item.name || "(series)";
left.textContent = label;
const right = document.createElement("div");
const tag = document.createElement("span");
tag.className = "badge";
tag.textContent = "Remembered";
right.appendChild(tag);
li.appendChild(left);
li.appendChild(right);
li.addEventListener("click", () => withHandler(() => selectSeries(item), li));
el.searchResults.appendChild(li);
});
}
function renderSearchResults(items) {
el.searchResults.innerHTML = "";
(items || []).forEach((item) => {
const li = document.createElement("li");
const left = document.createElement("span");
left.textContent = item.display_name || item.name || "(series)";
li.appendChild(left);
li.addEventListener("click", () => withHandler(() => selectSeries(item), li));
el.searchResults.appendChild(li);
});
}
async function selectSeries(item) {
state.selectedSeries = item;
state.selectedSeriesSummary = null;
el.seriesInfo.textContent = `Selected: ${item.display_name || item.name}`;
renderSelectedSeriesDetails();
await rememberSeries(item);
renderRememberedDropdown();
try {
await loadSeriesSummary(item.id);
renderSelectedSeriesDetails();
} catch (_err) {
// Keep UI responsive with fallback data.
}
await loadEpisodes();
}
function preferredRootId() {
if (!state.roots.length) return "";
const wantedPath = (state.settings.default_media_root_path || "").trim();
@@ -370,31 +468,14 @@
async function doSearch() {
const query = (el.searchInput.value || "").trim();
if (!query) return;
if (!query || query.length < 2) {
state.liveSearchItems = [];
renderRememberedDropdown();
return;
}
const data = await api(`/api/tvdb/search?q=${encodeURIComponent(query)}`);
el.searchResults.innerHTML = "";
(data.items || []).forEach((item) => {
const li = document.createElement("li");
const left = document.createElement("span");
left.textContent = item.display_name || item.name || "(series)";
const right = document.createElement("div");
right.appendChild(makeBtn("Select", async () => {
state.selectedSeries = item;
state.selectedSeriesSummary = null;
el.seriesInfo.textContent = `Selected: ${item.display_name || item.name}`;
renderSelectedSeriesDetails();
try {
await loadSeriesSummary(item.id);
renderSelectedSeriesDetails();
} catch (_err) {
// Keep UI responsive with search payload fallback if summary lookup fails.
}
await loadEpisodes();
}));
li.appendChild(left);
li.appendChild(right);
el.searchResults.appendChild(li);
});
state.liveSearchItems = data.items || [];
renderSearchResults(state.liveSearchItems);
out("Search result", data);
}
@@ -704,9 +785,14 @@
function bindEvents() {
el.searchBtn.addEventListener("click", () => withHandler(doSearch, el.searchBtn));
el.searchInput.addEventListener("focus", renderRememberedDropdown);
el.searchInput.addEventListener("click", renderRememberedDropdown);
el.searchInput.addEventListener("input", renderRememberedDropdown);
el.searchDropdownBtn.addEventListener("click", renderRememberedDropdown);
el.settingsBtn.addEventListener("click", openSettingsModal);
el.closeSettingsModalBtn.addEventListener("click", closeSettingsModal);
el.saveSettingsBtn.addEventListener("click", () => withHandler(saveSettings, el.saveSettingsBtn));
el.purgeRememberedSeriesBtn.addEventListener("click", () => withHandler(purgeRememberedSeries, el.purgeRememberedSeriesBtn));
el.settingsModal.addEventListener("click", (e) => {
if (e.target === el.settingsModal) closeSettingsModal();
});
@@ -765,6 +851,8 @@
renderSelectedSeriesDetails();
bindEvents();
await loadSettings();
await loadRememberedSeries();
renderRememberedDropdown();
await loadSelectedEpisodes();
await loadSelectedFiles();
await loadRoots();