chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQuery } from '@tanstack/react-query';
import { CheckIcon, CopyIcon, PackageIcon } from 'lucide-react';
import { useState } from '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 { TextLink } from '#/components/molecules/text-link.jsx';
import { get } from '#/lib/api.js';
import { bytes, timeAgo } from '#/lib/format.js';
import { queryKeys } from '#/lib/query.js';
/** One line summarizing a package's shape. */
function packageSummary(pkg) {
if (pkg.versions.length === 0) return 'nothing published yet';
const parts = [
`${pkg.versions.length} ${pkg.versions.length === 1 ? 'version' : 'versions'}`,
];
parts.push(bytes(pkg.size));
if (pkg.updatedAt) parts.push(`published ${timeAgo(pkg.updatedAt)}`);
return parts.join(' · ');
}
/** A command with a copy button beside it, shown in full where room allows. */
function CopyCommand({ command, compact }) {
const [copied, setCopied] = useState(false);
const copy = (event) => {
event.preventDefault();
event.stopPropagation();
navigator.clipboard.writeText(command).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
});
};
return (
<button
type="button"
onClick={copy}
title={`Copy: ${command}`}
className="flex shrink-0 items-center gap-1.5 rounded-lg border border-border bg-background px-2 py-1 font-mono text-[12px] text-muted-foreground hover:text-foreground"
>
{!compact && <span className="hidden lg:inline">{command}</span>}
{copied ? (
<CheckIcon className="size-3.5" />
) : (
<CopyIcon className="size-3.5" />
)}
</button>
);
}
/** Every version of one package: number, size, when, its install command. */
function VersionList({ pkg, registryUrl }) {
const rows = [...pkg.versions].reverse();
return (
<div className="max-h-72 overflow-y-auto rounded-2xl border border-border bg-card px-5 py-2">
{rows.map((entry) => (
<div
key={entry.version}
className="flex items-center gap-3 border-border border-b py-2 text-[12.5px] last:border-b-0"
>
<span className="min-w-0 shrink-0 font-mono">{entry.version}</span>
<span className="min-w-0 flex-1 truncate text-muted-foreground">
{bytes(entry.size)}
{entry.publishedAt
? ` · published ${timeAgo(entry.publishedAt)}`
: ''}
</span>
<CopyCommand
command={`npm install ${pkg.name}@${entry.version} --registry ${registryUrl}`}
compact
/>
</div>
))}
</div>
);
}
/**
* @param {{data: Object}} props
*/
export function PackagesPage({ data: account }) {
const [openVersions, setOpenVersions] = useState(
/** @type {string|null} */ (null),
);
const { data, error } = useQuery({
queryKey: queryKeys.npmPackages,
queryFn: () => get('/npm/packages'),
});
if (error) {
return <Notice variant="error">{error.message}</Notice>;
}
if (!data) return null;
if (!data.packages.length) {
return (
<EmptyState
icon={<PackageIcon />}
title="No packages yet"
description={
<>
Your account is an npm registry. Point npm at{' '}
<span className="font-mono text-[12px]">
https://{account.hostname}/npm/
</span>
, sign in with{' '}
<span className="font-mono text-[12px]">npm login</span> using an
app password, and{' '}
<span className="font-mono text-[12px]">npm publish</span>. Tarballs
are stored in your repo as blobs, so packages ride along in backups
and migrate with your account. Anyone can install.
</>
}
/>
);
}
return (
<div className="flex flex-col gap-3">
{data.packages.map((pkg) => {
const latest = pkg.distTags.find((t) => t.tag === 'latest');
const otherTags = pkg.distTags.filter((t) => t.tag !== 'latest');
return (
<div key={pkg.name} className="flex flex-col gap-2">
<ListRow
className="px-5 py-4"
media={
<RowIcon>
<PackageIcon />
</RowIcon>
}
title={
<div className="flex min-w-0 items-center gap-2">
<span className="min-w-0 truncate text-[16px]">
{pkg.name}
</span>
{latest && (
<Badge variant="scope" className="shrink-0">
v{latest.version}
</Badge>
)}
{otherTags.slice(0, 3).map((tag) => (
<Badge variant="scope" className="shrink-0" key={tag.tag}>
{tag.tag}
</Badge>
))}
</div>
}
description={packageSummary(pkg)}
actions={
<div className="flex shrink-0 items-center gap-3">
{pkg.versions.length > 0 && (
<TextLink
onClick={() =>
setOpenVersions(
openVersions === pkg.name ? null : pkg.name,
)
}
>
{openVersions === pkg.name ? 'Hide versions' : 'Versions'}
</TextLink>
)}
<CopyCommand
command={`npm install ${pkg.name} --registry ${data.registryUrl}`}
/>
</div>
}
/>
{openVersions === pkg.name && (
<VersionList pkg={pkg} registryUrl={data.registryUrl} />
)}
</div>
);
})}
</div>
);
}