From c62f61fc38a233014c61f4fb1327d87afd7fbae7 Mon Sep 17 00:00:00 2001 From: kodi Date: Mon, 9 Mar 2026 16:19:50 +0100 Subject: [PATCH] feat (ui): series overlay --- app/main.py | 5 ++ app/static/app.js | 76 ++++++++++++++++++ app/static/styles.css | 35 +++++++++ app/templates/index.html | 20 +++++ app/templates/tvdb_embed_test.html | 120 +++++++++++++++++++++++++++++ data/session_state.sqlite3 | Bin 221184 -> 221184 bytes feature_tests_tvdb_embed.sh | 56 ++++++++++++++ 7 files changed, 312 insertions(+) create mode 100644 app/templates/tvdb_embed_test.html create mode 100755 feature_tests_tvdb_embed.sh diff --git a/app/main.py b/app/main.py index 0157039..ad1986e 100644 --- a/app/main.py +++ b/app/main.py @@ -28,3 +28,8 @@ def index(request: Request): @app.get("/debug.html", response_class=HTMLResponse) def debug_page(request: Request): return templates.TemplateResponse("debug.html", {"request": request}) + + +@app.get("/tvdb-embed-test.html", response_class=HTMLResponse) +def tvdb_embed_test_page(request: Request): + return templates.TemplateResponse("tvdb_embed_test.html", {"request": request}) diff --git a/app/static/app.js b/app/static/app.js index 0552c4d..6a49fca 100644 --- a/app/static/app.js +++ b/app/static/app.js @@ -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)); diff --git a/app/static/styles.css b/app/static/styles.css index 011f04d..84bf239 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -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; diff --git a/app/templates/index.html b/app/templates/index.html index 2af9544..38f8372 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -192,6 +192,26 @@ + + diff --git a/app/templates/tvdb_embed_test.html b/app/templates/tvdb_embed_test.html new file mode 100644 index 0000000..e364982 --- /dev/null +++ b/app/templates/tvdb_embed_test.html @@ -0,0 +1,120 @@ + + + + + + Rename MVP - TVDB Embed Test + + + + +
+

TVDB Embed Test

+
Standalone iframe validation page
+
+
+
+
+

Embed Validation

+
+
+
+ + + + +
+

+ + +
+
+
+ + + + diff --git a/data/session_state.sqlite3 b/data/session_state.sqlite3 index eef230f3325966d01fc138af73b949584599a3bf..51317f7e1882a82b1d96510b36cb43d7147db1e0 100644 GIT binary patch delta 377 zcmZoTz}s+ucY-uy^F$eE#^#L)OZ1tIIHpc!H%McS=ZN1dD4@zQIo^VAvi2RG&Fd{f z*@3dhC$l@GF;C{043rJ$n7sWq@8owLJewbQsE9Kga2%e@o{+@Yy*VpEkz?Wli^<&? ze48(37_$SVzD#B>NMhW#IjbN9C}lQzUj^@EnM&Txe3jp>$#Ao!FmU;CPUX16q0H{b zmcp;jXT)pBW615u(zRV7fH8|@vx2}aM$X3cAT|bbj%L-#@|L38o5C2IEM>UZ7cuZ9 z@uYE|;O6IY;%w!Z$j`u=%)W^A8|ycqBb(T{8nYWkf!a8mb*JafWo+4QFoW@yItyPv zzsGjI1B?^-_)M(~EvyWT^o-2REe$QEuRX^oE(#ViveYv+x3n;`)HX1%GBDtr-hYl! xbozyNO#JPC&oOTQdyYxdAEFzsa(m-GrY6RT3w*aTD=@#*M>Bi-FMnn>0RT}Oa=!oo delta 822 zcma)3O=uHA7~R?3WG8L1sfYd~jdZhZDN@tSWVcO|ldXzHu`ODAYH5>nV^brPHbGJ; z>86VK0~!WdFG4F?Peo)8d#eQrh=+ogf?7n6Ui8+3P;fRN#CY*CJl^--`ySt{?RwUB zJ&)Q!u^c!Jimzv%hdC9_5CNsR6*yxaYx*hhem?3VYn2wqG)1tArm9@4e1k>;u!}1AV8+w-KNDb)>76i@^Itm`#G0=E7_cI@)P0OY};!z-&?s ztC(dr%(wGH0u}ENVKE6lnhT2)koR-Z>fFg}{()3Vyam5077q6V9{wKFFzR{?4`8P- z>YVfTh!GEV1-mY zWo`O0!e=p-Lex}mr(}(3RnJb*c5pqM&xCL^svvT=;;Cz?{@c>{G6}@N+H|_7Ae~7R z{->U?k^;xu>YH4>RBh5tI%3UMX%duT0k`9j5tHt8MAC zzVB$z$Mv9n-qH>GyhMKyHab$E*EpX9e%|h4T-aiLhBpahx4f!P=#sspRIF8>`&$T& zVhRLo^)u2SgyIOfy10wLc=Lmm?i}p_>u8TXF-Fa`LQCIaOQ5`Q(c&cTd$=yz`ZdNm Gn|=dk;_I{k diff --git a/feature_tests_tvdb_embed.sh b/feature_tests_tvdb_embed.sh new file mode 100755 index 0000000..5dda956 --- /dev/null +++ b/feature_tests_tvdb_embed.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE_URL="${BASE_URL:-http://127.0.0.1:8085}" +ALT_BASE_URL="http://host.containers.internal:8085" + +detect_base_url() { + if curl -fsS --max-time 2 "$BASE_URL/api/health" >/dev/null 2>&1; then + echo "$BASE_URL" + return + fi + if curl -fsS --max-time 2 "$ALT_BASE_URL/api/health" >/dev/null 2>&1; then + echo "$ALT_BASE_URL" + return + fi + echo "$BASE_URL" +} + +BASE_URL="$(detect_base_url)" + +echo "Using BASE_URL=$BASE_URL" + +echo "== Feature test 1: tvdb embed test route is served ==" +page="$(curl -fsS "$BASE_URL/tvdb-embed-test.html")" +echo "$page" | grep -q "TVDB Embed Test" || { + echo "Expected TVDB Embed Test title missing" + exit 1 +} +echo "route availability validation passed" + +echo +echo "== Feature test 2: iframe and fallback UI exist ==" +echo "$page" | grep -q 'id="tvdbFrame"' || { + echo "Expected iframe with id tvdbFrame missing" + exit 1 +} +echo "$page" | grep -q 'id="embedFallback"' || { + echo "Expected embed fallback container missing" + exit 1 +} +echo "iframe/fallback validation passed" + +echo +echo "== Feature test 3: open-in-tab action exists ==" +echo "$page" | grep -q 'id="openInTabBtn"' || { + echo "Expected Open in tab button missing" + exit 1 +} +echo "$page" | grep -q "window.open" || { + echo "Expected window.open integration missing" + exit 1 +} +echo "open-in-tab validation passed" + +echo +echo "All TVDB embed feature tests passed."