Open alternative titles in modal overlay

This commit is contained in:
Dymas
2026-05-25 10:25:00 +02:00
parent df4eb39ddf
commit cad3cc46c4
6 changed files with 98 additions and 47 deletions
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## 0.40.2 - 2026-05-25
- Replaced the inline `Alternative titles` expansion with a centered overlay dialog so opening AniDB title variants no longer shifts the watchlist layout.
## 0.40.1 - 2026-05-25
- Replaced the per-card auto-download expansion with a centered overlay dialog so opening settings no longer shifts the watchlist grid layout.
+1 -1
View File
@@ -223,7 +223,7 @@ Main behavior:
6. Each card has an inline category selector.
7. Summary cards show total tracked shows plus `sub` and `dub` counts.
8. Pagination shows 30 cards per page.
9. `Alternative titles` expands English alternate titles from AniDB when the watchlist refresh can match the show to an AniDB entry.
9. `Alternative titles` opens a centered overlay with English alternate titles from AniDB when the watchlist refresh can match the show to an AniDB entry.
10. `Auto-download` opens a centered overlay with per-series settings for language, quality, series number, and the sanitized library name used for automatically queued episodes.
Status behavior:
+1 -1
View File
@@ -1 +1 @@
0.40.1
0.40.2
+1 -1
View File
@@ -19,7 +19,7 @@ from uuid import uuid4
PROJECT_ROOT = Path(__file__).resolve().parent
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
APP_NAME = "ani-cli-web"
VERSION = "0.40.1"
VERSION = "0.40.2"
ALLANIME_BASE = "allanime.day"
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
ALLANIME_REFERER = "https://allmanga.to"
+3 -1
View File
@@ -1981,7 +1981,9 @@ class TemplateHelperTests(unittest.TestCase):
def test_watchlist_page_renders_alternative_titles_toggle(self):
self.assertIn("Alternative titles", APP.WATCHLIST_HTML)
self.assertIn('class="ghost alt-titles-toggle"', APP.WATCHLIST_HTML)
self.assertIn('altTitlesPanel.classList.toggle("open")', APP.WATCHLIST_HTML)
self.assertIn('id="altTitlesOverlay"', APP.WATCHLIST_HTML)
self.assertIn("openAltTitlesOverlay(item)", APP.WATCHLIST_HTML)
self.assertIn("closeAltTitlesOverlay()", APP.WATCHLIST_HTML)
self.assertIn("AniDB English alternative titles were not available for this show.", APP.WATCHLIST_HTML)
def test_watchlist_page_renders_auto_download_editor(self):
+88 -43
View File
@@ -501,10 +501,6 @@ WATCHLIST_HTML = r"""<!doctype html>
line-height: 1.35;
white-space: pre-line;
}
.alt-titles {
display: grid;
gap: 8px;
}
.alt-titles-toggle {
width: 100%;
min-height: 34px;
@@ -513,16 +509,31 @@ WATCHLIST_HTML = r"""<!doctype html>
font-weight: 700;
justify-content: center;
}
.alt-titles-panel {
display: none;
gap: 8px;
border: 1px solid var(--line);
border-radius: 14px;
background: rgba(255, 255, 255, 0.04);
padding: 10px;
}
.alt-titles-panel.open {
.alt-titles-modal {
width: min(640px, 100%);
display: grid;
gap: 14px;
border: 1px solid var(--line);
border-radius: 22px;
background: linear-gradient(180deg, rgba(20, 27, 40, 0.98), rgba(12, 17, 28, 0.98));
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.45);
padding: 18px;
}
.alt-titles-modal-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
}
.alt-titles-modal-header p {
margin: 4px 0 0;
}
.alt-titles-modal-close {
min-width: 36px;
min-height: 36px;
border-radius: 999px;
font-size: 18px;
line-height: 1;
}
.alt-titles-list {
display: flex;
@@ -764,6 +775,20 @@ WATCHLIST_HTML = r"""<!doctype html>
</main>
</div>
<div class="overlay" id="altTitlesOverlay" hidden>
<div class="alt-titles-modal" role="dialog" aria-modal="true" aria-labelledby="altTitlesTitle">
<div class="alt-titles-modal-header">
<div>
<h2 id="altTitlesTitle">Alternative titles</h2>
<p class="muted" id="altTitlesSubtitle">AniDB English titles for the selected show.</p>
</div>
<button class="ghost alt-titles-modal-close" id="altTitlesCloseBtn" type="button" aria-label="Close alternative titles">&times;</button>
</div>
<div class="alt-titles-list" id="altTitlesList"></div>
<div class="muted" id="altTitlesEmpty">AniDB English alternative titles were not available for this show.</div>
</div>
</div>
<div class="overlay" id="autoDownloadOverlay" hidden>
<div class="auto-download-modal" role="dialog" aria-modal="true" aria-labelledby="autoDownloadTitle">
<div class="auto-download-modal-header">
@@ -819,6 +844,7 @@ WATCHLIST_HTML = r"""<!doctype html>
refreshRunning: false,
refreshJobId: null,
watchlistRequestId: 0,
altTitlesDraft: null,
autoDownloadDraft: null
};
const categoryLabels = {
@@ -828,6 +854,11 @@ WATCHLIST_HTML = r"""<!doctype html>
dropped: "Dropped"
};
const autoDownloadOverlay = document.getElementById("autoDownloadOverlay");
const altTitlesOverlay = document.getElementById("altTitlesOverlay");
const altTitlesCloseBtn = document.getElementById("altTitlesCloseBtn");
const altTitlesSubtitle = document.getElementById("altTitlesSubtitle");
const altTitlesList = document.getElementById("altTitlesList");
const altTitlesEmpty = document.getElementById("altTitlesEmpty");
const autoDownloadCloseBtn = document.getElementById("autoDownloadCloseBtn");
const autoDownloadSubtitle = document.getElementById("autoDownloadSubtitle");
const autoDownloadStatus = document.getElementById("autoDownloadStatus");
@@ -956,6 +987,44 @@ WATCHLIST_HTML = r"""<!doctype html>
controls.append(prev, next);
}
function closeAltTitlesOverlay() {
state.altTitlesDraft = null;
altTitlesOverlay.hidden = true;
}
function openAltTitlesOverlay(item) {
state.altTitlesDraft = { ...item };
altTitlesSubtitle.textContent = `${item.title} · AniDB English titles for the selected show.`;
altTitlesList.innerHTML = "";
const alternativeTitles = Array.isArray(item.alternative_titles) ? item.alternative_titles : [];
if (alternativeTitles.length) {
for (const title of alternativeTitles) {
const pill = document.createElement("span");
pill.className = "alt-title-pill";
const label = document.createElement("strong");
label.textContent = title.label;
const value = document.createElement("span");
value.textContent = title.value;
pill.append(label, value);
altTitlesList.appendChild(pill);
}
altTitlesEmpty.textContent = "Fetched from AniDB English titles.";
altTitlesList.hidden = false;
} else {
altTitlesEmpty.textContent = "AniDB English alternative titles were not available for this show.";
altTitlesList.hidden = true;
}
altTitlesEmpty.hidden = false;
altTitlesOverlay.hidden = false;
}
altTitlesCloseBtn.addEventListener("click", closeAltTitlesOverlay);
altTitlesOverlay.addEventListener("click", (event) => {
if (event.target === altTitlesOverlay) {
closeAltTitlesOverlay();
}
});
function closeAutoDownloadOverlay() {
state.autoDownloadDraft = null;
autoDownloadOverlay.hidden = true;
@@ -1006,6 +1075,10 @@ WATCHLIST_HTML = r"""<!doctype html>
}
});
window.addEventListener("keydown", (event) => {
if (event.key === "Escape" && !altTitlesOverlay.hidden) {
closeAltTitlesOverlay();
return;
}
if (event.key === "Escape" && !autoDownloadOverlay.hidden) {
closeAutoDownloadOverlay();
}
@@ -1257,13 +1330,7 @@ WATCHLIST_HTML = r"""<!doctype html>
<span class="count-pill${item.dub_complete ? " complete" : ""}">Dub <strong>${formatModeProgress(item.dub_count, item.expected_count)}</strong></span>
</div>
<div class="status-row"></div>
<div class="alt-titles">
<button class="ghost alt-titles-toggle" type="button" aria-expanded="false">Alternative titles</button>
<div class="alt-titles-panel">
<div class="alt-titles-list"></div>
<div class="muted alt-titles-empty"></div>
</div>
</div>
<button class="ghost alt-titles-toggle" type="button">Alternative titles</button>
<div class="category-editor">
<label>Category
<select class="category-select">${categoryOptions(item.category)}</select>
@@ -1307,29 +1374,7 @@ WATCHLIST_HTML = r"""<!doctype html>
appendStatusTag(statusRow, "Dub ready", "complete");
}
const altTitlesToggle = card.querySelector(".alt-titles-toggle");
const altTitlesPanel = card.querySelector(".alt-titles-panel");
const altTitlesList = card.querySelector(".alt-titles-list");
const altTitlesEmpty = card.querySelector(".alt-titles-empty");
const alternativeTitles = Array.isArray(item.alternative_titles) ? item.alternative_titles : [];
if (alternativeTitles.length) {
for (const title of alternativeTitles) {
const pill = document.createElement("span");
pill.className = "alt-title-pill";
const label = document.createElement("strong");
label.textContent = title.label;
const value = document.createElement("span");
value.textContent = title.value;
pill.append(label, value);
altTitlesList.appendChild(pill);
}
altTitlesEmpty.textContent = "Fetched from AniDB English titles.";
} else {
altTitlesEmpty.textContent = "AniDB English alternative titles were not available for this show.";
}
altTitlesToggle.addEventListener("click", () => {
const open = altTitlesPanel.classList.toggle("open");
altTitlesToggle.setAttribute("aria-expanded", open ? "true" : "false");
});
altTitlesToggle.addEventListener("click", () => openAltTitlesOverlay(item));
card.querySelector(".card-date").textContent = item.status_message
? `Last checked: ${checked}\n${item.status_message}`
: `Last checked: ${checked}`;