~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
58 lines · 1.8 KB Raw History
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>
  );
}