mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-16 13:55:51 -07:00
fix(anime): guard playback races and harden install/extract tests
- Tag subtitle cache writes with a generation counter so overlapping playEpisode calls can't clobber the shared cache dir, and don't fail playback when track setup errors - Ignore stale episode clicks in the detail panel via a LatestRequest guard on playback - Extract a resetGrid helper in animeui to dedupe grid-clearing logic - Assert reader cancellation and extracted-file writes actually happen in installer/subsync tests
This commit is contained in:
+10
-10
@@ -202,10 +202,16 @@ function createCard(entry: AnimeBrowserEntry, showSource: boolean): HTMLButtonEl
|
||||
return card;
|
||||
}
|
||||
|
||||
function renderEntries(entries: AnimeBrowserEntry[], emptyMessage: string): void {
|
||||
// Which source a cover came from only matters when they are mixed together.
|
||||
/** Drops every card and the empty-state message so a fresh page can build up. */
|
||||
function resetGrid(): void {
|
||||
seenEntries.clear();
|
||||
grid.replaceChildren();
|
||||
gridEmpty.classList.add('hidden');
|
||||
}
|
||||
|
||||
function renderEntries(entries: AnimeBrowserEntry[], emptyMessage: string): void {
|
||||
// Which source a cover came from only matters when they are mixed together.
|
||||
resetGrid();
|
||||
appendEntries(entries);
|
||||
|
||||
const empty = grid.childElementCount === 0;
|
||||
@@ -255,11 +261,7 @@ api.onSearchUpdate((update) => {
|
||||
const request = soleBrowseRequest(inFlightBrowses);
|
||||
activeStreamRequestId = request?.id ?? 0;
|
||||
const append = request?.append === true;
|
||||
if (!append) {
|
||||
seenEntries.clear();
|
||||
grid.replaceChildren();
|
||||
gridEmpty.classList.add('hidden');
|
||||
}
|
||||
if (!append) resetGrid();
|
||||
return;
|
||||
}
|
||||
if (activeStreamRequestId !== browseState.requestId) return;
|
||||
@@ -317,9 +319,7 @@ async function runSearch(query: string): Promise<void> {
|
||||
// A new search means new results; leave the detail page for them.
|
||||
if (detailPanel.isOpen()) detailPanel.close();
|
||||
setStatus(query ? `Searching for “${query}”…` : 'Loading popular…');
|
||||
seenEntries.clear();
|
||||
grid.replaceChildren();
|
||||
gridEmpty.classList.add('hidden');
|
||||
resetGrid();
|
||||
await runBrowse(started.request);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export function createDetailPanel({ api, setStatus }: DetailPanelOptions) {
|
||||
let selectedAnime: { url: string; title: string; sourceId: string } | null = null;
|
||||
let resultsScrollTop = 0;
|
||||
const requests = new LatestRequest();
|
||||
const playbacks = new LatestRequest();
|
||||
|
||||
function formatEpisodeIndex(episode: AnimeBrowserEpisode, fallbackIndex: number): string {
|
||||
const value = episode.number ?? fallbackIndex;
|
||||
@@ -38,6 +39,9 @@ export function createDetailPanel({ api, setStatus }: DetailPanelOptions) {
|
||||
const anime = selectedAnime;
|
||||
if (!anime) return;
|
||||
|
||||
// Only the newest click owns the button states and the status line; an
|
||||
// earlier episode resolving late must not overwrite them.
|
||||
const playback = playbacks.begin();
|
||||
for (const other of episodes.querySelectorAll<HTMLButtonElement>('.cue')) {
|
||||
other.removeAttribute('data-state');
|
||||
}
|
||||
@@ -55,6 +59,8 @@ export function createDetailPanel({ api, setStatus }: DetailPanelOptions) {
|
||||
}),
|
||||
);
|
||||
|
||||
if (!playbacks.isCurrent(playback)) return;
|
||||
|
||||
if (!attempt.ok) {
|
||||
button.removeAttribute('data-state');
|
||||
setStatus(describe(attempt.error), 'error');
|
||||
@@ -163,6 +169,7 @@ export function createDetailPanel({ api, setStatus }: DetailPanelOptions) {
|
||||
|
||||
function close(): void {
|
||||
requests.cancel();
|
||||
playbacks.cancel();
|
||||
detail.classList.add('hidden');
|
||||
results.classList.remove('hidden');
|
||||
results.scrollTop = resultsScrollTop;
|
||||
|
||||
Reference in New Issue
Block a user