mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-21 00:15:26 -07:00
feat: show mining frame toast on OSD and speed up stats session deletes
- Flash mined-frame screenshot as in-overlay image toast for OSD card notifications - Batch cover art from stored DB blobs via POST /api/stats/covers (no extra AniList fetches) - Show delete progress toast on stats home and sessions pages - Refresh only affected rollups on session delete instead of full rebuild - Rebuild lifetime summaries with aggregate SQL CTEs instead of per-session loop
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import type { SessionSummary, StatsCoverImagesData } from '../types/stats';
|
||||
|
||||
export type CoverImageKind = 'anime' | 'media';
|
||||
export type CoverImageMap = Record<string, string>;
|
||||
|
||||
export interface CoverImageRequest {
|
||||
animeIds: number[];
|
||||
videoIds: number[];
|
||||
}
|
||||
|
||||
function normalizePositiveIds(ids: Iterable<number | null | undefined>): number[] {
|
||||
const uniqueIds = new Set<number>();
|
||||
for (const id of ids) {
|
||||
if (typeof id === 'number' && Number.isFinite(id) && id > 0) {
|
||||
uniqueIds.add(Math.floor(id));
|
||||
}
|
||||
}
|
||||
return Array.from(uniqueIds).sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
export function getCoverImageKey(kind: CoverImageKind, id: number): string {
|
||||
return `${kind}:${id}`;
|
||||
}
|
||||
|
||||
export function collectSessionCoverRequests(
|
||||
sessions: Pick<SessionSummary, 'animeId' | 'videoId'>[],
|
||||
): CoverImageRequest {
|
||||
const animeIds: number[] = [];
|
||||
const videoIds: number[] = [];
|
||||
|
||||
for (const session of sessions) {
|
||||
if (session.animeId != null) {
|
||||
animeIds.push(session.animeId);
|
||||
} else if (session.videoId != null) {
|
||||
videoIds.push(session.videoId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
animeIds: normalizePositiveIds(animeIds),
|
||||
videoIds: normalizePositiveIds(videoIds),
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeCoverImageData(
|
||||
previous: CoverImageMap,
|
||||
data: StatsCoverImagesData,
|
||||
): CoverImageMap {
|
||||
const next = { ...previous };
|
||||
|
||||
for (const [id, image] of Object.entries(data.anime)) {
|
||||
if (image?.dataUrl) {
|
||||
next[getCoverImageKey('anime', Number(id))] = image.dataUrl;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, image] of Object.entries(data.media)) {
|
||||
if (image?.dataUrl) {
|
||||
next[getCoverImageKey('media', Number(id))] = image.dataUrl;
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
export function getCoverImageSrc(
|
||||
images: CoverImageMap,
|
||||
kind: CoverImageKind,
|
||||
id: number | null,
|
||||
): string | null {
|
||||
return id == null ? null : (images[getCoverImageKey(kind, id)] ?? null);
|
||||
}
|
||||
Reference in New Issue
Block a user