@ai-matrx/media 0.3.0 → 0.4.0

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,1747 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/viewers.ts
22
+ var viewers_exports = {};
23
+ __export(viewers_exports, {
24
+ CodeViewer: () => CodeViewer,
25
+ GenericViewer: () => GenericViewer,
26
+ HtmlViewer: () => HtmlViewer,
27
+ MediaFileViewer: () => MediaFileViewer,
28
+ MediaViewerUnavailable: () => MediaViewerUnavailable,
29
+ SvgViewer: () => SvgViewer,
30
+ TRANSPARENCY_GRID_STYLE: () => TRANSPARENCY_GRID_STYLE,
31
+ TextViewer: () => TextViewer,
32
+ ViewerCopyButton: () => ViewerCopyButton,
33
+ ViewerError: () => ViewerError,
34
+ ViewerSkeleton: () => ViewerSkeleton,
35
+ ViewerToolbar: () => ViewerToolbar,
36
+ detectCodeLanguage: () => detectCodeLanguage,
37
+ getMediaCodeHighlighter: () => getMediaCodeHighlighter,
38
+ getMediaViewer: () => getMediaViewer,
39
+ getPreviewKind: () => getPreviewKind,
40
+ registerMediaCodeHighlighter: () => registerMediaCodeHighlighter,
41
+ registerMediaViewer: () => registerMediaViewer,
42
+ registeredMediaViewerKinds: () => registeredMediaViewerKinds,
43
+ useViewerBytes: () => useViewerBytes,
44
+ useViewerDisplayUrl: () => useViewerDisplayUrl,
45
+ useViewerText: () => useViewerText
46
+ });
47
+ module.exports = __toCommonJS(viewers_exports);
48
+
49
+ // src/viewers/registry.ts
50
+ var VIEWER_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.media.viewer-registry");
51
+ var HIGHLIGHTER_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.media.code-highlighter");
52
+ function slots() {
53
+ return globalThis;
54
+ }
55
+ function viewerMap() {
56
+ const host = slots();
57
+ const existing = host[VIEWER_SLOT];
58
+ if (existing) return existing;
59
+ const created = /* @__PURE__ */ new Map();
60
+ host[VIEWER_SLOT] = created;
61
+ return created;
62
+ }
63
+ function registerMediaViewer(kind, viewer) {
64
+ const map = viewerMap();
65
+ const previous = map.get(kind);
66
+ map.set(kind, viewer);
67
+ return () => {
68
+ if (previous) map.set(kind, previous);
69
+ else map.delete(kind);
70
+ };
71
+ }
72
+ function getMediaViewer(kind) {
73
+ return viewerMap().get(kind) ?? null;
74
+ }
75
+ function registeredMediaViewerKinds() {
76
+ return [...viewerMap().keys()];
77
+ }
78
+ function registerMediaCodeHighlighter(highlighter) {
79
+ const host = slots();
80
+ const previous = host[HIGHLIGHTER_SLOT] ?? null;
81
+ host[HIGHLIGHTER_SLOT] = highlighter;
82
+ return () => {
83
+ host[HIGHLIGHTER_SLOT] = previous;
84
+ };
85
+ }
86
+ function getMediaCodeHighlighter() {
87
+ return slots()[HIGHLIGHTER_SLOT] ?? null;
88
+ }
89
+
90
+ // src/viewers/code-viewer.tsx
91
+ var import_react7 = require("react");
92
+
93
+ // src/react/cn.ts
94
+ var import_tailwind_merge = require("tailwind-merge");
95
+ function cn(...inputs) {
96
+ return (0, import_tailwind_merge.twMerge)(inputs.filter(Boolean).join(" "));
97
+ }
98
+
99
+ // src/viewers/chrome.tsx
100
+ var import_react = require("react");
101
+
102
+ // src/react/icons.tsx
103
+ var import_jsx_runtime = require("react/jsx-runtime");
104
+ function svgProps(props) {
105
+ return {
106
+ xmlns: "http://www.w3.org/2000/svg",
107
+ viewBox: "0 0 24 24",
108
+ width: 24,
109
+ height: 24,
110
+ fill: "none",
111
+ stroke: "currentColor",
112
+ strokeWidth: 2,
113
+ strokeLinecap: "round",
114
+ strokeLinejoin: "round",
115
+ "aria-hidden": true,
116
+ ...props
117
+ };
118
+ }
119
+ function ArchiveIcon(props) {
120
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
121
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: "20", height: "5", x: "2", y: "3", rx: "1" }),
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" }),
123
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 12h4" })
124
+ ] });
125
+ }
126
+ function BracesIcon(props) {
127
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1" }),
129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1" })
130
+ ] });
131
+ }
132
+ function CheckIcon(props) {
133
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...svgProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 6 9 17l-5-5" }) });
134
+ }
135
+ function CodeIcon(props) {
136
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
137
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m16 18 6-6-6-6" }),
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m8 6-6 6 6 6" })
139
+ ] });
140
+ }
141
+ function CopyIcon(props) {
142
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }),
144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
145
+ ] });
146
+ }
147
+ function DatabaseIcon(props) {
148
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "12", cy: "5", rx: "9", ry: "3" }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 5V19A9 3 0 0 0 21 19V5" }),
151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 12A9 3 0 0 0 21 12" })
152
+ ] });
153
+ }
154
+ function DownloadIcon(props) {
155
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 15V3" }),
157
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m7 10 5 5 5-5" })
159
+ ] });
160
+ }
161
+ function ExternalLinkIcon(props) {
162
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15 3h6v6" }),
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 14 21 3" }),
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" })
166
+ ] });
167
+ }
168
+ function FileIcon(props) {
169
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" }),
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2v5a1 1 0 0 0 1 1h5" })
172
+ ] });
173
+ }
174
+ function FileAudioIcon(props) {
175
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 6.835V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-.343" }),
177
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }),
178
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 19a2 2 0 0 1 4 0v1a2 2 0 0 1-4 0v-4a6 6 0 0 1 12 0v4a2 2 0 0 1-4 0v-1a2 2 0 0 1 4 0" })
179
+ ] });
180
+ }
181
+ function FileCodeIcon(props) {
182
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" }),
184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }),
185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 12.5 8 15l2 2.5" }),
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m14 12.5 2 2.5-2 2.5" })
187
+ ] });
188
+ }
189
+ function FileJsonIcon(props) {
190
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" }),
192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }),
193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1" }),
194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1" })
195
+ ] });
196
+ }
197
+ function FileSpreadsheetIcon(props) {
198
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" }),
200
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }),
201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 13h2" }),
202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 13h2" }),
203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 17h2" }),
204
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 17h2" })
205
+ ] });
206
+ }
207
+ function FileTextIcon(props) {
208
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" }),
210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }),
211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 9H8" }),
212
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M16 13H8" }),
213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M16 17H8" })
214
+ ] });
215
+ }
216
+ function FileTypeIcon(props) {
217
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
218
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" }),
219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }),
220
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M11 18h2" }),
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 12v6" }),
222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 13v-.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v.5" })
223
+ ] });
224
+ }
225
+ function FileVideoIcon(props) {
226
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" }),
228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2v5a1 1 0 0 0 1 1h5" }),
229
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15.033 13.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56v-4.704a.645.645 0 0 1 .967-.56z" })
230
+ ] });
231
+ }
232
+ function FolderIcon(props) {
233
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...svgProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" }) });
234
+ }
235
+ function FolderOpenIcon(props) {
236
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...svgProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2" }) });
237
+ }
238
+ function ImageIcon(props) {
239
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
241
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "9", cy: "9", r: "2" }),
242
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
243
+ ] });
244
+ }
245
+ function MusicIcon(props) {
246
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
247
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 18V5l12-2v13" }),
248
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "6", cy: "18", r: "3" }),
249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "18", cy: "16", r: "3" })
250
+ ] });
251
+ }
252
+ function PackageBoxIcon(props) {
253
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z" }),
255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 22V12" }),
256
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "3.29 7 12 12 20.71 7" }),
257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m7.5 4.27 9 5.15" })
258
+ ] });
259
+ }
260
+ function PenToolIcon(props) {
261
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15.707 21.293a1 1 0 0 1-1.414 0l-1.586-1.586a1 1 0 0 1 0-1.414l5.586-5.586a1 1 0 0 1 1.414 0l1.586 1.586a1 1 0 0 1 0 1.414z" }),
263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m18 13-1.375-6.874a1 1 0 0 0-.746-.776L3.235 2.028a1 1 0 0 0-1.207 1.207L5.35 15.879a1 1 0 0 0 .776.746L13 18" }),
264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m2.3 2.3 7.286 7.286" }),
265
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "11", cy: "11", r: "2" })
266
+ ] });
267
+ }
268
+ function SubtitlesIcon(props) {
269
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: "18", height: "14", x: "3", y: "5", rx: "2", ry: "2" }),
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M7 15h4M15 15h2M7 11h2M13 11h4" })
272
+ ] });
273
+ }
274
+ function VideoIcon(props) {
275
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
276
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5" }),
277
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "6", width: "14", height: "12", rx: "2" })
278
+ ] });
279
+ }
280
+ function XIcon(props) {
281
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }),
283
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })
284
+ ] });
285
+ }
286
+ function AlertCircleIcon(props) {
287
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
289
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12", y1: "8", y2: "12" }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12.01", y1: "16", y2: "16" })
291
+ ] });
292
+ }
293
+ function CheckCircle2Icon(props) {
294
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
295
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21.801 10A10 10 0 1 1 17 3.335" }),
296
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m9 11 3 3L22 4" })
297
+ ] });
298
+ }
299
+ function GlobeIcon(props) {
300
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
301
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" }),
303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 12h20" })
304
+ ] });
305
+ }
306
+ function SpinnerIcon(props) {
307
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...svgProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
308
+ }
309
+
310
+ // src/viewers/chrome.tsx
311
+ var import_jsx_runtime2 = require("react/jsx-runtime");
312
+ function ViewerSkeleton({
313
+ className,
314
+ tone = "muted"
315
+ }) {
316
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
317
+ "div",
318
+ {
319
+ className: cn(
320
+ "flex h-full w-full items-center justify-center",
321
+ tone === "muted" && "bg-muted/20",
322
+ className
323
+ ),
324
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-6 w-40 animate-pulse rounded bg-muted" })
325
+ }
326
+ );
327
+ }
328
+ function ViewerError({
329
+ title = "Couldn't load this file",
330
+ message,
331
+ hint,
332
+ className
333
+ }) {
334
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
335
+ "div",
336
+ {
337
+ className: cn(
338
+ "flex h-full w-full flex-col items-center justify-center gap-3 p-6 text-center",
339
+ className
340
+ ),
341
+ role: "alert",
342
+ children: [
343
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-destructive/10", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AlertCircleIcon, { className: "h-6 w-6 text-destructive" }) }),
344
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-1", children: [
345
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-sm font-semibold", children: title }),
346
+ message ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "max-w-md text-xs break-words text-muted-foreground", children: message }) : null,
347
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "max-w-md text-[11px] text-muted-foreground/80", children: hint }) : null
348
+ ] })
349
+ ]
350
+ }
351
+ );
352
+ }
353
+ function ViewerCopyButton({
354
+ text,
355
+ className
356
+ }) {
357
+ const [copied, setCopied] = (0, import_react.useState)(false);
358
+ const onCopy = (0, import_react.useCallback)(() => {
359
+ if (text == null) return;
360
+ void navigator.clipboard.writeText(text).then(() => {
361
+ setCopied(true);
362
+ setTimeout(() => setCopied(false), 1600);
363
+ }).catch(() => {
364
+ });
365
+ }, [text]);
366
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
367
+ "button",
368
+ {
369
+ type: "button",
370
+ onClick: onCopy,
371
+ "aria-label": "Copy text to clipboard",
372
+ title: "Copy text to clipboard",
373
+ className: cn(
374
+ "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground",
375
+ copied && "text-emerald-600 dark:text-emerald-400",
376
+ className
377
+ ),
378
+ children: [
379
+ copied ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CopyIcon, { className: "h-3.5 w-3.5" }),
380
+ copied ? "Copied" : "Copy"
381
+ ]
382
+ }
383
+ );
384
+ }
385
+ function ViewerToolbar({
386
+ children,
387
+ className
388
+ }) {
389
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
390
+ "div",
391
+ {
392
+ className: cn(
393
+ "flex shrink-0 items-center justify-between gap-2 border-b border-border/60 bg-muted/20 px-3 py-1 text-xs",
394
+ className
395
+ ),
396
+ children
397
+ }
398
+ );
399
+ }
400
+ function truncationNote(maxBytes) {
401
+ const mb = maxBytes / 1024 / 1024;
402
+ return `truncated to the first ${mb >= 1 ? `${mb.toFixed(0)}MB` : `${(maxBytes / 1024).toFixed(0)}KB`}`;
403
+ }
404
+
405
+ // src/viewers/use-viewer-bytes.ts
406
+ var import_react6 = require("react");
407
+
408
+ // src/core/use-media-blob.ts
409
+ var import_react4 = require("react");
410
+
411
+ // src/core/context.ts
412
+ var import_react2 = require("react");
413
+ var CLIENT_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.media.client-context");
414
+ var PORTS_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.media.host-ports-context");
415
+ function clientContext() {
416
+ const host = globalThis;
417
+ host[CLIENT_SLOT] ??= (0, import_react2.createContext)(null);
418
+ return host[CLIENT_SLOT];
419
+ }
420
+ var INERT_PORTS = {};
421
+ function portsContext() {
422
+ const host = globalThis;
423
+ host[PORTS_SLOT] ??= (0, import_react2.createContext)(INERT_PORTS);
424
+ return host[PORTS_SLOT];
425
+ }
426
+ function useMediaClient() {
427
+ const client = (0, import_react2.useContext)(clientContext());
428
+ if (!client) {
429
+ throw new Error(
430
+ "@ai-matrx/media: no MediaClient. Mount <MediaProvider client={...}> above every media surface (canonically wired to @ai-matrx/data/files)."
431
+ );
432
+ }
433
+ return client;
434
+ }
435
+ function useMediaHostPorts() {
436
+ return (0, import_react2.useContext)(portsContext());
437
+ }
438
+
439
+ // src/core/diagnostics.ts
440
+ var import_react3 = require("react");
441
+ var LATCH_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.media.failure-latch");
442
+ function latch() {
443
+ const host = globalThis;
444
+ host[LATCH_SLOT] ??= /* @__PURE__ */ new Set();
445
+ return host[LATCH_SLOT];
446
+ }
447
+ function failureMediaIdentity(ref, src) {
448
+ if (ref && typeof ref === "object" && ref.file_id) return `file:${ref.file_id}`;
449
+ const raw = typeof ref === "string" ? ref : (ref && typeof ref === "object" ? ref.url ?? null : null) ?? src ?? null;
450
+ if (!raw) return "<unknown-media>";
451
+ if (raw.startsWith("blob:")) return "blob:object-url";
452
+ try {
453
+ const u = new URL(raw);
454
+ return `url:${u.origin}${u.pathname}`;
455
+ } catch {
456
+ const clean = raw.split(/[?#]/, 1)[0] ?? raw;
457
+ return `ref:${clean}`;
458
+ }
459
+ }
460
+ function emitMediaFailure(capture, info) {
461
+ const key = `${info.terminal ? "terminal" : "warning"}|${info.mediaRef}`;
462
+ const seen = latch();
463
+ if (seen.has(key)) return;
464
+ seen.add(key);
465
+ if (capture) {
466
+ try {
467
+ capture(info);
468
+ return;
469
+ } catch {
470
+ }
471
+ }
472
+ console.error(
473
+ "[@ai-matrx/media] media failure (no diagnostics sink captured it \u2014 bind MediaHostPorts.diagnostics.capture to your error system)",
474
+ info
475
+ );
476
+ }
477
+ function useMediaFailureCapture() {
478
+ const ports = useMediaHostPorts();
479
+ const capture = ports.diagnostics?.capture;
480
+ const captureRef = (0, import_react3.useRef)(capture);
481
+ captureRef.current = capture;
482
+ return (0, import_react3.useCallback)((info) => {
483
+ emitMediaFailure(captureRef.current, { source: "media", ...info });
484
+ }, []);
485
+ }
486
+
487
+ // src/core/ref-key.ts
488
+ function stableRefKey(ref) {
489
+ if (!ref) return "<none>";
490
+ if (typeof ref === "string") return `s:${ref}`;
491
+ if (ref.file_id) return `id:${ref.file_id}:${ref.mime_type ?? ""}`;
492
+ if (ref.url) return `url:${ref.url}:${ref.mime_type ?? ""}`;
493
+ return "<empty-ref>";
494
+ }
495
+
496
+ // src/core/use-media-blob.ts
497
+ function statusOf(err) {
498
+ if (err && typeof err === "object") {
499
+ const candidate = err.status ?? err.statusCode;
500
+ if (typeof candidate === "number") return candidate;
501
+ }
502
+ return void 0;
503
+ }
504
+ function useMediaBlob(ref) {
505
+ const client = useMediaClient();
506
+ const capture = useMediaFailureCapture();
507
+ const refKey = stableRefKey(ref);
508
+ const [state, setState] = (0, import_react4.useState)({ url: null, blob: null, loading: !!ref, error: null });
509
+ const [retryToken, setRetryToken] = (0, import_react4.useState)(0);
510
+ (0, import_react4.useEffect)(() => {
511
+ if (!ref) {
512
+ setState({ url: null, blob: null, loading: false, error: null });
513
+ return void 0;
514
+ }
515
+ let cancelled = false;
516
+ let release = null;
517
+ setState({ url: null, blob: null, loading: true, error: null });
518
+ client.getBlob(ref).then(
519
+ (handle) => {
520
+ if (cancelled) {
521
+ handle.release();
522
+ return;
523
+ }
524
+ release = handle.release;
525
+ setState({ url: handle.url, blob: handle.blob, loading: false, error: null });
526
+ },
527
+ (err) => {
528
+ if (cancelled) return;
529
+ const message = err instanceof Error ? err.message : String(err ?? "Failed to load file");
530
+ setState({ url: null, blob: null, loading: false, error: message });
531
+ const status = statusOf(err);
532
+ capture({
533
+ phase: "blob",
534
+ mediaRef: failureMediaIdentity(ref),
535
+ terminal: true,
536
+ message: `getBlob() failed: ${message}`,
537
+ ...status !== void 0 ? { status } : {}
538
+ });
539
+ }
540
+ );
541
+ return () => {
542
+ cancelled = true;
543
+ release?.();
544
+ };
545
+ }, [client, refKey, retryToken]);
546
+ return {
547
+ ...state,
548
+ retry: () => setRetryToken((t) => t + 1)
549
+ };
550
+ }
551
+
552
+ // src/core/use-media-resolution.ts
553
+ var import_react5 = require("react");
554
+ function useMediaResolution(ref) {
555
+ const client = useMediaClient();
556
+ const capture = useMediaFailureCapture();
557
+ const refKey = stableRefKey(ref);
558
+ const { state, failureMessage } = (0, import_react5.useMemo)(() => {
559
+ if (!ref) {
560
+ return {
561
+ state: { resolution: null, status: "empty", reason: null },
562
+ failureMessage: null
563
+ };
564
+ }
565
+ try {
566
+ const resolution = client.resolve(ref);
567
+ if (!resolution) {
568
+ return {
569
+ state: { resolution: null, status: "empty", reason: null },
570
+ failureMessage: null
571
+ };
572
+ }
573
+ return {
574
+ state: { resolution, status: "ready", reason: null },
575
+ failureMessage: null
576
+ };
577
+ } catch (err) {
578
+ return {
579
+ state: {
580
+ resolution: null,
581
+ status: "unavailable",
582
+ reason: client.classifyError(err)
583
+ },
584
+ failureMessage: err instanceof Error ? err.message : String(err ?? "resolve() failed")
585
+ };
586
+ }
587
+ }, [client, refKey]);
588
+ (0, import_react5.useEffect)(() => {
589
+ if (state.status !== "unavailable") return;
590
+ capture({
591
+ phase: "resolve",
592
+ mediaRef: failureMediaIdentity(ref ?? null),
593
+ terminal: true,
594
+ message: `resolve() refused (${state.reason ?? "unknown"}): ${failureMessage ?? "no error message"}`
595
+ });
596
+ }, [refKey, state.status]);
597
+ return state;
598
+ }
599
+
600
+ // src/viewers/use-viewer-bytes.ts
601
+ function useViewerBytes(source) {
602
+ const fromClient = useMediaBlob(source.kind === "ref" ? source.ref : null);
603
+ if (source.kind === "blob") {
604
+ return { blob: source.blob, loading: false, error: null };
605
+ }
606
+ return {
607
+ blob: fromClient.blob,
608
+ loading: fromClient.loading,
609
+ error: fromClient.error
610
+ };
611
+ }
612
+ async function readBlobText(blob) {
613
+ const maybeText = blob.text;
614
+ if (typeof maybeText === "function") return await maybeText.call(blob);
615
+ const maybeArrayBuffer = blob.arrayBuffer;
616
+ if (typeof maybeArrayBuffer === "function") {
617
+ return new TextDecoder().decode(await maybeArrayBuffer.call(blob));
618
+ }
619
+ return await new Promise((resolve, reject) => {
620
+ const reader = new FileReader();
621
+ reader.onload = () => resolve(String(reader.result ?? ""));
622
+ reader.onerror = () => reject(reader.error ?? new Error("could not read the file's bytes"));
623
+ reader.readAsText(blob);
624
+ });
625
+ }
626
+ function useViewerText(source, maxBytes = 1024 * 1024) {
627
+ const { blob, loading, error: bytesError } = useViewerBytes(source);
628
+ const [text, setText] = (0, import_react6.useState)(null);
629
+ const [readError, setReadError] = (0, import_react6.useState)(null);
630
+ const [truncated, setTruncated] = (0, import_react6.useState)(false);
631
+ (0, import_react6.useEffect)(() => {
632
+ if (!blob) {
633
+ setText(null);
634
+ return void 0;
635
+ }
636
+ let cancelled = false;
637
+ setReadError(null);
638
+ setText(null);
639
+ setTruncated(false);
640
+ void (async () => {
641
+ try {
642
+ const oversized = blob.size > maxBytes;
643
+ const value = await readBlobText(
644
+ oversized ? blob.slice(0, maxBytes) : blob
645
+ );
646
+ if (cancelled) return;
647
+ setTruncated(oversized);
648
+ setText(value);
649
+ } catch (err) {
650
+ if (cancelled) return;
651
+ setReadError(err instanceof Error ? err.message : String(err));
652
+ }
653
+ })();
654
+ return () => {
655
+ cancelled = true;
656
+ };
657
+ }, [blob, maxBytes]);
658
+ const error = readError ?? bytesError;
659
+ return {
660
+ text,
661
+ error,
662
+ truncated,
663
+ loading: (loading || text === null) && error === null
664
+ };
665
+ }
666
+ function useViewerDisplayUrl(source) {
667
+ const resolved = useMediaResolution(
668
+ source.kind === "ref" ? source.ref : null
669
+ );
670
+ if (source.kind === "blob") {
671
+ return {
672
+ url: source.url,
673
+ recoverable: false,
674
+ unavailable: false,
675
+ loading: false
676
+ };
677
+ }
678
+ return {
679
+ url: resolved.resolution?.src ?? null,
680
+ recoverable: resolved.resolution?.recoverable !== false,
681
+ unavailable: resolved.status === "unavailable",
682
+ loading: resolved.status === "empty"
683
+ };
684
+ }
685
+
686
+ // src/viewers/code-viewer.tsx
687
+ var import_jsx_runtime3 = require("react/jsx-runtime");
688
+ var EXT_TO_LANGUAGE = {
689
+ js: "javascript",
690
+ jsx: "jsx",
691
+ mjs: "javascript",
692
+ cjs: "javascript",
693
+ ts: "typescript",
694
+ tsx: "tsx",
695
+ py: "python",
696
+ rb: "ruby",
697
+ go: "go",
698
+ rs: "rust",
699
+ java: "java",
700
+ kt: "kotlin",
701
+ swift: "swift",
702
+ c: "c",
703
+ h: "c",
704
+ cpp: "cpp",
705
+ cc: "cpp",
706
+ cxx: "cpp",
707
+ hpp: "cpp",
708
+ cs: "csharp",
709
+ php: "php",
710
+ scala: "scala",
711
+ html: "markup",
712
+ htm: "markup",
713
+ xml: "markup",
714
+ svg: "markup",
715
+ css: "css",
716
+ scss: "scss",
717
+ less: "less",
718
+ json: "json",
719
+ yaml: "yaml",
720
+ yml: "yaml",
721
+ toml: "toml",
722
+ ini: "ini",
723
+ sh: "bash",
724
+ bash: "bash",
725
+ zsh: "bash",
726
+ fish: "bash",
727
+ ps1: "powershell",
728
+ sql: "sql",
729
+ graphql: "graphql",
730
+ gql: "graphql",
731
+ proto: "protobuf",
732
+ dockerfile: "docker",
733
+ makefile: "makefile",
734
+ lua: "lua",
735
+ r: "r"
736
+ };
737
+ function extensionOf(fileName) {
738
+ const base = fileName.split(/[\\/]/).pop() ?? fileName;
739
+ const dot = base.lastIndexOf(".");
740
+ if (dot <= 0) return "";
741
+ return base.slice(dot + 1).toLowerCase();
742
+ }
743
+ function detectCodeLanguage(fileName) {
744
+ const ext = extensionOf(fileName);
745
+ if (ext) return EXT_TO_LANGUAGE[ext] ?? "text";
746
+ const lower = fileName.toLowerCase();
747
+ if (lower === "dockerfile") return "docker";
748
+ if (lower === "makefile") return "makefile";
749
+ return "text";
750
+ }
751
+ function CodeViewer({
752
+ source,
753
+ fileName,
754
+ className,
755
+ maxBytes = 1024 * 1024
756
+ }) {
757
+ const { text, error, loading, truncated } = useViewerText(source, maxBytes);
758
+ const [wrap, setWrap] = (0, import_react7.useState)(false);
759
+ const language = (0, import_react7.useMemo)(() => detectCodeLanguage(fileName), [fileName]);
760
+ const highlighter = getMediaCodeHighlighter();
761
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ViewerError, { message: error, className });
762
+ if (loading || text === null) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ViewerSkeleton, { className });
763
+ const lines = text.split("\n");
764
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
765
+ "div",
766
+ {
767
+ className: cn(
768
+ "flex h-full w-full flex-col overflow-hidden bg-card",
769
+ className
770
+ ),
771
+ children: [
772
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-border bg-muted/30 px-3 py-1.5", children: [
773
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs text-muted-foreground", children: [
774
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "font-mono tracking-wide uppercase", children: language }),
775
+ truncated ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-warning", children: [
776
+ "\xB7 ",
777
+ truncationNote(maxBytes)
778
+ ] }) : null
779
+ ] }),
780
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1", children: [
781
+ highlighter ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
782
+ "button",
783
+ {
784
+ type: "button",
785
+ onClick: () => setWrap((value) => !value),
786
+ "aria-pressed": wrap,
787
+ className: cn(
788
+ "rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground",
789
+ wrap && "bg-accent text-accent-foreground"
790
+ ),
791
+ children: "Wrap"
792
+ }
793
+ ),
794
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ViewerCopyButton, { text })
795
+ ] })
796
+ ] }),
797
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "min-h-0 flex-1 overflow-auto", children: highlighter ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: highlighter({ code: text, language }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("pre", { className: "m-0 flex p-3 font-mono text-xs leading-5", children: [
798
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
799
+ "code",
800
+ {
801
+ "aria-hidden": true,
802
+ className: "mr-3 shrink-0 border-r border-border/60 pr-3 text-right text-muted-foreground/60 select-none",
803
+ children: lines.map((_, index) => `${index + 1}
804
+ `).join("")
805
+ }
806
+ ),
807
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
808
+ "code",
809
+ {
810
+ className: cn(
811
+ "min-w-0 flex-1 text-foreground",
812
+ wrap ? "break-words whitespace-pre-wrap" : "whitespace-pre"
813
+ ),
814
+ children: text
815
+ }
816
+ )
817
+ ] }) })
818
+ ]
819
+ }
820
+ );
821
+ }
822
+
823
+ // src/core/file-kind.ts
824
+ var ROWS = [
825
+ [["jpg", "jpeg", "png", "gif", "webp", "avif", "heic", "heif", "bmp", "tif", "tiff", "ico"], "IMAGE", "text-emerald-500", "image", "Image"],
826
+ [["svg"], "IMAGE", "text-amber-500", "pen-tool", "SVG image"],
827
+ [["mp4", "mov", "webm", "mkv", "avi", "m4v"], "VIDEO", "text-purple-500", "video", "Video"],
828
+ [["mp3", "wav", "ogg", "m4a", "aac", "flac", "opus"], "AUDIO", "text-pink-500", "music", "Audio"],
829
+ [["pdf"], "DOCUMENT", "text-red-500", "file-type", "PDF Document"],
830
+ [["md", "markdown", "mdx", "rst", "adoc", "asciidoc", "org"], "DOCUMENT", "text-slate-400", "file-text", "Markdown"],
831
+ [["txt", "text", "asc", "me", "log", "out", "err"], "DOCUMENT", "text-muted-foreground", "file-text", "Text"],
832
+ [["dockerfile", "containerfile", "lua"], "CODE", "text-blue-500", "code", "Code"],
833
+ [["mk", "make", "swift", "ml", "mli", "fs", "fsi", "fsx", "svelte"], "CODE", "text-orange-500", "code", "Code"],
834
+ [["diff", "patch", "pem", "csr", "crt", "cer", "der", "key", "pub", "p7b", "p7c", "pfx", "p12"], "CODE", "text-amber-500", "file-text", "Text"],
835
+ [["ini", "cfg", "conf", "config", "properties", "prefs"], "CODE", "text-amber-400", "braces", "Config"],
836
+ [["tex", "latex", "ltx", "sty", "cls", "bib"], "CODE", "text-emerald-500", "file-text", "LaTeX"],
837
+ [["srt", "vtt"], "SUBTITLES", "text-cyan-500", "subtitles", "Subtitles"],
838
+ [["js", "mjs", "cjs", "jsx"], "CODE", "text-yellow-500", "file-code", "JavaScript"],
839
+ [["ts", "tsx"], "CODE", "text-blue-500", "file-code", "TypeScript"],
840
+ [["py", "clj", "cljs", "cljc", "edn", "vue", "styl", "stylus"], "CODE", "text-emerald-500", "code", "Code"],
841
+ [["rb", "rbw", "rake", "ru", "gemspec", "scala", "sbt"], "CODE", "text-red-500", "code", "Code"],
842
+ [["go", "dart"], "CODE", "text-cyan-500", "code", "Code"],
843
+ [["rs"], "CODE", "text-orange-600", "code", "Rust"],
844
+ [["java"], "CODE", "text-red-600", "code", "Java"],
845
+ [["c", "h"], "CODE", "text-blue-600", "code", "C"],
846
+ [["cpp", "cc", "cxx", "hpp"], "CODE", "text-blue-700", "code", "C++"],
847
+ [["cs"], "CODE", "text-purple-600", "code", "C#"],
848
+ [["html", "htm", "kt", "kts"], "CODE", "text-orange-400", "code", "Code"],
849
+ [["css"], "CODE", "text-sky-400", "code", "CSS"],
850
+ [["scss"], "CODE", "text-pink-400", "code", "SCSS"],
851
+ [["sh", "bash", "zsh", "fish", "ksh", "ash", "bat", "cmd", "twig", "jinja", "j2", "njk"], "CODE", "text-emerald-400", "code", "Script"],
852
+ [["sql"], "CODE", "text-amber-400", "code", "SQL"],
853
+ [["pl", "pm"], "CODE", "text-indigo-400", "code", "Perl"],
854
+ [["r", "rmd", "proto", "ps1", "psm1", "psd1", "less"], "CODE", "text-blue-400", "code", "Code"],
855
+ [["ex", "exs"], "CODE", "text-purple-400", "code", "Elixir"],
856
+ [["erl", "hrl"], "CODE", "text-rose-500", "code", "Erlang"],
857
+ [["hs", "lhs"], "CODE", "text-violet-500", "code", "Haskell"],
858
+ [["zig"], "CODE", "text-yellow-500", "code", "Zig"],
859
+ [["nim", "nims"], "CODE", "text-yellow-400", "code", "Nim"],
860
+ [["jl"], "CODE", "text-purple-500", "code", "Julia"],
861
+ [["astro"], "CODE", "text-fuchsia-500", "code", "Astro"],
862
+ [["graphql", "gql", "graphqls"], "CODE", "text-pink-500", "code", "GraphQL"],
863
+ [["sol"], "CODE", "text-slate-400", "code", "Solidity"],
864
+ [["php", "phtml", "phps"], "CODE", "text-indigo-500", "code", "PHP"],
865
+ [["hbs", "handlebars", "mustache", "ejs", "liquid"], "CODE", "text-amber-500", "code", "Template"],
866
+ [["json", "jsonc", "json5", "har", "geojson", "topojson"], "DATA", "text-amber-500", "file-json", "JSON"],
867
+ [["yaml", "yml", "toml"], "CODE", "text-amber-500", "braces", "Config"],
868
+ [["xml"], "DATA", "text-amber-500", "braces", "XML"],
869
+ [["csv", "tsv"], "DATA", "text-orange-500", "file-spreadsheet", "Spreadsheet data"],
870
+ [["sqlite", "sqlite3", "db"], "DATA", "text-blue-400", "database", "Database"],
871
+ [["ttl", "n3", "nt", "nq", "trig"], "DATA", "text-fuchsia-400", "braces", "RDF data"],
872
+ [["xlsx", "xls"], "DOCUMENT", "text-emerald-600", "file-spreadsheet", "Excel workbook"],
873
+ [["doc", "docx"], "DOCUMENT", "text-blue-500", "file-text", "Word document"],
874
+ [["ppt", "pptx"], "DOCUMENT", "text-orange-600", "file-type", "Presentation"],
875
+ [["ipynb"], "NOTEBOOK", "text-orange-400", "file-json", "Notebook"],
876
+ [["epub"], "EBOOK", "text-indigo-500", "file-text", "eBook"],
877
+ [["eml"], "EMAIL", "text-blue-400", "file-text", "Email"],
878
+ [["glb", "gltf", "stl", "obj", "fbx"], "MODEL_3D", "text-violet-400", "package", "3D model"],
879
+ [["zip", "rar", "7z"], "ARCHIVE", "text-amber-600", "archive", "Archive"],
880
+ [["tar", "gz", "tgz"], "ARCHIVE", "text-amber-600", "package", "Archive"]
881
+ ];
882
+ var BY_EXTENSION = /* @__PURE__ */ new Map();
883
+ for (const [extensions, category, colorClass, icon, displayName] of ROWS) {
884
+ for (const ext of extensions) {
885
+ BY_EXTENSION.set(ext, { category, colorClass, icon, displayName });
886
+ }
887
+ }
888
+ var UNKNOWN_DETAILS = {
889
+ category: "UNKNOWN",
890
+ displayName: "File",
891
+ colorClass: "text-muted-foreground",
892
+ icon: "file"
893
+ };
894
+ var ASSUMED_TEXT_DETAILS = {
895
+ category: "DOCUMENT",
896
+ displayName: "Text",
897
+ colorClass: "text-muted-foreground",
898
+ icon: "file-text"
899
+ };
900
+ var FOLDER_DETAILS = {
901
+ category: "FOLDER",
902
+ displayName: "Folder",
903
+ colorClass: "text-sky-500",
904
+ icon: "folder"
905
+ };
906
+ var IMAGE_DETAILS = BY_EXTENSION.get("png");
907
+ var VIDEO_DETAILS = BY_EXTENSION.get("mp4");
908
+ var AUDIO_DETAILS = BY_EXTENSION.get("mp3");
909
+ var PDF_DETAILS = BY_EXTENSION.get("pdf");
910
+ var SVG_DETAILS = BY_EXTENSION.get("svg");
911
+ function basenameOf(path) {
912
+ const idx = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
913
+ return idx >= 0 ? path.slice(idx + 1) : path;
914
+ }
915
+ function extOf(name) {
916
+ const idx = name.lastIndexOf(".");
917
+ if (idx <= 0 || idx === name.length - 1) return null;
918
+ return name.slice(idx + 1).toLowerCase();
919
+ }
920
+ function dotfileLooksLikeText(filename) {
921
+ const name = basenameOf(filename);
922
+ if (!name.startsWith(".")) return false;
923
+ for (let i = 1; i < name.length; i++) {
924
+ const c = name.charCodeAt(i);
925
+ if (c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95 || c === 45) {
926
+ return true;
927
+ }
928
+ }
929
+ return false;
930
+ }
931
+ function canonicalMimeBase(raw) {
932
+ return raw.split(";")[0]?.trim().toLowerCase() ?? raw;
933
+ }
934
+ function getFileKindDetails(fileName, mimeType) {
935
+ const name = basenameOf(fileName);
936
+ const lower = name.toLowerCase();
937
+ const ext = lower === "dockerfile" || lower === "containerfile" ? lower : extOf(name);
938
+ const fromExt = ext ? BY_EXTENSION.get(ext) : void 0;
939
+ const mime = mimeType ? canonicalMimeBase(mimeType) : null;
940
+ if (mime) {
941
+ if (mime === "image/svg+xml") return fromExt ?? SVG_DETAILS;
942
+ if (mime.startsWith("image/")) return fromExt?.category === "IMAGE" ? fromExt : IMAGE_DETAILS;
943
+ if (mime.startsWith("video/")) return fromExt?.category === "VIDEO" ? fromExt : VIDEO_DETAILS;
944
+ if (mime.startsWith("audio/")) return fromExt?.category === "AUDIO" ? fromExt : AUDIO_DETAILS;
945
+ if (mime === "application/pdf") return PDF_DETAILS;
946
+ }
947
+ if (fromExt) return fromExt;
948
+ if (dotfileLooksLikeText(fileName)) return ASSUMED_TEXT_DETAILS;
949
+ return UNKNOWN_DETAILS;
950
+ }
951
+ function getFolderKindDetails(open = false) {
952
+ return open ? { ...FOLDER_DETAILS, icon: "folder-open" } : FOLDER_DETAILS;
953
+ }
954
+
955
+ // src/react/file-icon.tsx
956
+ var import_jsx_runtime4 = require("react/jsx-runtime");
957
+ var ICONS = {
958
+ image: ImageIcon,
959
+ "pen-tool": PenToolIcon,
960
+ video: VideoIcon,
961
+ music: MusicIcon,
962
+ "file-type": FileTypeIcon,
963
+ "file-text": FileTextIcon,
964
+ code: CodeIcon,
965
+ braces: BracesIcon,
966
+ subtitles: SubtitlesIcon,
967
+ "file-code": FileCodeIcon,
968
+ "file-json": FileJsonIcon,
969
+ "file-spreadsheet": FileSpreadsheetIcon,
970
+ database: DatabaseIcon,
971
+ package: PackageBoxIcon,
972
+ archive: ArchiveIcon,
973
+ folder: FolderIcon,
974
+ "folder-open": FolderOpenIcon,
975
+ file: FileIcon,
976
+ "file-audio": FileAudioIcon,
977
+ "file-video": FileVideoIcon
978
+ };
979
+ function FileIcon2({
980
+ fileName,
981
+ mimeType = null,
982
+ isFolder,
983
+ open,
984
+ colorClass,
985
+ className,
986
+ size = 16
987
+ }) {
988
+ const details = isFolder ? getFolderKindDetails(open) : getFileKindDetails(fileName ?? "", mimeType);
989
+ const Icon = ICONS[details.icon];
990
+ const style = {
991
+ width: `${size}px`,
992
+ height: `${size}px`,
993
+ minWidth: `${size}px`
994
+ };
995
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
996
+ Icon,
997
+ {
998
+ style,
999
+ className: cn("shrink-0", colorClass ?? details.colorClass, className),
1000
+ "aria-hidden": "true"
1001
+ }
1002
+ );
1003
+ }
1004
+
1005
+ // src/react/upload-progress-list.tsx
1006
+ var import_react8 = require("react");
1007
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1008
+ var AUTO_DISMISS_MS = 1e4;
1009
+ function formatFileSize(bytes) {
1010
+ if (bytes == null || !Number.isFinite(bytes) || bytes < 0) return "\u2014";
1011
+ if (bytes < 1024) return `${bytes} B`;
1012
+ const units = ["KB", "MB", "GB", "TB"];
1013
+ let value = bytes;
1014
+ let unit = -1;
1015
+ do {
1016
+ value /= 1024;
1017
+ unit += 1;
1018
+ } while (value >= 1024 && unit < units.length - 1);
1019
+ return `${value >= 10 ? Math.round(value) : value.toFixed(1)} ${units[unit] ?? "TB"}`;
1020
+ }
1021
+ function UploadProgressListImpl({
1022
+ uploads,
1023
+ onDismiss,
1024
+ onOpenFile,
1025
+ className
1026
+ }) {
1027
+ const [hovered, setHovered] = (0, import_react8.useState)(false);
1028
+ const activeCount = uploads.filter((u) => u.status === "uploading").length;
1029
+ const successCount = uploads.filter((u) => u.status === "success").length;
1030
+ const errorCount = uploads.filter((u) => u.status === "error").length;
1031
+ const timersRef = (0, import_react8.useRef)(
1032
+ /* @__PURE__ */ new Map()
1033
+ );
1034
+ const onDismissRef = (0, import_react8.useRef)(onDismiss);
1035
+ onDismissRef.current = onDismiss;
1036
+ (0, import_react8.useEffect)(() => {
1037
+ const timers = timersRef.current;
1038
+ if (hovered) {
1039
+ timers.forEach((t) => clearTimeout(t));
1040
+ timers.clear();
1041
+ return;
1042
+ }
1043
+ for (const u of uploads) {
1044
+ if (u.status !== "success") continue;
1045
+ if (timers.has(u.id)) continue;
1046
+ const elapsed = u.completedAt ? Date.now() - u.completedAt : 0;
1047
+ const remaining = Math.max(0, AUTO_DISMISS_MS - elapsed);
1048
+ const handle = setTimeout(() => {
1049
+ timers.delete(u.id);
1050
+ onDismissRef.current(u.id);
1051
+ }, remaining);
1052
+ timers.set(u.id, handle);
1053
+ }
1054
+ const liveSuccessIds = new Set(
1055
+ uploads.filter((u) => u.status === "success").map((u) => u.id)
1056
+ );
1057
+ for (const [id, handle] of timers) {
1058
+ if (!liveSuccessIds.has(id)) {
1059
+ clearTimeout(handle);
1060
+ timers.delete(id);
1061
+ }
1062
+ }
1063
+ }, [uploads, hovered]);
1064
+ (0, import_react8.useEffect)(() => {
1065
+ const timers = timersRef.current;
1066
+ return () => {
1067
+ timers.forEach((t) => clearTimeout(t));
1068
+ timers.clear();
1069
+ };
1070
+ }, []);
1071
+ if (uploads.length === 0) return null;
1072
+ const headerLabel = activeCount > 0 ? `Uploading ${activeCount} file${activeCount === 1 ? "" : "s"}\u2026` : errorCount > 0 && successCount === 0 ? `${errorCount} upload${errorCount === 1 ? "" : "s"} failed` : `Uploaded ${successCount} file${successCount === 1 ? "" : "s"}`;
1073
+ const completedIds = uploads.filter((u) => u.status === "success" || u.status === "error").map((u) => u.id);
1074
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1075
+ "div",
1076
+ {
1077
+ className: cn(
1078
+ "overflow-hidden rounded-lg border bg-background/95 shadow-lg backdrop-blur",
1079
+ className
1080
+ ),
1081
+ onMouseEnter: () => setHovered(true),
1082
+ onMouseLeave: () => setHovered(false),
1083
+ "aria-label": "Uploads",
1084
+ children: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between gap-2 border-b bg-muted/40 px-3 py-1.5", children: [
1086
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "truncate text-xs font-medium text-foreground", children: headerLabel }),
1087
+ completedIds.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1088
+ "button",
1089
+ {
1090
+ type: "button",
1091
+ onClick: () => {
1092
+ for (const id of completedIds) onDismiss(id);
1093
+ },
1094
+ className: "shrink-0 rounded px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground",
1095
+ children: "Clear"
1096
+ }
1097
+ ) : null
1098
+ ] }),
1099
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "max-h-72 space-y-1 overflow-y-auto p-2", children: uploads.map((u) => {
1100
+ const percent = u.fileSize > 0 ? Math.min(100, Math.round(u.bytesUploaded / u.fileSize * 100)) : 0;
1101
+ const done = u.status === "success";
1102
+ const failed = u.status === "error";
1103
+ const canOpen = done && !!u.fileId && !!onOpenFile;
1104
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1105
+ "li",
1106
+ {
1107
+ className: cn(
1108
+ "flex items-center gap-2 rounded-md px-2 py-1.5 text-xs",
1109
+ canOpen && "cursor-pointer hover:bg-accent/60"
1110
+ ),
1111
+ onClick: canOpen ? () => onOpenFile(u.fileId) : void 0,
1112
+ children: [
1113
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FileIcon2, { fileName: u.fileName, size: 18, className: "shrink-0" }),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "min-w-0 flex-1", children: [
1115
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
1116
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "truncate font-medium text-foreground", children: u.fileName }),
1117
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "shrink-0 tabular-nums text-muted-foreground", children: failed ? "Failed" : done ? "Done" : `${percent}%` })
1118
+ ] }),
1119
+ done ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mt-1 h-1 w-full rounded bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1120
+ "div",
1121
+ {
1122
+ className: cn(
1123
+ "h-1 rounded transition-all",
1124
+ failed ? "bg-destructive" : "bg-primary"
1125
+ ),
1126
+ style: { width: failed ? "100%" : `${percent}%` }
1127
+ }
1128
+ ) }),
1129
+ failed && u.error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-0.5 truncate text-[10px] text-destructive", children: u.error }) : !done ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: [
1130
+ formatFileSize(u.bytesUploaded),
1131
+ " /",
1132
+ " ",
1133
+ formatFileSize(u.fileSize)
1134
+ ] }) : null
1135
+ ] }),
1136
+ canOpen ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex shrink-0 items-center gap-1 rounded px-1.5 py-0.5 text-[11px] font-medium text-primary", children: [
1137
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ExternalLinkIcon, { className: "h-3 w-3", width: 12, height: 12 }),
1138
+ "Open"
1139
+ ] }) : done ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1140
+ CheckCircle2Icon,
1141
+ {
1142
+ className: "h-4 w-4 shrink-0 text-emerald-500",
1143
+ width: 16,
1144
+ height: 16
1145
+ }
1146
+ ) : failed ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1147
+ AlertCircleIcon,
1148
+ {
1149
+ className: "h-4 w-4 shrink-0 text-destructive",
1150
+ width: 16,
1151
+ height: 16
1152
+ }
1153
+ ) : null,
1154
+ done || failed ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1155
+ "button",
1156
+ {
1157
+ type: "button",
1158
+ onClick: (e) => {
1159
+ e.stopPropagation();
1160
+ onDismiss(u.id);
1161
+ },
1162
+ "aria-label": "Dismiss",
1163
+ className: "flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground",
1164
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XIcon, { className: "h-3 w-3", width: 12, height: 12 })
1165
+ }
1166
+ ) : null
1167
+ ]
1168
+ },
1169
+ u.id
1170
+ );
1171
+ }) })
1172
+ ]
1173
+ }
1174
+ );
1175
+ }
1176
+ var UploadProgressList = (0, import_react8.memo)(UploadProgressListImpl);
1177
+
1178
+ // src/viewers/generic-viewer.tsx
1179
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1180
+ function GenericViewer({
1181
+ fileName,
1182
+ fileSize,
1183
+ generic,
1184
+ className
1185
+ }) {
1186
+ const onDownload = generic?.onDownload;
1187
+ const onViewAsText = generic?.onViewAsText;
1188
+ const viewAsTextLabel = generic?.viewAsTextLabel ?? "View as text";
1189
+ const viewAsTextPrimary = generic?.viewAsTextPrimary ?? true;
1190
+ const viewAsTextBusy = generic?.viewAsTextBusy ?? false;
1191
+ const hasViewAsText = typeof onViewAsText === "function";
1192
+ const hasDownload = typeof onDownload === "function";
1193
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1194
+ "div",
1195
+ {
1196
+ className: cn(
1197
+ "relative flex h-full w-full flex-col items-center justify-center gap-4 p-6 text-center",
1198
+ className
1199
+ ),
1200
+ children: [
1201
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FileIcon2, { fileName, size: 48 }),
1202
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1203
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "max-w-md truncate text-sm font-medium", children: fileName }),
1204
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-xs text-muted-foreground", children: formatFileSize(fileSize ?? null) })
1205
+ ] }),
1206
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "max-w-sm text-sm text-muted-foreground", children: generic?.message ?? (hasViewAsText ? "No built-in previewer for this file type." : "This file type can't be previewed here.") }),
1207
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
1208
+ onViewAsText && viewAsTextPrimary ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1209
+ ViewAsTextButton,
1210
+ {
1211
+ onClick: onViewAsText,
1212
+ label: viewAsTextLabel,
1213
+ busy: viewAsTextBusy,
1214
+ variant: "primary"
1215
+ }
1216
+ ) : null,
1217
+ onDownload ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1218
+ "button",
1219
+ {
1220
+ type: "button",
1221
+ onClick: onDownload,
1222
+ className: cn(
1223
+ "inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm",
1224
+ hasViewAsText && viewAsTextPrimary ? "border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground" : "bg-primary text-primary-foreground hover:bg-primary/90"
1225
+ ),
1226
+ children: [
1227
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DownloadIcon, { className: "h-4 w-4" }),
1228
+ "Download"
1229
+ ]
1230
+ }
1231
+ ) : null,
1232
+ onViewAsText && !viewAsTextPrimary ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1233
+ ViewAsTextButton,
1234
+ {
1235
+ onClick: onViewAsText,
1236
+ label: viewAsTextLabel,
1237
+ busy: viewAsTextBusy,
1238
+ variant: "secondary"
1239
+ }
1240
+ ) : null
1241
+ ] }),
1242
+ !hasDownload && !hasViewAsText ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "max-w-sm text-[11px] text-muted-foreground/70", children: "Download it from the file's actions menu to open it in another application." }) : null
1243
+ ]
1244
+ }
1245
+ );
1246
+ }
1247
+ function ViewAsTextButton({
1248
+ onClick,
1249
+ label,
1250
+ busy,
1251
+ variant
1252
+ }) {
1253
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1254
+ "button",
1255
+ {
1256
+ type: "button",
1257
+ onClick,
1258
+ disabled: busy,
1259
+ className: cn(
1260
+ "inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm transition-colors",
1261
+ "disabled:cursor-not-allowed disabled:opacity-60",
1262
+ variant === "primary" ? "bg-primary text-primary-foreground hover:bg-primary/90" : "border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground"
1263
+ ),
1264
+ children: [
1265
+ busy ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SpinnerIcon, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FileTextIcon, { className: "h-4 w-4" }),
1266
+ label
1267
+ ]
1268
+ }
1269
+ );
1270
+ }
1271
+
1272
+ // src/viewers/html-viewer.tsx
1273
+ var import_react10 = require("react");
1274
+
1275
+ // src/viewers/markup-chrome.tsx
1276
+ var import_react9 = require("react");
1277
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1278
+ function useMarkupMode(controls) {
1279
+ const [localMode, setLocalMode] = (0, import_react9.useState)("rendered");
1280
+ const controlled = controls?.mode !== void 0;
1281
+ return {
1282
+ mode: controls?.mode ?? localMode,
1283
+ setMode: controls?.onModeChange ?? setLocalMode,
1284
+ controlled
1285
+ };
1286
+ }
1287
+ function MarkupModeToggle({
1288
+ mode,
1289
+ onChange,
1290
+ renderedLabel = "Rendered",
1291
+ renderedIcon = "image",
1292
+ className
1293
+ }) {
1294
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: cn("flex items-center gap-0.5", className), children: [
1295
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1296
+ ToggleButton,
1297
+ {
1298
+ active: mode === "rendered",
1299
+ onClick: () => onChange("rendered"),
1300
+ label: renderedLabel,
1301
+ icon: renderedIcon === "globe" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GlobeIcon, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ImageIcon, { className: "h-3.5 w-3.5" })
1302
+ }
1303
+ ),
1304
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1305
+ ToggleButton,
1306
+ {
1307
+ active: mode === "source",
1308
+ onClick: () => onChange("source"),
1309
+ label: "Source",
1310
+ icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CodeIcon, { className: "h-3.5 w-3.5" })
1311
+ }
1312
+ )
1313
+ ] });
1314
+ }
1315
+ function ToggleButton({
1316
+ active,
1317
+ onClick,
1318
+ icon,
1319
+ label
1320
+ }) {
1321
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1322
+ "button",
1323
+ {
1324
+ type: "button",
1325
+ onClick,
1326
+ "aria-pressed": active,
1327
+ className: cn(
1328
+ "inline-flex items-center gap-1.5 rounded px-2 py-1 text-[11px] font-medium transition-colors",
1329
+ active ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
1330
+ ),
1331
+ children: [
1332
+ icon,
1333
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: label })
1334
+ ]
1335
+ }
1336
+ );
1337
+ }
1338
+
1339
+ // src/viewers/html-viewer.tsx
1340
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1341
+ var VIEWPORT_WIDTH = {
1342
+ mobile: 390,
1343
+ tablet: 768,
1344
+ desktop: 1280
1345
+ };
1346
+ function HtmlViewer(props) {
1347
+ const { className, fileName, markupControls } = props;
1348
+ const { mode, setMode, controlled } = useMarkupMode(markupControls);
1349
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("flex h-full w-full flex-col", className), children: [
1350
+ controlled ? null : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-border/60 bg-background px-3 py-1.5 text-xs", children: [
1351
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate font-medium text-foreground", children: fileName }),
1352
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1353
+ MarkupModeToggle,
1354
+ {
1355
+ mode,
1356
+ onChange: setMode,
1357
+ renderedLabel: "Rendered",
1358
+ renderedIcon: "globe"
1359
+ }
1360
+ )
1361
+ ] }),
1362
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "relative min-h-0 flex-1", children: mode === "rendered" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(HtmlRenderedView, { ...props }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(HtmlSourceView, { ...props }) })
1363
+ ] });
1364
+ }
1365
+ function HtmlRenderedView({ source, fileName, markupControls }) {
1366
+ const { url, unavailable } = useViewerDisplayUrl(source);
1367
+ const viewport = markupControls?.viewport ?? "auto";
1368
+ const reloadKey = markupControls?.reloadKey ?? 0;
1369
+ const [errored, setErrored] = (0, import_react10.useState)(false);
1370
+ (0, import_react10.useEffect)(() => {
1371
+ setErrored(false);
1372
+ }, [reloadKey, url]);
1373
+ if (unavailable) {
1374
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1375
+ ViewerError,
1376
+ {
1377
+ title: "This page isn't available",
1378
+ hint: "You may not have access to it, or it may have been deleted."
1379
+ }
1380
+ );
1381
+ }
1382
+ if (!url) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ViewerSkeleton, {});
1383
+ if (errored) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ViewerError, { title: "Preview unavailable" });
1384
+ const constrained = viewport !== "auto";
1385
+ const width = constrained ? VIEWPORT_WIDTH[viewport] : void 0;
1386
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1387
+ "div",
1388
+ {
1389
+ className: cn(
1390
+ "h-full w-full overflow-auto bg-muted/10",
1391
+ constrained && "flex items-start justify-center"
1392
+ ),
1393
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1394
+ "div",
1395
+ {
1396
+ className: cn(
1397
+ "h-full bg-background",
1398
+ constrained ? "my-4 overflow-hidden rounded-md border border-border/60 shadow-lg" : "w-full"
1399
+ ),
1400
+ style: constrained ? { width } : void 0,
1401
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1402
+ "iframe",
1403
+ {
1404
+ src: url,
1405
+ title: fileName,
1406
+ sandbox: "allow-scripts allow-popups allow-forms",
1407
+ referrerPolicy: "no-referrer",
1408
+ className: "h-full w-full border-0 bg-white",
1409
+ onError: () => setErrored(true)
1410
+ },
1411
+ `${reloadKey}-${url}`
1412
+ )
1413
+ }
1414
+ )
1415
+ }
1416
+ );
1417
+ }
1418
+ function HtmlSourceView({ source, maxBytes = 1024 * 1024 }) {
1419
+ const { text, error, loading } = useViewerText(source, maxBytes);
1420
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ViewerError, { title: "Couldn't load source", message: error });
1421
+ if (loading || text === null) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ViewerSkeleton, {});
1422
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("pre", { className: "h-full w-full overflow-auto bg-muted/10 p-4 font-mono text-xs leading-relaxed break-all whitespace-pre-wrap", children: text });
1423
+ }
1424
+
1425
+ // src/core/use-media-load-recovery.ts
1426
+ var import_react11 = require("react");
1427
+
1428
+ // src/types.ts
1429
+ function mintDurableSrc(url) {
1430
+ return url;
1431
+ }
1432
+
1433
+ // src/core/use-media-load-recovery.ts
1434
+ function normalizeOutcome(outcome) {
1435
+ return typeof outcome === "string" ? { verdict: outcome } : outcome;
1436
+ }
1437
+ var EPHEMERAL_SECRET_IDENTITY = "server:files-session-secret";
1438
+ function useMediaLoadRecovery(src, options) {
1439
+ const client = useMediaClient();
1440
+ const capture = useMediaFailureCapture();
1441
+ const recoverable = options?.recoverable !== false;
1442
+ const onTerminal = options?.onTerminal;
1443
+ const failureRef = options?.failureRef;
1444
+ const [result, setResult] = (0, import_react11.useState)({ source: src, retryKey: 0, failed: false });
1445
+ const attemptRef = (0, import_react11.useRef)(0);
1446
+ (0, import_react11.useEffect)(() => {
1447
+ attemptRef.current = 0;
1448
+ }, [src]);
1449
+ const identity = () => failureMediaIdentity(failureRef ?? null, src);
1450
+ const onLoadError = (event) => {
1451
+ if (!src || !recoverable) {
1452
+ setResult({ source: src, retryKey: 0, failed: true });
1453
+ if (src) {
1454
+ capture({
1455
+ phase: "element-load",
1456
+ mediaRef: identity(),
1457
+ terminal: true,
1458
+ retryOutcome: "not-attempted",
1459
+ message: "media element failed to load and the source is unrecoverable (foreign/object URL \u2014 a session refresh cannot fix it)"
1460
+ });
1461
+ }
1462
+ onTerminal?.(event);
1463
+ return;
1464
+ }
1465
+ attemptRef.current += 1;
1466
+ const attempt = attemptRef.current;
1467
+ client.recoverLoadError(mintDurableSrc(src), attempt).then(
1468
+ (outcome) => {
1469
+ const report = normalizeOutcome(outcome);
1470
+ if (report.secretSource === "ephemeral") {
1471
+ capture({
1472
+ phase: "session",
1473
+ mediaRef: EPHEMERAL_SECRET_IDENTITY,
1474
+ terminal: false,
1475
+ message: "server cookie secret is ephemeral \u2014 private renders will break across restarts/instances (set a persistent files-session secret on the server)"
1476
+ });
1477
+ }
1478
+ if (report.verdict === "retry") {
1479
+ setResult((current) => ({
1480
+ source: src,
1481
+ retryKey: current.source === src ? current.retryKey + 1 : 1,
1482
+ failed: false
1483
+ }));
1484
+ } else {
1485
+ setResult({ source: src, retryKey: 0, failed: true });
1486
+ capture({
1487
+ phase: attempt > 1 ? "element-load" : "recovery",
1488
+ mediaRef: identity(),
1489
+ terminal: true,
1490
+ retryOutcome: attempt > 1 ? "retried" : "not-attempted",
1491
+ message: attempt > 1 ? "media element failed to load after the session-refresh retry" : "media load recovery declined on the first attempt (session refresh did not produce a usable session)",
1492
+ ...report.status !== void 0 ? { status: report.status } : {}
1493
+ });
1494
+ onTerminal?.(event);
1495
+ }
1496
+ },
1497
+ (err) => {
1498
+ setResult({ source: src, retryKey: 0, failed: true });
1499
+ capture({
1500
+ phase: "session",
1501
+ mediaRef: identity(),
1502
+ terminal: true,
1503
+ retryOutcome: "recovery-failed",
1504
+ message: `session recovery failed: ${err instanceof Error ? err.message : String(err ?? "unknown error")}`
1505
+ });
1506
+ onTerminal?.(event);
1507
+ }
1508
+ );
1509
+ };
1510
+ return {
1511
+ retryKey: result.source === src ? result.retryKey : 0,
1512
+ onLoadError,
1513
+ failed: result.source === src ? result.failed : false
1514
+ };
1515
+ }
1516
+
1517
+ // src/viewers/transparency-grid.ts
1518
+ var TRANSPARENCY_GRID_STYLE = {
1519
+ backgroundImage: "linear-gradient(45deg, rgb(0 0 0 / 0.08) 25%, transparent 25%), linear-gradient(-45deg, rgb(0 0 0 / 0.08) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgb(0 0 0 / 0.08) 75%), linear-gradient(-45deg, transparent 75%, rgb(0 0 0 / 0.08) 75%)",
1520
+ backgroundSize: "16px 16px",
1521
+ backgroundPosition: "0 0, 0 8px, 8px -8px, -8px 0"
1522
+ };
1523
+
1524
+ // src/viewers/svg-viewer.tsx
1525
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1526
+ function SvgViewer(props) {
1527
+ const { className, markupControls } = props;
1528
+ const { mode, setMode, controlled } = useMarkupMode(markupControls);
1529
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1530
+ "div",
1531
+ {
1532
+ className: cn(
1533
+ "relative flex h-full min-h-0 w-full flex-col bg-background",
1534
+ className
1535
+ ),
1536
+ children: [
1537
+ controlled ? null : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1538
+ MarkupModeToggle,
1539
+ {
1540
+ mode,
1541
+ onChange: setMode,
1542
+ className: "absolute top-3 right-3 z-10 rounded-md border border-border bg-background/95 p-0.5 shadow-sm backdrop-blur-sm"
1543
+ }
1544
+ ),
1545
+ mode === "rendered" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SvgRenderedView, { ...props }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SvgSourceView, { ...props })
1546
+ ]
1547
+ }
1548
+ );
1549
+ }
1550
+ function SvgRenderedView({ source, fileName }) {
1551
+ const { url, recoverable, unavailable } = useViewerDisplayUrl(source);
1552
+ const { retryKey, onLoadError, failed } = useMediaLoadRecovery(url, {
1553
+ recoverable,
1554
+ ...source.kind === "ref" ? { failureRef: source.ref } : {}
1555
+ });
1556
+ if (unavailable) {
1557
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1558
+ ViewerError,
1559
+ {
1560
+ title: "This SVG isn't available",
1561
+ hint: "You may not have access to it, or it may have been deleted."
1562
+ }
1563
+ );
1564
+ }
1565
+ if (!url) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ViewerSkeleton, { className: "flex-1" });
1566
+ if (failed) {
1567
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1568
+ ViewerError,
1569
+ {
1570
+ title: "SVG could not be rendered",
1571
+ hint: "Switch to the Source view to inspect the markup."
1572
+ }
1573
+ );
1574
+ }
1575
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1576
+ "div",
1577
+ {
1578
+ className: "flex flex-1 items-center justify-center overflow-auto p-4",
1579
+ style: TRANSPARENCY_GRID_STYLE,
1580
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1581
+ "img",
1582
+ {
1583
+ src: url,
1584
+ alt: fileName,
1585
+ className: "max-h-full max-w-full object-contain drop-shadow-sm",
1586
+ onError: onLoadError
1587
+ },
1588
+ retryKey
1589
+ )
1590
+ }
1591
+ );
1592
+ }
1593
+ function SvgSourceView({ source, maxBytes = 1024 * 1024 }) {
1594
+ const { text, error, loading } = useViewerText(source, maxBytes);
1595
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ViewerError, { title: "Could not load source", message: error });
1596
+ if (loading || text === null) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ViewerSkeleton, { className: "flex-1" });
1597
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pre", { className: "m-0 flex-1 overflow-auto bg-muted/20 px-4 py-3 font-mono text-xs leading-relaxed break-words whitespace-pre-wrap text-foreground", children: text });
1598
+ }
1599
+
1600
+ // src/viewers/text-viewer.tsx
1601
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1602
+ function TextViewer({
1603
+ source,
1604
+ className,
1605
+ maxBytes = 1024 * 1024
1606
+ }) {
1607
+ const { text, error, loading, truncated } = useViewerText(source, maxBytes);
1608
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ViewerError, { message: error, className });
1609
+ if (loading || text === null) {
1610
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ViewerSkeleton, { tone: "plain", className });
1611
+ }
1612
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1613
+ "div",
1614
+ {
1615
+ className: cn("flex h-full w-full flex-col overflow-hidden", className),
1616
+ children: [
1617
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ViewerToolbar, { children: [
1618
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-muted-foreground", children: truncated ? `Preview ${truncationNote(maxBytes)}.` : `${text.length.toLocaleString()} characters` }),
1619
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ViewerCopyButton, { text })
1620
+ ] }),
1621
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pre", { className: "flex-1 overflow-auto bg-background p-3 font-mono text-xs leading-5 break-words whitespace-pre-wrap", children: text })
1622
+ ]
1623
+ }
1624
+ );
1625
+ }
1626
+
1627
+ // src/viewers/unavailable-viewer.tsx
1628
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1629
+ var ENGINE_FOR_KIND = {
1630
+ pdf: "a PDF engine (e.g. PDF.js / react-pdf)",
1631
+ markdown: "a Markdown renderer",
1632
+ data: "a spreadsheet/CSV parser",
1633
+ spreadsheet: "a spreadsheet/CSV parser",
1634
+ office: "an Office document extractor",
1635
+ image: "an image viewer",
1636
+ video: "a video player",
1637
+ audio: "an audio player"
1638
+ };
1639
+ function MediaViewerUnavailable({
1640
+ kind,
1641
+ ...props
1642
+ }) {
1643
+ const engine = ENGINE_FOR_KIND[kind] ?? `a "${kind}" viewer`;
1644
+ const generic = props.generic;
1645
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: cn("flex h-full w-full flex-col", props.className), children: [
1646
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "shrink-0 border-b border-amber-500/30 bg-amber-500/10 px-3 py-2 text-center text-xs text-amber-700 dark:text-amber-300", children: [
1647
+ "No ",
1648
+ kind,
1649
+ " viewer is registered in this app, so this file can't be displayed here. An app developer registers ",
1650
+ engine,
1651
+ " with",
1652
+ " ",
1653
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("code", { className: "font-mono", children: [
1654
+ 'registerMediaViewer("',
1655
+ kind,
1656
+ '", \u2026)'
1657
+ ] }),
1658
+ "."
1659
+ ] }),
1660
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1661
+ GenericViewer,
1662
+ {
1663
+ ...props,
1664
+ className: void 0,
1665
+ generic: {
1666
+ ...generic,
1667
+ message: generic?.message ?? "You can still download this file and open it in another application."
1668
+ }
1669
+ }
1670
+ ) })
1671
+ ] });
1672
+ }
1673
+
1674
+ // src/viewers/preview-kind.ts
1675
+ var EXTENSION_KIND = {
1676
+ svg: "svg",
1677
+ pdf: "pdf",
1678
+ md: "markdown",
1679
+ markdown: "markdown",
1680
+ mdx: "markdown",
1681
+ html: "html",
1682
+ htm: "html",
1683
+ csv: "data",
1684
+ tsv: "data",
1685
+ xlsx: "spreadsheet",
1686
+ xls: "spreadsheet",
1687
+ doc: "office",
1688
+ docx: "office",
1689
+ ppt: "office",
1690
+ pptx: "office",
1691
+ txt: "text",
1692
+ log: "text",
1693
+ text: "text"
1694
+ };
1695
+ function extensionOf2(fileName) {
1696
+ const base = fileName.split(/[\\/]/).pop() ?? fileName;
1697
+ const dot = base.lastIndexOf(".");
1698
+ if (dot <= 0) return "";
1699
+ return base.slice(dot + 1).toLowerCase();
1700
+ }
1701
+ function getPreviewKind(fileName, mimeType) {
1702
+ const explicit = EXTENSION_KIND[extensionOf2(fileName)];
1703
+ if (explicit) return explicit;
1704
+ const details = getFileKindDetails(fileName, mimeType ?? void 0);
1705
+ switch (details.category) {
1706
+ case "IMAGE":
1707
+ return details.icon === "pen-tool" ? "svg" : "image";
1708
+ case "VIDEO":
1709
+ return "video";
1710
+ case "AUDIO":
1711
+ return "audio";
1712
+ case "CODE":
1713
+ return "code";
1714
+ case "DATA":
1715
+ return "code";
1716
+ case "NOTEBOOK":
1717
+ return "code";
1718
+ case "SUBTITLES":
1719
+ return "text";
1720
+ case "DOCUMENT":
1721
+ return "text";
1722
+ default:
1723
+ return "generic";
1724
+ }
1725
+ }
1726
+
1727
+ // src/viewers/media-file-viewer.tsx
1728
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1729
+ var BUILT_IN = {
1730
+ text: TextViewer,
1731
+ code: CodeViewer,
1732
+ svg: SvgViewer,
1733
+ html: HtmlViewer,
1734
+ generic: GenericViewer
1735
+ };
1736
+ function MediaFileViewer({
1737
+ previewKind,
1738
+ ...props
1739
+ }) {
1740
+ const kind = previewKind ?? getPreviewKind(props.fileName, props.mimeType ?? null);
1741
+ const registered = getMediaViewer(kind);
1742
+ if (registered) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_jsx_runtime12.Fragment, { children: registered(props) });
1743
+ const builtIn = BUILT_IN[kind];
1744
+ if (builtIn) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_jsx_runtime12.Fragment, { children: builtIn(props) });
1745
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MediaViewerUnavailable, { ...props, kind });
1746
+ }
1747
+ //# sourceMappingURL=viewers.cjs.map