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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
118 lines · 3.9 KB Raw History
import { useQuery } from '@tanstack/react-query';
import { History } from 'lucide-react';
import { Badge } from '#/components/atoms/badge.jsx';
import { Card } from '#/components/atoms/card.jsx';
import { Link } from '#/components/atoms/link.jsx';
import { Notice } from '#/components/atoms/notice.jsx';
import { Skeleton } from '#/components/atoms/skeleton.jsx';
import { EmptyState } from '#/components/molecules/empty-state.jsx';
import { RepoScope } from '#/components/molecules/repo-scope.jsx';
import { ago, rkeyTime } from '#/lib/format.js';
import { href } from '#/lib/navigation.jsx';
import { useSession } from '#/lib/session.jsx';
import { listRepoOps } from '#/lib/spaces.js';

/**
 * What one op did. The wire says it in two fields: a `cid` of null is a delete,
 * and a `prev` names the version this one replaced.
 * @param {{cid: string|null, prev: string|null}} op
 * @returns {string}
 */
function opAction(op) {
  if (!op.cid) return 'delete';
  return op.prev ? 'update' : 'create';
}

export function OpsPage({ space, repo }) {
  const { session } = useSession();

  const { data, error, isPending } = useQuery({
    queryKey: ['ops', space, repo],
    queryFn: () => listRepoOps(session, { space, repo }),
    retry: false,
  });

  return (
    <div className="flex flex-col gap-4">
      <div>
        <Link
          href={href('/space', { uri: space, repo })}
          className="text-[13px]"
        >
          ← Back to the space
        </Link>
        <h1 className="mt-2 font-semibold text-[16px] text-foreground">
          Write log
        </h1>
      </div>

      <RepoScope space={space} repo={repo} />

      <p className="text-muted-foreground text-sm leading-relaxed">
        Every write this repo made to the space, oldest first on the wire and
        newest first here. A syncer reads the same list to catch up from a
        revision it already holds.
      </p>

      {isPending && <Skeleton className="h-40" />}
      {error && <Notice variant="error">{error.message}</Notice>}

      {data?.ops.length === 0 && (
        <EmptyState
          icon={<History />}
          title="Nothing written"
          description="This repo has made no writes to this space."
        />
      )}

      {data && (
        <div className="flex flex-col gap-2">
          {[...data.ops].reverse().map((op) => {
            const action = opAction(op);
            const written = rkeyTime(op.rkey);
            return (
              <Card
                key={`${op.rev}-${op.collection}-${op.rkey}`}
                className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 p-3.5"
              >
                <span className="flex min-w-0 flex-wrap items-baseline gap-2">
                  <Badge variant={action === 'delete' ? 'default' : 'scope'}>
                    {action}
                  </Badge>
                  {action === 'delete' ? (
                    <span className="font-mono text-[12.5px] text-muted-foreground">
                      {op.collection}/{op.rkey}
                    </span>
                  ) : (
                    <Link
                      href={href('/record', {
                        uri: space,
                        collection: op.collection,
                        rkey: op.rkey,
                        repo,
                      })}
                      className="font-mono text-[12.5px]"
                    >
                      {op.collection}/{op.rkey}
                    </Link>
                  )}
                </span>
                <span className="font-mono text-faint text-xs">
                  {op.rev}
                  {written && ` · ${ago(written)}`}
                </span>
              </Card>
            );
          })}
        </div>
      )}

      {data?.cursor && (
        <Notice>
          Showing the first {data.ops.length} ops. There are more behind a
          cursor this page does not follow yet.
        </Notice>
      )}
    </div>
  );
}