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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
77 lines · 2.7 KB Raw History
import { useMemo } from 'react';
import { Skeleton } from '#/components/atoms/skeleton.jsx';
import { ActivityTicks } from '#/components/molecules/activity-ticks.jsx';
import { LanguageBar } from '#/components/molecules/language-bar.jsx';
import { ACTIVITY_WEEKS } from '#/lib/activity.js';
import { languageShares, percent } from '#/lib/languages.js';
import { useFileList, useHistory } from '#/lib/source.js';

/**
 * Two readings of the repository as a whole: what it is written in, and how
 * lately it was worked on. Both stand under the file pane, so they keep the
 * reader's company whatever file or directory is open.
 *
 * Each walk reads objects the page has already downloaded, and both share
 * their keys with the pages that want the same answers, so a gauge costs no
 * request of its own.
 */
export function RepoGauges({ repo, refName, space }) {
  const { data: files, isPending: languagesPending } = useFileList(
    repo,
    refName,
    space,
  );
  const languages = useMemo(
    () => (files ? languageShares(files) : undefined),
    [files],
  );

  const { data: history, isPending: historyPending } = useHistory(
    repo,
    refName,
    space,
  );

  const showLanguages = languagesPending || Boolean(languages?.length);
  const showActivity = historyPending || Boolean(history?.dates?.length);
  if (!showLanguages && !showActivity) return null;

  return (
    <div className="flex flex-col gap-3.5">
      {/* Each gauge keeps its place from the first frame. Both walks run
          over the whole repository, so they land well after the file beside
          them, and a listing that stepped down the page twice on the way
          would be the reader's problem rather than the walk's. */}
      {showLanguages && (
        <div>
          {languages?.length ? (
            <LanguageBar languages={languages} />
          ) : (
            <Skeleton className="h-3 w-full" />
          )}
          {/* The placeholder sits inside the line rather than replacing it,
              so the label keeps the height its text will have. */}
          <div className="mt-1.5 truncate text-[10px] text-faint uppercase tracking-[0.12em]">
            {languages?.length ? (
              `${languages[0].name} ${percent(languages[0].share)}`
            ) : (
              <Skeleton className="inline-block h-2 w-24 align-middle" />
            )}
          </div>
        </div>
      )}
      {showActivity && (
        <div>
          {history?.dates?.length ? (
            <ActivityTicks dates={history.dates} />
          ) : (
            <Skeleton className="h-3 w-full" />
          )}
          <div className="mt-1.5 text-[10px] text-faint uppercase tracking-[0.12em]">
            {ACTIVITY_WEEKS} weeks
          </div>
        </div>
      )}
    </div>
  );
}