chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { lfsPointer } from '@pdsjs/git/browser';
import { useQuery } from '@tanstack/react-query';
import { FileCodeIcon } from 'lucide-react';
import { ButtonLink } from '#/components/atoms/button-link.jsx';
import { Skeleton } from '#/components/atoms/skeleton.jsx';
import { FileCommit } from '#/components/molecules/file-commit.jsx';
import { PageNotice } from '#/components/molecules/page-notice.jsx';
import { ReadingRepo } from '#/components/molecules/reading-repo.jsx';
import { RefSelect } from '#/components/molecules/ref-select.jsx';
import { SourceCrumbs } from '#/components/molecules/source-crumbs.jsx';
import { SourceLines } from '#/components/molecules/source-lines.jsx';
import { SourcePanes } from '#/components/molecules/source-panes.jsx';
import { bytes } from '#/lib/format.js';
import { capabilities, rawUrl, repoRecord, repoSize } from '#/lib/git.js';
import { useNavigate } from '#/lib/navigation.jsx';
import { useObjectUrl } from '#/lib/object-url.js';
import { fileKey, parentOf, readSource, useFileCommit } from '#/lib/source.js';
export function FilePage({ repo, refName, path, space }) {
const navigate = useNavigate();
const record = repoRecord(repo, space);
const { data, error } = useQuery({
queryKey: fileKey(repo, refName, path, space),
enabled: Boolean(record),
queryFn: () => readSource(repo, refName, path, space),
});
// A file tracked by Git LFS is a few lines naming the file it stands for,
// so the bytes an image path returns are text rather than pixels.
const pointer = data?.image ? lfsPointer(data.image.bytes) : null;
const imageUrl = useObjectUrl(
pointer ? null : data?.image?.bytes,
data?.image?.mediaType ?? '',
);
// Who last changed this file. The walk reads trees rather than the file, so
// it runs behind the paint and fills its line in when it lands.
const { data: fileCommit, isPending: commitPending } = useFileCommit(
repo,
refName,
path,
space,
);
if (!record) {
return <PageNotice>No such repository.</PageNotice>;
}
const selectRef = (next) =>
navigate(
`/${encodeURIComponent(repo)}/blob/${encodeURI(next)}/${encodeURI(path)}`,
);
const lines =
data?.file?.text != null ? data.file.text.split('\n').length : 0;
return (
<div className="flex min-h-0 flex-1 flex-col">
<SourcePanes
repo={repo}
record={record}
refName={refName}
onSelectRef={selectRef}
dirPath={parentOf(path)}
selected={path}
space={space}
>
<div className="px-(--gutter) pb-(--gutter)">
<SourceCrumbs
repo={repo}
refName={refName}
path={path}
actions={
<span className="md:hidden">
<RefSelect repo={record} value={refName} onSelect={selectRef} />
</span>
}
/>
{/* The panel is not clipped, so its header can pin under the crumbs
while the lines scroll past; the lines clip themselves to the
panel's bottom corners. */}
<div className="rounded-lg border border-border bg-card">
<FileCommit
repo={repo}
commit={fileCommit}
pending={commitPending}
historyHref={`/${encodeURIComponent(repo)}/commits/${encodeURI(refName)}/${encodeURI(path)}`}
className="rounded-t-lg px-4"
/>
<div className="sticky top-11 z-10 flex min-h-10 flex-wrap items-center gap-x-4 gap-y-1 border-border border-b bg-muted px-4 py-1.5 text-[12.5px] text-muted-foreground">
<span className="flex items-center gap-2 font-mono">
{lines > 0 && (
<span>
{lines} {lines === 1 ? 'line' : 'lines'}
</span>
)}
{lines > 0 && <span className="text-faint">·</span>}
<span>{bytes(data?.file?.size)}</span>
{data?.file?.mode === '120000' && <span>symlink</span>}
</span>
<span className="ml-auto flex items-center gap-2">
{capabilities.rawFiles && !space && (
<ButtonLink
href={rawUrl(repo, refName, path)}
external
size="xs"
>
<FileCodeIcon />
Raw
</ButtonLink>
)}
</span>
</div>
<div className="overflow-hidden rounded-b-lg">
{error && <PageNotice tone="error">{error.message}</PageNotice>}
{!data && !error && (
<>
<div className="empty:hidden px-4 pt-4">
<ReadingRepo size={repoSize(repoRecord(repo, space))} />
</div>
<div
className="flex flex-col gap-2.5 px-4 py-4"
aria-hidden="true"
>
{['w-1/3', 'w-2/3', 'w-1/2', 'w-3/5', 'w-1/4', 'w-2/5'].map(
(width) => (
<Skeleton key={width} className={`h-4 ${width}`} />
),
)}
</div>
</>
)}
{data?.file &&
(pointer ? (
<p className="px-4 py-5 text-[13px] text-muted-foreground">
{`Stored with Git LFS, ${bytes(pointer.size)}. This repository holds the pointer, not the file itself.`}
</p>
) : data.image ? (
<div className="flex justify-center p-4">
{imageUrl && (
<img
src={imageUrl}
alt={path}
className="h-auto max-w-full rounded-lg"
/>
)}
</div>
) : data.file.text !== null ? (
<SourceLines key={path} text={data.file.text} path={path} />
) : (
<p className="px-4 py-5 text-[13px] text-muted-foreground">
{data.file.binary
? `Binary file, ${bytes(data.file.size)}.`
: `File is ${bytes(data.file.size)}, too large to show.`}
</p>
))}
</div>
</div>
</div>
</SourcePanes>
</div>
);
}