import { defineRenderers, type HeaderContext, type HeaderView } from "@berry/api/web.ts"; import * as j from "@char/justin"; import { isProvider, type Provider, providerNames, type SubscriptionUsage, usageSchema, } from "./types.ts"; import "./web.css"; const usageOf = j.validation.compile(usageSchema); export function subscriptionHeader(): HeaderView { const button = as HTMLButtonElement; const body =
as HTMLElement; const panel = ( ) as HTMLElement; const element = as HTMLElement; let context: HeaderContext; let provider: Provider | null = null; let open = false; let disposed = false; let revision = 0; let pending = false; let timer: ReturnType | undefined; let result: SubscriptionUsage | null = null; let fetchedAt = 0; function draw() { if (result === null) { body.textContent = pending ? "Loading subscription usage…" : "Click to view subscription usage"; } else if (result.status !== "ok") { body.textContent = result.message; } else { const heading = {provider ? providerNames[provider] : ""} subscription; const rows = result.windows.map((window) => { const reset = window.resetsAt ? new Date(window.resetsAt) : null; const remaining = reset ? Math.max(0, reset.getTime() - Date.now()) : null; const duration = remaining === null ? "Reset time unavailable" : remaining === 0 ? "Reset due; refresh pending" : remaining < 3600_000 ? `Resets in ${Math.ceil(remaining / 60_000)}m` : remaining < 86400_000 ? `Resets in ${Math.ceil(remaining / 3600_000)}h` : `Resets in ${Math.ceil(remaining / 86400_000)}d`; const exact = reset?.toLocaleString(undefined, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", }); const detail = {duration}{exact ? ` · ${exact}` : ""} as HTMLElement; if (reset) detail.title = reset.toLocaleString(); return (
{window.label} {window.usedPercent.toFixed(0)}% used
meter.value = Math.min(100, window.usedPercent)} aria-label={`${window.label} used`} /> {detail}
); }); const updated = new Date(result.fetchedAt); body.replaceChildren(heading, ...rows, Updated {updated.toLocaleTimeString()}); } } async function refresh() { if (disposed || !open || !provider || pending || document.hidden) return; if (result !== null && Date.now() - fetchedAt < 60_000) { draw(); return; } const current = ++revision; pending = true; draw(); try { const value = await context.request("usage", { provider }); if (disposed || current !== revision) return; result = usageOf(value).value ?? { status: "error", message: "Invalid subscription usage response." }; } catch { if (disposed || current !== revision) return; result = { status: "error", message: "Could not load subscription usage. Try again shortly.", }; } if (disposed || current !== revision) return; pending = false; fetchedAt = Date.now(); draw(); } function setOpen(value: boolean) { open = value && provider !== null && !disposed; panel.hidden = !open; button.setAttribute("aria-expanded", String(open)); clearTimeout(timer); if (open) { void refresh(); const tick = () => { void refresh(); timer = setTimeout(tick, 60_000); }; timer = setTimeout(tick, 60_000); } } element.addEventListener("focusout", (event) => { if (!element.contains((event as FocusEvent).relatedTarget as Node | null)) { setOpen(false); } }); button.addEventListener("click", () => { setOpen(!open); }); element.addEventListener("keydown", (event) => { if ((event as KeyboardEvent).key === "Escape") { setOpen(false); } }); return { element, update(next) { context = next; const chosen = next.model?.provider ?? ""; const nextProvider = isProvider(chosen) ? chosen : null; if (nextProvider === provider) return; provider = nextProvider; element.hidden = provider === null; button.setAttribute( "aria-label", `${provider ? providerNames[provider] : ""} subscription limits`, ); revision++; pending = false; result = null; fetchedAt = 0; setOpen(false); draw(); }, dispose() { disposed = true; revision++; clearTimeout(timer); }, }; } export default defineRenderers({ header: subscriptionHeader });