chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { nsidDomain } from '#/lib/format.js';
import { appProfileOf } from '#/lib/profile.js';
import { cn } from '#/lib/utils.js';
/**
* A square tile standing for the app a space belongs to.
*
* The picture is the app account's own, read from its own PDS, because an NSID
* reverses to a domain and these domains are atproto handles. A favicon would
* mean asking the app's web server or an icon service, and either is then told
* which apps' spaces this browser is reading.
*
* A domain that is no handle, or an account with no picture, reads as its
* initial. The tile keeps its size either way, so a row does not reflow when a
* picture arrives.
*
* @param {Object} props
* @param {string} props.nsid - the space type
* @param {number} [props.size]
* @param {string} [props.className]
*/
export function AppTile({ nsid, size = 40, className }) {
const [broken, setBroken] = useState(false);
const { data } = useQuery({
queryKey: ['app-profile', nsid],
queryFn: () => appProfileOf(nsid),
retry: false,
staleTime: Number.POSITIVE_INFINITY,
enabled: Boolean(nsid),
});
const src = !broken && data?.avatar;
return (
<div
className={cn(
'flex shrink-0 items-center justify-center overflow-hidden rounded-xl bg-muted font-extrabold text-muted-foreground',
className,
)}
style={{ width: size, height: size, fontSize: Math.round(size * 0.42) }}
title={nsidDomain(nsid || '')}
>
{src ? (
<img
src={data.avatar}
alt=""
width={size}
height={size}
className="size-full object-cover"
onError={() => setBroken(true)}
/>
) : (
(nsid || '?').slice(0, 1).toUpperCase()
)}
</div>
);
}