Show identicons for breadcrumbs to match with user profile avatar
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run

This commit is contained in:
Kalle 2026-07-09 21:13:19 +03:00
parent d84b5834ac
commit 3cef92e82f
4 changed files with 22 additions and 3 deletions

View File

@ -42,7 +42,7 @@ function generateColors(hash: number) {
};
}
function generateIdenticon(input: string, size = 128, gridSize = 5) {
export function generateIdenticon(input: string, size = 128, gridSize = 5) {
const cacheKey = `${input}-${size}-${gridSize}`;
const cached = identiconCache.get(cacheKey);
if (cached) return cached;

View File

@ -40,7 +40,7 @@ import {
SETTINGS_PAGE,
userPage,
} from "~/utils/urls";
import { Avatar } from "../Avatar";
import { Avatar, generateIdenticon } from "../Avatar";
import { SendouButton } from "../elements/Button";
import { SendouPopover } from "../elements/Popover";
import { FuseZone } from "../fuse/Fuse";
@ -601,6 +601,9 @@ function SideNavCollapseButton({
}
function PageIcon({ crumb }: { crumb: Breadcrumb }) {
const [isErrored, setIsErrored] = React.useState(false);
const isClient = useHydrated();
if (crumb.type !== "IMAGE") {
return null;
}
@ -609,15 +612,28 @@ function PageIcon({ crumb }: { crumb: Breadcrumb }) {
const isExternal = lastPathSegment.includes(".");
const iconClass = clsx(styles.pageIcon, "rounded");
// an <img> can finish loading (and fail) before React hydrates and attaches onError, so that
// error is missed — re-check on mount and fall back manually so SSR'd icons still heal
const checkAlreadyErrored = (img: HTMLImageElement | null) => {
if (img?.complete && img.naturalWidth === 0) setIsErrored(true);
};
const identiconSrc =
isErrored && isClient && crumb.identiconInput
? generateIdenticon(crumb.identiconInput, 28, 7)
: null;
return (
<div className={styles.pageIconWrapper}>
{isExternal ? (
<img
src={crumb.imgPath}
ref={checkAlreadyErrored}
src={identiconSrc ?? crumb.imgPath}
alt=""
className={iconClass}
width={28}
height={28}
onError={() => setIsErrored(true)}
/>
) : (
<Image

View File

@ -69,6 +69,7 @@ export const handle: SendouRouteHandle = {
href: userPage(data.user),
type: "IMAGE",
text: data.user.username,
identiconInput: String(data.user.discordId),
};
},
};

View File

@ -290,6 +290,8 @@ export type Breadcrumb =
type: "IMAGE";
href: string;
text?: string;
/** Seed for the identicon shown if `imgPath` fails to load. */
identiconInput?: string;
}
| { text: string; type: "TEXT"; href: string };