zebflow 0.11.12

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
import { cx } from "zeb/react";
import { formatBytes } from "@/components/lib/format";
import { ExposureMark } from "@/pages/project-studio/files/components/exposure-mark";

function sum(rules, access) {
  const picked = rules.filter((rule) => rule.access === access);
  return {
    folders: picked.length,
    files: picked.reduce((n, rule) => n + (rule.files || 0), 0),
    bytes: picked.reduce((n, rule) => n + (rule.bytes || 0), 0),
    rules: picked,
  };
}

/**
 * What this project exposes, at the top of the explorer, every time: runs as a
 * site, readable by anyone, and everything else private. A rule jumps the
 * explorer to its folder.
 */
export function ExposureSummary({ exposure, onOpen }) {
  const rules = Array.isArray(exposure.rules) ? exposure.rules : [];
  const execute = sum(rules, "public_execute");
  const read = sum(rules, "public_read");
  const rows = [
    { key: "execute", label: "EXECUTE", totals: execute },
    { key: "read", label: "PUBLIC", totals: read },
  ];
  return (
    <div className="mx-3 mt-2 rounded-md border border-border bg-card text-[0.74rem]">
      {rows.map(({ key, label, totals }) => (
        <div key={key} className="flex flex-wrap items-center gap-2 border-b border-border px-3 py-1.5">
          <span className={cx("font-semibold w-16", key === "execute" ? "text-red-400" : "text-amber-400")}>{label}</span>
          <span className="text-muted-foreground">
            {totals.folders} rule{totals.folders === 1 ? "" : "s"} · {totals.files} file{totals.files === 1 ? "" : "s"} · {formatBytes(totals.bytes)}
          </span>
          {totals.rules.map((rule) => (
            <button
              key={rule.path}
              type="button"
              className="bg-transparent border-0 p-0 cursor-pointer"
              title={`Open ${rule.path}`}
              onClick={() => onOpen(rule.scope === "prefix" ? rule.path : rule.path.split("/").slice(0, -1).join("/"))}
            >
              <span className="inline-flex items-center gap-1">
                <span className="font-mono text-foreground">{rule.path}</span>
                <ExposureMark access={rule.access} serve={rule.serve} />
              </span>
            </button>
          ))}
        </div>
      ))}
      <div className="flex flex-wrap items-center gap-2 px-3 py-1.5 text-muted-foreground">
        <span className="font-semibold w-16">PRIVATE</span>
        <span>everything else</span>
        {exposure.fileHost ? (
          <span className="ml-auto">file host: <span className="font-mono">{exposure.fileHost}</span></span>
        ) : null}
      </div>
    </div>
  );
}