chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQuery } from '@tanstack/react-query';
import { ImageOff } from 'lucide-react';
import { useState } from 'react';
import { Button } from '#/components/atoms/button.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 { Copyable } from '#/components/molecules/copyable.jsx';
import { EmptyState } from '#/components/molecules/empty-state.jsx';
import { RepoScope } from '#/components/molecules/repo-scope.jsx';
import { fileSize } from '#/lib/format.js';
import { href } from '#/lib/navigation.jsx';
import { useSession } from '#/lib/session.jsx';
import { getBlob, listBlobs } from '#/lib/spaces.js';
/** One blob, fetched only when a reader asks to see it. */
function BlobRow({ space, repo, cid }) {
const { session } = useSession();
const [open, setOpen] = useState(false);
const { data, error, isPending } = useQuery({
queryKey: ['blob', space, repo, cid],
queryFn: () => getBlob(session, { space, repo }, cid),
enabled: open,
retry: false,
});
return (
<Card className="flex flex-col gap-3 p-3.5">
<div className="flex items-center justify-between gap-3">
<Copyable value={cid} className="text-[12px]">
{cid.slice(0, 28)}…
</Copyable>
<Button variant="ghost" size="xs" onClick={() => setOpen(!open)}>
{open ? 'Hide' : 'Open'}
</Button>
</div>
{open && isPending && <Skeleton className="h-24" />}
{open && error && <Notice variant="error">{error.message}</Notice>}
{open && data && (
<>
{data.type.startsWith('image/') && (
<img
src={data.url}
alt={cid}
className="max-h-72 rounded-xl object-contain"
/>
)}
<a
href={data.url}
download={cid}
className="text-[13px] text-key hover:underline"
>
{data.type || 'unknown type'} · {fileSize(data.size)}
</a>
</>
)}
</Card>
);
}
export function BlobsPage({ space, repo }) {
const { session } = useSession();
const { data, error, isPending } = useQuery({
queryKey: ['blobs', space, repo],
queryFn: () => listBlobs(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">
Blobs
</h1>
</div>
<RepoScope space={space} repo={repo} />
<p className="text-muted-foreground text-sm leading-relaxed">
The blobs this repo's records in the space reference. Reachability is
read from the records themselves, so a blob no record names is not
listed and is not served.
</p>
{isPending && <Skeleton className="h-32" />}
{error && <Notice variant="error">{error.message}</Notice>}
{data?.cids.length === 0 && (
<EmptyState
icon={<ImageOff />}
title="No blobs"
description="No record in this space references one."
/>
)}
{data?.cids.map((cid) => (
<BlobRow key={cid} space={space} repo={repo} cid={cid} />
))}
</div>
);
}