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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
255 lines · 8.8 KB Raw History
import { useQuery } from '@tanstack/react-query';
import { GitBranchIcon, TagIcon } from 'lucide-react';
import { Skeleton } from '#/components/atoms/skeleton.jsx';
import { CodeMenu } from '#/components/molecules/code-menu.jsx';
import { DirTable } from '#/components/molecules/dir-table.jsx';
import { FileCommit } from '#/components/molecules/file-commit.jsx';
import { LatestCommit } from '#/components/molecules/latest-commit.jsx';
import { ListBox } from '#/components/molecules/list-box.jsx';
import { ListSkeleton } from '#/components/molecules/list-skeleton.jsx';
import { PageNotice } from '#/components/molecules/page-notice.jsx';
import { QuickSetup } from '#/components/molecules/quick-setup.jsx';
import { ReadingRepo } from '#/components/molecules/reading-repo.jsx';
import { ReadmePanel } from '#/components/molecules/readme-panel.jsx';
import { RefSelect } from '#/components/molecules/ref-select.jsx';
import { RepoAbout } from '#/components/molecules/repo-about.jsx';
import { RepoHeading } from '#/components/molecules/repo-heading.jsx';
import { Screen } from '#/components/molecules/screen.jsx';
import { SourceCrumbs } from '#/components/molecules/source-crumbs.jsx';
import { SourcePanes } from '#/components/molecules/source-panes.jsx';
import {
  account,
  branchRefs,
  capabilities,
  isEmpty,
  readerFor,
  repoRecord,
  repoSize,
  tagRefs,
} from '#/lib/git.js';
import { useNavigate } from '#/lib/navigation.jsx';
import { useDirectory, useFileCommit, useHistory } from '#/lib/source.js';

const README = /^readme(\.md|\.txt|\.markdown)?$/i;

export function TreePage({ repo, refName, path, space }) {
  const navigate = useNavigate();
  const record = repoRecord(repo, space);

  // The listing the pane beside this one draws. Reading it here too costs
  // nothing: one query key serves both.
  const { data: entries, error } = useDirectory(repo, refName, path, space);

  // What this directory says about itself. A directory with no README leaves
  // the reading pane empty, which is what a checkout looks like.
  const readme = (entries ?? []).find(
    (entry) => entry.type === 'file' && README.test(entry.name),
  );
  const { data: readmeFile } = useQuery({
    queryKey: ['readme', repo, refName, path, readme?.name, space ?? ''],
    enabled: Boolean(readme),
    queryFn: () =>
      readerFor(repo, undefined, space).readFile(
        account.did,
        repo,
        refName,
        path ? `${path}/${readme.name}` : readme.name,
      ),
  });

  // The tip of the ref and how far its history runs. The gauges in the pane
  // beside this one read the same answer.
  const { data: history } = useHistory(repo, refName, space);

  // The last commit to touch this directory, for the panel's header inside
  // one. The walk runs behind the paint and the line fills in when it lands.
  const { data: dirCommit, isPending: dirCommitPending } = useFileCommit(
    repo,
    refName,
    path,
    space,
  );

  if (!record) {
    return <PageNotice>No such repository.</PageNotice>;
  }

  const branches = branchRefs(record).length;
  const tags = tagRefs(record).length;

  // What this view is looking at: which ref, and where inside it. The
  // sections of the repository, and cloning it, are in the band above.
  //
  // Inside a directory the path leads, and the picker answers its right, the
  // way a forge reads: the reader is navigating the path. At the root there
  // is no path, so the picker stands first and the ref counts beside it.
  const selectRef = (next) =>
    navigate(`/${encodeURIComponent(repo)}/tree/${encodeURI(next)}`);
  const refPicker = (
    <RefSelect repo={record} value={refName} onSelect={selectRef} />
  );
  if (isEmpty(record)) {
    return (
      <Screen>
        <div className="px-(--gutter) pt-5">
          <RepoHeading repo={repo} space={space} />
        </div>
        <div className="px-(--gutter) py-6">
          <QuickSetup record={record} space={space} />
        </div>
      </Screen>
    );
  }

  const readmePanel = readme && (
    <ReadmePanel
      className="m-0 mt-4"
      name={readme.name}
      text={readmeFile?.text}
      repo={repo}
      refName={refName}
      basePath={path}
      space={space}
    />
  );

  // The root is where a repository is read about: its files in one panel
  // with the latest commit on the panel's header, what it says about itself
  // in the panel under that, and what it is beside them. Inside a directory
  // the reader is navigating, and the panes take over.
  if (!path) {
    return (
      <Screen>
        <div className="px-(--gutter) pt-5">
          <RepoHeading repo={repo} space={space} />
        </div>
        <div className="flex flex-col gap-6 px-(--gutter) py-4 lg:flex-row lg:items-start">
          <div className="min-w-0 flex-1">
            {/* What the panel below is looking at, and the one action a
                repository has: the way to take it away. */}
            <div className="mb-3 flex flex-wrap items-center gap-3">
              {refPicker}
              <span className="flex items-center gap-1.5 text-[13px] text-faint">
                <GitBranchIcon className="size-3.5" />
                <span className="font-semibold text-foreground">
                  {branches}
                </span>
                {branches === 1 ? 'branch' : 'branches'}
              </span>
              {tags > 0 && (
                <span className="flex items-center gap-1.5 text-[13px] text-faint">
                  <TagIcon className="size-3.5" />
                  <span className="font-semibold text-foreground">{tags}</span>
                  {tags === 1 ? 'tag' : 'tags'}
                </span>
              )}
              <span className="ml-auto">
                <CodeMenu
                  repo={repo}
                  httpClone={capabilities.httpClone}
                  space={space}
                />
              </span>
            </div>
            {error && <PageNotice tone="error">{error.message}</PageNotice>}
            <ListBox className="m-0">
              {history?.head ? (
                <LatestCommit
                  repo={repo}
                  refName={refName}
                  commit={history.head}
                  count={history.count}
                  truncated={history.truncated}
                  space={space}
                />
              ) : (
                <div className="flex min-h-[41px] items-center border-border border-b bg-muted px-4 py-2.5">
                  <Skeleton className="h-4 w-72 max-w-full" />
                </div>
              )}
              {entries ? (
                <DirTable
                  repo={repo}
                  refName={refName}
                  path={path}
                  entries={entries}
                />
              ) : (
                !error && (
                  <>
                    <div className="empty:hidden border-border border-b p-4">
                      <ReadingRepo size={repoSize(record)} />
                    </div>
                    <ListSkeleton rows={5} />
                  </>
                )
              )}
            </ListBox>
            {readmePanel}
          </div>
          <aside className="w-full shrink-0 lg:w-72">
            <RepoAbout
              record={record}
              repo={repo}
              refName={refName}
              space={space}
            />
          </aside>
        </div>
      </Screen>
    );
  }

  return (
    <div className="flex min-h-0 flex-1 flex-col">
      <SourcePanes
        repo={repo}
        record={record}
        refName={refName}
        onSelectRef={selectRef}
        dirPath={path}
        space={space}
      >
        <div className="px-(--gutter) pb-(--gutter)">
          <SourceCrumbs
            repo={repo}
            refName={refName}
            path={path}
            actions={<span className="md:hidden">{refPicker}</span>}
          />
          {error && <PageNotice tone="error">{error.message}</PageNotice>}
          {!entries && !error && (
            <>
              <div className="empty:hidden pb-4">
                <ReadingRepo size={repoSize(record)} />
              </div>
              <ListBox className="m-0">
                <ListSkeleton rows={5} />
              </ListBox>
            </>
          )}
          {entries && (
            <>
              <ListBox className="m-0">
                <FileCommit
                  repo={repo}
                  commit={dirCommit}
                  pending={dirCommitPending}
                  historyHref={`/${encodeURIComponent(repo)}/commits/${encodeURI(refName)}/${encodeURI(path)}`}
                  className="px-4"
                />
                <DirTable
                  repo={repo}
                  refName={refName}
                  path={path}
                  entries={entries}
                />
              </ListBox>
              {readmePanel}
            </>
          )}
        </div>
      </SourcePanes>
    </div>
  );
}