chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
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>
);
}