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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
86 lines · 3.3 KB Raw History
import { useEffect, useState, useSyncExternalStore } from 'react';
import { bytes } from '#/lib/format.js';
import { onRepoProgress, readSource, repoProgress } from '#/lib/git.js';

/**
 * How long a read has to run before it is worth telling anyone about. A
 * repository already open answers in a few milliseconds, and a bar that
 * appears and leaves inside one frame reads as a flicker rather than as
 * progress.
 */
const QUIET_MS = 400;

/**
 * What a repository being opened looks like while it opens.
 *
 * Read in this page, an indexed repository opens off its small index blobs
 * and one without indexes downloads its whole bundle chain. Either way the
 * record says how large the download is before the first byte arrives, so
 * the wait can be an honest measure rather than a spinner. Read on the
 * server, nothing is downloaded here, so the wait has no measure and says
 * only that the server is at work.
 *
 * Nothing renders until the read has run past QUIET_MS, so the common case,
 * an indexed repository or one already in memory, shows no wait at all.
 * Whatever appears after that fades in, so a read that finishes just over
 * the line does not snap on and straight off again.
 */
export function ReadingRepo({ size }) {
  // The third argument is the snapshot for a render with no client behind
  // it. The store is a plain module value, so it reads the same either way.
  const progress = useSyncExternalStore(
    onRepoProgress,
    repoProgress,
    repoProgress,
  );
  const source = useSyncExternalStore(onRepoProgress, readSource, readSource);
  const [slow, setSlow] = useState(false);

  useEffect(() => {
    const timer = setTimeout(() => setSlow(true), QUIET_MS);
    return () => clearTimeout(timer);
  }, []);

  if (!slow) return null;

  if (source === 'server' && progress.total === 0) {
    return (
      <div className="fade-in rounded-2xl border border-border bg-card px-5 py-6">
        <div className="text-[13px] text-muted-foreground">
          Reading the repository
        </div>
        <div className="mt-3 h-1.5 overflow-hidden rounded-full bg-muted">
          <div className="h-full w-1/3 animate-pulse rounded-full bg-key" />
        </div>
        <p className="mt-3 text-[12.5px] text-faint">
          The server reads it from its own storage. The answer is cached from
          here on.
        </p>
      </div>
    );
  }

  const total = progress.total || size || 0;
  const done = total > 0 ? Math.min(1, progress.loaded / total) : 0;

  return (
    <div className="fade-in rounded-2xl border border-border bg-card px-5 py-6">
      <div className="flex items-baseline justify-between gap-3 text-[13px]">
        <span className="text-muted-foreground">Reading the repository</span>
        <span className="font-mono text-[12.5px] text-faint tabular-nums">
          {total > 0 ? `${bytes(progress.loaded)} of ${bytes(total)}` : ''}
        </span>
      </div>
      <div className="mt-3 h-1.5 overflow-hidden rounded-full bg-muted">
        <div
          className="h-full rounded-full bg-key transition-[width] duration-200"
          style={{ width: `${Math.round(done * 100)}%` }}
        />
      </div>
      <p className="mt-3 text-[12.5px] text-faint">
        The repository is read straight from its bundles in blob storage. It is
        cached from here on.
      </p>
    </div>
  );
}