A bare <img> sent a Referer that Google's image CDNs refuse and had no retry and no fallback, so a Home shelf card showed the browser's broken-image mark. ui/RemoteImage gathers what Avatar, the channel banner and the download rows had each hand-rolled: no Referer, up to three retries past the browser's negative cache, the caller's fallback (by default the card's empty frame) instead of the broken mark, the failure remembered by URL. Every picture now goes through it; a lint rule refuses a bare <img>.
36 lines
1.0 KiB
TypeScript
36 lines
1.0 KiB
TypeScript
import RemoteImage from "./RemoteImage";
|
|
|
|
// Avatar image with a graceful fallback: a neutral disc (optionally the name's initial) while there is
|
|
// no picture. The loading itself — no Referer for Google's avatar CDNs, the retry past the browser's
|
|
// negative cache, the failure remembered by URL so a recycled feed slot never paints one channel's
|
|
// initial over the next one's picture — is ui/RemoteImage's.
|
|
export default function Avatar({
|
|
src,
|
|
alt = "",
|
|
fallback,
|
|
className = "",
|
|
}: {
|
|
src?: string | null;
|
|
alt?: string;
|
|
fallback?: string; // a name/title; its first letter is shown when there's no image
|
|
className?: string;
|
|
}) {
|
|
const initial = fallback?.trim()?.[0]?.toUpperCase() ?? "";
|
|
return (
|
|
<RemoteImage
|
|
src={src}
|
|
alt={alt}
|
|
loading="lazy"
|
|
className={className}
|
|
fallback={
|
|
<div
|
|
className={`bg-card grid place-items-center font-semibold text-muted select-none ${className}`}
|
|
aria-hidden
|
|
>
|
|
{initial}
|
|
</div>
|
|
}
|
|
/>
|
|
);
|
|
}
|