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.
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.
src/ui.tsx
// Small shared pieces: the app bar, toast, copy and share, keeping the screen awake, and icons.
import { useEffect, useState, type ReactNode } from "react";
import { navigate } from "./lib/router";
let pushToast: ((text: string) => void) | null = null;
export function toast(text: string) { pushToast?.(text); }
export function Toaster() {
const [text, setText] = useState<string | null>(null);
useEffect(() => {
let timer: number | undefined;
pushToast = (t) => {
setText(t);
window.clearTimeout(timer);
timer = window.setTimeout(() => setText(null), 3600);
};
return () => { pushToast = null; window.clearTimeout(timer); };
}, []);
return text ? <div className="toast" role="status">{text}</div> : null;
}
export async function copy(text: string, done = "Copied") {
try {
await navigator.clipboard.writeText(text);
toast(done);
} catch {
toast("Could not copy. Select the text and copy it.");
}
}
/** The app screens' top bar: Drop's name (home) and the screen's own action. */
export function AppBar({ children, leave }: { children?: ReactNode; leave?: string | null }) {
return (
<header className="app-bar">
<a
className="app-brand" href="/"
onClick={(e) => {
e.preventDefault();
if (leave && !confirm(leave)) return;
navigate("/");
}}
>
<img src="/logo.svg" alt="" width="24" height="24" />
<span>Drop</span>
</a>
<div className="app-bar-right">{children}</div>
</header>
);
}
/** Asks before closing or reloading the tab while `active` (a transfer would stop). */
export function useLeaveGuard(active: boolean) {
useEffect(() => {
if (!active) return;
const onLeave = (e: BeforeUnloadEvent) => { e.preventDefault(); e.returnValue = ""; };
window.addEventListener("beforeunload", onLeave);
return () => window.removeEventListener("beforeunload", onLeave);
}, [active]);
}
/** Keeps the screen on while active (where the browser allows), so a phone does not sleep mid-transfer. */
export function useWakeLock(active: boolean) {
useEffect(() => {
if (!active || !("wakeLock" in navigator)) return;
let lock: WakeLockSentinel | null = null;
let cancelled = false;
const take = async () => {
if (document.visibilityState !== "visible" || cancelled) return;
try { lock = await navigator.wakeLock.request("screen"); } catch { /* not allowed here */ }
};
const onVisible = () => { if (!lock || lock.released) void take(); };
void take();
document.addEventListener("visibilitychange", onVisible);
return () => {
cancelled = true;
document.removeEventListener("visibilitychange", onVisible);
void lock?.release().catch(() => null);
};
}, [active]);
}
export function ProgressBar({ value, label }: { value: number; label: string }) {
const pct = Math.max(0, Math.min(100, value * 100));
return (
<div className="progress" role="progressbar" aria-label={label} aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(pct)}>
<span style={{ width: `${pct}%` }} />
</div>
);
}
const PATHS: Record<string, ReactNode> = {
send: <path d="M17.5 2.5 2.5 9l6 2.5m9-9L11 17.5 8.5 11.5m9-9-9 9" />,
folder: <path d="M2.5 5.5a1.5 1.5 0 0 1 1.5-1.5h3.6l1.8 2H16a1.5 1.5 0 0 1 1.5 1.5v7.5A1.5 1.5 0 0 1 16 16.5H4A1.5 1.5 0 0 1 2.5 15z" />,
file: <><path d="M5 2.5h6.5L15.5 6.5v11H5z" /><path d="M11.5 2.5v4h4" /></>,
zip: <><path d="M5 2.5h6.5L15.5 6.5v11H5z" /><path d="M9 4.5h1.5M8 6.5h1.5M9 8.5h1.5M8 10.5h1.5M8.2 12.5h2.6v2.5H8.2z" /></>,
link: <><path d="M8.5 11.5a3.5 3.5 0 0 0 5 0l2.5-2.5a3.5 3.5 0 0 0-5-5l-1 1" /><path d="M11.5 8.5a3.5 3.5 0 0 0-5 0L4 11a3.5 3.5 0 0 0 5 5l1-1" /></>,
share: <><path d="M10 12.5V2.5M6.5 6 10 2.5 13.5 6" /><path d="M6 9H4.5v8.5h11V9H14" /></>,
download: <><path d="M10 2.5v10M6.5 9 10 12.5 13.5 9" /><path d="M3.5 13.5v3h13v-3" /></>,
check: <path d="m4.5 10.5 3.5 3.5 7.5-8" />,
plus: <path d="M10 4v12M4 10h12" />,
close: <path d="M5 5l10 10M15 5 5 15" />,
wifi: <><path d="M2.5 7.5a11 11 0 0 1 15 0M5 10.5a7.5 7.5 0 0 1 10 0M7.5 13.3a3.8 3.8 0 0 1 5 0" /><circle cx="10" cy="16" r=".9" /></>,
lock: <><rect x="4.5" y="9" width="11" height="8" rx="1.5" /><path d="M7 9V6.5a3 3 0 0 1 6 0V9" /></>,
};
export function Icon({ name, size = 20 }: { name: keyof typeof PATHS | string; size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
{PATHS[name]}
</svg>
);
}
Change history
Point OpenApps links to openapps.free
Bind agent address in policy
Retry release capture
Use OpenApps service URLs and add contribution strip
Bounded source sync
Bounded source sync
Bounded source sync