mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
feat(covers): logo fallback for broken cover art images
CachedImage now shows /logo-psysonic.png when an image fails to load instead of a broken icon. The fallback switches to object-fit: contain with a card background so the logo stays proportional inside the container. If the caller provides a custom onError (e.g. to hide the element), that handler is used instead. A separate fallbackSrc state prevents React from re-writing the broken URL on re-renders. The DOM-level onerror = null guard prevents any infinite error loop. extractCoverColors skips color extraction when the logo fallback is active, falling back to the theme accent color instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,8 +26,9 @@ export function useCachedUrl(fetchUrl: string, cacheKey: string, fallbackToFetch
|
|||||||
return fallbackToFetch ? (resolved || fetchUrl) : resolved;
|
return fallbackToFetch ? (resolved || fetchUrl) : resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CachedImage({ src, cacheKey, style, onLoad, ...props }: CachedImageProps) {
|
export default function CachedImage({ src, cacheKey, style, onLoad, onError, ...props }: CachedImageProps) {
|
||||||
const [inView, setInView] = useState(false);
|
const [inView, setInView] = useState(false);
|
||||||
|
const [fallbackSrc, setFallbackSrc] = useState<string | undefined>(undefined);
|
||||||
const imgRef = useRef<HTMLImageElement>(null);
|
const imgRef = useRef<HTMLImageElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -49,14 +50,34 @@ export default function CachedImage({ src, cacheKey, style, onLoad, ...props }:
|
|||||||
// URL upgrades within the same image — avoids the end-of-load flash.
|
// URL upgrades within the same image — avoids the end-of-load flash.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoaded(false);
|
setLoaded(false);
|
||||||
|
setFallbackSrc(undefined);
|
||||||
}, [cacheKey]);
|
}, [cacheKey]);
|
||||||
|
|
||||||
|
const handleError = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||||
|
if (onError) {
|
||||||
|
// Caller wants custom error handling (e.g. hide the element)
|
||||||
|
onError(e);
|
||||||
|
} else {
|
||||||
|
// Nullify the DOM-level handler first to prevent any infinite loop
|
||||||
|
e.currentTarget.onerror = null;
|
||||||
|
setFallbackSrc('/logo-psysonic.png');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFallback = fallbackSrc !== undefined;
|
||||||
|
const finalSrc = fallbackSrc ?? (resolvedSrc || undefined);
|
||||||
|
|
||||||
|
const fallbackStyle: React.CSSProperties = isFallback
|
||||||
|
? { objectFit: 'contain', background: 'var(--bg-card, var(--ctp-surface0, #313244))', padding: '15%' }
|
||||||
|
: {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
ref={imgRef}
|
ref={imgRef}
|
||||||
src={resolvedSrc || undefined}
|
src={finalSrc}
|
||||||
style={{ ...style, opacity: loaded ? 1 : 0, transition: 'opacity 0.15s ease' }}
|
style={{ ...style, opacity: loaded ? 1 : 0, transition: 'opacity 0.15s ease', ...fallbackStyle }}
|
||||||
onLoad={e => { setLoaded(true); onLoad?.(e); }}
|
onLoad={e => { setLoaded(true); onLoad?.(e); }}
|
||||||
|
onError={handleError}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ const MIN_CONTRAST = 4.5;
|
|||||||
*/
|
*/
|
||||||
export function extractCoverColors(imageUrl: string): Promise<CoverColors> {
|
export function extractCoverColors(imageUrl: string): Promise<CoverColors> {
|
||||||
if (!imageUrl) return Promise.resolve({ accent: '' });
|
if (!imageUrl) return Promise.resolve({ accent: '' });
|
||||||
|
// Logo fallback has no meaningful color — skip extraction and use theme accent
|
||||||
|
if (imageUrl.includes('logo-psysonic')) return Promise.resolve({ accent: '' });
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
|
|||||||
Reference in New Issue
Block a user