@putkoff/abstract-securefiles 0.0.52 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ import type { SecureFilesApi } from "./api";
2
+ import type { SecureFile } from "./types";
3
+ export declare function People({ api, files }: {
4
+ api: SecureFilesApi;
5
+ files: SecureFile[];
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ export declare function SharedWithMe({ api }: {
8
+ api: SecureFilesApi;
9
+ }): import("react/jsx-runtime").JSX.Element | null;
package/dist/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BrowseResult, Device, LinkRoot, RootChallenge, SecureFile, ShareUpdate } from "./types";
1
+ import type { BrowseResult, Device, LinkRoot, Person, RootChallenge, SecureFile, SharedOwner, ShareUpdate } from "./types";
2
2
  export declare const DEFAULT_API_BASE = "https://api.abstractendeavors.com";
3
3
  export declare class ApiError extends Error {
4
4
  status: number;
@@ -21,6 +21,31 @@ export declare function createApi(apiBase?: string): {
21
21
  removeDevice: (id: number) => Promise<{
22
22
  devices: Device[];
23
23
  }>;
24
+ people: () => Promise<{
25
+ people: Person[];
26
+ }>;
27
+ invitePerson: () => Promise<{
28
+ code: string;
29
+ expires_in: number;
30
+ }>;
31
+ acceptPerson: (code: string) => Promise<{
32
+ connected_with: string;
33
+ people: Person[];
34
+ }>;
35
+ setGrants: (username: string, grant: {
36
+ all: boolean;
37
+ ids: number[];
38
+ }) => Promise<{
39
+ people: Person[];
40
+ }>;
41
+ removePerson: (username: string) => Promise<{
42
+ people: Person[];
43
+ }>;
44
+ sharedWithMe: () => Promise<{
45
+ owners: SharedOwner[];
46
+ }>;
47
+ /** Browser-native download of an item someone shared with me (streams to disk, resumable). */
48
+ downloadShared(id: number): void;
24
49
  roots: () => Promise<{
25
50
  roots: LinkRoot[];
26
51
  }>;
package/dist/index.cjs CHANGED
@@ -40,6 +40,27 @@ function createApi(apiBase = DEFAULT_API_BASE) {
40
40
  devices: () => json("/devices"),
41
41
  pairDevice: () => json("/devices/pair", { method: "POST", body: "{}" }),
42
42
  removeDevice: (id) => json(`/devices/${id}`, { method: "DELETE" }),
43
+ people: () => json("/people"),
44
+ invitePerson: () => json("/people/invite", { method: "POST", body: "{}" }),
45
+ acceptPerson: (code) => json("/people/accept", {
46
+ method: "POST",
47
+ body: JSON.stringify({ code }),
48
+ }),
49
+ setGrants: (username, grant) => json(`/people/${encodeURIComponent(username)}/grants`, {
50
+ method: "PUT",
51
+ body: JSON.stringify(grant),
52
+ }),
53
+ removePerson: (username) => json(`/people/${encodeURIComponent(username)}`, { method: "DELETE" }),
54
+ sharedWithMe: () => json("/shared-with-me"),
55
+ /** Browser-native download of an item someone shared with me (streams to disk, resumable). */
56
+ downloadShared(id) {
57
+ const a = document.createElement("a");
58
+ a.href = `${root}/shared-with-me/${id}`;
59
+ a.rel = "noopener";
60
+ document.body.appendChild(a);
61
+ a.click();
62
+ a.remove();
63
+ },
43
64
  roots: () => json("/admin/roots"),
44
65
  requestRoot: (path) => json("/admin/roots", { method: "POST", body: JSON.stringify({ path }) }),
45
66
  confirmRoot: (nonce, signature, label) => json("/admin/roots/confirm", {
@@ -322,7 +343,7 @@ function ShareDialog({ file, onClose, onSave, }) {
322
343
  return (jsxRuntime.jsx("div", { className: "sf-overlay", onMouseDown: (e) => e.target === e.currentTarget && onClose(), children: jsxRuntime.jsxs("div", { className: "sf-dialog", role: "dialog", "aria-modal": "true", "aria-label": `Share settings for ${file.filename}`, children: [jsxRuntime.jsxs("header", { className: "sf-dialog__head", children: [jsxRuntime.jsx("h3", { children: "Share settings" }), jsxRuntime.jsx("button", { type: "button", className: "sf-icon-btn", "aria-label": "Close", onClick: onClose, children: "\u2715" })] }), jsxRuntime.jsx("p", { className: "sf-muted", children: file.filepath }), jsxRuntime.jsxs("label", { className: "sf-check", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: shareable, onChange: (e) => setShareable(e.target.checked) }), "Anyone with the link can download"] }), jsxRuntime.jsxs("fieldset", { disabled: !shareable, className: "sf-fieldset", children: [jsxRuntime.jsxs("label", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Download limit (blank = unlimited)" }), jsxRuntime.jsx("input", { className: "sf-input", inputMode: "numeric", value: limit, onChange: (e) => setLimit(e.target.value.trim()), placeholder: "unlimited" }), !limitValid && jsxRuntime.jsx("span", { className: "sf-error", children: "Enter a whole number" })] }), jsxRuntime.jsxs("p", { className: "sf-muted", children: ["Downloaded ", file.download_count, " time", file.download_count === 1 ? "" : "s", file.download_limit ? ` of ${file.download_limit}` : "", ".", " ", jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: resetCount, onChange: (e) => setResetCount(e.target.checked) }), "reset count"] })] }), jsxRuntime.jsxs("div", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Password" }), jsxRuntime.jsxs("div", { className: "sf-row", children: [file.has_password && (jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "radio", checked: pwMode === "keep", onChange: () => setPwMode("keep") }), "keep current"] })), jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "radio", checked: pwMode === "set", onChange: () => setPwMode("set") }), file.has_password ? "change" : "require password"] }), jsxRuntime.jsxs("label", { className: "sf-check sf-check--inline", children: [jsxRuntime.jsx("input", { type: "radio", checked: pwMode === "remove", onChange: () => setPwMode("remove") }), file.has_password ? "remove" : "none"] })] }), pwMode === "set" && (jsxRuntime.jsx("input", { className: "sf-input", type: "password", autoComplete: "new-password", value: password, onChange: (e) => setPassword(e.target.value), placeholder: "New password" }))] }), jsxRuntime.jsxs("div", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Share link" }), file.share_url ? (jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("input", { className: "sf-input", readOnly: true, value: file.share_url, onFocus: (e) => e.target.select() }), jsxRuntime.jsx(CopyButton, { text: file.share_url }), jsxRuntime.jsx(ConfirmButton, { label: "New link", disabled: saving, onConfirm: newLink })] })) : (jsxRuntime.jsx("span", { className: "sf-muted", children: "A private link is created when you save with sharing on." })), file.share_url && !file.shareable && (jsxRuntime.jsx("span", { className: "sf-muted", children: "The link works once you save with sharing on." })), jsxRuntime.jsx("span", { className: "sf-muted", children: "\"New link\" invalidates the current one immediately." })] })] }), error && jsxRuntime.jsx("p", { className: "sf-error", children: error }), jsxRuntime.jsxs("footer", { className: "sf-row sf-dialog__foot", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: onClose, children: "Cancel" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn sf-btn--primary", disabled: saving || !limitValid || !passwordValid, onClick: save, children: saving ? "Saving…" : "Save" })] })] }) }));
323
344
  }
324
345
 
325
- function formatSize(bytes) {
346
+ function formatSize$1(bytes) {
326
347
  const units = ["B", "KB", "MB", "GB", "TB"];
327
348
  let n = bytes;
328
349
  let i = 0;
@@ -332,7 +353,7 @@ function formatSize(bytes) {
332
353
  }
333
354
  return `${n.toFixed(i ? 1 : 0)} ${units[i]}`;
334
355
  }
335
- const errText$1 = (err) => (err instanceof Error ? err.message : String(err));
356
+ const errText$2 = (err) => (err instanceof Error ? err.message : String(err));
336
357
  function AddRoot({ api, onAdded }) {
337
358
  const [path, setPath] = react.useState("");
338
359
  const [label, setLabel] = react.useState("");
@@ -348,7 +369,7 @@ function AddRoot({ api, onAdded }) {
348
369
  setSignature("");
349
370
  }
350
371
  catch (err) {
351
- setError(errText$1(err));
372
+ setError(errText$2(err));
352
373
  }
353
374
  finally {
354
375
  setBusy(false);
@@ -367,7 +388,7 @@ function AddRoot({ api, onAdded }) {
367
388
  setSignature("");
368
389
  }
369
390
  catch (err) {
370
- setError(errText$1(err));
391
+ setError(errText$2(err));
371
392
  }
372
393
  finally {
373
394
  setBusy(false);
@@ -390,7 +411,7 @@ function Browser({ api, roots, folders, onLinked, }) {
390
411
  setPicked(new Set());
391
412
  }
392
413
  catch (err) {
393
- setError(errText$1(err));
414
+ setError(errText$2(err));
394
415
  }
395
416
  }, [api]);
396
417
  react.useEffect(() => {
@@ -418,7 +439,7 @@ function Browser({ api, roots, folders, onLinked, }) {
418
439
  await open(listing.path);
419
440
  }
420
441
  catch (err) {
421
- setResult(errText$1(err));
442
+ setResult(errText$2(err));
422
443
  }
423
444
  finally {
424
445
  setBusy(false);
@@ -431,7 +452,7 @@ function Browser({ api, roots, folders, onLinked, }) {
431
452
  ...(listing?.files.filter((f) => !f.linked_id) ?? []),
432
453
  ];
433
454
  const relative = listing ? listing.path.slice(listing.root.length) || "/" : "";
434
- return (jsxRuntime.jsxs("div", { className: "sf-card", children: [jsxRuntime.jsx("h4", { children: "Link files or folders" }), jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("select", { className: "sf-input sf-root-select", value: listing?.root ?? "", onChange: (e) => open(e.target.value), children: roots.map((r) => (jsxRuntime.jsx("option", { value: r.path, children: r.label ? `${r.label} (${r.path})` : r.path }, r.id))) }), listing && jsxRuntime.jsx("code", { className: "sf-muted", children: relative })] }), error && jsxRuntime.jsx("p", { className: "sf-error", children: error }), listing && (jsxRuntime.jsxs("div", { className: "sf-browser", children: [listing.parent && (jsxRuntime.jsx("button", { type: "button", className: "sf-browser__dir", onClick: () => open(listing.parent), children: "\u21A9 .." })), listing.dirs.map((d) => (jsxRuntime.jsxs("div", { className: `sf-browser__row${d.linked_id ? " sf-muted" : ""}`, children: [jsxRuntime.jsx("input", { type: "checkbox", "aria-label": `Select folder ${d.name}`, disabled: !!d.linked_id, checked: picked.has(d.path), onChange: (e) => togglePick(d.path, e.target.checked) }), jsxRuntime.jsxs("button", { type: "button", className: "sf-browser__dir", onClick: () => open(d.path), title: "Open folder", children: ["\uD83D\uDCC1 ", d.name] }), jsxRuntime.jsx("span", { className: "sf-muted", children: d.linked_id ? "already linked" : "folder → .tar" })] }, d.path))), linkable.length > 0 && (jsxRuntime.jsxs("label", { className: "sf-check sf-browser__all", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: linkable.length > 0 && linkable.every((f) => picked.has(f.path)), disabled: !linkable.length, onChange: (e) => setPicked(e.target.checked ? new Set(linkable.map((f) => f.path)) : new Set()) }), "Select everything here"] })), listing.files.map((f) => (jsxRuntime.jsxs("label", { className: `sf-check sf-browser__file${f.linked_id ? " sf-muted" : ""}`, children: [jsxRuntime.jsx("input", { type: "checkbox", disabled: !!f.linked_id, checked: picked.has(f.path), onChange: (e) => togglePick(f.path, e.target.checked) }), jsxRuntime.jsx("span", { className: "sf-browser__name", children: f.name }), jsxRuntime.jsx("span", { className: "sf-muted", children: f.linked_id ? "already linked" : formatSize(f.size) })] }, f.path))), !listing.dirs.length && !listing.files.length && jsxRuntime.jsx("p", { className: "sf-muted", children: "Empty directory" }), listing.truncated && jsxRuntime.jsx("p", { className: "sf-muted", children: "Showing the first 5000 entries." })] })), jsxRuntime.jsxs("div", { className: "sf-row sf-row--bottom", children: [jsxRuntime.jsx(FolderSelect, { label: "Show in Secure Files under", folders: folders, value: target, onChange: setTarget }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn sf-btn--primary", disabled: busy || !picked.size, onClick: link, children: busy ? "Linking…" : `Link ${picked.size || ""}`.trim() })] }), result && jsxRuntime.jsx("p", { className: result.includes("skipped") ? "sf-error" : "sf-muted", children: result })] }));
455
+ return (jsxRuntime.jsxs("div", { className: "sf-card", children: [jsxRuntime.jsx("h4", { children: "Link files or folders" }), jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("select", { className: "sf-input sf-root-select", value: listing?.root ?? "", onChange: (e) => open(e.target.value), children: roots.map((r) => (jsxRuntime.jsx("option", { value: r.path, children: r.label ? `${r.label} (${r.path})` : r.path }, r.id))) }), listing && jsxRuntime.jsx("code", { className: "sf-muted", children: relative })] }), error && jsxRuntime.jsx("p", { className: "sf-error", children: error }), listing && (jsxRuntime.jsxs("div", { className: "sf-browser", children: [listing.parent && (jsxRuntime.jsx("button", { type: "button", className: "sf-browser__dir", onClick: () => open(listing.parent), children: "\u21A9 .." })), listing.dirs.map((d) => (jsxRuntime.jsxs("div", { className: `sf-browser__row${d.linked_id ? " sf-muted" : ""}`, children: [jsxRuntime.jsx("input", { type: "checkbox", "aria-label": `Select folder ${d.name}`, disabled: !!d.linked_id, checked: picked.has(d.path), onChange: (e) => togglePick(d.path, e.target.checked) }), jsxRuntime.jsxs("button", { type: "button", className: "sf-browser__dir", onClick: () => open(d.path), title: "Open folder", children: ["\uD83D\uDCC1 ", d.name] }), jsxRuntime.jsx("span", { className: "sf-muted", children: d.linked_id ? "already linked" : "folder → .tar" })] }, d.path))), linkable.length > 0 && (jsxRuntime.jsxs("label", { className: "sf-check sf-browser__all", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: linkable.length > 0 && linkable.every((f) => picked.has(f.path)), disabled: !linkable.length, onChange: (e) => setPicked(e.target.checked ? new Set(linkable.map((f) => f.path)) : new Set()) }), "Select everything here"] })), listing.files.map((f) => (jsxRuntime.jsxs("label", { className: `sf-check sf-browser__file${f.linked_id ? " sf-muted" : ""}`, children: [jsxRuntime.jsx("input", { type: "checkbox", disabled: !!f.linked_id, checked: picked.has(f.path), onChange: (e) => togglePick(f.path, e.target.checked) }), jsxRuntime.jsx("span", { className: "sf-browser__name", children: f.name }), jsxRuntime.jsx("span", { className: "sf-muted", children: f.linked_id ? "already linked" : formatSize$1(f.size) })] }, f.path))), !listing.dirs.length && !listing.files.length && jsxRuntime.jsx("p", { className: "sf-muted", children: "Empty directory" }), listing.truncated && jsxRuntime.jsx("p", { className: "sf-muted", children: "Showing the first 5000 entries." })] })), jsxRuntime.jsxs("div", { className: "sf-row sf-row--bottom", children: [jsxRuntime.jsx(FolderSelect, { label: "Show in Secure Files under", folders: folders, value: target, onChange: setTarget }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn sf-btn--primary", disabled: busy || !picked.size, onClick: link, children: busy ? "Linking…" : `Link ${picked.size || ""}`.trim() })] }), result && jsxRuntime.jsx("p", { className: result.includes("skipped") ? "sf-error" : "sf-muted", children: result })] }));
435
456
  }
436
457
  function ServerFiles({ api, folders, onLinked, }) {
437
458
  const [roots, setRoots] = react.useState(null);
@@ -442,7 +463,7 @@ function ServerFiles({ api, folders, onLinked, }) {
442
463
  setError(null);
443
464
  }
444
465
  catch (err) {
445
- setError(errText$1(err));
466
+ setError(errText$2(err));
446
467
  }
447
468
  }, [api]);
448
469
  return (jsxRuntime.jsxs("details", { className: "sf-admin", onToggle: (e) => e.currentTarget.open && !roots && load(), children: [jsxRuntime.jsx("summary", { children: "Server files (admin)" }), jsxRuntime.jsxs("div", { className: "sf-stack", children: [jsxRuntime.jsx("p", { className: "sf-muted", children: "Link files or whole folders that already live on the server instead of uploading copies. A linked folder downloads as one uncompressed .tar with an exact size, and interrupted downloads can be resumed. Only items inside allowed directories can be linked; removing a directory immediately disables its links. Deleting a linked entry never touches the original. Available from the local network only." }), error && jsxRuntime.jsx("p", { className: "sf-error", children: error }), roots && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "sf-card", children: [jsxRuntime.jsx("h4", { children: "Allowed directories" }), roots.length === 0 ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "None yet." })) : (jsxRuntime.jsx("ul", { className: "sf-roots", children: roots.map((r) => (jsxRuntime.jsxs("li", { children: [jsxRuntime.jsxs("span", { children: [jsxRuntime.jsx("code", { children: r.path }), " ", r.label && jsxRuntime.jsxs("span", { className: "sf-muted", children: ["\u2014 ", r.label] }), !r.exists && jsxRuntime.jsx("span", { className: "sf-error", children: " (missing)" })] }), jsxRuntime.jsx(ConfirmButton, { label: "Remove", onConfirm: async () => {
@@ -451,12 +472,14 @@ function ServerFiles({ api, folders, onLinked, }) {
451
472
  onLinked();
452
473
  }
453
474
  catch (err) {
454
- setError(errText$1(err));
475
+ setError(errText$2(err));
455
476
  }
456
477
  } })] }, r.id))) }))] }), jsxRuntime.jsx(AddRoot, { api: api, onAdded: setRoots }), jsxRuntime.jsx(Browser, { api: api, roots: roots, folders: folders, onLinked: onLinked })] }))] })] }));
457
478
  }
458
479
 
459
- const errText = (err) => (err instanceof Error ? err.message : String(err));
480
+ const errText$1 = (err) => (err instanceof Error ? err.message : String(err));
481
+ const UNIX_CMD = "pipx install abstract-securefiles && pipx ensurepath && ~/.local/bin/securefiles setup";
482
+ const WIN_CMD = "py -m pip install --user pipx; py -m pipx install abstract-securefiles; py -m pipx ensurepath";
460
483
  function ago(iso) {
461
484
  if (!iso)
462
485
  return "never";
@@ -493,14 +516,14 @@ function AddComputer({ api, onPaired }) {
493
516
  setExpiresAt(Date.now() + res.expires_in * 1000);
494
517
  }
495
518
  catch (err) {
496
- setError(errText(err));
519
+ setError(errText$1(err));
497
520
  }
498
521
  };
499
522
  const minutesLeft = Math.max(1, Math.round((expiresAt - Date.now()) / 60000));
500
523
  if (!code) {
501
524
  return (jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn sf-btn--primary", onClick: create, children: "Add a computer" }), error && jsxRuntime.jsx("span", { className: "sf-error", children: error })] }));
502
525
  }
503
- return (jsxRuntime.jsxs("div", { className: "sf-card", children: [jsxRuntime.jsx("h4", { children: "Connect a computer" }), jsxRuntime.jsxs("ol", { className: "sf-steps", children: [jsxRuntime.jsxs("li", { children: ["On that computer, install the agent (Linux, macOS or Windows with Python 3.9+):", jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("code", { className: "sf-codeline", children: "pipx install abstract-securefiles && securefiles setup" }), jsxRuntime.jsx(CopyButton, { text: "pipx install abstract-securefiles && securefiles setup", label: "Copy" })] }), jsxRuntime.jsxs("span", { className: "sf-muted", children: ["No pipx? Use ", jsxRuntime.jsx("code", { children: "python3 -m pip install --user abstract-securefiles" }), " then ", jsxRuntime.jsx("code", { children: "securefiles setup" }), "."] })] }), jsxRuntime.jsxs("li", { children: ["In the panel that opens (", jsxRuntime.jsx("code", { children: "http://127.0.0.1:47821" }), "), enter this code:", jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("span", { className: "sf-paircode", children: code }), jsxRuntime.jsx(CopyButton, { text: code, label: "Copy" })] }), jsxRuntime.jsxs("span", { className: "sf-muted", children: ["Valid for about ", minutesLeft, " more minutes, once."] })] }), jsxRuntime.jsxs("li", { children: ["Pick the files and folders to share there. They appear here under ", jsxRuntime.jsx("code", { children: "devices/<computer>/" }), "."] })] }), jsxRuntime.jsx("div", { className: "sf-row", children: jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCode(null), children: "Done" }) })] }));
526
+ return (jsxRuntime.jsxs("div", { className: "sf-card", children: [jsxRuntime.jsx("h4", { children: "Connect a computer" }), jsxRuntime.jsxs("ol", { className: "sf-steps", children: [jsxRuntime.jsxs("li", { children: ["On that computer, install the agent (needs Python 3.9+):", jsxRuntime.jsxs("div", { className: "sf-os", children: [jsxRuntime.jsx("b", { children: "Linux / macOS" }), " (Terminal)", jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("code", { className: "sf-codeline", children: UNIX_CMD }), jsxRuntime.jsx(CopyButton, { text: UNIX_CMD, label: "Copy" })] }), jsxRuntime.jsxs("span", { className: "sf-muted", children: ["No pipx? ", jsxRuntime.jsx("code", { children: "python3 -m pip install --user pipx" }), " then use ", jsxRuntime.jsx("code", { children: "python3 -m pipx" }), ". On a Mac without Python, run ", jsxRuntime.jsx("code", { children: "python3" }), " once and accept Apple's developer-tools install."] })] }), jsxRuntime.jsxs("div", { className: "sf-os", children: [jsxRuntime.jsx("b", { children: "Windows" }), " (PowerShell; install Python from python.org first)", jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("code", { className: "sf-codeline", children: WIN_CMD }), jsxRuntime.jsx(CopyButton, { text: WIN_CMD, label: "Copy" })] }), jsxRuntime.jsxs("span", { className: "sf-muted", children: ["Then open a new PowerShell window and run ", jsxRuntime.jsx("code", { children: "securefiles setup" }), "."] })] })] }), jsxRuntime.jsxs("li", { children: ["In the panel that opens (", jsxRuntime.jsx("code", { children: "http://127.0.0.1:47821" }), "), enter this code, or run", " ", jsxRuntime.jsxs("code", { children: ["securefiles pair ", code] }), ":", jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("span", { className: "sf-paircode", children: code }), jsxRuntime.jsx(CopyButton, { text: code, label: "Copy" })] }), jsxRuntime.jsxs("span", { className: "sf-muted", children: ["Valid for about ", minutesLeft, " more minutes, once."] })] }), jsxRuntime.jsxs("li", { children: ["Pick files and folders to share in that panel (or ", jsxRuntime.jsx("code", { children: "securefiles share PATH" }), "). They appear here under ", jsxRuntime.jsx("code", { children: "devices/<computer>/" }), ", where you set links, passwords and limits."] })] }), jsxRuntime.jsx("div", { className: "sf-row", children: jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCode(null), children: "Done" }) })] }));
504
527
  }
505
528
  function MyComputers({ api, devices, onChange, }) {
506
529
  const [error, setError] = react.useState(null);
@@ -510,11 +533,139 @@ function MyComputers({ api, devices, onChange, }) {
510
533
  onChange();
511
534
  }
512
535
  catch (err) {
513
- setError(errText(err));
536
+ setError(errText$1(err));
514
537
  }
515
538
  } })] }, d.id))) })), error && jsxRuntime.jsx("p", { className: "sf-error", children: error }), jsxRuntime.jsx(AddComputer, { api: api, onPaired: onChange })] })] }));
516
539
  }
517
540
 
541
+ const errText = (err) => (err instanceof Error ? err.message : String(err));
542
+ function formatSize(bytes) {
543
+ if (bytes == null)
544
+ return "";
545
+ const units = ["B", "KB", "MB", "GB", "TB"];
546
+ let n = bytes;
547
+ let i = 0;
548
+ while (n >= 1024 && i < units.length - 1) {
549
+ n /= 1024;
550
+ i++;
551
+ }
552
+ return `${n.toFixed(i ? 1 : 0)} ${units[i]}`;
553
+ }
554
+ function GrantEditor({ person, files, onSave, onClose, }) {
555
+ const [all, setAll] = react.useState(person.my_grant.all);
556
+ const [ids, setIds] = react.useState(new Set(person.my_grant.ids));
557
+ const [search, setSearch] = react.useState("");
558
+ const [saving, setSaving] = react.useState(false);
559
+ const [error, setError] = react.useState(null);
560
+ const visible = react.useMemo(() => {
561
+ const q = search.trim().toLowerCase();
562
+ return [...files]
563
+ .sort((a, b) => a.filepath.localeCompare(b.filepath))
564
+ .filter((f) => !q || f.filepath.toLowerCase().includes(q));
565
+ }, [files, search]);
566
+ const save = async () => {
567
+ setSaving(true);
568
+ setError(null);
569
+ try {
570
+ await onSave({ all, ids: all ? [] : [...ids] });
571
+ onClose();
572
+ }
573
+ catch (err) {
574
+ setError(errText(err));
575
+ }
576
+ finally {
577
+ setSaving(false);
578
+ }
579
+ };
580
+ return (jsxRuntime.jsxs("div", { className: "sf-card", children: [jsxRuntime.jsxs("h4", { children: ["What ", person.username, " can access"] }), jsxRuntime.jsxs("label", { className: "sf-check", children: [jsxRuntime.jsx("input", { type: "radio", checked: all, onChange: () => setAll(true) }), jsxRuntime.jsxs("span", { children: [jsxRuntime.jsx("b", { children: "All my files" }), " ", jsxRuntime.jsx("span", { className: "sf-muted", children: "\u2014 including anything you add later" })] })] }), jsxRuntime.jsxs("label", { className: "sf-check", children: [jsxRuntime.jsx("input", { type: "radio", checked: !all, onChange: () => setAll(false) }), jsxRuntime.jsx("b", { children: "Only the items I choose" })] }), !all && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { className: "sf-input", type: "search", placeholder: "Filter your files\u2026", value: search, onChange: (e) => setSearch(e.target.value) }), jsxRuntime.jsxs("div", { className: "sf-browser", children: [visible.length === 0 && jsxRuntime.jsx("p", { className: "sf-muted", children: "No files." }), visible.map((f) => (jsxRuntime.jsxs("label", { className: "sf-check sf-browser__file", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: ids.has(f.id), onChange: (e) => setIds((s) => {
581
+ const next = new Set(s);
582
+ e.target.checked ? next.add(f.id) : next.delete(f.id);
583
+ return next;
584
+ }) }), jsxRuntime.jsx("span", { className: "sf-browser__name", children: f.filepath }), jsxRuntime.jsx("span", { className: "sf-muted", children: f.kind === "folder" ? "folder · .tar" : formatSize(f.size) })] }, f.id)))] }), jsxRuntime.jsxs("span", { className: "sf-muted", children: [ids.size, " selected"] })] })), error && jsxRuntime.jsx("p", { className: "sf-error", children: error }), jsxRuntime.jsxs("div", { className: "sf-row sf-row--end", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: onClose, children: "Cancel" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn sf-btn--primary", disabled: saving, onClick: save, children: saving ? "Saving…" : "Save" })] })] }));
585
+ }
586
+ function People({ api, files }) {
587
+ const [people, setPeople] = react.useState([]);
588
+ const [code, setCode] = react.useState(null);
589
+ const [entered, setEntered] = react.useState("");
590
+ const [editing, setEditing] = react.useState(null);
591
+ const [error, setError] = react.useState(null);
592
+ const [notice, setNotice] = react.useState(null);
593
+ const load = react.useCallback(async () => {
594
+ try {
595
+ setPeople((await api.people()).people);
596
+ }
597
+ catch (err) {
598
+ setError(errText(err));
599
+ }
600
+ }, [api]);
601
+ react.useEffect(() => {
602
+ load();
603
+ }, [load]);
604
+ react.useEffect(() => {
605
+ if (!code)
606
+ return;
607
+ const timer = setInterval(load, 5000);
608
+ const expire = setTimeout(() => setCode(null), 15 * 60 * 1000);
609
+ return () => {
610
+ clearInterval(timer);
611
+ clearTimeout(expire);
612
+ };
613
+ }, [code, load]);
614
+ const invite = async () => {
615
+ setError(null);
616
+ try {
617
+ setCode((await api.invitePerson()).code);
618
+ }
619
+ catch (err) {
620
+ setError(errText(err));
621
+ }
622
+ };
623
+ const accept = async () => {
624
+ setError(null);
625
+ setNotice(null);
626
+ try {
627
+ const res = await api.acceptPerson(entered);
628
+ setPeople(res.people);
629
+ setEntered("");
630
+ setNotice(`Connected with ${res.connected_with}.`);
631
+ }
632
+ catch (err) {
633
+ setError(errText(err));
634
+ }
635
+ };
636
+ const describe = (p) => p.my_grant.all ? "can access all your files" : `can access ${p.my_grant.ids.length} of your items`;
637
+ return (jsxRuntime.jsxs("details", { className: "sf-admin", children: [jsxRuntime.jsxs("summary", { children: ["People ", jsxRuntime.jsxs("span", { className: "sf-muted", children: ["(", people.length, ")"] })] }), jsxRuntime.jsxs("div", { className: "sf-stack", children: [jsxRuntime.jsx("p", { className: "sf-muted", children: "Connect with other abstractendeavors.com accounts, then choose what each person can access: all your files or specific ones. They download while signed in; nothing is public." }), people.length > 0 && (jsxRuntime.jsx("ul", { className: "sf-roots", children: people.map((p) => (jsxRuntime.jsxs("li", { children: [jsxRuntime.jsxs("span", { children: [jsxRuntime.jsx("b", { children: p.username }), " ", jsxRuntime.jsx("span", { className: p.my_grant.all ? "sf-tag sf-tag--warn" : "sf-muted", children: describe(p) }), p.shared_with_me > 0 && (jsxRuntime.jsxs("span", { className: "sf-muted", children: [" \u00B7 shares ", p.shared_with_me, " with you"] }))] }), jsxRuntime.jsxs("span", { className: "sf-row", children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setEditing(p.username), children: "Choose access" }), jsxRuntime.jsx(ConfirmButton, { label: "Disconnect", onConfirm: async () => {
638
+ try {
639
+ setPeople((await api.removePerson(p.username)).people);
640
+ }
641
+ catch (err) {
642
+ setError(errText(err));
643
+ }
644
+ } })] })] }, p.username))) })), editing && (jsxRuntime.jsx(GrantEditor, { person: people.find((p) => p.username === editing), files: files, onClose: () => setEditing(null), onSave: async (grant) => setPeople((await api.setGrants(editing, grant)).people) }, editing)), jsxRuntime.jsxs("div", { className: "sf-row sf-row--bottom", children: [code ? (jsxRuntime.jsxs("div", { className: "sf-field", children: [jsxRuntime.jsx("span", { children: "Send this code to the person (valid 15 minutes, once):" }), jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("span", { className: "sf-paircode", children: code }), jsxRuntime.jsx(CopyButton, { text: code, label: "Copy" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCode(null), children: "Done" })] }), jsxRuntime.jsx("span", { className: "sf-muted", children: "They enter it under People \u2192 \"Have a code?\" on their Secure Files page." })] })) : (jsxRuntime.jsx("button", { type: "button", className: "sf-btn sf-btn--primary", onClick: invite, children: "Invite someone" })), jsxRuntime.jsxs("label", { className: "sf-field sf-field--narrow", children: [jsxRuntime.jsx("span", { children: "Have a code?" }), jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsx("input", { className: "sf-input", value: entered, placeholder: "ABCD-1234", onChange: (e) => setEntered(e.target.value), onKeyDown: (e) => e.key === "Enter" && entered.trim() && accept() }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !entered.trim(), onClick: accept, children: "Connect" })] })] })] }), notice && jsxRuntime.jsx("p", { className: "sf-muted", children: notice }), error && jsxRuntime.jsx("p", { className: "sf-error", children: error })] })] }));
645
+ }
646
+ function SharedWithMe({ api }) {
647
+ const [owners, setOwners] = react.useState(null);
648
+ const [error, setError] = react.useState(null);
649
+ const load = react.useCallback(async () => {
650
+ try {
651
+ setOwners((await api.sharedWithMe()).owners);
652
+ setError(null);
653
+ }
654
+ catch (err) {
655
+ setError(errText(err));
656
+ }
657
+ }, [api]);
658
+ react.useEffect(() => {
659
+ load();
660
+ const onVisible = () => document.visibilityState === "visible" && load();
661
+ document.addEventListener("visibilitychange", onVisible);
662
+ return () => document.removeEventListener("visibilitychange", onVisible);
663
+ }, [load]);
664
+ if (!owners || owners.length === 0)
665
+ return error ? jsxRuntime.jsx("p", { className: "sf-error", children: error }) : null;
666
+ return (jsxRuntime.jsxs("section", { className: "sf-stack", children: [jsxRuntime.jsx("h3", { className: "sf-subhead", children: "Shared with me" }), owners.map((o) => (jsxRuntime.jsx("div", { className: "sf-table-wrap", children: jsxRuntime.jsxs("table", { className: "sf-table", children: [jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsxs("th", { children: ["From ", jsxRuntime.jsx("b", { children: o.owner }), " ", o.all && jsxRuntime.jsx("span", { className: "sf-muted", children: "(all their files)" })] }), jsxRuntime.jsx("th", { children: "Size" }), jsxRuntime.jsx("th", { className: "sf-col-actions" })] }) }), jsxRuntime.jsx("tbody", { children: o.files.map((f) => (jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsxs("td", { children: [f.filepath, f.kind === "folder" && jsxRuntime.jsx("span", { className: "sf-tag", children: "folder \u00B7 .tar" }), f.device && (jsxRuntime.jsxs("span", { className: `sf-tag${f.device.online ? "" : " sf-tag--warn"}`, children: ["on ", f.device.name, " \u00B7 ", f.device.online ? "online" : "offline"] }))] }), jsxRuntime.jsx("td", { className: "sf-muted", children: formatSize(f.size) }), jsxRuntime.jsx("td", { className: "sf-col-actions", children: jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !f.available, onClick: () => api.downloadShared(f.id), children: "Download" }) })] }, f.id))) })] }) }, o.owner)))] }));
667
+ }
668
+
518
669
  function styleInject(css, ref) {
519
670
  if ( ref === void 0 ) ref = {};
520
671
  var insertAt = ref.insertAt;
@@ -542,7 +693,7 @@ function styleInject(css, ref) {
542
693
  }
543
694
  }
544
695
 
545
- var css_248z = ".sf-root{--sf-border:color-mix(in srgb,currentColor 22%,transparent);--sf-subtle:color-mix(in srgb,currentColor 6%,transparent);--sf-accent:#2563eb;--sf-danger:#dc2626;display:flex;flex-direction:column;font-size:14px;gap:16px;margin:0 auto;max-width:1100px;padding:16px;text-align:left}.sf-root *,.sf-root :after,.sf-root :before{box-sizing:border-box}.sf-head{align-items:baseline;display:flex;gap:12px;justify-content:space-between}.sf-head h2{font-size:1.4em;margin:0}.sf-muted{opacity:.7}.sf-error{color:var(--sf-danger)}.sf-row{align-items:center;display:flex;flex-wrap:wrap;gap:8px}.sf-row--end{justify-content:flex-end}.sf-field{display:flex;flex:1;flex-direction:column;gap:4px;min-width:200px}.sf-input{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;color:inherit;font:inherit;min-width:0;padding:6px 10px;width:100%}.sf-row .sf-input{flex:1;width:auto}.sf-btn{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;color:inherit;cursor:pointer;font:inherit;padding:5px 12px;white-space:nowrap}.sf-btn:hover:not(:disabled){border-color:var(--sf-accent)}.sf-btn:disabled{cursor:default;opacity:.45}.sf-btn--primary{background:var(--sf-accent);border-color:var(--sf-accent);color:#fff}.sf-btn--armed,.sf-btn--danger:hover:not(:disabled){border-color:var(--sf-danger);color:var(--sf-danger)}.sf-btn--armed{background:color-mix(in srgb,var(--sf-danger) 12%,transparent)}.sf-icon-btn{background:none;border:1px solid transparent;border-radius:6px;color:inherit;cursor:pointer;font:inherit;padding:3px 6px}.sf-icon-btn:hover{border-color:var(--sf-border)}.sf-upload{display:flex;flex-direction:column;gap:10px}.sf-drop{align-items:center;border:2px dashed var(--sf-border);border-radius:12px;cursor:pointer;display:flex;gap:20px;padding:28px 24px;transition:border-color .15s,background .15s,box-shadow .15s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.sf-drop:focus-visible,.sf-drop:hover{border-color:var(--sf-accent);box-shadow:0 4px 14px color-mix(in srgb,currentColor 10%,transparent);outline:none}.sf-drop p{margin:0}.sf-drop__icon{flex-shrink:0;height:72px;opacity:.6;width:72px}.sf-drop__title{font-size:1.1em;font-weight:600;margin-bottom:4px!important}.sf-drop__count{color:var(--sf-accent);font-weight:600;margin-top:6px!important}.sf-drop--active{background:color-mix(in srgb,var(--sf-accent) 8%,transparent);border-color:var(--sf-accent)}.sf-drop--active .sf-drop__icon{color:var(--sf-accent);opacity:1}.sf-mode{gap:14px}.sf-queue progress{width:120px}.sf-upload>.sf-row{align-items:flex-end}.sf-queue{display:flex;flex-direction:column;gap:4px;list-style:none;margin:0;max-height:200px;overflow:auto;padding:0}.sf-queue li{display:flex;gap:12px;justify-content:space-between}.sf-queue__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-toolbar{display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between}.sf-search{max-width:320px}.sf-table-wrap{border:1px solid var(--sf-border);border-radius:8px;overflow-x:auto}.sf-table{border-collapse:collapse;width:100%}.sf-table td,.sf-table th{border-bottom:1px solid var(--sf-border);padding:8px 10px;text-align:left;vertical-align:middle}.sf-table tbody tr:last-child td{border-bottom:none}.sf-table th{background:var(--sf-subtle);font-weight:600}.sf-col-check{width:36px}.sf-col-actions{text-align:right!important}.sf-dir td{background:var(--sf-subtle)}.sf-dir__toggle{background:none;border:none;color:inherit;cursor:pointer;font:inherit;font-weight:600;padding:0}.sf-busy{opacity:.55}.sf-pill{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:999px;color:inherit;cursor:pointer;font:inherit;font-size:12px;padding:2px 10px}.sf-pill--on{background:color-mix(in srgb,#16a34a 18%,transparent);border-color:#16a34a}.sf-overlay{align-items:center;background:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;padding:16px;position:fixed;z-index:1000}.sf-dialog{--sf-border:color-mix(in srgb,CanvasText 22%,transparent);--sf-subtle:color-mix(in srgb,CanvasText 6%,transparent);background:Canvas;border-radius:10px;color:CanvasText;display:flex;flex-direction:column;gap:12px;max-height:90vh;overflow:auto;padding:18px 20px;text-align:left;width:min(520px,100%)}.sf-dialog__head{align-items:center;display:flex;justify-content:space-between}.sf-dialog__head h3{margin:0}.sf-dialog>.sf-muted{margin:0;word-break:break-all}.sf-dialog__foot{justify-content:flex-end}.sf-fieldset{border:none;display:flex;flex-direction:column;gap:12px;margin:0;padding:0}.sf-fieldset:disabled{opacity:.5}.sf-fieldset p{margin:0}.sf-check{align-items:center;cursor:pointer;display:flex;gap:8px}.sf-check--inline{display:inline-flex}.sf-stack{display:flex;flex-direction:column;gap:12px}.sf-stack p{margin:0}.sf-row--bottom{align-items:flex-end}.sf-field--narrow{flex:0 1 200px;min-width:140px}.sf-admin{border:1px solid var(--sf-border);border-radius:10px;padding:10px 14px}.sf-admin>summary{cursor:pointer;font-weight:600}.sf-admin[open]>summary{margin-bottom:12px}.sf-card{border:1px solid var(--sf-border);border-radius:8px;display:flex;flex-direction:column;gap:10px;padding:12px}.sf-card h4,.sf-card p{margin:0}.sf-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;resize:vertical}.sf-roots{display:flex;flex-direction:column;gap:6px;list-style:none;margin:0;padding:0}.sf-roots li{align-items:center;display:flex;gap:12px;justify-content:space-between}.sf-root-select{max-width:420px}.sf-browser{border:1px solid var(--sf-border);border-radius:6px;display:flex;flex-direction:column;max-height:320px;overflow:auto}.sf-browser>*{border-bottom:1px solid var(--sf-border);padding:5px 10px}.sf-browser>:last-child{border-bottom:none}.sf-browser__dir{background:none;border:none;border-bottom:1px solid var(--sf-border);color:inherit;cursor:pointer;font:inherit;text-align:left}.sf-browser__all,.sf-browser__dir:hover{background:var(--sf-subtle)}.sf-browser__all{font-weight:600}.sf-browser__name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-tag{border:1px solid var(--sf-border);border-radius:999px;display:inline-block;font-size:11px;margin-left:8px;padding:0 7px;vertical-align:middle}.sf-tag--warn{border-color:var(--sf-danger);color:var(--sf-danger)}.sf-strike{opacity:.7;text-decoration:line-through}.sf-browser__row{align-items:center;display:flex;gap:8px}.sf-browser__row .sf-browser__dir{border-bottom:none;flex:1;padding:0}.sf-dot{background:var(--sf-danger);border-radius:50%;display:inline-block;height:9px;vertical-align:middle;width:9px}.sf-dot--on{background:#16a34a}.sf-steps{display:flex;flex-direction:column;gap:10px;margin:0;padding-left:20px}.sf-steps li>.sf-row{margin:4px 0}.sf-codeline{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;font-size:12px;padding:4px 8px}.sf-codeline,.sf-paircode{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.sf-paircode{font-size:22px;font-weight:700;letter-spacing:.12em}.sf-small{font-size:12px}";
696
+ var css_248z = ".sf-root{--sf-border:color-mix(in srgb,currentColor 22%,transparent);--sf-subtle:color-mix(in srgb,currentColor 6%,transparent);--sf-accent:#2563eb;--sf-danger:#dc2626;display:flex;flex-direction:column;font-size:14px;gap:16px;margin:0 auto;max-width:1100px;padding:16px;text-align:left}.sf-root *,.sf-root :after,.sf-root :before{box-sizing:border-box}.sf-head{align-items:baseline;display:flex;gap:12px;justify-content:space-between}.sf-head h2{font-size:1.4em;margin:0}.sf-muted{opacity:.7}.sf-error{color:var(--sf-danger)}.sf-row{align-items:center;display:flex;flex-wrap:wrap;gap:8px}.sf-row--end{justify-content:flex-end}.sf-field{display:flex;flex:1;flex-direction:column;gap:4px;min-width:200px}.sf-input{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;color:inherit;font:inherit;min-width:0;padding:6px 10px;width:100%}.sf-row .sf-input{flex:1;width:auto}.sf-btn{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;color:inherit;cursor:pointer;font:inherit;padding:5px 12px;white-space:nowrap}.sf-btn:hover:not(:disabled){border-color:var(--sf-accent)}.sf-btn:disabled{cursor:default;opacity:.45}.sf-btn--primary{background:var(--sf-accent);border-color:var(--sf-accent);color:#fff}.sf-btn--armed,.sf-btn--danger:hover:not(:disabled){border-color:var(--sf-danger);color:var(--sf-danger)}.sf-btn--armed{background:color-mix(in srgb,var(--sf-danger) 12%,transparent)}.sf-icon-btn{background:none;border:1px solid transparent;border-radius:6px;color:inherit;cursor:pointer;font:inherit;padding:3px 6px}.sf-icon-btn:hover{border-color:var(--sf-border)}.sf-upload{display:flex;flex-direction:column;gap:10px}.sf-drop{align-items:center;border:2px dashed var(--sf-border);border-radius:12px;cursor:pointer;display:flex;gap:20px;padding:28px 24px;transition:border-color .15s,background .15s,box-shadow .15s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.sf-drop:focus-visible,.sf-drop:hover{border-color:var(--sf-accent);box-shadow:0 4px 14px color-mix(in srgb,currentColor 10%,transparent);outline:none}.sf-drop p{margin:0}.sf-drop__icon{flex-shrink:0;height:72px;opacity:.6;width:72px}.sf-drop__title{font-size:1.1em;font-weight:600;margin-bottom:4px!important}.sf-drop__count{color:var(--sf-accent);font-weight:600;margin-top:6px!important}.sf-drop--active{background:color-mix(in srgb,var(--sf-accent) 8%,transparent);border-color:var(--sf-accent)}.sf-drop--active .sf-drop__icon{color:var(--sf-accent);opacity:1}.sf-mode{gap:14px}.sf-queue progress{width:120px}.sf-upload>.sf-row{align-items:flex-end}.sf-queue{display:flex;flex-direction:column;gap:4px;list-style:none;margin:0;max-height:200px;overflow:auto;padding:0}.sf-queue li{display:flex;gap:12px;justify-content:space-between}.sf-queue__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-toolbar{display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between}.sf-search{max-width:320px}.sf-table-wrap{border:1px solid var(--sf-border);border-radius:8px;overflow-x:auto}.sf-table{border-collapse:collapse;width:100%}.sf-table td,.sf-table th{border-bottom:1px solid var(--sf-border);padding:8px 10px;text-align:left;vertical-align:middle}.sf-table tbody tr:last-child td{border-bottom:none}.sf-table th{background:var(--sf-subtle);font-weight:600}.sf-col-check{width:36px}.sf-col-actions{text-align:right!important}.sf-dir td{background:var(--sf-subtle)}.sf-dir__toggle{background:none;border:none;color:inherit;cursor:pointer;font:inherit;font-weight:600;padding:0}.sf-busy{opacity:.55}.sf-pill{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:999px;color:inherit;cursor:pointer;font:inherit;font-size:12px;padding:2px 10px}.sf-pill--on{background:color-mix(in srgb,#16a34a 18%,transparent);border-color:#16a34a}.sf-overlay{align-items:center;background:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;padding:16px;position:fixed;z-index:1000}.sf-dialog{--sf-border:color-mix(in srgb,CanvasText 22%,transparent);--sf-subtle:color-mix(in srgb,CanvasText 6%,transparent);background:Canvas;border-radius:10px;color:CanvasText;display:flex;flex-direction:column;gap:12px;max-height:90vh;overflow:auto;padding:18px 20px;text-align:left;width:min(520px,100%)}.sf-dialog__head{align-items:center;display:flex;justify-content:space-between}.sf-dialog__head h3{margin:0}.sf-dialog>.sf-muted{margin:0;word-break:break-all}.sf-dialog__foot{justify-content:flex-end}.sf-fieldset{border:none;display:flex;flex-direction:column;gap:12px;margin:0;padding:0}.sf-fieldset:disabled{opacity:.5}.sf-fieldset p{margin:0}.sf-check{align-items:center;cursor:pointer;display:flex;gap:8px}.sf-check--inline{display:inline-flex}.sf-stack{display:flex;flex-direction:column;gap:12px}.sf-stack p{margin:0}.sf-row--bottom{align-items:flex-end}.sf-field--narrow{flex:0 1 200px;min-width:140px}.sf-admin{border:1px solid var(--sf-border);border-radius:10px;padding:10px 14px}.sf-admin>summary{cursor:pointer;font-weight:600}.sf-admin[open]>summary{margin-bottom:12px}.sf-card{border:1px solid var(--sf-border);border-radius:8px;display:flex;flex-direction:column;gap:10px;padding:12px}.sf-card h4,.sf-card p{margin:0}.sf-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;resize:vertical}.sf-roots{display:flex;flex-direction:column;gap:6px;list-style:none;margin:0;padding:0}.sf-roots li{align-items:center;display:flex;gap:12px;justify-content:space-between}.sf-root-select{max-width:420px}.sf-browser{border:1px solid var(--sf-border);border-radius:6px;display:flex;flex-direction:column;max-height:320px;overflow:auto}.sf-browser>*{border-bottom:1px solid var(--sf-border);padding:5px 10px}.sf-browser>:last-child{border-bottom:none}.sf-browser__dir{background:none;border:none;border-bottom:1px solid var(--sf-border);color:inherit;cursor:pointer;font:inherit;text-align:left}.sf-browser__all,.sf-browser__dir:hover{background:var(--sf-subtle)}.sf-browser__all{font-weight:600}.sf-browser__name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-tag{border:1px solid var(--sf-border);border-radius:999px;display:inline-block;font-size:11px;margin-left:8px;padding:0 7px;vertical-align:middle}.sf-tag--warn{border-color:var(--sf-danger);color:var(--sf-danger)}.sf-strike{opacity:.7;text-decoration:line-through}.sf-browser__row{align-items:center;display:flex;gap:8px}.sf-browser__row .sf-browser__dir{border-bottom:none;flex:1;padding:0}.sf-dot{background:var(--sf-danger);border-radius:50%;display:inline-block;height:9px;vertical-align:middle;width:9px}.sf-dot--on{background:#16a34a}.sf-steps{display:flex;flex-direction:column;gap:10px;margin:0;padding-left:20px}.sf-steps li>.sf-row{margin:4px 0}.sf-codeline{background:var(--sf-subtle);border:1px solid var(--sf-border);border-radius:6px;font-size:12px;padding:4px 8px}.sf-codeline,.sf-paircode{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.sf-paircode{font-size:22px;font-weight:700;letter-spacing:.12em}.sf-small{font-size:12px}.sf-os{display:flex;flex-direction:column;gap:4px;margin-top:6px}.sf-subhead{font-size:1.1em;margin:8px 0 0}";
546
697
  styleInject(css_248z);
547
698
 
548
699
  function buildTree(files) {
@@ -683,7 +834,7 @@ function SecureFiles({ apiBase }) {
683
834
  rows.push(...ownFiles.map((f) => renderFile(f, depth)));
684
835
  return rows;
685
836
  };
686
- return (jsxRuntime.jsxs("div", { className: "sf-root", children: [jsxRuntime.jsxs("header", { className: "sf-head", children: [jsxRuntime.jsx("h2", { children: "Secure Files" }), user && jsxRuntime.jsxs("span", { className: "sf-muted", children: ["Signed in as ", user] })] }), jsxRuntime.jsx(UploadPanel, { api: api, folders: folders.filter((d) => d !== "devices" && !d.startsWith("devices/")), onUploaded: load }), jsxRuntime.jsx(MyComputers, { api: api, devices: devices, onChange: load }), isAdmin && jsxRuntime.jsx(ServerFiles, { api: api, folders: folders, onLinked: load }), jsxRuntime.jsxs("section", { className: "sf-toolbar", children: [jsxRuntime.jsx("input", { className: "sf-input sf-search", type: "search", placeholder: "Search files\u2026", value: search, onChange: (e) => setSearch(e.target.value) }), jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsxs("span", { className: "sf-muted", children: [selected.size, " selected"] }), folders.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set()), children: "Expand all" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set(folders)), children: "Collapse all" })] })), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => download(selectedFiles), children: "Download" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, true), children: "Share" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, false), children: "Unshare" }), jsxRuntime.jsx(ConfirmButton, { label: "Delete", disabled: !selected.size, onConfirm: () => remove(selectedFiles) })] })] }), notice && (jsxRuntime.jsx("p", { className: "sf-error", role: "alert", children: notice })), loading ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "Loading\u2026" })) : error ? (jsxRuntime.jsx("p", { className: "sf-error", role: "alert", children: error })) : files.length === 0 ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "No files yet \u2014 upload something above." })) : (jsxRuntime.jsx("div", { className: "sf-table-wrap", children: jsxRuntime.jsxs("table", { className: "sf-table", children: [jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { className: "sf-col-check", children: jsxRuntime.jsx("input", { type: "checkbox", "aria-label": "Select all", checked: allVisibleSelected, onChange: (e) => toggle(filtered.map((f) => f.id), e.target.checked) }) }), jsxRuntime.jsx("th", { children: "Name" }), jsxRuntime.jsx("th", { children: "Sharing" }), jsxRuntime.jsx("th", { children: "Downloads" }), jsxRuntime.jsx("th", { className: "sf-col-actions", children: "Actions" })] }) }), jsxRuntime.jsx("tbody", { children: renderFolder(tree, 0) })] }) })), editing && (jsxRuntime.jsx(ShareDialog, { file: files.find((f) => f.id === editing.id) ?? editing, onClose: () => setEditing(null), onSave: saveShare }, editing.id))] }));
837
+ return (jsxRuntime.jsxs("div", { className: "sf-root", children: [jsxRuntime.jsxs("header", { className: "sf-head", children: [jsxRuntime.jsx("h2", { children: "Secure Files" }), user && jsxRuntime.jsxs("span", { className: "sf-muted", children: ["Signed in as ", user] })] }), jsxRuntime.jsx(UploadPanel, { api: api, folders: folders.filter((d) => d !== "devices" && !d.startsWith("devices/")), onUploaded: load }), jsxRuntime.jsx(MyComputers, { api: api, devices: devices, onChange: load }), jsxRuntime.jsx(People, { api: api, files: files }), isAdmin && jsxRuntime.jsx(ServerFiles, { api: api, folders: folders, onLinked: load }), jsxRuntime.jsxs("section", { className: "sf-toolbar", children: [jsxRuntime.jsx("input", { className: "sf-input sf-search", type: "search", placeholder: "Search files\u2026", value: search, onChange: (e) => setSearch(e.target.value) }), jsxRuntime.jsxs("div", { className: "sf-row", children: [jsxRuntime.jsxs("span", { className: "sf-muted", children: [selected.size, " selected"] }), folders.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set()), children: "Expand all" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", onClick: () => setCollapsed(new Set(folders)), children: "Collapse all" })] })), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => download(selectedFiles), children: "Download" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, true), children: "Share" }), jsxRuntime.jsx("button", { type: "button", className: "sf-btn", disabled: !selected.size, onClick: () => setShared(selectedFiles, false), children: "Unshare" }), jsxRuntime.jsx(ConfirmButton, { label: "Delete", disabled: !selected.size, onConfirm: () => remove(selectedFiles) })] })] }), notice && (jsxRuntime.jsx("p", { className: "sf-error", role: "alert", children: notice })), loading ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "Loading\u2026" })) : error ? (jsxRuntime.jsx("p", { className: "sf-error", role: "alert", children: error })) : files.length === 0 ? (jsxRuntime.jsx("p", { className: "sf-muted", children: "No files yet \u2014 upload something above." })) : (jsxRuntime.jsx("div", { className: "sf-table-wrap", children: jsxRuntime.jsxs("table", { className: "sf-table", children: [jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { className: "sf-col-check", children: jsxRuntime.jsx("input", { type: "checkbox", "aria-label": "Select all", checked: allVisibleSelected, onChange: (e) => toggle(filtered.map((f) => f.id), e.target.checked) }) }), jsxRuntime.jsx("th", { children: "Name" }), jsxRuntime.jsx("th", { children: "Sharing" }), jsxRuntime.jsx("th", { children: "Downloads" }), jsxRuntime.jsx("th", { className: "sf-col-actions", children: "Actions" })] }) }), jsxRuntime.jsx("tbody", { children: renderFolder(tree, 0) })] }) })), jsxRuntime.jsx(SharedWithMe, { api: api }), editing && (jsxRuntime.jsx(ShareDialog, { file: files.find((f) => f.id === editing.id) ?? editing, onClose: () => setEditing(null), onSave: saveShare }, editing.id))] }));
687
838
  }
688
839
 
689
840
  exports.ApiError = ApiError;