sign-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 528326727848
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 1da91c033c738ab74538d13d
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/ui.tsx
// Small shared pieces: dialog sheet, menu, toast, copy and share.
import { useEffect, useRef, useState, type ReactNode } from "react";

const FOCUSABLE = "a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex='-1'])";

/** Keeps rendering the last content while `open` turns false, so the dialog can animate out. */
function usePresence(open: boolean, content: ReactNode) {
  const last = useRef(content);
  if (open) last.current = content;
  const [mounted, setMounted] = useState(open);
  useEffect(() => { if (open) setMounted(true); }, [open]);
  return { mounted: open || mounted, leaving: !open && mounted, content: last.current, done: () => setMounted(false) };
}

/** A centered dialog on wide screens, a bottom sheet on phones. Escape, the backdrop and the close button call onClose. */
export function Modal({ open, title, onClose, children, footer }: { open: boolean; title: string; onClose: () => void; children: ReactNode; footer?: ReactNode }) {
  const p = usePresence(open, <><div className="dialog-body">{children}</div>{footer && <div className="dialog-foot">{footer}</div>}</>);
  const ref = useRef<HTMLDivElement>(null);
  const close = useRef(onClose);
  close.current = onClose;

  useEffect(() => {
    if (!open) return;
    const prev = document.activeElement as HTMLElement | null;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") { e.stopPropagation(); close.current(); }
      if (e.key === "Tab" && ref.current) {
        const items = [...ref.current.querySelectorAll<HTMLElement>(FOCUSABLE)].filter((el) => el.offsetParent !== null);
        if (!items.length) return;
        const first = items[0], last = items[items.length - 1];
        if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
        else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
      }
    };
    document.addEventListener("keydown", onKey, true);
    document.body.style.overflow = "hidden";
    // Focus the control marked data-autofocus, or the dialog itself (so phones do not open the keyboard uninvited).
    (ref.current?.querySelector<HTMLElement>("[data-autofocus]") ?? ref.current)?.focus({ preventScroll: true });
    return () => {
      document.removeEventListener("keydown", onKey, true);
      document.body.style.overflow = "";
      prev?.focus({ preventScroll: true });
    };
  }, [open]);

  if (!p.mounted) return null;
  return (
    <div className={`dialog-backdrop${p.leaving ? " leaving" : ""}`}
      onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}
      onAnimationEnd={(e) => { if (p.leaving && e.target === e.currentTarget) p.done(); }}>
      <div className="dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title" ref={ref} tabIndex={-1}>
        <div className="dialog-head">
          <h2 className="dialog-title" id="dialog-title">{title}</h2>
          <button className="btn btn-quiet btn-icon btn-sm" type="button" onClick={onClose} aria-label="Close">
            <svg width="18" height="18" viewBox="0 0 20 20" aria-hidden="true"><path d="M5 5l10 10M15 5 5 15" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" /></svg>
          </button>
        </div>
        {p.content}
      </div>
    </div>
  );
}

/** A button that shows a short list of actions (a disclosure, so Tab moves through the items). The list closes after one is chosen. */
export function Menu({ label, icon, children, end = true, className = "btn btn-line" }: { label: string; icon?: boolean; children: ReactNode; end?: boolean; className?: string }) {
  const [open, setOpen] = useState(false);
  const [leaving, setLeaving] = useState(false);
  const box = useRef<HTMLDivElement>(null);
  const shut = () => { if (open) setLeaving(true); };
  useEffect(() => {
    if (!open) return;
    const away = (e: Event) => { if (!box.current?.contains(e.target as Node)) setLeaving(true); };
    const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") { setLeaving(true); box.current?.querySelector<HTMLElement>("button")?.focus(); } };
    document.addEventListener("pointerdown", away);
    document.addEventListener("keydown", onKey);
    box.current?.querySelector<HTMLElement>(".dropdown-menu :is(a, button)")?.focus();
    return () => { document.removeEventListener("pointerdown", away); document.removeEventListener("keydown", onKey); };
  }, [open]);
  return (
    <div className="dropdown" ref={box}>
      <button type="button" className={`${className}${icon ? " btn-icon" : ""}`} aria-expanded={open} aria-label={icon ? label : undefined}
        onClick={() => (open ? shut() : setOpen(true))}>
        {icon ? <svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><circle cx="4.5" cy="10" r="1.6" fill="currentColor" /><circle cx="10" cy="10" r="1.6" fill="currentColor" /><circle cx="15.5" cy="10" r="1.6" fill="currentColor" /></svg> : label}
      </button>
      {open && (
        <div className={`dropdown-menu${end ? " dropdown-menu-end" : ""}${leaving ? " leaving" : ""}`}
          onClick={(e) => { if ((e.target as HTMLElement).closest("a, button")) shut(); }}
          onAnimationEnd={() => { if (leaving) { setOpen(false); setLeaving(false); } }}>
          {children}
        </div>
      )}
    </div>
  );
}

let pushToast: ((text: string) => void) | null = null;
export function toast(text: string) { pushToast?.(text); }

export function Toaster() {
  const [text, setText] = useState<string | null>(null);
  const [leaving, setLeaving] = useState(false);
  useEffect(() => {
    let timer: number | undefined;
    pushToast = (t) => {
      setText(t);
      setLeaving(false);
      window.clearTimeout(timer);
      timer = window.setTimeout(() => setLeaving(true), 2600);
    };
    return () => { pushToast = null; window.clearTimeout(timer); };
  }, []);
  return text ? <div className={`toast${leaving ? " leaving" : ""}`} role="status" onAnimationEnd={() => { if (leaving) { setText(null); setLeaving(false); } }}>{text}</div> : null;
}

/** Shows a notice while the browser is offline, since nothing saves until it reconnects. */
export function OfflineNotice() {
  const [offline, setOffline] = useState(typeof navigator !== "undefined" && navigator.onLine === false);
  useEffect(() => {
    const on = () => setOffline(false);
    const off = () => setOffline(true);
    window.addEventListener("online", on);
    window.addEventListener("offline", off);
    return () => { window.removeEventListener("online", on); window.removeEventListener("offline", off); };
  }, []);
  return offline ? <p className="offline enter" role="status">You are offline. Nothing saves until you reconnect.</p> : null;
}

export async function copy(text: string, done = "Copied") {
  try {
    await navigator.clipboard.writeText(text);
    toast(done);
  } catch {
    window.prompt("Copy this:", text);
  }
}

/** The phone's share sheet where there is one (pick Messages, WhatsApp, Mail...). */
export const canShare = () => typeof navigator !== "undefined" && typeof navigator.share === "function" && matchMedia("(pointer: coarse)").matches;

export async function share(url: string, title: string, text?: string) {
  if (canShare()) {
    try { await navigator.share({ title, text, url }); return; } catch { return; /* cancelled */ }
  }
  await copy(url, "Link copied");
}

Change history

Quality pass: phone signing, edit signers, copy to send again
5283267278 · 5 hours ago
Bounded source sync
15f0fa2e27 · 1 day ago
Bounded source sync
d3aa1ca65b · 1 day ago
Bounded source sync
8e2ca0b773 · 1 day ago
Bounded source sync
5cfa84715c · 1 day ago
Bounded source sync
c849e7b286 · 1 day ago
Bounded source sync
68c1cabe03 · 1 day ago
Bounded source sync
62ae8944f0 · 1 day ago
Bounded source sync
7ed54e7381 · 1 day ago