drop-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 b2e053fd98cb
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 80ddb8c5df822e290afdd3c7
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/Send.tsx
// The sending screen: the link, QR code and 6-digit code for the other device, the files on offer,
// and who is receiving them, with progress. The files stay on this device and go out from here.
import { useRef, useSyncExternalStore } from "react";
import { navigate } from "./lib/router";
import { bytes, files as nFiles, speed, timeLeft } from "./lib/format";
import { picksFromInput } from "./lib/items";
import { isIOS } from "./lib/device";
import type { ReceiverView, Sender, SenderSnapshot } from "./sender";
import { Start, retrySending, send, stopSending, useLastSender } from "./Start";
import { QrCode } from "./QrCode";
import { AppBar, Icon, ProgressBar, copy, useLeaveGuard, useWakeLock } from "./ui";

export function SendPage() {
  const sender = useLastSender();
  if (!sender) {
    return (
      <div className="app-page">
        <AppBar />
        <h1 className="app-h1">Send files</h1>
        <p className="app-lede">Pick the files to send. They stay on this device until the other device has them.</p>
        <div className="card start-card"><Start /></div>
      </div>
    );
  }
  return <Sharing sender={sender} />;
}

function receiverLine(r: ReceiverView): string {
  if (!r.online) return r.count && r.done === r.count ? `Received ${nFiles(r.count)}` : "Not connected";
  if (!r.count) return "Connected, choosing files";
  if (r.done === r.count) return `Received ${nFiles(r.count)}`;
  const left = r.rate > 0 ? timeLeft((r.total - r.got) / r.rate) : "";
  return [`${bytes(r.got)} of ${bytes(r.total)}`, r.rate > 0 ? speed(r.rate) : "", left].filter(Boolean).join(" · ");
}

function headline(s: SenderSnapshot): { title: string; lede: string } {
  const online = s.receivers.filter((r) => r.online);
  const busy = online.some((r) => r.count > 0 && r.done < r.count);
  const finished = s.receivers.some((r) => r.count > 0 && r.done === r.count);
  if (s.state === "starting") return { title: "Getting a link", lede: "One moment." };
  if (s.state === "failed") return { title: "Could not start sharing", lede: s.error || "Try again." };
  if (s.state === "stopped") return { title: "Sharing stopped", lede: "The link and code no longer work. The files never left this device except to the devices that received them." };
  if (busy) return { title: "Sending", lede: "Keep this page open until the other device has everything." };
  if (finished && !online.some((r) => r.done < r.count || !r.count)) return { title: "Sent", lede: "The other device has the files. You can stop sharing, or keep the link open for another device." };
  if (online.length) return { title: "Connected", lede: "The other device is choosing which files to receive." };
  return { title: "Ready to send", lede: "Open the link on the other device, scan the QR code, or type the code there. Keep this page open." };
}

function Sharing({ sender }: { sender: Sender }) {
  const s = useSyncExternalStore(sender.subscribe, sender.snapshot);
  const more = useRef<HTMLInputElement>(null);
  const sharing = s.state === "sharing";
  useWakeLock(sharing);
  useLeaveGuard(sharing);
  const total = s.files.reduce((n, f) => n + f.size, 0);
  const { title, lede } = headline(s);
  const host = location.host;
  const canShareLink = typeof navigator.share === "function";

  return (
    <div className="app-page">
      <AppBar>
        {sharing && <button className="btn btn-line btn-sm" type="button" onClick={() => void stopSending()}>Stop sharing</button>}
      </AppBar>
      <h1 className="app-h1">{title}</h1>
      <p className="app-lede">{lede}</p>

      {s.state === "failed" && (
        <div className="row"><button className="btn" type="button" onClick={retrySending}>Try again</button></div>
      )}
      {s.state === "stopped" && (
        <div className="row"><button className="btn" type="button" onClick={() => navigate("/app")}>Send more files</button></div>
      )}

      <div className="send-grid">
        {sharing && (
          <section className="card share-card" aria-label="Open on the other device">
            <p className="label">On the other device</p>
            {s.link ? <QrCode text={s.link} size={188} label="QR code with the link to receive the files" /> : <div className="qr-wait"><span className="spinner" /></div>}
            <p className="share-hint">Scan with its camera, or go to <strong>{host}</strong> and type</p>
            <p className="pair-code" aria-label={s.code ? `Code ${s.code.split("").join(" ")}` : "Making a code"}>
              {s.code ? <>{s.code.slice(0, 3)}<span aria-hidden="true" />{s.code.slice(3)}</> : <span className="spinner" />}
            </p>
            <p className="share-small">The code changes every 10 minutes. The link works until you stop sharing.</p>
            {s.link && (
              <div className="link-row">
                <input className="input link-input" readOnly value={s.link} aria-label="Link to receive the files" onFocus={(e) => e.currentTarget.select()} />
                <button className="btn btn-sm" type="button" onClick={() => void copy(s.link!, "Link copied")}><Icon name="link" size={16} /> Copy</button>
              </div>
            )}
            {s.link && canShareLink && (
              <button className="btn btn-line btn-block" type="button" onClick={() => void navigator.share({ title: "Files for you", text: `Open this to get ${nFiles(s.files.length)} from my device:`, url: s.link! }).catch(() => null)}>
                <Icon name="share" size={18} /> Send the link
              </button>
            )}
          </section>
        )}

        <div className="send-side">
          {s.receivers.length > 0 && (
            <section className="card list-card" aria-label="Receiving devices">
              <p className="label">Receiving</p>
              <ul className="peers">
                {s.receivers.map((r) => (
                  <li key={r.id}>
                    <span className={`dot${r.online ? " on" : ""}`} aria-hidden="true" />
                    <div className="grow">
                      <span className="peer-name">{r.name}</span>
                      <span className="peer-state">{receiverLine(r)}{r.online && r.route === "local" ? " · same network" : ""}</span>
                      {r.online && r.count > 0 && r.done < r.count && <ProgressBar value={r.total ? r.got / r.total : 0} label={`${r.name} progress`} />}
                    </div>
                  </li>
                ))}
              </ul>
            </section>
          )}

          <section className="card list-card" aria-label="Files">
            <div className="spread">
              <p className="label">{nFiles(s.files.length)}, {bytes(total)}</p>
              {sharing && (
                <>
                  <button className="btn btn-quiet btn-sm" type="button" onClick={() => more.current?.click()}><Icon name="plus" size={16} /> Add files</button>
                  <input ref={more} type="file" multiple hidden onChange={(e) => { const input = e.currentTarget; const picks = input.files ? picksFromInput(input.files) : []; input.value = ""; send(picks); }} />
                </>
              )}
            </div>
            <ul className="files">
              {s.files.map((f) => (
                <li key={f.i}>
                  <span className="file-icon"><Icon name={f.zip ? "zip" : "file"} size={18} /></span>
                  <span className="file-name" title={f.name}>{f.name}</span>
                  <span className="file-size">{bytes(f.size)}</span>
                </li>
              ))}
            </ul>
            {s.files.some((f) => f.zip) && <p className="hint">Folders are zipped on this device while they are sent.</p>}
          </section>

          {sharing && (
            <p className="notice">
              Nothing is uploaded: the files go from this {isIOS ? "device" : "page"} straight to the other device, encrypted. Keep this page open{isIOS ? " and the screen on" : ""} until it finishes.
            </p>
          )}
        </div>
      </div>
    </div>
  );
}

Change history

Point OpenApps links to openapps.free
b2e053fd98 · 17 hours ago
Bind agent address in policy
ca424dd814 · 19 hours ago
Retry release capture
ae07f1a3fd · 19 hours ago
Use OpenApps service URLs and add contribution strip
0452ce136f · 19 hours ago
Bounded source sync
ca1bc00aa4 · 1 day ago
Bounded source sync
d4982911a0 · 1 day ago
Bounded source sync
18c97fe4ca · 1 day ago