Files
Psychotoxical-psysonic/src/cover/lightbox.tsx
T
Psychotoxical 184501744b fix(cover): restore full-resolution album and artist covers (#1205)
* fix(cover): build cover tiers from the full-resolution source

Derive the larger tiers from the decoded download instead of re-reading the just-written smaller tier (resize never upscales, so 512/800 were stored at the small resolution). Resolve the full-res 2000 tier exactly so it is actually downloaded and cached. Bump the cache layout stamp to drop already-poisoned tiers.

* fix(cover): open the cover lightbox at full resolution

Require the exact requested tier for the full-res helper so a smaller warmed tier no longer pins the lightbox to a downscaled image. Race the 2000 fetch against a 500ms opening window, show the 800 tier meanwhile, and persist 2000 for the next open. Adds an opening animation (reduced-motion aware) and tests.

* docs(changelog): note full-resolution cover fix (#1205)

* fix(cover): keep full-res peek exact in peek_batch and the grid seeder

Review follow-up: ensure-path peek alone left a hole — cover_cache_peek_batch still laddered a 2000 request down to a smaller tier, and the in-memory grid seeder wrote that smaller path under the 2000 key, so Hero/fullscreen/lightbox surfaces (which peek 2000 before ensure) still showed a downscaled cover. Share one exact-2000 rule (peek_plain_cover_tier) across ensure and peek_batch, and never seed the full-res key from a smaller tier file.
2026-06-28 04:23:12 +02:00

71 lines
2.8 KiB
TypeScript

import { useCallback, useEffect, useState, type ReactNode } from 'react';
import CoverLightbox from '../components/CoverLightbox';
import { buildCoverArtFetchUrl } from './fetchUrl';
import { coverImgSrc } from './imgSrc';
import { getDiskSrcForGrid } from './diskSrcLookup';
import { ensureCoverTierDiskSrc } from './resolveDisk';
import type { CoverArtRef } from './types';
/** Opening window: wait this long for the full-res 2000 tier before showing 800. */
const LIGHTBOX_FULLRES_WINDOW_MS = 500;
export function useCoverLightboxSrc(
ref: CoverArtRef | null,
opts?: { alt?: string },
): { open: () => void; lightbox: ReactNode; src: string; loading: boolean } {
const [open, setOpen] = useState(false);
const [src, setSrc] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!open || !ref) return;
let cancelled = false;
// React Compiler set-state-in-effect rule: state set from an async result resolved in this effect.
// eslint-disable-next-line react-hooks/set-state-in-effect
setLoading(true);
void (async () => {
// Kick the full-res (2000) ensure — Rust downloads + stores `2000.webp`. Do
// not block the open on it: race it against a short opening window. If the
// 2000 lands in time, show it; otherwise show the warm 800 tier now and let
// the 2000 finish + persist in the background, so the next open is full-res.
const fullSrc = ensureCoverTierDiskSrc(ref, 2000);
const winner = await Promise.race([
fullSrc,
new Promise<''>(resolve => {
setTimeout(() => resolve(''), LIGHTBOX_FULLRES_WINDOW_MS);
}),
]);
if (cancelled) return;
if (winner) {
setSrc(winner);
} else {
setSrc(getDiskSrcForGrid(ref, 800) || buildCoverArtFetchUrl(ref, 2000));
}
setLoading(false);
})();
return () => {
cancelled = true;
};
// Keyed on the ref's identity fields intentionally; depending on the `ref`
// object itself would re-fetch the lightbox source on every render.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, ref?.cacheEntityId, ref?.cacheKind, ref?.fetchCoverArtId, ref?.serverScope]);
useEffect(() => {
if (open) return;
// React Compiler set-state-in-effect rule: state set from an async result resolved in this effect.
// eslint-disable-next-line react-hooks/set-state-in-effect
setSrc('');
setLoading(false);
}, [open]);
const handleClose = useCallback(() => setOpen(false), []);
const handleOpen = useCallback(() => setOpen(true), []);
const lightbox = open && coverImgSrc(src) && !loading ? (
<CoverLightbox src={src} alt={opts?.alt ?? ''} onClose={handleClose} />
) : null;
return { open: handleOpen, lightbox, src, loading };
}