@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.
Files changed (36) hide show
  1. package/README.md +24 -0
  2. package/dist/client/index.d.ts +1 -1
  3. package/dist/client/media/queue.d.ts +1 -1
  4. package/dist/client/media/queue.js +1 -1
  5. package/dist/client/media/queue.js.map +1 -1
  6. package/dist/client.js +2 -2
  7. package/dist/components/admin/media-item-panel.d.ts +27 -0
  8. package/dist/components/admin/media-item-panel.js +397 -0
  9. package/dist/components/admin/media-item-panel.js.map +1 -0
  10. package/dist/components/admin/upload-queue.d.ts +23 -0
  11. package/dist/components/admin/upload-queue.js +365 -0
  12. package/dist/components/admin/upload-queue.js.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +4 -2
  15. package/dist/locales/admin/de.js +51 -0
  16. package/dist/locales/admin/de.js.map +1 -1
  17. package/dist/locales/admin/en.d.ts +51 -0
  18. package/dist/locales/admin/en.js +51 -0
  19. package/dist/locales/admin/en.js.map +1 -1
  20. package/dist/locales/admin/es.js +51 -0
  21. package/dist/locales/admin/es.js.map +1 -1
  22. package/dist/locales/admin/ja.js +51 -0
  23. package/dist/locales/admin/ja.js.map +1 -1
  24. package/dist/locales/admin/ko.js +51 -0
  25. package/dist/locales/admin/ko.js.map +1 -1
  26. package/dist/locales/admin/zh.js +51 -0
  27. package/dist/locales/admin/zh.js.map +1 -1
  28. package/dist/locales/en.d.ts +51 -0
  29. package/dist/react/context.d.ts +1 -1
  30. package/dist/react/context.js.map +1 -1
  31. package/dist/react/folder.js.map +1 -1
  32. package/dist/react/upload.d.ts +10 -10
  33. package/dist/react/upload.js +1 -1
  34. package/dist/react/upload.js.map +1 -1
  35. package/dist/styles.css +1 -1
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -494,6 +494,30 @@ const nav = useMemo(() => ({ pathname: location.pathname, search: location.searc
494
494
  { kind: "character", label: "Characters", relations: [{ relation: "member_of", to: "series", label: "Series" }] }]} />
495
495
  ```
496
496
 
497
+ - **`MediaItemPanel`**: one video item for its editors: encode state and
498
+ progress, failures with their reason, replace the source, subtitle
499
+ sidecars (language, label, forced), the poster from a frame or an image,
500
+ and regenerate (retries failed uploads). Props: `item`, `source`,
501
+ `subtitles` (path pattern, `null` hides), `poster` (`null` hides),
502
+ `posterPreset`, `languages`. Access is the item's: the host's `CanUpload`.
503
+
504
+ ```tsx
505
+ <MediaItemPanel client={contentkit} item={{ kind: "video", id }} languages={["en", "ja"]} />
506
+ ```
507
+
508
+ - **`UploadQueue`**: many files, each into a new item from `createItem(file)`
509
+ (the host's ref): progress, pause, retry, a leave-page guard, and resume:
510
+ an unfinished upload is kept in `localStorage` (`storageKey`), and adding
511
+ the same file after a reload continues it in the same item. Props:
512
+ `createItem`, `path`, `accept`, `concurrency`, `onUploaded`, `storageKey`.
513
+
514
+ ```tsx
515
+ <UploadQueue client={contentkit} accept="video/*" createItem={(f) => api.createVideo(f.name)} onUploaded={(ref) => open(ref)} />
516
+ ```
517
+
518
+ The per-item queue engine behind `useUploadQueue` is `ItemUploadQueue` in
519
+ `/client`.
520
+
497
521
  - **`UserCommentStanding`**: for a user page, the person's site-wide comment
498
522
  ban (ban, replace, lift) and their recent comments (delete, restore). Props:
499
523
  `user` (their id), `name`, `itemHref`, `pageSize`.
@@ -14,7 +14,7 @@ export { CodesClient } from './codes.js';
14
14
  export { call, routeURL, type CallOptions, type Method, type PageQuery, type PathParams, type RouteQuery, type RoutePath } from './route.js';
15
15
  export { MediaApi, type ReadOptions } from './media/api.js';
16
16
  export { MediaClient, backoff, samePath, stem, type CommitOptions, type CommitSource, type ContentFolders, type MediaOptions, type NamedOptions, type NamedUpload, type PlannedPart, type Progress, type PutOptions, type UploadOptions, type UploadState, type UploadedFile, type WaitOptions, } from './media/client.js';
17
- export { UploadQueue, type ItemStatus, type QueueItem, type QueueOptions, type QueueSnapshot } from './media/queue.js';
17
+ export { ItemUploadQueue, type ItemStatus, type QueueItem, type QueueOptions, type QueueSnapshot } from './media/queue.js';
18
18
  export { acceptOf, defaultImage, filesFor, isPattern, ratioLabel, ruleDir, ruleFor, screenFiles, uniqueName, uploadRules, withMediaType, type Screened, } from './media/rules.js';
19
19
  export { READ_CHUNK, chunkOffsets, isProcessing, mergeReads, processing } from './media/windows.js';
20
20
  export { defaultTransport, fetchTransport, xhrTransport, type Transport } from './media/transport.js';
@@ -49,7 +49,7 @@ export interface QueueOptions {
49
49
  * order the user arranges, and commits the uploads as puts appended in that
50
50
  * order. Framework-free; the React hooks wrap it.
51
51
  */
52
- export declare class UploadQueue {
52
+ export declare class ItemUploadQueue {
53
53
  private readonly client;
54
54
  private readonly o;
55
55
  private items;
@@ -269,6 +269,6 @@ var a = 2 ** 31 - 1, o = class {
269
269
  }
270
270
  };
271
271
  //#endregion
272
- export { o as UploadQueue };
272
+ export { o as ItemUploadQueue };
273
273
 
274
274
  //# sourceMappingURL=queue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"queue.js","names":[],"sources":["../../../src/client/media/queue.ts"],"sourcesContent":["import { ContentKitError, toContentKitError } from \"../errors.js\";\nimport type { FileInfo, Op, RefBody } from \"../generated/wire.js\";\nimport { fill } from \"../public.js\";\nimport { samePath, stem, type MediaClient, type Progress, type UploadedFile, type UploadState } from \"./client.js\";\n\nexport type ItemStatus = \"queued\" | \"uploading\" | \"uploaded\" | \"failed\" | \"committed\";\n\nexport interface QueueItem {\n readonly id: string;\n readonly file: File;\n /** The upload path the commit puts; the server's canonical one once uploaded. */\n path: string;\n meta?: Record<string, unknown>;\n status: ItemStatus;\n progress?: Progress;\n error?: ContentKitError;\n result?: UploadedFile;\n /** Resumable multipart state while uploading or after a failure. */\n state?: UploadState;\n /**\n * Committed unattached for processing on upload (the host's\n * ProcessOnUpload): commit() attaches it, remove() discards it.\n */\n unattached?: boolean;\n /** The server's view of an unattached upload while it processes (editor read): dims, pending, failed, progress. */\n processing?: FileInfo;\n /** Processing finished (placed and outputs written, or failed). */\n processed?: boolean;\n}\n\nexport interface QueueSnapshot {\n readonly items: readonly QueueItem[];\n /** A rate, quota or permission refusal: no new uploads start until start(). */\n readonly blocked?: ContentKitError;\n /** Every item is uploaded (or committed) and none is in flight. */\n readonly ready: boolean;\n}\n\nexport interface QueueOptions {\n ref: RefBody;\n /** Each file's upload path: a template whose {name} is the file's name, e.g. \"originals/{name}\". */\n path: string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Start uploads as files are added. Default true. */\n autoStart?: boolean;\n /** Called with every item state change, to persist resumable state. */\n onState?: (item: QueueItem, state: UploadState | null) => void;\n /** Milliseconds between processing polls of unattached uploads. Default 2000. */\n pollInterval?: number;\n}\n\n/** An op index past the end of the list: the upload is appended. */\nconst END = 2 ** 31 - 1;\n\n/**\n * An ordered file queue for one item: uploads in the background, keeps the\n * order the user arranges, and commits the uploads as puts appended in that\n * order. Framework-free; the React hooks wrap it.\n */\nexport class UploadQueue {\n private items: QueueItem[] = [];\n private blocked?: ContentKitError;\n private running = new Map<string, AbortController>();\n private listeners = new Set<() => void>();\n private snap!: QueueSnapshot;\n private started: boolean;\n /** In-flight unattached commits, by item id. */\n private staging = new Map<string, Promise<FileInfo | undefined>>();\n private poll?: ReturnType<typeof setTimeout>;\n private disposed = false;\n private batch?: { input: string; operationID: string };\n\n constructor(\n private readonly client: MediaClient,\n private readonly o: QueueOptions,\n ) {\n this.started = o.autoStart ?? true;\n this.publish();\n }\n\n subscribe = (fn: () => void): (() => void) => {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n };\n\n getSnapshot = (): QueueSnapshot => this.snap;\n\n /** Queues files; state restores a multipart upload saved through onState before a reload. */\n add(\n files: Iterable<File>,\n opts: { path?: (f: File) => string; state?: (f: File) => UploadState | undefined } = {},\n ): QueueItem[] {\n const added = [...files].map(\n (file): QueueItem => ({\n id: crypto.randomUUID(),\n file,\n path: opts.path?.(file) ?? fill(this.o.path, { name: file.name }),\n status: \"queued\",\n state: opts.state?.(file),\n }),\n );\n this.items.push(...added);\n this.changed();\n return added;\n }\n\n /** Moves an item to index (before commit). */\n move(id: string, index: number): void {\n const from = this.items.findIndex((i) => i.id === id);\n if (from < 0) return;\n const [item] = this.items.splice(from, 1);\n this.items.splice(Math.max(0, Math.min(index, this.items.length)), 0, item!);\n this.changed();\n }\n\n update(id: string, patch: Pick<Partial<QueueItem>, \"path\" | \"meta\">): void {\n const item = this.find(id);\n if (item?.unattached && patch.path && patch.path !== item.path) {\n void this.client.commit(this.o.ref, [{ op: \"rename\", path: item.path, to: patch.path }]).then(\n (files) => {\n const f = files.find((x) => samePath(x.path, stem(patch.path!)));\n if (f) this.patch(id, { path: f.path });\n },\n () => {},\n );\n }\n this.patch(id, patch);\n }\n\n /**\n * Removes an item, discarding its multipart upload if one is open, or its\n * unattached upload (the commit removes it and its outputs).\n */\n remove(id: string): void {\n const item = this.find(id);\n if (!item) return;\n this.running.get(id)?.abort();\n if (item.state) void this.client.discard(item.state).catch(() => {});\n const staged = this.staging.get(id);\n if (item.unattached || staged) {\n void (staged ?? Promise.resolve())\n .then((file) => {\n if (file || item.unattached) {\n return this.client.commit(this.o.ref, [{ op: \"remove\", path: file?.path ?? item.path }]);\n }\n })\n .catch(() => {});\n }\n this.o.onState?.(item, null);\n this.items = this.items.filter((i) => i.id !== id);\n this.changed();\n }\n\n /** Re-queues a failed item; a multipart upload resumes from its state. */\n retry(id: string): void {\n this.patch(id, { status: \"queued\", error: undefined });\n }\n\n /** Starts uploading (autoStart false) or lifts a block after a refusal. */\n start(): void {\n this.started = true;\n this.blocked = undefined;\n this.changed();\n }\n\n /** Stops starting uploads and pauses running ones (they stay resumable). */\n pause(): void {\n this.started = false;\n for (const c of this.running.values()) c.abort();\n this.changed();\n }\n\n /**\n * Commits every uploaded item, in queue order, after the item's uploads:\n * unattached ones are attached (no reprocessing), the rest put. With head,\n * only the uploaded items before the first one still uploading or failed\n * (a draft committing files as they finish). Returns the item's attached\n * uploads in order.\n */\n async commit(signal?: AbortSignal, o: { head?: boolean } = {}): Promise<FileInfo[]> {\n await Promise.all(this.staging.values());\n let items = this.items;\n if (o.head) {\n // Only the uploaded head of the queue, so files land in the order added.\n const end = items.findIndex((i) => i.status !== \"uploaded\" && i.status !== \"committed\");\n if (end >= 0) items = items.slice(0, end);\n }\n const ready = items.filter((i) => i.status === \"uploaded\");\n if (ready.length === 0) return [];\n const ops = ready.flatMap((i): Op[] => {\n const attach: Op = { op: \"attach\", path: i.path, index: END, ...(i.meta ? { meta: i.meta } : {}) };\n if (i.unattached) return [attach];\n const put: Op = { op: \"put\", path: i.path, blob: i.result!.blob, create_id: i.id, index: END, ...(i.meta ? { meta: i.meta } : {}) };\n // A lost staging response may hide a successful unattached create.\n return i.result!.processOnUpload ? [put, attach] : [put];\n });\n const sources = Object.fromEntries(ready.map((i) => [i.result!.blob, { file: i.file, type: i.result!.type }]));\n const input = JSON.stringify(ops);\n if (this.batch?.input !== input) this.batch = { input, operationID: crypto.randomUUID() };\n const batch = this.batch;\n const files = await this.client.commit(this.o.ref, ops, {\n signal,\n sources,\n operationID: batch.operationID,\n onState: (body) => {\n batch.input = JSON.stringify(body.ops);\n batch.operationID = body.operation_id;\n let changed = false;\n for (const op of body.ops) {\n if (op.op !== \"put\" || !op.blob) continue;\n const original = ready.find((i) => i.id === op.create_id);\n if (!original?.result) continue;\n const current = this.find(original.id);\n if (current?.result && current.result.blob === original.result.blob && current.result.blob !== op.blob) {\n changed = this.set(current.id, { result: { ...current.result, blob: op.blob } }) || changed;\n }\n }\n if (changed) this.changed();\n },\n });\n for (const i of ready) this.set(i.id, { status: \"committed\", unattached: false });\n this.changed();\n return files.filter((f) => !f.unattached);\n }\n\n /** Aborts running uploads and stops polling; call on unmount. */\n dispose(): void {\n this.started = false;\n this.disposed = true;\n for (const c of this.running.values()) c.abort();\n clearTimeout(this.poll);\n this.listeners.clear();\n }\n\n /** Commits an uploaded file unattached, so the host processes it now. */\n private stage(id: string): void {\n const item = this.find(id);\n if (!item?.result) return;\n const { blob, type } = item.result;\n const op: Op = { op: \"put\", path: item.path, blob, create_id: item.id, unattached: true, ...(item.meta ? { meta: item.meta } : {}) };\n const p = this.client\n .commit(this.o.ref, [op], { sources: { [blob]: { file: item.file, type } } })\n .then(\n (files) => {\n const f = files.find((x) => x.unattached && samePath(x.path, stem(item.path)));\n if (f && this.find(id)) this.patch(id, { unattached: true, path: f.path });\n this.schedulePoll();\n return f;\n },\n (error: unknown) => {\n if (error instanceof ContentKitError && error.code === \"conflict\") {\n this.patch(id, { status: \"failed\", error });\n }\n // Other failures leave it uploaded; commit() retries the same create.\n return undefined;\n },\n )\n .finally(() => this.staging.delete(id));\n this.staging.set(id, p);\n }\n\n private schedulePoll(): void {\n if (this.poll || this.disposed) return;\n this.poll = setTimeout(() => {\n this.poll = undefined;\n void this.refresh().finally(() => {\n if (this.items.some((i) => i.unattached && !i.processed)) this.schedulePoll();\n });\n }, this.o.pollInterval ?? 2000);\n }\n\n /** Reads the processing state of unattached uploads still processing. */\n private async refresh(): Promise<void> {\n const pending = this.items.filter((i) => i.unattached && !i.processed);\n if (pending.length === 0) return;\n let files: FileInfo[];\n try {\n files = (await this.client.read(this.o.ref, { editor: true })).files;\n } catch {\n return;\n }\n const byPath = new Map(files.filter((f) => f.upload).map((f) => [f.path, f]));\n let changed = false;\n for (const i of pending) {\n const f = byPath.get(i.path);\n if (!f) continue;\n changed = this.set(i.id, { processing: f, processed: !!f.failed || (!f.pending?.length && !f.staged) }) || changed;\n }\n if (changed) this.changed();\n }\n\n private find(id: string): QueueItem | undefined {\n return this.items.find((i) => i.id === id);\n }\n\n private patch(id: string, patch: Partial<QueueItem>): void {\n if (this.set(id, patch)) this.changed();\n }\n\n private set(id: string, patch: Partial<QueueItem>): boolean {\n const i = this.items.findIndex((x) => x.id === id);\n if (i >= 0) this.items[i] = { ...this.items[i]!, ...patch };\n return i >= 0;\n }\n\n private changed(): void {\n this.pump();\n this.publish();\n for (const fn of this.listeners) fn();\n }\n\n private publish(): void {\n this.snap = {\n items: [...this.items],\n blocked: this.blocked,\n ready:\n this.running.size === 0 &&\n this.items.length > 0 &&\n this.items.every((i) => i.status === \"uploaded\" || i.status === \"committed\"),\n };\n }\n\n private pump(): void {\n if (!this.started || this.blocked) return;\n const limit = this.o.concurrency ?? 2;\n for (const item of this.items) {\n if (this.running.size >= limit) return;\n if (item.status === \"queued\" && !this.running.has(item.id)) this.run(item.id);\n }\n }\n\n private run(id: string): void {\n const item = this.find(id)!;\n const ctl = new AbortController();\n this.running.set(id, ctl);\n this.set(id, { status: \"uploading\", progress: undefined, error: undefined });\n this.client\n .upload(item.file, {\n ref: this.o.ref,\n path: item.path,\n signal: ctl.signal,\n resume: item.state,\n onProgress: (progress) => this.patch(id, { progress }),\n onState: (state) => {\n this.patch(id, { state: state ?? undefined });\n const cur = this.find(id);\n if (cur) this.o.onState?.(cur, state);\n },\n })\n .then(\n (result) => {\n this.running.delete(id);\n this.patch(id, { status: \"uploaded\", result, path: result.path, state: undefined });\n if (result.processOnUpload) this.stage(id);\n },\n (e: unknown) => {\n this.running.delete(id);\n const error = toContentKitError(e);\n if (error.code === \"aborted\") {\n this.patch(id, { status: \"queued\" });\n return;\n }\n if (error.blocksQueue) {\n // Refused for every file: keep it queued behind the block.\n this.blocked = error;\n this.patch(id, { status: \"queued\", error });\n return;\n }\n this.patch(id, { status: \"failed\", error });\n },\n );\n }\n}\n"],"mappings":";;;;AAqDA,IAAM,IAAM,KAAK,KAAK,GAOT,IAAb,MAAyB;CAcJ;CACA;CAdnB,QAA6B,CAAC;CAC9B;CACA,0BAAkB,IAAI,IAA6B;CACnD,4BAAoB,IAAI,IAAgB;CACxC;CACA;CAEA,0BAAkB,IAAI,IAA2C;CACjE;CACA,WAAmB;CACnB;CAEA,YACE,GACA,GACA;EAEA,AAJiB,KAAA,SAAA,GACA,KAAA,IAAA,GAEjB,KAAK,UAAU,EAAE,aAAa,IAC9B,KAAK,QAAQ;CACf;CAEA,aAAa,OACX,KAAK,UAAU,IAAI,CAAE,SACR,KAAK,UAAU,OAAO,CAAE;CAGvC,oBAAmC,KAAK;CAGxC,IACE,GACA,IAAqF,CAAC,GACzE;EACb,IAAM,IAAQ,CAAC,GAAG,CAAK,CAAC,CAAC,KACtB,OAAqB;GACpB,IAAI,OAAO,WAAW;GACtB;GACA,MAAM,EAAK,OAAO,CAAI,KAAK,EAAK,KAAK,EAAE,MAAM,EAAE,MAAM,EAAK,KAAK,CAAC;GAChE,QAAQ;GACR,OAAO,EAAK,QAAQ,CAAI;EAC1B,EACF;EAGA,OAFA,KAAK,MAAM,KAAK,GAAG,CAAK,GACxB,KAAK,QAAQ,GACN;CACT;CAGA,KAAK,GAAY,GAAqB;EACpC,IAAM,IAAO,KAAK,MAAM,WAAW,MAAM,EAAE,OAAO,CAAE;EACpD,IAAI,IAAO,GAAG;EACd,IAAM,CAAC,KAAQ,KAAK,MAAM,OAAO,GAAM,CAAC;EAExC,AADA,KAAK,MAAM,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAO,KAAK,MAAM,MAAM,CAAC,GAAG,GAAG,CAAK,GAC3E,KAAK,QAAQ;CACf;CAEA,OAAO,GAAY,GAAwD;EACzE,IAAM,IAAO,KAAK,KAAK,CAAE;EAUzB,AATI,GAAM,cAAc,EAAM,QAAQ,EAAM,SAAS,EAAK,QACxD,KAAU,OAAO,OAAO,KAAK,EAAE,KAAK,CAAC;GAAE,IAAI;GAAU,MAAM,EAAK;GAAM,IAAI,EAAM;EAAK,CAAC,CAAC,CAAC,CAAC,MACtF,MAAU;GACT,IAAM,IAAI,EAAM,MAAM,MAAM,EAAS,EAAE,MAAM,EAAK,EAAM,IAAK,CAAC,CAAC;GAC/D,AAAI,KAAG,KAAK,MAAM,GAAI,EAAE,MAAM,EAAE,KAAK,CAAC;EACxC,SACM,CAAC,CACT,GAEF,KAAK,MAAM,GAAI,CAAK;CACtB;CAMA,OAAO,GAAkB;EACvB,IAAM,IAAO,KAAK,KAAK,CAAE;EACzB,IAAI,CAAC,GAAM;EAEX,AADA,KAAK,QAAQ,IAAI,CAAE,CAAC,EAAE,MAAM,GACxB,EAAK,SAAO,KAAU,OAAO,QAAQ,EAAK,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC;EACnE,IAAM,IAAS,KAAK,QAAQ,IAAI,CAAE;EAYlC,CAXI,EAAK,cAAc,OACf,KAAU,QAAQ,QAAQ,EAAA,CAC7B,MAAM,MAAS;GACd,IAAI,KAAQ,EAAK,YACf,OAAO,KAAK,OAAO,OAAO,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI;IAAU,MAAM,GAAM,QAAQ,EAAK;GAAK,CAAC,CAAC;EAE3F,CAAC,CAAC,CACD,YAAY,CAAC,CAAC,GAEnB,KAAK,EAAE,UAAU,GAAM,IAAI,GAC3B,KAAK,QAAQ,KAAK,MAAM,QAAQ,MAAM,EAAE,OAAO,CAAE,GACjD,KAAK,QAAQ;CACf;CAGA,MAAM,GAAkB;EACtB,KAAK,MAAM,GAAI;GAAE,QAAQ;GAAU,OAAO,KAAA;EAAU,CAAC;CACvD;CAGA,QAAc;EAGZ,AAFA,KAAK,UAAU,IACf,KAAK,UAAU,KAAA,GACf,KAAK,QAAQ;CACf;CAGA,QAAc;EACZ,KAAK,UAAU;EACf,KAAK,IAAM,KAAK,KAAK,QAAQ,OAAO,GAAG,EAAE,MAAM;EAC/C,KAAK,QAAQ;CACf;CASA,MAAM,OAAO,GAAsB,IAAwB,CAAC,GAAwB;EAClF,MAAM,QAAQ,IAAI,KAAK,QAAQ,OAAO,CAAC;EACvC,IAAI,IAAQ,KAAK;EACjB,IAAI,EAAE,MAAM;GAEV,IAAM,IAAM,EAAM,WAAW,MAAM,EAAE,WAAW,cAAc,EAAE,WAAW,WAAW;GACtF,AAAI,KAAO,MAAG,IAAQ,EAAM,MAAM,GAAG,CAAG;EAC1C;EACA,IAAM,IAAQ,EAAM,QAAQ,MAAM,EAAE,WAAW,UAAU;EACzD,IAAI,EAAM,WAAW,GAAG,OAAO,CAAC;EAChC,IAAM,IAAM,EAAM,SAAS,MAAY;GACrC,IAAM,IAAa;IAAE,IAAI;IAAU,MAAM,EAAE;IAAM,OAAO;IAAK,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;GAAG;GACjG,IAAI,EAAE,YAAY,OAAO,CAAC,CAAM;GAChC,IAAM,IAAU;IAAE,IAAI;IAAO,MAAM,EAAE;IAAM,MAAM,EAAE,OAAQ;IAAM,WAAW,EAAE;IAAI,OAAO;IAAK,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;GAAG;GAElI,OAAO,EAAE,OAAQ,kBAAkB,CAAC,GAAK,CAAM,IAAI,CAAC,CAAG;EACzD,CAAC,GACK,IAAU,OAAO,YAAY,EAAM,KAAK,MAAM,CAAC,EAAE,OAAQ,MAAM;GAAE,MAAM,EAAE;GAAM,MAAM,EAAE,OAAQ;EAAK,CAAC,CAAC,CAAC,GACvG,IAAQ,KAAK,UAAU,CAAG;EAChC,AAAI,KAAK,OAAO,UAAU,MAAO,KAAK,QAAQ;GAAE;GAAO,aAAa,OAAO,WAAW;EAAE;EACxF,IAAM,IAAQ,KAAK,OACb,IAAQ,MAAM,KAAK,OAAO,OAAO,KAAK,EAAE,KAAK,GAAK;GACtD;GACA;GACA,aAAa,EAAM;GACnB,UAAU,MAAS;IAEjB,AADA,EAAM,QAAQ,KAAK,UAAU,EAAK,GAAG,GACrC,EAAM,cAAc,EAAK;IACzB,IAAI,IAAU;IACd,KAAK,IAAM,KAAM,EAAK,KAAK;KACzB,IAAI,EAAG,OAAO,SAAS,CAAC,EAAG,MAAM;KACjC,IAAM,IAAW,EAAM,MAAM,MAAM,EAAE,OAAO,EAAG,SAAS;KACxD,IAAI,CAAC,GAAU,QAAQ;KACvB,IAAM,IAAU,KAAK,KAAK,EAAS,EAAE;KACrC,AAAI,GAAS,UAAU,EAAQ,OAAO,SAAS,EAAS,OAAO,QAAQ,EAAQ,OAAO,SAAS,EAAG,SAChG,IAAU,KAAK,IAAI,EAAQ,IAAI,EAAE,QAAQ;MAAE,GAAG,EAAQ;MAAQ,MAAM,EAAG;KAAK,EAAE,CAAC,KAAK;IAExF;IACA,AAAI,KAAS,KAAK,QAAQ;GAC5B;EACF,CAAC;EACD,KAAK,IAAM,KAAK,GAAO,KAAK,IAAI,EAAE,IAAI;GAAE,QAAQ;GAAa,YAAY;EAAM,CAAC;EAEhF,OADA,KAAK,QAAQ,GACN,EAAM,QAAQ,MAAM,CAAC,EAAE,UAAU;CAC1C;CAGA,UAAgB;EAEd,AADA,KAAK,UAAU,IACf,KAAK,WAAW;EAChB,KAAK,IAAM,KAAK,KAAK,QAAQ,OAAO,GAAG,EAAE,MAAM;EAE/C,AADA,aAAa,KAAK,IAAI,GACtB,KAAK,UAAU,MAAM;CACvB;CAGA,MAAc,GAAkB;EAC9B,IAAM,IAAO,KAAK,KAAK,CAAE;EACzB,IAAI,CAAC,GAAM,QAAQ;EACnB,IAAM,EAAE,SAAM,YAAS,EAAK,QACtB,IAAS;GAAE,IAAI;GAAO,MAAM,EAAK;GAAM;GAAM,WAAW,EAAK;GAAI,YAAY;GAAM,GAAI,EAAK,OAAO,EAAE,MAAM,EAAK,KAAK,IAAI,CAAC;EAAG,GAC7H,IAAI,KAAK,OACZ,OAAO,KAAK,EAAE,KAAK,CAAC,CAAE,GAAG,EAAE,SAAS,GAAG,IAAO;GAAE,MAAM,EAAK;GAAM;EAAK,EAAE,EAAE,CAAC,CAAC,CAC5E,MACE,MAAU;GACT,IAAM,IAAI,EAAM,MAAM,MAAM,EAAE,cAAc,EAAS,EAAE,MAAM,EAAK,EAAK,IAAI,CAAC,CAAC;GAG7E,OAFI,KAAK,KAAK,KAAK,CAAE,KAAG,KAAK,MAAM,GAAI;IAAE,YAAY;IAAM,MAAM,EAAE;GAAK,CAAC,GACzE,KAAK,aAAa,GACX;EACT,IACC,MAAmB;GAClB,AAAI,aAAiB,KAAmB,EAAM,SAAS,cACrD,KAAK,MAAM,GAAI;IAAE,QAAQ;IAAU;GAAM,CAAC;EAI9C,CACF,CAAC,CACA,cAAc,KAAK,QAAQ,OAAO,CAAE,CAAC;EACxC,KAAK,QAAQ,IAAI,GAAI,CAAC;CACxB;CAEA,eAA6B;EACvB,KAAK,QAAQ,KAAK,aACtB,KAAK,OAAO,iBAAiB;GAE3B,AADA,KAAK,OAAO,KAAA,GACZ,KAAU,QAAQ,CAAC,CAAC,cAAc;IAChC,AAAI,KAAK,MAAM,MAAM,MAAM,EAAE,cAAc,CAAC,EAAE,SAAS,KAAG,KAAK,aAAa;GAC9E,CAAC;EACH,GAAG,KAAK,EAAE,gBAAgB,GAAI;CAChC;CAGA,MAAc,UAAyB;EACrC,IAAM,IAAU,KAAK,MAAM,QAAQ,MAAM,EAAE,cAAc,CAAC,EAAE,SAAS;EACrE,IAAI,EAAQ,WAAW,GAAG;EAC1B,IAAI;EACJ,IAAI;GACF,KAAS,MAAM,KAAK,OAAO,KAAK,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAK,CAAC,EAAA,CAAG;EACjE,QAAQ;GACN;EACF;EACA,IAAM,IAAS,IAAI,IAAI,EAAM,QAAQ,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GACxE,IAAU;EACd,KAAK,IAAM,KAAK,GAAS;GACvB,IAAM,IAAI,EAAO,IAAI,EAAE,IAAI;GACtB,MACL,IAAU,KAAK,IAAI,EAAE,IAAI;IAAE,YAAY;IAAG,WAAW,CAAC,CAAC,EAAE,UAAW,CAAC,EAAE,SAAS,UAAU,CAAC,EAAE;GAAQ,CAAC,KAAK;EAC7G;EACA,AAAI,KAAS,KAAK,QAAQ;CAC5B;CAEA,KAAa,GAAmC;EAC9C,OAAO,KAAK,MAAM,MAAM,MAAM,EAAE,OAAO,CAAE;CAC3C;CAEA,MAAc,GAAY,GAAiC;EACzD,AAAI,KAAK,IAAI,GAAI,CAAK,KAAG,KAAK,QAAQ;CACxC;CAEA,IAAY,GAAY,GAAoC;EAC1D,IAAM,IAAI,KAAK,MAAM,WAAW,MAAM,EAAE,OAAO,CAAE;EAEjD,OADI,KAAK,MAAG,KAAK,MAAM,KAAK;GAAE,GAAG,KAAK,MAAM;GAAK,GAAG;EAAM,IACnD,KAAK;CACd;CAEA,UAAwB;EAEtB,AADA,KAAK,KAAK,GACV,KAAK,QAAQ;EACb,KAAK,IAAM,KAAM,KAAK,WAAW,EAAG;CACtC;CAEA,UAAwB;EACtB,KAAK,OAAO;GACV,OAAO,CAAC,GAAG,KAAK,KAAK;GACrB,SAAS,KAAK;GACd,OACE,KAAK,QAAQ,SAAS,KACtB,KAAK,MAAM,SAAS,KACpB,KAAK,MAAM,OAAO,MAAM,EAAE,WAAW,cAAc,EAAE,WAAW,WAAW;EAC/E;CACF;CAEA,OAAqB;EACnB,IAAI,CAAC,KAAK,WAAW,KAAK,SAAS;EACnC,IAAM,IAAQ,KAAK,EAAE,eAAe;EACpC,KAAK,IAAM,KAAQ,KAAK,OAAO;GAC7B,IAAI,KAAK,QAAQ,QAAQ,GAAO;GAChC,AAAI,EAAK,WAAW,YAAY,CAAC,KAAK,QAAQ,IAAI,EAAK,EAAE,KAAG,KAAK,IAAI,EAAK,EAAE;EAC9E;CACF;CAEA,IAAY,GAAkB;EAC5B,IAAM,IAAO,KAAK,KAAK,CAAE,GACnB,IAAM,IAAI,gBAAgB;EAGhC,AAFA,KAAK,QAAQ,IAAI,GAAI,CAAG,GACxB,KAAK,IAAI,GAAI;GAAE,QAAQ;GAAa,UAAU,KAAA;GAAW,OAAO,KAAA;EAAU,CAAC,GAC3E,KAAK,OACF,OAAO,EAAK,MAAM;GACjB,KAAK,KAAK,EAAE;GACZ,MAAM,EAAK;GACX,QAAQ,EAAI;GACZ,QAAQ,EAAK;GACb,aAAa,MAAa,KAAK,MAAM,GAAI,EAAE,YAAS,CAAC;GACrD,UAAU,MAAU;IAClB,KAAK,MAAM,GAAI,EAAE,OAAO,KAAS,KAAA,EAAU,CAAC;IAC5C,IAAM,IAAM,KAAK,KAAK,CAAE;IACxB,AAAI,KAAK,KAAK,EAAE,UAAU,GAAK,CAAK;GACtC;EACF,CAAC,CAAC,CACD,MACE,MAAW;GAGV,AAFA,KAAK,QAAQ,OAAO,CAAE,GACtB,KAAK,MAAM,GAAI;IAAE,QAAQ;IAAY;IAAQ,MAAM,EAAO;IAAM,OAAO,KAAA;GAAU,CAAC,GAC9E,EAAO,mBAAiB,KAAK,MAAM,CAAE;EAC3C,IACC,MAAe;GACd,KAAK,QAAQ,OAAO,CAAE;GACtB,IAAM,IAAQ,EAAkB,CAAC;GACjC,IAAI,EAAM,SAAS,WAAW;IAC5B,KAAK,MAAM,GAAI,EAAE,QAAQ,SAAS,CAAC;IACnC;GACF;GACA,IAAI,EAAM,aAAa;IAGrB,AADA,KAAK,UAAU,GACf,KAAK,MAAM,GAAI;KAAE,QAAQ;KAAU;IAAM,CAAC;IAC1C;GACF;GACA,KAAK,MAAM,GAAI;IAAE,QAAQ;IAAU;GAAM,CAAC;EAC5C,CACF;CACJ;AACF"}
1
+ {"version":3,"file":"queue.js","names":[],"sources":["../../../src/client/media/queue.ts"],"sourcesContent":["import { ContentKitError, toContentKitError } from \"../errors.js\";\nimport type { FileInfo, Op, RefBody } from \"../generated/wire.js\";\nimport { fill } from \"../public.js\";\nimport { samePath, stem, type MediaClient, type Progress, type UploadedFile, type UploadState } from \"./client.js\";\n\nexport type ItemStatus = \"queued\" | \"uploading\" | \"uploaded\" | \"failed\" | \"committed\";\n\nexport interface QueueItem {\n readonly id: string;\n readonly file: File;\n /** The upload path the commit puts; the server's canonical one once uploaded. */\n path: string;\n meta?: Record<string, unknown>;\n status: ItemStatus;\n progress?: Progress;\n error?: ContentKitError;\n result?: UploadedFile;\n /** Resumable multipart state while uploading or after a failure. */\n state?: UploadState;\n /**\n * Committed unattached for processing on upload (the host's\n * ProcessOnUpload): commit() attaches it, remove() discards it.\n */\n unattached?: boolean;\n /** The server's view of an unattached upload while it processes (editor read): dims, pending, failed, progress. */\n processing?: FileInfo;\n /** Processing finished (placed and outputs written, or failed). */\n processed?: boolean;\n}\n\nexport interface QueueSnapshot {\n readonly items: readonly QueueItem[];\n /** A rate, quota or permission refusal: no new uploads start until start(). */\n readonly blocked?: ContentKitError;\n /** Every item is uploaded (or committed) and none is in flight. */\n readonly ready: boolean;\n}\n\nexport interface QueueOptions {\n ref: RefBody;\n /** Each file's upload path: a template whose {name} is the file's name, e.g. \"originals/{name}\". */\n path: string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Start uploads as files are added. Default true. */\n autoStart?: boolean;\n /** Called with every item state change, to persist resumable state. */\n onState?: (item: QueueItem, state: UploadState | null) => void;\n /** Milliseconds between processing polls of unattached uploads. Default 2000. */\n pollInterval?: number;\n}\n\n/** An op index past the end of the list: the upload is appended. */\nconst END = 2 ** 31 - 1;\n\n/**\n * An ordered file queue for one item: uploads in the background, keeps the\n * order the user arranges, and commits the uploads as puts appended in that\n * order. Framework-free; the React hooks wrap it.\n */\nexport class ItemUploadQueue {\n private items: QueueItem[] = [];\n private blocked?: ContentKitError;\n private running = new Map<string, AbortController>();\n private listeners = new Set<() => void>();\n private snap!: QueueSnapshot;\n private started: boolean;\n /** In-flight unattached commits, by item id. */\n private staging = new Map<string, Promise<FileInfo | undefined>>();\n private poll?: ReturnType<typeof setTimeout>;\n private disposed = false;\n private batch?: { input: string; operationID: string };\n\n constructor(\n private readonly client: MediaClient,\n private readonly o: QueueOptions,\n ) {\n this.started = o.autoStart ?? true;\n this.publish();\n }\n\n subscribe = (fn: () => void): (() => void) => {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n };\n\n getSnapshot = (): QueueSnapshot => this.snap;\n\n /** Queues files; state restores a multipart upload saved through onState before a reload. */\n add(\n files: Iterable<File>,\n opts: { path?: (f: File) => string; state?: (f: File) => UploadState | undefined } = {},\n ): QueueItem[] {\n const added = [...files].map(\n (file): QueueItem => ({\n id: crypto.randomUUID(),\n file,\n path: opts.path?.(file) ?? fill(this.o.path, { name: file.name }),\n status: \"queued\",\n state: opts.state?.(file),\n }),\n );\n this.items.push(...added);\n this.changed();\n return added;\n }\n\n /** Moves an item to index (before commit). */\n move(id: string, index: number): void {\n const from = this.items.findIndex((i) => i.id === id);\n if (from < 0) return;\n const [item] = this.items.splice(from, 1);\n this.items.splice(Math.max(0, Math.min(index, this.items.length)), 0, item!);\n this.changed();\n }\n\n update(id: string, patch: Pick<Partial<QueueItem>, \"path\" | \"meta\">): void {\n const item = this.find(id);\n if (item?.unattached && patch.path && patch.path !== item.path) {\n void this.client.commit(this.o.ref, [{ op: \"rename\", path: item.path, to: patch.path }]).then(\n (files) => {\n const f = files.find((x) => samePath(x.path, stem(patch.path!)));\n if (f) this.patch(id, { path: f.path });\n },\n () => {},\n );\n }\n this.patch(id, patch);\n }\n\n /**\n * Removes an item, discarding its multipart upload if one is open, or its\n * unattached upload (the commit removes it and its outputs).\n */\n remove(id: string): void {\n const item = this.find(id);\n if (!item) return;\n this.running.get(id)?.abort();\n if (item.state) void this.client.discard(item.state).catch(() => {});\n const staged = this.staging.get(id);\n if (item.unattached || staged) {\n void (staged ?? Promise.resolve())\n .then((file) => {\n if (file || item.unattached) {\n return this.client.commit(this.o.ref, [{ op: \"remove\", path: file?.path ?? item.path }]);\n }\n })\n .catch(() => {});\n }\n this.o.onState?.(item, null);\n this.items = this.items.filter((i) => i.id !== id);\n this.changed();\n }\n\n /** Re-queues a failed item; a multipart upload resumes from its state. */\n retry(id: string): void {\n this.patch(id, { status: \"queued\", error: undefined });\n }\n\n /** Starts uploading (autoStart false) or lifts a block after a refusal. */\n start(): void {\n this.started = true;\n this.blocked = undefined;\n this.changed();\n }\n\n /** Stops starting uploads and pauses running ones (they stay resumable). */\n pause(): void {\n this.started = false;\n for (const c of this.running.values()) c.abort();\n this.changed();\n }\n\n /**\n * Commits every uploaded item, in queue order, after the item's uploads:\n * unattached ones are attached (no reprocessing), the rest put. With head,\n * only the uploaded items before the first one still uploading or failed\n * (a draft committing files as they finish). Returns the item's attached\n * uploads in order.\n */\n async commit(signal?: AbortSignal, o: { head?: boolean } = {}): Promise<FileInfo[]> {\n await Promise.all(this.staging.values());\n let items = this.items;\n if (o.head) {\n // Only the uploaded head of the queue, so files land in the order added.\n const end = items.findIndex((i) => i.status !== \"uploaded\" && i.status !== \"committed\");\n if (end >= 0) items = items.slice(0, end);\n }\n const ready = items.filter((i) => i.status === \"uploaded\");\n if (ready.length === 0) return [];\n const ops = ready.flatMap((i): Op[] => {\n const attach: Op = { op: \"attach\", path: i.path, index: END, ...(i.meta ? { meta: i.meta } : {}) };\n if (i.unattached) return [attach];\n const put: Op = { op: \"put\", path: i.path, blob: i.result!.blob, create_id: i.id, index: END, ...(i.meta ? { meta: i.meta } : {}) };\n // A lost staging response may hide a successful unattached create.\n return i.result!.processOnUpload ? [put, attach] : [put];\n });\n const sources = Object.fromEntries(ready.map((i) => [i.result!.blob, { file: i.file, type: i.result!.type }]));\n const input = JSON.stringify(ops);\n if (this.batch?.input !== input) this.batch = { input, operationID: crypto.randomUUID() };\n const batch = this.batch;\n const files = await this.client.commit(this.o.ref, ops, {\n signal,\n sources,\n operationID: batch.operationID,\n onState: (body) => {\n batch.input = JSON.stringify(body.ops);\n batch.operationID = body.operation_id;\n let changed = false;\n for (const op of body.ops) {\n if (op.op !== \"put\" || !op.blob) continue;\n const original = ready.find((i) => i.id === op.create_id);\n if (!original?.result) continue;\n const current = this.find(original.id);\n if (current?.result && current.result.blob === original.result.blob && current.result.blob !== op.blob) {\n changed = this.set(current.id, { result: { ...current.result, blob: op.blob } }) || changed;\n }\n }\n if (changed) this.changed();\n },\n });\n for (const i of ready) this.set(i.id, { status: \"committed\", unattached: false });\n this.changed();\n return files.filter((f) => !f.unattached);\n }\n\n /** Aborts running uploads and stops polling; call on unmount. */\n dispose(): void {\n this.started = false;\n this.disposed = true;\n for (const c of this.running.values()) c.abort();\n clearTimeout(this.poll);\n this.listeners.clear();\n }\n\n /** Commits an uploaded file unattached, so the host processes it now. */\n private stage(id: string): void {\n const item = this.find(id);\n if (!item?.result) return;\n const { blob, type } = item.result;\n const op: Op = { op: \"put\", path: item.path, blob, create_id: item.id, unattached: true, ...(item.meta ? { meta: item.meta } : {}) };\n const p = this.client\n .commit(this.o.ref, [op], { sources: { [blob]: { file: item.file, type } } })\n .then(\n (files) => {\n const f = files.find((x) => x.unattached && samePath(x.path, stem(item.path)));\n if (f && this.find(id)) this.patch(id, { unattached: true, path: f.path });\n this.schedulePoll();\n return f;\n },\n (error: unknown) => {\n if (error instanceof ContentKitError && error.code === \"conflict\") {\n this.patch(id, { status: \"failed\", error });\n }\n // Other failures leave it uploaded; commit() retries the same create.\n return undefined;\n },\n )\n .finally(() => this.staging.delete(id));\n this.staging.set(id, p);\n }\n\n private schedulePoll(): void {\n if (this.poll || this.disposed) return;\n this.poll = setTimeout(() => {\n this.poll = undefined;\n void this.refresh().finally(() => {\n if (this.items.some((i) => i.unattached && !i.processed)) this.schedulePoll();\n });\n }, this.o.pollInterval ?? 2000);\n }\n\n /** Reads the processing state of unattached uploads still processing. */\n private async refresh(): Promise<void> {\n const pending = this.items.filter((i) => i.unattached && !i.processed);\n if (pending.length === 0) return;\n let files: FileInfo[];\n try {\n files = (await this.client.read(this.o.ref, { editor: true })).files;\n } catch {\n return;\n }\n const byPath = new Map(files.filter((f) => f.upload).map((f) => [f.path, f]));\n let changed = false;\n for (const i of pending) {\n const f = byPath.get(i.path);\n if (!f) continue;\n changed = this.set(i.id, { processing: f, processed: !!f.failed || (!f.pending?.length && !f.staged) }) || changed;\n }\n if (changed) this.changed();\n }\n\n private find(id: string): QueueItem | undefined {\n return this.items.find((i) => i.id === id);\n }\n\n private patch(id: string, patch: Partial<QueueItem>): void {\n if (this.set(id, patch)) this.changed();\n }\n\n private set(id: string, patch: Partial<QueueItem>): boolean {\n const i = this.items.findIndex((x) => x.id === id);\n if (i >= 0) this.items[i] = { ...this.items[i]!, ...patch };\n return i >= 0;\n }\n\n private changed(): void {\n this.pump();\n this.publish();\n for (const fn of this.listeners) fn();\n }\n\n private publish(): void {\n this.snap = {\n items: [...this.items],\n blocked: this.blocked,\n ready:\n this.running.size === 0 &&\n this.items.length > 0 &&\n this.items.every((i) => i.status === \"uploaded\" || i.status === \"committed\"),\n };\n }\n\n private pump(): void {\n if (!this.started || this.blocked) return;\n const limit = this.o.concurrency ?? 2;\n for (const item of this.items) {\n if (this.running.size >= limit) return;\n if (item.status === \"queued\" && !this.running.has(item.id)) this.run(item.id);\n }\n }\n\n private run(id: string): void {\n const item = this.find(id)!;\n const ctl = new AbortController();\n this.running.set(id, ctl);\n this.set(id, { status: \"uploading\", progress: undefined, error: undefined });\n this.client\n .upload(item.file, {\n ref: this.o.ref,\n path: item.path,\n signal: ctl.signal,\n resume: item.state,\n onProgress: (progress) => this.patch(id, { progress }),\n onState: (state) => {\n this.patch(id, { state: state ?? undefined });\n const cur = this.find(id);\n if (cur) this.o.onState?.(cur, state);\n },\n })\n .then(\n (result) => {\n this.running.delete(id);\n this.patch(id, { status: \"uploaded\", result, path: result.path, state: undefined });\n if (result.processOnUpload) this.stage(id);\n },\n (e: unknown) => {\n this.running.delete(id);\n const error = toContentKitError(e);\n if (error.code === \"aborted\") {\n this.patch(id, { status: \"queued\" });\n return;\n }\n if (error.blocksQueue) {\n // Refused for every file: keep it queued behind the block.\n this.blocked = error;\n this.patch(id, { status: \"queued\", error });\n return;\n }\n this.patch(id, { status: \"failed\", error });\n },\n );\n }\n}\n"],"mappings":"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"}
package/dist/client.js CHANGED
@@ -13,7 +13,7 @@ import { MediaClient as A, backoff as j, samePath as M, stem as N } from "./clie
13
13
  import { acceptOf as P, defaultImage as F, filesFor as I, isPattern as L, ratioLabel as R, ruleDir as z, ruleFor as B, screenFiles as V, uniqueName as H, uploadRules as U, withMediaType as W } from "./client/media/rules.js";
14
14
  import { centeredCrop as G, constrainCrop as K, editOf as q, editedSize as J, fromRotated as Y, rotation as X, sameEdit as Z, toOriginal as Q, toRotated as $ } from "./client/crop.js";
15
15
  import { encodeRemaining as ee } from "./client/media/encode.js";
16
- import { UploadQueue as te } from "./client/media/queue.js";
16
+ import { ItemUploadQueue as te } from "./client/media/queue.js";
17
17
  import { decodeImage as ne, isAnimatedImage as re } from "./client/image.js";
18
18
  import { READ_CHUNK as ie, chunkOffsets as ae, isProcessing as oe, mergeReads as se, processing as ce } from "./client/media/windows.js";
19
19
  import { ABR_DEFAULTS as le, NETWORK_FAILURES_BEFORE_ERROR as ue, REFRESH_BEFORE_EXPIRY as de, abrHlsConfig as fe, capRung as pe, classifyHlsError as me, classifyMediaError as he, expiryDelay as ge, hlsConfig as _e, initialEstimate as ve, refreshable as ye, startRung as be, statusKind as xe } from "./client/playback.js";
@@ -30,4 +30,4 @@ import { IMAGE_SCHEME as He, PostsClient as Ue, imageRef as We, imageRefs as Ge
30
30
  import { ReactionsClient as Ke } from "./client/content/reactions.js";
31
31
  import { TaxonomyClient as qe } from "./client/taxonomy.js";
32
32
  import { createContentKitClient as Je } from "./client/client.js";
33
- export { le as ABR_DEFAULTS, Ie as BansClient, i as CLIENT_ERROR_CODES, Fe as CodesClient, Re as CommentsClient, a as ContentKitError, e as DEFAULT_DENSITY, o as ERROR_CODES, ze as FavoritesClient, He as IMAGE_SCHEME, S as MAX_PART_SIZE, C as MAX_SINGLE_PUT, w as MIN_PART_SIZE, x as MediaApi, A as MediaClient, Be as ModerationClient, ue as NETWORK_FAILURES_BEFORE_ERROR, ke as PLAYBACK_SPEEDS, Ae as PLAYER_TRACKS_KEY, je as PLAYER_VOLUME_KEY, E as Pacer, Ve as PollsClient, Ue as PostsClient, ie as READ_CHUNK, de as REFRESH_BEFORE_EXPIRY, Ke as ReactionsClient, qe as TaxonomyClient, te as UploadQueue, fe as abrHlsConfig, P as acceptOf, h as aspectOf, j as backoff, _ as call, pe as capRung, G as centeredCrop, y as checkRef, ae as chunkOffsets, me as classifyHlsError, he as classifyMediaError, K as constrainCrop, Je as createContentKitClient, ne as decodeImage, F as defaultImage, D as defaultTransport, t as densityFor, q as editOf, J as editedSize, ee as encodeRemaining, ge as expiryDelay, s as failureError, O as fetchTransport, I as filesFor, d as fill, Se as formatDuration, Y as fromRotated, Ce as galleryItems, _e as hlsConfig, We as imageRef, Ge as imageRefs, ve as initialEstimate, re as isAnimatedImage, we as isAudioType, b as isContentId, c as isContentKitError, Te as isImageType, L as isPattern, oe as isProcessing, Ee as isSubtitleType, De as isVideoType, se as mergeReads, Me as parseSpriteVtt, n as pickRendition, Ne as playerKeyAction, ce as processing, f as publicRenditions, p as publicURL, g as ratio, R as ratioLabel, Le as reactionVerb, l as readContentKitError, ye as refreshable, Pe as resumeAt, X as rotation, v as routeURL, z as ruleDir, B as ruleFor, Z as sameEdit, M as samePath, V as screenFiles, T as sha256Hex, r as sortRenditions, m as srcSet, Oe as stageAspect, be as startRung, xe as statusKind, N as stem, u as toContentKitError, Q as toOriginal, $ as toRotated, H as uniqueName, U as uploadRules, W as withMediaType, k as xhrTransport };
33
+ export { le as ABR_DEFAULTS, Ie as BansClient, i as CLIENT_ERROR_CODES, Fe as CodesClient, Re as CommentsClient, a as ContentKitError, e as DEFAULT_DENSITY, o as ERROR_CODES, ze as FavoritesClient, He as IMAGE_SCHEME, te as ItemUploadQueue, S as MAX_PART_SIZE, C as MAX_SINGLE_PUT, w as MIN_PART_SIZE, x as MediaApi, A as MediaClient, Be as ModerationClient, ue as NETWORK_FAILURES_BEFORE_ERROR, ke as PLAYBACK_SPEEDS, Ae as PLAYER_TRACKS_KEY, je as PLAYER_VOLUME_KEY, E as Pacer, Ve as PollsClient, Ue as PostsClient, ie as READ_CHUNK, de as REFRESH_BEFORE_EXPIRY, Ke as ReactionsClient, qe as TaxonomyClient, fe as abrHlsConfig, P as acceptOf, h as aspectOf, j as backoff, _ as call, pe as capRung, G as centeredCrop, y as checkRef, ae as chunkOffsets, me as classifyHlsError, he as classifyMediaError, K as constrainCrop, Je as createContentKitClient, ne as decodeImage, F as defaultImage, D as defaultTransport, t as densityFor, q as editOf, J as editedSize, ee as encodeRemaining, ge as expiryDelay, s as failureError, O as fetchTransport, I as filesFor, d as fill, Se as formatDuration, Y as fromRotated, Ce as galleryItems, _e as hlsConfig, We as imageRef, Ge as imageRefs, ve as initialEstimate, re as isAnimatedImage, we as isAudioType, b as isContentId, c as isContentKitError, Te as isImageType, L as isPattern, oe as isProcessing, Ee as isSubtitleType, De as isVideoType, se as mergeReads, Me as parseSpriteVtt, n as pickRendition, Ne as playerKeyAction, ce as processing, f as publicRenditions, p as publicURL, g as ratio, R as ratioLabel, Le as reactionVerb, l as readContentKitError, ye as refreshable, Pe as resumeAt, X as rotation, v as routeURL, z as ruleDir, B as ruleFor, Z as sameEdit, M as samePath, V as screenFiles, T as sha256Hex, r as sortRenditions, m as srcSet, Oe as stageAspect, be as startRung, xe as statusKind, N as stem, u as toContentKitError, Q as toOriginal, $ as toRotated, H as uniqueName, U as uploadRules, W as withMediaType, k as xhrTransport };
@@ -0,0 +1,27 @@
1
+ import { RefBody } from '../../client/generated/wire.js';
2
+ import { AdminFlowProps } from './shell.js';
3
+ type Choice = string | {
4
+ value: string;
5
+ label: string;
6
+ };
7
+ export interface MediaItemPanelProps extends AdminFlowProps {
8
+ /** The video item. */
9
+ item: RefBody;
10
+ /** The video's upload path. Default "source". */
11
+ source?: string;
12
+ /** The subtitle uploads' path pattern; null hides them. Default "subs/{name}". */
13
+ subtitles?: string | null;
14
+ /** The poster's upload path (its frames come from the video); null hides it. Default "poster". */
15
+ poster?: string | null;
16
+ /** The poster's public preset. Default "poster". */
17
+ posterPreset?: string;
18
+ /** Subtitle languages offered: codes, or codes with labels. Without them the language is typed. */
19
+ languages?: readonly Choice[];
20
+ }
21
+ /**
22
+ * One video item for its editors: its encode state and progress, the source
23
+ * (replace it), subtitle sidecars (language, label, forced), the poster from
24
+ * a frame or an image, and regenerate, which retries failed uploads.
25
+ */
26
+ export declare function MediaItemPanel({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: MediaItemPanelProps): import('react').JSX.Element;
27
+ export {};
@@ -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