@openrails/contentkit-ui 0.90.3 → 0.92.1
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 +6 -4
- package/dist/client/client.d.ts +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/generated/routes.d.ts +7 -7
- package/dist/client/generated/routes.js +6 -6
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/http.d.ts +13 -8
- package/dist/client/http.js +16 -4
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/media/client.d.ts +3 -3
- package/dist/client/media/client.js +2 -2
- package/dist/client/media/client.js.map +1 -1
- package/dist/components/hover-preview.js +1 -1
- package/dist/components/hover-preview.js.map +1 -1
- package/dist/components/media-gallery.d.ts +2 -2
- package/dist/components/media-gallery.js +4 -4
- package/dist/components/media-gallery.js.map +1 -1
- package/dist/components/video-mini-player.js +1 -1
- package/dist/components/video-mini-player.js.map +1 -1
- package/dist/components/video-player.d.ts +3 -3
- package/dist/components/video-player.js +3 -3
- package/dist/components/video-player.js.map +1 -1
- package/dist/react/gallery.d.ts +6 -3
- package/dist/react/gallery.js +303 -228
- package/dist/react/gallery.js.map +1 -1
- package/dist/react/player.d.ts +4 -6
- package/dist/react/player.js +54 -61
- package/dist/react/player.js.map +1 -1
- package/package.json +2 -2
package/dist/client/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","names":[],"sources":["../../src/client/http.ts"],"sourcesContent":["import { readContentKitError, requestFailure } from \"./errors.js\";\nimport type { ContentChange } from \"./content/types.js\";\nimport { CONTENTKIT_MODULES } from \"./generated/routes.js\";\nimport type { FileInfo, Item, RefBody } from \"./generated/wire.js\";\n\n/** A module's base URL, or one per item (a host that splits uploads by kind). */\nexport type Mount = string | ((ref: RefBody) => string);\n\n/**\n * Where each module lives when the host does not serve them all under\n * baseUrl's fixed sub-paths (CONTENTKIT_MODULES: the API version, then the\n * module's path). A mount is the module's full base URL.\n */\nexport interface ContentKitMounts {\n /** Comments, reactions, favorites, polls, bans, moderation. Default `{baseUrl}/v1`. */\n content?: string;\n /** Content items and catalog totals. Default `{baseUrl}/v1`. */\n contents?: string;\n /** Media reads and HLS playlists. Default `{baseUrl}/v1/media`. */\n media?: Mount;\n /** Uploads and commits. Default `{baseUrl}/v1/media/upload`. */\n upload?: Mount;\n /** Content-code lookup. Default `{baseUrl}/v1/codes`. */\n codes?: string;\n /** Groups. Default `{baseUrl}/v1`. */\n groups?: string;\n /** The site's content model. Default `{baseUrl}/v1`. */\n kinds?: string;\n /** User inbox. Default `{baseUrl}/v1/notifications`. */\n notifications?: string;\n /** Content versions: reader plans, reader metadata, forks, downloads. Default `{baseUrl}/v1`. */\n versions?: string;\n /** Offers, group joins and the library. Default `{baseUrl}/v1`. */\n offers?: string;\n /** Discovery: lists, search, typeahead, similar, recommendations, history, signals. Default `{baseUrl}/v1`. */\n discovery?: string;\n /** Bulk import (a service's). Default `{baseUrl}/v1`. */\n import?: string;\n}\n\nexport type ContentKitModule = keyof ContentKitMounts;\n\n/** A mutation made through the client: the host's cache-invalidation hook gets each one. */\nexport type ContentKitChange =\n /** A commit applied; files are the item's uploads as an editor reads them, without URLs. */\n | { type: \"media.committed\"; ref: RefBody; files: FileInfo[] }\n /** An upload finished processing, as an editor read returns it. */\n | { type: \"media.processed\"; ref: RefBody; file: FileInfo }\n | { type: \"notifications.changed\" }\n | { type: \"notifications.updated\"; item: Item }\n | ContentChange;\n\nexport type TokenSource = () => string | null | undefined | Promise<string | null | undefined>;\n\nexport interface HttpOptions {\n /** Where the host mounts ContentKit's routes (contentkit.Routes.Prefix), e.g. \"/api/contentkit\". */\n baseUrl: string;\n mounts?: ContentKitMounts;\n /** Transport for API calls; pass an authenticating fetch (auth-ui's authFetch). */\n fetch?: typeof fetch;\n /**\n * Bearer for API calls and for HLS playlists on the API origin (hls.js\n * loads them with XHR, not fetch). An authenticating fetch overrides it.\n */\n token?: TokenSource;\n /** Sent as Accept-Language. */\n language?: () => string | null | undefined;\n /** Extra headers per call (CSRF). */\n headers?: () => HeadersInit | Promise<HeadersInit>;\n /** Default \"same-origin\"; \"include\" also sends cookies with HLS requests. */\n credentials?: RequestCredentials;\n}\n\nexport interface RequestOptions {\n method?: string;\n body?: unknown;\n signal?: AbortSignal;\n /** Read the body as a Blob instead of JSON. */\n blob?: boolean;\n keepalive?: boolean;\n}\n\nconst trim = (u: string) => u.replace(/\\/+$/, \"\");\n\n/** The transport every module shares: mounts, auth, language, errors and change events. */\nexport class Http {\n private readonly listeners = new Set<(change: ContentKitChange, scope: string | null) => void>();\n private readScope = { key: \"{}\" };\n private readonly base: string;\n\n constructor(private readonly o: HttpOptions) {\n if (typeof o.baseUrl !== \"string\") throw new Error(\"contentkit: createContentKitClient needs baseUrl\");\n this.base = trim(o.baseUrl);\n }\n\n /** The module's base URL, without a trailing slash. */\n mount(module: ContentKitModule, ref?: RefBody): string {\n const m = this.o.mounts?.[module];\n if (typeof m === \"function\") {\n if (!ref) throw new Error(`contentkit: the ${module} mount is per item; pass a ref`);\n return trim(m(ref));\n }\n if (m !== undefined) return trim(m);\n return this.base + CONTENTKIT_MODULES[module];\n }\n\n async request<T>(url: string, { method, body, signal, blob, keepalive }: RequestOptions = {}): Promise<T> {\n const f = this.o.fetch ?? fetch;\n const headers = new Headers(await this.o.headers?.());\n if (body !== undefined) headers.set(\"Content-Type\", \"application/json\");\n const token = await this.o.token?.();\n if (token && !headers.has(\"Authorization\")) headers.set(\"Authorization\", `Bearer ${token}`);\n const lang = this.o.language?.();\n if (lang) headers.set(\"Accept-Language\", lang);\n let res: Response;\n try {\n res = await f(url, {\n method: method ?? (body === undefined ? \"GET\" : \"POST\"),\n headers,\n body: body === undefined ? null : JSON.stringify(body),\n credentials: this.o.credentials ?? \"same-origin\",\n signal: signal ?? null,\n ...(keepalive ? { keepalive } : {}),\n });\n } catch (err) {\n throw requestFailure(err, signal, `ContentKit API ${new URL(url, \"http://x\").pathname}`);\n }\n if (!res.ok) throw await readContentKitError(res);\n if (blob) return (await res.blob()) as T;\n return (res.status === 204 ? undefined : await res.json()) as T;\n }\n\n /**\n * hls.js `xhrSetup`: opens the request, sends cookies when credentials is\n * \"include\", and adds the bearer only on the API's origin (never a media host).\n */\n readonly xhrSetup = async (xhr: XMLHttpRequest, url: string): Promise<void> => {\n if (!xhr.readyState) xhr.open(\"GET\", url, true);\n if (this.o.credentials === \"include\") xhr.withCredentials = true;\n if (!this.onApiOrigin(url)) return;\n const token = await this.o.token?.();\n if (token) xhr.setRequestHeader(\"Authorization\", `Bearer ${token}`);\n };\n\n setReadScope(key: string): void {\n if (key !== this.readScope.key) this.readScope = { key };\n }\n\n /** Capture before awaiting a write; null tells caches to refetch after a scope change. */\n captureChanges(): (change: ContentKitChange) => void {\n const scope = this.readScope;\n return (change) => this.emit(change, scope === this.readScope ? scope.key : null);\n }\n\n subscribe(listener: (change: ContentKitChange, scope: string | null) => void): () => void {\n this.listeners.add(listener);\n return () => void this.listeners.delete(listener);\n }\n\n private emit(change: ContentKitChange, scope: string | null): void {\n for (const l of this.listeners) {\n try {\n l(change, scope);\n } catch (e) {\n // A host listener's fault must not fail the mutation that succeeded.\n console.error(\"contentkit: change listener failed\", e);\n }\n }\n }\n\n private onApiOrigin(url: string): boolean {\n const here = typeof location === \"undefined\" ? \"http://localhost\" : location.href;\n try {\n return new URL(url, here).origin === new URL(this.base || \"/\", here).origin;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http.js","names":[],"sources":["../../src/client/http.ts"],"sourcesContent":["import { readContentKitError, requestFailure } from \"./errors.js\";\nimport type { ContentChange } from \"./content/types.js\";\nimport { CONTENTKIT_MODULES } from \"./generated/routes.js\";\nimport type { FileInfo, Item, RefBody } from \"./generated/wire.js\";\n\n/** A module's base URL, or one per item (a host that splits uploads by kind). */\nexport type Mount = string | ((ref: RefBody) => string);\n\n/**\n * Where each module lives when the host does not serve them all under\n * baseUrl's fixed sub-paths (CONTENTKIT_MODULES: the API version, then the\n * module's path). A mount is the module's full base URL.\n */\nexport interface ContentKitMounts {\n /** Comments, reactions, favorites, polls, bans, moderation. Default `{baseUrl}/v1`. */\n content?: string;\n /** Content items and catalog totals. Default `{baseUrl}/v1`. */\n contents?: string;\n /** Media reads and HLS playlists. Default `{baseUrl}/v1/media`. */\n media?: Mount;\n /** Uploads and commits. Default `{baseUrl}/v1/media/upload`. */\n upload?: Mount;\n /** Content-code lookup. Default `{baseUrl}/v1/codes`. */\n codes?: string;\n /** Groups. Default `{baseUrl}/v1`. */\n groups?: string;\n /** The site's content model. Default `{baseUrl}/v1`. */\n kinds?: string;\n /** User inbox. Default `{baseUrl}/v1/notifications`. */\n notifications?: string;\n /** Content versions: reader plans, reader metadata, forks, downloads. Default `{baseUrl}/v1`. */\n versions?: string;\n /** Offers, group joins and the library. Default `{baseUrl}/v1`. */\n offers?: string;\n /** Discovery: lists, search, typeahead, similar, recommendations, history, signals. Default `{baseUrl}/v1`. */\n discovery?: string;\n /** Bulk import (a service's). Default `{baseUrl}/v1`. */\n import?: string;\n}\n\nexport type ContentKitModule = keyof ContentKitMounts;\n\n/** A mutation made through the client: the host's cache-invalidation hook gets each one. */\nexport type ContentKitChange =\n /** A commit applied; files are the item's uploads as an editor reads them, without URLs. */\n | { type: \"media.committed\"; ref: RefBody; files: FileInfo[] }\n /** An upload finished processing, as an editor read returns it. */\n | { type: \"media.processed\"; ref: RefBody; file: FileInfo }\n | { type: \"notifications.changed\" }\n | { type: \"notifications.updated\"; item: Item }\n | ContentChange;\n\nexport type TokenSource = () => string | null | undefined | Promise<string | null | undefined>;\n\nexport interface HttpOptions {\n /** Where the host mounts ContentKit's routes (contentkit.Routes.Prefix), e.g. \"/api/contentkit\". */\n baseUrl: string;\n mounts?: ContentKitMounts;\n /** Transport for API calls; pass an authenticating fetch (auth-ui's authFetch). */\n fetch?: typeof fetch;\n /** Bearer for API calls and HLS playlists. An authenticating fetch overrides it. */\n token?: TokenSource;\n /** Sent as Accept-Language. */\n language?: () => string | null | undefined;\n /** Extra headers per call (CSRF). */\n headers?: () => HeadersInit | Promise<HeadersInit>;\n /** Default \"same-origin\"; \"include\" also sends cookies with HLS segment requests. */\n credentials?: RequestCredentials;\n}\n\nexport interface RequestOptions {\n method?: string;\n body?: unknown;\n signal?: AbortSignal;\n /** Read the body as a Blob instead of JSON. */\n blob?: boolean;\n keepalive?: boolean;\n}\n\nconst trim = (u: string) => u.replace(/\\/+$/, \"\");\n\n/** How a player loads HLS: the media client's `hls`. */\nexport interface HlsLoader {\n /** Fetches a playlist or sprite VTT with the app's credentials. */\n playlist(url: string, signal?: AbortSignal): Promise<Response>;\n /** hls.js `xhrSetup` for segments: cookies, never the app's credentials. */\n segment(xhr: XMLHttpRequest, url: string): void;\n}\n\n/** The transport every module shares: mounts, auth, language, errors and change events. */\nexport class Http {\n private readonly listeners = new Set<(change: ContentKitChange, scope: string | null) => void>();\n private readScope = { key: \"{}\" };\n private readonly base: string;\n\n constructor(private readonly o: HttpOptions) {\n if (typeof o.baseUrl !== \"string\") throw new Error(\"contentkit: createContentKitClient needs baseUrl\");\n this.base = trim(o.baseUrl);\n }\n\n /** The module's base URL, without a trailing slash. */\n mount(module: ContentKitModule, ref?: RefBody): string {\n const m = this.o.mounts?.[module];\n if (typeof m === \"function\") {\n if (!ref) throw new Error(`contentkit: the ${module} mount is per item; pass a ref`);\n return trim(m(ref));\n }\n if (m !== undefined) return trim(m);\n return this.base + CONTENTKIT_MODULES[module];\n }\n\n async request<T>(url: string, { method, body, signal, blob, keepalive }: RequestOptions = {}): Promise<T> {\n const f = this.o.fetch ?? fetch;\n const headers = new Headers(await this.o.headers?.());\n if (body !== undefined) headers.set(\"Content-Type\", \"application/json\");\n const token = await this.o.token?.();\n if (token && !headers.has(\"Authorization\")) headers.set(\"Authorization\", `Bearer ${token}`);\n const lang = this.o.language?.();\n if (lang) headers.set(\"Accept-Language\", lang);\n let res: Response;\n try {\n res = await f(url, {\n method: method ?? (body === undefined ? \"GET\" : \"POST\"),\n headers,\n body: body === undefined ? null : JSON.stringify(body),\n credentials: this.o.credentials ?? \"same-origin\",\n signal: signal ?? null,\n ...(keepalive ? { keepalive } : {}),\n });\n } catch (err) {\n throw requestFailure(err, signal, `ContentKit API ${new URL(url, \"http://x\").pathname}`);\n }\n if (!res.ok) throw await readContentKitError(res);\n if (blob) return (await res.blob()) as T;\n return (res.status === 204 ? undefined : await res.json()) as T;\n }\n\n /**\n * How players load this API's HLS. Playlists and sprite VTTs go through\n * the client's fetch, so they carry what its other calls carry (a DPoP\n * proof when that fetch adds one); segments only carry cookies.\n */\n readonly hls: HlsLoader = {\n playlist: async (url, signal) => {\n const init: RequestInit = { credentials: this.o.credentials ?? \"same-origin\", signal: signal ?? null };\n if (!this.onApiOrigin(url)) return fetch(url, init);\n const token = await this.o.token?.();\n return (this.o.fetch ?? fetch)(url, token ? { ...init, headers: { Authorization: `Bearer ${token}` } } : init);\n },\n segment: (xhr, url) => {\n if (!xhr.readyState) xhr.open(\"GET\", url, true);\n if (this.o.credentials === \"include\") xhr.withCredentials = true;\n },\n };\n\n setReadScope(key: string): void {\n if (key !== this.readScope.key) this.readScope = { key };\n }\n\n /** Capture before awaiting a write; null tells caches to refetch after a scope change. */\n captureChanges(): (change: ContentKitChange) => void {\n const scope = this.readScope;\n return (change) => this.emit(change, scope === this.readScope ? scope.key : null);\n }\n\n subscribe(listener: (change: ContentKitChange, scope: string | null) => void): () => void {\n this.listeners.add(listener);\n return () => void this.listeners.delete(listener);\n }\n\n private emit(change: ContentKitChange, scope: string | null): void {\n for (const l of this.listeners) {\n try {\n l(change, scope);\n } catch (e) {\n // A host listener's fault must not fail the mutation that succeeded.\n console.error(\"contentkit: change listener failed\", e);\n }\n }\n }\n\n private onApiOrigin(url: string): boolean {\n const here = typeof location === \"undefined\" ? \"http://localhost\" : location.href;\n try {\n return new URL(url, here).origin === new URL(this.base || \"/\", here).origin;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createContentKitClient, type ContentKitClient, type ContentKitClientOptions } from './client.js';
|
|
2
|
-
export type { ContentKitChange, ContentKitModule, ContentKitMounts, Mount, TokenSource } from './http.js';
|
|
2
|
+
export type { ContentKitChange, ContentKitModule, ContentKitMounts, HlsLoader, Mount, TokenSource } from './http.js';
|
|
3
3
|
export { CLIENT_ERROR_CODES, ContentKitError, ERROR_CODES, failureError, isContentKitError, readContentKitError, toContentKitError, transportFailure, type ContentKitErrorCode, type ContentKitErrorInit, } from './errors.js';
|
|
4
4
|
export { IMAGE_SCHEME, imageRef, imageRefs, ContentsClient, type ManageQuery } from './contents.js';
|
|
5
5
|
export { CatalogClient, type CatalogTotalsQuery } from './catalog.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CommitBody, Edit, FileInfo, InlineImage, Op, PresetRule, ReadResult, RefBody } from '../generated/wire.js';
|
|
2
|
-
import { Http } from '../http.js';
|
|
2
|
+
import { HlsLoader, Http } from '../http.js';
|
|
3
3
|
import { MediaApi, ReadOptions } from './api.js';
|
|
4
4
|
import { Transport } from './transport.js';
|
|
5
5
|
/** Upload tuning for the media module. */
|
|
@@ -219,8 +219,8 @@ export declare class MediaClient {
|
|
|
219
219
|
getFrame(ref: RefBody, path: string, t: number, w?: number, signal?: AbortSignal): Promise<Blob>;
|
|
220
220
|
/** The folder of an HLS ladder from a read's `hls`: `{media}/{kind}/{id}/hls/{dir}`; master.m3u8 and sprite.vtt resolve under it. */
|
|
221
221
|
hlsBase(ref: RefBody, dir: string): string;
|
|
222
|
-
/**
|
|
223
|
-
get
|
|
222
|
+
/** How players load HLS from this API (playlists through the client's fetch); pass it to players as is. */
|
|
223
|
+
get hls(): HlsLoader;
|
|
224
224
|
/**
|
|
225
225
|
* Uploads an image to the item's server-named upload path and commits it
|
|
226
226
|
* (by default waiting until it is processed); resolves with the name content
|
|
@@ -203,8 +203,8 @@ var d = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, f =
|
|
|
203
203
|
hlsBase(e, t) {
|
|
204
204
|
return `${this.api.itemURL(e)}/hls/${t}`;
|
|
205
205
|
}
|
|
206
|
-
get
|
|
207
|
-
return this.http.
|
|
206
|
+
get hls() {
|
|
207
|
+
return this.http.hls;
|
|
208
208
|
}
|
|
209
209
|
async uploadNamed(t, n, r = {}) {
|
|
210
210
|
let i = await this.put(n, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../../src/client/media/client.ts"],"sourcesContent":["import { ContentKitError, aborted, failureError, throwIfAborted, toContentKitError } from \"../errors.js\";\nimport type { CommitBody, Edit, FileInfo, InlineImage, Op, PresetRule, PresignReply, ReadResult, RefBody, RequestReply } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport { call } from \"../route.js\";\nimport { MediaApi, type ReadOptions } from \"./api.js\";\nimport { sha256Hex } from \"./hash.js\";\nimport { Pacer } from \"./pacer.js\";\nimport { itemRef } from \"./ref.js\";\nimport { defaultTransport, type Transport } from \"./transport.js\";\n\n/** Upload tuning for the media module. */\nexport interface MediaOptions {\n /** Sends presigned PUTs. Default XMLHttpRequest (upload progress), else fetch. */\n transport?: Transport;\n /** Parallel parts per multipart upload (the pacer may run fewer). Default 4. */\n concurrency?: number;\n /** Retries per part and per idempotent API call. Default 5. */\n retries?: number;\n /** Delay before retry attempt (0-based); retryAfter is the server's hint in seconds. */\n retryDelay?: (attempt: number, retryAfter?: number) => number;\n /** Seconds one part should take. Default 30. */\n targetPartSeconds?: number;\n}\n\nexport interface Progress {\n /** processing: committed, waiting for the worker (put). */\n phase: \"hashing\" | \"uploading\" | \"completing\" | \"processing\";\n loaded: number;\n total: number;\n}\n\nexport interface UploadOptions {\n ref: RefBody;\n /** The upload path: a literal (\"cover\") or under a pattern (\"originals/001.png\"); the server cleans it and adds the extension. */\n path: string;\n /** Content type; default the file's. */\n type?: string;\n /** Aborting pauses: a multipart upload stays resumable from the last state. */\n signal?: AbortSignal;\n onProgress?: (p: Progress) => void;\n /** State saved from onState, to resume a multipart upload of the same file. */\n resume?: UploadState;\n /** Resumable state after every change; null once the upload completed. Persist it to survive a reload. */\n onState?: (s: UploadState | null) => void;\n}\n\n/** An upload, ready for a put op. */\nexport interface UploadedFile {\n /** The path to commit: cleaned, with an extension, named by the server for a Named upload. */\n path: string;\n /**\n * The name to commit: the folder's identical blob (\"sha256-{hex}-{uuid}\") when\n * exists, else the staged upload (\"u-{uuid}\") the worker hashes and places\n * after the commit.\n */\n blob: string;\n type: string;\n size: number;\n /** The identical file was already in the item's folder; nothing was sent. */\n exists: boolean;\n /** The host processes files on upload: commit it unattached now (the queue does). */\n processOnUpload?: boolean;\n}\n\n/** The put op's fields besides path and blob. */\nexport interface PutOptions {\n /** Refuse to replace an existing upload. Default false. */\n createOnly?: boolean;\n edit?: Edit | null;\n meta?: Record<string, unknown>;\n index?: number;\n /** Wait until the worker processed the upload (waitFor). Default true. */\n wait?: boolean;\n /** How long to wait, ms. */\n timeout?: number;\n /** The upload, before the commit. */\n onUploaded?: (up: UploadedFile) => void;\n}\n\nexport interface WaitOptions {\n signal?: AbortSignal;\n /** Poll interval, ms. Default 1000. */\n interval?: number;\n /** ms; then render_timeout. Default 120000. */\n timeout?: number;\n}\n\nexport interface PlannedPart {\n number: number;\n offset: number;\n size: number;\n sha256?: string;\n}\n\n/** A multipart upload in flight; JSON-serializable. */\nexport interface UploadState {\n ticket: string;\n path: string;\n /** The staged upload (\"u-{uuid}\") the parts write. */\n blob: string;\n type: string;\n size: number;\n ref: RefBody;\n file: { name?: string; lastModified?: number; size: number };\n limits: { minPartSize: number; maxPartSize: number; maxParts: number };\n parts: PlannedPart[];\n processOnUpload?: boolean;\n}\n\n/** An upload to a server-named (Named) path: a post's or poll's image. */\nexport interface NamedOptions {\n /** The requested path; the server names the file. Default the file's name. */\n path?: string;\n signal?: AbortSignal;\n onProgress?: (p: Progress) => void;\n /** Wait until the worker processed it. Default true. */\n wait?: boolean;\n /** How long to wait, ms. */\n timeout?: number;\n /** A crop or rotation the file is committed with. */\n edit?: Edit;\n}\n\n/** An upload to a Named path, committed. */\nexport interface NamedUpload {\n /** The server's name for it (\"i-{uuid}\"), which content routes take. */\n name: string;\n path: string;\n file: FileInfo;\n}\n\n/** The media kind of poll folders (content.Media.PollKind). A content item's folder is its kind's. */\nexport interface ContentFolders {\n /** Default \"poll\". */\n poll?: string;\n}\n\nconst named = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\n/** A file to upload again when its blob is gone at commit; type defaults to the file's. */\nexport type CommitSource = Blob | { file: Blob; type?: string };\n\nexport interface CommitOptions {\n signal?: AbortSignal;\n /** UUID for this complete batch. Keep it with unchanged ops for a later retry; default a new UUID. */\n operationID?: string;\n /** Persist the active batch before sending it, including any replacement after re-upload. Resume with its ops and operation_id. */\n onState?: (batch: CommitBody) => void;\n /** Blob (\"sha256-…\") → the file it was uploaded from. */\n sources?: Record<string, CommitSource>;\n}\n\ntype Uploadable = Blob & { name?: string; lastModified?: number };\n\n/** A path names a file by its full path or, for an upload, its stem (\"cover\" for \"cover.png\"). */\nexport const samePath = (file: string, path: string) => file === path || stem(file) === path;\n\n/** The path without its extension. */\nexport const stem = (path: string) => {\n const i = path.lastIndexOf(\".\");\n return i > path.lastIndexOf(\"/\") + 1 ? path.slice(0, i) : path;\n};\n\n/** The media module: uploads, commits, reads, frames and HLS. */\nexport class MediaClient {\n readonly api: MediaApi;\n private readonly transport: Transport;\n private readonly concurrency: number;\n private readonly retries: number;\n private readonly delay: (attempt: number, retryAfter?: number) => number;\n private readonly targetSeconds: number;\n private presetList?: Promise<PresetRule[]>;\n private readonly bodyDirs = new Map<string, string>();\n\n private readonly folders: Required<ContentFolders>;\n\n constructor(\n private readonly http: Http,\n o: MediaOptions = {},\n folders: ContentFolders = {},\n ) {\n this.folders = { poll: folders.poll ?? \"poll\" };\n this.api = new MediaApi(http);\n this.transport = o.transport ?? defaultTransport;\n this.concurrency = o.concurrency ?? 4;\n this.retries = o.retries ?? 5;\n this.delay = o.retryDelay ?? backoff;\n this.targetSeconds = o.targetPartSeconds ?? 30;\n }\n\n /**\n * Uploads file to the item's folder: it hashes the whole file (off the\n * main thread) and presigns {path, type, size, sha256}, then sends nothing\n * (the folder holds the identical blob), or stages it: one checksum-bound\n * PUT up to 64 MiB, resumable multipart above. A refused presign throws\n * before any bytes move. Commit the result with a put op (or use put());\n * the worker then places a staged upload at the hash of its bytes.\n */\n async upload(file: Uploadable, o: UploadOptions): Promise<UploadedFile> {\n const type = o.type || file.type;\n if (!type) throw new ContentKitError(\"invalid_request\", \"the file has no content type\");\n throwIfAborted(o.signal);\n if (o.resume) return this.resumeMultipart(file, o.resume, o);\n const total = file.size;\n const sha256 = await sha256Hex(file, { signal: o.signal, onProgress: (loaded) => o.onProgress?.({ phase: \"hashing\", loaded, total }) });\n const presign = () => this.api.presign({ ref: o.ref, path: o.path, type, size: total, sha256 }, o.signal);\n let p = await presign();\n const out = (p: PresignReply): UploadedFile => ({ path: p.path, blob: p.blob, type, size: total, exists: !!p.exists, processOnUpload: !!p.process_on_upload });\n if (p.exists) return out(p);\n if (p.multipart) {\n const m = p.multipart;\n const state: UploadState = {\n ticket: m.ticket,\n path: p.path,\n blob: p.blob,\n type,\n size: total,\n ref: o.ref,\n file: fingerprint(file),\n limits: { minPartSize: m.min_part_size, maxPartSize: m.max_part_size, maxParts: m.max_parts },\n parts: [],\n processOnUpload: !!p.process_on_upload,\n };\n return this.multipart(file, state, new Set(), o);\n }\n await this.retry(\n async () => {\n if (p.exists) return;\n await this.transport(p.put!, file, { signal: o.signal, onProgress: (loaded) => o.onProgress?.({ phase: \"uploading\", loaded, total }) });\n },\n o.signal,\n async (err) => {\n // An expired URL: presign again (a Named upload gets a new name).\n if (err.code === \"storage\" && err.status === 403) p = await presign();\n },\n );\n return out(p);\n }\n\n /**\n * Uploads file and commits it as a put to its path (with edit, meta and\n * index), then by default waits until the worker processed it; resolves\n * with the upload as an editor reads it.\n */\n async put(file: Uploadable, o: UploadOptions & PutOptions): Promise<FileInfo> {\n const up = await this.upload(file, o);\n o.onUploaded?.(up);\n const op: Op = { op: \"put\", path: up.path, blob: up.blob };\n if (o.createOnly) op.create_id = crypto.randomUUID();\n if (o.edit) op.edit = o.edit;\n if (o.meta) op.meta = o.meta;\n if (o.index !== undefined) op.index = o.index;\n const files = await this.commit(o.ref, [op], { signal: o.signal, sources: { [up.blob]: { file, type: up.type } } });\n if (o.wait === false) return files.find((f) => f.path === up.path) ?? { path: up.path, type: up.type };\n o.onProgress?.({ phase: \"processing\", loaded: up.size, total: up.size });\n return this.waitFor(o.ref, up.path, { signal: o.signal, timeout: o.timeout });\n }\n\n /**\n * Applies ops to the item in one conditional write; returns its uploads as\n * an editor reads them. With sources (blob → the file uploaded as it), a\n * not_uploaded refusal (the blob expired or is due for cleanup) uploads\n * the affected files again and retries the commit once.\n */\n async commit(ref: RefBody, ops: Op[], o: CommitOptions = {}): Promise<FileInfo[]> {\n const emit = this.http.captureChanges();\n // Snapshot the batch once: retries must not pick up edits to the caller's\n // ops while the first request or its response is still in flight.\n const body: CommitBody = structuredClone({ ref, ops, operation_id: o.operationID ?? crypto.randomUUID() });\n o.onState?.(structuredClone(body));\n let files: FileInfo[];\n try {\n files = (await this.retry(() => this.api.commit(body, o.signal), o.signal)).files;\n } catch (e) {\n if (!(e instanceof ContentKitError) || e.code !== \"not_uploaded\") throw e;\n const sources = o.sources ?? {};\n const puts = body.ops.filter((op) => op.op === \"put\" && op.blob && op.blob in sources);\n const stale = [...new Set(puts.map((op) => op.blob!))].filter((b) => !e.blobs || e.blobs.includes(b));\n if (stale.length === 0) throw e;\n const renamed = new Map<string, string>();\n for (const blob of stale) {\n const src = sources[blob]!;\n const file = src instanceof Blob ? src : src.file;\n const type = src instanceof Blob ? undefined : src.type;\n const op = puts.find((p) => p.blob === blob)!;\n renamed.set(blob, (await this.upload(file, { ref: body.ref, path: op.path!, type, signal: o.signal })).blob);\n }\n const retried = body.ops.map((op) => (op.blob && renamed.has(op.blob) ? { ...op, blob: renamed.get(op.blob) } : op));\n // Re-uploading changes the inputs. This is a new batch, not a retry\n // under the old ID; transport retries retain its new identity.\n const replacement = { ref: body.ref, ops: retried, operation_id: crypto.randomUUID() };\n o.onState?.(structuredClone(replacement));\n files = (await this.retry(() => this.api.commit(replacement, o.signal), o.signal)).files;\n }\n emit({ type: \"media.committed\", ref: itemRef(ref), files });\n return files;\n }\n\n /** The read API: the item's files under prefix in manifest order, with signed URLs for what this viewer may have. */\n async read(ref: RefBody, o: ReadOptions & { signal?: AbortSignal } = {}): Promise<ReadResult> {\n this.api.itemURL(ref); // a bad ref fails at once\n return this.retry(() => this.api.read(ref, o, o.signal), o.signal);\n }\n\n /**\n * Polls an editor read until the upload at path (or with that stem) is\n * processed: placed (not staged), nothing pending and its blob present (a\n * frame grabbed).\n * Rejects with its failure, not_found once it is gone, too_large while the\n * item is full (nothing is processed until uploads are removed),\n * render_timeout after the timeout.\n */\n async waitFor(ref: RefBody, path: string, o: WaitOptions = {}): Promise<FileInfo> {\n const emit = this.http.captureChanges();\n const until = Date.now() + (o.timeout ?? 120_000);\n for (;;) {\n const r = await this.read(ref, { editor: true, prefix: stem(path), signal: o.signal });\n const f = r.files.find((x) => x.upload && samePath(x.path, path));\n if (!f) throw new ContentKitError(\"not_found\", `no upload ${path}`, { status: 404 });\n if (f.failed) throw failureError(f.failed);\n if (!f.pending?.length && !f.staged && (f.size ?? 0) > 0) {\n emit({ type: \"media.processed\", ref: itemRef(ref), file: f });\n return f;\n }\n if (r.full) throw new ContentKitError(\"too_large\", `${path} waits: the item is full; remove uploads to process more`, { status: 413 });\n if (Date.now() >= until) throw new ContentKitError(\"render_timeout\", `${path} is still processing`);\n await sleep(o.interval ?? 1000, o.signal);\n }\n }\n\n /**\n * The upload's editor view, for re-cropping: its URL and the upload's\n * oriented size. Waits while it renders (an editor read asks for it);\n * not_found when the path has no upload, render_timeout after the timeout\n * (default 30 s).\n */\n async editorView(ref: RefBody, path: string, o: WaitOptions = {}): Promise<{ url: string; width: number; height: number; edit?: Edit }> {\n const until = Date.now() + (o.timeout ?? 30_000);\n for (;;) {\n const r = await this.read(ref, { editor: true, prefix: stem(path), signal: o.signal });\n const f = r.files.find((x) => x.upload && samePath(x.path, path));\n if (!f) throw new ContentKitError(\"not_found\", `no upload ${path}`, { status: 404 });\n if (f.editor_url && f.w && f.h) return { url: f.editor_url, width: f.w, height: f.h, ...(f.edit ? { edit: f.edit } : {}) };\n if (f.failed) throw failureError(f.failed);\n if (Date.now() >= until) throw new ContentKitError(\"render_timeout\", `the editor view of ${path} is still rendering`);\n await sleep(o.interval ?? 1000, o.signal);\n }\n }\n\n /**\n * Every kind's public presets, fetched once per client: crop bounds,\n * widths and default images. An item's current image is in its read's\n * `public`, never built from a preset. Shared by every caller, so it takes\n * no signal; a failed fetch is retried on the next call.\n */\n presets(): Promise<PresetRule[]> {\n this.presetList ??= this.retry(() => this.api.presets()).catch((e: unknown) => {\n this.presetList = undefined;\n throw e;\n });\n return this.presetList;\n }\n\n /** A JPEG still of the video upload at path, t seconds in, w pixels wide (default the frame's). */\n getFrame(ref: RefBody, path: string, t: number, w?: number, signal?: AbortSignal): Promise<Blob> {\n return this.retry(() => this.api.frame(ref, path, t, w, signal), signal);\n }\n\n /** The folder of an HLS ladder from a read's `hls`: `{media}/{kind}/{id}/hls/{dir}`; master.m3u8 and sprite.vtt resolve under it. */\n hlsBase(ref: RefBody, dir: string): string {\n return `${this.api.itemURL(ref)}/hls/${dir}`;\n }\n\n /** hls.js `xhrSetup` for playlists on the API (the client's token and credentials); pass it to players as is. */\n get xhrSetup(): (xhr: XMLHttpRequest, url: string) => Promise<void> {\n return this.http.xhrSetup;\n }\n\n /**\n * Uploads an image to the item's server-named upload path and commits it\n * (by default waiting until it is processed); resolves with the name content\n * routes take: a poll or option image, a post cover.\n */\n async uploadNamed(ref: RefBody, file: Uploadable, o: NamedOptions = {}): Promise<NamedUpload> {\n const f = await this.put(file, { ref, path: o.path ?? (file.name || \"image\"), signal: o.signal, onProgress: o.onProgress, wait: o.wait, timeout: o.timeout, edit: o.edit });\n const name = stem(f.path.slice(f.path.lastIndexOf(\"/\") + 1));\n if (!named.test(name)) throw new ContentKitError(\"invalid_request\", `${f.path} is not a server-named upload path of ${ref.kind}`);\n return { name, path: f.path, file: f };\n }\n\n /**\n * An image for a content item's rich-text body, uploaded to a version's\n * body upload (the kind's `body` upload rule), a private file only viewers\n * who open the version read, and copied into the other versions; resolves\n * with its reference to store in the body (`ref`) and a URL that shows it\n * to the editor now (`url`, null while none exists).\n */\n async uploadInline(item: RefBody, file: Uploadable, o: NamedOptions = {}): Promise<NamedUpload & InlineImage> {\n if (!item.version) throw new ContentKitError(\"invalid_request\", \"body images live in a version\");\n const up = await this.uploadNamed(item, file, { ...o, path: o.path ?? (await this.bodyPath(item, file)) });\n const r: InlineImage = await call(this.http, \"POST\", \"/contents/{id}/images\", { params: { id: item.id }, body: { image: up.name, version: item.version }, signal: o.signal });\n return { ...up, ...r };\n }\n\n /** The upload path of file as a body image of item's kind, from its editor read's body rule. */\n private async bodyPath(item: RefBody, file: Uploadable): Promise<string> {\n let dir = this.bodyDirs.get(item.kind);\n if (dir === undefined) {\n const { uploads } = await this.read(item, { editor: true, limit: 1 });\n // Non-editors get the reader view, which carries no upload rules.\n if (!uploads) throw new ContentKitError(\"forbidden\", `only editors of ${item.kind} ${item.id} upload to it`);\n const rule = uploads.find((r) => r.body);\n if (!rule) throw new ContentKitError(\"invalid_request\", `kind ${item.kind} takes no body images`);\n dir = rule.path.endsWith(\"{name}\") ? rule.path.slice(0, -\"{name}\".length) : rule.path;\n this.bodyDirs.set(item.kind, dir);\n }\n return dir + (file.name || \"image\");\n }\n\n /** The upload path of file for kind's public preset (its first without one): the preset's source, its {name} the file's. */\n async presetPath(kind: string, preset: string | undefined, file: Uploadable): Promise<string> {\n const name = file.name || \"image\";\n const rule = (await this.presets()).find((p) => p.kind === kind && (!preset || p.name === preset));\n if (!rule) return name;\n return rule.from.endsWith(\"{name}\") ? rule.from.slice(0, -\"{name}\".length) + name : rule.from;\n }\n\n /** The media folder of a poll. */\n pollRef(id: string): RefBody {\n return { kind: this.folders.poll, id };\n }\n\n /** Discards a paused multipart upload. */\n async discard(state: UploadState): Promise<void> {\n await this.api.abort(state.ref, { ticket: state.ticket });\n }\n\n private async resumeMultipart(file: Uploadable, state: UploadState, o: UploadOptions): Promise<UploadedFile> {\n const fp = fingerprint(file);\n if (fp.size !== state.size || fp.name !== state.file.name || fp.lastModified !== state.file.lastModified) {\n throw new ContentKitError(\"resume_mismatch\", \"the saved upload is for a different file\");\n }\n let listed;\n try {\n listed = await this.retry(() => this.api.listParts(state.ref, { ticket: state.ticket }, o.signal), o.signal);\n } catch (err) {\n // Gone: already completed (Complete is idempotent) or expired.\n if (err instanceof ContentKitError && err.code === \"not_found\") return this.complete(state, o);\n throw err;\n }\n const landed = new Set<number>();\n for (const l of listed.parts) {\n const plan = state.parts.find((p) => p.number === l.number);\n if (!plan || plan.size !== l.size || (l.sha256 && plan.sha256 !== l.sha256)) {\n // Parts this state does not describe: the upload cannot be finished consistently.\n await this.discard(state).catch(() => {});\n o.onState?.(null);\n return this.upload(file, { ...o, resume: undefined });\n }\n landed.add(l.number);\n }\n return this.multipart(file, structuredClone(state), landed, o);\n }\n\n private async multipart(file: Uploadable, state: UploadState, landed: Set<number>, o: UploadOptions): Promise<UploadedFile> {\n const pacer = new Pacer({ ...state.limits, maxConcurrency: this.concurrency, targetSeconds: this.targetSeconds });\n const emitState = () => o.onState?.(structuredClone(state));\n const sending = new Map<number, number>();\n let sent = state.parts.filter((p) => landed.has(p.number)).reduce((n, p) => n + p.size, 0);\n const emitProgress = () => {\n let loaded = sent;\n for (const n of sending.values()) loaded += n;\n o.onProgress?.({ phase: \"uploading\", loaded, total: state.size });\n };\n const pending = state.parts.filter((p) => !landed.has(p.number));\n let offset = state.parts.reduce((end, p) => Math.max(end, p.offset + p.size), 0);\n const next = (): PlannedPart | undefined => {\n const p = pending.shift();\n if (p || offset >= state.size) return p;\n const part: PlannedPart = {\n number: state.parts.length + 1,\n offset,\n size: Math.min(pacer.partSize(), state.size - offset),\n };\n offset += part.size;\n state.parts.push(part);\n return part;\n };\n\n const stop = new AbortController();\n const signal = o.signal ? AbortSignal.any([o.signal, stop.signal]) : stop.signal;\n // Parts hash and presign ahead of the PUT slots, so a freed slot starts\n // its next PUT at once; pacer.concurrency bounds only the PUTs.\n let putting = 0;\n const waiting: (() => void)[] = [];\n const acquire = () =>\n new Promise<void>((resolve, reject) => {\n const take = () => {\n signal.removeEventListener(\"abort\", drop);\n putting++;\n resolve();\n };\n const drop = () => {\n waiting.splice(waiting.indexOf(take) >>> 0, 1);\n reject(aborted(signal));\n };\n if (putting < pacer.concurrency) return take();\n if (signal.aborted) return reject(aborted(signal));\n signal.addEventListener(\"abort\", drop, { once: true });\n waiting.push(take);\n });\n const release = () => {\n putting--;\n while (putting < pacer.concurrency && waiting.length) waiting.shift()!();\n };\n const run = async (part: PlannedPart) => {\n const body = file.slice(part.offset, part.offset + part.size);\n await this.retry(async () => {\n if (!part.sha256) {\n part.sha256 = await sha256Hex(body, { signal });\n emitState();\n }\n const { parts } = await this.api.parts(\n state.ref,\n { ticket: state.ticket, parts: [{ number: part.number, size: part.size, sha256: part.sha256 }] },\n signal,\n );\n const req = parts[0]?.request as RequestReply;\n await acquire();\n const began = performance.now();\n const alongside = putting;\n try {\n await this.transport(req, body, {\n signal,\n onProgress: (n) => {\n sending.set(part.number, n);\n emitProgress();\n },\n });\n } finally {\n sending.delete(part.number);\n release();\n }\n pacer.record(part.size, performance.now() - began, alongside);\n sent += part.size;\n emitProgress();\n }, signal);\n };\n\n emitState();\n emitProgress();\n const inFlight = new Set<Promise<void>>();\n let failure: unknown;\n for (;;) {\n let part: PlannedPart | undefined;\n while (failure === undefined && inFlight.size < pacer.concurrency + 2 && (part = next())) {\n emitState();\n const job: Promise<void> = run(part).then(\n () => void inFlight.delete(job),\n (err) => {\n inFlight.delete(job);\n if (failure === undefined) {\n failure = err;\n stop.abort();\n }\n },\n );\n inFlight.add(job);\n }\n if (inFlight.size === 0) break;\n await Promise.race(inFlight);\n }\n if (failure !== undefined) {\n throw o.signal?.aborted ? aborted(o.signal) : failure;\n }\n return this.complete(state, o);\n }\n\n private async complete(state: UploadState, o: UploadOptions): Promise<UploadedFile> {\n o.onProgress?.({ phase: \"completing\", loaded: state.size, total: state.size });\n const c = await this.retry(() => this.api.complete(state.ref, { ticket: state.ticket }, o.signal), o.signal);\n o.onState?.(null);\n return { path: state.path, blob: c.blob, type: c.type, size: c.size, exists: false, processOnUpload: state.processOnUpload };\n }\n\n private async retry<T>(fn: () => Promise<T>, signal?: AbortSignal, before?: (err: ContentKitError) => Promise<void>): Promise<T> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await fn();\n } catch (e) {\n const err = toContentKitError(e);\n if (signal?.aborted) throw aborted(signal);\n if (!err.transient || attempt >= this.retries) throw err;\n await sleep(this.delay(attempt, err.retryAfter), signal);\n await before?.(err);\n }\n }\n }\n}\n\nfunction fingerprint(f: Uploadable): UploadState[\"file\"] {\n return { name: f.name, lastModified: f.lastModified, size: f.size };\n}\n\n/** Exponential backoff from 1 s to 30 s with jitter, or the server's Retry-After. */\nexport function backoff(attempt: number, retryAfter?: number): number {\n if (retryAfter) return retryAfter * 1000;\n const base = Math.min(30_000, 1000 * 2 ** attempt);\n return base * (0.75 + Math.random() * 0.5);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(aborted(signal));\n const t = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(t);\n reject(aborted(signal));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/client/media/client.ts"],"sourcesContent":["import { ContentKitError, aborted, failureError, throwIfAborted, toContentKitError } from \"../errors.js\";\nimport type { CommitBody, Edit, FileInfo, InlineImage, Op, PresetRule, PresignReply, ReadResult, RefBody, RequestReply } from \"../generated/wire.js\";\nimport type { HlsLoader, Http } from \"../http.js\";\nimport { call } from \"../route.js\";\nimport { MediaApi, type ReadOptions } from \"./api.js\";\nimport { sha256Hex } from \"./hash.js\";\nimport { Pacer } from \"./pacer.js\";\nimport { itemRef } from \"./ref.js\";\nimport { defaultTransport, type Transport } from \"./transport.js\";\n\n/** Upload tuning for the media module. */\nexport interface MediaOptions {\n /** Sends presigned PUTs. Default XMLHttpRequest (upload progress), else fetch. */\n transport?: Transport;\n /** Parallel parts per multipart upload (the pacer may run fewer). Default 4. */\n concurrency?: number;\n /** Retries per part and per idempotent API call. Default 5. */\n retries?: number;\n /** Delay before retry attempt (0-based); retryAfter is the server's hint in seconds. */\n retryDelay?: (attempt: number, retryAfter?: number) => number;\n /** Seconds one part should take. Default 30. */\n targetPartSeconds?: number;\n}\n\nexport interface Progress {\n /** processing: committed, waiting for the worker (put). */\n phase: \"hashing\" | \"uploading\" | \"completing\" | \"processing\";\n loaded: number;\n total: number;\n}\n\nexport interface UploadOptions {\n ref: RefBody;\n /** The upload path: a literal (\"cover\") or under a pattern (\"originals/001.png\"); the server cleans it and adds the extension. */\n path: string;\n /** Content type; default the file's. */\n type?: string;\n /** Aborting pauses: a multipart upload stays resumable from the last state. */\n signal?: AbortSignal;\n onProgress?: (p: Progress) => void;\n /** State saved from onState, to resume a multipart upload of the same file. */\n resume?: UploadState;\n /** Resumable state after every change; null once the upload completed. Persist it to survive a reload. */\n onState?: (s: UploadState | null) => void;\n}\n\n/** An upload, ready for a put op. */\nexport interface UploadedFile {\n /** The path to commit: cleaned, with an extension, named by the server for a Named upload. */\n path: string;\n /**\n * The name to commit: the folder's identical blob (\"sha256-{hex}-{uuid}\") when\n * exists, else the staged upload (\"u-{uuid}\") the worker hashes and places\n * after the commit.\n */\n blob: string;\n type: string;\n size: number;\n /** The identical file was already in the item's folder; nothing was sent. */\n exists: boolean;\n /** The host processes files on upload: commit it unattached now (the queue does). */\n processOnUpload?: boolean;\n}\n\n/** The put op's fields besides path and blob. */\nexport interface PutOptions {\n /** Refuse to replace an existing upload. Default false. */\n createOnly?: boolean;\n edit?: Edit | null;\n meta?: Record<string, unknown>;\n index?: number;\n /** Wait until the worker processed the upload (waitFor). Default true. */\n wait?: boolean;\n /** How long to wait, ms. */\n timeout?: number;\n /** The upload, before the commit. */\n onUploaded?: (up: UploadedFile) => void;\n}\n\nexport interface WaitOptions {\n signal?: AbortSignal;\n /** Poll interval, ms. Default 1000. */\n interval?: number;\n /** ms; then render_timeout. Default 120000. */\n timeout?: number;\n}\n\nexport interface PlannedPart {\n number: number;\n offset: number;\n size: number;\n sha256?: string;\n}\n\n/** A multipart upload in flight; JSON-serializable. */\nexport interface UploadState {\n ticket: string;\n path: string;\n /** The staged upload (\"u-{uuid}\") the parts write. */\n blob: string;\n type: string;\n size: number;\n ref: RefBody;\n file: { name?: string; lastModified?: number; size: number };\n limits: { minPartSize: number; maxPartSize: number; maxParts: number };\n parts: PlannedPart[];\n processOnUpload?: boolean;\n}\n\n/** An upload to a server-named (Named) path: a post's or poll's image. */\nexport interface NamedOptions {\n /** The requested path; the server names the file. Default the file's name. */\n path?: string;\n signal?: AbortSignal;\n onProgress?: (p: Progress) => void;\n /** Wait until the worker processed it. Default true. */\n wait?: boolean;\n /** How long to wait, ms. */\n timeout?: number;\n /** A crop or rotation the file is committed with. */\n edit?: Edit;\n}\n\n/** An upload to a Named path, committed. */\nexport interface NamedUpload {\n /** The server's name for it (\"i-{uuid}\"), which content routes take. */\n name: string;\n path: string;\n file: FileInfo;\n}\n\n/** The media kind of poll folders (content.Media.PollKind). A content item's folder is its kind's. */\nexport interface ContentFolders {\n /** Default \"poll\". */\n poll?: string;\n}\n\nconst named = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\n/** A file to upload again when its blob is gone at commit; type defaults to the file's. */\nexport type CommitSource = Blob | { file: Blob; type?: string };\n\nexport interface CommitOptions {\n signal?: AbortSignal;\n /** UUID for this complete batch. Keep it with unchanged ops for a later retry; default a new UUID. */\n operationID?: string;\n /** Persist the active batch before sending it, including any replacement after re-upload. Resume with its ops and operation_id. */\n onState?: (batch: CommitBody) => void;\n /** Blob (\"sha256-…\") → the file it was uploaded from. */\n sources?: Record<string, CommitSource>;\n}\n\ntype Uploadable = Blob & { name?: string; lastModified?: number };\n\n/** A path names a file by its full path or, for an upload, its stem (\"cover\" for \"cover.png\"). */\nexport const samePath = (file: string, path: string) => file === path || stem(file) === path;\n\n/** The path without its extension. */\nexport const stem = (path: string) => {\n const i = path.lastIndexOf(\".\");\n return i > path.lastIndexOf(\"/\") + 1 ? path.slice(0, i) : path;\n};\n\n/** The media module: uploads, commits, reads, frames and HLS. */\nexport class MediaClient {\n readonly api: MediaApi;\n private readonly transport: Transport;\n private readonly concurrency: number;\n private readonly retries: number;\n private readonly delay: (attempt: number, retryAfter?: number) => number;\n private readonly targetSeconds: number;\n private presetList?: Promise<PresetRule[]>;\n private readonly bodyDirs = new Map<string, string>();\n\n private readonly folders: Required<ContentFolders>;\n\n constructor(\n private readonly http: Http,\n o: MediaOptions = {},\n folders: ContentFolders = {},\n ) {\n this.folders = { poll: folders.poll ?? \"poll\" };\n this.api = new MediaApi(http);\n this.transport = o.transport ?? defaultTransport;\n this.concurrency = o.concurrency ?? 4;\n this.retries = o.retries ?? 5;\n this.delay = o.retryDelay ?? backoff;\n this.targetSeconds = o.targetPartSeconds ?? 30;\n }\n\n /**\n * Uploads file to the item's folder: it hashes the whole file (off the\n * main thread) and presigns {path, type, size, sha256}, then sends nothing\n * (the folder holds the identical blob), or stages it: one checksum-bound\n * PUT up to 64 MiB, resumable multipart above. A refused presign throws\n * before any bytes move. Commit the result with a put op (or use put());\n * the worker then places a staged upload at the hash of its bytes.\n */\n async upload(file: Uploadable, o: UploadOptions): Promise<UploadedFile> {\n const type = o.type || file.type;\n if (!type) throw new ContentKitError(\"invalid_request\", \"the file has no content type\");\n throwIfAborted(o.signal);\n if (o.resume) return this.resumeMultipart(file, o.resume, o);\n const total = file.size;\n const sha256 = await sha256Hex(file, { signal: o.signal, onProgress: (loaded) => o.onProgress?.({ phase: \"hashing\", loaded, total }) });\n const presign = () => this.api.presign({ ref: o.ref, path: o.path, type, size: total, sha256 }, o.signal);\n let p = await presign();\n const out = (p: PresignReply): UploadedFile => ({ path: p.path, blob: p.blob, type, size: total, exists: !!p.exists, processOnUpload: !!p.process_on_upload });\n if (p.exists) return out(p);\n if (p.multipart) {\n const m = p.multipart;\n const state: UploadState = {\n ticket: m.ticket,\n path: p.path,\n blob: p.blob,\n type,\n size: total,\n ref: o.ref,\n file: fingerprint(file),\n limits: { minPartSize: m.min_part_size, maxPartSize: m.max_part_size, maxParts: m.max_parts },\n parts: [],\n processOnUpload: !!p.process_on_upload,\n };\n return this.multipart(file, state, new Set(), o);\n }\n await this.retry(\n async () => {\n if (p.exists) return;\n await this.transport(p.put!, file, { signal: o.signal, onProgress: (loaded) => o.onProgress?.({ phase: \"uploading\", loaded, total }) });\n },\n o.signal,\n async (err) => {\n // An expired URL: presign again (a Named upload gets a new name).\n if (err.code === \"storage\" && err.status === 403) p = await presign();\n },\n );\n return out(p);\n }\n\n /**\n * Uploads file and commits it as a put to its path (with edit, meta and\n * index), then by default waits until the worker processed it; resolves\n * with the upload as an editor reads it.\n */\n async put(file: Uploadable, o: UploadOptions & PutOptions): Promise<FileInfo> {\n const up = await this.upload(file, o);\n o.onUploaded?.(up);\n const op: Op = { op: \"put\", path: up.path, blob: up.blob };\n if (o.createOnly) op.create_id = crypto.randomUUID();\n if (o.edit) op.edit = o.edit;\n if (o.meta) op.meta = o.meta;\n if (o.index !== undefined) op.index = o.index;\n const files = await this.commit(o.ref, [op], { signal: o.signal, sources: { [up.blob]: { file, type: up.type } } });\n if (o.wait === false) return files.find((f) => f.path === up.path) ?? { path: up.path, type: up.type };\n o.onProgress?.({ phase: \"processing\", loaded: up.size, total: up.size });\n return this.waitFor(o.ref, up.path, { signal: o.signal, timeout: o.timeout });\n }\n\n /**\n * Applies ops to the item in one conditional write; returns its uploads as\n * an editor reads them. With sources (blob → the file uploaded as it), a\n * not_uploaded refusal (the blob expired or is due for cleanup) uploads\n * the affected files again and retries the commit once.\n */\n async commit(ref: RefBody, ops: Op[], o: CommitOptions = {}): Promise<FileInfo[]> {\n const emit = this.http.captureChanges();\n // Snapshot the batch once: retries must not pick up edits to the caller's\n // ops while the first request or its response is still in flight.\n const body: CommitBody = structuredClone({ ref, ops, operation_id: o.operationID ?? crypto.randomUUID() });\n o.onState?.(structuredClone(body));\n let files: FileInfo[];\n try {\n files = (await this.retry(() => this.api.commit(body, o.signal), o.signal)).files;\n } catch (e) {\n if (!(e instanceof ContentKitError) || e.code !== \"not_uploaded\") throw e;\n const sources = o.sources ?? {};\n const puts = body.ops.filter((op) => op.op === \"put\" && op.blob && op.blob in sources);\n const stale = [...new Set(puts.map((op) => op.blob!))].filter((b) => !e.blobs || e.blobs.includes(b));\n if (stale.length === 0) throw e;\n const renamed = new Map<string, string>();\n for (const blob of stale) {\n const src = sources[blob]!;\n const file = src instanceof Blob ? src : src.file;\n const type = src instanceof Blob ? undefined : src.type;\n const op = puts.find((p) => p.blob === blob)!;\n renamed.set(blob, (await this.upload(file, { ref: body.ref, path: op.path!, type, signal: o.signal })).blob);\n }\n const retried = body.ops.map((op) => (op.blob && renamed.has(op.blob) ? { ...op, blob: renamed.get(op.blob) } : op));\n // Re-uploading changes the inputs. This is a new batch, not a retry\n // under the old ID; transport retries retain its new identity.\n const replacement = { ref: body.ref, ops: retried, operation_id: crypto.randomUUID() };\n o.onState?.(structuredClone(replacement));\n files = (await this.retry(() => this.api.commit(replacement, o.signal), o.signal)).files;\n }\n emit({ type: \"media.committed\", ref: itemRef(ref), files });\n return files;\n }\n\n /** The read API: the item's files under prefix in manifest order, with signed URLs for what this viewer may have. */\n async read(ref: RefBody, o: ReadOptions & { signal?: AbortSignal } = {}): Promise<ReadResult> {\n this.api.itemURL(ref); // a bad ref fails at once\n return this.retry(() => this.api.read(ref, o, o.signal), o.signal);\n }\n\n /**\n * Polls an editor read until the upload at path (or with that stem) is\n * processed: placed (not staged), nothing pending and its blob present (a\n * frame grabbed).\n * Rejects with its failure, not_found once it is gone, too_large while the\n * item is full (nothing is processed until uploads are removed),\n * render_timeout after the timeout.\n */\n async waitFor(ref: RefBody, path: string, o: WaitOptions = {}): Promise<FileInfo> {\n const emit = this.http.captureChanges();\n const until = Date.now() + (o.timeout ?? 120_000);\n for (;;) {\n const r = await this.read(ref, { editor: true, prefix: stem(path), signal: o.signal });\n const f = r.files.find((x) => x.upload && samePath(x.path, path));\n if (!f) throw new ContentKitError(\"not_found\", `no upload ${path}`, { status: 404 });\n if (f.failed) throw failureError(f.failed);\n if (!f.pending?.length && !f.staged && (f.size ?? 0) > 0) {\n emit({ type: \"media.processed\", ref: itemRef(ref), file: f });\n return f;\n }\n if (r.full) throw new ContentKitError(\"too_large\", `${path} waits: the item is full; remove uploads to process more`, { status: 413 });\n if (Date.now() >= until) throw new ContentKitError(\"render_timeout\", `${path} is still processing`);\n await sleep(o.interval ?? 1000, o.signal);\n }\n }\n\n /**\n * The upload's editor view, for re-cropping: its URL and the upload's\n * oriented size. Waits while it renders (an editor read asks for it);\n * not_found when the path has no upload, render_timeout after the timeout\n * (default 30 s).\n */\n async editorView(ref: RefBody, path: string, o: WaitOptions = {}): Promise<{ url: string; width: number; height: number; edit?: Edit }> {\n const until = Date.now() + (o.timeout ?? 30_000);\n for (;;) {\n const r = await this.read(ref, { editor: true, prefix: stem(path), signal: o.signal });\n const f = r.files.find((x) => x.upload && samePath(x.path, path));\n if (!f) throw new ContentKitError(\"not_found\", `no upload ${path}`, { status: 404 });\n if (f.editor_url && f.w && f.h) return { url: f.editor_url, width: f.w, height: f.h, ...(f.edit ? { edit: f.edit } : {}) };\n if (f.failed) throw failureError(f.failed);\n if (Date.now() >= until) throw new ContentKitError(\"render_timeout\", `the editor view of ${path} is still rendering`);\n await sleep(o.interval ?? 1000, o.signal);\n }\n }\n\n /**\n * Every kind's public presets, fetched once per client: crop bounds,\n * widths and default images. An item's current image is in its read's\n * `public`, never built from a preset. Shared by every caller, so it takes\n * no signal; a failed fetch is retried on the next call.\n */\n presets(): Promise<PresetRule[]> {\n this.presetList ??= this.retry(() => this.api.presets()).catch((e: unknown) => {\n this.presetList = undefined;\n throw e;\n });\n return this.presetList;\n }\n\n /** A JPEG still of the video upload at path, t seconds in, w pixels wide (default the frame's). */\n getFrame(ref: RefBody, path: string, t: number, w?: number, signal?: AbortSignal): Promise<Blob> {\n return this.retry(() => this.api.frame(ref, path, t, w, signal), signal);\n }\n\n /** The folder of an HLS ladder from a read's `hls`: `{media}/{kind}/{id}/hls/{dir}`; master.m3u8 and sprite.vtt resolve under it. */\n hlsBase(ref: RefBody, dir: string): string {\n return `${this.api.itemURL(ref)}/hls/${dir}`;\n }\n\n /** How players load HLS from this API (playlists through the client's fetch); pass it to players as is. */\n get hls(): HlsLoader {\n return this.http.hls;\n }\n\n /**\n * Uploads an image to the item's server-named upload path and commits it\n * (by default waiting until it is processed); resolves with the name content\n * routes take: a poll or option image, a post cover.\n */\n async uploadNamed(ref: RefBody, file: Uploadable, o: NamedOptions = {}): Promise<NamedUpload> {\n const f = await this.put(file, { ref, path: o.path ?? (file.name || \"image\"), signal: o.signal, onProgress: o.onProgress, wait: o.wait, timeout: o.timeout, edit: o.edit });\n const name = stem(f.path.slice(f.path.lastIndexOf(\"/\") + 1));\n if (!named.test(name)) throw new ContentKitError(\"invalid_request\", `${f.path} is not a server-named upload path of ${ref.kind}`);\n return { name, path: f.path, file: f };\n }\n\n /**\n * An image for a content item's rich-text body, uploaded to a version's\n * body upload (the kind's `body` upload rule), a private file only viewers\n * who open the version read, and copied into the other versions; resolves\n * with its reference to store in the body (`ref`) and a URL that shows it\n * to the editor now (`url`, null while none exists).\n */\n async uploadInline(item: RefBody, file: Uploadable, o: NamedOptions = {}): Promise<NamedUpload & InlineImage> {\n if (!item.version) throw new ContentKitError(\"invalid_request\", \"body images live in a version\");\n const up = await this.uploadNamed(item, file, { ...o, path: o.path ?? (await this.bodyPath(item, file)) });\n const r: InlineImage = await call(this.http, \"POST\", \"/contents/{id}/images\", { params: { id: item.id }, body: { image: up.name, version: item.version }, signal: o.signal });\n return { ...up, ...r };\n }\n\n /** The upload path of file as a body image of item's kind, from its editor read's body rule. */\n private async bodyPath(item: RefBody, file: Uploadable): Promise<string> {\n let dir = this.bodyDirs.get(item.kind);\n if (dir === undefined) {\n const { uploads } = await this.read(item, { editor: true, limit: 1 });\n // Non-editors get the reader view, which carries no upload rules.\n if (!uploads) throw new ContentKitError(\"forbidden\", `only editors of ${item.kind} ${item.id} upload to it`);\n const rule = uploads.find((r) => r.body);\n if (!rule) throw new ContentKitError(\"invalid_request\", `kind ${item.kind} takes no body images`);\n dir = rule.path.endsWith(\"{name}\") ? rule.path.slice(0, -\"{name}\".length) : rule.path;\n this.bodyDirs.set(item.kind, dir);\n }\n return dir + (file.name || \"image\");\n }\n\n /** The upload path of file for kind's public preset (its first without one): the preset's source, its {name} the file's. */\n async presetPath(kind: string, preset: string | undefined, file: Uploadable): Promise<string> {\n const name = file.name || \"image\";\n const rule = (await this.presets()).find((p) => p.kind === kind && (!preset || p.name === preset));\n if (!rule) return name;\n return rule.from.endsWith(\"{name}\") ? rule.from.slice(0, -\"{name}\".length) + name : rule.from;\n }\n\n /** The media folder of a poll. */\n pollRef(id: string): RefBody {\n return { kind: this.folders.poll, id };\n }\n\n /** Discards a paused multipart upload. */\n async discard(state: UploadState): Promise<void> {\n await this.api.abort(state.ref, { ticket: state.ticket });\n }\n\n private async resumeMultipart(file: Uploadable, state: UploadState, o: UploadOptions): Promise<UploadedFile> {\n const fp = fingerprint(file);\n if (fp.size !== state.size || fp.name !== state.file.name || fp.lastModified !== state.file.lastModified) {\n throw new ContentKitError(\"resume_mismatch\", \"the saved upload is for a different file\");\n }\n let listed;\n try {\n listed = await this.retry(() => this.api.listParts(state.ref, { ticket: state.ticket }, o.signal), o.signal);\n } catch (err) {\n // Gone: already completed (Complete is idempotent) or expired.\n if (err instanceof ContentKitError && err.code === \"not_found\") return this.complete(state, o);\n throw err;\n }\n const landed = new Set<number>();\n for (const l of listed.parts) {\n const plan = state.parts.find((p) => p.number === l.number);\n if (!plan || plan.size !== l.size || (l.sha256 && plan.sha256 !== l.sha256)) {\n // Parts this state does not describe: the upload cannot be finished consistently.\n await this.discard(state).catch(() => {});\n o.onState?.(null);\n return this.upload(file, { ...o, resume: undefined });\n }\n landed.add(l.number);\n }\n return this.multipart(file, structuredClone(state), landed, o);\n }\n\n private async multipart(file: Uploadable, state: UploadState, landed: Set<number>, o: UploadOptions): Promise<UploadedFile> {\n const pacer = new Pacer({ ...state.limits, maxConcurrency: this.concurrency, targetSeconds: this.targetSeconds });\n const emitState = () => o.onState?.(structuredClone(state));\n const sending = new Map<number, number>();\n let sent = state.parts.filter((p) => landed.has(p.number)).reduce((n, p) => n + p.size, 0);\n const emitProgress = () => {\n let loaded = sent;\n for (const n of sending.values()) loaded += n;\n o.onProgress?.({ phase: \"uploading\", loaded, total: state.size });\n };\n const pending = state.parts.filter((p) => !landed.has(p.number));\n let offset = state.parts.reduce((end, p) => Math.max(end, p.offset + p.size), 0);\n const next = (): PlannedPart | undefined => {\n const p = pending.shift();\n if (p || offset >= state.size) return p;\n const part: PlannedPart = {\n number: state.parts.length + 1,\n offset,\n size: Math.min(pacer.partSize(), state.size - offset),\n };\n offset += part.size;\n state.parts.push(part);\n return part;\n };\n\n const stop = new AbortController();\n const signal = o.signal ? AbortSignal.any([o.signal, stop.signal]) : stop.signal;\n // Parts hash and presign ahead of the PUT slots, so a freed slot starts\n // its next PUT at once; pacer.concurrency bounds only the PUTs.\n let putting = 0;\n const waiting: (() => void)[] = [];\n const acquire = () =>\n new Promise<void>((resolve, reject) => {\n const take = () => {\n signal.removeEventListener(\"abort\", drop);\n putting++;\n resolve();\n };\n const drop = () => {\n waiting.splice(waiting.indexOf(take) >>> 0, 1);\n reject(aborted(signal));\n };\n if (putting < pacer.concurrency) return take();\n if (signal.aborted) return reject(aborted(signal));\n signal.addEventListener(\"abort\", drop, { once: true });\n waiting.push(take);\n });\n const release = () => {\n putting--;\n while (putting < pacer.concurrency && waiting.length) waiting.shift()!();\n };\n const run = async (part: PlannedPart) => {\n const body = file.slice(part.offset, part.offset + part.size);\n await this.retry(async () => {\n if (!part.sha256) {\n part.sha256 = await sha256Hex(body, { signal });\n emitState();\n }\n const { parts } = await this.api.parts(\n state.ref,\n { ticket: state.ticket, parts: [{ number: part.number, size: part.size, sha256: part.sha256 }] },\n signal,\n );\n const req = parts[0]?.request as RequestReply;\n await acquire();\n const began = performance.now();\n const alongside = putting;\n try {\n await this.transport(req, body, {\n signal,\n onProgress: (n) => {\n sending.set(part.number, n);\n emitProgress();\n },\n });\n } finally {\n sending.delete(part.number);\n release();\n }\n pacer.record(part.size, performance.now() - began, alongside);\n sent += part.size;\n emitProgress();\n }, signal);\n };\n\n emitState();\n emitProgress();\n const inFlight = new Set<Promise<void>>();\n let failure: unknown;\n for (;;) {\n let part: PlannedPart | undefined;\n while (failure === undefined && inFlight.size < pacer.concurrency + 2 && (part = next())) {\n emitState();\n const job: Promise<void> = run(part).then(\n () => void inFlight.delete(job),\n (err) => {\n inFlight.delete(job);\n if (failure === undefined) {\n failure = err;\n stop.abort();\n }\n },\n );\n inFlight.add(job);\n }\n if (inFlight.size === 0) break;\n await Promise.race(inFlight);\n }\n if (failure !== undefined) {\n throw o.signal?.aborted ? aborted(o.signal) : failure;\n }\n return this.complete(state, o);\n }\n\n private async complete(state: UploadState, o: UploadOptions): Promise<UploadedFile> {\n o.onProgress?.({ phase: \"completing\", loaded: state.size, total: state.size });\n const c = await this.retry(() => this.api.complete(state.ref, { ticket: state.ticket }, o.signal), o.signal);\n o.onState?.(null);\n return { path: state.path, blob: c.blob, type: c.type, size: c.size, exists: false, processOnUpload: state.processOnUpload };\n }\n\n private async retry<T>(fn: () => Promise<T>, signal?: AbortSignal, before?: (err: ContentKitError) => Promise<void>): Promise<T> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await fn();\n } catch (e) {\n const err = toContentKitError(e);\n if (signal?.aborted) throw aborted(signal);\n if (!err.transient || attempt >= this.retries) throw err;\n await sleep(this.delay(attempt, err.retryAfter), signal);\n await before?.(err);\n }\n }\n }\n}\n\nfunction fingerprint(f: Uploadable): UploadState[\"file\"] {\n return { name: f.name, lastModified: f.lastModified, size: f.size };\n}\n\n/** Exponential backoff from 1 s to 30 s with jitter, or the server's Retry-After. */\nexport function backoff(attempt: number, retryAfter?: number): number {\n if (retryAfter) return retryAfter * 1000;\n const base = Math.min(30_000, 1000 * 2 ** attempt);\n return base * (0.75 + Math.random() * 0.5);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(aborted(signal));\n const t = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(t);\n reject(aborted(signal));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ import { jsx as a } from "react/jsx-runtime";
|
|
|
7
7
|
function o({ item: o, dir: s = "hls/", duration: c, start: l, available: u = !0, target: d, enabled: f, abr: p, className: m, client: h }) {
|
|
8
8
|
let { media: g } = t(h), [_, v] = i(null), y = n({
|
|
9
9
|
src: `${g.hlsBase(o, s)}master.m3u8`,
|
|
10
|
-
|
|
10
|
+
loader: g.hls,
|
|
11
11
|
abr: p,
|
|
12
12
|
qualityKey: null
|
|
13
13
|
}), b = c ?? 0, x = Math.max(0, l ?? (b > 1 ? Math.min(b * .1, b - 1) : 0));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hover-preview.js","names":[],"sources":["../../src/components/hover-preview.tsx"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { RefBody } from \"../client/generated/wire.js\";\nimport { useContentKitClient } from \"../react/context.js\";\nimport { useHlsPlayer, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\n\nexport interface HoverPreviewProps extends Pick<HlsPlayerOptions, \"abr\"> {\n /** The video item. */\n item: RefBody;\n /** Its HLS folder (a read's `hls` entry). Default \"hls/\". */\n dir?: string;\n /** Seconds; the preview starts 10% in. */\n duration?: number;\n /** Where the preview starts, seconds; overrides the 10% default. */\n start?: number;\n /** The viewer may play it (free, or entitled). Default true. */\n available?: boolean;\n /** What is hovered (fine pointers) or watched for visibility (touch); default the preview's parent, the card. */\n target?: HTMLElement | null;\n /** Previews on; default the provider's `inlinePreview`. */\n enabled?: boolean;\n className?: string;\n client?: ContentKitClient;\n}\n\n/**\n * A card's muted inline preview: the item's real HLS stream through the\n * client (its base and playlist auth) at the lowest rendition, fading in over\n * the card's thumbnail on hover, or while it is the most visible card on\n * touch screens. One plays page-wide; none with reduced motion or Save-Data.\n * Place it inside the card, which must be positioned.\n */\nexport function HoverPreview({ item, dir = \"hls/\", duration, start, available = true, target, enabled, abr, className, client: own }: HoverPreviewProps) {\n const { media } = useContentKitClient(own);\n const [el, setEl] = useState<HTMLVideoElement | null>(null);\n const player = useHlsPlayer({ src: `${media.hlsBase(item, dir)}master.m3u8`,
|
|
1
|
+
{"version":3,"file":"hover-preview.js","names":[],"sources":["../../src/components/hover-preview.tsx"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { RefBody } from \"../client/generated/wire.js\";\nimport { useContentKitClient } from \"../react/context.js\";\nimport { useHlsPlayer, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\n\nexport interface HoverPreviewProps extends Pick<HlsPlayerOptions, \"abr\"> {\n /** The video item. */\n item: RefBody;\n /** Its HLS folder (a read's `hls` entry). Default \"hls/\". */\n dir?: string;\n /** Seconds; the preview starts 10% in. */\n duration?: number;\n /** Where the preview starts, seconds; overrides the 10% default. */\n start?: number;\n /** The viewer may play it (free, or entitled). Default true. */\n available?: boolean;\n /** What is hovered (fine pointers) or watched for visibility (touch); default the preview's parent, the card. */\n target?: HTMLElement | null;\n /** Previews on; default the provider's `inlinePreview`. */\n enabled?: boolean;\n className?: string;\n client?: ContentKitClient;\n}\n\n/**\n * A card's muted inline preview: the item's real HLS stream through the\n * client (its base and playlist auth) at the lowest rendition, fading in over\n * the card's thumbnail on hover, or while it is the most visible card on\n * touch screens. One plays page-wide; none with reduced motion or Save-Data.\n * Place it inside the card, which must be positioned.\n */\nexport function HoverPreview({ item, dir = \"hls/\", duration, start, available = true, target, enabled, abr, className, client: own }: HoverPreviewProps) {\n const { media } = useContentKitClient(own);\n const [el, setEl] = useState<HTMLVideoElement | null>(null);\n const player = useHlsPlayer({ src: `${media.hlsBase(item, dir)}master.m3u8`, loader: media.hls, abr, qualityKey: null });\n const d = duration ?? 0;\n const at = Math.max(0, start ?? (d > 1 ? Math.min(d * 0.1, d - 1) : 0));\n useInlinePreview({ setting: enabled, available, target: target ?? el?.parentElement ?? null, start: () => player.preview(at), stop: player.unload });\n const attach = player.ref;\n const ref = useCallback(\n (v: HTMLVideoElement | null) => {\n attach(v);\n setEl(v);\n },\n [attach],\n );\n return (\n <video\n ref={ref}\n muted\n playsInline\n preload=\"none\"\n aria-hidden\n tabIndex={-1}\n className={className}\n style={{\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n pointerEvents: \"none\",\n opacity: player.previewing && player.status === \"playing\" ? 1 : 0,\n transition: \"opacity 200ms\",\n }}\n data-ckui=\"hover-preview\"\n data-playing={player.previewing && player.status === \"playing\" ? \"\" : undefined}\n />\n );\n}\n"],"mappings":";;;;;;AAiCA,SAAgB,EAAa,EAAE,SAAM,SAAM,QAAQ,aAAU,UAAO,eAAY,IAAM,WAAQ,YAAS,QAAK,cAAW,QAAQ,KAA0B;CACvJ,IAAM,EAAE,aAAU,EAAoB,CAAG,GACnC,CAAC,GAAI,KAAS,EAAkC,IAAI,GACpD,IAAS,EAAa;EAAE,KAAK,GAAG,EAAM,QAAQ,GAAM,CAAG,EAAE;EAAc,QAAQ,EAAM;EAAK;EAAK,YAAY;CAAK,CAAC,GACjH,IAAI,KAAY,GAChB,IAAK,KAAK,IAAI,GAAG,MAAU,IAAI,IAAI,KAAK,IAAI,IAAI,IAAK,IAAI,CAAC,IAAI,EAAE;CACtE,EAAiB;EAAE,SAAS;EAAS;EAAW,QAAQ,KAAU,GAAI,iBAAiB;EAAM,aAAa,EAAO,QAAQ,CAAE;EAAG,MAAM,EAAO;CAAO,CAAC;CACnJ,IAAM,IAAS,EAAO,KAChB,IAAM,GACT,MAA+B;EAE9B,AADA,EAAO,CAAC,GACR,EAAM,CAAC;CACT,GACA,CAAC,CAAM,CACT;CACA,OACE,kBAAC,SAAD;EACO;EACL,OAAA;EACA,aAAA;EACA,SAAQ;EACR,eAAA;EACA,UAAU;EACC;EACX,OAAO;GACL,UAAU;GACV,OAAO;GACP,OAAO;GACP,QAAQ;GACR,WAAW;GACX,eAAe;GACf,SAAS,EAAO,cAAc,EAAO,WAAW,YAAY,IAAI;GAChE,YAAY;EACd;EACA,aAAU;EACV,gBAAc,EAAO,cAAc,EAAO,WAAW,YAAY,KAAK,KAAA;CACvE,CAAA;AAEL"}
|
|
@@ -5,11 +5,11 @@ import { GalleryViewOptions, HlsPlayerOptions } from '../react/gallery.js';
|
|
|
5
5
|
import { PublicPreset } from '../client/public.js';
|
|
6
6
|
import { ContentKitClient } from '../client/client.js';
|
|
7
7
|
import { ReadResult, RefBody } from '../client/generated/wire.js';
|
|
8
|
-
export interface MediaGalleryProps extends GalleryViewOptions, Pick<HlsPlayerOptions, "
|
|
8
|
+
export interface MediaGalleryProps extends GalleryViewOptions, Pick<HlsPlayerOptions, "loader" | "refresh" | "abr"> {
|
|
9
9
|
/**
|
|
10
10
|
* The item to show, read through the client: its read (refreshed before
|
|
11
11
|
* the URLs expire, after commits), HLS bases, playlist auth and grant
|
|
12
|
-
* refresh. Or pass `read` (and hlsBase,
|
|
12
|
+
* refresh. Or pass `read` (and hlsBase, loader, refresh) yourself.
|
|
13
13
|
*/
|
|
14
14
|
item?: RefBody;
|
|
15
15
|
/** With item: only files under this path prefix. */
|
|
@@ -114,7 +114,7 @@ function W(e) {
|
|
|
114
114
|
...e,
|
|
115
115
|
read: n.read,
|
|
116
116
|
hlsBase: e.hlsBase ?? i,
|
|
117
|
-
|
|
117
|
+
loader: e.loader ?? t.media.hls,
|
|
118
118
|
refresh: e.refresh ?? n.refresh
|
|
119
119
|
};
|
|
120
120
|
}
|
|
@@ -253,7 +253,7 @@ function q({ ctx: e, item: t, position: n, active: i, lightbox: a }) {
|
|
|
253
253
|
height: s.h,
|
|
254
254
|
duration: s.dur,
|
|
255
255
|
active: i,
|
|
256
|
-
|
|
256
|
+
loader: e.loader,
|
|
257
257
|
refresh: e.refresh,
|
|
258
258
|
expires: e.read?.expires,
|
|
259
259
|
abr: e.abr,
|
|
@@ -450,7 +450,7 @@ function ee({ ctx: e, item: r, label: i, onOpen: a }) {
|
|
|
450
450
|
className: "absolute inset-0 size-full object-cover"
|
|
451
451
|
}) : d ? /* @__PURE__ */ T(g, {
|
|
452
452
|
vtt: `${d}sprite.vtt`,
|
|
453
|
-
|
|
453
|
+
loader: e.loader,
|
|
454
454
|
fit: "cover"
|
|
455
455
|
}) : null,
|
|
456
456
|
d && /* @__PURE__ */ T(te, {
|
|
@@ -493,7 +493,7 @@ function ee({ ctx: e, item: r, label: i, onOpen: a }) {
|
|
|
493
493
|
function te({ ctx: t, base: n, start: r, target: i }) {
|
|
494
494
|
let a = m({
|
|
495
495
|
src: `${n}master.m3u8`,
|
|
496
|
-
|
|
496
|
+
loader: t.loader,
|
|
497
497
|
refresh: t.refresh,
|
|
498
498
|
expires: t.read?.expires,
|
|
499
499
|
abr: t.abr,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-gallery.js","names":[],"sources":["../../src/components/media-gallery.tsx"],"sourcesContent":["import { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport {\n AlertCircleIcon,\n ArrowLeft01Icon,\n ArrowRight01Icon,\n Cancel01Icon,\n CarouselHorizontalIcon,\n Download01Icon,\n GridViewIcon,\n MusicNote01Icon,\n PlayIcon,\n SquareLock02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useMemo, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { formatDuration, galleryItems, stageAspect, type GalleryItem, type GalleryLockedItem, type GalleryMediaItem } from \"../client/gallery.js\";\nimport { useCarousel, useGalleryView, useHlsPlayer, useRefreshBeforeExpiry, type GalleryViewOptions, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport { ContentKitUiRoot, useScopeProps } from \"../scope.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { useOptionalContentKitClient } from \"../react/context.js\";\nimport { useMediaRead } from \"../react/read.js\";\nimport { previewStartAt, SpriteFrame, VideoPlayer } from \"./video-player.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ToggleGroup, ToggleGroupItem } from \"#ckui/ui/toggle-group\";\n\nexport interface MediaGalleryProps extends GalleryViewOptions, Pick<HlsPlayerOptions, \"xhrSetup\" | \"refresh\" | \"abr\"> {\n /**\n * The item to show, read through the client: its read (refreshed before\n * the URLs expire, after commits), HLS bases, playlist auth and grant\n * refresh. Or pass `read` (and hlsBase, xhrSetup, refresh) yourself.\n */\n item?: RefBody;\n /** With item: only files under this path prefix. */\n prefix?: string;\n /** With item: each audio file's download name signed into its URL. */\n download?: boolean;\n /**\n * The read API result: files in manifest order with this viewer's access\n * (scope it with a prefix). A download read adds each audio file's download.\n * With `refresh`, the gallery reads again shortly before `read.expires`.\n */\n read?: ReadResult | null;\n /** A ladder's HLS folder from the read's `hls` dir: client.media.hlsBase(ref, dir). */\n hlsBase?: (dir: string) => string;\n /** The item's poster (its public preset, or a URL), drawn on the first video. */\n poster?: PublicPreset | string | null;\n /** Where the first video's preview starts, seconds (e.g. the poster's frame); default 10% in. */\n previewStart?: number;\n /** Playable videos preview muted inline (hover, or in view on touch); default the provider's. */\n inlinePreview?: boolean;\n /** The host's unlock call to action, drawn over the locked item. */\n renderLocked?: () => ReactNode;\n /** Below the carousel, for its current item (e.g. downloads). */\n renderDetails?: (item: GalleryItem) => ReactNode;\n /** `sizes` for carousel images. Default \"(min-width: 768px) 720px, 100vw\". */\n sizes?: string;\n /** Tallest the carousel gets; taller media letterboxes. Default \"80svh\": every slide at its native aspect, full width, never taller than the screen; \"none\" lifts it. */\n maxHeight?: string;\n label?: string;\n className?: string;\n appearance?: ContentKitUiAppearance;\n /** Overrides the provider's client (item). */\n client?: ContentKitClient;\n}\n\ninterface Ctx extends MediaGalleryProps {\n items: GalleryItem[];\n}\n\n// The item's poster and preview start belong to its first video.\nfunction videoArt(ctx: Ctx, item: GalleryMediaItem): { poster?: PublicPreset | string; start: number } {\n const first = ctx.items.find((i): i is GalleryMediaItem => i.kind === \"video\") === item;\n return { poster: first ? (ctx.poster ?? undefined) : undefined, start: previewStartAt(first ? ctx.previewStart : undefined, item.file.dur) };\n}\n\nconst baseOf = (ctx: Ctx, item: GalleryMediaItem) => (item.dir && ctx.hlsBase ? ctx.hlsBase(item.dir) : null);\n\n/**\n * A post's images and videos as a swipeable carousel or a tile grid that opens\n * a lightbox, with a view toggle. One item renders alone.\n */\nexport function MediaGallery(given: MediaGalleryProps) {\n const props = useItemRead(given);\n const { read, label, className, appearance, view: chosen, defaultView, onViewChange, storageKey } = props;\n const { t } = useMessages();\n const items = useMemo(() => galleryItems(read), [read]);\n useRefreshBeforeExpiry(read?.expires, props.refresh);\n const [view, setView] = useGalleryView({ view: chosen, defaultView, onViewChange, storageKey });\n const [index, setIndex] = useState(0);\n const [lightbox, setLightbox] = useState<number | null>(null);\n if (items.length === 0) return null;\n const ctx: Ctx = { ...props, items };\n const multi = items.length > 1;\n const current = Math.min(index, items.length - 1);\n const shown = multi ? view : \"carousel\";\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"@container grid gap-2\", className)} data-ckui=\"media-gallery\" data-view={shown} role=\"region\" aria-label={label ?? t(\"gallery.label\")}>\n {multi && (\n <div className=\"flex items-center justify-between gap-2\" data-ckui=\"gallery-header\">\n <span className=\"text-sm text-muted-foreground tabular-nums\" data-ckui=\"gallery-count\">\n {shown === \"carousel\" ? t(\"gallery.counter\", { current: current + 1, total: items.length }) : null}\n </span>\n <ToggleGroup aria-label={t(\"gallery.view\")} value={[shown]} onValueChange={(v) => v[0] && setView(v[0] as typeof view)}>\n <ToggleGroupItem value=\"carousel\" aria-label={t(\"gallery.carousel\")} title={t(\"gallery.carousel\")}>\n <HugeiconsIcon icon={CarouselHorizontalIcon} strokeWidth={2} />\n </ToggleGroupItem>\n <ToggleGroupItem value=\"grid\" aria-label={t(\"gallery.grid\")} title={t(\"gallery.grid\")}>\n <HugeiconsIcon icon={GridViewIcon} strokeWidth={2} />\n </ToggleGroupItem>\n </ToggleGroup>\n </div>\n )}\n {shown === \"carousel\" ? (\n <>\n <Carousel ctx={ctx} index={current} onIndex={setIndex} />\n {props.renderDetails?.(items[current]!)}\n </>\n ) : (\n <Grid ctx={ctx} onOpen={setLightbox} />\n )}\n <Lightbox ctx={ctx} index={lightbox} onIndex={setLightbox} />\n </ContentKitUiRoot>\n );\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\n\n// With item, the read, HLS bases, playlist auth and refresh come from the client.\nfunction useItemRead(p: MediaGalleryProps): MediaGalleryProps {\n const client = useOptionalContentKitClient(p.client);\n if (p.item && !client) throw new Error(\"contentkit-ui: MediaGallery item needs `client` or a <ContentKitProvider>\");\n // One refresh timer for the gallery and its players, keyed by r.refresh.\n const r = useMediaRead(p.item ?? NO_ITEM, { prefix: p.prefix, download: p.download, read: p.item ? p.read : null, refresh: false, client });\n const item = p.item;\n const hlsBase = useCallback((dir: string) => client!.media.hlsBase(item!, dir), [client, item]);\n if (!item || !client) return p;\n return { ...p, read: r.read, hlsBase: p.hlsBase ?? hlsBase, xhrSetup: p.xhrSetup ?? client.media.xhrSetup, refresh: p.refresh ?? r.refresh };\n}\n\nfunction Carousel({ ctx, index: given, onIndex, lightbox }: { ctx: Ctx; index: number; onIndex: (i: number) => void; lightbox?: boolean }) {\n const { t } = useMessages();\n const { items } = ctx;\n const c = useCarousel({ count: items.length, index: given, onIndexChange: onIndex });\n const { index } = c;\n const multi = items.length > 1;\n const stage = lightbox ? {} : { aspectRatio: String(stageAspect(items, index)), maxHeight: ctx.maxHeight ?? \"80svh\" };\n return (\n <div\n className={cn(\"group/carousel relative outline-none\", lightbox ? \"size-full\" : \"grid gap-2\")}\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"carousel\" : undefined}\n aria-label={multi ? (ctx.label ?? t(\"gallery.label\")) : undefined}\n tabIndex={multi && !lightbox ? 0 : undefined}\n onKeyDown={multi && !lightbox ? c.onKeyDown : undefined}\n data-ckui=\"carousel\"\n >\n <div\n className={cn(\n \"relative w-full touch-pan-y overflow-hidden select-none\",\n lightbox ? \"h-full\" : \"rounded-lg bg-muted transition-[aspect-ratio] duration-300 ease-out motion-reduce:transition-none\",\n )}\n style={stage}\n data-ckui=\"stage\"\n {...(multi ? c.swipe : {})}\n >\n <div\n className={cn(\"flex h-full\", c.dragging ? \"transition-none\" : \"transition-transform duration-300 ease-out motion-reduce:transition-none\")}\n style={{ transform: `translateX(calc(${-index * 100}% + ${c.offset}px))` }}\n >\n {items.map((item, i) => {\n const near = Math.abs(i - index) <= 1;\n return (\n <div\n key={item.key}\n className=\"relative h-full w-full shrink-0 overflow-hidden\"\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"slide\" : undefined}\n aria-label={multi ? t(\"gallery.slide\", { current: i + 1, total: items.length }) : undefined}\n aria-hidden={i !== index || undefined}\n inert={i !== index}\n data-ckui=\"slide\"\n data-current={i === index ? \"\" : undefined}\n >\n {near && <Slide ctx={ctx} item={item} position={i} active={i === index} lightbox={lightbox} />}\n </div>\n );\n })}\n </div>\n {multi && (\n <>\n <NavButton side=\"prev\" hidden={index === 0} last={index === 1} onClick={c.prev} label={t(\"gallery.previous\")} lightbox={lightbox} />\n <NavButton side=\"next\" hidden={index === items.length - 1} last={index === items.length - 2} onClick={c.next} label={t(\"gallery.next\")} lightbox={lightbox} />\n </>\n )}\n </div>\n {multi && !lightbox && items.length <= 12 && (\n <div className=\"flex justify-center gap-0.5\" data-ckui=\"dots\">\n {items.map((item, i) => (\n <button\n key={item.key}\n type=\"button\"\n className=\"group/dot flex size-4 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n aria-label={t(\"gallery.goTo\", { index: i + 1 })}\n aria-current={i === index || undefined}\n onClick={() => c.go(i)}\n >\n <span className={cn(\"size-1.5 rounded-full transition-colors\", i === index ? \"bg-foreground\" : \"bg-foreground/25 group-hover/dot:bg-foreground/50\")} />\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n// A strip down the stage's side, clear of a video's control bar: the stage's\n// height follows each slide's aspect, so a centred-only target would move\n// between quick clicks. A swipe may start on it. The click that reaches an end\n// removes the button, so focus moves to the carousel and the arrow keys keep working.\nfunction NavButton({ side, hidden, last, onClick, label, lightbox }: { side: \"prev\" | \"next\"; hidden: boolean; last: boolean; onClick: () => void; label: string; lightbox?: boolean }) {\n if (hidden) return null;\n return (\n <button\n type=\"button\"\n aria-label={label}\n data-ckui=\"gallery-nav\"\n data-side={side}\n onClick={(e) => {\n const root = e.currentTarget.closest<HTMLElement>(\"[data-ckui=lightbox], [data-ckui=carousel][tabindex]\");\n onClick();\n if (last && document.activeElement === e.currentTarget) root?.focus({ preventScroll: true });\n }}\n className={cn(\n \"group/nav absolute top-0 bottom-14 z-10 flex items-center outline-none\",\n lightbox ? \"w-16 sm:w-20\" : \"w-12\",\n side === \"prev\" ? (lightbox ? \"left-0 justify-start pl-3 sm:pl-5\" : \"left-0 justify-start pl-2\") : lightbox ? \"right-0 justify-end pr-3 sm:pr-5\" : \"right-0 justify-end pr-2\",\n )}\n >\n <span\n className={cn(\n \"flex items-center justify-center rounded-full backdrop-blur-sm transition-colors group-focus-visible/nav:ring-3 group-focus-visible/nav:ring-ring/60\",\n lightbox ? \"size-10 bg-white/10 text-white group-hover/nav:bg-white/20\" : \"size-8 bg-white/85 text-zinc-900 shadow-md group-hover/nav:bg-white\",\n )}\n >\n <HugeiconsIcon icon={side === \"prev\" ? ArrowLeft01Icon : ArrowRight01Icon} strokeWidth={2} className=\"size-4\" />\n </span>\n </button>\n );\n}\n\nfunction Slide({ ctx, item, position, active, lightbox }: { ctx: Ctx; item: GalleryItem; position: number; active: boolean; lightbox?: boolean }) {\n const { t } = useMessages();\n if (item.kind === \"locked\") return <Locked ctx={ctx} item={item} />;\n const f = item.file;\n if (item.kind === \"audio\") return <AudioSlide item={item} position={position} />;\n if (item.kind === \"image\") {\n if (!f.url) return f.failed ? <ImageFailed file={f} /> : <Processing>{t(\"gallery.processingImage\")}</Processing>;\n // A public teaser of a locked item carries the unlock, so no one swipes past it to find it.\n const locked = item.key.startsWith(\"preview/\") && !lightbox ? ctx.items.find((i): i is GalleryLockedItem => i.kind === \"locked\") : undefined;\n return (\n <>\n <img\n src={f.url}\n alt={t(\"gallery.image\", { index: position + 1 })}\n width={f.w}\n height={f.h}\n sizes={lightbox ? \"100vw\" : (ctx.sizes ?? \"(min-width: 768px) 720px, 100vw\")}\n loading={active ? \"eager\" : \"lazy\"}\n decoding=\"async\"\n draggable={false}\n className=\"absolute inset-0 size-full object-contain\"\n />\n {locked && <LockedBar ctx={ctx} />}\n </>\n );\n }\n const { poster, start } = videoArt(ctx, item);\n return (\n <VideoPlayer\n inlinePreview={lightbox ? false : ctx.inlinePreview}\n previewStart={start}\n layout=\"fill\"\n base={baseOf(ctx, item)}\n poster={poster}\n width={f.w}\n height={f.h}\n duration={f.dur}\n active={active}\n xhrSetup={ctx.xhrSetup}\n refresh={ctx.refresh}\n expires={ctx.read?.expires}\n abr={ctx.abr}\n label={t(\"gallery.video\", { index: position + 1 })}\n className={lightbox ? \"bg-transparent\" : undefined}\n />\n );\n}\n\n// An audio file (a ladder's M4A): a native player, its duration and, in a\n// download read, its download.\nfunction AudioSlide({ item, position }: { item: GalleryMediaItem; position: number }) {\n const { t } = useMessages();\n const f = item.file;\n const name = (item.dir ?? f.path).replace(/\\/$/, \"\").split(\"/\").pop();\n const label = t(\"gallery.audio\", { index: position + 1 });\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-muted p-4\" data-ckui=\"audio\">\n <div className=\"flex min-w-0 items-center gap-2 text-sm text-muted-foreground\">\n <HugeiconsIcon icon={MusicNote01Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {name && <span className=\"truncate\">{name}</span>}\n {f.dur ? <span className=\"tabular-nums\">{formatDuration(f.dur)}</span> : null}\n </div>\n {f.failed ? (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {t(\"gallery.failedAudio\")}\n </p>\n ) : f.url ? (\n <audio src={f.url} controls preload=\"metadata\" aria-label={label} className=\"w-full max-w-md\" data-ckui-noswipe=\"\" />\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t(\"gallery.processingAudio\")}</p>\n )}\n {f.download && f.url && (\n <a href={f.url} download={f.download} className=\"inline-flex items-center gap-1.5 text-sm underline-offset-4 hover:underline\" data-ckui-noswipe=\"\">\n <HugeiconsIcon icon={Download01Icon} className=\"size-4\" strokeWidth={2} />\n {t(\"gallery.download\")}\n </a>\n )}\n </div>\n );\n}\n\n// Why an image cannot be shown (editors only: the read API's failed fields).\nfunction ImageFailed({ file }: { file: FileInfo }) {\n const { t, error } = useMessages();\n const f = file.failed;\n const text = f?.code ? error({ code: f.code, message: f.message, details: f.details, refusal: true }) : t(\"gallery.failedImage\");\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted p-4 text-center text-sm text-destructive\" role=\"alert\" data-ckui=\"image-failed\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n <p className=\"max-w-sm text-balance\">{text}</p>\n </div>\n );\n}\n\nfunction Processing({ children }: { children: string }) {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center gap-2 bg-muted text-sm text-muted-foreground\">\n <span aria-hidden className=\"size-4 rounded-full border-2 border-current/30 border-t-current motion-safe:animate-spin\" />\n {children}\n </div>\n );\n}\n\nfunction Locked({ ctx, item, tile }: { ctx: Ctx; item: GalleryLockedItem; tile?: boolean }) {\n const { t } = useMessages();\n return (\n <div className=\"absolute inset-0 overflow-hidden bg-muted\" data-ckui=\"locked\">\n {item.backdrop && <img src={item.backdrop} alt=\"\" draggable={false} className=\"absolute inset-0 size-full scale-110 object-cover blur-xl\" />}\n <div className={cn(\"absolute inset-0 flex flex-col items-center justify-center gap-3 p-4 text-center\", item.backdrop ? \"bg-black/45 text-white\" : \"text-foreground\")}>\n <HugeiconsIcon icon={SquareLock02Icon} className={tile ? \"size-6\" : \"size-8\"} strokeWidth={1.75} />\n {!tile && (\n <>\n <p className=\"text-sm font-medium text-balance\">{t(\"gallery.locked\")}</p>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction LockedBar({ ctx }: { ctx: Ctx }) {\n const { t } = useMessages();\n return (\n <div\n className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-center justify-between gap-x-3 gap-y-2 bg-linear-to-t from-black/75 via-black/45 to-transparent px-4 pt-10 pb-4 text-white\"\n data-ckui=\"locked-bar\"\n >\n <span className=\"flex items-center gap-2 text-sm font-medium\">\n <HugeiconsIcon icon={SquareLock02Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {t(\"gallery.locked\")}\n </span>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </div>\n );\n}\n\nfunction Grid({ ctx, onOpen }: { ctx: Ctx; onOpen: (i: number) => void }) {\n const { t } = useMessages();\n const n = ctx.items.length;\n return (\n <ul className={cn(\"grid gap-1\", n === 2 || n === 4 ? \"grid-cols-2\" : \"grid-cols-2 @md:grid-cols-3\")} data-ckui=\"grid\">\n {ctx.items.map((item, i) => (\n <li key={item.key}>\n <Tile ctx={ctx} item={item} label={t(\"gallery.open\", { index: i + 1, total: n })} onOpen={() => onOpen(i)} />\n </li>\n ))}\n </ul>\n );\n}\n\nfunction Tile({ ctx, item, label, onOpen }: { ctx: Ctx; item: GalleryItem; label: string; onOpen: () => void }) {\n const [el, setEl] = useState<HTMLButtonElement | null>(null);\n let body: ReactNode;\n if (item.kind === \"locked\") body = <Locked ctx={ctx} item={item} tile />;\n else if (item.kind === \"audio\")\n body = (\n <span className=\"absolute inset-0 flex flex-col items-center justify-center gap-1 bg-muted text-muted-foreground\">\n <HugeiconsIcon icon={item.file.failed ? AlertCircleIcon : MusicNote01Icon} className={cn(\"size-7\", item.file.failed && \"text-destructive\")} strokeWidth={1.75} />\n {item.file.dur ? <span className=\"text-[11px] font-medium tabular-nums\">{formatDuration(item.file.dur)}</span> : null}\n </span>\n );\n else if (item.kind === \"image\")\n body = item.file.url ? (\n <img src={item.file.url} alt=\"\" loading=\"lazy\" decoding=\"async\" draggable={false} className=\"absolute inset-0 size-full object-cover transition-transform duration-300 motion-safe:group-hover:scale-[1.03]\" />\n ) : item.file.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : (\n <Processing>{\"\"}</Processing>\n );\n else {\n const f = item.file;\n const { poster, start } = videoArt(ctx, item);\n const covers = typeof poster === \"string\" ? [] : publicRenditions(poster);\n const base = baseOf(ctx, item);\n body = (\n <>\n {typeof poster === \"string\" ? (\n <img src={poster} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"absolute inset-0 size-full object-cover\" />\n ) : covers.length ? (\n <RenditionImg outputs={covers} loading=\"lazy\" className=\"absolute inset-0 size-full object-cover\" />\n ) : base ? (\n <SpriteFrame vtt={`${base}sprite.vtt`} xhrSetup={ctx.xhrSetup} fit=\"cover\" />\n ) : null}\n {base && <TilePreview ctx={ctx} base={base} start={start} target={el} />}\n {f.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : !base ? (\n <Processing>{\"\"}</Processing>\n ) : (\n <span className=\"pointer-events-none absolute top-1/2 left-1/2 flex size-10 -translate-1/2 items-center justify-center rounded-full bg-black/55 text-white backdrop-blur-sm transition-opacity group-hover:opacity-0\">\n <HugeiconsIcon icon={PlayIcon} className=\"ml-0.5 size-5 fill-current\" strokeWidth={1.5} />\n </span>\n )}\n {f.dur ? (\n <span className=\"pointer-events-none absolute right-1.5 bottom-1.5 rounded bg-black/65 px-1.5 py-0.5 text-[11px] font-medium text-white tabular-nums\">\n {formatDuration(f.dur)}\n </span>\n ) : null}\n </>\n );\n }\n return (\n <button\n type=\"button\"\n aria-label={label}\n onClick={onOpen}\n ref={setEl}\n className=\"group relative block aspect-square w-full overflow-hidden rounded-md bg-muted outline-none focus-visible:ring-3 focus-visible:ring-ring/60\"\n data-ckui=\"tile\"\n data-kind={item.kind}\n >\n {body}\n </button>\n );\n}\n\n// A grid tile's inline preview; the tile opens the lightbox for real playback.\nfunction TilePreview({ ctx, base, start, target }: { ctx: Ctx; base: string; start: number; target: HTMLElement | null }) {\n const player = useHlsPlayer({ src: `${base}master.m3u8`, xhrSetup: ctx.xhrSetup, refresh: ctx.refresh, expires: ctx.read?.expires, abr: ctx.abr, qualityKey: null });\n useInlinePreview({ setting: ctx.inlinePreview, available: true, target, start: () => player.preview(start), stop: player.unload });\n return (\n <video\n ref={player.ref}\n muted\n playsInline\n preload=\"none\"\n aria-hidden\n tabIndex={-1}\n className={cn(\n \"pointer-events-none absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-300 motion-reduce:transition-none\",\n player.previewing && player.status === \"playing\" && \"opacity-100\",\n )}\n data-ckui=\"tile-preview\"\n />\n );\n}\n\nfunction Lightbox({ ctx, index, onIndex }: { ctx: Ctx; index: number | null; onIndex: (i: number | null) => void }) {\n const { t } = useMessages();\n const scope = useScopeProps(ctx.appearance);\n const [shown, setShown] = useState(index ?? 0);\n const [prev, setPrev] = useState(index);\n if (index !== prev) {\n setPrev(index);\n if (index !== null) setShown(index);\n }\n const open = index !== null;\n const c = useCarousel({ count: ctx.items.length, index: shown, onIndexChange: setShown });\n return (\n <DialogPrimitive.Root open={open} onOpenChange={(o) => !o && onIndex(null)}>\n <DialogPrimitive.Portal {...scope} data-ckui-theme=\"dark\">\n <DialogPrimitive.Backdrop className=\"fixed inset-0 z-50 bg-black duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\" />\n <DialogPrimitive.Popup\n className=\"fixed inset-0 z-50 flex flex-col bg-black text-white outline-none duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\"\n data-ckui=\"lightbox\"\n onKeyDown={c.onKeyDown}\n >\n <DialogPrimitive.Title className=\"sr-only\">{ctx.label ?? t(\"gallery.lightbox\")}</DialogPrimitive.Title>\n <div className=\"flex h-14 shrink-0 items-center justify-between px-4\">\n <span className=\"text-sm text-white/80 tabular-nums\">{ctx.items.length > 1 ? t(\"gallery.counter\", { current: shown + 1, total: ctx.items.length }) : null}</span>\n <DialogPrimitive.Close render={<Button variant=\"ghost\" size=\"icon\" className=\"rounded-full text-white hover:bg-white/10 hover:text-white\" />}>\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />\n <span className=\"sr-only\">{t(\"common.close\")}</span>\n </DialogPrimitive.Close>\n </div>\n <div className=\"min-h-0 flex-1 pb-4 sm:px-4 sm:pb-6\">\n {open && <Carousel ctx={ctx} index={shown} onIndex={setShown} lightbox />}\n </div>\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"media-gallery.js","names":[],"sources":["../../src/components/media-gallery.tsx"],"sourcesContent":["import { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport {\n AlertCircleIcon,\n ArrowLeft01Icon,\n ArrowRight01Icon,\n Cancel01Icon,\n CarouselHorizontalIcon,\n Download01Icon,\n GridViewIcon,\n MusicNote01Icon,\n PlayIcon,\n SquareLock02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useMemo, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { formatDuration, galleryItems, stageAspect, type GalleryItem, type GalleryLockedItem, type GalleryMediaItem } from \"../client/gallery.js\";\nimport { useCarousel, useGalleryView, useHlsPlayer, useRefreshBeforeExpiry, type GalleryViewOptions, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport { ContentKitUiRoot, useScopeProps } from \"../scope.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { useOptionalContentKitClient } from \"../react/context.js\";\nimport { useMediaRead } from \"../react/read.js\";\nimport { previewStartAt, SpriteFrame, VideoPlayer } from \"./video-player.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ToggleGroup, ToggleGroupItem } from \"#ckui/ui/toggle-group\";\n\nexport interface MediaGalleryProps extends GalleryViewOptions, Pick<HlsPlayerOptions, \"loader\" | \"refresh\" | \"abr\"> {\n /**\n * The item to show, read through the client: its read (refreshed before\n * the URLs expire, after commits), HLS bases, playlist auth and grant\n * refresh. Or pass `read` (and hlsBase, loader, refresh) yourself.\n */\n item?: RefBody;\n /** With item: only files under this path prefix. */\n prefix?: string;\n /** With item: each audio file's download name signed into its URL. */\n download?: boolean;\n /**\n * The read API result: files in manifest order with this viewer's access\n * (scope it with a prefix). A download read adds each audio file's download.\n * With `refresh`, the gallery reads again shortly before `read.expires`.\n */\n read?: ReadResult | null;\n /** A ladder's HLS folder from the read's `hls` dir: client.media.hlsBase(ref, dir). */\n hlsBase?: (dir: string) => string;\n /** The item's poster (its public preset, or a URL), drawn on the first video. */\n poster?: PublicPreset | string | null;\n /** Where the first video's preview starts, seconds (e.g. the poster's frame); default 10% in. */\n previewStart?: number;\n /** Playable videos preview muted inline (hover, or in view on touch); default the provider's. */\n inlinePreview?: boolean;\n /** The host's unlock call to action, drawn over the locked item. */\n renderLocked?: () => ReactNode;\n /** Below the carousel, for its current item (e.g. downloads). */\n renderDetails?: (item: GalleryItem) => ReactNode;\n /** `sizes` for carousel images. Default \"(min-width: 768px) 720px, 100vw\". */\n sizes?: string;\n /** Tallest the carousel gets; taller media letterboxes. Default \"80svh\": every slide at its native aspect, full width, never taller than the screen; \"none\" lifts it. */\n maxHeight?: string;\n label?: string;\n className?: string;\n appearance?: ContentKitUiAppearance;\n /** Overrides the provider's client (item). */\n client?: ContentKitClient;\n}\n\ninterface Ctx extends MediaGalleryProps {\n items: GalleryItem[];\n}\n\n// The item's poster and preview start belong to its first video.\nfunction videoArt(ctx: Ctx, item: GalleryMediaItem): { poster?: PublicPreset | string; start: number } {\n const first = ctx.items.find((i): i is GalleryMediaItem => i.kind === \"video\") === item;\n return { poster: first ? (ctx.poster ?? undefined) : undefined, start: previewStartAt(first ? ctx.previewStart : undefined, item.file.dur) };\n}\n\nconst baseOf = (ctx: Ctx, item: GalleryMediaItem) => (item.dir && ctx.hlsBase ? ctx.hlsBase(item.dir) : null);\n\n/**\n * A post's images and videos as a swipeable carousel or a tile grid that opens\n * a lightbox, with a view toggle. One item renders alone.\n */\nexport function MediaGallery(given: MediaGalleryProps) {\n const props = useItemRead(given);\n const { read, label, className, appearance, view: chosen, defaultView, onViewChange, storageKey } = props;\n const { t } = useMessages();\n const items = useMemo(() => galleryItems(read), [read]);\n useRefreshBeforeExpiry(read?.expires, props.refresh);\n const [view, setView] = useGalleryView({ view: chosen, defaultView, onViewChange, storageKey });\n const [index, setIndex] = useState(0);\n const [lightbox, setLightbox] = useState<number | null>(null);\n if (items.length === 0) return null;\n const ctx: Ctx = { ...props, items };\n const multi = items.length > 1;\n const current = Math.min(index, items.length - 1);\n const shown = multi ? view : \"carousel\";\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"@container grid gap-2\", className)} data-ckui=\"media-gallery\" data-view={shown} role=\"region\" aria-label={label ?? t(\"gallery.label\")}>\n {multi && (\n <div className=\"flex items-center justify-between gap-2\" data-ckui=\"gallery-header\">\n <span className=\"text-sm text-muted-foreground tabular-nums\" data-ckui=\"gallery-count\">\n {shown === \"carousel\" ? t(\"gallery.counter\", { current: current + 1, total: items.length }) : null}\n </span>\n <ToggleGroup aria-label={t(\"gallery.view\")} value={[shown]} onValueChange={(v) => v[0] && setView(v[0] as typeof view)}>\n <ToggleGroupItem value=\"carousel\" aria-label={t(\"gallery.carousel\")} title={t(\"gallery.carousel\")}>\n <HugeiconsIcon icon={CarouselHorizontalIcon} strokeWidth={2} />\n </ToggleGroupItem>\n <ToggleGroupItem value=\"grid\" aria-label={t(\"gallery.grid\")} title={t(\"gallery.grid\")}>\n <HugeiconsIcon icon={GridViewIcon} strokeWidth={2} />\n </ToggleGroupItem>\n </ToggleGroup>\n </div>\n )}\n {shown === \"carousel\" ? (\n <>\n <Carousel ctx={ctx} index={current} onIndex={setIndex} />\n {props.renderDetails?.(items[current]!)}\n </>\n ) : (\n <Grid ctx={ctx} onOpen={setLightbox} />\n )}\n <Lightbox ctx={ctx} index={lightbox} onIndex={setLightbox} />\n </ContentKitUiRoot>\n );\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\n\n// With item, the read, HLS bases, playlist auth and refresh come from the client.\nfunction useItemRead(p: MediaGalleryProps): MediaGalleryProps {\n const client = useOptionalContentKitClient(p.client);\n if (p.item && !client) throw new Error(\"contentkit-ui: MediaGallery item needs `client` or a <ContentKitProvider>\");\n // One refresh timer for the gallery and its players, keyed by r.refresh.\n const r = useMediaRead(p.item ?? NO_ITEM, { prefix: p.prefix, download: p.download, read: p.item ? p.read : null, refresh: false, client });\n const item = p.item;\n const hlsBase = useCallback((dir: string) => client!.media.hlsBase(item!, dir), [client, item]);\n if (!item || !client) return p;\n return { ...p, read: r.read, hlsBase: p.hlsBase ?? hlsBase, loader: p.loader ?? client.media.hls, refresh: p.refresh ?? r.refresh };\n}\n\nfunction Carousel({ ctx, index: given, onIndex, lightbox }: { ctx: Ctx; index: number; onIndex: (i: number) => void; lightbox?: boolean }) {\n const { t } = useMessages();\n const { items } = ctx;\n const c = useCarousel({ count: items.length, index: given, onIndexChange: onIndex });\n const { index } = c;\n const multi = items.length > 1;\n const stage = lightbox ? {} : { aspectRatio: String(stageAspect(items, index)), maxHeight: ctx.maxHeight ?? \"80svh\" };\n return (\n <div\n className={cn(\"group/carousel relative outline-none\", lightbox ? \"size-full\" : \"grid gap-2\")}\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"carousel\" : undefined}\n aria-label={multi ? (ctx.label ?? t(\"gallery.label\")) : undefined}\n tabIndex={multi && !lightbox ? 0 : undefined}\n onKeyDown={multi && !lightbox ? c.onKeyDown : undefined}\n data-ckui=\"carousel\"\n >\n <div\n className={cn(\n \"relative w-full touch-pan-y overflow-hidden select-none\",\n lightbox ? \"h-full\" : \"rounded-lg bg-muted transition-[aspect-ratio] duration-300 ease-out motion-reduce:transition-none\",\n )}\n style={stage}\n data-ckui=\"stage\"\n {...(multi ? c.swipe : {})}\n >\n <div\n className={cn(\"flex h-full\", c.dragging ? \"transition-none\" : \"transition-transform duration-300 ease-out motion-reduce:transition-none\")}\n style={{ transform: `translateX(calc(${-index * 100}% + ${c.offset}px))` }}\n >\n {items.map((item, i) => {\n const near = Math.abs(i - index) <= 1;\n return (\n <div\n key={item.key}\n className=\"relative h-full w-full shrink-0 overflow-hidden\"\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"slide\" : undefined}\n aria-label={multi ? t(\"gallery.slide\", { current: i + 1, total: items.length }) : undefined}\n aria-hidden={i !== index || undefined}\n inert={i !== index}\n data-ckui=\"slide\"\n data-current={i === index ? \"\" : undefined}\n >\n {near && <Slide ctx={ctx} item={item} position={i} active={i === index} lightbox={lightbox} />}\n </div>\n );\n })}\n </div>\n {multi && (\n <>\n <NavButton side=\"prev\" hidden={index === 0} last={index === 1} onClick={c.prev} label={t(\"gallery.previous\")} lightbox={lightbox} />\n <NavButton side=\"next\" hidden={index === items.length - 1} last={index === items.length - 2} onClick={c.next} label={t(\"gallery.next\")} lightbox={lightbox} />\n </>\n )}\n </div>\n {multi && !lightbox && items.length <= 12 && (\n <div className=\"flex justify-center gap-0.5\" data-ckui=\"dots\">\n {items.map((item, i) => (\n <button\n key={item.key}\n type=\"button\"\n className=\"group/dot flex size-4 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n aria-label={t(\"gallery.goTo\", { index: i + 1 })}\n aria-current={i === index || undefined}\n onClick={() => c.go(i)}\n >\n <span className={cn(\"size-1.5 rounded-full transition-colors\", i === index ? \"bg-foreground\" : \"bg-foreground/25 group-hover/dot:bg-foreground/50\")} />\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n// A strip down the stage's side, clear of a video's control bar: the stage's\n// height follows each slide's aspect, so a centred-only target would move\n// between quick clicks. A swipe may start on it. The click that reaches an end\n// removes the button, so focus moves to the carousel and the arrow keys keep working.\nfunction NavButton({ side, hidden, last, onClick, label, lightbox }: { side: \"prev\" | \"next\"; hidden: boolean; last: boolean; onClick: () => void; label: string; lightbox?: boolean }) {\n if (hidden) return null;\n return (\n <button\n type=\"button\"\n aria-label={label}\n data-ckui=\"gallery-nav\"\n data-side={side}\n onClick={(e) => {\n const root = e.currentTarget.closest<HTMLElement>(\"[data-ckui=lightbox], [data-ckui=carousel][tabindex]\");\n onClick();\n if (last && document.activeElement === e.currentTarget) root?.focus({ preventScroll: true });\n }}\n className={cn(\n \"group/nav absolute top-0 bottom-14 z-10 flex items-center outline-none\",\n lightbox ? \"w-16 sm:w-20\" : \"w-12\",\n side === \"prev\" ? (lightbox ? \"left-0 justify-start pl-3 sm:pl-5\" : \"left-0 justify-start pl-2\") : lightbox ? \"right-0 justify-end pr-3 sm:pr-5\" : \"right-0 justify-end pr-2\",\n )}\n >\n <span\n className={cn(\n \"flex items-center justify-center rounded-full backdrop-blur-sm transition-colors group-focus-visible/nav:ring-3 group-focus-visible/nav:ring-ring/60\",\n lightbox ? \"size-10 bg-white/10 text-white group-hover/nav:bg-white/20\" : \"size-8 bg-white/85 text-zinc-900 shadow-md group-hover/nav:bg-white\",\n )}\n >\n <HugeiconsIcon icon={side === \"prev\" ? ArrowLeft01Icon : ArrowRight01Icon} strokeWidth={2} className=\"size-4\" />\n </span>\n </button>\n );\n}\n\nfunction Slide({ ctx, item, position, active, lightbox }: { ctx: Ctx; item: GalleryItem; position: number; active: boolean; lightbox?: boolean }) {\n const { t } = useMessages();\n if (item.kind === \"locked\") return <Locked ctx={ctx} item={item} />;\n const f = item.file;\n if (item.kind === \"audio\") return <AudioSlide item={item} position={position} />;\n if (item.kind === \"image\") {\n if (!f.url) return f.failed ? <ImageFailed file={f} /> : <Processing>{t(\"gallery.processingImage\")}</Processing>;\n // A public teaser of a locked item carries the unlock, so no one swipes past it to find it.\n const locked = item.key.startsWith(\"preview/\") && !lightbox ? ctx.items.find((i): i is GalleryLockedItem => i.kind === \"locked\") : undefined;\n return (\n <>\n <img\n src={f.url}\n alt={t(\"gallery.image\", { index: position + 1 })}\n width={f.w}\n height={f.h}\n sizes={lightbox ? \"100vw\" : (ctx.sizes ?? \"(min-width: 768px) 720px, 100vw\")}\n loading={active ? \"eager\" : \"lazy\"}\n decoding=\"async\"\n draggable={false}\n className=\"absolute inset-0 size-full object-contain\"\n />\n {locked && <LockedBar ctx={ctx} />}\n </>\n );\n }\n const { poster, start } = videoArt(ctx, item);\n return (\n <VideoPlayer\n inlinePreview={lightbox ? false : ctx.inlinePreview}\n previewStart={start}\n layout=\"fill\"\n base={baseOf(ctx, item)}\n poster={poster}\n width={f.w}\n height={f.h}\n duration={f.dur}\n active={active}\n loader={ctx.loader}\n refresh={ctx.refresh}\n expires={ctx.read?.expires}\n abr={ctx.abr}\n label={t(\"gallery.video\", { index: position + 1 })}\n className={lightbox ? \"bg-transparent\" : undefined}\n />\n );\n}\n\n// An audio file (a ladder's M4A): a native player, its duration and, in a\n// download read, its download.\nfunction AudioSlide({ item, position }: { item: GalleryMediaItem; position: number }) {\n const { t } = useMessages();\n const f = item.file;\n const name = (item.dir ?? f.path).replace(/\\/$/, \"\").split(\"/\").pop();\n const label = t(\"gallery.audio\", { index: position + 1 });\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-muted p-4\" data-ckui=\"audio\">\n <div className=\"flex min-w-0 items-center gap-2 text-sm text-muted-foreground\">\n <HugeiconsIcon icon={MusicNote01Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {name && <span className=\"truncate\">{name}</span>}\n {f.dur ? <span className=\"tabular-nums\">{formatDuration(f.dur)}</span> : null}\n </div>\n {f.failed ? (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {t(\"gallery.failedAudio\")}\n </p>\n ) : f.url ? (\n <audio src={f.url} controls preload=\"metadata\" aria-label={label} className=\"w-full max-w-md\" data-ckui-noswipe=\"\" />\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t(\"gallery.processingAudio\")}</p>\n )}\n {f.download && f.url && (\n <a href={f.url} download={f.download} className=\"inline-flex items-center gap-1.5 text-sm underline-offset-4 hover:underline\" data-ckui-noswipe=\"\">\n <HugeiconsIcon icon={Download01Icon} className=\"size-4\" strokeWidth={2} />\n {t(\"gallery.download\")}\n </a>\n )}\n </div>\n );\n}\n\n// Why an image cannot be shown (editors only: the read API's failed fields).\nfunction ImageFailed({ file }: { file: FileInfo }) {\n const { t, error } = useMessages();\n const f = file.failed;\n const text = f?.code ? error({ code: f.code, message: f.message, details: f.details, refusal: true }) : t(\"gallery.failedImage\");\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted p-4 text-center text-sm text-destructive\" role=\"alert\" data-ckui=\"image-failed\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n <p className=\"max-w-sm text-balance\">{text}</p>\n </div>\n );\n}\n\nfunction Processing({ children }: { children: string }) {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center gap-2 bg-muted text-sm text-muted-foreground\">\n <span aria-hidden className=\"size-4 rounded-full border-2 border-current/30 border-t-current motion-safe:animate-spin\" />\n {children}\n </div>\n );\n}\n\nfunction Locked({ ctx, item, tile }: { ctx: Ctx; item: GalleryLockedItem; tile?: boolean }) {\n const { t } = useMessages();\n return (\n <div className=\"absolute inset-0 overflow-hidden bg-muted\" data-ckui=\"locked\">\n {item.backdrop && <img src={item.backdrop} alt=\"\" draggable={false} className=\"absolute inset-0 size-full scale-110 object-cover blur-xl\" />}\n <div className={cn(\"absolute inset-0 flex flex-col items-center justify-center gap-3 p-4 text-center\", item.backdrop ? \"bg-black/45 text-white\" : \"text-foreground\")}>\n <HugeiconsIcon icon={SquareLock02Icon} className={tile ? \"size-6\" : \"size-8\"} strokeWidth={1.75} />\n {!tile && (\n <>\n <p className=\"text-sm font-medium text-balance\">{t(\"gallery.locked\")}</p>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction LockedBar({ ctx }: { ctx: Ctx }) {\n const { t } = useMessages();\n return (\n <div\n className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-center justify-between gap-x-3 gap-y-2 bg-linear-to-t from-black/75 via-black/45 to-transparent px-4 pt-10 pb-4 text-white\"\n data-ckui=\"locked-bar\"\n >\n <span className=\"flex items-center gap-2 text-sm font-medium\">\n <HugeiconsIcon icon={SquareLock02Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {t(\"gallery.locked\")}\n </span>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </div>\n );\n}\n\nfunction Grid({ ctx, onOpen }: { ctx: Ctx; onOpen: (i: number) => void }) {\n const { t } = useMessages();\n const n = ctx.items.length;\n return (\n <ul className={cn(\"grid gap-1\", n === 2 || n === 4 ? \"grid-cols-2\" : \"grid-cols-2 @md:grid-cols-3\")} data-ckui=\"grid\">\n {ctx.items.map((item, i) => (\n <li key={item.key}>\n <Tile ctx={ctx} item={item} label={t(\"gallery.open\", { index: i + 1, total: n })} onOpen={() => onOpen(i)} />\n </li>\n ))}\n </ul>\n );\n}\n\nfunction Tile({ ctx, item, label, onOpen }: { ctx: Ctx; item: GalleryItem; label: string; onOpen: () => void }) {\n const [el, setEl] = useState<HTMLButtonElement | null>(null);\n let body: ReactNode;\n if (item.kind === \"locked\") body = <Locked ctx={ctx} item={item} tile />;\n else if (item.kind === \"audio\")\n body = (\n <span className=\"absolute inset-0 flex flex-col items-center justify-center gap-1 bg-muted text-muted-foreground\">\n <HugeiconsIcon icon={item.file.failed ? AlertCircleIcon : MusicNote01Icon} className={cn(\"size-7\", item.file.failed && \"text-destructive\")} strokeWidth={1.75} />\n {item.file.dur ? <span className=\"text-[11px] font-medium tabular-nums\">{formatDuration(item.file.dur)}</span> : null}\n </span>\n );\n else if (item.kind === \"image\")\n body = item.file.url ? (\n <img src={item.file.url} alt=\"\" loading=\"lazy\" decoding=\"async\" draggable={false} className=\"absolute inset-0 size-full object-cover transition-transform duration-300 motion-safe:group-hover:scale-[1.03]\" />\n ) : item.file.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : (\n <Processing>{\"\"}</Processing>\n );\n else {\n const f = item.file;\n const { poster, start } = videoArt(ctx, item);\n const covers = typeof poster === \"string\" ? [] : publicRenditions(poster);\n const base = baseOf(ctx, item);\n body = (\n <>\n {typeof poster === \"string\" ? (\n <img src={poster} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"absolute inset-0 size-full object-cover\" />\n ) : covers.length ? (\n <RenditionImg outputs={covers} loading=\"lazy\" className=\"absolute inset-0 size-full object-cover\" />\n ) : base ? (\n <SpriteFrame vtt={`${base}sprite.vtt`} loader={ctx.loader} fit=\"cover\" />\n ) : null}\n {base && <TilePreview ctx={ctx} base={base} start={start} target={el} />}\n {f.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : !base ? (\n <Processing>{\"\"}</Processing>\n ) : (\n <span className=\"pointer-events-none absolute top-1/2 left-1/2 flex size-10 -translate-1/2 items-center justify-center rounded-full bg-black/55 text-white backdrop-blur-sm transition-opacity group-hover:opacity-0\">\n <HugeiconsIcon icon={PlayIcon} className=\"ml-0.5 size-5 fill-current\" strokeWidth={1.5} />\n </span>\n )}\n {f.dur ? (\n <span className=\"pointer-events-none absolute right-1.5 bottom-1.5 rounded bg-black/65 px-1.5 py-0.5 text-[11px] font-medium text-white tabular-nums\">\n {formatDuration(f.dur)}\n </span>\n ) : null}\n </>\n );\n }\n return (\n <button\n type=\"button\"\n aria-label={label}\n onClick={onOpen}\n ref={setEl}\n className=\"group relative block aspect-square w-full overflow-hidden rounded-md bg-muted outline-none focus-visible:ring-3 focus-visible:ring-ring/60\"\n data-ckui=\"tile\"\n data-kind={item.kind}\n >\n {body}\n </button>\n );\n}\n\n// A grid tile's inline preview; the tile opens the lightbox for real playback.\nfunction TilePreview({ ctx, base, start, target }: { ctx: Ctx; base: string; start: number; target: HTMLElement | null }) {\n const player = useHlsPlayer({ src: `${base}master.m3u8`, loader: ctx.loader, refresh: ctx.refresh, expires: ctx.read?.expires, abr: ctx.abr, qualityKey: null });\n useInlinePreview({ setting: ctx.inlinePreview, available: true, target, start: () => player.preview(start), stop: player.unload });\n return (\n <video\n ref={player.ref}\n muted\n playsInline\n preload=\"none\"\n aria-hidden\n tabIndex={-1}\n className={cn(\n \"pointer-events-none absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-300 motion-reduce:transition-none\",\n player.previewing && player.status === \"playing\" && \"opacity-100\",\n )}\n data-ckui=\"tile-preview\"\n />\n );\n}\n\nfunction Lightbox({ ctx, index, onIndex }: { ctx: Ctx; index: number | null; onIndex: (i: number | null) => void }) {\n const { t } = useMessages();\n const scope = useScopeProps(ctx.appearance);\n const [shown, setShown] = useState(index ?? 0);\n const [prev, setPrev] = useState(index);\n if (index !== prev) {\n setPrev(index);\n if (index !== null) setShown(index);\n }\n const open = index !== null;\n const c = useCarousel({ count: ctx.items.length, index: shown, onIndexChange: setShown });\n return (\n <DialogPrimitive.Root open={open} onOpenChange={(o) => !o && onIndex(null)}>\n <DialogPrimitive.Portal {...scope} data-ckui-theme=\"dark\">\n <DialogPrimitive.Backdrop className=\"fixed inset-0 z-50 bg-black duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\" />\n <DialogPrimitive.Popup\n className=\"fixed inset-0 z-50 flex flex-col bg-black text-white outline-none duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\"\n data-ckui=\"lightbox\"\n onKeyDown={c.onKeyDown}\n >\n <DialogPrimitive.Title className=\"sr-only\">{ctx.label ?? t(\"gallery.lightbox\")}</DialogPrimitive.Title>\n <div className=\"flex h-14 shrink-0 items-center justify-between px-4\">\n <span className=\"text-sm text-white/80 tabular-nums\">{ctx.items.length > 1 ? t(\"gallery.counter\", { current: shown + 1, total: ctx.items.length }) : null}</span>\n <DialogPrimitive.Close render={<Button variant=\"ghost\" size=\"icon\" className=\"rounded-full text-white hover:bg-white/10 hover:text-white\" />}>\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />\n <span className=\"sr-only\">{t(\"common.close\")}</span>\n </DialogPrimitive.Close>\n </div>\n <div className=\"min-h-0 flex-1 pb-4 sm:px-4 sm:pb-6\">\n {open && <Carousel ctx={ctx} index={shown} onIndex={setShown} lightbox />}\n </div>\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n}\n"],"mappings":"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"}
|
|
@@ -121,7 +121,7 @@ function v({ handoff: v, onExpand: _e, onClose: y, onProgress: b, progressInterv
|
|
|
121
121
|
className: "pointer-events-none absolute inset-0 size-full object-contain"
|
|
122
122
|
}) : /* @__PURE__ */ h(l, {
|
|
123
123
|
vtt: `${v.base}sprite.vtt`,
|
|
124
|
-
|
|
124
|
+
loader: C.loader
|
|
125
125
|
})),
|
|
126
126
|
/* @__PURE__ */ h("div", {
|
|
127
127
|
"aria-hidden": !0,
|