@openrails/contentkit-ui 0.72.0 → 0.73.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.
- package/README.md +24 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/media/queue.d.ts +1 -1
- package/dist/client/media/queue.js +1 -1
- package/dist/client/media/queue.js.map +1 -1
- package/dist/client.js +2 -2
- package/dist/components/admin/media-item-panel.d.ts +27 -0
- package/dist/components/admin/media-item-panel.js +397 -0
- package/dist/components/admin/media-item-panel.js.map +1 -0
- package/dist/components/admin/upload-queue.d.ts +23 -0
- package/dist/components/admin/upload-queue.js +365 -0
- package/dist/components/admin/upload-queue.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -2
- package/dist/locales/admin/de.js +51 -0
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +51 -0
- package/dist/locales/admin/en.js +51 -0
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +51 -0
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +51 -0
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +51 -0
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +51 -0
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/en.d.ts +51 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/folder.js.map +1 -1
- package/dist/react/upload.d.ts +10 -10
- package/dist/react/upload.js +1 -1
- package/dist/react/upload.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -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":"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"}
|
|
@@ -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;
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { toContentKitError as e } from "../../client/errors.js";
|
|
2
|
+
import { samePath as t } from "../../client/media/client.js";
|
|
3
|
+
import { megabytes as n } from "../../i18n/messages.js";
|
|
4
|
+
import { useMessages as r } from "../../i18n/context.js";
|
|
5
|
+
import { useErrorReporter as i, useGuarded as a } from "../../react/context.js";
|
|
6
|
+
import { Button as o } from "../../ui/button.js";
|
|
7
|
+
import { Badge as s } from "../../ui/badge.js";
|
|
8
|
+
import { ErrorLine as c } from "../social/parts.js";
|
|
9
|
+
import { AdminRoot as l } from "./shell.js";
|
|
10
|
+
import { useFlowClient as u } from "../../react/admin.js";
|
|
11
|
+
import { useCallback as d, useEffect as f, useRef as p, useState as m } from "react";
|
|
12
|
+
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
13
|
+
import { cn as v } from "cn";
|
|
14
|
+
//#region src/components/admin/upload-queue.tsx
|
|
15
|
+
var y = [
|
|
16
|
+
"creating",
|
|
17
|
+
"uploading",
|
|
18
|
+
"committing"
|
|
19
|
+
], b = (e) => `${e.name}\u0000${e.size}\u0000${e.lastModified}`, x = 0;
|
|
20
|
+
function S(e) {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(localStorage.getItem(e) ?? "{}");
|
|
23
|
+
} catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function C(e, t) {
|
|
28
|
+
try {
|
|
29
|
+
let n = S(e);
|
|
30
|
+
t(n), Object.keys(n).length ? localStorage.setItem(e, JSON.stringify(n)) : localStorage.removeItem(e);
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
function w({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className: a, language: o, messages: s, ...c }) {
|
|
34
|
+
let d = u({
|
|
35
|
+
client: e,
|
|
36
|
+
baseUrl: t,
|
|
37
|
+
auth: n
|
|
38
|
+
});
|
|
39
|
+
return /* @__PURE__ */ g(l, {
|
|
40
|
+
flow: "upload-queue",
|
|
41
|
+
appearance: i,
|
|
42
|
+
className: a,
|
|
43
|
+
language: o,
|
|
44
|
+
messages: s,
|
|
45
|
+
guard: r,
|
|
46
|
+
children: /* @__PURE__ */ g(T, {
|
|
47
|
+
...c,
|
|
48
|
+
client: d
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function T({ client: l, createItem: u, path: w = "source", accept: T, concurrency: E = 2, onUploaded: D, storageKey: O = "ckui.uploads", onError: k }) {
|
|
53
|
+
let A = r(), { t: j } = A, M = i(k), N = a(), [P, F] = m([]), [I, L] = m(() => Object.values(S(O))), [R, z] = m(!1), B = p(null), V = p(/* @__PURE__ */ new Map()), H = p({
|
|
54
|
+
createItem: u,
|
|
55
|
+
onUploaded: D
|
|
56
|
+
});
|
|
57
|
+
f(() => {
|
|
58
|
+
H.current = {
|
|
59
|
+
createItem: u,
|
|
60
|
+
onUploaded: D
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
let U = d((e, t) => F((n) => n.map((n) => n.id === e ? {
|
|
64
|
+
...n,
|
|
65
|
+
...t
|
|
66
|
+
} : n)), []), W = d(async (n) => {
|
|
67
|
+
let r = new AbortController();
|
|
68
|
+
V.current.set(n.id, r);
|
|
69
|
+
let i = S(O)[n.key];
|
|
70
|
+
try {
|
|
71
|
+
let a = n.ref ?? i?.ref;
|
|
72
|
+
if (!a) {
|
|
73
|
+
U(n.id, {
|
|
74
|
+
status: "creating",
|
|
75
|
+
error: void 0
|
|
76
|
+
}), a = await H.current.createItem(n.file);
|
|
77
|
+
let e = a;
|
|
78
|
+
C(O, (t) => void (t[n.key] = {
|
|
79
|
+
ref: e,
|
|
80
|
+
name: n.file.name,
|
|
81
|
+
size: n.file.size
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
let o = a;
|
|
85
|
+
U(n.id, {
|
|
86
|
+
status: "uploading",
|
|
87
|
+
ref: a,
|
|
88
|
+
error: void 0
|
|
89
|
+
});
|
|
90
|
+
let s = {
|
|
91
|
+
ref: a,
|
|
92
|
+
path: w,
|
|
93
|
+
signal: r.signal,
|
|
94
|
+
onProgress: (e) => U(n.id, { progress: e }),
|
|
95
|
+
onState: (e) => C(O, (t) => void (t[n.key] = {
|
|
96
|
+
ref: o,
|
|
97
|
+
name: n.file.name,
|
|
98
|
+
size: n.file.size,
|
|
99
|
+
state: e ?? void 0
|
|
100
|
+
}))
|
|
101
|
+
}, c = await N(async () => {
|
|
102
|
+
try {
|
|
103
|
+
return await l.media.upload(n.file, {
|
|
104
|
+
...s,
|
|
105
|
+
resume: i?.state
|
|
106
|
+
});
|
|
107
|
+
} catch (t) {
|
|
108
|
+
if (i?.state && e(t).code === "resume_mismatch") return l.media.upload(n.file, s);
|
|
109
|
+
throw t;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
U(n.id, { status: "committing" });
|
|
113
|
+
let u = await N(() => l.media.commit(o, [{
|
|
114
|
+
op: "put",
|
|
115
|
+
path: c.path,
|
|
116
|
+
blob: c.blob
|
|
117
|
+
}], { signal: r.signal }));
|
|
118
|
+
C(O, (e) => void delete e[n.key]), U(n.id, {
|
|
119
|
+
status: "done",
|
|
120
|
+
progress: void 0
|
|
121
|
+
});
|
|
122
|
+
let d = u.find((e) => t(e.path, c.path));
|
|
123
|
+
d && H.current.onUploaded?.(o, d);
|
|
124
|
+
} catch (t) {
|
|
125
|
+
let r = e(t);
|
|
126
|
+
r.code === "aborted" ? U(n.id, { status: "paused" }) : (U(n.id, {
|
|
127
|
+
status: "failed",
|
|
128
|
+
error: r
|
|
129
|
+
}), M(r, "upload", n.file.name));
|
|
130
|
+
} finally {
|
|
131
|
+
V.current.delete(n.id);
|
|
132
|
+
}
|
|
133
|
+
}, [
|
|
134
|
+
l,
|
|
135
|
+
w,
|
|
136
|
+
O,
|
|
137
|
+
N,
|
|
138
|
+
U,
|
|
139
|
+
M
|
|
140
|
+
]);
|
|
141
|
+
f(() => {
|
|
142
|
+
let e = P.filter((e) => y.includes(e.status)).length, t = P.filter((e) => e.status === "queued").slice(0, Math.max(0, E - e));
|
|
143
|
+
if (t.length) {
|
|
144
|
+
F((e) => e.map((e) => t.some((t) => t.id === e.id) ? {
|
|
145
|
+
...e,
|
|
146
|
+
status: "creating"
|
|
147
|
+
} : e));
|
|
148
|
+
for (let e of t) W(e);
|
|
149
|
+
}
|
|
150
|
+
}, [
|
|
151
|
+
P,
|
|
152
|
+
E,
|
|
153
|
+
W
|
|
154
|
+
]);
|
|
155
|
+
let G = P.some((e) => y.includes(e.status) || e.status === "queued");
|
|
156
|
+
f(() => {
|
|
157
|
+
if (!G) return;
|
|
158
|
+
let e = (e) => {
|
|
159
|
+
e.preventDefault(), e.returnValue = "";
|
|
160
|
+
};
|
|
161
|
+
return addEventListener("beforeunload", e), () => removeEventListener("beforeunload", e);
|
|
162
|
+
}, [G]), f(() => {
|
|
163
|
+
let e = V.current;
|
|
164
|
+
return () => {
|
|
165
|
+
for (let t of e.values()) t.abort();
|
|
166
|
+
};
|
|
167
|
+
}, []);
|
|
168
|
+
let K = (e) => {
|
|
169
|
+
let t = S(O), n = [];
|
|
170
|
+
for (let r of e) {
|
|
171
|
+
let e = b(r);
|
|
172
|
+
P.some((t) => t.key === e && t.status !== "done") || n.some((t) => t.key === e) || n.push({
|
|
173
|
+
id: `u${++x}`,
|
|
174
|
+
file: r,
|
|
175
|
+
key: e,
|
|
176
|
+
status: "queued",
|
|
177
|
+
ref: t[e]?.ref,
|
|
178
|
+
resumed: !!t[e]
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
n.length && (L((e) => e.filter((e) => !n.some((t) => t.file.name === e.name && t.file.size === e.size))), F((e) => [...e, ...n]));
|
|
182
|
+
}, q = (e) => {
|
|
183
|
+
for (let [t, n] of V.current) (!e || t === e) && n.abort();
|
|
184
|
+
F((t) => t.map((t) => t.status === "queued" && (!e || t.id === e) ? {
|
|
185
|
+
...t,
|
|
186
|
+
status: "paused"
|
|
187
|
+
} : t));
|
|
188
|
+
}, J = (e) => F((t) => t.map((t) => (t.status === "paused" || t.status === "failed") && (!e || t.id === e) ? {
|
|
189
|
+
...t,
|
|
190
|
+
status: "queued",
|
|
191
|
+
error: void 0
|
|
192
|
+
} : t)), Y = (e) => {
|
|
193
|
+
V.current.get(e.id)?.abort(), e.status !== "done" && C(O, (t) => void delete t[e.key]), F((t) => t.filter((t) => t.id !== e.id));
|
|
194
|
+
}, X = (e) => {
|
|
195
|
+
C(O, (t) => {
|
|
196
|
+
for (let [n, r] of Object.entries(t)) r.name === e.name && r.size === e.size && delete t[n];
|
|
197
|
+
}), L((t) => t.filter((t) => t !== e));
|
|
198
|
+
}, Z = P.filter((e) => e.status === "done").length, Q = P.some((e) => y.includes(e.status) || e.status === "queued"), ee = P.some((e) => e.status === "paused" || e.status === "failed"), $ = (e) => e.progress?.total ? Math.round(e.progress.loaded / e.progress.total * 100) : 0;
|
|
199
|
+
return /* @__PURE__ */ _(h, { children: [
|
|
200
|
+
/* @__PURE__ */ _("div", {
|
|
201
|
+
className: v("grid justify-items-center gap-2 rounded-lg border-2 border-dashed border-border p-6 text-center transition-colors", R && "border-primary bg-accent"),
|
|
202
|
+
"data-ckui": "upload-drop",
|
|
203
|
+
onDragOver: (e) => {
|
|
204
|
+
e.preventDefault(), z(!0);
|
|
205
|
+
},
|
|
206
|
+
onDragLeave: () => z(!1),
|
|
207
|
+
onDrop: (e) => {
|
|
208
|
+
e.preventDefault(), z(!1), K(e.dataTransfer.files);
|
|
209
|
+
},
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ g("p", {
|
|
212
|
+
className: "text-muted-foreground",
|
|
213
|
+
children: j("uploadQueue.drop")
|
|
214
|
+
}),
|
|
215
|
+
/* @__PURE__ */ g(o, {
|
|
216
|
+
size: "sm",
|
|
217
|
+
onClick: () => B.current?.click(),
|
|
218
|
+
children: j("uploadQueue.add")
|
|
219
|
+
}),
|
|
220
|
+
/* @__PURE__ */ g("input", {
|
|
221
|
+
ref: B,
|
|
222
|
+
type: "file",
|
|
223
|
+
multiple: !0,
|
|
224
|
+
hidden: !0,
|
|
225
|
+
accept: T,
|
|
226
|
+
"aria-label": j("uploadQueue.add"),
|
|
227
|
+
onChange: (e) => {
|
|
228
|
+
K(e.target.files ?? []), e.target.value = "";
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
]
|
|
232
|
+
}),
|
|
233
|
+
I.length > 0 && /* @__PURE__ */ _("div", {
|
|
234
|
+
className: "grid gap-2 rounded-lg border border-border p-3",
|
|
235
|
+
"data-ckui": "upload-waiting",
|
|
236
|
+
role: "status",
|
|
237
|
+
children: [
|
|
238
|
+
/* @__PURE__ */ g("p", {
|
|
239
|
+
className: "font-medium",
|
|
240
|
+
children: j("uploadQueue.unfinished")
|
|
241
|
+
}),
|
|
242
|
+
/* @__PURE__ */ g("ul", {
|
|
243
|
+
className: "grid gap-1",
|
|
244
|
+
children: I.map((e) => /* @__PURE__ */ _("li", {
|
|
245
|
+
className: "flex flex-wrap items-center gap-2",
|
|
246
|
+
children: [
|
|
247
|
+
/* @__PURE__ */ g("span", {
|
|
248
|
+
className: "min-w-0 flex-1 break-all",
|
|
249
|
+
children: e.name
|
|
250
|
+
}),
|
|
251
|
+
/* @__PURE__ */ g("span", {
|
|
252
|
+
className: "text-xs text-muted-foreground",
|
|
253
|
+
children: n(e.size)
|
|
254
|
+
}),
|
|
255
|
+
/* @__PURE__ */ g(o, {
|
|
256
|
+
size: "xs",
|
|
257
|
+
variant: "ghost",
|
|
258
|
+
onClick: () => X(e),
|
|
259
|
+
children: j("uploadQueue.forget")
|
|
260
|
+
})
|
|
261
|
+
]
|
|
262
|
+
}, `${e.name}:${e.size}`))
|
|
263
|
+
}),
|
|
264
|
+
/* @__PURE__ */ g("p", {
|
|
265
|
+
className: "text-xs text-muted-foreground",
|
|
266
|
+
children: j("uploadQueue.unfinishedHint")
|
|
267
|
+
})
|
|
268
|
+
]
|
|
269
|
+
}),
|
|
270
|
+
P.length > 0 && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", {
|
|
271
|
+
className: "flex flex-wrap items-center gap-2",
|
|
272
|
+
children: [/* @__PURE__ */ g("p", {
|
|
273
|
+
className: "text-muted-foreground",
|
|
274
|
+
"aria-live": "polite",
|
|
275
|
+
children: j("uploadQueue.summary", {
|
|
276
|
+
done: Z,
|
|
277
|
+
total: P.length
|
|
278
|
+
})
|
|
279
|
+
}), /* @__PURE__ */ _("div", {
|
|
280
|
+
className: "ms-auto flex gap-2",
|
|
281
|
+
children: [Q && /* @__PURE__ */ g(o, {
|
|
282
|
+
size: "sm",
|
|
283
|
+
variant: "outline",
|
|
284
|
+
onClick: () => q(),
|
|
285
|
+
children: j("uploadQueue.pauseAll")
|
|
286
|
+
}), ee && /* @__PURE__ */ g(o, {
|
|
287
|
+
size: "sm",
|
|
288
|
+
variant: "outline",
|
|
289
|
+
onClick: () => J(),
|
|
290
|
+
children: j("uploadQueue.resumeAll")
|
|
291
|
+
})]
|
|
292
|
+
})]
|
|
293
|
+
}), /* @__PURE__ */ g("ul", {
|
|
294
|
+
className: "grid divide-y divide-border rounded-lg border border-border",
|
|
295
|
+
children: P.map((e) => /* @__PURE__ */ _("li", {
|
|
296
|
+
className: "grid gap-1.5 p-3",
|
|
297
|
+
"data-ckui": "upload-entry",
|
|
298
|
+
"data-status": e.status,
|
|
299
|
+
children: [
|
|
300
|
+
/* @__PURE__ */ _("div", {
|
|
301
|
+
className: "flex flex-wrap items-center gap-2",
|
|
302
|
+
children: [
|
|
303
|
+
/* @__PURE__ */ g("span", {
|
|
304
|
+
className: "min-w-0 flex-1 font-medium break-all",
|
|
305
|
+
children: e.file.name
|
|
306
|
+
}),
|
|
307
|
+
/* @__PURE__ */ g("span", {
|
|
308
|
+
className: "text-xs text-muted-foreground",
|
|
309
|
+
children: n(e.file.size)
|
|
310
|
+
}),
|
|
311
|
+
e.resumed && e.status !== "done" && /* @__PURE__ */ g(s, {
|
|
312
|
+
variant: "secondary",
|
|
313
|
+
children: j("uploadQueue.resumed")
|
|
314
|
+
}),
|
|
315
|
+
/* @__PURE__ */ g(s, {
|
|
316
|
+
variant: e.status === "failed" ? "destructive" : e.status === "done" ? "outline" : "secondary",
|
|
317
|
+
children: j(`uploadQueue.${e.status}`)
|
|
318
|
+
})
|
|
319
|
+
]
|
|
320
|
+
}),
|
|
321
|
+
e.status === "uploading" && /* @__PURE__ */ g("div", {
|
|
322
|
+
className: "h-1.5 overflow-hidden rounded-full bg-muted",
|
|
323
|
+
role: "progressbar",
|
|
324
|
+
"aria-label": e.file.name,
|
|
325
|
+
"aria-valuenow": $(e),
|
|
326
|
+
"aria-valuemin": 0,
|
|
327
|
+
"aria-valuemax": 100,
|
|
328
|
+
children: /* @__PURE__ */ g("div", {
|
|
329
|
+
className: "h-full bg-primary transition-[width]",
|
|
330
|
+
style: { width: `${$(e)}%` }
|
|
331
|
+
})
|
|
332
|
+
}),
|
|
333
|
+
e.error && /* @__PURE__ */ g(c, { children: A.error(e.error) }),
|
|
334
|
+
/* @__PURE__ */ _("div", {
|
|
335
|
+
className: "flex flex-wrap gap-2",
|
|
336
|
+
children: [
|
|
337
|
+
(y.includes(e.status) || e.status === "queued") && /* @__PURE__ */ g(o, {
|
|
338
|
+
size: "xs",
|
|
339
|
+
variant: "ghost",
|
|
340
|
+
onClick: () => q(e.id),
|
|
341
|
+
children: j("uploadQueue.pause")
|
|
342
|
+
}),
|
|
343
|
+
(e.status === "paused" || e.status === "failed") && /* @__PURE__ */ g(o, {
|
|
344
|
+
size: "xs",
|
|
345
|
+
variant: "outline",
|
|
346
|
+
onClick: () => J(e.id),
|
|
347
|
+
children: j(e.status === "failed" ? "common.retry" : "uploadQueue.resume")
|
|
348
|
+
}),
|
|
349
|
+
e.status !== "done" && /* @__PURE__ */ g(o, {
|
|
350
|
+
size: "xs",
|
|
351
|
+
variant: "ghost",
|
|
352
|
+
onClick: () => Y(e),
|
|
353
|
+
children: j("common.remove")
|
|
354
|
+
})
|
|
355
|
+
]
|
|
356
|
+
})
|
|
357
|
+
]
|
|
358
|
+
}, e.id))
|
|
359
|
+
})] })
|
|
360
|
+
] });
|
|
361
|
+
}
|
|
362
|
+
//#endregion
|
|
363
|
+
export { w as UploadQueue };
|
|
364
|
+
|
|
365
|
+
//# sourceMappingURL=upload-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-queue.js","names":[],"sources":["../../../src/components/admin/upload-queue.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useCallback, useEffect, 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 { samePath, type Progress, type UploadState } from \"../../client/media/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { megabytes } from \"../../i18n/messages.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useErrorReporter, useGuarded } from \"../../react/context.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ErrorLine } from \"../social/parts.js\";\nimport { AdminRoot, type AdminFlowProps } from \"./shell.js\";\n\nexport interface UploadQueueProps extends AdminFlowProps {\n /** Makes the host's item for a file (e.g. a video record) and resolves with its ref; once per file, a reload included. */\n createItem: (file: File) => Promise<RefBody>;\n /** The upload path in each item. Default \"source\". */\n path?: string;\n /** The file picker's accept, e.g. \"video/*\". */\n accept?: string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Each committed file: its item and the upload as an editor reads it. */\n onUploaded?: (ref: RefBody, file: FileInfo) => void;\n /** The localStorage key unfinished uploads are kept under, to resume after a reload. Default \"ckui.uploads\". */\n storageKey?: string;\n}\n\ntype Status = \"queued\" | \"creating\" | \"uploading\" | \"committing\" | \"done\" | \"failed\" | \"paused\";\n\ninterface Entry {\n id: string;\n file: File;\n key: string;\n status: Status;\n progress?: Progress;\n ref?: RefBody;\n error?: ContentKitError;\n /** Resumed from an upload saved before a reload. */\n resumed?: boolean;\n}\n\ninterface Saved {\n ref: RefBody;\n name: string;\n size: number;\n state?: UploadState;\n}\n\nconst ACTIVE: readonly Status[] = [\"creating\", \"uploading\", \"committing\"];\nconst fingerprint = (f: File) => `${f.name}\\u0000${f.size}\\u0000${f.lastModified}`;\nlet seq = 0;\n\nfunction readSaved(key: string): Record<string, Saved> {\n try {\n return JSON.parse(localStorage.getItem(key) ?? \"{}\") as Record<string, Saved>;\n } catch {\n return {};\n }\n}\n\nfunction writeSaved(key: string, fn: (all: Record<string, Saved>) => void) {\n try {\n const all = readSaved(key);\n fn(all);\n if (Object.keys(all).length) localStorage.setItem(key, JSON.stringify(all));\n else localStorage.removeItem(key);\n } catch {\n // Without storage, a reload starts over.\n }\n}\n\n/**\n * Many files, each into its own new item (createItem): uploads in the\n * background with progress, pause, retry and resume. An unfinished upload is\n * kept in localStorage; adding the same file after a reload continues it in\n * the same item. Leaving the page while files upload asks first.\n */\nexport function UploadQueue({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: UploadQueueProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"upload-queue\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Queue {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<UploadQueueProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Queue({ client, createItem, path = \"source\", accept, concurrency = 2, onUploaded, storageKey = \"ckui.uploads\", onError }: Inner) {\n const m = useMessages();\n const { t } = m;\n const report = useErrorReporter(onError);\n const guarded = useGuarded();\n const [entries, setEntries] = useState<Entry[]>([]);\n const [waiting, setWaiting] = useState<Saved[]>(() => Object.values(readSaved(storageKey)));\n const [over, setOver] = useState(false);\n const input = useRef<HTMLInputElement>(null);\n const controllers = useRef(new Map<string, AbortController>());\n const latest = useRef({ createItem, onUploaded });\n useEffect(() => {\n latest.current = { createItem, onUploaded };\n });\n\n const patch = useCallback((id: string, next: Partial<Entry>) => setEntries((es) => es.map((e) => (e.id === id ? { ...e, ...next } : e))), []);\n\n const run = useCallback(\n async (entry: Entry) => {\n const ctl = new AbortController();\n controllers.current.set(entry.id, ctl);\n const saved = readSaved(storageKey)[entry.key];\n try {\n let ref = entry.ref ?? saved?.ref;\n if (!ref) {\n patch(entry.id, { status: \"creating\", error: undefined });\n ref = await latest.current.createItem(entry.file);\n const made = ref;\n writeSaved(storageKey, (all) => void (all[entry.key] = { ref: made, name: entry.file.name, size: entry.file.size }));\n }\n const target = ref;\n patch(entry.id, { status: \"uploading\", ref, error: undefined });\n const remember = (state: UploadState | null) =>\n writeSaved(storageKey, (all) => void (all[entry.key] = { ref: target, name: entry.file.name, size: entry.file.size, state: state ?? undefined }));\n const options = { ref, path, signal: ctl.signal, onProgress: (progress: Progress) => patch(entry.id, { progress }), onState: remember };\n const up = await guarded(async () => {\n try {\n return await client.media.upload(entry.file, { ...options, resume: saved?.state });\n } catch (e) {\n if (saved?.state && toContentKitError(e).code === \"resume_mismatch\") return client.media.upload(entry.file, options);\n throw e;\n }\n });\n patch(entry.id, { status: \"committing\" });\n const files = await guarded(() => client.media.commit(target, [{ op: \"put\", path: up.path, blob: up.blob }], { signal: ctl.signal }));\n writeSaved(storageKey, (all) => void delete all[entry.key]);\n patch(entry.id, { status: \"done\", progress: undefined });\n const file = files.find((f) => samePath(f.path, up.path));\n if (file) latest.current.onUploaded?.(target, file);\n } catch (e) {\n const error = toContentKitError(e);\n if (error.code === \"aborted\") patch(entry.id, { status: \"paused\" });\n else {\n patch(entry.id, { status: \"failed\", error });\n report(error, \"upload\", entry.file.name);\n }\n } finally {\n controllers.current.delete(entry.id);\n }\n },\n [client, path, storageKey, guarded, patch, report],\n );\n\n // Starts queued files while fewer than concurrency run.\n useEffect(() => {\n const active = entries.filter((e) => ACTIVE.includes(e.status)).length;\n const next = entries.filter((e) => e.status === \"queued\").slice(0, Math.max(0, concurrency - active));\n if (!next.length) return;\n setEntries((es) => es.map((e) => (next.some((n) => n.id === e.id) ? { ...e, status: \"creating\" } : e)));\n for (const e of next) void run(e);\n }, [entries, concurrency, run]);\n\n const busy = entries.some((e) => ACTIVE.includes(e.status) || e.status === \"queued\");\n useEffect(() => {\n if (!busy) return;\n const ask = (e: BeforeUnloadEvent) => {\n e.preventDefault();\n e.returnValue = \"\";\n };\n addEventListener(\"beforeunload\", ask);\n return () => removeEventListener(\"beforeunload\", ask);\n }, [busy]);\n useEffect(() => {\n const running = controllers.current;\n return () => {\n for (const c of running.values()) c.abort();\n };\n }, []);\n\n const add = (files: Iterable<File>) => {\n const saved = readSaved(storageKey);\n const added: Entry[] = [];\n for (const file of files) {\n const key = fingerprint(file);\n if (entries.some((e) => e.key === key && e.status !== \"done\") || added.some((e) => e.key === key)) continue;\n added.push({ id: `u${++seq}`, file, key, status: \"queued\", ref: saved[key]?.ref, resumed: !!saved[key] });\n }\n if (!added.length) return;\n setWaiting((w) => w.filter((s) => !added.some((a) => a.file.name === s.name && a.file.size === s.size)));\n setEntries((es) => [...es, ...added]);\n };\n const pause = (id?: string) => {\n for (const [k, c] of controllers.current) if (!id || k === id) c.abort();\n setEntries((es) => es.map((e) => (e.status === \"queued\" && (!id || e.id === id) ? { ...e, status: \"paused\" } : e)));\n };\n const resume = (id?: string) => setEntries((es) => es.map((e) => ((e.status === \"paused\" || e.status === \"failed\") && (!id || e.id === id) ? { ...e, status: \"queued\", error: undefined } : e)));\n const remove = (entry: Entry) => {\n controllers.current.get(entry.id)?.abort();\n if (entry.status !== \"done\") writeSaved(storageKey, (all) => void delete all[entry.key]);\n setEntries((es) => es.filter((e) => e.id !== entry.id));\n };\n const forget = (s: Saved) => {\n writeSaved(storageKey, (all) => {\n for (const [k, v] of Object.entries(all)) if (v.name === s.name && v.size === s.size) delete all[k];\n });\n setWaiting((w) => w.filter((x) => x !== s));\n };\n\n const done = entries.filter((e) => e.status === \"done\").length;\n const pausable = entries.some((e) => ACTIVE.includes(e.status) || e.status === \"queued\");\n const resumable = entries.some((e) => e.status === \"paused\" || e.status === \"failed\");\n const percent = (e: Entry) => (e.progress?.total ? Math.round((e.progress.loaded / e.progress.total) * 100) : 0);\n return (\n <>\n <div\n className={cn(\"grid justify-items-center gap-2 rounded-lg border-2 border-dashed border-border p-6 text-center transition-colors\", over && \"border-primary bg-accent\")}\n data-ckui=\"upload-drop\"\n onDragOver={(e) => {\n e.preventDefault();\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n e.preventDefault();\n setOver(false);\n add(e.dataTransfer.files);\n }}\n >\n <p className=\"text-muted-foreground\">{t(\"uploadQueue.drop\")}</p>\n <Button size=\"sm\" onClick={() => input.current?.click()}>\n {t(\"uploadQueue.add\")}\n </Button>\n <input\n ref={input}\n type=\"file\"\n multiple\n hidden\n accept={accept}\n aria-label={t(\"uploadQueue.add\")}\n onChange={(e) => {\n add(e.target.files ?? []);\n e.target.value = \"\";\n }}\n />\n </div>\n {waiting.length > 0 && (\n <div className=\"grid gap-2 rounded-lg border border-border p-3\" data-ckui=\"upload-waiting\" role=\"status\">\n <p className=\"font-medium\">{t(\"uploadQueue.unfinished\")}</p>\n <ul className=\"grid gap-1\">\n {waiting.map((s) => (\n <li key={`${s.name}:${s.size}`} className=\"flex flex-wrap items-center gap-2\">\n <span className=\"min-w-0 flex-1 break-all\">{s.name}</span>\n <span className=\"text-xs text-muted-foreground\">{megabytes(s.size)}</span>\n <Button size=\"xs\" variant=\"ghost\" onClick={() => forget(s)}>\n {t(\"uploadQueue.forget\")}\n </Button>\n </li>\n ))}\n </ul>\n <p className=\"text-xs text-muted-foreground\">{t(\"uploadQueue.unfinishedHint\")}</p>\n </div>\n )}\n {entries.length > 0 && (\n <>\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-muted-foreground\" aria-live=\"polite\">\n {t(\"uploadQueue.summary\", { done, total: entries.length })}\n </p>\n <div className=\"ms-auto flex gap-2\">\n {pausable && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => pause()}>\n {t(\"uploadQueue.pauseAll\")}\n </Button>\n )}\n {resumable && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => resume()}>\n {t(\"uploadQueue.resumeAll\")}\n </Button>\n )}\n </div>\n </div>\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {entries.map((e) => (\n <li key={e.id} className=\"grid gap-1.5 p-3\" data-ckui=\"upload-entry\" data-status={e.status}>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"min-w-0 flex-1 font-medium break-all\">{e.file.name}</span>\n <span className=\"text-xs text-muted-foreground\">{megabytes(e.file.size)}</span>\n {e.resumed && e.status !== \"done\" && <Badge variant=\"secondary\">{t(\"uploadQueue.resumed\")}</Badge>}\n <Badge variant={e.status === \"failed\" ? \"destructive\" : e.status === \"done\" ? \"outline\" : \"secondary\"}>{t(`uploadQueue.${e.status}`)}</Badge>\n </div>\n {e.status === \"uploading\" && (\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\" role=\"progressbar\" aria-label={e.file.name} aria-valuenow={percent(e)} aria-valuemin={0} aria-valuemax={100}>\n <div className=\"h-full bg-primary transition-[width]\" style={{ width: `${percent(e)}%` }} />\n </div>\n )}\n {e.error && <ErrorLine>{m.error(e.error)}</ErrorLine>}\n <div className=\"flex flex-wrap gap-2\">\n {(ACTIVE.includes(e.status) || e.status === \"queued\") && (\n <Button size=\"xs\" variant=\"ghost\" onClick={() => pause(e.id)}>\n {t(\"uploadQueue.pause\")}\n </Button>\n )}\n {(e.status === \"paused\" || e.status === \"failed\") && (\n <Button size=\"xs\" variant=\"outline\" onClick={() => resume(e.id)}>\n {t(e.status === \"failed\" ? \"common.retry\" : \"uploadQueue.resume\")}\n </Button>\n )}\n {e.status !== \"done\" && (\n <Button size=\"xs\" variant=\"ghost\" onClick={() => remove(e)}>\n {t(\"common.remove\")}\n </Button>\n )}\n </div>\n </li>\n ))}\n </ul>\n </>\n )}\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAmDA,IAAM,IAA4B;CAAC;CAAY;CAAa;AAAY,GAClE,KAAe,MAAY,GAAG,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,gBAChE,IAAM;AAEV,SAAS,EAAU,GAAoC;CACrD,IAAI;EACF,OAAO,KAAK,MAAM,aAAa,QAAQ,CAAG,KAAK,IAAI;CACrD,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,EAAW,GAAa,GAA0C;CACzE,IAAI;EACF,IAAM,IAAM,EAAU,CAAG;EAEzB,AADA,EAAG,CAAG,GACF,OAAO,KAAK,CAAG,CAAC,CAAC,SAAQ,aAAa,QAAQ,GAAK,KAAK,UAAU,CAAG,CAAC,IACrE,aAAa,WAAW,CAAG;CAClC,QAAQ,CAER;AACF;AAQA,SAAgB,EAAY,EAAE,QAAQ,GAAK,YAAS,SAAM,UAAO,eAAY,cAAW,aAAU,aAAU,GAAG,KAAuB;CACpI,IAAM,IAAS,EAAc;EAAE,QAAQ;EAAK;EAAS;CAAK,CAAC;CAC3D,OACE,kBAAC,GAAD;EAAW,MAAK;EAA2B;EAAuB;EAAqB;EAAoB;EAAiB;EAC1H,UAAA,kBAAC,GAAD;GAAO,GAAI;GAAW;EAAS,CAAA;CACtB,CAAA;AAEf;AAIA,SAAS,EAAM,EAAE,WAAQ,eAAY,UAAO,UAAU,WAAQ,iBAAc,GAAG,eAAY,gBAAa,gBAAgB,cAAkB;CACxI,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAS,EAAiB,CAAO,GACjC,IAAU,EAAW,GACrB,CAAC,GAAS,KAAc,EAAkB,CAAC,CAAC,GAC5C,CAAC,GAAS,KAAc,QAAwB,OAAO,OAAO,EAAU,CAAU,CAAC,CAAC,GACpF,CAAC,GAAM,KAAW,EAAS,EAAK,GAChC,IAAQ,EAAyB,IAAI,GACrC,IAAc,kBAAO,IAAI,IAA6B,CAAC,GACvD,IAAS,EAAO;EAAE;EAAY;CAAW,CAAC;CAChD,QAAgB;EACd,EAAO,UAAU;GAAE;GAAY;EAAW;CAC5C,CAAC;CAED,IAAM,IAAQ,GAAa,GAAY,MAAyB,GAAY,MAAO,EAAG,KAAK,MAAO,EAAE,OAAO,IAAK;EAAE,GAAG;EAAG,GAAG;CAAK,IAAI,CAAE,CAAC,GAAG,CAAC,CAAC,GAEtI,IAAM,EACV,OAAO,MAAiB;EACtB,IAAM,IAAM,IAAI,gBAAgB;EAChC,EAAY,QAAQ,IAAI,EAAM,IAAI,CAAG;EACrC,IAAM,IAAQ,EAAU,CAAU,CAAC,CAAC,EAAM;EAC1C,IAAI;GACF,IAAI,IAAM,EAAM,OAAO,GAAO;GAC9B,IAAI,CAAC,GAAK;IAER,AADA,EAAM,EAAM,IAAI;KAAE,QAAQ;KAAY,OAAO,KAAA;IAAU,CAAC,GACxD,IAAM,MAAM,EAAO,QAAQ,WAAW,EAAM,IAAI;IAChD,IAAM,IAAO;IACb,EAAW,IAAa,MAAQ,MAAM,EAAI,EAAM,OAAO;KAAE,KAAK;KAAM,MAAM,EAAM,KAAK;KAAM,MAAM,EAAM,KAAK;IAAK,EAAE;GACrH;GACA,IAAM,IAAS;GACf,EAAM,EAAM,IAAI;IAAE,QAAQ;IAAa;IAAK,OAAO,KAAA;GAAU,CAAC;GAG9D,IAAM,IAAU;IAAE;IAAK;IAAM,QAAQ,EAAI;IAAQ,aAAa,MAAuB,EAAM,EAAM,IAAI,EAAE,YAAS,CAAC;IAAG,UAFlG,MAChB,EAAW,IAAa,MAAQ,MAAM,EAAI,EAAM,OAAO;KAAE,KAAK;KAAQ,MAAM,EAAM,KAAK;KAAM,MAAM,EAAM,KAAK;KAAM,OAAO,KAAS,KAAA;IAAU,EAAE;GACZ,GAChI,IAAK,MAAM,EAAQ,YAAY;IACnC,IAAI;KACF,OAAO,MAAM,EAAO,MAAM,OAAO,EAAM,MAAM;MAAE,GAAG;MAAS,QAAQ,GAAO;KAAM,CAAC;IACnF,SAAS,GAAG;KACV,IAAI,GAAO,SAAS,EAAkB,CAAC,CAAC,CAAC,SAAS,mBAAmB,OAAO,EAAO,MAAM,OAAO,EAAM,MAAM,CAAO;KACnH,MAAM;IACR;GACF,CAAC;GACD,EAAM,EAAM,IAAI,EAAE,QAAQ,aAAa,CAAC;GACxC,IAAM,IAAQ,MAAM,QAAc,EAAO,MAAM,OAAO,GAAQ,CAAC;IAAE,IAAI;IAAO,MAAM,EAAG;IAAM,MAAM,EAAG;GAAK,CAAC,GAAG,EAAE,QAAQ,EAAI,OAAO,CAAC,CAAC;GAEpI,AADA,EAAW,IAAa,MAAQ,KAAK,OAAO,EAAI,EAAM,IAAI,GAC1D,EAAM,EAAM,IAAI;IAAE,QAAQ;IAAQ,UAAU,KAAA;GAAU,CAAC;GACvD,IAAM,IAAO,EAAM,MAAM,MAAM,EAAS,EAAE,MAAM,EAAG,IAAI,CAAC;GACxD,AAAI,KAAM,EAAO,QAAQ,aAAa,GAAQ,CAAI;EACpD,SAAS,GAAG;GACV,IAAM,IAAQ,EAAkB,CAAC;GACjC,AAAI,EAAM,SAAS,YAAW,EAAM,EAAM,IAAI,EAAE,QAAQ,SAAS,CAAC,KAEhE,EAAM,EAAM,IAAI;IAAE,QAAQ;IAAU;GAAM,CAAC,GAC3C,EAAO,GAAO,UAAU,EAAM,KAAK,IAAI;EAE3C,UAAU;GACR,EAAY,QAAQ,OAAO,EAAM,EAAE;EACrC;CACF,GACA;EAAC;EAAQ;EAAM;EAAY;EAAS;EAAO;CAAM,CACnD;CAGA,QAAgB;EACd,IAAM,IAAS,EAAQ,QAAQ,MAAM,EAAO,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,QAC1D,IAAO,EAAQ,QAAQ,MAAM,EAAE,WAAW,QAAQ,CAAC,CAAC,MAAM,GAAG,KAAK,IAAI,GAAG,IAAc,CAAM,CAAC;EAC/F,MAAK,QACV;MAAY,MAAO,EAAG,KAAK,MAAO,EAAK,MAAM,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI;IAAE,GAAG;IAAG,QAAQ;GAAW,IAAI,CAAE,CAAC;GACtG,KAAK,IAAM,KAAK,GAAM,EAAS,CAAC;EADsE;CAExG,GAAG;EAAC;EAAS;EAAa;CAAG,CAAC;CAE9B,IAAM,IAAO,EAAQ,MAAM,MAAM,EAAO,SAAS,EAAE,MAAM,KAAK,EAAE,WAAW,QAAQ;CAUnF,AATA,QAAgB;EACd,IAAI,CAAC,GAAM;EACX,IAAM,KAAO,MAAyB;GAEpC,AADA,EAAE,eAAe,GACjB,EAAE,cAAc;EAClB;EAEA,OADA,iBAAiB,gBAAgB,CAAG,SACvB,oBAAoB,gBAAgB,CAAG;CACtD,GAAG,CAAC,CAAI,CAAC,GACT,QAAgB;EACd,IAAM,IAAU,EAAY;EAC5B,aAAa;GACX,KAAK,IAAM,KAAK,EAAQ,OAAO,GAAG,EAAE,MAAM;EAC5C;CACF,GAAG,CAAC,CAAC;CAEL,IAAM,KAAO,MAA0B;EACrC,IAAM,IAAQ,EAAU,CAAU,GAC5B,IAAiB,CAAC;EACxB,KAAK,IAAM,KAAQ,GAAO;GACxB,IAAM,IAAM,EAAY,CAAI;GACxB,EAAQ,MAAM,MAAM,EAAE,QAAQ,KAAO,EAAE,WAAW,MAAM,KAAK,EAAM,MAAM,MAAM,EAAE,QAAQ,CAAG,KAChG,EAAM,KAAK;IAAE,IAAI,IAAI,EAAE;IAAO;IAAM;IAAK,QAAQ;IAAU,KAAK,EAAM,EAAI,EAAE;IAAK,SAAS,CAAC,CAAC,EAAM;GAAK,CAAC;EAC1G;EACK,EAAM,WACX,GAAY,MAAM,EAAE,QAAQ,MAAM,CAAC,EAAM,MAAM,MAAM,EAAE,KAAK,SAAS,EAAE,QAAQ,EAAE,KAAK,SAAS,EAAE,IAAI,CAAC,CAAC,GACvG,GAAY,MAAO,CAAC,GAAG,GAAI,GAAG,CAAK,CAAC;CACtC,GACM,KAAS,MAAgB;EAC7B,KAAK,IAAM,CAAC,GAAG,MAAM,EAAY,SAAS,CAAI,CAAC,KAAM,MAAM,MAAI,EAAE,MAAM;EACvE,GAAY,MAAO,EAAG,KAAK,MAAO,EAAE,WAAW,aAAa,CAAC,KAAM,EAAE,OAAO,KAAM;GAAE,GAAG;GAAG,QAAQ;EAAS,IAAI,CAAE,CAAC;CACpH,GACM,KAAU,MAAgB,GAAY,MAAO,EAAG,KAAK,OAAQ,EAAE,WAAW,YAAY,EAAE,WAAW,cAAc,CAAC,KAAM,EAAE,OAAO,KAAM;EAAE,GAAG;EAAG,QAAQ;EAAU,OAAO,KAAA;CAAU,IAAI,CAAE,CAAC,GACzL,KAAU,MAAiB;EAG/B,AAFA,EAAY,QAAQ,IAAI,EAAM,EAAE,CAAC,EAAE,MAAM,GACrC,EAAM,WAAW,UAAQ,EAAW,IAAa,MAAQ,KAAK,OAAO,EAAI,EAAM,IAAI,GACvF,GAAY,MAAO,EAAG,QAAQ,MAAM,EAAE,OAAO,EAAM,EAAE,CAAC;CACxD,GACM,KAAU,MAAa;EAI3B,AAHA,EAAW,IAAa,MAAQ;GAC9B,KAAK,IAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,CAAG,GAAG,AAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAM,OAAO,EAAI;EACnG,CAAC,GACD,GAAY,MAAM,EAAE,QAAQ,MAAM,MAAM,CAAC,CAAC;CAC5C,GAEM,IAAO,EAAQ,QAAQ,MAAM,EAAE,WAAW,MAAM,CAAC,CAAC,QAClD,IAAW,EAAQ,MAAM,MAAM,EAAO,SAAS,EAAE,MAAM,KAAK,EAAE,WAAW,QAAQ,GACjF,KAAY,EAAQ,MAAM,MAAM,EAAE,WAAW,YAAY,EAAE,WAAW,QAAQ,GAC9E,KAAW,MAAc,EAAE,UAAU,QAAQ,KAAK,MAAO,EAAE,SAAS,SAAS,EAAE,SAAS,QAAS,GAAG,IAAI;CAC9G,OACE,kBAAA,GAAA,EAAA,UAAA;EACE,kBAAC,OAAD;GACE,WAAW,EAAG,qHAAqH,KAAQ,0BAA0B;GACrK,aAAU;GACV,aAAa,MAAM;IAEjB,AADA,EAAE,eAAe,GACjB,EAAQ,EAAI;GACd;GACA,mBAAmB,EAAQ,EAAK;GAChC,SAAS,MAAM;IAGb,AAFA,EAAE,eAAe,GACjB,EAAQ,EAAK,GACb,EAAI,EAAE,aAAa,KAAK;GAC1B;GAZF,UAAA;IAcE,kBAAC,KAAD;KAAG,WAAU;KAAyB,UAAA,EAAE,kBAAkB;IAAK,CAAA;IAC/D,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAM,SAAS,MAAM;KACnD,UAAA,EAAE,iBAAiB;IACd,CAAA;IACR,kBAAC,SAAD;KACE,KAAK;KACL,MAAK;KACL,UAAA;KACA,QAAA;KACQ;KACR,cAAY,EAAE,iBAAiB;KAC/B,WAAW,MAAM;MAEf,AADA,EAAI,EAAE,OAAO,SAAS,CAAC,CAAC,GACxB,EAAE,OAAO,QAAQ;KACnB;IACD,CAAA;GACE;;EACJ,EAAQ,SAAS,KAChB,kBAAC,OAAD;GAAK,WAAU;GAAiD,aAAU;GAAiB,MAAK;GAAhG,UAAA;IACE,kBAAC,KAAD;KAAG,WAAU;KAAe,UAAA,EAAE,wBAAwB;IAAK,CAAA;IAC3D,kBAAC,MAAD;KAAI,WAAU;KACX,UAAA,EAAQ,KAAK,MACZ,kBAAC,MAAD;MAAgC,WAAU;MAA1C,UAAA;OACE,kBAAC,QAAD;QAAM,WAAU;QAA4B,UAAA,EAAE;OAAW,CAAA;OACzD,kBAAC,QAAD;QAAM,WAAU;QAAiC,UAAA,EAAU,EAAE,IAAI;OAAQ,CAAA;OACzE,kBAAC,GAAD;QAAQ,MAAK;QAAK,SAAQ;QAAQ,eAAe,EAAO,CAAC;QACtD,UAAA,EAAE,oBAAoB;OACjB,CAAA;MACN;KANK,GAAA,GAAG,EAAE,KAAK,GAAG,EAAE,MAMpB,CACL;IACC,CAAA;IACJ,kBAAC,KAAD;KAAG,WAAU;KAAiC,UAAA,EAAE,4BAA4B;IAAK,CAAA;GAC9E;;EAEN,EAAQ,SAAS,KAChB,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;IAAwB,aAAU;IAC5C,UAAA,EAAE,uBAAuB;KAAE;KAAM,OAAO,EAAQ;IAAO,CAAC;GACxD,CAAA,GACH,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACG,KACC,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAU,eAAe,EAAM;KACtD,UAAA,EAAE,sBAAsB;IACnB,CAAA,GAET,MACC,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAU,eAAe,EAAO;KACvD,UAAA,EAAE,uBAAuB;IACpB,CAAA,CAEP;GACF,CAAA,CAAA;EACL,CAAA,GAAA,kBAAC,MAAD;GAAI,WAAU;GACX,UAAA,EAAQ,KAAK,MACZ,kBAAC,MAAD;IAAe,WAAU;IAAmB,aAAU;IAAe,eAAa,EAAE;IAApF,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACE,kBAAC,QAAD;QAAM,WAAU;QAAwC,UAAA,EAAE,KAAK;OAAW,CAAA;OAC1E,kBAAC,QAAD;QAAM,WAAU;QAAiC,UAAA,EAAU,EAAE,KAAK,IAAI;OAAQ,CAAA;OAC7E,EAAE,WAAW,EAAE,WAAW,UAAU,kBAAC,GAAD;QAAO,SAAQ;QAAa,UAAA,EAAE,qBAAqB;OAAS,CAAA;OACjG,kBAAC,GAAD;QAAO,SAAS,EAAE,WAAW,WAAW,gBAAgB,EAAE,WAAW,SAAS,YAAY;QAAc,UAAA,EAAE,eAAe,EAAE,QAAQ;OAAS,CAAA;MACzI;;KACJ,EAAE,WAAW,eACZ,kBAAC,OAAD;MAAK,WAAU;MAA8C,MAAK;MAAc,cAAY,EAAE,KAAK;MAAM,iBAAe,EAAQ,CAAC;MAAG,iBAAe;MAAG,iBAAe;MACnK,UAAA,kBAAC,OAAD;OAAK,WAAU;OAAuC,OAAO,EAAE,OAAO,GAAG,EAAQ,CAAC,EAAE,GAAG;MAAI,CAAA;KACxF,CAAA;KAEN,EAAE,SAAS,kBAAC,GAAD,EAAA,UAAY,EAAE,MAAM,EAAE,KAAK,EAAa,CAAA;KACpD,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;QACI,EAAO,SAAS,EAAE,MAAM,KAAK,EAAE,WAAW,aAC1C,kBAAC,GAAD;QAAQ,MAAK;QAAK,SAAQ;QAAQ,eAAe,EAAM,EAAE,EAAE;QACxD,UAAA,EAAE,mBAAmB;OAChB,CAAA;QAER,EAAE,WAAW,YAAY,EAAE,WAAW,aACtC,kBAAC,GAAD;QAAQ,MAAK;QAAK,SAAQ;QAAU,eAAe,EAAO,EAAE,EAAE;QAC3D,UAAA,EAAE,EAAE,WAAW,WAAW,iBAAiB,oBAAoB;OAC1D,CAAA;OAET,EAAE,WAAW,UACZ,kBAAC,GAAD;QAAQ,MAAK;QAAK,SAAQ;QAAQ,eAAe,EAAO,CAAC;QACtD,UAAA,EAAE,eAAe;OACZ,CAAA;MAEP;;IACH;GA9BK,GAAA,EAAE,EA8BP,CACL;EACC,CAAA,CACJ,EAAA,CAAA;CAEJ,EAAA,CAAA;AAEN"}
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export { UserCommentStanding, useUserCommentStandingAccess, type UserCommentStan
|
|
|
23
23
|
export { PollManager, pollState, usePollManagerAccess, type PollManagerProps } from './components/admin/poll-manager.js';
|
|
24
24
|
export { PostManager, postStatus, usePostManagerAccess, type PostEditorProps, type PostManagerProps, type PostStatus } from './components/admin/post-manager.js';
|
|
25
25
|
export { TaxonomyManager, slugify, useTaxonomyManagerAccess, type TaxonomyKind, type TaxonomyManagerProps, type TaxonomyRelation, } from './components/admin/taxonomy-manager.js';
|
|
26
|
+
export { MediaItemPanel, type MediaItemPanelProps } from './components/admin/media-item-panel.js';
|
|
27
|
+
export { UploadQueue, type UploadQueueProps } from './components/admin/upload-queue.js';
|
|
26
28
|
export { NoAccess, Pager, useConfirm, type AdminFlowProps, type AdminRouteProps, type ConfirmOptions, type ContentKitGuard } from './components/admin/shell.js';
|
|
27
29
|
export type { AdminNavigation, ContentKitAuth, StaffAccess, StaffArea } from './react/admin.js';
|
|
28
30
|
export { createTranslator, defaultMessages, defineMessages, resolveMessages, type MessageKey, type MessageVars, type Translator, type ContentKitUiMessageBundle, type ContentKitUiMessages, type ContentKitUiTranslate, } from './i18n/messages.js';
|