chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQuery } from '@tanstack/react-query';
import { FolderGit2Icon, LockIcon } from 'lucide-react';
import { Badge } from '#/components/atoms/badge.jsx';
import { Notice } from '#/components/atoms/notice.jsx';
import { RowIcon } from '#/components/atoms/row-icon.jsx';
import { EmptyState } from '#/components/molecules/empty-state.jsx';
import { ListRow } from '#/components/molecules/list-row.jsx';
import { xrpcGet } from '#/lib/api.js';
import { bytes, timeAgo } from '#/lib/format.js';
import { queryKeys } from '#/lib/query.js';
import { NewRepoDialog } from '#/pages/git-new-repo.jsx';
/** One line summarizing a repository's shape. */
function repoSummary(repo) {
if (repo.branches === 0 && repo.bundles === 0) return 'nothing pushed yet';
const parts = [
`${repo.branches} ${repo.branches === 1 ? 'branch' : 'branches'}`,
];
if (repo.tags > 0) {
parts.push(`${repo.tags} ${repo.tags === 1 ? 'tag' : 'tags'}`);
}
parts.push(bytes(repo.size));
if (repo.updatedAt) parts.push(`pushed ${timeAgo(repo.updatedAt)}`);
return parts.join(' · ');
}
/** The repository page URL, carrying the space for a private repository. */
export function repoHref(repo) {
const base = `/account/git/${encodeURIComponent(repo.name)}`;
return repo.space ? `${base}?space=${encodeURIComponent(repo.space)}` : base;
}
/**
* @param {{data: Object, onNavigate: (href: string) => void}} props
*/
export function GitPage({ data: account, onNavigate }) {
const { data, error } = useQuery({
queryKey: queryKeys.gitRepos,
queryFn: () => xrpcGet('dev.pdsjs.git.listRepos'),
});
if (error) {
return <Notice variant="error">{error.message}</Notice>;
}
if (!data) return null;
const newRepo = <NewRepoDialog data={account} onNavigate={onNavigate} />;
if (!data.repos.length) {
return (
<div className="flex flex-col gap-3">
<div className="flex justify-end">{newRepo}</div>
<EmptyState
icon={<FolderGit2Icon />}
title="No repositories yet"
description={
<>
Your account can host git repositories. Install the helper with{' '}
<span className="font-mono text-[12px]">
npm install -g @pdsjs/git
</span>{' '}
and push to{' '}
<span className="font-mono text-[12px]">
atproto://your.handle/repo-name
</span>
. History is stored in your repo as bundles, so it rides along in
backups and migrates with your account.
</>
}
/>
</div>
);
}
return (
<div className="flex flex-col gap-3">
<div className="flex justify-end">{newRepo}</div>
{data.repos.map((repo) => (
<ListRow
key={repo.space ? `${repo.space}#${repo.name}` : repo.name}
className="px-5 py-4"
media={
<RowIcon>
<FolderGit2Icon />
</RowIcon>
}
title={
<div className="flex min-w-0 items-center gap-2">
<span className="min-w-0 truncate text-[16px]">{repo.name}</span>
{repo.space && (
<Badge variant="scope" className="shrink-0">
<LockIcon className="size-3" /> Private
</Badge>
)}
{repo.defaultBranch && (
<Badge variant="scope" className="shrink-0">
{repo.defaultBranch}
</Badge>
)}
</div>
}
description={repo.description || repoSummary(repo)}
actions={
repo.description ? (
<span className="shrink-0 text-[12px] text-muted-foreground">
{repoSummary(repo)}
</span>
) : undefined
}
href={repoHref(repo)}
onNavigate={onNavigate}
/>
))}
</div>
);
}