/** * Renders a user's avatar, or their initial, wherever a user is named: cards, * comments, chat, profiles and mention suggestions. * * @module */ import { useContext, useState } from "react"; import { imageProps, userApiUrl } from "../../utils/shared/urls.ts"; import { AvatarOverrideContext } from "../../contexts/ws/AvatarOverrideContext.ts"; /** * Passed to {@link Avatar} from whatever data the caller has on the user, which * may be stale: a live override from `AvatarOverrideContext` wins over * `hasAvatar` and `version`. */ export interface AvatarProps { userId: string; username: string; hasAvatar: boolean; /** Sets the side, in pixels. */ size?: number; /** Added to the URL so that a changed avatar isn't served from cache. */ version?: number; } /** * Renders the image from `GET /api/users/:userId/avatar`, scaled by the server * to the size it is shown at, or the user's initial when they have none or it * fails to load. An avatar added, changed or removed * since the caller's data was fetched shows at once, through the * `user_updated` messages that `WSProvider` puts in `AvatarOverrideContext`. */ export function Avatar( { userId, username, hasAvatar, size = 36, version }: AvatarProps, ): React.JSX.Element { const [imgFailed, setImgFailed] = useState(false); const sizeStyle = { width: size, height: size }; const override = useContext(AvatarOverrideContext)[userId]; const effectiveHasAvatar = override ? override.hasAvatar : hasAvatar; const effectiveVersion = override ? override.version : version; // A new image deserves a new try, so a past load failure is cleared. It is // done during render, since an effect would show the initial for a frame. const imgKey = `${effectiveHasAvatar}:${effectiveVersion ?? ""}`; const [prevImgKey, setPrevImgKey] = useState(imgKey); if (prevImgKey !== imgKey) { setPrevImgKey(imgKey); setImgFailed(false); } if (effectiveHasAvatar && !imgFailed) { const src = effectiveVersion ? `${userApiUrl({ id: userId }, "avatar")}?v=${effectiveVersion}` : userApiUrl({ id: userId }, "avatar"); return ( {username} setImgFailed(true)} /> ); } return (
{username.charAt(0).toUpperCase()}
); }