feat (ui): series overlay
This commit is contained in:
@@ -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})
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -192,6 +192,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tvdbModal" class="modal hidden" aria-hidden="true">
|
||||
<div class="modal-card tvdb-modal-card">
|
||||
<div class="row modal-head tvdb-modal-head">
|
||||
<h3>TVDB Preview</h3>
|
||||
<div class="panel-head-actions">
|
||||
<button id="tvdbModalOpenInTabBtn" class="secondary">Open in tab</button>
|
||||
<button id="closeTvdbModalBtn" class="secondary" aria-label="Close TVDB preview">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<p id="tvdbModalStatus" class="muted"></p>
|
||||
<div id="tvdbModalFallback" class="tvdb-fallback hidden">
|
||||
<h4>Embedding blocked or unavailable</h4>
|
||||
<p>
|
||||
This page cannot be embedded here. TheTVDB may block framing using browser security headers.
|
||||
</p>
|
||||
</div>
|
||||
<iframe id="tvdbModalFrame" title="TVDB Preview" class="tvdb-modal-frame"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Rename MVP - TVDB Embed Test</title>
|
||||
<script>
|
||||
(function () {
|
||||
var key = "rename_mvp_theme";
|
||||
var theme = localStorage.getItem(key);
|
||||
if (theme !== "light" && theme !== "dark") {
|
||||
theme = "dark";
|
||||
}
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="/static/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<h1>TVDB Embed Test</h1>
|
||||
<div id="testMeta">Standalone iframe validation page</div>
|
||||
</header>
|
||||
<main class="debug-page">
|
||||
<section class="panel" style="height: calc(100vh - 100px); min-height: 0;">
|
||||
<div class="panel-head">
|
||||
<h2>Embed Validation</h2>
|
||||
</div>
|
||||
<div class="panel-body" style="min-height: 0;">
|
||||
<div class="row">
|
||||
<label for="tvdbUrlInput">TVDB URL</label>
|
||||
<input
|
||||
id="tvdbUrlInput"
|
||||
type="text"
|
||||
value="https://www.thetvdb.com/series/ghosts-us"
|
||||
style="flex: 1; min-width: 260px;"
|
||||
/>
|
||||
<button id="loadEmbedBtn">Load in iframe</button>
|
||||
<button id="openInTabBtn" class="secondary">Open in tab</button>
|
||||
</div>
|
||||
<p id="embedStatus" class="muted"></p>
|
||||
<div id="embedFallback" class="panel hidden" style="margin-bottom: 8px;">
|
||||
<h3>Embedding blocked or failed</h3>
|
||||
<p class="muted" style="margin: 0;">
|
||||
The page could not be embedded. This is usually caused by TheTVDB frame restrictions
|
||||
(`X-Frame-Options` or `Content-Security-Policy: frame-ancestors`).
|
||||
</p>
|
||||
</div>
|
||||
<iframe
|
||||
id="tvdbFrame"
|
||||
title="TVDB Embed Test Frame"
|
||||
style="width: 100%; height: 100%; border: 1px solid var(--border); border-radius: 6px; background: var(--surface-subtle);"
|
||||
></iframe>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var input = document.getElementById("tvdbUrlInput");
|
||||
var frame = document.getElementById("tvdbFrame");
|
||||
var status = document.getElementById("embedStatus");
|
||||
var fallback = document.getElementById("embedFallback");
|
||||
var loadBtn = document.getElementById("loadEmbedBtn");
|
||||
var openBtn = document.getElementById("openInTabBtn");
|
||||
var fallbackTimer = null;
|
||||
|
||||
function validTvdbUrl(value) {
|
||||
try {
|
||||
var u = new URL(value);
|
||||
return u.protocol === "https:" && (u.hostname === "www.thetvdb.com" || u.hostname === "thetvdb.com");
|
||||
} catch (_err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showFallback(text) {
|
||||
status.textContent = text;
|
||||
fallback.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function loadFrame() {
|
||||
var url = (input.value || "").trim();
|
||||
if (!validTvdbUrl(url)) {
|
||||
showFallback("Enter a valid https://www.thetvdb.com URL.");
|
||||
frame.removeAttribute("src");
|
||||
return;
|
||||
}
|
||||
|
||||
fallback.classList.add("hidden");
|
||||
status.textContent = "Loading iframe...";
|
||||
if (fallbackTimer) window.clearTimeout(fallbackTimer);
|
||||
fallbackTimer = window.setTimeout(function () {
|
||||
showFallback("No iframe load confirmation received. Embedding is likely blocked.");
|
||||
}, 5000);
|
||||
frame.src = url;
|
||||
}
|
||||
|
||||
frame.addEventListener("load", function () {
|
||||
if (fallbackTimer) window.clearTimeout(fallbackTimer);
|
||||
status.textContent =
|
||||
"Iframe load event received. If TVDB content is visible below, embedding works in this browser.";
|
||||
fallback.classList.add("hidden");
|
||||
});
|
||||
|
||||
loadBtn.addEventListener("click", loadFrame);
|
||||
openBtn.addEventListener("click", function () {
|
||||
var url = (input.value || "").trim();
|
||||
if (!validTvdbUrl(url)) {
|
||||
showFallback("Enter a valid https://www.thetvdb.com URL.");
|
||||
return;
|
||||
}
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
});
|
||||
|
||||
loadFrame();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user