feat(anime): add anime browser powered by Aniyomi extensions

- Add `subminer anime` / `--anime` and a tray entry to open a browser that searches installed Aniyomi extension sources, shows cover art and episodes, and plays into mpv with overlay/mining attached
- Add an Extensions tab to add repos and install/update/remove sources, and per-source settings for sources needing config
- Support searching all sources at once with streaming, per-source results and status
- Prefer Japanese audio/subtitle tracks from the source and keep the primary subtitle slot reserved for Japanese
- Fix window/tray/Dock handling so the browser and mpv can be switched between without quitting the app or losing the Dock icon
- Add anime.repos, anime.extensionsDir, anime.preferredQuality config keys (no bundled repos or discovery)
This commit is contained in:
2026-07-31 17:16:49 -07:00
parent b204d4dd6e
commit e64ff1a0ee
117 changed files with 7565 additions and 529 deletions
+158
View File
@@ -0,0 +1,158 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob: http://127.0.0.1:* http://localhost:* https:; object-src 'none'; base-uri 'self';"
/>
<title>SubMiner Anime</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<header class="topbar">
<div class="brand-block">
<div class="brand-title">SubMiner</div>
<div class="brand-subtitle">Anime</div>
</div>
<form class="search-form" id="search-form" role="search">
<input
class="text-input search-input"
id="search-input"
type="search"
placeholder="Search anime"
autocomplete="off"
aria-label="Search anime"
/>
<button class="primary-button" id="search-button" type="submit">Search</button>
</form>
<label class="source-picker">
<span class="source-label">Source</span>
<select class="text-input" id="source-select" aria-label="Extension source"></select>
</label>
<nav class="tabs" role="tablist" aria-label="View">
<button
class="tab"
id="tab-browse"
type="button"
role="tab"
aria-controls="layout"
aria-selected="true"
>
Browse
</button>
<button
class="tab"
id="tab-extensions"
type="button"
role="tab"
aria-controls="extensions"
aria-selected="false"
>
Extensions
</button>
<button
class="tab"
id="tab-settings"
type="button"
role="tab"
aria-controls="settings"
aria-selected="false"
>
Source settings
</button>
</nav>
</header>
<div class="bridge-banner hidden" id="bridge-banner" role="status" aria-live="polite">
<span class="bridge-dot" id="bridge-dot"></span>
<span class="bridge-message" id="bridge-message"></span>
<span class="bridge-meter hidden" id="bridge-meter"><i id="bridge-meter-fill"></i></span>
</div>
<section class="settings hidden" id="settings" role="tabpanel" aria-labelledby="tab-settings">
<div class="settings-head">
<h2 class="settings-title" id="settings-title">Source settings</h2>
<span class="settings-note">Saved as you change them.</span>
</div>
<div class="settings-fields" id="settings-fields"></div>
</section>
<section
class="settings hidden"
id="extensions"
role="tabpanel"
aria-labelledby="tab-extensions"
>
<div class="settings-head">
<h2 class="settings-title">Extensions</h2>
<span class="settings-note" id="extensions-dir"></span>
</div>
<h3 class="ext-group-title">
Installed <span class="ext-group-count" id="installed-count"></span>
</h3>
<div class="ext-list" id="installed-list" aria-label="Installed extensions"></div>
<h3 class="ext-group-title">Repositories</h3>
<div class="repo-add">
<input
class="text-input"
id="repo-input"
type="url"
placeholder="https://example.org/repo/index.min.json"
aria-label="Repository index URL"
/>
<button class="primary-button" id="repo-add" type="button">Add repository</button>
</div>
<p class="repo-hint" id="repo-hint">
SubMiner ships no repositories. Add any https URL to a repository's <code>.json</code> index
to install extensions from it, or drop <code>.apk</code> files in the extensions directory.
</p>
<div class="ext-list" id="repo-list" aria-label="Repositories"></div>
<h3 class="ext-group-title">Available</h3>
<div class="ext-list" id="extensions-list" aria-label="Available extensions"></div>
</section>
<main class="layout" id="layout" role="tabpanel" aria-labelledby="tab-browse">
<section class="results" id="results" aria-label="Results">
<div class="grid" id="grid"></div>
<p class="empty hidden" id="grid-empty"></p>
</section>
<section class="detail hidden" id="detail" aria-label="Details">
<div class="detail-body">
<button class="ghost-button detail-back" id="detail-back" type="button">
← Back to results
</button>
<div class="detail-head">
<img class="detail-cover" id="detail-cover" alt="" />
<div class="detail-meta">
<h2 class="detail-title" id="detail-title"></h2>
<div class="detail-chips" id="detail-chips"></div>
<p class="detail-description" id="detail-description"></p>
</div>
</div>
<div class="episodes-head">
<h3 class="episodes-title">Episodes</h3>
<span class="episodes-count" id="episodes-count"></span>
</div>
<ol class="cue-rail" id="episodes"></ol>
</div>
</section>
</main>
<footer class="statusbar">
<span id="status-message"></span>
</footer>
<script type="module" src="./animeui.js"></script>
</body>
</html>