@openrails/contentkit-ui 0.72.0 → 0.74.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.
Files changed (73) hide show
  1. package/README.md +52 -0
  2. package/dist/client/client.d.ts +3 -0
  3. package/dist/client/client.js +20 -18
  4. package/dist/client/client.js.map +1 -1
  5. package/dist/client/generated/routes.d.ts +66 -1
  6. package/dist/client/generated/routes.js +85 -0
  7. package/dist/client/generated/routes.js.map +1 -1
  8. package/dist/client/generated/wire.d.ts +30 -0
  9. package/dist/client/generated/wire.js.map +1 -1
  10. package/dist/client/http.d.ts +8 -1
  11. package/dist/client/http.js.map +1 -1
  12. package/dist/client/index.d.ts +2 -1
  13. package/dist/client/media/queue.d.ts +1 -1
  14. package/dist/client/media/queue.js +1 -1
  15. package/dist/client/media/queue.js.map +1 -1
  16. package/dist/client/notifications.d.ts +23 -0
  17. package/dist/client/notifications.js +46 -0
  18. package/dist/client/notifications.js.map +1 -0
  19. package/dist/client.js +5 -4
  20. package/dist/components/admin/media-item-panel.d.ts +27 -0
  21. package/dist/components/admin/media-item-panel.js +397 -0
  22. package/dist/components/admin/media-item-panel.js.map +1 -0
  23. package/dist/components/admin/upload-queue.d.ts +23 -0
  24. package/dist/components/admin/upload-queue.js +365 -0
  25. package/dist/components/admin/upload-queue.js.map +1 -0
  26. package/dist/components/notifications.d.ts +28 -0
  27. package/dist/components/notifications.js +222 -0
  28. package/dist/components/notifications.js.map +1 -0
  29. package/dist/index.d.ts +3 -0
  30. package/dist/index.js +5 -2
  31. package/dist/locales/admin/de.js +51 -0
  32. package/dist/locales/admin/de.js.map +1 -1
  33. package/dist/locales/admin/en.d.ts +51 -0
  34. package/dist/locales/admin/en.js +51 -0
  35. package/dist/locales/admin/en.js.map +1 -1
  36. package/dist/locales/admin/es.js +51 -0
  37. package/dist/locales/admin/es.js.map +1 -1
  38. package/dist/locales/admin/ja.js +51 -0
  39. package/dist/locales/admin/ja.js.map +1 -1
  40. package/dist/locales/admin/ko.js +51 -0
  41. package/dist/locales/admin/ko.js.map +1 -1
  42. package/dist/locales/admin/zh.js +51 -0
  43. package/dist/locales/admin/zh.js.map +1 -1
  44. package/dist/locales/en.d.ts +74 -0
  45. package/dist/locales/social/de.js +23 -0
  46. package/dist/locales/social/de.js.map +1 -1
  47. package/dist/locales/social/en.d.ts +23 -0
  48. package/dist/locales/social/en.js +23 -0
  49. package/dist/locales/social/en.js.map +1 -1
  50. package/dist/locales/social/es.js +23 -0
  51. package/dist/locales/social/es.js.map +1 -1
  52. package/dist/locales/social/ja.js +23 -0
  53. package/dist/locales/social/ja.js.map +1 -1
  54. package/dist/locales/social/ko.js +23 -0
  55. package/dist/locales/social/ko.js.map +1 -1
  56. package/dist/locales/social/zh.js +23 -0
  57. package/dist/locales/social/zh.js.map +1 -1
  58. package/dist/react/context.d.ts +1 -1
  59. package/dist/react/context.js.map +1 -1
  60. package/dist/react/folder.js.map +1 -1
  61. package/dist/react/index.d.ts +1 -0
  62. package/dist/react/notifications.d.ts +29 -0
  63. package/dist/react/notifications.js +56 -0
  64. package/dist/react/notifications.js.map +1 -0
  65. package/dist/react/resources.d.ts +1 -0
  66. package/dist/react/resources.js +26 -0
  67. package/dist/react/resources.js.map +1 -1
  68. package/dist/react/upload.d.ts +10 -10
  69. package/dist/react/upload.js +1 -1
  70. package/dist/react/upload.js.map +1 -1
  71. package/dist/react.js +4 -3
  72. package/dist/styles.css +1 -1
  73. package/package.json +1 -1
@@ -0,0 +1,397 @@
1
+ import { toContentKitError as e } from "../../client/errors.js";
2
+ import { samePath as t } from "../../client/media/client.js";
3
+ import { useMessages as n } from "../../i18n/context.js";
4
+ import { useErrorReporter as r, useGuarded as i } from "../../react/context.js";
5
+ import { EncodeProgress as a } from "../encode-progress.js";
6
+ import { isProcessing as o } from "../../client/media/windows.js";
7
+ import { useMediaRead as s } from "../../react/read.js";
8
+ import { usePublicImage as c } from "../../react/public.js";
9
+ import { Alert as l, AlertDescription as u } from "../../ui/alert.js";
10
+ import { Button as d } from "../../ui/button.js";
11
+ import { SlotImage as f } from "../slot-image.js";
12
+ import { VideoPosterPicker as p } from "../video-poster-picker.js";
13
+ import { Badge as m } from "../../ui/badge.js";
14
+ import { Input as h } from "../../ui/input.js";
15
+ import { Label as g } from "../../ui/label.js";
16
+ import { Select as _, SelectContent as v, SelectItem as y, SelectTrigger as b, SelectValue as x } from "../../ui/select.js";
17
+ import { ErrorLine as S, useAction as C } from "../social/parts.js";
18
+ import { AdminRoot as w, LoadFailed as T, NoAccess as E, Rows as D, useConfirm as O } from "./shell.js";
19
+ import { Switch as k } from "../../ui/switch.js";
20
+ import { useFlowClient as A } from "../../react/admin.js";
21
+ import { useId as j, useRef as M, useState as N } from "react";
22
+ import { Fragment as P, jsx as F, jsxs as I } from "react/jsx-runtime";
23
+ //#region src/components/admin/media-item-panel.tsx
24
+ var L = (e) => (e ?? []).map((e) => typeof e == "string" ? {
25
+ value: e,
26
+ label: e
27
+ } : e), R = (e) => e.slice(0, e.indexOf("{name}"));
28
+ function z({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className: a, language: o, messages: s, ...c }) {
29
+ let l = A({
30
+ client: e,
31
+ baseUrl: t,
32
+ auth: n
33
+ });
34
+ return /* @__PURE__ */ F(w, {
35
+ flow: "media-item-panel",
36
+ appearance: i,
37
+ className: a,
38
+ language: o,
39
+ messages: s,
40
+ guard: r,
41
+ children: /* @__PURE__ */ F(B, {
42
+ ...c,
43
+ client: l
44
+ })
45
+ });
46
+ }
47
+ function B({ item: e, client: r, source: i = "source", subtitles: a = "subs/{name}", poster: c = "poster", posterPreset: f = "poster", languages: p, onError: h }) {
48
+ let g = n(), { t: _ } = g, v = s(e, {
49
+ editor: !0,
50
+ client: r
51
+ }), y = C("media.save", h);
52
+ if (!v.read) return v.error?.code === "not_found" ? /* @__PURE__ */ F(E, { children: _("mediaPanel.noAccess") }) : v.error ? /* @__PURE__ */ F(T, {
53
+ error: v.error,
54
+ message: _("mediaPanel.loadFailed"),
55
+ retry: v.reload
56
+ }) : /* @__PURE__ */ F(D, { count: 2 });
57
+ if (!v.read.uploads) return /* @__PURE__ */ F(E, { children: _("mediaPanel.noAccess") });
58
+ let b = v.read.files.filter((e) => e.upload && !e.unattached), x = b.find((e) => t(e.path, i)), w = b.filter((e) => e.failed), O = x ? w.length ? "failed" : b.some(o) ? "processing" : "ready" : "empty", k = a === null ? [] : b.filter((e) => e.path.startsWith(R(a)));
59
+ return /* @__PURE__ */ I(P, { children: [
60
+ /* @__PURE__ */ I("div", {
61
+ className: "flex flex-wrap items-center gap-2",
62
+ children: [
63
+ /* @__PURE__ */ F("h2", {
64
+ className: "text-base font-semibold",
65
+ children: _("mediaPanel.title")
66
+ }),
67
+ /* @__PURE__ */ F(m, {
68
+ variant: O === "failed" ? "destructive" : O === "ready" ? "outline" : "secondary",
69
+ "data-ckui": "media-state",
70
+ "data-state": O,
71
+ children: _(`mediaPanel.${O}`)
72
+ }),
73
+ /* @__PURE__ */ F(d, {
74
+ size: "sm",
75
+ variant: "outline",
76
+ className: "ms-auto",
77
+ disabled: y.pending || !b.length,
78
+ onClick: () => void y.run(() => r.media.commit(e, [{ op: "regenerate" }])),
79
+ children: _("mediaPanel.regenerate")
80
+ })
81
+ ]
82
+ }),
83
+ /* @__PURE__ */ F(S, { error: y.error }),
84
+ w.map((e) => /* @__PURE__ */ F(l, {
85
+ variant: "destructive",
86
+ "data-ckui": "media-failure",
87
+ children: /* @__PURE__ */ F(u, { children: _("mediaPanel.failedFile", {
88
+ name: e.path.split("/").pop(),
89
+ reason: g.error({
90
+ code: e.failed.code,
91
+ message: e.failed.message,
92
+ status: 422
93
+ })
94
+ }) })
95
+ }, e.path)),
96
+ /* @__PURE__ */ F(U, {
97
+ item: e,
98
+ path: i,
99
+ video: x,
100
+ client: r,
101
+ onError: h
102
+ }),
103
+ c !== null && x && /* @__PURE__ */ F(W, {
104
+ item: e,
105
+ path: c,
106
+ video: i,
107
+ preset: f,
108
+ client: r,
109
+ onError: h
110
+ }),
111
+ a !== null && /* @__PURE__ */ F(G, {
112
+ item: e,
113
+ pattern: a,
114
+ files: k,
115
+ languages: p,
116
+ client: r,
117
+ onError: h
118
+ })
119
+ ] });
120
+ }
121
+ function V(e) {
122
+ if (!e) return "";
123
+ let t = e / (1 << 20);
124
+ return t >= 1024 ? `${(t / 1024).toFixed(1)} GB` : `${t.toFixed(+(t < 10))} MB`;
125
+ }
126
+ function H(e) {
127
+ if (!e) return "";
128
+ let t = Math.floor(e / 60), n = Math.round(e - t * 60);
129
+ return `${t}:${String(n).padStart(2, "0")}`;
130
+ }
131
+ function U({ item: t, path: s, video: c, client: l, onError: u }) {
132
+ let { t: f } = n(), p = M(null), [m, h] = N(null), g = r(u), _ = i(), [v, y] = N(null), b = async (n) => {
133
+ y(null);
134
+ try {
135
+ await _(() => l.media.put(n, {
136
+ ref: t,
137
+ path: s,
138
+ wait: !1,
139
+ onProgress: h
140
+ }));
141
+ } catch (t) {
142
+ let n = e(t);
143
+ y(n), g(n, "media.save");
144
+ } finally {
145
+ h(null);
146
+ }
147
+ }, x = c ? [
148
+ c.w && c.h ? `${c.w}×${c.h}` : "",
149
+ H(c.dur),
150
+ V(c.size)
151
+ ].filter(Boolean).join(" · ") : "";
152
+ return /* @__PURE__ */ I("section", {
153
+ className: "grid gap-2",
154
+ "data-ckui": "media-source",
155
+ children: [
156
+ /* @__PURE__ */ F("h3", {
157
+ className: "font-medium",
158
+ children: f("mediaPanel.source")
159
+ }),
160
+ /* @__PURE__ */ I("div", {
161
+ className: "flex flex-wrap items-center gap-3",
162
+ children: [
163
+ /* @__PURE__ */ F("span", {
164
+ className: "min-w-0 flex-1 text-muted-foreground",
165
+ children: c ? x || c.path : f("mediaPanel.noSource")
166
+ }),
167
+ /* @__PURE__ */ F(d, {
168
+ size: "sm",
169
+ variant: "outline",
170
+ disabled: !!m,
171
+ onClick: () => p.current?.click(),
172
+ children: f(c ? "mediaPanel.replace" : "mediaPanel.upload")
173
+ }),
174
+ /* @__PURE__ */ F("input", {
175
+ ref: p,
176
+ type: "file",
177
+ accept: "video/*",
178
+ hidden: !0,
179
+ "aria-label": f("mediaPanel.source"),
180
+ onChange: (e) => {
181
+ let t = e.target.files?.[0];
182
+ e.target.value = "", t && b(t);
183
+ }
184
+ })
185
+ ]
186
+ }),
187
+ m && /* @__PURE__ */ I("div", {
188
+ className: "grid gap-1",
189
+ role: "status",
190
+ children: [/* @__PURE__ */ F("div", {
191
+ className: "h-1.5 overflow-hidden rounded-full bg-muted",
192
+ children: /* @__PURE__ */ F("div", {
193
+ className: "h-full bg-primary transition-[width]",
194
+ style: { width: `${m.total ? Math.round(m.loaded / m.total * 100) : 0}%` }
195
+ })
196
+ }), /* @__PURE__ */ F("span", {
197
+ className: "text-xs text-muted-foreground",
198
+ children: f(`mediaPanel.phase_${m.phase}`)
199
+ })]
200
+ }),
201
+ c && o(c) && !m && /* @__PURE__ */ F(a, { progress: c.progress }),
202
+ /* @__PURE__ */ F(S, { error: v })
203
+ ]
204
+ });
205
+ }
206
+ function W({ item: e, path: t, video: r, preset: i, client: a, onError: o }) {
207
+ let { t: s } = n(), [l, u] = N(!1), { image: m, reload: h } = c(e.kind, e.id, i, { client: a });
208
+ return /* @__PURE__ */ I("section", {
209
+ className: "grid gap-2",
210
+ "data-ckui": "media-poster",
211
+ children: [
212
+ /* @__PURE__ */ F("h3", {
213
+ className: "font-medium",
214
+ children: s("mediaPanel.poster")
215
+ }),
216
+ /* @__PURE__ */ I("div", {
217
+ className: "flex flex-wrap items-center gap-3",
218
+ children: [/* @__PURE__ */ F("div", {
219
+ className: "w-40 overflow-hidden rounded-md border border-border",
220
+ children: /* @__PURE__ */ F(f, {
221
+ image: m,
222
+ fallback: /* @__PURE__ */ F("div", { className: "aspect-video bg-muted" })
223
+ })
224
+ }), /* @__PURE__ */ F(d, {
225
+ size: "sm",
226
+ variant: "outline",
227
+ onClick: () => u(!0),
228
+ children: s("mediaPanel.choosePoster")
229
+ })]
230
+ }),
231
+ /* @__PURE__ */ F(p, {
232
+ open: l,
233
+ onOpenChange: u,
234
+ item: e,
235
+ video: r,
236
+ path: t,
237
+ image: m,
238
+ client: a,
239
+ onError: o,
240
+ onChange: () => h()
241
+ })
242
+ ]
243
+ });
244
+ }
245
+ function G({ item: e, pattern: t, files: r, languages: i, client: a, onError: o }) {
246
+ let { t: s } = n(), c = j(), l = L(i), [u, f] = N(l[0]?.value ?? ""), [p, w] = N(""), [T, E] = N(!1), [D, A] = N(null), P = M(null), R = C("media.save", o), z = C("media.save", o), { confirm: B, dialog: V } = O(), H = async () => {
247
+ if (!D || !u.trim()) return;
248
+ let n = u.trim(), r = `${n}${T ? "-forced" : ""}`;
249
+ await R.run(() => a.media.put(D, {
250
+ ref: e,
251
+ path: t.replace("{name}", r),
252
+ wait: !1,
253
+ meta: {
254
+ lang: n,
255
+ label: p.trim() || (l.find((e) => e.value === n)?.label ?? n),
256
+ forced: T
257
+ }
258
+ })) && (A(null), P.current && (P.current.value = ""), w(""), E(!1));
259
+ }, U = async (t) => {
260
+ await B({
261
+ title: s("mediaPanel.removeSubtitleTitle", { name: String(t.meta?.label ?? t.path) }),
262
+ confirm: s("common.remove")
263
+ }) && await z.run(() => a.media.commit(e, [{
264
+ op: "remove",
265
+ path: t.path
266
+ }]));
267
+ };
268
+ return /* @__PURE__ */ I("section", {
269
+ className: "grid gap-2",
270
+ "data-ckui": "media-subtitles",
271
+ children: [
272
+ /* @__PURE__ */ F("h3", {
273
+ className: "font-medium",
274
+ children: s("mediaPanel.subtitles")
275
+ }),
276
+ r.length ? /* @__PURE__ */ F("ul", {
277
+ className: "grid divide-y divide-border rounded-lg border border-border",
278
+ children: r.map((e) => /* @__PURE__ */ I("li", {
279
+ className: "flex flex-wrap items-center gap-2 p-2.5",
280
+ "data-ckui": "subtitle",
281
+ children: [
282
+ /* @__PURE__ */ F(m, {
283
+ variant: "outline",
284
+ children: String(e.meta?.lang ?? "?")
285
+ }),
286
+ /* @__PURE__ */ F("span", {
287
+ className: "min-w-0 flex-1 break-words",
288
+ children: String(e.meta?.label ?? e.path.split("/").pop())
289
+ }),
290
+ e.meta?.forced === !0 && /* @__PURE__ */ F(m, {
291
+ variant: "secondary",
292
+ children: s("mediaPanel.forced")
293
+ }),
294
+ e.failed && /* @__PURE__ */ F(m, {
295
+ variant: "destructive",
296
+ children: s("mediaPanel.failed")
297
+ }),
298
+ /* @__PURE__ */ F(d, {
299
+ size: "xs",
300
+ variant: "ghost",
301
+ disabled: z.pending,
302
+ onClick: () => void U(e),
303
+ children: s("common.remove")
304
+ })
305
+ ]
306
+ }, e.path))
307
+ }) : /* @__PURE__ */ F("p", {
308
+ className: "text-muted-foreground",
309
+ children: s("mediaPanel.noSubtitles")
310
+ }),
311
+ /* @__PURE__ */ I("form", {
312
+ className: "grid gap-2 rounded-lg border border-dashed border-border p-3 sm:grid-cols-2",
313
+ onSubmit: (e) => {
314
+ e.preventDefault(), H();
315
+ },
316
+ children: [
317
+ /* @__PURE__ */ I("div", {
318
+ className: "grid gap-1.5",
319
+ children: [/* @__PURE__ */ F(g, {
320
+ htmlFor: `${c}-file`,
321
+ children: s("mediaPanel.subtitleFile")
322
+ }), /* @__PURE__ */ F(h, {
323
+ id: `${c}-file`,
324
+ ref: P,
325
+ type: "file",
326
+ accept: ".vtt,.srt,text/vtt,application/x-subrip",
327
+ onChange: (e) => A(e.target.files?.[0] ?? null)
328
+ })]
329
+ }),
330
+ /* @__PURE__ */ I("div", {
331
+ className: "grid gap-1.5",
332
+ children: [/* @__PURE__ */ F(g, {
333
+ htmlFor: `${c}-lang`,
334
+ children: s("mediaPanel.language")
335
+ }), l.length ? /* @__PURE__ */ I(_, {
336
+ value: u,
337
+ onValueChange: (e) => f(String(e)),
338
+ children: [/* @__PURE__ */ F(b, {
339
+ id: `${c}-lang`,
340
+ className: "w-full",
341
+ children: /* @__PURE__ */ F(x, { children: (e) => l.find((t) => t.value === e)?.label ?? e })
342
+ }), /* @__PURE__ */ F(v, { children: l.map((e) => /* @__PURE__ */ F(y, {
343
+ value: e.value,
344
+ children: e.label
345
+ }, e.value)) })]
346
+ }) : /* @__PURE__ */ F(h, {
347
+ id: `${c}-lang`,
348
+ value: u,
349
+ placeholder: "en",
350
+ onChange: (e) => f(e.target.value)
351
+ })]
352
+ }),
353
+ /* @__PURE__ */ I("div", {
354
+ className: "grid gap-1.5",
355
+ children: [/* @__PURE__ */ F(g, {
356
+ htmlFor: `${c}-label`,
357
+ children: s("mediaPanel.label")
358
+ }), /* @__PURE__ */ F(h, {
359
+ id: `${c}-label`,
360
+ value: p,
361
+ placeholder: s("mediaPanel.labelPlaceholder"),
362
+ onChange: (e) => w(e.target.value)
363
+ })]
364
+ }),
365
+ /* @__PURE__ */ I("div", {
366
+ className: "flex items-center gap-2 self-end",
367
+ children: [/* @__PURE__ */ F(k, {
368
+ id: `${c}-forced`,
369
+ checked: T,
370
+ onCheckedChange: E
371
+ }), /* @__PURE__ */ F(g, {
372
+ htmlFor: `${c}-forced`,
373
+ children: s("mediaPanel.forcedLabel")
374
+ })]
375
+ }),
376
+ /* @__PURE__ */ F(S, {
377
+ error: R.error,
378
+ className: "sm:col-span-2"
379
+ }),
380
+ /* @__PURE__ */ F(d, {
381
+ type: "submit",
382
+ size: "sm",
383
+ className: "justify-self-start",
384
+ disabled: R.pending || !D || !u.trim(),
385
+ children: R.pending ? s("common.saving") : s("mediaPanel.addSubtitle")
386
+ })
387
+ ]
388
+ }),
389
+ /* @__PURE__ */ F(S, { error: z.error }),
390
+ V
391
+ ]
392
+ });
393
+ }
394
+ //#endregion
395
+ export { z as MediaItemPanel };
396
+
397
+ //# sourceMappingURL=media-item-panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-item-panel.js","names":[],"sources":["../../../src/components/admin/media-item-panel.tsx"],"sourcesContent":["import { useId, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { FileInfo, RefBody } from \"../../client/generated/wire.js\";\nimport type { Progress } from \"../../client/media/client.js\";\nimport { samePath } from \"../../client/media/client.js\";\nimport { isProcessing } from \"../../client/media/windows.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useErrorReporter, useGuarded } from \"../../react/context.js\";\nimport { usePublicImage } from \"../../react/public.js\";\nimport { useMediaRead } from \"../../react/read.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Switch } from \"#ckui/ui/switch\";\nimport { EncodeProgress } from \"../encode-progress.js\";\nimport { SlotImage } from \"../slot-image.js\";\nimport { VideoPosterPicker } from \"../video-poster-picker.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, useConfirm, type AdminFlowProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\n\nexport interface MediaItemPanelProps extends AdminFlowProps {\n /** The video item. */\n item: RefBody;\n /** The video's upload path. Default \"source\". */\n source?: string;\n /** The subtitle uploads' path pattern; null hides them. Default \"subs/{name}\". */\n subtitles?: string | null;\n /** The poster's upload path (its frames come from the video); null hides it. Default \"poster\". */\n poster?: string | null;\n /** The poster's public preset. Default \"poster\". */\n posterPreset?: string;\n /** Subtitle languages offered: codes, or codes with labels. Without them the language is typed. */\n languages?: readonly Choice[];\n}\n\ntype ItemState = \"ready\" | \"processing\" | \"failed\" | \"empty\";\nconst choices = (list?: readonly Choice[]) => (list ?? []).map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst under = (pattern: string) => pattern.slice(0, pattern.indexOf(\"{name}\"));\n\n/**\n * One video item for its editors: its encode state and progress, the source\n * (replace it), subtitle sidecars (language, label, forced), the poster from\n * a frame or an image, and regenerate, which retries failed uploads.\n */\nexport function MediaItemPanel({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: MediaItemPanelProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"media-item-panel\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Panel {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<MediaItemPanelProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Panel({ item, client, source = \"source\", subtitles = \"subs/{name}\", poster = \"poster\", posterPreset = \"poster\", languages, onError }: Inner) {\n const m = useMessages();\n const { t } = m;\n const r = useMediaRead(item, { editor: true, client });\n const regen = useAction(\"media.save\", onError);\n if (!r.read) {\n if (r.error?.code === \"not_found\") return <NoAccess>{t(\"mediaPanel.noAccess\")}</NoAccess>;\n return r.error ? <LoadFailed error={r.error} message={t(\"mediaPanel.loadFailed\")} retry={r.reload} /> : <Rows count={2} />;\n }\n if (!r.read.uploads) return <NoAccess>{t(\"mediaPanel.noAccess\")}</NoAccess>;\n const uploads = r.read.files.filter((f) => f.upload && !f.unattached);\n const video = uploads.find((f) => samePath(f.path, source));\n const failed = uploads.filter((f) => f.failed);\n const state: ItemState = !video ? \"empty\" : failed.length ? \"failed\" : uploads.some(isProcessing) ? \"processing\" : \"ready\";\n const subs = subtitles === null ? [] : uploads.filter((f) => f.path.startsWith(under(subtitles)));\n return (\n <>\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"mediaPanel.title\")}</h2>\n <Badge variant={state === \"failed\" ? \"destructive\" : state === \"ready\" ? \"outline\" : \"secondary\"} data-ckui=\"media-state\" data-state={state}>\n {t(`mediaPanel.${state}`)}\n </Badge>\n <Button size=\"sm\" variant=\"outline\" className=\"ms-auto\" disabled={regen.pending || !uploads.length} onClick={() => void regen.run(() => client.media.commit(item, [{ op: \"regenerate\" }]))}>\n {t(\"mediaPanel.regenerate\")}\n </Button>\n </div>\n <ErrorLine error={regen.error} />\n {failed.map((f) => (\n <Alert key={f.path} variant=\"destructive\" data-ckui=\"media-failure\">\n <AlertDescription>\n {t(\"mediaPanel.failedFile\", { name: f.path.split(\"/\").pop()!, reason: m.error({ code: f.failed!.code, message: f.failed!.message, status: 422 }) })}\n </AlertDescription>\n </Alert>\n ))}\n <SourceSection item={item} path={source} video={video} client={client} onError={onError} />\n {poster !== null && video && <PosterSection item={item} path={poster} video={source} preset={posterPreset} client={client} onError={onError} />}\n {subtitles !== null && <Subtitles item={item} pattern={subtitles} files={subs} languages={languages} client={client} onError={onError} />}\n </>\n );\n}\n\nfunction bytes(n?: number): string {\n if (!n) return \"\";\n const mb = n / (1 << 20);\n return mb >= 1024 ? `${(mb / 1024).toFixed(1)} GB` : `${mb.toFixed(mb < 10 ? 1 : 0)} MB`;\n}\n\nfunction duration(s?: number): string {\n if (!s) return \"\";\n const m = Math.floor(s / 60);\n const sec = Math.round(s - m * 60);\n return `${m}:${String(sec).padStart(2, \"0\")}`;\n}\n\nfunction SourceSection({ item, path, video, client, onError }: { item: RefBody; path: string; video?: FileInfo; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const file = useRef<HTMLInputElement>(null);\n const [progress, setProgress] = useState<Progress | null>(null);\n const report = useErrorReporter(onError);\n const guarded = useGuarded();\n const [error, setError] = useState<ContentKitError | null>(null);\n const replace = async (f: File) => {\n setError(null);\n try {\n await guarded(() => client.media.put(f, { ref: item, path, wait: false, onProgress: setProgress }));\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"media.save\");\n } finally {\n setProgress(null);\n }\n };\n const facts = video ? [video.w && video.h ? `${video.w}×${video.h}` : \"\", duration(video.dur), bytes(video.size)].filter(Boolean).join(\" · \") : \"\";\n return (\n <section className=\"grid gap-2\" data-ckui=\"media-source\">\n <h3 className=\"font-medium\">{t(\"mediaPanel.source\")}</h3>\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"min-w-0 flex-1 text-muted-foreground\">{video ? facts || video.path : t(\"mediaPanel.noSource\")}</span>\n <Button size=\"sm\" variant=\"outline\" disabled={!!progress} onClick={() => file.current?.click()}>\n {t(video ? \"mediaPanel.replace\" : \"mediaPanel.upload\")}\n </Button>\n <input\n ref={file}\n type=\"file\"\n accept=\"video/*\"\n hidden\n aria-label={t(\"mediaPanel.source\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) void replace(f);\n }}\n />\n </div>\n {progress && (\n <div className=\"grid gap-1\" role=\"status\">\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\">\n <div className=\"h-full bg-primary transition-[width]\" style={{ width: `${progress.total ? Math.round((progress.loaded / progress.total) * 100) : 0}%` }} />\n </div>\n <span className=\"text-xs text-muted-foreground\">{t(`mediaPanel.phase_${progress.phase}`)}</span>\n </div>\n )}\n {video && isProcessing(video) && !progress && <EncodeProgress progress={video.progress} />}\n <ErrorLine error={error} />\n </section>\n );\n}\n\nfunction PosterSection({ item, path, video, preset, client, onError }: { item: RefBody; path: string; video: string; preset: string; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const [open, setOpen] = useState(false);\n const { image, reload } = usePublicImage(item.kind, item.id, preset, { client });\n return (\n <section className=\"grid gap-2\" data-ckui=\"media-poster\">\n <h3 className=\"font-medium\">{t(\"mediaPanel.poster\")}</h3>\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"w-40 overflow-hidden rounded-md border border-border\">\n <SlotImage image={image} fallback={<div className=\"aspect-video bg-muted\" />} />\n </div>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setOpen(true)}>\n {t(\"mediaPanel.choosePoster\")}\n </Button>\n </div>\n <VideoPosterPicker open={open} onOpenChange={setOpen} item={item} video={video} path={path} image={image} client={client} onError={onError} onChange={() => reload()} />\n </section>\n );\n}\n\nfunction Subtitles({ item, pattern, files, languages: offered, client, onError }: { item: RefBody; pattern: string; files: FileInfo[]; languages?: readonly Choice[]; client: ContentKitClient; onError?: Inner[\"onError\"] }) {\n const { t } = useMessages();\n const ids = useId();\n const languages = choices(offered);\n const [lang, setLang] = useState(languages[0]?.value ?? \"\");\n const [label, setLabel] = useState(\"\");\n const [forced, setForced] = useState(false);\n const [file, setFile] = useState<File | null>(null);\n const input = useRef<HTMLInputElement>(null);\n const add = useAction(\"media.save\", onError);\n const remove = useAction(\"media.save\", onError);\n const { confirm, dialog } = useConfirm();\n const save = async () => {\n if (!file || !lang.trim()) return;\n const code = lang.trim();\n const name = `${code}${forced ? \"-forced\" : \"\"}`;\n const done = await add.run(() =>\n client.media.put(file, {\n ref: item,\n path: pattern.replace(\"{name}\", name),\n wait: false,\n meta: { lang: code, label: label.trim() || (languages.find((l) => l.value === code)?.label ?? code), forced },\n }),\n );\n if (done) {\n setFile(null);\n if (input.current) input.current.value = \"\";\n setLabel(\"\");\n setForced(false);\n }\n };\n const drop = async (f: FileInfo) => {\n if (await confirm({ title: t(\"mediaPanel.removeSubtitleTitle\", { name: String(f.meta?.label ?? f.path) }), confirm: t(\"common.remove\") })) {\n await remove.run(() => client.media.commit(item, [{ op: \"remove\", path: f.path }]));\n }\n };\n return (\n <section className=\"grid gap-2\" data-ckui=\"media-subtitles\">\n <h3 className=\"font-medium\">{t(\"mediaPanel.subtitles\")}</h3>\n {files.length ? (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {files.map((f) => (\n <li key={f.path} className=\"flex flex-wrap items-center gap-2 p-2.5\" data-ckui=\"subtitle\">\n <Badge variant=\"outline\">{String(f.meta?.lang ?? \"?\")}</Badge>\n <span className=\"min-w-0 flex-1 break-words\">{String(f.meta?.label ?? f.path.split(\"/\").pop())}</span>\n {f.meta?.forced === true && <Badge variant=\"secondary\">{t(\"mediaPanel.forced\")}</Badge>}\n {f.failed && <Badge variant=\"destructive\">{t(\"mediaPanel.failed\")}</Badge>}\n <Button size=\"xs\" variant=\"ghost\" disabled={remove.pending} onClick={() => void drop(f)}>\n {t(\"common.remove\")}\n </Button>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-muted-foreground\">{t(\"mediaPanel.noSubtitles\")}</p>\n )}\n <form\n className=\"grid gap-2 rounded-lg border border-dashed border-border p-3 sm:grid-cols-2\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-file`}>{t(\"mediaPanel.subtitleFile\")}</Label>\n <Input id={`${ids}-file`} ref={input} type=\"file\" accept=\".vtt,.srt,text/vtt,application/x-subrip\" onChange={(e) => setFile(e.target.files?.[0] ?? null)} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-lang`}>{t(\"mediaPanel.language\")}</Label>\n {languages.length ? (\n <Select value={lang} onValueChange={(v) => setLang(String(v))}>\n <SelectTrigger id={`${ids}-lang`} className=\"w-full\">\n <SelectValue>{(v: string) => languages.find((l) => l.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l.value} value={l.value}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : (\n <Input id={`${ids}-lang`} value={lang} placeholder=\"en\" onChange={(e) => setLang(e.target.value)} />\n )}\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-label`}>{t(\"mediaPanel.label\")}</Label>\n <Input id={`${ids}-label`} value={label} placeholder={t(\"mediaPanel.labelPlaceholder\")} onChange={(e) => setLabel(e.target.value)} />\n </div>\n <div className=\"flex items-center gap-2 self-end\">\n <Switch id={`${ids}-forced`} checked={forced} onCheckedChange={setForced} />\n <Label htmlFor={`${ids}-forced`}>{t(\"mediaPanel.forcedLabel\")}</Label>\n </div>\n <ErrorLine error={add.error} className=\"sm:col-span-2\" />\n <Button type=\"submit\" size=\"sm\" className=\"justify-self-start\" disabled={add.pending || !file || !lang.trim()}>\n {add.pending ? t(\"common.saving\") : t(\"mediaPanel.addSubtitle\")}\n </Button>\n </form>\n <ErrorLine error={remove.error} />\n {dialog}\n </section>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAM,KAAW,OAA8B,KAAQ,CAAC,EAAA,CAAG,KAAK,MAAO,OAAO,KAAM,WAAW;CAAE,OAAO;CAAG,OAAO;AAAE,IAAI,CAAE,GACpH,KAAS,MAAoB,EAAQ,MAAM,GAAG,EAAQ,QAAQ,QAAQ,CAAC;AAO7E,SAAgB,EAAe,EAAE,QAAQ,GAAK,YAAS,SAAM,UAAO,eAAY,cAAW,aAAU,aAAU,GAAG,KAA0B;CAC1I,IAAM,IAAS,EAAc;EAAE,QAAQ;EAAK;EAAS;CAAK,CAAC;CAC3D,OACE,kBAAC,GAAD;EAAW,MAAK;EAA+B;EAAuB;EAAqB;EAAoB;EAAiB;EAC9H,UAAA,kBAAC,GAAD;GAAO,GAAI;GAAW;EAAS,CAAA;CACtB,CAAA;AAEf;AAIA,SAAS,EAAM,EAAE,SAAM,WAAQ,YAAS,UAAU,eAAY,eAAe,YAAS,UAAU,kBAAe,UAAU,cAAW,cAAkB;CACpJ,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAI,EAAa,GAAM;EAAE,QAAQ;EAAM;CAAO,CAAC,GAC/C,IAAQ,EAAU,cAAc,CAAO;CAC7C,IAAI,CAAC,EAAE,MAEL,OADI,EAAE,OAAO,SAAS,cAAoB,kBAAC,GAAD,EAAA,UAAW,EAAE,qBAAqB,EAAY,CAAA,IACjF,EAAE,QAAQ,kBAAC,GAAD;EAAY,OAAO,EAAE;EAAO,SAAS,EAAE,uBAAuB;EAAG,OAAO,EAAE;CAAS,CAAA,IAAI,kBAAC,GAAD,EAAM,OAAO,EAAI,CAAA;CAE3H,IAAI,CAAC,EAAE,KAAK,SAAS,OAAO,kBAAC,GAAD,EAAA,UAAW,EAAE,qBAAqB,EAAY,CAAA;CAC1E,IAAM,IAAU,EAAE,KAAK,MAAM,QAAQ,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,GAC9D,IAAQ,EAAQ,MAAM,MAAM,EAAS,EAAE,MAAM,CAAM,CAAC,GACpD,IAAS,EAAQ,QAAQ,MAAM,EAAE,MAAM,GACvC,IAAoB,IAAkB,EAAO,SAAS,WAAW,EAAQ,KAAK,CAAY,IAAI,eAAe,UAAjF,SAC5B,IAAO,MAAc,OAAO,CAAC,IAAI,EAAQ,QAAQ,MAAM,EAAE,KAAK,WAAW,EAAM,CAAS,CAAC,CAAC;CAChG,OACE,kBAAA,GAAA,EAAA,UAAA;EACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,MAAD;KAAI,WAAU;KAA2B,UAAA,EAAE,kBAAkB;IAAM,CAAA;IACnE,kBAAC,GAAD;KAAO,SAAS,MAAU,WAAW,gBAAgB,MAAU,UAAU,YAAY;KAAa,aAAU;KAAc,cAAY;KACnI,UAAA,EAAE,cAAc,GAAO;IACnB,CAAA;IACP,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAU,WAAU;KAAU,UAAU,EAAM,WAAW,CAAC,EAAQ;KAAQ,eAAe,KAAK,EAAM,UAAU,EAAO,MAAM,OAAO,GAAM,CAAC,EAAE,IAAI,aAAa,CAAC,CAAC,CAAC;KACtL,UAAA,EAAE,uBAAuB;IACpB,CAAA;GACL;;EACL,kBAAC,GAAD,EAAW,OAAO,EAAM,MAAQ,CAAA;EAC/B,EAAO,KAAK,MACX,kBAAC,GAAD;GAAoB,SAAQ;GAAc,aAAU;GAClD,UAAA,kBAAC,GAAD,EAAA,UACG,EAAE,yBAAyB;IAAE,MAAM,EAAE,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI;IAAI,QAAQ,EAAE,MAAM;KAAE,MAAM,EAAE,OAAQ;KAAM,SAAS,EAAE,OAAQ;KAAS,QAAQ;IAAI,CAAC;GAAE,CAAC,EAClI,CAAA;EACb,GAJK,EAAE,IAIP,CACR;EACD,kBAAC,GAAD;GAAqB;GAAM,MAAM;GAAe;GAAe;GAAiB;EAAU,CAAA;EACzF,MAAW,QAAQ,KAAS,kBAAC,GAAD;GAAqB;GAAM,MAAM;GAAQ,OAAO;GAAQ,QAAQ;GAAsB;GAAiB;EAAU,CAAA;EAC7I,MAAc,QAAQ,kBAAC,GAAD;GAAiB;GAAM,SAAS;GAAW,OAAO;GAAiB;GAAmB;GAAiB;EAAU,CAAA;CACxI,EAAA,CAAA;AAEN;AAEA,SAAS,EAAM,GAAoB;CACjC,IAAI,CAAC,GAAG,OAAO;CACf,IAAM,IAAK,KAAK,KAAK;CACrB,OAAO,KAAM,OAAO,IAAI,IAAK,KAAA,CAAM,QAAQ,CAAC,EAAE,OAAO,GAAG,EAAG,QAAQ,MAAK,GAAU,EAAE;AACtF;AAEA,SAAS,EAAS,GAAoB;CACpC,IAAI,CAAC,GAAG,OAAO;CACf,IAAM,IAAI,KAAK,MAAM,IAAI,EAAE,GACrB,IAAM,KAAK,MAAM,IAAI,IAAI,EAAE;CACjC,OAAO,GAAG,EAAE,GAAG,OAAO,CAAG,CAAC,CAAC,SAAS,GAAG,GAAG;AAC5C;AAEA,SAAS,EAAc,EAAE,SAAM,SAAM,UAAO,WAAQ,cAAoH;CACtK,IAAM,EAAE,SAAM,EAAY,GACpB,IAAO,EAAyB,IAAI,GACpC,CAAC,GAAU,KAAe,EAA0B,IAAI,GACxD,IAAS,EAAiB,CAAO,GACjC,IAAU,EAAW,GACrB,CAAC,GAAO,KAAY,EAAiC,IAAI,GACzD,IAAU,OAAO,MAAY;EACjC,EAAS,IAAI;EACb,IAAI;GACF,MAAM,QAAc,EAAO,MAAM,IAAI,GAAG;IAAE,KAAK;IAAM;IAAM,MAAM;IAAO,YAAY;GAAY,CAAC,CAAC;EACpG,SAAS,GAAG;GACV,IAAM,IAAM,EAAkB,CAAC;GAE/B,AADA,EAAS,CAAG,GACZ,EAAO,GAAK,YAAY;EAC1B,UAAU;GACR,EAAY,IAAI;EAClB;CACF,GACM,IAAQ,IAAQ;EAAC,EAAM,KAAK,EAAM,IAAI,GAAG,EAAM,EAAE,GAAG,EAAM,MAAM;EAAI,EAAS,EAAM,GAAG;EAAG,EAAM,EAAM,IAAI;CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK,IAAI;CAChJ,OACE,kBAAC,WAAD;EAAS,WAAU;EAAa,aAAU;EAA1C,UAAA;GACE,kBAAC,MAAD;IAAI,WAAU;IAAe,UAAA,EAAE,mBAAmB;GAAM,CAAA;GACxD,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,kBAAC,QAAD;MAAM,WAAU;MAAwC,UAAA,IAAQ,KAAS,EAAM,OAAO,EAAE,qBAAqB;KAAQ,CAAA;KACrH,kBAAC,GAAD;MAAQ,MAAK;MAAK,SAAQ;MAAU,UAAU,CAAC,CAAC;MAAU,eAAe,EAAK,SAAS,MAAM;MAC1F,UAAA,EAAE,IAAQ,uBAAuB,mBAAmB;KAC/C,CAAA;KACR,kBAAC,SAAD;MACE,KAAK;MACL,MAAK;MACL,QAAO;MACP,QAAA;MACA,cAAY,EAAE,mBAAmB;MACjC,WAAW,MAAM;OACf,IAAM,IAAI,EAAE,OAAO,QAAQ;OAE3B,AADA,EAAE,OAAO,QAAQ,IACb,KAAG,EAAa,CAAC;MACvB;KACD,CAAA;IACE;;GACJ,KACC,kBAAC,OAAD;IAAK,WAAU;IAAa,MAAK;IAAjC,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,OAAD;MAAK,WAAU;MAAuC,OAAO,EAAE,OAAO,GAAG,EAAS,QAAQ,KAAK,MAAO,EAAS,SAAS,EAAS,QAAS,GAAG,IAAI,EAAE,GAAG;KAAI,CAAA;IACvJ,CAAA,GACL,kBAAC,QAAD;KAAM,WAAU;KAAiC,UAAA,EAAE,oBAAoB,EAAS,OAAO;IAAQ,CAAA,CAC5F;;GAEN,KAAS,EAAa,CAAK,KAAK,CAAC,KAAY,kBAAC,GAAD,EAAgB,UAAU,EAAM,SAAW,CAAA;GACzF,kBAAC,GAAD,EAAkB,SAAQ,CAAA;EACnB;;AAEb;AAEA,SAAS,EAAc,EAAE,SAAM,SAAM,UAAO,WAAQ,WAAQ,cAAiI;CAC3L,IAAM,EAAE,SAAM,EAAY,GACpB,CAAC,GAAM,KAAW,EAAS,EAAK,GAChC,EAAE,UAAO,cAAW,EAAe,EAAK,MAAM,EAAK,IAAI,GAAQ,EAAE,UAAO,CAAC;CAC/E,OACE,kBAAC,WAAD;EAAS,WAAU;EAAa,aAAU;EAA1C,UAAA;GACE,kBAAC,MAAD;IAAI,WAAU;IAAe,UAAA,EAAE,mBAAmB;GAAM,CAAA;GACxD,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,GAAD;MAAkB;MAAO,UAAU,kBAAC,OAAD,EAAK,WAAU,wBAAyB,CAAA;KAAI,CAAA;IAC5E,CAAA,GACL,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAU,eAAe,EAAQ,EAAI;KAC5D,UAAA,EAAE,yBAAyB;IACtB,CAAA,CACL;;GACL,kBAAC,GAAD;IAAyB;IAAM,cAAc;IAAe;IAAa;IAAa;IAAa;IAAe;IAAiB;IAAS,gBAAgB,EAAO;GAAI,CAAA;EAChK;;AAEb;AAEA,SAAS,EAAU,EAAE,SAAM,YAAS,UAAO,WAAW,GAAS,WAAQ,cAAuJ;CAC5N,IAAM,EAAE,SAAM,EAAY,GACpB,IAAM,EAAM,GACZ,IAAY,EAAQ,CAAO,GAC3B,CAAC,GAAM,KAAW,EAAS,EAAU,EAAE,EAAE,SAAS,EAAE,GACpD,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAQ,KAAa,EAAS,EAAK,GACpC,CAAC,GAAM,KAAW,EAAsB,IAAI,GAC5C,IAAQ,EAAyB,IAAI,GACrC,IAAM,EAAU,cAAc,CAAO,GACrC,IAAS,EAAU,cAAc,CAAO,GACxC,EAAE,YAAS,cAAW,EAAW,GACjC,IAAO,YAAY;EACvB,IAAI,CAAC,KAAQ,CAAC,EAAK,KAAK,GAAG;EAC3B,IAAM,IAAO,EAAK,KAAK,GACjB,IAAO,GAAG,IAAO,IAAS,YAAY;EAS5C,AAAI,MARe,EAAI,UACrB,EAAO,MAAM,IAAI,GAAM;GACrB,KAAK;GACL,MAAM,EAAQ,QAAQ,UAAU,CAAI;GACpC,MAAM;GACN,MAAM;IAAE,MAAM;IAAM,OAAO,EAAM,KAAK,MAAM,EAAU,MAAM,MAAM,EAAE,UAAU,CAAI,CAAC,EAAE,SAAS;IAAO;GAAO;EAC9G,CAAC,CACH,MAEE,EAAQ,IAAI,GACR,EAAM,YAAS,EAAM,QAAQ,QAAQ,KACzC,EAAS,EAAE,GACX,EAAU,EAAK;CAEnB,GACM,IAAO,OAAO,MAAgB;EAClC,AAAI,MAAM,EAAQ;GAAE,OAAO,EAAE,kCAAkC,EAAE,MAAM,OAAO,EAAE,MAAM,SAAS,EAAE,IAAI,EAAE,CAAC;GAAG,SAAS,EAAE,eAAe;EAAE,CAAC,KACtI,MAAM,EAAO,UAAU,EAAO,MAAM,OAAO,GAAM,CAAC;GAAE,IAAI;GAAU,MAAM,EAAE;EAAK,CAAC,CAAC,CAAC;CAEtF;CACA,OACE,kBAAC,WAAD;EAAS,WAAU;EAAa,aAAU;EAA1C,UAAA;GACE,kBAAC,MAAD;IAAI,WAAU;IAAe,UAAA,EAAE,sBAAsB;GAAM,CAAA;GAC1D,EAAM,SACL,kBAAC,MAAD;IAAI,WAAU;IACX,UAAA,EAAM,KAAK,MACV,kBAAC,MAAD;KAAiB,WAAU;KAA0C,aAAU;KAA/E,UAAA;MACE,kBAAC,GAAD;OAAO,SAAQ;OAAW,UAAA,OAAO,EAAE,MAAM,QAAQ,GAAG;MAAS,CAAA;MAC7D,kBAAC,QAAD;OAAM,WAAU;OAA8B,UAAA,OAAO,EAAE,MAAM,SAAS,EAAE,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC;MAAQ,CAAA;MACpG,EAAE,MAAM,WAAW,MAAQ,kBAAC,GAAD;OAAO,SAAQ;OAAa,UAAA,EAAE,mBAAmB;MAAS,CAAA;MACrF,EAAE,UAAU,kBAAC,GAAD;OAAO,SAAQ;OAAe,UAAA,EAAE,mBAAmB;MAAS,CAAA;MACzE,kBAAC,GAAD;OAAQ,MAAK;OAAK,SAAQ;OAAQ,UAAU,EAAO;OAAS,eAAe,KAAK,EAAK,CAAC;OACnF,UAAA,EAAE,eAAe;MACZ,CAAA;KACN;IARK,GAAA,EAAE,IAQP,CACL;GACC,CAAA,IAEJ,kBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,EAAE,wBAAwB;GAAK,CAAA;GAEvE,kBAAC,QAAD;IACE,WAAU;IACV,WAAW,MAAM;KAEf,AADA,EAAE,eAAe,GACjB,EAAU;IACZ;IALF,UAAA;KAOE,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OAAO,SAAS,GAAG,EAAI;OAAS,UAAA,EAAE,yBAAyB;MAAS,CAAA,GACpE,kBAAC,GAAD;OAAO,IAAI,GAAG,EAAI;OAAQ,KAAK;OAAO,MAAK;OAAO,QAAO;OAA0C,WAAW,MAAM,EAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI;MAAI,CAAA,CACxJ;;KACL,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OAAO,SAAS,GAAG,EAAI;OAAS,UAAA,EAAE,qBAAqB;MAAS,CAAA,GAC/D,EAAU,SACT,kBAAC,GAAD;OAAQ,OAAO;OAAM,gBAAgB,MAAM,EAAQ,OAAO,CAAC,CAAC;OAA5D,UAAA,CACE,kBAAC,GAAD;QAAe,IAAI,GAAG,EAAI;QAAQ,WAAU;QAC1C,UAAA,kBAAC,GAAD,EAAA,WAAe,MAAc,EAAU,MAAM,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,SAAS,EAAe,CAAA;OAC9E,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAU,KAAK,MACd,kBAAC,GAAD;QAA0B,OAAO,EAAE;QAChC,UAAA,EAAE;OACO,GAFK,EAAE,KAEP,CACb,EACY,CAAA,CACT;MAER,CAAA,IAAA,kBAAC,GAAD;OAAO,IAAI,GAAG,EAAI;OAAQ,OAAO;OAAM,aAAY;OAAK,WAAW,MAAM,EAAQ,EAAE,OAAO,KAAK;MAAI,CAAA,CAElG;;KACL,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OAAO,SAAS,GAAG,EAAI;OAAU,UAAA,EAAE,kBAAkB;MAAS,CAAA,GAC9D,kBAAC,GAAD;OAAO,IAAI,GAAG,EAAI;OAAS,OAAO;OAAO,aAAa,EAAE,6BAA6B;OAAG,WAAW,MAAM,EAAS,EAAE,OAAO,KAAK;MAAI,CAAA,CACjI;;KACL,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OAAQ,IAAI,GAAG,EAAI;OAAU,SAAS;OAAQ,iBAAiB;MAAY,CAAA,GAC3E,kBAAC,GAAD;OAAO,SAAS,GAAG,EAAI;OAAW,UAAA,EAAE,wBAAwB;MAAS,CAAA,CAClE;;KACL,kBAAC,GAAD;MAAW,OAAO,EAAI;MAAO,WAAU;KAAiB,CAAA;KACxD,kBAAC,GAAD;MAAQ,MAAK;MAAS,MAAK;MAAK,WAAU;MAAqB,UAAU,EAAI,WAAW,CAAC,KAAQ,CAAC,EAAK,KAAK;MACzG,UAAA,EAAI,UAAU,EAAE,eAAe,IAAI,EAAE,wBAAwB;KACxD,CAAA;IACJ;;GACN,kBAAC,GAAD,EAAW,OAAO,EAAO,MAAQ,CAAA;GAChC;EACM;;AAEb"}
@@ -0,0 +1,23 @@
1
+ import { FileInfo, RefBody } from '../../client/generated/wire.js';
2
+ import { AdminFlowProps } from './shell.js';
3
+ export interface UploadQueueProps extends AdminFlowProps {
4
+ /** Makes the host's item for a file (e.g. a video record) and resolves with its ref; once per file, a reload included. */
5
+ createItem: (file: File) => Promise<RefBody>;
6
+ /** The upload path in each item. Default "source". */
7
+ path?: string;
8
+ /** The file picker's accept, e.g. "video/*". */
9
+ accept?: string;
10
+ /** Files uploading at once. Default 2. */
11
+ concurrency?: number;
12
+ /** Each committed file: its item and the upload as an editor reads it. */
13
+ onUploaded?: (ref: RefBody, file: FileInfo) => void;
14
+ /** The localStorage key unfinished uploads are kept under, to resume after a reload. Default "ckui.uploads". */
15
+ storageKey?: string;
16
+ }
17
+ /**
18
+ * Many files, each into its own new item (createItem): uploads in the
19
+ * background with progress, pause, retry and resume. An unfinished upload is
20
+ * kept in localStorage; adding the same file after a reload continues it in
21
+ * the same item. Leaving the page while files upload asks first.
22
+ */
23
+ export declare function UploadQueue({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: UploadQueueProps): import('react').JSX.Element;