Reflow config page into responsive grid
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.40.5 - 2026-05-25
|
||||||
|
|
||||||
|
- Moved the Config page `Dependencies` panel to the top as a full-width card and changed the remaining settings panels to a two-column layout that collapses back to one column at `1080px` and below.
|
||||||
|
|
||||||
## 0.40.4 - 2026-05-25
|
## 0.40.4 - 2026-05-25
|
||||||
|
|
||||||
- Widened the watchlist auto-download modal layout so `Name source` and `Name` each fill half of the dialog row instead of staying in narrow grid cells.
|
- Widened the watchlist auto-download modal layout so `Name source` and `Name` each fill half of the dialog row instead of staying in narrow grid cells.
|
||||||
|
|||||||
@@ -187,6 +187,11 @@ Use it to set:
|
|||||||
9. Discord webhook URL.
|
9. Discord webhook URL.
|
||||||
10. Discord notification events plus a test action.
|
10. Discord notification events plus a test action.
|
||||||
|
|
||||||
|
Layout:
|
||||||
|
|
||||||
|
- `Dependencies` stays at the top as a full-width runtime status card.
|
||||||
|
- The remaining Config sections use two columns on larger screens and collapse to one column at `1080px` width and below.
|
||||||
|
|
||||||
Saved settings are written to `.ani-cli-web/config.json`.
|
Saved settings are written to `.ani-cli-web/config.json`.
|
||||||
|
|
||||||
Discord notification events:
|
Discord notification events:
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ from uuid import uuid4
|
|||||||
PROJECT_ROOT = Path(__file__).resolve().parent
|
PROJECT_ROOT = Path(__file__).resolve().parent
|
||||||
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
|
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
|
||||||
APP_NAME = "ani-cli-web"
|
APP_NAME = "ani-cli-web"
|
||||||
VERSION = "0.40.4"
|
VERSION = "0.40.5"
|
||||||
ALLANIME_BASE = "allanime.day"
|
ALLANIME_BASE = "allanime.day"
|
||||||
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
|
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
|
||||||
ALLANIME_REFERER = "https://allmanga.to"
|
ALLANIME_REFERER = "https://allmanga.to"
|
||||||
|
|||||||
+127
-112
@@ -280,14 +280,28 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.settings-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 18px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
.settings-main, .deps {
|
.settings-main, .deps {
|
||||||
width: min(760px, 100%);
|
width: min(1180px, 100%);
|
||||||
|
}
|
||||||
|
.deps {
|
||||||
|
width: min(1180px, 100%);
|
||||||
}
|
}
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.app { grid-template-columns: 1fr; }
|
.app { grid-template-columns: 1fr; }
|
||||||
aside { border-right: 0; border-bottom: 1px solid var(--line); }
|
aside { border-right: 0; border-bottom: 1px solid var(--line); }
|
||||||
.toolbar, .row { align-items: stretch; flex-direction: column; }
|
.toolbar, .row { align-items: stretch; flex-direction: column; }
|
||||||
}
|
}
|
||||||
|
@media (max-width: 1080px) {
|
||||||
|
.settings-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.form-grid, .check-grid {
|
.form-grid, .check-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -320,118 +334,119 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<section class="settings settings-main">
|
|
||||||
<div class="toolbar">
|
|
||||||
<div>
|
|
||||||
<h2>Defaults</h2>
|
|
||||||
<p class="muted">Manage the folder, mode, and quality that the Search page starts with.</p>
|
|
||||||
</div>
|
|
||||||
<button class="primary" id="saveConfigBtn" type="button">Save</button>
|
|
||||||
</div>
|
|
||||||
<div class="notice" id="notice"></div>
|
|
||||||
<div class="stack">
|
|
||||||
<label>Default folder
|
|
||||||
<input id="downloadDir" placeholder="/home/me/Downloads/Anime">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-grid">
|
|
||||||
<label>Default mode
|
|
||||||
<select id="configMode">
|
|
||||||
<option value="sub">Subtitled (Sub)</option>
|
|
||||||
<option value="dub">Dubbed (Dub)</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>Default quality
|
|
||||||
<select id="configQuality">
|
|
||||||
<option value="best">Best</option>
|
|
||||||
<option value="1080">1080p</option>
|
|
||||||
<option value="720">720p</option>
|
|
||||||
<option value="480">480p</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="field-hint">Tip: these are the saved defaults only. The active search form can still be adjusted per download.</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="settings settings-main">
|
|
||||||
<div class="toolbar">
|
|
||||||
<div>
|
|
||||||
<h2>Watchlist schedule</h2>
|
|
||||||
<p class="muted">Periodically queue a background watchlist refresh to check for newly available episodes.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-grid">
|
|
||||||
<label>Automatic refresh
|
|
||||||
<select id="watchlistAutoRefreshEnabled">
|
|
||||||
<option value="false">Disabled</option>
|
|
||||||
<option value="true">Enabled</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>Refresh period (minutes)
|
|
||||||
<input id="watchlistAutoRefreshMinutes" type="number" min="1" max="10080" step="1" value="60">
|
|
||||||
</label>
|
|
||||||
<label>Delay between series (seconds)
|
|
||||||
<input id="watchlistRefreshDelaySeconds" type="number" min="0" max="300" step="1" value="5">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="field-hint">When enabled, the server periodically starts the existing background “Refresh All” job using this interval. Bulk refreshes now wait this many seconds between shows to avoid flooding upstream sources.</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="settings settings-main">
|
|
||||||
<div class="toolbar">
|
|
||||||
<div>
|
|
||||||
<h2>Auto-download</h2>
|
|
||||||
<p class="muted">Automatically queue newly discovered episodes for shows in the `Watching` category after manual or scheduled refreshes.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-grid">
|
|
||||||
<label>Automatic queueing
|
|
||||||
<select id="autoDownloadEnabled">
|
|
||||||
<option value="false">Disabled</option>
|
|
||||||
<option value="true">Enabled</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>Default language
|
|
||||||
<select id="autoDownloadMode">
|
|
||||||
<option value="dub">Dubbed (Dub)</option>
|
|
||||||
<option value="sub">Subtitled (Sub)</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>Default quality
|
|
||||||
<select id="autoDownloadQuality">
|
|
||||||
<option value="best">Best</option>
|
|
||||||
<option value="1080">1080p</option>
|
|
||||||
<option value="720">720p</option>
|
|
||||||
<option value="480">480p</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="field-hint">Adding a new watchlist entry does not auto-download anything. Auto-download only reacts to later manual refreshes or scheduled refreshes that find new episodes.</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="settings settings-main">
|
|
||||||
<div class="toolbar">
|
|
||||||
<div>
|
|
||||||
<h2>Discord notifications</h2>
|
|
||||||
<p class="muted">Send webhook notifications for refresh discoveries, refresh problems, and unexpected runtime errors.</p>
|
|
||||||
</div>
|
|
||||||
<button id="testWebhookBtn" type="button">Test webhook</button>
|
|
||||||
</div>
|
|
||||||
<div class="stack">
|
|
||||||
<label>Discord webhook URL
|
|
||||||
<textarea id="discordWebhookUrl" placeholder="https://discord.com/api/webhooks/..."></textarea>
|
|
||||||
</label>
|
|
||||||
<div class="field-hint">The new-episodes notification includes one card per anime, with local cached thumbnails attached to the webhook when available.</div>
|
|
||||||
</div>
|
|
||||||
<div class="stack">
|
|
||||||
<label>Notify for</label>
|
|
||||||
<div class="check-grid">
|
|
||||||
""" + WEBHOOK_EVENT_OPTIONS + r"""
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="deps" id="deps"></section>
|
<section class="deps" id="deps"></section>
|
||||||
|
<div class="notice" id="notice"></div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<section class="settings settings-main">
|
||||||
|
<div class="toolbar">
|
||||||
|
<div>
|
||||||
|
<h2>Defaults</h2>
|
||||||
|
<p class="muted">Manage the folder, mode, and quality that the Search page starts with.</p>
|
||||||
|
</div>
|
||||||
|
<button class="primary" id="saveConfigBtn" type="button">Save</button>
|
||||||
|
</div>
|
||||||
|
<div class="stack">
|
||||||
|
<label>Default folder
|
||||||
|
<input id="downloadDir" placeholder="/home/me/Downloads/Anime">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid">
|
||||||
|
<label>Default mode
|
||||||
|
<select id="configMode">
|
||||||
|
<option value="sub">Subtitled (Sub)</option>
|
||||||
|
<option value="dub">Dubbed (Dub)</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Default quality
|
||||||
|
<select id="configQuality">
|
||||||
|
<option value="best">Best</option>
|
||||||
|
<option value="1080">1080p</option>
|
||||||
|
<option value="720">720p</option>
|
||||||
|
<option value="480">480p</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-hint">Tip: these are the saved defaults only. The active search form can still be adjusted per download.</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="settings settings-main">
|
||||||
|
<div class="toolbar">
|
||||||
|
<div>
|
||||||
|
<h2>Watchlist schedule</h2>
|
||||||
|
<p class="muted">Periodically queue a background watchlist refresh to check for newly available episodes.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid">
|
||||||
|
<label>Automatic refresh
|
||||||
|
<select id="watchlistAutoRefreshEnabled">
|
||||||
|
<option value="false">Disabled</option>
|
||||||
|
<option value="true">Enabled</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Refresh period (minutes)
|
||||||
|
<input id="watchlistAutoRefreshMinutes" type="number" min="1" max="10080" step="1" value="60">
|
||||||
|
</label>
|
||||||
|
<label>Delay between series (seconds)
|
||||||
|
<input id="watchlistRefreshDelaySeconds" type="number" min="0" max="300" step="1" value="5">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-hint">When enabled, the server periodically starts the existing background “Refresh All” job using this interval. Bulk refreshes now wait this many seconds between shows to avoid flooding upstream sources.</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="settings settings-main">
|
||||||
|
<div class="toolbar">
|
||||||
|
<div>
|
||||||
|
<h2>Auto-download</h2>
|
||||||
|
<p class="muted">Automatically queue newly discovered episodes for shows in the `Watching` category after manual or scheduled refreshes.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid">
|
||||||
|
<label>Automatic queueing
|
||||||
|
<select id="autoDownloadEnabled">
|
||||||
|
<option value="false">Disabled</option>
|
||||||
|
<option value="true">Enabled</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Default language
|
||||||
|
<select id="autoDownloadMode">
|
||||||
|
<option value="dub">Dubbed (Dub)</option>
|
||||||
|
<option value="sub">Subtitled (Sub)</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Default quality
|
||||||
|
<select id="autoDownloadQuality">
|
||||||
|
<option value="best">Best</option>
|
||||||
|
<option value="1080">1080p</option>
|
||||||
|
<option value="720">720p</option>
|
||||||
|
<option value="480">480p</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-hint">Adding a new watchlist entry does not auto-download anything. Auto-download only reacts to later manual refreshes or scheduled refreshes that find new episodes.</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="settings settings-main">
|
||||||
|
<div class="toolbar">
|
||||||
|
<div>
|
||||||
|
<h2>Discord notifications</h2>
|
||||||
|
<p class="muted">Send webhook notifications for refresh discoveries, refresh problems, and unexpected runtime errors.</p>
|
||||||
|
</div>
|
||||||
|
<button id="testWebhookBtn" type="button">Test webhook</button>
|
||||||
|
</div>
|
||||||
|
<div class="stack">
|
||||||
|
<label>Discord webhook URL
|
||||||
|
<textarea id="discordWebhookUrl" placeholder="https://discord.com/api/webhooks/..."></textarea>
|
||||||
|
</label>
|
||||||
|
<div class="field-hint">The new-episodes notification includes one card per anime, with local cached thumbnails attached to the webhook when available.</div>
|
||||||
|
</div>
|
||||||
|
<div class="stack">
|
||||||
|
<label>Notify for</label>
|
||||||
|
<div class="check-grid">
|
||||||
|
""" + WEBHOOK_EVENT_OPTIONS + r"""
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1959,6 +1959,12 @@ class TemplateHelperTests(unittest.TestCase):
|
|||||||
self.assertIn("async function api(path, options = {})", APP.CONFIG_HTML)
|
self.assertIn("async function api(path, options = {})", APP.CONFIG_HTML)
|
||||||
self.assertIn("async function api(path, options = {})", APP.WATCHLIST_HTML)
|
self.assertIn("async function api(path, options = {})", APP.WATCHLIST_HTML)
|
||||||
|
|
||||||
|
def test_config_page_places_dependencies_before_settings_grid(self):
|
||||||
|
self.assertIn('<section class="deps" id="deps"></section>', APP.CONFIG_HTML)
|
||||||
|
self.assertIn('<div class="settings-grid">', APP.CONFIG_HTML)
|
||||||
|
self.assertLess(APP.CONFIG_HTML.index('<section class="deps" id="deps"></section>'), APP.CONFIG_HTML.index('<div class="settings-grid">'))
|
||||||
|
self.assertIn("@media (max-width: 1080px)", APP.CONFIG_HTML)
|
||||||
|
|
||||||
def test_search_page_sends_watchlist_auto_download_series(self):
|
def test_search_page_sends_watchlist_auto_download_series(self):
|
||||||
self.assertIn('auto_download_series: $("seasonInput").value || "1"', APP.INDEX_HTML)
|
self.assertIn('auto_download_series: $("seasonInput").value || "1"', APP.INDEX_HTML)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user