fauna-api 0.0.0-stage → 1.0.679

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.
Files changed (171) hide show
  1. package/dist/brands.d.ts +2 -0
  2. package/dist/brands.js +2 -0
  3. package/dist/index.d.ts +64 -0
  4. package/dist/index.js +121 -0
  5. package/dist/src/ant/chat.d.ts +130 -0
  6. package/dist/src/ant/chat.js +467 -0
  7. package/dist/src/ant/format.d.ts +40 -0
  8. package/dist/src/ant/format.js +143 -0
  9. package/dist/src/ant/graph.d.ts +130 -0
  10. package/dist/src/ant/graph.js +449 -0
  11. package/dist/src/ant/index.d.ts +16 -0
  12. package/dist/src/ant/index.js +61 -0
  13. package/dist/src/ant/persona.d.ts +151 -0
  14. package/dist/src/ant/persona.js +136 -0
  15. package/dist/src/ant/timeline.d.ts +283 -0
  16. package/dist/src/ant/timeline.js +1025 -0
  17. package/dist/src/authentification.d.ts +45 -0
  18. package/dist/src/authentification.js +194 -0
  19. package/dist/src/billing.d.ts +135 -0
  20. package/dist/src/billing.js +128 -0
  21. package/dist/src/calendar.d.ts +83 -0
  22. package/dist/src/calendar.js +133 -0
  23. package/dist/src/chats.d.ts +113 -0
  24. package/dist/src/chats.js +100 -0
  25. package/dist/src/cicada/contacts.d.ts +31 -0
  26. package/dist/src/cicada/contacts.js +28 -0
  27. package/dist/src/cicada/conversations.d.ts +73 -0
  28. package/dist/src/cicada/conversations.js +236 -0
  29. package/dist/src/cicada/inboxes.d.ts +48 -0
  30. package/dist/src/cicada/inboxes.js +82 -0
  31. package/dist/src/cicada/index.d.ts +16 -0
  32. package/dist/src/cicada/index.js +61 -0
  33. package/dist/src/cicada/phone.d.ts +48 -0
  34. package/dist/src/cicada/phone.js +306 -0
  35. package/dist/src/drive.d.ts +80 -0
  36. package/dist/src/drive.js +472 -0
  37. package/dist/src/extensions/index.d.ts +4 -0
  38. package/dist/src/extensions/index.js +5 -0
  39. package/dist/src/extensions/web/index.d.ts +6 -0
  40. package/dist/src/extensions/web/index.js +7 -0
  41. package/dist/src/extensions/web/session.d.ts +108 -0
  42. package/dist/src/extensions/web/session.js +470 -0
  43. package/dist/src/extensions/web/tools.d.ts +13 -0
  44. package/dist/src/extensions/web/tools.js +72 -0
  45. package/dist/src/friends.d.ts +39 -0
  46. package/dist/src/friends.js +119 -0
  47. package/dist/src/groups.d.ts +82 -0
  48. package/dist/src/groups.js +269 -0
  49. package/dist/src/inbox.d.ts +11 -0
  50. package/dist/src/inbox.js +35 -0
  51. package/dist/src/integrations.d.ts +52 -0
  52. package/dist/src/integrations.js +205 -0
  53. package/dist/src/magpie/explorer.d.ts +14 -0
  54. package/dist/src/magpie/explorer.js +36 -0
  55. package/dist/src/magpie/index.d.ts +16 -0
  56. package/dist/src/magpie/index.js +61 -0
  57. package/dist/src/magpie/portfolio.d.ts +52 -0
  58. package/dist/src/magpie/portfolio.js +179 -0
  59. package/dist/src/magpie/quotes.d.ts +35 -0
  60. package/dist/src/magpie/quotes.js +47 -0
  61. package/dist/src/magpie/room.d.ts +61 -0
  62. package/dist/src/magpie/room.js +238 -0
  63. package/dist/src/maps.d.ts +159 -0
  64. package/dist/src/maps.js +171 -0
  65. package/dist/src/marmot/browser.d.ts +139 -0
  66. package/dist/src/marmot/browser.js +389 -0
  67. package/dist/src/marmot/index.d.ts +20 -0
  68. package/dist/src/marmot/index.js +65 -0
  69. package/dist/src/marmot/media.d.ts +56 -0
  70. package/dist/src/marmot/media.js +265 -0
  71. package/dist/src/marmot/research.d.ts +337 -0
  72. package/dist/src/marmot/research.js +1138 -0
  73. package/dist/src/marmot/scraper.d.ts +59 -0
  74. package/dist/src/marmot/scraper.js +94 -0
  75. package/dist/src/marmot/web.d.ts +402 -0
  76. package/dist/src/marmot/web.js +2586 -0
  77. package/dist/src/marmot/workspace.d.ts +81 -0
  78. package/dist/src/marmot/workspace.js +263 -0
  79. package/dist/src/parrot/agent.d.ts +53 -0
  80. package/dist/src/parrot/agent.js +93 -0
  81. package/dist/src/parrot/chat.d.ts +84 -0
  82. package/dist/src/parrot/chat.js +237 -0
  83. package/dist/src/parrot/format.d.ts +128 -0
  84. package/dist/src/parrot/format.js +639 -0
  85. package/dist/src/parrot/generative.d.ts +261 -0
  86. package/dist/src/parrot/generative.js +185 -0
  87. package/dist/src/parrot/index.d.ts +22 -0
  88. package/dist/src/parrot/index.js +67 -0
  89. package/dist/src/parrot/room.d.ts +225 -0
  90. package/dist/src/parrot/room.js +466 -0
  91. package/dist/src/parrot/routing.d.ts +90 -0
  92. package/dist/src/parrot/routing.js +78 -0
  93. package/dist/src/parrot/share.d.ts +126 -0
  94. package/dist/src/parrot/share.js +261 -0
  95. package/dist/src/parrot/socket.d.ts +41 -0
  96. package/dist/src/parrot/socket.js +478 -0
  97. package/dist/src/payments.d.ts +73 -0
  98. package/dist/src/payments.js +107 -0
  99. package/dist/src/private/cards.css +75 -0
  100. package/dist/src/rooms.d.ts +76 -0
  101. package/dist/src/rooms.js +168 -0
  102. package/dist/src/shared/schema.d.ts +24 -0
  103. package/dist/src/shared/schema.js +1 -0
  104. package/dist/src/shared/server.d.ts +65 -0
  105. package/dist/src/shared/server.js +302 -0
  106. package/dist/src/termite/environment.d.ts +110 -0
  107. package/dist/src/termite/environment.js +263 -0
  108. package/dist/src/termite/index.d.ts +14 -0
  109. package/dist/src/termite/index.js +59 -0
  110. package/dist/src/termite/os.d.ts +326 -0
  111. package/dist/src/termite/os.js +572 -0
  112. package/dist/src/termite/versioning.d.ts +94 -0
  113. package/dist/src/termite/versioning.js +217 -0
  114. package/dist/src/tickets.d.ts +43 -0
  115. package/dist/src/tickets.js +138 -0
  116. package/dist/src/travel.d.ts +85 -0
  117. package/dist/src/travel.js +44 -0
  118. package/dist/src/ui/authentification.d.ts +24 -0
  119. package/dist/src/ui/authentification.js +177 -0
  120. package/dist/src/ui/brands.css +11 -0
  121. package/dist/src/ui/brands.d.ts +7 -0
  122. package/dist/src/ui/brands.js +14 -0
  123. package/dist/src/ui/browser.d.ts +74 -0
  124. package/dist/src/ui/browser.js +470 -0
  125. package/dist/src/ui/chat.d.ts +183 -0
  126. package/dist/src/ui/chat.js +2379 -0
  127. package/dist/src/ui/chatPanel.d.ts +109 -0
  128. package/dist/src/ui/chatPanel.js +768 -0
  129. package/dist/src/ui/chatSequence.d.ts +36 -0
  130. package/dist/src/ui/chatSequence.js +42 -0
  131. package/dist/src/ui/crown.d.ts +19 -0
  132. package/dist/src/ui/crown.js +199 -0
  133. package/dist/src/ui/cursor.d.ts +73 -0
  134. package/dist/src/ui/cursor.js +175 -0
  135. package/dist/src/ui/device.d.ts +33 -0
  136. package/dist/src/ui/device.js +49 -0
  137. package/dist/src/ui/drive.d.ts +69 -0
  138. package/dist/src/ui/drive.js +1243 -0
  139. package/dist/src/ui/generative.d.ts +39 -0
  140. package/dist/src/ui/generative.js +313 -0
  141. package/dist/src/ui/hover.d.ts +6 -0
  142. package/dist/src/ui/hover.js +210 -0
  143. package/dist/src/ui/index.d.ts +42 -0
  144. package/dist/src/ui/index.js +87 -0
  145. package/dist/src/ui/integrations.d.ts +13 -0
  146. package/dist/src/ui/integrations.js +175 -0
  147. package/dist/src/ui/maps.d.ts +87 -0
  148. package/dist/src/ui/maps.js +547 -0
  149. package/dist/src/ui/roster.d.ts +32 -0
  150. package/dist/src/ui/roster.js +163 -0
  151. package/dist/src/ui/talk.d.ts +12 -0
  152. package/dist/src/ui/talk.js +560 -0
  153. package/dist/src/ui/tickets.d.ts +13 -0
  154. package/dist/src/ui/tickets.js +966 -0
  155. package/dist/src/ui/toast.d.ts +7 -0
  156. package/dist/src/ui/toast.js +258 -0
  157. package/dist/src/ui/tokenText.d.ts +18 -0
  158. package/dist/src/ui/tokenText.js +96 -0
  159. package/dist/src/ui/tokenized.d.ts +42 -0
  160. package/dist/src/ui/tokenized.js +622 -0
  161. package/dist/src/ui/ui.css +4763 -0
  162. package/dist/src/ui/view.d.ts +23 -0
  163. package/dist/src/ui/view.js +584 -0
  164. package/dist/src/view.d.ts +113 -0
  165. package/dist/src/view.js +386 -0
  166. package/dist/src/webhooks.d.ts +66 -0
  167. package/dist/src/webhooks.js +78 -0
  168. package/dist/ui.d.ts +1 -0
  169. package/dist/ui.js +1 -0
  170. package/package.json +87 -4
  171. package/README.md +0 -3
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { Fauna } from "../../index.js";
3
+ import type { Presence } from "../shared/server.js";
4
+ type Target = Fauna.View.Target;
5
+ export type ViewOptions = {
6
+ /** Join the file's live room (default). With false the file shows alone, read only. */
7
+ live?: boolean;
8
+ /** Show the bar with the name, size, people and download (default true). */
9
+ header?: boolean;
10
+ /** Who else has the file open changed, for a tab that shows them. */
11
+ onPeople?(people: Presence[]): void;
12
+ /** The room's edits were written back to the file, for anything showing it elsewhere (a preview). */
13
+ onSaved?(): void;
14
+ };
15
+ /** Mount a file's view in `container`: its live room and the right editor or player. */
16
+ export declare function createView(container: HTMLElement, target: Target, options?: ViewOptions): {
17
+ destroy(): void;
18
+ };
19
+ /** A file's view: its live room (unless `live` is false) and the editor or player for its kind. */
20
+ export declare function View({ target, live, header, onPeople, onSaved }: {
21
+ target: Target;
22
+ } & ViewOptions): React.JSX.Element;
23
+ export {};
@@ -0,0 +1,584 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import * as Y from "yjs";
5
+ import { EditorView, basicSetup } from "codemirror";
6
+ import { EditorState } from "@codemirror/state";
7
+ import { yCollab } from "y-codemirror.next";
8
+ import { javascript } from "@codemirror/lang-javascript";
9
+ import { python } from "@codemirror/lang-python";
10
+ import { json } from "@codemirror/lang-json";
11
+ import { markdown } from "@codemirror/lang-markdown";
12
+ import { html } from "@codemirror/lang-html";
13
+ import { css } from "@codemirror/lang-css";
14
+ import { Fauna } from "../../index.js";
15
+ import { colorFromSeed, hashSeed } from "./cursor.js";
16
+ /** Mount a file's view in `container`: its live room and the right editor or player. */
17
+ export function createView(container, target, options = {}) {
18
+ const root = createRoot(container);
19
+ root.render(_jsx(View, { target: target, ...options }));
20
+ return { destroy: () => root.unmount() };
21
+ }
22
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
23
+ function fileOf(target) {
24
+ return 'ref' in target && 'chat' in target ? target.file : target;
25
+ }
26
+ function nameOf(presence) {
27
+ return presence.profile?.displayName || presence.profile?.email?.split("@")[0] || "Guest";
28
+ }
29
+ function avatarOf(presence) {
30
+ if (presence.profile?.icon)
31
+ return presence.profile.icon;
32
+ return `https://ui-avatars.com/api/?name=${encodeURIComponent(nameOf(presence))}&background=161617&color=E8E2DC&size=64`;
33
+ }
34
+ function colorOf(seed) {
35
+ return colorFromSeed(seed || "?");
36
+ }
37
+ function faded(color, alpha) {
38
+ return color.replace(/\)$/, ` / ${alpha})`);
39
+ }
40
+ function sizeLabel(bytes) {
41
+ if (bytes < 1024)
42
+ return `${bytes} bytes`;
43
+ if (bytes < 1024 * 1024)
44
+ return `${(bytes / 1024).toFixed(1)} KB`;
45
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
46
+ }
47
+ function kindLabel(kind, contentType) {
48
+ if (kind === "text")
49
+ return "Text";
50
+ if (kind === "table")
51
+ return "Table";
52
+ if (kind === "image")
53
+ return "Image";
54
+ if (contentType.startsWith("video/"))
55
+ return "Video";
56
+ if (contentType.startsWith("audio/"))
57
+ return "Audio";
58
+ if (contentType === "application/pdf")
59
+ return "PDF";
60
+ return "File";
61
+ }
62
+ function statusOf(presence) {
63
+ try {
64
+ return presence.status ? JSON.parse(presence.status) : {};
65
+ }
66
+ catch {
67
+ return {};
68
+ }
69
+ }
70
+ function throttle(fn, ms) {
71
+ let last = 0;
72
+ let pending = null;
73
+ let next = null;
74
+ return (...args) => {
75
+ next = args;
76
+ const wait = last + ms - Date.now();
77
+ if (wait <= 0) {
78
+ last = Date.now();
79
+ fn(...args);
80
+ next = null;
81
+ }
82
+ else if (!pending) {
83
+ pending = setTimeout(() => {
84
+ pending = null;
85
+ last = Date.now();
86
+ if (next)
87
+ fn(...next);
88
+ next = null;
89
+ }, wait);
90
+ }
91
+ };
92
+ }
93
+ function languageFor(name) {
94
+ const ext = name.includes(".") ? name.split(".").pop().toLowerCase() : "";
95
+ if (["js", "mjs", "cjs", "jsx"].includes(ext))
96
+ return javascript({ jsx: true });
97
+ if (["ts", "tsx"].includes(ext))
98
+ return javascript({ jsx: ext === "tsx", typescript: true });
99
+ if (ext === "py")
100
+ return python();
101
+ if (["json", "jsonl"].includes(ext))
102
+ return json();
103
+ if (["md", "markdown"].includes(ext))
104
+ return markdown();
105
+ if (["html", "htm", "vue", "svelte"].includes(ext))
106
+ return html();
107
+ if (ext === "css")
108
+ return css();
109
+ return [];
110
+ }
111
+ function useObjectURL(file, enabled, version) {
112
+ const [state, setState] = useState({ url: null, failed: false });
113
+ useEffect(() => {
114
+ if (!enabled)
115
+ return;
116
+ let url = null;
117
+ let cancelled = false;
118
+ setState({ url: null, failed: false });
119
+ file.download(version > 0).then((bytes) => {
120
+ if (cancelled)
121
+ return;
122
+ url = URL.createObjectURL(new Blob([new Uint8Array(bytes)], { type: file.contentType || "application/octet-stream" }));
123
+ setState({ url, failed: false });
124
+ }, () => { if (!cancelled)
125
+ setState({ url: null, failed: true }); });
126
+ return () => {
127
+ cancelled = true;
128
+ if (url)
129
+ URL.revokeObjectURL(url);
130
+ };
131
+ }, [file, enabled, version]);
132
+ return state;
133
+ }
134
+ /** An awareness for y-codemirror built from room seats: honest, one cursor per seat. */
135
+ class SeatAwareness {
136
+ constructor(doc, onLocal) {
137
+ Object.defineProperty(this, "onLocal", {
138
+ enumerable: true,
139
+ configurable: true,
140
+ writable: true,
141
+ value: onLocal
142
+ });
143
+ Object.defineProperty(this, "doc", {
144
+ enumerable: true,
145
+ configurable: true,
146
+ writable: true,
147
+ value: void 0
148
+ });
149
+ Object.defineProperty(this, "local", {
150
+ enumerable: true,
151
+ configurable: true,
152
+ writable: true,
153
+ value: {}
154
+ });
155
+ Object.defineProperty(this, "states", {
156
+ enumerable: true,
157
+ configurable: true,
158
+ writable: true,
159
+ value: new Map()
160
+ });
161
+ Object.defineProperty(this, "listeners", {
162
+ enumerable: true,
163
+ configurable: true,
164
+ writable: true,
165
+ value: new Set()
166
+ });
167
+ this.doc = doc;
168
+ }
169
+ getLocalState() { return this.local; }
170
+ setLocalStateField(field, value) {
171
+ this.local = { ...this.local, [field]: value };
172
+ this.onLocal(this.local);
173
+ }
174
+ getStates() {
175
+ const states = new Map(this.states);
176
+ states.set(this.doc.clientID, this.local);
177
+ return states;
178
+ }
179
+ on(_event, listener) { this.listeners.add(listener); }
180
+ off(_event, listener) { this.listeners.delete(listener); }
181
+ setRemote(states) {
182
+ const added = [];
183
+ const updated = [];
184
+ const removed = [];
185
+ for (const [id, state] of states) {
186
+ if (!this.states.has(id))
187
+ added.push(id);
188
+ else if (JSON.stringify(this.states.get(id)) !== JSON.stringify(state))
189
+ updated.push(id);
190
+ }
191
+ for (const id of this.states.keys())
192
+ if (!states.has(id))
193
+ removed.push(id);
194
+ this.states = states;
195
+ if (added.length || updated.length || removed.length) {
196
+ for (const listener of this.listeners)
197
+ listener({ added, updated, removed }, "remote");
198
+ }
199
+ }
200
+ }
201
+ // ─── The view ────────────────────────────────────────────────────────────────
202
+ /** A file's view: its live room (unless `live` is false) and the editor or player for its kind. */
203
+ export function View({ target, live = true, header = true, onPeople, onSaved }) {
204
+ const file = fileOf(target);
205
+ const [room, setRoom] = useState(null);
206
+ const [self, setSelf] = useState(null);
207
+ const [people, setPeople] = useState([]);
208
+ const [version, setVersion] = useState(0);
209
+ const [error, setError] = useState(null);
210
+ const [local, setLocal] = useState(null);
211
+ const onPeopleRef = useRef(onPeople);
212
+ onPeopleRef.current = onPeople;
213
+ const onSavedRef = useRef(onSaved);
214
+ onSavedRef.current = onSaved;
215
+ // Join the room, and join again whenever it closes or the connection drops.
216
+ useEffect(() => {
217
+ if (!live)
218
+ return;
219
+ let active = true;
220
+ let current = null;
221
+ let retry = null;
222
+ // A chat attachment opens as a chat member; someone who isn't one (a share link guest) looks through its code.
223
+ let attempt = target;
224
+ const join = async () => {
225
+ try {
226
+ const joined = await Fauna.View.connect(attempt, {
227
+ onPeople: (r) => {
228
+ if (!active)
229
+ return;
230
+ const others = r.people(joined.self);
231
+ setPeople([...others]);
232
+ onPeopleRef.current?.(others);
233
+ },
234
+ onReload: () => { if (active)
235
+ setVersion((v) => v + 1); },
236
+ onSaved: () => { if (active)
237
+ onSavedRef.current?.(); },
238
+ onClosed: () => {
239
+ if (!active)
240
+ return;
241
+ retry = setTimeout(() => void join(), 1500);
242
+ },
243
+ });
244
+ if (!active) {
245
+ joined.room.disconnect();
246
+ return;
247
+ }
248
+ current = joined.room;
249
+ setError(null);
250
+ setRoom(joined.room);
251
+ setSelf(joined.self);
252
+ const others = joined.room.people(joined.self);
253
+ setPeople([...others]);
254
+ onPeopleRef.current?.(others);
255
+ setVersion((v) => v + 1);
256
+ }
257
+ catch (err) {
258
+ if (!active)
259
+ return;
260
+ const code = fileOf(target).authorization.accessCode;
261
+ if (attempt === target && 'ref' in target && code) {
262
+ attempt = fileOf(target);
263
+ void join();
264
+ return;
265
+ }
266
+ setError(err?.message ?? "Couldn't open this file.");
267
+ retry = setTimeout(() => void join(), 4000);
268
+ }
269
+ };
270
+ void join();
271
+ return () => {
272
+ active = false;
273
+ if (retry)
274
+ clearTimeout(retry);
275
+ current?.disconnect();
276
+ };
277
+ }, [target, live]);
278
+ // Without a room: the file alone, read only.
279
+ useEffect(() => {
280
+ if (live)
281
+ return;
282
+ const kind = Fauna.View.kindOf(file.contentType, file.name, file.contentLength);
283
+ if (kind !== "text" && kind !== "table") {
284
+ setLocal({ kind, doc: new Y.Doc() });
285
+ return;
286
+ }
287
+ let cancelled = false;
288
+ file.download().then((bytes) => {
289
+ if (!cancelled)
290
+ setLocal({ kind, doc: Fauna.View.docFrom(kind, new Uint8Array(bytes)) });
291
+ }, () => { if (!cancelled)
292
+ setError("Couldn't open this file."); });
293
+ return () => { cancelled = true; };
294
+ }, [file, live]);
295
+ const kind = live ? (room ? room.kind : null) : local?.kind ?? null;
296
+ const doc = live ? room?.doc ?? null : local?.doc ?? null;
297
+ const readOnly = !live || room?.role !== "write";
298
+ const contentType = live && room ? room.file.contentType || file.contentType : file.contentType;
299
+ const name = live && room ? room.file.name || file.name : file.name;
300
+ const size = live && room ? room.file.contentLength : file.contentLength;
301
+ const mediaKind = kind === "image" || kind === "view";
302
+ const media = useObjectURL(file, Boolean(kind) && mediaKind, version);
303
+ const download = useCallback(async () => {
304
+ const bytes = await file.download(true);
305
+ const url = URL.createObjectURL(new Blob([new Uint8Array(bytes)], { type: file.contentType || "application/octet-stream" }));
306
+ const a = document.createElement("a");
307
+ a.href = url;
308
+ a.download = name;
309
+ a.click();
310
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
311
+ }, [file, name]);
312
+ let body;
313
+ if (!kind || !doc) {
314
+ body = _jsx("div", { className: "view-state", children: error ?? "Opening\u2026" });
315
+ }
316
+ else if (kind === "text") {
317
+ body = _jsx(TextEditor, { doc: doc, name: name, readOnly: readOnly, self: self, people: people }, version);
318
+ }
319
+ else if (kind === "table") {
320
+ body = _jsx(TableEditor, { doc: doc, readOnly: readOnly, self: self, people: people }, version);
321
+ }
322
+ else if (kind === "image") {
323
+ body = media.url
324
+ ? _jsx(ImageMarks, { doc: doc, url: media.url, readOnly: readOnly, owner: room?.owner === true, self: self, people: people }, version)
325
+ : _jsx("div", { className: "view-state", children: media.failed ? "Couldn't load this image." : "Opening\u2026" });
326
+ }
327
+ else {
328
+ body = _jsx(Player, { url: media.url, failed: media.failed, contentType: contentType, name: name, size: size, onDownload: download });
329
+ }
330
+ return (_jsxs("div", { className: "view-root", children: [header && (_jsxs("div", { className: "view-bar", children: [_jsx("div", { className: "view-bar-name", title: name, children: name }), _jsxs("div", { className: "view-bar-meta", children: [kind ? `${kindLabel(kind, contentType)}, ${sizeLabel(size)}` : sizeLabel(size), live && readOnly && room ? ", view only" : ""] }), _jsx(People, { self: self, people: people }), _jsx("button", { className: "view-bar-button material-symbols-outlined", title: "Download", "aria-label": "Download", onClick: () => void download(), children: "download" })] })), _jsx("div", { className: "view-body", children: body })] }));
331
+ }
332
+ function People({ self, people }) {
333
+ const seats = self ? [self, ...people] : people;
334
+ if (seats.length === 0)
335
+ return null;
336
+ return (_jsxs("div", { className: "view-people", children: [seats.slice(0, 6).map((p) => (_jsx("img", { className: "view-person", src: avatarOf(p), alt: nameOf(p), title: p === self ? `${nameOf(p)} (you)` : nameOf(p), style: { borderColor: colorOf(p.profile?.email ?? p.id) } }, p.id))), seats.length > 6 && _jsxs("span", { className: "view-people-more", children: ["+", seats.length - 6] })] }));
337
+ }
338
+ // ─── Text and code ───────────────────────────────────────────────────────────
339
+ function TextEditor({ doc, name, readOnly, self, people }) {
340
+ const host = useRef(null);
341
+ const awarenessRef = useRef(null);
342
+ useEffect(() => {
343
+ const ytext = doc.getText("text");
344
+ const publish = throttle((state) => {
345
+ self?.setStatus(JSON.stringify({ sel: state.cursor ?? null }));
346
+ }, 120);
347
+ const awareness = new SeatAwareness(doc, publish);
348
+ awarenessRef.current = awareness;
349
+ const undoManager = new Y.UndoManager(ytext);
350
+ const view = new EditorView({
351
+ parent: host.current,
352
+ state: EditorState.create({
353
+ doc: ytext.toString(),
354
+ extensions: [
355
+ basicSetup,
356
+ languageFor(name),
357
+ yCollab(ytext, awareness, { undoManager }),
358
+ EditorState.readOnly.of(readOnly),
359
+ EditorView.editable.of(!readOnly),
360
+ EditorView.theme({
361
+ "&": { height: "100%", color: "var(--v-text)", backgroundColor: "var(--v-bg)" },
362
+ ".cm-scroller": { fontFamily: "var(--view-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)" },
363
+ ".cm-gutters": { backgroundColor: "var(--v-surface)", color: "var(--v-muted)", borderRight: "1px solid var(--v-border)" },
364
+ ".cm-activeLineGutter": { backgroundColor: "color-mix(in srgb, var(--v-accent) 18%, transparent)" },
365
+ ".cm-activeLine": { backgroundColor: "color-mix(in srgb, var(--v-accent) 8%, transparent)" },
366
+ ".cm-cursor": { borderLeftColor: "var(--v-text)" },
367
+ }),
368
+ ],
369
+ }),
370
+ });
371
+ return () => {
372
+ view.destroy();
373
+ undoManager.destroy();
374
+ awarenessRef.current = null;
375
+ };
376
+ }, [doc, name, readOnly, self]);
377
+ useEffect(() => {
378
+ const states = new Map();
379
+ for (const p of people) {
380
+ const sel = statusOf(p).sel;
381
+ const color = colorOf(p.profile?.email ?? p.id);
382
+ states.set(hashSeed(p.id) || 1, { user: { name: nameOf(p), color, colorLight: faded(color, 0.22) }, cursor: sel ?? null });
383
+ }
384
+ awarenessRef.current?.setRemote(states);
385
+ }, [people, doc]);
386
+ return _jsx("div", { className: "view-text", ref: host });
387
+ }
388
+ // ─── Tables ──────────────────────────────────────────────────────────────────
389
+ const ROW_PAGE = 500;
390
+ function columnName(index) {
391
+ let name = "";
392
+ let n = index + 1;
393
+ while (n > 0) {
394
+ const r = (n - 1) % 26;
395
+ name = String.fromCharCode(65 + r) + name;
396
+ n = Math.floor((n - 1) / 26);
397
+ }
398
+ return name;
399
+ }
400
+ function useDocTick(doc) {
401
+ const [tick, setTick] = useState(0);
402
+ useEffect(() => {
403
+ const bump = () => setTick((t) => t + 1);
404
+ doc.on("update", bump);
405
+ return () => doc.off("update", bump);
406
+ }, [doc]);
407
+ return tick;
408
+ }
409
+ function TableEditor({ doc, readOnly, self, people }) {
410
+ useDocTick(doc);
411
+ const [selected, setSelected] = useState(null);
412
+ const [shown, setShown] = useState(ROW_PAGE);
413
+ const columns = doc.getArray("columns").toArray();
414
+ const rows = doc.getArray("rows").toArray();
415
+ const cells = doc.getMap("cells");
416
+ const select = (cell) => {
417
+ setSelected(cell);
418
+ self?.setStatus(JSON.stringify({ cell }));
419
+ };
420
+ const others = new Map();
421
+ for (const p of people) {
422
+ const cell = statusOf(p).cell;
423
+ if (Array.isArray(cell) && cell.length === 2)
424
+ others.set(`${cell[0]}|${cell[1]}`, colorOf(p.profile?.email ?? p.id));
425
+ }
426
+ const setCell = (row, column, value) => {
427
+ let map = cells.get(row);
428
+ doc.transact(() => {
429
+ if (!map) {
430
+ map = new Y.Map();
431
+ cells.set(row, map);
432
+ }
433
+ if (value)
434
+ map.set(column, value);
435
+ else
436
+ map.delete(column);
437
+ });
438
+ };
439
+ return (_jsxs("div", { className: "view-table", children: [!readOnly && (_jsxs("div", { className: "view-tools", children: [_jsx("button", { onClick: () => { const row = Fauna.View.addRow(doc, selected?.[0]); select([row, selected?.[1] ?? columns[0]]); }, children: "Add row" }), _jsx("button", { onClick: () => { const column = Fauna.View.addColumn(doc, selected?.[1]); select([selected?.[0] ?? rows[0], column]); }, children: "Add column" }), _jsx("button", { disabled: !selected, onClick: () => { if (selected) {
440
+ Fauna.View.removeRow(doc, selected[0]);
441
+ select(null);
442
+ } }, children: "Remove row" }), _jsx("button", { disabled: !selected || columns.length < 2, onClick: () => { if (selected) {
443
+ Fauna.View.removeColumn(doc, selected[1]);
444
+ select(null);
445
+ } }, children: "Remove column" })] })), _jsxs("div", { className: "view-table-scroll", children: [_jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", {}), columns.map((column, c) => _jsx("th", { children: columnName(c) }, column))] }) }), _jsx("tbody", { children: rows.slice(0, shown).map((row, r) => (_jsxs("tr", { children: [_jsx("th", { children: r + 1 }), columns.map((column) => {
446
+ const mine = selected?.[0] === row && selected?.[1] === column;
447
+ const theirs = others.get(`${row}|${column}`);
448
+ return (_jsx("td", { className: mine ? "view-cell-mine" : undefined, style: theirs ? { outline: `2px solid ${theirs}`, outlineOffset: "-2px" } : undefined, children: _jsx("input", { value: cells.get(row)?.get(column) ?? "", readOnly: readOnly, onFocus: () => select([row, column]), onChange: (e) => setCell(row, column, e.target.value) }) }, column));
449
+ })] }, row))) })] }), rows.length > shown && (_jsxs("button", { className: "view-more", onClick: () => setShown((n) => n + ROW_PAGE), children: ["Showing ", shown, " of ", rows.length, " rows. Show more"] }))] })] }));
450
+ }
451
+ const TOOLS = [
452
+ { tool: "move", icon: "arrow_selector_tool", label: "Select" },
453
+ { tool: "pen", icon: "draw", label: "Pen" },
454
+ { tool: "box", icon: "crop_square", label: "Box" },
455
+ { tool: "arrow", icon: "north_east", label: "Arrow" },
456
+ { tool: "note", icon: "sticky_note_2", label: "Note" },
457
+ { tool: "pin", icon: "location_on", label: "Pin" },
458
+ ];
459
+ function markId() {
460
+ return Math.random().toString(36).slice(2, 10);
461
+ }
462
+ function ImageMarks({ doc, url, readOnly, owner, self, people }) {
463
+ useDocTick(doc);
464
+ const marks = doc.getArray("marks");
465
+ const [tool, setTool] = useState("move");
466
+ const [zoom, setZoom] = useState(null);
467
+ const [drawing, setDrawing] = useState(null);
468
+ const [picked, setPicked] = useState(null);
469
+ const [editing, setEditing] = useState(null);
470
+ const [natural, setNatural] = useState(null);
471
+ const box = useRef(null);
472
+ const me = self?.profile?.email ?? "";
473
+ const myColor = colorOf(me || self?.id || "");
474
+ const point = (e) => {
475
+ const rect = box.current.getBoundingClientRect();
476
+ return [Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width)), Math.min(1, Math.max(0, (e.clientY - rect.top) / rect.height))];
477
+ };
478
+ const share = useMemo(() => throttle((x, y) => self?.move([x, y, 0]), 60), [self]);
479
+ const indexOf = (id) => marks.toArray().findIndex((m) => m.id === id);
480
+ const replace = (mark) => {
481
+ const index = indexOf(mark.id);
482
+ doc.transact(() => {
483
+ if (index >= 0)
484
+ marks.delete(index, 1);
485
+ marks.insert(index >= 0 ? index : marks.length, [mark]);
486
+ });
487
+ };
488
+ const removable = (mark) => !readOnly && (mark.author === me || owner);
489
+ const down = (e) => {
490
+ if (readOnly || tool === "move") {
491
+ if (tool === "move")
492
+ setPicked(null);
493
+ return;
494
+ }
495
+ const p = point(e);
496
+ const mark = { id: markId(), kind: tool, points: [p, p], author: me, color: myColor, at: Date.now() };
497
+ if (tool === "note" || tool === "pin") {
498
+ marks.push([{ ...mark, points: [p], text: "" }]);
499
+ setEditing(mark.id);
500
+ setTool("move");
501
+ return;
502
+ }
503
+ ;
504
+ e.target.setPointerCapture?.(e.pointerId);
505
+ setDrawing(mark);
506
+ };
507
+ const moveOver = (e) => {
508
+ const p = point(e);
509
+ share(p[0], p[1]);
510
+ if (!drawing)
511
+ return;
512
+ setDrawing(drawing.kind === "pen" ? { ...drawing, points: [...drawing.points, p] } : { ...drawing, points: [drawing.points[0], p] });
513
+ };
514
+ const up = () => {
515
+ if (!drawing)
516
+ return;
517
+ const [a, b] = [drawing.points[0], drawing.points[drawing.points.length - 1]];
518
+ if (drawing.kind === "pen" ? drawing.points.length > 2 : Math.hypot(a[0] - b[0], a[1] - b[1]) > 0.005)
519
+ marks.push([drawing]);
520
+ setDrawing(null);
521
+ };
522
+ useEffect(() => {
523
+ const onKey = (e) => {
524
+ if (!picked || editing)
525
+ return;
526
+ if (e.key !== "Delete" && e.key !== "Backspace")
527
+ return;
528
+ const mark = marks.toArray().find((m) => m.id === picked);
529
+ if (mark && removable(mark)) {
530
+ marks.delete(indexOf(picked), 1);
531
+ setPicked(null);
532
+ }
533
+ };
534
+ window.addEventListener("keydown", onKey);
535
+ return () => window.removeEventListener("keydown", onKey);
536
+ });
537
+ const all = [...marks.toArray(), ...(drawing ? [drawing] : [])];
538
+ const pickedMark = picked ? marks.toArray().find((m) => m.id === picked) : undefined;
539
+ const width = natural ? (zoom === null ? undefined : natural.width * zoom) : undefined;
540
+ return (_jsxs("div", { className: "view-image", children: [_jsxs("div", { className: "view-tools", children: [!readOnly && TOOLS.map(({ tool: t, icon, label }) => (_jsx("button", { className: `material-symbols-outlined${tool === t ? " view-tool-on" : ""}`, title: label, "aria-label": label, onClick: () => setTool(t), children: icon }, t))), pickedMark && removable(pickedMark) && (_jsx("button", { onClick: () => { marks.delete(indexOf(pickedMark.id), 1); setPicked(null); }, children: "Remove mark" })), _jsx("span", { className: "view-tools-gap" }), _jsx("button", { className: "material-symbols-outlined", title: "Zoom out", "aria-label": "Zoom out", onClick: () => setZoom((z) => Math.max(0.1, (z ?? 1) / 1.25)), children: "zoom_out" }), _jsx("button", { className: zoom === null ? "view-tool-on" : undefined, onClick: () => setZoom(null), children: "Fit" }), _jsx("button", { className: zoom === 1 ? "view-tool-on" : undefined, onClick: () => setZoom(1), children: "100%" }), _jsx("button", { className: "material-symbols-outlined", title: "Zoom in", "aria-label": "Zoom in", onClick: () => setZoom((z) => Math.min(8, (z ?? 1) * 1.25)), children: "zoom_in" })] }), _jsx("div", { className: `view-image-stage${zoom === null ? " view-image-fit" : ""}`, children: _jsxs("div", { className: "view-image-box", ref: box, style: width ? { width } : undefined, onPointerDown: down, onPointerMove: moveOver, onPointerUp: up, onPointerLeave: () => self?.move([-1, -1, 0]), children: [_jsx("img", { src: url, alt: "", draggable: false, onLoad: (e) => setNatural({ width: e.currentTarget.naturalWidth, height: e.currentTarget.naturalHeight }) }), _jsxs("svg", { viewBox: "0 0 1 1", preserveAspectRatio: "none", className: "view-image-marks", children: [_jsx("defs", { children: all.filter((m) => m.kind === "arrow").map((m) => (_jsx("marker", { id: `view-arrow-${m.id}`, viewBox: "0 0 10 10", refX: "8", refY: "5", markerWidth: "5", markerHeight: "5", orient: "auto-start-reverse", children: _jsx("path", { d: "M0,0 L10,5 L0,10 z", fill: m.color }) }, m.id))) }), all.map((m) => {
541
+ const stroke = { stroke: m.color, strokeWidth: m.id === picked ? 4 : 2.5, fill: "none", vectorEffect: "non-scaling-stroke" };
542
+ const pick = (e) => { if (tool === "move") {
543
+ e.stopPropagation();
544
+ setPicked(m.id);
545
+ } };
546
+ if (m.kind === "pen")
547
+ return _jsx("polyline", { points: m.points.map((p) => p.join(",")).join(" "), ...stroke, onPointerDown: pick }, m.id);
548
+ if (m.kind === "box") {
549
+ const [a, b] = [m.points[0], m.points[m.points.length - 1]];
550
+ return _jsx("rect", { x: Math.min(a[0], b[0]), y: Math.min(a[1], b[1]), width: Math.abs(a[0] - b[0]), height: Math.abs(a[1] - b[1]), ...stroke, onPointerDown: pick }, m.id);
551
+ }
552
+ if (m.kind === "arrow") {
553
+ const [a, b] = [m.points[0], m.points[m.points.length - 1]];
554
+ return _jsx("line", { x1: a[0], y1: a[1], x2: b[0], y2: b[1], ...stroke, markerEnd: `url(#view-arrow-${m.id})`, onPointerDown: pick }, m.id);
555
+ }
556
+ return null;
557
+ })] }), all.filter((m) => m.kind === "note" || m.kind === "pin").map((m) => (_jsxs("div", { className: `view-mark-${m.kind}${m.id === picked ? " view-mark-picked" : ""}`, style: { left: `${m.points[0][0] * 100}%`, top: `${m.points[0][1] * 100}%`, borderColor: m.color, ...(m.kind === "pin" ? { color: m.color } : {}) }, onPointerDown: (e) => { e.stopPropagation(); if (tool === "move")
558
+ setPicked(m.id); }, onDoubleClick: () => { if (removable(m) || m.author === me)
559
+ setEditing(m.id); }, children: [m.kind === "pin" && _jsx("span", { className: "material-symbols-outlined", children: "location_on" }), editing === m.id
560
+ ? _jsx("textarea", { autoFocus: true, defaultValue: m.text ?? "", placeholder: "Write a note", onBlur: (e) => { replace({ ...m, text: e.target.value }); setEditing(null); }, onKeyDown: (e) => { if (e.key === "Enter" && !e.shiftKey)
561
+ e.target.blur(); } })
562
+ : m.text ? _jsx("span", { className: "view-mark-text", children: m.text }) : null] }, m.id))), people.map((p) => {
563
+ const [x, y] = p.position;
564
+ if (!(x >= 0 && x <= 1 && y >= 0 && y <= 1))
565
+ return null;
566
+ const color = colorOf(p.profile?.email ?? p.id);
567
+ return (_jsxs("div", { className: "view-pointer", style: { left: `${x * 100}%`, top: `${y * 100}%`, color }, children: [_jsx("span", { className: "view-pointer-dot", style: { background: color } }), _jsx("span", { className: "view-pointer-name", style: { background: color }, children: nameOf(p) })] }, p.id));
568
+ })] }) })] }));
569
+ }
570
+ // ─── Everything else ─────────────────────────────────────────────────────────
571
+ function Player({ url, failed, contentType, name, size, onDownload }) {
572
+ if (failed)
573
+ return _jsx("div", { className: "view-state", children: "Couldn't load this file." });
574
+ const card = (_jsxs("div", { className: "view-card", children: [_jsx("span", { className: "material-symbols-outlined", children: "draft" }), _jsx("div", { className: "view-card-name", children: name }), _jsxs("div", { className: "view-card-meta", children: [sizeLabel(size), ". There's no preview for this kind of file."] }), _jsx("button", { onClick: onDownload, children: "Download" })] }));
575
+ if (!contentType.startsWith("video/") && !contentType.startsWith("audio/") && contentType !== "application/pdf")
576
+ return card;
577
+ if (!url)
578
+ return _jsxs("div", { className: "view-state", children: ["Opening", "\u2026"] });
579
+ if (contentType.startsWith("video/"))
580
+ return _jsx("video", { className: "view-video", src: url, controls: true });
581
+ if (contentType.startsWith("audio/"))
582
+ return _jsx("div", { className: "view-audio", children: _jsx("audio", { src: url, controls: true }) });
583
+ return _jsx("iframe", { className: "view-pdf", src: url, title: name });
584
+ }