@openrails/contentkit-ui 0.104.0-rc.2 → 0.104.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/contents.d.ts +2 -0
- package/dist/client/contents.js +68 -58
- package/dist/client/contents.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +4 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +2 -2
- package/dist/client/generated/routes.js +7 -2
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/components/admin/reader-content-editor.js +1 -1
- package/dist/components/admin/reader-content-editor.js.map +1 -1
- package/dist/components/social/poll.js +57 -48
- package/dist/components/social/poll.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/locales/de.js +1 -0
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts +1 -0
- package/dist/locales/en.js +1 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +1 -0
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +1 -0
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +1 -0
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/contents.js +1 -1
- package/dist/react/contents.js.map +1 -1
- package/dist/react/use-engagement.js +32 -31
- package/dist/react/use-engagement.js.map +1 -1
- package/dist/react/versions.d.ts +3 -1
- package/dist/react/versions.js +62 -60
- package/dist/react/versions.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/react/versions.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { sleep as
|
|
3
|
-
import { useMessages as
|
|
4
|
-
import { keyOf as
|
|
5
|
-
import { useMediaRead as
|
|
6
|
-
import { isPending as
|
|
7
|
-
import { useCallback as
|
|
1
|
+
import { ContentKitError as e, throwIfAborted as t, toContentKitError as n } from "../client/errors.js";
|
|
2
|
+
import { sleep as r } from "../client/media/client.js";
|
|
3
|
+
import { useMessages as i } from "../i18n/context.js";
|
|
4
|
+
import { keyOf as a, useContentScope as o, useResource as s } from "./use-resource.js";
|
|
5
|
+
import { useMediaRead as c } from "./read.js";
|
|
6
|
+
import { isPending as l } from "../client/versions.js";
|
|
7
|
+
import { useCallback as u, useEffect as d, useMemo as f, useState as p } from "react";
|
|
8
8
|
//#region src/react/versions.ts
|
|
9
|
-
var
|
|
10
|
-
function
|
|
9
|
+
var m = 2 ** 31 - 1;
|
|
10
|
+
function h(e, t = Date.now()) {
|
|
11
11
|
let n = (e?.segments ?? []).flatMap((e) => !e.released && e.release_at ? [Date.parse(e.release_at)] : []).filter((e) => e > t);
|
|
12
12
|
return n.length ? Math.min(...n) : null;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
let { client: r, store:
|
|
14
|
+
function g(e, t, n = {}) {
|
|
15
|
+
let { client: r, store: i, scope: c } = o(n.client), u = e && t ? a("reader-plan", c, e, t) : null, f = s(i, u, {
|
|
16
16
|
type: "reader-plan",
|
|
17
17
|
id: t ?? void 0
|
|
18
|
-
}, (n) => r.versions.reader(e, t, n)),
|
|
19
|
-
return
|
|
20
|
-
let e =
|
|
18
|
+
}, (n) => r.versions.reader(e, t, n)), p = l(f.data), g = f.data && !p ? f.data : null, { data: _, reload: v } = f;
|
|
19
|
+
return d(() => {
|
|
20
|
+
let e = l(_) ? Date.now() + (n.poll ?? 5e3) : h(_ || null);
|
|
21
21
|
if (e === null) return;
|
|
22
|
-
let t = setTimeout(v, Math.min(
|
|
22
|
+
let t = setTimeout(v, Math.min(m, Math.max(0, e - Date.now()) + 500));
|
|
23
23
|
return () => clearTimeout(t);
|
|
24
24
|
}, [
|
|
25
25
|
_,
|
|
@@ -27,14 +27,14 @@ function h(e, t, n = {}) {
|
|
|
27
27
|
n.poll
|
|
28
28
|
]), {
|
|
29
29
|
plan: g,
|
|
30
|
-
pending:
|
|
31
|
-
loading: !!
|
|
30
|
+
pending: p,
|
|
31
|
+
loading: !!u && (f.loading || !f.loaded),
|
|
32
32
|
error: f.error,
|
|
33
33
|
reload: v
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
function
|
|
37
|
-
let r = e?.item ?? null, i =
|
|
36
|
+
function _(e, t, n = {}) {
|
|
37
|
+
let r = e?.item ?? null, i = c(r ?? {
|
|
38
38
|
kind: "",
|
|
39
39
|
id: ""
|
|
40
40
|
}, {
|
|
@@ -43,7 +43,7 @@ function g(e, t, n = {}) {
|
|
|
43
43
|
client: n.client
|
|
44
44
|
});
|
|
45
45
|
return {
|
|
46
|
-
pages:
|
|
46
|
+
pages: f(() => {
|
|
47
47
|
let n = new Map((i.read?.files ?? []).flatMap((e) => e.url ? [[e.path, e.url]] : []));
|
|
48
48
|
return (e?.pages ?? []).map((e) => {
|
|
49
49
|
let r = e.files[t];
|
|
@@ -63,11 +63,11 @@ function g(e, t, n = {}) {
|
|
|
63
63
|
error: i.error
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
-
function
|
|
66
|
+
function v(e) {
|
|
67
67
|
let t = document.createElement("a");
|
|
68
68
|
t.href = e.url, t.download = e.name, t.rel = "noopener", t.style.display = "none", document.body.appendChild(t), t.click(), t.remove();
|
|
69
69
|
}
|
|
70
|
-
function
|
|
70
|
+
function y(e, t, n = Date.now()) {
|
|
71
71
|
switch (t?.code) {
|
|
72
72
|
case void 0: return;
|
|
73
73
|
case "rate_limited": return e.t("versionDownload.limited", { minutes: Math.max(1, Math.ceil((t.retryAfter ?? 60) / 60)) });
|
|
@@ -80,64 +80,66 @@ function v(e, t, n = Date.now()) {
|
|
|
80
80
|
default: return e.t("versionDownload.failed");
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
function
|
|
84
|
-
let { client:
|
|
85
|
-
client:
|
|
86
|
-
scope:
|
|
87
|
-
content:
|
|
88
|
-
version:
|
|
83
|
+
function b(a, s, c = {}) {
|
|
84
|
+
let { client: m, scope: h } = o(c.client), g = i(), _ = f(() => ({
|
|
85
|
+
client: m,
|
|
86
|
+
scope: h,
|
|
87
|
+
content: a,
|
|
88
|
+
version: s,
|
|
89
89
|
active: void 0
|
|
90
90
|
}), [
|
|
91
|
-
p,
|
|
92
91
|
m,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
h,
|
|
93
|
+
a,
|
|
94
|
+
s
|
|
95
|
+
]), [b, x] = p();
|
|
96
|
+
d(() => () => _.active?.abort(), [_]);
|
|
97
|
+
let S = b?.owner === _ ? b : void 0, C = c.start ?? v;
|
|
98
98
|
return {
|
|
99
|
-
download:
|
|
100
|
-
|
|
101
|
-
let
|
|
102
|
-
|
|
103
|
-
owner:
|
|
99
|
+
download: u(async () => {
|
|
100
|
+
_.active?.abort();
|
|
101
|
+
let i = _.active = new AbortController(), { signal: o } = i;
|
|
102
|
+
x({
|
|
103
|
+
owner: _,
|
|
104
104
|
preparing: !0
|
|
105
105
|
});
|
|
106
|
+
let u = Date.now() + 6e5;
|
|
106
107
|
try {
|
|
107
108
|
for (;;) {
|
|
108
|
-
let
|
|
109
|
-
if (
|
|
110
|
-
|
|
109
|
+
let n = await m.versions.download(a, s, o);
|
|
110
|
+
if (t(o), !l(n)) return C(n), n;
|
|
111
|
+
if (Date.now() >= u) throw new e("render_timeout", "the download is still being prepared");
|
|
112
|
+
await r(c.poll ?? 3e3, o);
|
|
111
113
|
}
|
|
112
114
|
} catch (e) {
|
|
113
|
-
let
|
|
114
|
-
throw !
|
|
115
|
-
owner:
|
|
115
|
+
let t = n(e);
|
|
116
|
+
throw !o.aborted && _.active === i && x({
|
|
117
|
+
owner: _,
|
|
116
118
|
preparing: !1,
|
|
117
|
-
error:
|
|
118
|
-
retryAt:
|
|
119
|
-
}),
|
|
119
|
+
error: t,
|
|
120
|
+
retryAt: t.code === "rate_limited" ? Date.now() + (t.retryAfter ?? 60) * 1e3 : void 0
|
|
121
|
+
}), t;
|
|
120
122
|
} finally {
|
|
121
|
-
!
|
|
123
|
+
!o.aborted && _.active === i && x((e) => e?.owner === _ ? {
|
|
122
124
|
...e,
|
|
123
125
|
preparing: !1
|
|
124
126
|
} : e);
|
|
125
127
|
}
|
|
126
128
|
}, [
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
m,
|
|
130
|
+
a,
|
|
131
|
+
s,
|
|
132
|
+
c.poll,
|
|
133
|
+
C,
|
|
134
|
+
_
|
|
133
135
|
]),
|
|
134
|
-
preparing:
|
|
135
|
-
error:
|
|
136
|
-
retryAt:
|
|
137
|
-
message:
|
|
136
|
+
preparing: S?.preparing ?? !1,
|
|
137
|
+
error: S?.error,
|
|
138
|
+
retryAt: S?.retryAt,
|
|
139
|
+
message: y(g, S?.error)
|
|
138
140
|
};
|
|
139
141
|
}
|
|
140
142
|
//#endregion
|
|
141
|
-
export {
|
|
143
|
+
export { y as downloadMessage, h as nextRelease, v as startDownload, g as useReaderPlan, _ as useSegmentPages, b as useVersionDownload };
|
|
142
144
|
|
|
143
145
|
//# sourceMappingURL=versions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"versions.js","names":[],"sources":["../../src/react/versions.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { throwIfAborted, toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { FileDownload, Pending, PlanSegment, ReaderPlan } from \"../client/generated/wire.js\";\nimport { isPending } from \"../client/versions.js\";\nimport { sleep } from \"../client/media/client.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport type { Translator } from \"../i18n/messages.js\";\nimport { useMediaRead, type ReadWindow } from \"./read.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\nexport interface UseReaderPlan {\n /** null while loading, pending or failed. */\n plan: ReaderPlan | null;\n /** The version has no current publication yet; the hook asks again. */\n pending: boolean;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\n// setTimeout's longest delay.\nconst MAX_DELAY = 2 ** 31 - 1;\n\n/** When the plan changes by itself: its next scheduled segment's release. */\nexport function nextRelease(plan: ReaderPlan | null, now = Date.now()): number | null {\n const at = (plan?.segments ?? []).flatMap((s) => (!s.released && s.release_at ? [Date.parse(s.release_at)] : [])).filter((t) => t > now);\n return at.length ? Math.min(...at) : null;\n}\n\n/**\n * A version's reader plan for this viewer: its segments (released or not,\n * open or locked), previews, playback item and reader metadata. It asks again\n * while the version is being published and when its next segment is released.\n */\nexport function useReaderPlan(content: string | null | undefined, version: string | null | undefined, o: { poll?: number; client?: ContentKitClient } = {}): UseReaderPlan {\n const { client, store, scope } = useContentScope(o.client);\n const key = content && version ? keyOf(\"reader-plan\", scope, content, version) : null;\n const r = useResource<ReaderPlan | Pending>(store, key, { type: \"reader-plan\", id: version ?? undefined }, (s) => client.versions.reader(content!, version!, s));\n const pending = isPending(r.data);\n const plan = r.data && !pending ? (r.data as ReaderPlan) : null;\n const { data, reload } = r;\n // Every answer schedules the next look: a pending one polls, a plan waits for its next release.\n useEffect(() => {\n const at = isPending(data) ? Date.now() + (o.poll ?? 5000) : nextRelease(data ? (data as ReaderPlan) : null);\n if (at === null) return;\n const t = setTimeout(reload, Math.min(MAX_DELAY, Math.max(0, at - Date.now()) + 500));\n return () => clearTimeout(t);\n }, [data, reload, o.poll]);\n return { plan, pending, loading: !!key && (r.loading || !r.loaded), error: r.error, reload };\n}\n\n/** A segment's page with the URL of one rendition, once its item's read signed it. */\nexport interface ReaderPage {\n name: string;\n w: number;\n h: number;\n /** undefined until read, or when the page lacks the rendition. */\n url?: string;\n}\n\nexport interface UseSegmentPages {\n pages: ReaderPage[];\n loading: boolean;\n error?: ContentKitError;\n}\n\n/**\n * An open segment's pages with URLs of one rendition, read through its\n * published item (windowed for long segments, refreshed before the URLs expire).\n */\nexport function useSegmentPages(segment: PlanSegment | null | undefined, rendition: string, o: { window?: ReadWindow; client?: ContentKitClient } = {}): UseSegmentPages {\n const item = segment?.item ?? null;\n const read = useMediaRead(item ?? { kind: \"\", id: \"\" }, { window: o.window, read: item ? undefined : null, client: o.client });\n const pages = useMemo(() => {\n const urls = new Map((read.read?.files ?? []).flatMap((f) => (f.url ? [[f.path, f.url] as const] : [])));\n return (segment?.pages ?? []).map((p) => {\n const path = p.files[rendition];\n return { name: p.name, w: p.w, h: p.h, url: path ? urls.get(path) : undefined };\n });\n }, [segment, rendition, read.read]);\n return { pages, loading: !!item && read.loading, error: read.error };\n}\n\nexport interface UseVersionDownload {\n /**\n * Grants the download (asking again while it is prepared), starts it in the\n * browser and resolves with the grant.\n */\n download: () => Promise<FileDownload>;\n /** Being prepared by the server, or granted and starting. */\n preparing: boolean;\n error?: ContentKitError;\n /** While a quota refused: when the viewer may download again (ms). */\n retryAt?: number;\n /**\n * What to tell the viewer about the last refusal: when a quota frees, when\n * a download the site holds back opens (\"available to download in N\n * days\"), a locked version, nothing to download, or a failure.\n */\n message?: string;\n}\n\n/** Starts a granted download: the browser fetches its URL, an attachment. */\nexport function startDownload(d: FileDownload): void {\n const a = document.createElement(\"a\");\n a.href = d.url;\n a.download = d.name;\n a.rel = \"noopener\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n a.remove();\n}\n\n/** The viewer's line for a download refusal at now. */\nexport function downloadMessage(m: Translator, err: ContentKitError | undefined, now = Date.now()): string | undefined {\n switch (err?.code) {\n case undefined:\n return undefined;\n case \"rate_limited\":\n return m.t(\"versionDownload.limited\", { minutes: Math.max(1, Math.ceil((err.retryAfter ?? 60) / 60)) });\n case \"download_unavailable\": {\n const ms = err.availableAt ? Date.parse(err.availableAt) - now : NaN;\n return ms > 0 ? m.plural(\"versionDownload.availableIn\", Math.max(1, Math.ceil(ms / 86_400_000))) : m.t(\"versionDownload.unavailable\");\n }\n case \"forbidden\":\n return m.t(\"versionDownload.locked\");\n case \"no_download\":\n return m.error(err);\n default:\n return m.t(\"versionDownload.failed\");\n }\n}\n\n/**\n * A version's download, charged to the viewer's download quota: the\n * original or rendition its kind serves, or its pages as a zip.\n */\nexport function useVersionDownload(\n content: string,\n version: string,\n o: { poll?: number; client?: ContentKitClient; start?: (d: FileDownload) => void } = {},\n): UseVersionDownload {\n const { client, scope } = useContentScope(o.client);\n const m = useMessages();\n const owner = useMemo(() => ({ client, scope, content, version, active: undefined as AbortController | undefined }), [client, scope, content, version]);\n const [state, setState] = useState<{ owner: typeof owner; preparing: boolean; error?: ContentKitError; retryAt?: number }>();\n useEffect(() => () => owner.active?.abort(), [owner]);\n const current = state?.owner === owner ? state : undefined;\n const start = o.start ?? startDownload;\n const download = useCallback(async () => {\n owner.active?.abort();\n const controller = owner.active = new AbortController();\n const { signal } = controller;\n setState({ owner, preparing: true });\n try {\n for (;;) {\n const out = await client.versions.download(content, version, signal);\n // Also reject a late result from a transport that ignored cancellation.\n throwIfAborted(signal);\n if (!isPending(out)) {\n start(out);\n return out;\n }\n await sleep(o.poll ?? 3000, signal);\n }\n } catch (e) {\n const error = toContentKitError(e);\n if (!signal.aborted && owner.active === controller) {\n setState({ owner, preparing: false, error, retryAt: error.code === \"rate_limited\" ? Date.now() + (error.retryAfter ?? 60) * 1000 : undefined });\n }\n throw error;\n } finally {\n if (!signal.aborted && owner.active === controller) setState((state) => state?.owner === owner ? { ...state, preparing: false } : state);\n }\n }, [client, content, version, o.poll, start, owner]);\n return { download, preparing: current?.preparing ?? false, error: current?.error, retryAt: current?.retryAt, message: downloadMessage(m, current?.error) };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"versions.js","names":[],"sources":["../../src/react/versions.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitError, throwIfAborted, toContentKitError } from \"../client/errors.js\";\nimport type { FileDownload, Pending, PlanSegment, ReaderPlan } from \"../client/generated/wire.js\";\nimport { isPending } from \"../client/versions.js\";\nimport { sleep } from \"../client/media/client.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport type { Translator } from \"../i18n/messages.js\";\nimport { useMediaRead, type ReadWindow } from \"./read.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\nexport interface UseReaderPlan {\n /** null while loading, pending or failed. */\n plan: ReaderPlan | null;\n /** The version has no current publication yet; the hook asks again. */\n pending: boolean;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\n// setTimeout's longest delay.\nconst MAX_DELAY = 2 ** 31 - 1;\n\n/** When the plan changes by itself: its next scheduled segment's release. */\nexport function nextRelease(plan: ReaderPlan | null, now = Date.now()): number | null {\n const at = (plan?.segments ?? []).flatMap((s) => (!s.released && s.release_at ? [Date.parse(s.release_at)] : [])).filter((t) => t > now);\n return at.length ? Math.min(...at) : null;\n}\n\n/**\n * A version's reader plan for this viewer: its segments (released or not,\n * open or locked), previews, playback item and reader metadata. It asks again\n * while the version is being published and when its next segment is released.\n */\nexport function useReaderPlan(content: string | null | undefined, version: string | null | undefined, o: { poll?: number; client?: ContentKitClient } = {}): UseReaderPlan {\n const { client, store, scope } = useContentScope(o.client);\n const key = content && version ? keyOf(\"reader-plan\", scope, content, version) : null;\n const r = useResource<ReaderPlan | Pending>(store, key, { type: \"reader-plan\", id: version ?? undefined }, (s) => client.versions.reader(content!, version!, s));\n const pending = isPending(r.data);\n const plan = r.data && !pending ? (r.data as ReaderPlan) : null;\n const { data, reload } = r;\n // Every answer schedules the next look: a pending one polls, a plan waits for its next release.\n useEffect(() => {\n const at = isPending(data) ? Date.now() + (o.poll ?? 5000) : nextRelease(data ? (data as ReaderPlan) : null);\n if (at === null) return;\n const t = setTimeout(reload, Math.min(MAX_DELAY, Math.max(0, at - Date.now()) + 500));\n return () => clearTimeout(t);\n }, [data, reload, o.poll]);\n return { plan, pending, loading: !!key && (r.loading || !r.loaded), error: r.error, reload };\n}\n\n/** A segment's page with the URL of one rendition, once its item's read signed it. */\nexport interface ReaderPage {\n name: string;\n w: number;\n h: number;\n /** undefined until read, or when the page lacks the rendition. */\n url?: string;\n}\n\nexport interface UseSegmentPages {\n pages: ReaderPage[];\n loading: boolean;\n error?: ContentKitError;\n}\n\n/**\n * An open segment's pages with URLs of one rendition, read through its\n * published item (windowed for long segments, refreshed before the URLs expire).\n */\nexport function useSegmentPages(segment: PlanSegment | null | undefined, rendition: string, o: { window?: ReadWindow; client?: ContentKitClient } = {}): UseSegmentPages {\n const item = segment?.item ?? null;\n const read = useMediaRead(item ?? { kind: \"\", id: \"\" }, { window: o.window, read: item ? undefined : null, client: o.client });\n const pages = useMemo(() => {\n const urls = new Map((read.read?.files ?? []).flatMap((f) => (f.url ? [[f.path, f.url] as const] : [])));\n return (segment?.pages ?? []).map((p) => {\n const path = p.files[rendition];\n return { name: p.name, w: p.w, h: p.h, url: path ? urls.get(path) : undefined };\n });\n }, [segment, rendition, read.read]);\n return { pages, loading: !!item && read.loading, error: read.error };\n}\n\nexport interface UseVersionDownload {\n /**\n * Grants the download (asking again while it is prepared), starts it in the\n * browser and resolves with the grant.\n */\n download: () => Promise<FileDownload>;\n /** Being prepared by the server, or granted and starting. */\n preparing: boolean;\n error?: ContentKitError;\n /** While a quota refused: when the viewer may download again (ms). */\n retryAt?: number;\n /**\n * What to tell the viewer about the last refusal: when a quota frees, when\n * a download the site holds back opens (\"available to download in N\n * days\"), a locked version, nothing to download, or a failure.\n */\n message?: string;\n}\n\n/** Starts a granted download: the browser fetches its URL, an attachment. */\nexport function startDownload(d: FileDownload): void {\n const a = document.createElement(\"a\");\n a.href = d.url;\n a.download = d.name;\n a.rel = \"noopener\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n a.remove();\n}\n\n/** The viewer's line for a download refusal at now. */\nexport function downloadMessage(m: Translator, err: ContentKitError | undefined, now = Date.now()): string | undefined {\n switch (err?.code) {\n case undefined:\n return undefined;\n case \"rate_limited\":\n return m.t(\"versionDownload.limited\", { minutes: Math.max(1, Math.ceil((err.retryAfter ?? 60) / 60)) });\n case \"download_unavailable\": {\n const ms = err.availableAt ? Date.parse(err.availableAt) - now : NaN;\n return ms > 0 ? m.plural(\"versionDownload.availableIn\", Math.max(1, Math.ceil(ms / 86_400_000))) : m.t(\"versionDownload.unavailable\");\n }\n case \"forbidden\":\n return m.t(\"versionDownload.locked\");\n case \"no_download\":\n return m.error(err);\n default:\n return m.t(\"versionDownload.failed\");\n }\n}\n\n/**\n * A version's download, charged to the viewer's download quota: the\n * original or rendition its kind serves, or its pages as a zip. Polls while\n * it is prepared (a zip is built after its pages publish, behind the worker\n * queue); render_timeout after ten minutes.\n */\nexport function useVersionDownload(\n content: string,\n version: string,\n o: { poll?: number; client?: ContentKitClient; start?: (d: FileDownload) => void } = {},\n): UseVersionDownload {\n const { client, scope } = useContentScope(o.client);\n const m = useMessages();\n const owner = useMemo(() => ({ client, scope, content, version, active: undefined as AbortController | undefined }), [client, scope, content, version]);\n const [state, setState] = useState<{ owner: typeof owner; preparing: boolean; error?: ContentKitError; retryAt?: number }>();\n useEffect(() => () => owner.active?.abort(), [owner]);\n const current = state?.owner === owner ? state : undefined;\n const start = o.start ?? startDownload;\n const download = useCallback(async () => {\n owner.active?.abort();\n const controller = owner.active = new AbortController();\n const { signal } = controller;\n setState({ owner, preparing: true });\n const until = Date.now() + 600_000;\n try {\n for (;;) {\n const out = await client.versions.download(content, version, signal);\n // Also reject a late result from a transport that ignored cancellation.\n throwIfAborted(signal);\n if (!isPending(out)) {\n start(out);\n return out;\n }\n if (Date.now() >= until) throw new ContentKitError(\"render_timeout\", \"the download is still being prepared\");\n await sleep(o.poll ?? 3000, signal);\n }\n } catch (e) {\n const error = toContentKitError(e);\n if (!signal.aborted && owner.active === controller) {\n setState({ owner, preparing: false, error, retryAt: error.code === \"rate_limited\" ? Date.now() + (error.retryAfter ?? 60) * 1000 : undefined });\n }\n throw error;\n } finally {\n if (!signal.aborted && owner.active === controller) setState((state) => state?.owner === owner ? { ...state, preparing: false } : state);\n }\n }, [client, content, version, o.poll, start, owner]);\n return { download, preparing: current?.preparing ?? false, error: current?.error, retryAt: current?.retryAt, message: downloadMessage(m, current?.error) };\n}\n"],"mappings":"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"}
|