feat (ui): series overlay

This commit is contained in:
kodi
2026-03-09 16:19:50 +01:00
parent 7a395a24b4
commit c62f61fc38
7 changed files with 312 additions and 0 deletions
+76
View File
@@ -25,6 +25,8 @@
},
rememberedSeries: [],
liveSearchItems: [],
tvdbModalUrl: "",
tvdbModalTimer: null,
};
const el = {
@@ -46,6 +48,12 @@
seriesStatus: document.getElementById("seriesStatus"),
seriesOverview: document.getElementById("seriesOverview"),
seriesTvdbLink: document.getElementById("seriesTvdbLink"),
tvdbModal: document.getElementById("tvdbModal"),
closeTvdbModalBtn: document.getElementById("closeTvdbModalBtn"),
tvdbModalOpenInTabBtn: document.getElementById("tvdbModalOpenInTabBtn"),
tvdbModalFrame: document.getElementById("tvdbModalFrame"),
tvdbModalStatus: document.getElementById("tvdbModalStatus"),
tvdbModalFallback: document.getElementById("tvdbModalFallback"),
settingsBtn: document.getElementById("settingsBtn"),
settingsModal: document.getElementById("settingsModal"),
closeSettingsModalBtn: document.getElementById("closeSettingsModalBtn"),
@@ -199,6 +207,57 @@
return "";
}
function validTvdbUrl(value) {
try {
const url = new URL((value || "").toString().trim());
if (url.protocol !== "https:") return false;
return url.hostname === "www.thetvdb.com" || url.hostname === "thetvdb.com";
} catch (_err) {
return false;
}
}
function clearTvdbModalTimer() {
if (state.tvdbModalTimer) {
window.clearTimeout(state.tvdbModalTimer);
state.tvdbModalTimer = null;
}
}
function showTvdbModalFallback(message) {
el.tvdbModalStatus.textContent = message;
el.tvdbModalFallback.classList.remove("hidden");
}
function openTvdbModal() {
if (!validTvdbUrl(state.tvdbModalUrl)) return;
el.tvdbModal.classList.remove("hidden");
el.tvdbModal.setAttribute("aria-hidden", "false");
el.tvdbModalFallback.classList.add("hidden");
el.tvdbModalStatus.textContent = "Loading TVDB preview...";
clearTvdbModalTimer();
state.tvdbModalTimer = window.setTimeout(() => {
showTvdbModalFallback("No embed load confirmation received. Embedding is likely blocked.");
}, 5000);
el.tvdbModalFrame.src = state.tvdbModalUrl;
}
function closeTvdbModal() {
clearTvdbModalTimer();
el.tvdbModal.classList.add("hidden");
el.tvdbModal.setAttribute("aria-hidden", "true");
el.tvdbModalFrame.removeAttribute("src");
el.tvdbModalStatus.textContent = "";
el.tvdbModalFallback.classList.add("hidden");
}
function openTvdbInTab() {
if (!validTvdbUrl(state.tvdbModalUrl)) return;
window.open(state.tvdbModalUrl, "_blank", "noopener,noreferrer");
}
function renderSelectedSeriesDetails() {
const item = state.selectedSeries;
if (!item) {
@@ -225,9 +284,11 @@
el.seriesOverview.textContent = overview || "No overview available.";
if (tvdbUrl) {
state.tvdbModalUrl = tvdbUrl;
el.seriesTvdbLink.href = tvdbUrl;
el.seriesTvdbLink.classList.remove("hidden");
} else {
state.tvdbModalUrl = "";
el.seriesTvdbLink.removeAttribute("href");
el.seriesTvdbLink.classList.add("hidden");
}
@@ -878,6 +939,21 @@
e.stopPropagation();
});
document.addEventListener("click", closeRememberedDropdown);
el.seriesTvdbLink.addEventListener("click", (e) => {
e.preventDefault();
openTvdbModal();
});
el.closeTvdbModalBtn.addEventListener("click", closeTvdbModal);
el.tvdbModalOpenInTabBtn.addEventListener("click", openTvdbInTab);
el.tvdbModal.addEventListener("click", (e) => {
if (e.target === el.tvdbModal) closeTvdbModal();
});
el.tvdbModalFrame.addEventListener("load", () => {
clearTvdbModalTimer();
el.tvdbModalStatus.textContent =
"Iframe load event received. If the page is visible, embedding works in this browser.";
el.tvdbModalFallback.classList.add("hidden");
});
el.settingsBtn.addEventListener("click", openSettingsModal);
el.closeSettingsModalBtn.addEventListener("click", closeSettingsModal);
el.saveSettingsBtn.addEventListener("click", () => withHandler(saveSettings, el.saveSettingsBtn));
+35
View File
@@ -412,6 +412,41 @@ button.secondary {
min-height: 0;
}
.tvdb-modal-card {
width: min(1500px, 92vw);
height: 86vh;
}
.tvdb-modal-head {
margin-bottom: 6px;
}
.tvdb-fallback {
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px;
margin-bottom: 8px;
background: var(--surface-subtle);
}
.tvdb-fallback h4 {
margin: 0 0 6px;
}
.tvdb-fallback p {
margin: 0;
color: var(--text-muted);
}
.tvdb-modal-frame {
width: 100%;
flex: 1;
min-height: 0;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface-subtle);
}
.modal-head {
display: flex;
align-items: center;