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.
- package/dist/brands.d.ts +2 -0
- package/dist/brands.js +2 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +121 -0
- package/dist/src/ant/chat.d.ts +130 -0
- package/dist/src/ant/chat.js +467 -0
- package/dist/src/ant/format.d.ts +40 -0
- package/dist/src/ant/format.js +143 -0
- package/dist/src/ant/graph.d.ts +130 -0
- package/dist/src/ant/graph.js +449 -0
- package/dist/src/ant/index.d.ts +16 -0
- package/dist/src/ant/index.js +61 -0
- package/dist/src/ant/persona.d.ts +151 -0
- package/dist/src/ant/persona.js +136 -0
- package/dist/src/ant/timeline.d.ts +283 -0
- package/dist/src/ant/timeline.js +1025 -0
- package/dist/src/authentification.d.ts +45 -0
- package/dist/src/authentification.js +194 -0
- package/dist/src/billing.d.ts +135 -0
- package/dist/src/billing.js +128 -0
- package/dist/src/calendar.d.ts +83 -0
- package/dist/src/calendar.js +133 -0
- package/dist/src/chats.d.ts +113 -0
- package/dist/src/chats.js +100 -0
- package/dist/src/cicada/contacts.d.ts +31 -0
- package/dist/src/cicada/contacts.js +28 -0
- package/dist/src/cicada/conversations.d.ts +73 -0
- package/dist/src/cicada/conversations.js +236 -0
- package/dist/src/cicada/inboxes.d.ts +48 -0
- package/dist/src/cicada/inboxes.js +82 -0
- package/dist/src/cicada/index.d.ts +16 -0
- package/dist/src/cicada/index.js +61 -0
- package/dist/src/cicada/phone.d.ts +48 -0
- package/dist/src/cicada/phone.js +306 -0
- package/dist/src/drive.d.ts +80 -0
- package/dist/src/drive.js +472 -0
- package/dist/src/extensions/index.d.ts +4 -0
- package/dist/src/extensions/index.js +5 -0
- package/dist/src/extensions/web/index.d.ts +6 -0
- package/dist/src/extensions/web/index.js +7 -0
- package/dist/src/extensions/web/session.d.ts +108 -0
- package/dist/src/extensions/web/session.js +470 -0
- package/dist/src/extensions/web/tools.d.ts +13 -0
- package/dist/src/extensions/web/tools.js +72 -0
- package/dist/src/friends.d.ts +39 -0
- package/dist/src/friends.js +119 -0
- package/dist/src/groups.d.ts +82 -0
- package/dist/src/groups.js +269 -0
- package/dist/src/inbox.d.ts +11 -0
- package/dist/src/inbox.js +35 -0
- package/dist/src/integrations.d.ts +52 -0
- package/dist/src/integrations.js +205 -0
- package/dist/src/magpie/explorer.d.ts +14 -0
- package/dist/src/magpie/explorer.js +36 -0
- package/dist/src/magpie/index.d.ts +16 -0
- package/dist/src/magpie/index.js +61 -0
- package/dist/src/magpie/portfolio.d.ts +52 -0
- package/dist/src/magpie/portfolio.js +179 -0
- package/dist/src/magpie/quotes.d.ts +35 -0
- package/dist/src/magpie/quotes.js +47 -0
- package/dist/src/magpie/room.d.ts +61 -0
- package/dist/src/magpie/room.js +238 -0
- package/dist/src/maps.d.ts +159 -0
- package/dist/src/maps.js +171 -0
- package/dist/src/marmot/browser.d.ts +139 -0
- package/dist/src/marmot/browser.js +389 -0
- package/dist/src/marmot/index.d.ts +20 -0
- package/dist/src/marmot/index.js +65 -0
- package/dist/src/marmot/media.d.ts +56 -0
- package/dist/src/marmot/media.js +265 -0
- package/dist/src/marmot/research.d.ts +337 -0
- package/dist/src/marmot/research.js +1138 -0
- package/dist/src/marmot/scraper.d.ts +59 -0
- package/dist/src/marmot/scraper.js +94 -0
- package/dist/src/marmot/web.d.ts +402 -0
- package/dist/src/marmot/web.js +2586 -0
- package/dist/src/marmot/workspace.d.ts +81 -0
- package/dist/src/marmot/workspace.js +263 -0
- package/dist/src/parrot/agent.d.ts +53 -0
- package/dist/src/parrot/agent.js +93 -0
- package/dist/src/parrot/chat.d.ts +84 -0
- package/dist/src/parrot/chat.js +237 -0
- package/dist/src/parrot/format.d.ts +128 -0
- package/dist/src/parrot/format.js +639 -0
- package/dist/src/parrot/generative.d.ts +261 -0
- package/dist/src/parrot/generative.js +185 -0
- package/dist/src/parrot/index.d.ts +22 -0
- package/dist/src/parrot/index.js +67 -0
- package/dist/src/parrot/room.d.ts +225 -0
- package/dist/src/parrot/room.js +466 -0
- package/dist/src/parrot/routing.d.ts +90 -0
- package/dist/src/parrot/routing.js +78 -0
- package/dist/src/parrot/share.d.ts +126 -0
- package/dist/src/parrot/share.js +261 -0
- package/dist/src/parrot/socket.d.ts +41 -0
- package/dist/src/parrot/socket.js +478 -0
- package/dist/src/payments.d.ts +73 -0
- package/dist/src/payments.js +107 -0
- package/dist/src/private/cards.css +75 -0
- package/dist/src/rooms.d.ts +76 -0
- package/dist/src/rooms.js +168 -0
- package/dist/src/shared/schema.d.ts +24 -0
- package/dist/src/shared/schema.js +1 -0
- package/dist/src/shared/server.d.ts +65 -0
- package/dist/src/shared/server.js +302 -0
- package/dist/src/termite/environment.d.ts +110 -0
- package/dist/src/termite/environment.js +263 -0
- package/dist/src/termite/index.d.ts +14 -0
- package/dist/src/termite/index.js +59 -0
- package/dist/src/termite/os.d.ts +326 -0
- package/dist/src/termite/os.js +572 -0
- package/dist/src/termite/versioning.d.ts +94 -0
- package/dist/src/termite/versioning.js +217 -0
- package/dist/src/tickets.d.ts +43 -0
- package/dist/src/tickets.js +138 -0
- package/dist/src/travel.d.ts +85 -0
- package/dist/src/travel.js +44 -0
- package/dist/src/ui/authentification.d.ts +24 -0
- package/dist/src/ui/authentification.js +177 -0
- package/dist/src/ui/brands.css +11 -0
- package/dist/src/ui/brands.d.ts +7 -0
- package/dist/src/ui/brands.js +14 -0
- package/dist/src/ui/browser.d.ts +74 -0
- package/dist/src/ui/browser.js +470 -0
- package/dist/src/ui/chat.d.ts +183 -0
- package/dist/src/ui/chat.js +2379 -0
- package/dist/src/ui/chatPanel.d.ts +109 -0
- package/dist/src/ui/chatPanel.js +768 -0
- package/dist/src/ui/chatSequence.d.ts +36 -0
- package/dist/src/ui/chatSequence.js +42 -0
- package/dist/src/ui/crown.d.ts +19 -0
- package/dist/src/ui/crown.js +199 -0
- package/dist/src/ui/cursor.d.ts +73 -0
- package/dist/src/ui/cursor.js +175 -0
- package/dist/src/ui/device.d.ts +33 -0
- package/dist/src/ui/device.js +49 -0
- package/dist/src/ui/drive.d.ts +69 -0
- package/dist/src/ui/drive.js +1243 -0
- package/dist/src/ui/generative.d.ts +39 -0
- package/dist/src/ui/generative.js +313 -0
- package/dist/src/ui/hover.d.ts +6 -0
- package/dist/src/ui/hover.js +210 -0
- package/dist/src/ui/index.d.ts +42 -0
- package/dist/src/ui/index.js +87 -0
- package/dist/src/ui/integrations.d.ts +13 -0
- package/dist/src/ui/integrations.js +175 -0
- package/dist/src/ui/maps.d.ts +87 -0
- package/dist/src/ui/maps.js +547 -0
- package/dist/src/ui/roster.d.ts +32 -0
- package/dist/src/ui/roster.js +163 -0
- package/dist/src/ui/talk.d.ts +12 -0
- package/dist/src/ui/talk.js +560 -0
- package/dist/src/ui/tickets.d.ts +13 -0
- package/dist/src/ui/tickets.js +966 -0
- package/dist/src/ui/toast.d.ts +7 -0
- package/dist/src/ui/toast.js +258 -0
- package/dist/src/ui/tokenText.d.ts +18 -0
- package/dist/src/ui/tokenText.js +96 -0
- package/dist/src/ui/tokenized.d.ts +42 -0
- package/dist/src/ui/tokenized.js +622 -0
- package/dist/src/ui/ui.css +4763 -0
- package/dist/src/ui/view.d.ts +23 -0
- package/dist/src/ui/view.js +584 -0
- package/dist/src/view.d.ts +113 -0
- package/dist/src/view.js +386 -0
- package/dist/src/webhooks.d.ts +66 -0
- package/dist/src/webhooks.js +78 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/package.json +87 -4
- 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
|
+
}
|