~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
164 lines · 6.4 KB Raw History
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>
  );
}