@openrails/contentkit-ui 0.104.0-rc.1 → 0.104.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +6 -6
  2. package/dist/client/beacons.js +4 -1
  3. package/dist/client/beacons.js.map +1 -1
  4. package/dist/client/contents.d.ts +7 -1
  5. package/dist/client/contents.js +8 -0
  6. package/dist/client/contents.js.map +1 -1
  7. package/dist/client/discovery.d.ts +6 -2
  8. package/dist/client/discovery.js +1 -1
  9. package/dist/client/discovery.js.map +1 -1
  10. package/dist/client/generated/routes.d.ts +41 -28
  11. package/dist/client/generated/routes.js +63 -61
  12. package/dist/client/generated/routes.js.map +1 -1
  13. package/dist/client/generated/wire.d.ts +36 -74
  14. package/dist/client/generated/wire.js.map +1 -1
  15. package/dist/client/index.d.ts +1 -1
  16. package/dist/client/media/windows.d.ts +2 -0
  17. package/dist/client/media/windows.js +4 -1
  18. package/dist/client/media/windows.js.map +1 -1
  19. package/dist/client/stats.d.ts +4 -20
  20. package/dist/client/stats.js +0 -11
  21. package/dist/client/stats.js.map +1 -1
  22. package/dist/client/versions.d.ts +8 -6
  23. package/dist/client/versions.js +15 -10
  24. package/dist/client/versions.js.map +1 -1
  25. package/dist/components/admin/bulk-drafts.d.ts +17 -0
  26. package/dist/components/admin/bulk-drafts.js +41 -0
  27. package/dist/components/admin/bulk-drafts.js.map +1 -0
  28. package/dist/components/admin/content-admin.d.ts +1 -1
  29. package/dist/components/admin/content-admin.js.map +1 -1
  30. package/dist/components/admin/content-manager.d.ts +11 -3
  31. package/dist/components/admin/content-manager.js +739 -380
  32. package/dist/components/admin/content-manager.js.map +1 -1
  33. package/dist/components/admin/group-manager.d.ts +8 -3
  34. package/dist/components/admin/group-manager.js +218 -146
  35. package/dist/components/admin/group-manager.js.map +1 -1
  36. package/dist/components/admin/page-notices.d.ts +16 -0
  37. package/dist/components/admin/page-notices.js +113 -0
  38. package/dist/components/admin/page-notices.js.map +1 -0
  39. package/dist/components/admin/page-picker.d.ts +38 -0
  40. package/dist/components/admin/page-picker.js +159 -0
  41. package/dist/components/admin/page-picker.js.map +1 -0
  42. package/dist/components/admin/reader-content-editor.js +537 -500
  43. package/dist/components/admin/reader-content-editor.js.map +1 -1
  44. package/dist/components/admin/reader-manifest.js +181 -179
  45. package/dist/components/admin/reader-manifest.js.map +1 -1
  46. package/dist/components/admin/shell.d.ts +10 -2
  47. package/dist/components/admin/shell.js +114 -42
  48. package/dist/components/admin/shell.js.map +1 -1
  49. package/dist/components/admin/stats-dashboard.d.ts +4 -9
  50. package/dist/components/admin/stats-dashboard.js +300 -202
  51. package/dist/components/admin/stats-dashboard.js.map +1 -1
  52. package/dist/components/admin/upload-queue.d.ts +14 -0
  53. package/dist/components/admin/upload-queue.js +115 -98
  54. package/dist/components/admin/upload-queue.js.map +1 -1
  55. package/dist/components/content/card.d.ts +31 -3
  56. package/dist/components/content/card.js +324 -158
  57. package/dist/components/content/card.js.map +1 -1
  58. package/dist/components/content/daily-releases.js +14 -12
  59. package/dist/components/content/daily-releases.js.map +1 -1
  60. package/dist/components/content/editor-save.js +27 -25
  61. package/dist/components/content/editor-save.js.map +1 -1
  62. package/dist/components/content/editors.js +274 -253
  63. package/dist/components/content/editors.js.map +1 -1
  64. package/dist/components/content/favorites.d.ts +1 -1
  65. package/dist/components/content/favorites.js +36 -22
  66. package/dist/components/content/favorites.js.map +1 -1
  67. package/dist/components/content/grid.d.ts +11 -4
  68. package/dist/components/content/grid.js +52 -43
  69. package/dist/components/content/grid.js.map +1 -1
  70. package/dist/components/content/history.js +83 -63
  71. package/dist/components/content/history.js.map +1 -1
  72. package/dist/components/content/index.d.ts +5 -3
  73. package/dist/components/content/listing.d.ts +14 -3
  74. package/dist/components/content/listing.js +65 -56
  75. package/dist/components/content/listing.js.map +1 -1
  76. package/dist/components/content/offer-fields.js +6 -6
  77. package/dist/components/content/paged-grid.d.ts +10 -0
  78. package/dist/components/content/paged-grid.js +116 -0
  79. package/dist/components/content/paged-grid.js.map +1 -0
  80. package/dist/components/content/pagination.d.ts +19 -0
  81. package/dist/components/content/pagination.js +97 -0
  82. package/dist/components/content/pagination.js.map +1 -0
  83. package/dist/components/content/paywall.d.ts +9 -0
  84. package/dist/components/content/paywall.js +27 -3
  85. package/dist/components/content/paywall.js.map +1 -1
  86. package/dist/components/content/post-list.d.ts +37 -0
  87. package/dist/components/content/post-list.js +203 -0
  88. package/dist/components/content/post-list.js.map +1 -0
  89. package/dist/components/content/reader-settings.d.ts +2 -0
  90. package/dist/components/content/reader-settings.js +1 -1
  91. package/dist/components/content/reader-settings.js.map +1 -1
  92. package/dist/components/groups/banner.d.ts +6 -2
  93. package/dist/components/groups/banner.js +79 -61
  94. package/dist/components/groups/banner.js.map +1 -1
  95. package/dist/components/groups/directory.d.ts +12 -2
  96. package/dist/components/groups/directory.js +168 -105
  97. package/dist/components/groups/directory.js.map +1 -1
  98. package/dist/components/groups/editor.js +10 -10
  99. package/dist/components/groups/editor.js.map +1 -1
  100. package/dist/components/groups/entity-chips.d.ts +61 -0
  101. package/dist/components/groups/entity-chips.js +382 -0
  102. package/dist/components/groups/entity-chips.js.map +1 -0
  103. package/dist/components/groups/group-members.d.ts +29 -0
  104. package/dist/components/groups/group-members.js +474 -0
  105. package/dist/components/groups/group-members.js.map +1 -0
  106. package/dist/components/groups/header.js +53 -45
  107. package/dist/components/groups/header.js.map +1 -1
  108. package/dist/components/groups/index.d.ts +3 -1
  109. package/dist/components/groups/memberships.d.ts +3 -36
  110. package/dist/components/groups/memberships.js +105 -480
  111. package/dist/components/groups/memberships.js.map +1 -1
  112. package/dist/components/groups/scoped.d.ts +23 -0
  113. package/dist/components/groups/scoped.js +92 -0
  114. package/dist/components/groups/scoped.js.map +1 -0
  115. package/dist/components/media-folder-editor.d.ts +5 -0
  116. package/dist/components/media-folder-editor.js +418 -230
  117. package/dist/components/media-folder-editor.js.map +1 -1
  118. package/dist/components/sortable-list.d.ts +5 -1
  119. package/dist/components/sortable-list.js +97 -81
  120. package/dist/components/sortable-list.js.map +1 -1
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.js +41 -37
  123. package/dist/locales/admin/de.js +78 -45
  124. package/dist/locales/admin/de.js.map +1 -1
  125. package/dist/locales/admin/en.d.ts +74 -41
  126. package/dist/locales/admin/en.js +78 -45
  127. package/dist/locales/admin/en.js.map +1 -1
  128. package/dist/locales/admin/es.js +78 -45
  129. package/dist/locales/admin/es.js.map +1 -1
  130. package/dist/locales/admin/ja.js +78 -45
  131. package/dist/locales/admin/ja.js.map +1 -1
  132. package/dist/locales/admin/ko.js +78 -45
  133. package/dist/locales/admin/ko.js.map +1 -1
  134. package/dist/locales/admin/zh.js +78 -45
  135. package/dist/locales/admin/zh.js.map +1 -1
  136. package/dist/locales/de.js +1 -0
  137. package/dist/locales/de.js.map +1 -1
  138. package/dist/locales/discovery/de.js +25 -1
  139. package/dist/locales/discovery/de.js.map +1 -1
  140. package/dist/locales/discovery/en.d.ts +24 -0
  141. package/dist/locales/discovery/en.js +25 -1
  142. package/dist/locales/discovery/en.js.map +1 -1
  143. package/dist/locales/discovery/es.js +25 -1
  144. package/dist/locales/discovery/es.js.map +1 -1
  145. package/dist/locales/discovery/ja.js +25 -1
  146. package/dist/locales/discovery/ja.js.map +1 -1
  147. package/dist/locales/discovery/ko.js +25 -1
  148. package/dist/locales/discovery/ko.js.map +1 -1
  149. package/dist/locales/discovery/zh.js +25 -1
  150. package/dist/locales/discovery/zh.js.map +1 -1
  151. package/dist/locales/en.d.ts +119 -43
  152. package/dist/locales/en.js +1 -0
  153. package/dist/locales/en.js.map +1 -1
  154. package/dist/locales/es.js +1 -0
  155. package/dist/locales/es.js.map +1 -1
  156. package/dist/locales/groups/de.js +22 -4
  157. package/dist/locales/groups/de.js.map +1 -1
  158. package/dist/locales/groups/en.d.ts +20 -2
  159. package/dist/locales/groups/en.js +22 -4
  160. package/dist/locales/groups/en.js.map +1 -1
  161. package/dist/locales/groups/es.js +22 -4
  162. package/dist/locales/groups/es.js.map +1 -1
  163. package/dist/locales/groups/ja.js +22 -4
  164. package/dist/locales/groups/ja.js.map +1 -1
  165. package/dist/locales/groups/ko.js +22 -4
  166. package/dist/locales/groups/ko.js.map +1 -1
  167. package/dist/locales/groups/zh.js +22 -4
  168. package/dist/locales/groups/zh.js.map +1 -1
  169. package/dist/locales/ja.js +1 -0
  170. package/dist/locales/ja.js.map +1 -1
  171. package/dist/locales/ko.js +1 -0
  172. package/dist/locales/ko.js.map +1 -1
  173. package/dist/locales/zh.js +1 -0
  174. package/dist/locales/zh.js.map +1 -1
  175. package/dist/react/discovery.d.ts +16 -0
  176. package/dist/react/discovery.js +69 -37
  177. package/dist/react/discovery.js.map +1 -1
  178. package/dist/react/groups.d.ts +19 -3
  179. package/dist/react/groups.js +49 -22
  180. package/dist/react/groups.js.map +1 -1
  181. package/dist/react/index.d.ts +6 -4
  182. package/dist/react/page-notices.d.ts +13 -0
  183. package/dist/react/page-notices.js +38 -0
  184. package/dist/react/page-notices.js.map +1 -0
  185. package/dist/react/post-reads.d.ts +23 -0
  186. package/dist/react/post-reads.js +90 -0
  187. package/dist/react/post-reads.js.map +1 -0
  188. package/dist/react/remembered-sort.d.ts +5 -0
  189. package/dist/react/remembered-sort.js +29 -0
  190. package/dist/react/remembered-sort.js.map +1 -0
  191. package/dist/react/resources.js +2 -2
  192. package/dist/react/resources.js.map +1 -1
  193. package/dist/react/stats.d.ts +3 -7
  194. package/dist/react/stats.js +2 -6
  195. package/dist/react/stats.js.map +1 -1
  196. package/dist/react/use-engagement.d.ts +2 -0
  197. package/dist/react/use-engagement.js +8 -3
  198. package/dist/react/use-engagement.js.map +1 -1
  199. package/dist/react.js +29 -27
  200. package/dist/styles.css +1 -1
  201. package/dist/urls/index.d.ts +1 -1
  202. package/dist/urls.js +2 -2
  203. package/dist/urls.js.map +1 -1
  204. package/package.json +1 -1
  205. package/dist/components/admin/page-review.d.ts +0 -13
  206. package/dist/components/admin/page-review.js +0 -225
  207. package/dist/components/admin/page-review.js.map +0 -1
  208. package/dist/react/page-review.d.ts +0 -14
  209. package/dist/react/page-review.js +0 -37
  210. package/dist/react/page-review.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { ContentKitClient } from '../../client/client.js';
1
2
  import { FileInfo, RefBody } from '../../client/generated/wire.js';
2
3
  import { AdminFlowProps } from './shell.js';
3
4
  export interface UploadQueueProps extends AdminFlowProps {
@@ -5,6 +6,13 @@ export interface UploadQueueProps extends AdminFlowProps {
5
6
  createItem: (file: File) => Promise<RefBody>;
6
7
  /** The upload path in each item. Default "source". */
7
8
  path?: string;
9
+ /** Where a file uploads in its new item (path and media type), in place of path. */
10
+ target?: (ref: RefBody, file: File, signal: AbortSignal) => Promise<{
11
+ path: string;
12
+ type?: string;
13
+ }>;
14
+ /** Opens a file's item: an Open button once it has one. */
15
+ onOpen?: (ref: RefBody) => void;
8
16
  /** The file picker's accept, e.g. "video/*". */
9
17
  accept?: string;
10
18
  /** Files uploading at once. Default 2. */
@@ -21,3 +29,9 @@ export interface UploadQueueProps extends AdminFlowProps {
21
29
  * the same item. Leaving the page while files upload asks first.
22
30
  */
23
31
  export declare function UploadQueue({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: UploadQueueProps): import('react').JSX.Element;
32
+ type Inner = Omit<UploadQueueProps, "client" | "baseUrl" | "auth" | "guard" | "appearance" | "className" | "language" | "messages"> & {
33
+ client: ContentKitClient;
34
+ };
35
+ /** The queue inside an admin flow that already has its root (ContentManager's bulk upload). */
36
+ export declare function UploadQueueBody(p: Inner): import('react').JSX.Element;
37
+ export {};
@@ -43,133 +43,144 @@ function w({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className:
43
43
  language: o,
44
44
  messages: s,
45
45
  guard: r,
46
- children: /* @__PURE__ */ g(T, {
46
+ children: /* @__PURE__ */ g(E, {
47
47
  ...c,
48
48
  client: d
49
49
  })
50
50
  });
51
51
  }
52
- function T({ client: l, createItem: u, path: w = "source", accept: T, concurrency: E = 2, onUploaded: D, storageKey: O = "ckui.uploads", onError: k }) {
53
- let A = r(), { t: j } = A, M = i(k), N = a(), [P, F] = m([]), [I, L] = m(() => Object.values(S(O))), [R, z] = m(!1), B = p(null), V = p(/* @__PURE__ */ new Map()), H = p({
52
+ function T(e) {
53
+ return /* @__PURE__ */ g(E, { ...e });
54
+ }
55
+ function E({ client: l, createItem: u, path: w = "source", target: T, accept: E, concurrency: D = 2, onUploaded: O, onOpen: k, storageKey: A = "ckui.uploads", onError: j }) {
56
+ let M = r(), { t: N } = M, P = i(j), F = a(), [I, L] = m([]), [R, z] = m(() => Object.values(S(A))), [B, V] = m(!1), H = p(null), U = p(/* @__PURE__ */ new Map()), W = p({
54
57
  createItem: u,
55
- onUploaded: D
58
+ onUploaded: O,
59
+ place: T
56
60
  });
57
61
  f(() => {
58
- H.current = {
62
+ W.current = {
59
63
  createItem: u,
60
- onUploaded: D
64
+ onUploaded: O,
65
+ place: T
61
66
  };
62
67
  });
63
- let U = d((e, t) => F((n) => n.map((n) => n.id === e ? {
68
+ let G = d((e, t) => L((n) => n.map((n) => n.id === e ? {
64
69
  ...n,
65
70
  ...t
66
- } : n)), []), W = d(async (n) => {
71
+ } : n)), []), K = d(async (n) => {
67
72
  let r = new AbortController();
68
- V.current.set(n.id, r);
69
- let i = S(O)[n.key];
73
+ U.current.set(n.id, r);
74
+ let i = S(A)[n.key];
70
75
  try {
71
76
  let a = n.ref ?? i?.ref;
72
77
  if (!a) {
73
- U(n.id, {
78
+ G(n.id, {
74
79
  status: "creating",
75
80
  error: void 0
76
- }), a = await H.current.createItem(n.file);
81
+ }), a = await W.current.createItem(n.file);
77
82
  let e = a;
78
- C(O, (t) => void (t[n.key] = {
83
+ C(A, (t) => void (t[n.key] = {
79
84
  ref: e,
80
85
  name: n.file.name,
81
86
  size: n.file.size
82
87
  }));
83
88
  }
84
89
  let o = a;
85
- U(n.id, {
86
- status: "uploading",
90
+ G(n.id, {
87
91
  ref: a,
88
92
  error: void 0
89
93
  });
90
- let s = {
91
- ref: a,
94
+ let s = W.current.place, c = s ? await F(() => s(o, n.file, r.signal)) : {
92
95
  path: w,
96
+ type: void 0
97
+ };
98
+ G(n.id, { status: "uploading" });
99
+ let u = {
100
+ ref: a,
101
+ path: c.path,
102
+ type: c.type,
93
103
  signal: r.signal,
94
- onProgress: (e) => U(n.id, { progress: e }),
95
- onState: (e) => C(O, (t) => void (t[n.key] = {
104
+ onProgress: (e) => G(n.id, { progress: e }),
105
+ onState: (e) => C(A, (t) => void (t[n.key] = {
96
106
  ref: o,
97
107
  name: n.file.name,
98
108
  size: n.file.size,
99
109
  state: e ?? void 0
100
110
  }))
101
- }, c = await N(async () => {
111
+ }, d = await F(async () => {
102
112
  try {
103
113
  return await l.media.upload(n.file, {
104
- ...s,
114
+ ...u,
105
115
  resume: i?.state
106
116
  });
107
117
  } catch (t) {
108
- if (i?.state && e(t).code === "resume_mismatch") return l.media.upload(n.file, s);
118
+ if (i?.state && e(t).code === "resume_mismatch") return l.media.upload(n.file, u);
109
119
  throw t;
110
120
  }
111
121
  });
112
- U(n.id, { status: "committing" });
113
- let u = await N(() => l.media.commit(o, [{
122
+ G(n.id, { status: "committing" });
123
+ let f = await F(() => l.media.commit(o, [{
114
124
  op: "put",
115
- path: c.path,
116
- blob: c.blob
125
+ path: d.path,
126
+ source_name: d.sourceName,
127
+ blob: d.blob
117
128
  }], { signal: r.signal }));
118
- C(O, (e) => void delete e[n.key]), U(n.id, {
129
+ C(A, (e) => void delete e[n.key]), G(n.id, {
119
130
  status: "done",
120
131
  progress: void 0
121
132
  });
122
- let d = u.find((e) => t(e.path, c.path));
123
- d && H.current.onUploaded?.(o, d);
133
+ let p = f.find((e) => t(e.path, d.path));
134
+ p && W.current.onUploaded?.(o, p);
124
135
  } catch (t) {
125
136
  let r = e(t);
126
- r.code === "aborted" ? U(n.id, { status: "paused" }) : (U(n.id, {
137
+ r.code === "aborted" ? G(n.id, { status: "paused" }) : (G(n.id, {
127
138
  status: "failed",
128
139
  error: r
129
- }), M(r, "upload", n.file.name));
140
+ }), P(r, "upload", n.file.name));
130
141
  } finally {
131
- V.current.delete(n.id);
142
+ U.current.delete(n.id);
132
143
  }
133
144
  }, [
134
145
  l,
135
146
  w,
136
- O,
137
- N,
138
- U,
139
- M
147
+ A,
148
+ F,
149
+ G,
150
+ P
140
151
  ]);
141
152
  f(() => {
142
- let e = P.filter((e) => y.includes(e.status)).length, t = P.filter((e) => e.status === "queued").slice(0, Math.max(0, E - e));
153
+ let e = I.filter((e) => y.includes(e.status)).length, t = I.filter((e) => e.status === "queued").slice(0, Math.max(0, D - e));
143
154
  if (t.length) {
144
- F((e) => e.map((e) => t.some((t) => t.id === e.id) ? {
155
+ L((e) => e.map((e) => t.some((t) => t.id === e.id) ? {
145
156
  ...e,
146
157
  status: "creating"
147
158
  } : e));
148
- for (let e of t) W(e);
159
+ for (let e of t) K(e);
149
160
  }
150
161
  }, [
151
- P,
152
- E,
153
- W
162
+ I,
163
+ D,
164
+ K
154
165
  ]);
155
- let G = P.some((e) => y.includes(e.status) || e.status === "queued");
166
+ let q = I.some((e) => y.includes(e.status) || e.status === "queued");
156
167
  f(() => {
157
- if (!G) return;
168
+ if (!q) return;
158
169
  let e = (e) => {
159
170
  e.preventDefault(), e.returnValue = "";
160
171
  };
161
172
  return addEventListener("beforeunload", e), () => removeEventListener("beforeunload", e);
162
- }, [G]), f(() => {
163
- let e = V.current;
173
+ }, [q]), f(() => {
174
+ let e = U.current;
164
175
  return () => {
165
176
  for (let t of e.values()) t.abort();
166
177
  };
167
178
  }, []);
168
- let K = (e) => {
169
- let t = S(O), n = [];
179
+ let J = (e) => {
180
+ let t = S(A), n = [];
170
181
  for (let r of e) {
171
182
  let e = b(r);
172
- P.some((t) => t.key === e && t.status !== "done") || n.some((t) => t.key === e) || n.push({
183
+ I.some((t) => t.key === e && t.status !== "done") || n.some((t) => t.key === e) || n.push({
173
184
  id: `u${++x}`,
174
185
  file: r,
175
186
  key: e,
@@ -178,70 +189,70 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
178
189
  resumed: !!t[e]
179
190
  });
180
191
  }
181
- n.length && (L((e) => e.filter((e) => !n.some((t) => t.file.name === e.name && t.file.size === e.size))), F((e) => [...e, ...n]));
182
- }, q = (e) => {
183
- for (let [t, n] of V.current) (!e || t === e) && n.abort();
184
- F((t) => t.map((t) => t.status === "queued" && (!e || t.id === e) ? {
192
+ n.length && (z((e) => e.filter((e) => !n.some((t) => t.file.name === e.name && t.file.size === e.size))), L((e) => [...e, ...n]));
193
+ }, Y = (e) => {
194
+ for (let [t, n] of U.current) (!e || t === e) && n.abort();
195
+ L((t) => t.map((t) => t.status === "queued" && (!e || t.id === e) ? {
185
196
  ...t,
186
197
  status: "paused"
187
198
  } : t));
188
- }, J = (e) => F((t) => t.map((t) => (t.status === "paused" || t.status === "failed") && (!e || t.id === e) ? {
199
+ }, X = (e) => L((t) => t.map((t) => (t.status === "paused" || t.status === "failed") && (!e || t.id === e) ? {
189
200
  ...t,
190
201
  status: "queued",
191
202
  error: void 0
192
- } : t)), Y = (e) => {
193
- V.current.get(e.id)?.abort(), e.status !== "done" && C(O, (t) => void delete t[e.key]), F((t) => t.filter((t) => t.id !== e.id));
194
- }, X = (e) => {
195
- C(O, (t) => {
203
+ } : t)), Z = (e) => {
204
+ U.current.get(e.id)?.abort(), e.status !== "done" && C(A, (t) => void delete t[e.key]), L((t) => t.filter((t) => t.id !== e.id));
205
+ }, Q = (e) => {
206
+ C(A, (t) => {
196
207
  for (let [n, r] of Object.entries(t)) r.name === e.name && r.size === e.size && delete t[n];
197
- }), L((t) => t.filter((t) => t !== e));
198
- }, Z = P.filter((e) => e.status === "done").length, Q = P.some((e) => y.includes(e.status) || e.status === "queued"), ee = P.some((e) => e.status === "paused" || e.status === "failed"), $ = (e) => e.progress?.total ? Math.round(e.progress.loaded / e.progress.total * 100) : 0;
208
+ }), z((t) => t.filter((t) => t !== e));
209
+ }, ee = I.filter((e) => e.status === "done").length, te = I.some((e) => y.includes(e.status) || e.status === "queued"), ne = I.some((e) => e.status === "paused" || e.status === "failed"), $ = (e) => e.progress?.total ? Math.round(e.progress.loaded / e.progress.total * 100) : 0;
199
210
  return /* @__PURE__ */ _(h, { children: [
200
211
  /* @__PURE__ */ _("div", {
201
- className: v("grid justify-items-center gap-2 rounded-lg border-2 border-dashed border-border p-6 text-center transition-colors", R && "border-primary bg-accent"),
212
+ className: v("grid justify-items-center gap-2 rounded-lg border-2 border-dashed border-border p-6 text-center transition-colors", B && "border-primary bg-accent"),
202
213
  "data-ckui": "upload-drop",
203
214
  onDragOver: (e) => {
204
- e.preventDefault(), z(!0);
215
+ e.preventDefault(), V(!0);
205
216
  },
206
- onDragLeave: () => z(!1),
217
+ onDragLeave: () => V(!1),
207
218
  onDrop: (e) => {
208
- e.preventDefault(), z(!1), K(e.dataTransfer.files);
219
+ e.preventDefault(), V(!1), J(e.dataTransfer.files);
209
220
  },
210
221
  children: [
211
222
  /* @__PURE__ */ g("p", {
212
223
  className: "text-muted-foreground",
213
- children: j("uploadQueue.drop")
224
+ children: N("uploadQueue.drop")
214
225
  }),
215
226
  /* @__PURE__ */ g(o, {
216
227
  size: "sm",
217
- onClick: () => B.current?.click(),
218
- children: j("uploadQueue.add")
228
+ onClick: () => H.current?.click(),
229
+ children: N("uploadQueue.add")
219
230
  }),
220
231
  /* @__PURE__ */ g("input", {
221
- ref: B,
232
+ ref: H,
222
233
  type: "file",
223
234
  multiple: !0,
224
235
  hidden: !0,
225
- accept: T,
226
- "aria-label": j("uploadQueue.add"),
236
+ accept: E,
237
+ "aria-label": N("uploadQueue.add"),
227
238
  onChange: (e) => {
228
- K(e.target.files ?? []), e.target.value = "";
239
+ J(e.target.files ?? []), e.target.value = "";
229
240
  }
230
241
  })
231
242
  ]
232
243
  }),
233
- I.length > 0 && /* @__PURE__ */ _("div", {
244
+ R.length > 0 && /* @__PURE__ */ _("div", {
234
245
  className: "grid gap-2 rounded-lg border border-border p-3",
235
246
  "data-ckui": "upload-waiting",
236
247
  role: "status",
237
248
  children: [
238
249
  /* @__PURE__ */ g("p", {
239
250
  className: "font-medium",
240
- children: j("uploadQueue.unfinished")
251
+ children: N("uploadQueue.unfinished")
241
252
  }),
242
253
  /* @__PURE__ */ g("ul", {
243
254
  className: "grid gap-1",
244
- children: I.map((e) => /* @__PURE__ */ _("li", {
255
+ children: R.map((e) => /* @__PURE__ */ _("li", {
245
256
  className: "flex flex-wrap items-center gap-2",
246
257
  children: [
247
258
  /* @__PURE__ */ g("span", {
@@ -255,44 +266,44 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
255
266
  /* @__PURE__ */ g(o, {
256
267
  size: "xs",
257
268
  variant: "ghost",
258
- onClick: () => X(e),
259
- children: j("uploadQueue.forget")
269
+ onClick: () => Q(e),
270
+ children: N("uploadQueue.forget")
260
271
  })
261
272
  ]
262
273
  }, `${e.name}:${e.size}`))
263
274
  }),
264
275
  /* @__PURE__ */ g("p", {
265
276
  className: "text-xs text-muted-foreground",
266
- children: j("uploadQueue.unfinishedHint")
277
+ children: N("uploadQueue.unfinishedHint")
267
278
  })
268
279
  ]
269
280
  }),
270
- P.length > 0 && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", {
281
+ I.length > 0 && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", {
271
282
  className: "flex flex-wrap items-center gap-2",
272
283
  children: [/* @__PURE__ */ g("p", {
273
284
  className: "text-muted-foreground",
274
285
  "aria-live": "polite",
275
- children: j("uploadQueue.summary", {
276
- done: Z,
277
- total: P.length
286
+ children: N("uploadQueue.summary", {
287
+ done: ee,
288
+ total: I.length
278
289
  })
279
290
  }), /* @__PURE__ */ _("div", {
280
291
  className: "ms-auto flex gap-2",
281
- children: [Q && /* @__PURE__ */ g(o, {
292
+ children: [te && /* @__PURE__ */ g(o, {
282
293
  size: "sm",
283
294
  variant: "outline",
284
- onClick: () => q(),
285
- children: j("uploadQueue.pauseAll")
286
- }), ee && /* @__PURE__ */ g(o, {
295
+ onClick: () => Y(),
296
+ children: N("uploadQueue.pauseAll")
297
+ }), ne && /* @__PURE__ */ g(o, {
287
298
  size: "sm",
288
299
  variant: "outline",
289
- onClick: () => J(),
290
- children: j("uploadQueue.resumeAll")
300
+ onClick: () => X(),
301
+ children: N("uploadQueue.resumeAll")
291
302
  })]
292
303
  })]
293
304
  }), /* @__PURE__ */ g("ul", {
294
305
  className: "grid divide-y divide-border rounded-lg border border-border",
295
- children: P.map((e) => /* @__PURE__ */ _("li", {
306
+ children: I.map((e) => /* @__PURE__ */ _("li", {
296
307
  className: "grid gap-1.5 p-3",
297
308
  "data-ckui": "upload-entry",
298
309
  "data-status": e.status,
@@ -310,11 +321,11 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
310
321
  }),
311
322
  e.resumed && e.status !== "done" && /* @__PURE__ */ g(s, {
312
323
  variant: "secondary",
313
- children: j("uploadQueue.resumed")
324
+ children: N("uploadQueue.resumed")
314
325
  }),
315
326
  /* @__PURE__ */ g(s, {
316
327
  variant: e.status === "failed" ? "destructive" : e.status === "done" ? "outline" : "secondary",
317
- children: j(`uploadQueue.${e.status}`)
328
+ children: N(`uploadQueue.${e.status}`)
318
329
  })
319
330
  ]
320
331
  }),
@@ -330,27 +341,33 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
330
341
  style: { width: `${$(e)}%` }
331
342
  })
332
343
  }),
333
- e.error && /* @__PURE__ */ g(c, { children: A.error(e.error) }),
344
+ e.error && /* @__PURE__ */ g(c, { children: M.error(e.error) }),
334
345
  /* @__PURE__ */ _("div", {
335
346
  className: "flex flex-wrap gap-2",
336
347
  children: [
337
348
  (y.includes(e.status) || e.status === "queued") && /* @__PURE__ */ g(o, {
338
349
  size: "xs",
339
350
  variant: "ghost",
340
- onClick: () => q(e.id),
341
- children: j("uploadQueue.pause")
351
+ onClick: () => Y(e.id),
352
+ children: N("uploadQueue.pause")
342
353
  }),
343
354
  (e.status === "paused" || e.status === "failed") && /* @__PURE__ */ g(o, {
344
355
  size: "xs",
345
356
  variant: "outline",
346
- onClick: () => J(e.id),
347
- children: j(e.status === "failed" ? "common.retry" : "uploadQueue.resume")
357
+ onClick: () => X(e.id),
358
+ children: N(e.status === "failed" ? "common.retry" : "uploadQueue.resume")
359
+ }),
360
+ k && e.ref && (e.status === "done" || e.status === "failed") && /* @__PURE__ */ g(o, {
361
+ size: "xs",
362
+ variant: "outline",
363
+ onClick: () => k(e.ref),
364
+ children: N("uploadQueue.open")
348
365
  }),
349
366
  e.status !== "done" && /* @__PURE__ */ g(o, {
350
367
  size: "xs",
351
368
  variant: "ghost",
352
- onClick: () => Y(e),
353
- children: j("common.remove")
369
+ onClick: () => Z(e),
370
+ children: N("common.remove")
354
371
  })
355
372
  ]
356
373
  })
@@ -360,6 +377,6 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
360
377
  ] });
361
378
  }
362
379
  //#endregion
363
- export { w as UploadQueue };
380
+ export { w as UploadQueue, T as UploadQueueBody };
364
381
 
365
382
  //# sourceMappingURL=upload-queue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload-queue.js","names":[],"sources":["../../../src/components/admin/upload-queue.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { FileInfo, RefBody } from \"../../client/generated/wire.js\";\nimport { samePath, type Progress, type UploadState } from \"../../client/media/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { megabytes } from \"../../i18n/messages.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useErrorReporter, useGuarded } from \"../../react/context.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ErrorLine } from \"../social/parts.js\";\nimport { AdminRoot, type AdminFlowProps } from \"./shell.js\";\n\nexport interface UploadQueueProps extends AdminFlowProps {\n /** Makes the host's item for a file (e.g. a video record) and resolves with its ref; once per file, a reload included. */\n createItem: (file: File) => Promise<RefBody>;\n /** The upload path in each item. Default \"source\". */\n path?: string;\n /** The file picker's accept, e.g. \"video/*\". */\n accept?: string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Each committed file: its item and the upload as an editor reads it. */\n onUploaded?: (ref: RefBody, file: FileInfo) => void;\n /** The localStorage key unfinished uploads are kept under, to resume after a reload. Default \"ckui.uploads\". */\n storageKey?: string;\n}\n\ntype Status = \"queued\" | \"creating\" | \"uploading\" | \"committing\" | \"done\" | \"failed\" | \"paused\";\n\ninterface Entry {\n id: string;\n file: File;\n key: string;\n status: Status;\n progress?: Progress;\n ref?: RefBody;\n error?: ContentKitError;\n /** Resumed from an upload saved before a reload. */\n resumed?: boolean;\n}\n\ninterface Saved {\n ref: RefBody;\n name: string;\n size: number;\n state?: UploadState;\n}\n\nconst ACTIVE: readonly Status[] = [\"creating\", \"uploading\", \"committing\"];\nconst fingerprint = (f: File) => `${f.name}\\u0000${f.size}\\u0000${f.lastModified}`;\nlet seq = 0;\n\nfunction readSaved(key: string): Record<string, Saved> {\n try {\n return JSON.parse(localStorage.getItem(key) ?? \"{}\") as Record<string, Saved>;\n } catch {\n return {};\n }\n}\n\nfunction writeSaved(key: string, fn: (all: Record<string, Saved>) => void) {\n try {\n const all = readSaved(key);\n fn(all);\n if (Object.keys(all).length) localStorage.setItem(key, JSON.stringify(all));\n else localStorage.removeItem(key);\n } catch {\n // Without storage, a reload starts over.\n }\n}\n\n/**\n * Many files, each into its own new item (createItem): uploads in the\n * background with progress, pause, retry and resume. An unfinished upload is\n * kept in localStorage; adding the same file after a reload continues it in\n * the same item. Leaving the page while files upload asks first.\n */\nexport function UploadQueue({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: UploadQueueProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"upload-queue\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Queue {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<UploadQueueProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Queue({ client, createItem, path = \"source\", accept, concurrency = 2, onUploaded, storageKey = \"ckui.uploads\", onError }: Inner) {\n const m = useMessages();\n const { t } = m;\n const report = useErrorReporter(onError);\n const guarded = useGuarded();\n const [entries, setEntries] = useState<Entry[]>([]);\n const [waiting, setWaiting] = useState<Saved[]>(() => Object.values(readSaved(storageKey)));\n const [over, setOver] = useState(false);\n const input = useRef<HTMLInputElement>(null);\n const controllers = useRef(new Map<string, AbortController>());\n const latest = useRef({ createItem, onUploaded });\n useEffect(() => {\n latest.current = { createItem, onUploaded };\n });\n\n const patch = useCallback((id: string, next: Partial<Entry>) => setEntries((es) => es.map((e) => (e.id === id ? { ...e, ...next } : e))), []);\n\n const run = useCallback(\n async (entry: Entry) => {\n const ctl = new AbortController();\n controllers.current.set(entry.id, ctl);\n const saved = readSaved(storageKey)[entry.key];\n try {\n let ref = entry.ref ?? saved?.ref;\n if (!ref) {\n patch(entry.id, { status: \"creating\", error: undefined });\n ref = await latest.current.createItem(entry.file);\n const made = ref;\n writeSaved(storageKey, (all) => void (all[entry.key] = { ref: made, name: entry.file.name, size: entry.file.size }));\n }\n const target = ref;\n patch(entry.id, { status: \"uploading\", ref, error: undefined });\n const remember = (state: UploadState | null) =>\n writeSaved(storageKey, (all) => void (all[entry.key] = { ref: target, name: entry.file.name, size: entry.file.size, state: state ?? undefined }));\n const options = { ref, path, signal: ctl.signal, onProgress: (progress: Progress) => patch(entry.id, { progress }), onState: remember };\n const up = await guarded(async () => {\n try {\n return await client.media.upload(entry.file, { ...options, resume: saved?.state });\n } catch (e) {\n if (saved?.state && toContentKitError(e).code === \"resume_mismatch\") return client.media.upload(entry.file, options);\n throw e;\n }\n });\n patch(entry.id, { status: \"committing\" });\n const files = await guarded(() => client.media.commit(target, [{ op: \"put\", path: up.path, blob: up.blob }], { signal: ctl.signal }));\n writeSaved(storageKey, (all) => void delete all[entry.key]);\n patch(entry.id, { status: \"done\", progress: undefined });\n const file = files.find((f) => samePath(f.path, up.path));\n if (file) latest.current.onUploaded?.(target, file);\n } catch (e) {\n const error = toContentKitError(e);\n if (error.code === \"aborted\") patch(entry.id, { status: \"paused\" });\n else {\n patch(entry.id, { status: \"failed\", error });\n report(error, \"upload\", entry.file.name);\n }\n } finally {\n controllers.current.delete(entry.id);\n }\n },\n [client, path, storageKey, guarded, patch, report],\n );\n\n // Starts queued files while fewer than concurrency run.\n useEffect(() => {\n const active = entries.filter((e) => ACTIVE.includes(e.status)).length;\n const next = entries.filter((e) => e.status === \"queued\").slice(0, Math.max(0, concurrency - active));\n if (!next.length) return;\n setEntries((es) => es.map((e) => (next.some((n) => n.id === e.id) ? { ...e, status: \"creating\" } : e)));\n for (const e of next) void run(e);\n }, [entries, concurrency, run]);\n\n const busy = entries.some((e) => ACTIVE.includes(e.status) || e.status === \"queued\");\n useEffect(() => {\n if (!busy) return;\n const ask = (e: BeforeUnloadEvent) => {\n e.preventDefault();\n e.returnValue = \"\";\n };\n addEventListener(\"beforeunload\", ask);\n return () => removeEventListener(\"beforeunload\", ask);\n }, [busy]);\n useEffect(() => {\n const running = controllers.current;\n return () => {\n for (const c of running.values()) c.abort();\n };\n }, []);\n\n const add = (files: Iterable<File>) => {\n const saved = readSaved(storageKey);\n const added: Entry[] = [];\n for (const file of files) {\n const key = fingerprint(file);\n if (entries.some((e) => e.key === key && e.status !== \"done\") || added.some((e) => e.key === key)) continue;\n added.push({ id: `u${++seq}`, file, key, status: \"queued\", ref: saved[key]?.ref, resumed: !!saved[key] });\n }\n if (!added.length) return;\n setWaiting((w) => w.filter((s) => !added.some((a) => a.file.name === s.name && a.file.size === s.size)));\n setEntries((es) => [...es, ...added]);\n };\n const pause = (id?: string) => {\n for (const [k, c] of controllers.current) if (!id || k === id) c.abort();\n setEntries((es) => es.map((e) => (e.status === \"queued\" && (!id || e.id === id) ? { ...e, status: \"paused\" } : e)));\n };\n const resume = (id?: string) => setEntries((es) => es.map((e) => ((e.status === \"paused\" || e.status === \"failed\") && (!id || e.id === id) ? { ...e, status: \"queued\", error: undefined } : e)));\n const remove = (entry: Entry) => {\n controllers.current.get(entry.id)?.abort();\n if (entry.status !== \"done\") writeSaved(storageKey, (all) => void delete all[entry.key]);\n setEntries((es) => es.filter((e) => e.id !== entry.id));\n };\n const forget = (s: Saved) => {\n writeSaved(storageKey, (all) => {\n for (const [k, v] of Object.entries(all)) if (v.name === s.name && v.size === s.size) delete all[k];\n });\n setWaiting((w) => w.filter((x) => x !== s));\n };\n\n const done = entries.filter((e) => e.status === \"done\").length;\n const pausable = entries.some((e) => ACTIVE.includes(e.status) || e.status === \"queued\");\n const resumable = entries.some((e) => e.status === \"paused\" || e.status === \"failed\");\n const percent = (e: Entry) => (e.progress?.total ? Math.round((e.progress.loaded / e.progress.total) * 100) : 0);\n return (\n <>\n <div\n className={cn(\"grid justify-items-center gap-2 rounded-lg border-2 border-dashed border-border p-6 text-center transition-colors\", over && \"border-primary bg-accent\")}\n data-ckui=\"upload-drop\"\n onDragOver={(e) => {\n e.preventDefault();\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n e.preventDefault();\n setOver(false);\n add(e.dataTransfer.files);\n }}\n >\n <p className=\"text-muted-foreground\">{t(\"uploadQueue.drop\")}</p>\n <Button size=\"sm\" onClick={() => input.current?.click()}>\n {t(\"uploadQueue.add\")}\n </Button>\n <input\n ref={input}\n type=\"file\"\n multiple\n hidden\n accept={accept}\n aria-label={t(\"uploadQueue.add\")}\n onChange={(e) => {\n add(e.target.files ?? []);\n e.target.value = \"\";\n }}\n />\n </div>\n {waiting.length > 0 && (\n <div className=\"grid gap-2 rounded-lg border border-border p-3\" data-ckui=\"upload-waiting\" role=\"status\">\n <p className=\"font-medium\">{t(\"uploadQueue.unfinished\")}</p>\n <ul className=\"grid gap-1\">\n {waiting.map((s) => (\n <li key={`${s.name}:${s.size}`} className=\"flex flex-wrap items-center gap-2\">\n <span className=\"min-w-0 flex-1 break-all\">{s.name}</span>\n <span className=\"text-xs text-muted-foreground\">{megabytes(s.size)}</span>\n <Button size=\"xs\" variant=\"ghost\" onClick={() => forget(s)}>\n {t(\"uploadQueue.forget\")}\n </Button>\n </li>\n ))}\n </ul>\n <p className=\"text-xs text-muted-foreground\">{t(\"uploadQueue.unfinishedHint\")}</p>\n </div>\n )}\n {entries.length > 0 && (\n <>\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-muted-foreground\" aria-live=\"polite\">\n {t(\"uploadQueue.summary\", { done, total: entries.length })}\n </p>\n <div className=\"ms-auto flex gap-2\">\n {pausable && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => pause()}>\n {t(\"uploadQueue.pauseAll\")}\n </Button>\n )}\n {resumable && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => resume()}>\n {t(\"uploadQueue.resumeAll\")}\n </Button>\n )}\n </div>\n </div>\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {entries.map((e) => (\n <li key={e.id} className=\"grid gap-1.5 p-3\" data-ckui=\"upload-entry\" data-status={e.status}>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"min-w-0 flex-1 font-medium break-all\">{e.file.name}</span>\n <span className=\"text-xs text-muted-foreground\">{megabytes(e.file.size)}</span>\n {e.resumed && e.status !== \"done\" && <Badge variant=\"secondary\">{t(\"uploadQueue.resumed\")}</Badge>}\n <Badge variant={e.status === \"failed\" ? \"destructive\" : e.status === \"done\" ? \"outline\" : \"secondary\"}>{t(`uploadQueue.${e.status}`)}</Badge>\n </div>\n {e.status === \"uploading\" && (\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\" role=\"progressbar\" aria-label={e.file.name} aria-valuenow={percent(e)} aria-valuemin={0} aria-valuemax={100}>\n <div className=\"h-full bg-primary transition-[width]\" style={{ width: `${percent(e)}%` }} />\n </div>\n )}\n {e.error && <ErrorLine>{m.error(e.error)}</ErrorLine>}\n <div className=\"flex flex-wrap gap-2\">\n {(ACTIVE.includes(e.status) || e.status === \"queued\") && (\n <Button size=\"xs\" variant=\"ghost\" onClick={() => pause(e.id)}>\n {t(\"uploadQueue.pause\")}\n </Button>\n )}\n {(e.status === \"paused\" || e.status === \"failed\") && (\n <Button size=\"xs\" variant=\"outline\" onClick={() => resume(e.id)}>\n {t(e.status === \"failed\" ? \"common.retry\" : \"uploadQueue.resume\")}\n </Button>\n )}\n {e.status !== \"done\" && (\n <Button size=\"xs\" variant=\"ghost\" onClick={() => remove(e)}>\n {t(\"common.remove\")}\n </Button>\n )}\n </div>\n </li>\n ))}\n </ul>\n </>\n )}\n </>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"upload-queue.js","names":[],"sources":["../../../src/components/admin/upload-queue.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { FileInfo, RefBody } from \"../../client/generated/wire.js\";\nimport { samePath, type Progress, type UploadState } from \"../../client/media/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { megabytes } from \"../../i18n/messages.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useErrorReporter, useGuarded } from \"../../react/context.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ErrorLine } from \"../social/parts.js\";\nimport { AdminRoot, type AdminFlowProps } from \"./shell.js\";\n\nexport interface UploadQueueProps extends AdminFlowProps {\n /** Makes the host's item for a file (e.g. a video record) and resolves with its ref; once per file, a reload included. */\n createItem: (file: File) => Promise<RefBody>;\n /** The upload path in each item. Default \"source\". */\n path?: string;\n /** Where a file uploads in its new item (path and media type), in place of path. */\n target?: (ref: RefBody, file: File, signal: AbortSignal) => Promise<{ path: string; type?: string }>;\n /** Opens a file's item: an Open button once it has one. */\n onOpen?: (ref: RefBody) => void;\n /** The file picker's accept, e.g. \"video/*\". */\n accept?: string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Each committed file: its item and the upload as an editor reads it. */\n onUploaded?: (ref: RefBody, file: FileInfo) => void;\n /** The localStorage key unfinished uploads are kept under, to resume after a reload. Default \"ckui.uploads\". */\n storageKey?: string;\n}\n\ntype Status = \"queued\" | \"creating\" | \"uploading\" | \"committing\" | \"done\" | \"failed\" | \"paused\";\n\ninterface Entry {\n id: string;\n file: File;\n key: string;\n status: Status;\n progress?: Progress;\n ref?: RefBody;\n error?: ContentKitError;\n /** Resumed from an upload saved before a reload. */\n resumed?: boolean;\n}\n\ninterface Saved {\n ref: RefBody;\n name: string;\n size: number;\n state?: UploadState;\n}\n\nconst ACTIVE: readonly Status[] = [\"creating\", \"uploading\", \"committing\"];\nconst fingerprint = (f: File) => `${f.name}\\u0000${f.size}\\u0000${f.lastModified}`;\nlet seq = 0;\n\nfunction readSaved(key: string): Record<string, Saved> {\n try {\n return JSON.parse(localStorage.getItem(key) ?? \"{}\") as Record<string, Saved>;\n } catch {\n return {};\n }\n}\n\nfunction writeSaved(key: string, fn: (all: Record<string, Saved>) => void) {\n try {\n const all = readSaved(key);\n fn(all);\n if (Object.keys(all).length) localStorage.setItem(key, JSON.stringify(all));\n else localStorage.removeItem(key);\n } catch {\n // Without storage, a reload starts over.\n }\n}\n\n/**\n * Many files, each into its own new item (createItem): uploads in the\n * background with progress, pause, retry and resume. An unfinished upload is\n * kept in localStorage; adding the same file after a reload continues it in\n * the same item. Leaving the page while files upload asks first.\n */\nexport function UploadQueue({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: UploadQueueProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"upload-queue\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Queue {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<UploadQueueProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\n/** The queue inside an admin flow that already has its root (ContentManager's bulk upload). */\nexport function UploadQueueBody(p: Inner) {\n return <Queue {...p} />;\n}\n\nfunction Queue({ client, createItem, path = \"source\", target: place, accept, concurrency = 2, onUploaded, onOpen, storageKey = \"ckui.uploads\", onError }: Inner) {\n const m = useMessages();\n const { t } = m;\n const report = useErrorReporter(onError);\n const guarded = useGuarded();\n const [entries, setEntries] = useState<Entry[]>([]);\n const [waiting, setWaiting] = useState<Saved[]>(() => Object.values(readSaved(storageKey)));\n const [over, setOver] = useState(false);\n const input = useRef<HTMLInputElement>(null);\n const controllers = useRef(new Map<string, AbortController>());\n const latest = useRef({ createItem, onUploaded, place });\n useEffect(() => {\n latest.current = { createItem, onUploaded, place };\n });\n\n const patch = useCallback((id: string, next: Partial<Entry>) => setEntries((es) => es.map((e) => (e.id === id ? { ...e, ...next } : e))), []);\n\n const run = useCallback(\n async (entry: Entry) => {\n const ctl = new AbortController();\n controllers.current.set(entry.id, ctl);\n const saved = readSaved(storageKey)[entry.key];\n try {\n let ref = entry.ref ?? saved?.ref;\n if (!ref) {\n patch(entry.id, { status: \"creating\", error: undefined });\n ref = await latest.current.createItem(entry.file);\n const made = ref;\n writeSaved(storageKey, (all) => void (all[entry.key] = { ref: made, name: entry.file.name, size: entry.file.size }));\n }\n const target = ref;\n patch(entry.id, { ref, error: undefined });\n const where = latest.current.place;\n const dest = where ? await guarded(() => where(target, entry.file, ctl.signal)) : { path, type: undefined };\n patch(entry.id, { status: \"uploading\" });\n const remember = (state: UploadState | null) =>\n writeSaved(storageKey, (all) => void (all[entry.key] = { ref: target, name: entry.file.name, size: entry.file.size, state: state ?? undefined }));\n const options = { ref, path: dest.path, type: dest.type, signal: ctl.signal, onProgress: (progress: Progress) => patch(entry.id, { progress }), onState: remember };\n const up = await guarded(async () => {\n try {\n return await client.media.upload(entry.file, { ...options, resume: saved?.state });\n } catch (e) {\n if (saved?.state && toContentKitError(e).code === \"resume_mismatch\") return client.media.upload(entry.file, options);\n throw e;\n }\n });\n patch(entry.id, { status: \"committing\" });\n const files = await guarded(() => client.media.commit(target, [{ op: \"put\", path: up.path, source_name: up.sourceName, blob: up.blob }], { signal: ctl.signal }));\n writeSaved(storageKey, (all) => void delete all[entry.key]);\n patch(entry.id, { status: \"done\", progress: undefined });\n const file = files.find((f) => samePath(f.path, up.path));\n if (file) latest.current.onUploaded?.(target, file);\n } catch (e) {\n const error = toContentKitError(e);\n if (error.code === \"aborted\") patch(entry.id, { status: \"paused\" });\n else {\n patch(entry.id, { status: \"failed\", error });\n report(error, \"upload\", entry.file.name);\n }\n } finally {\n controllers.current.delete(entry.id);\n }\n },\n [client, path, storageKey, guarded, patch, report],\n );\n\n // Starts queued files while fewer than concurrency run.\n useEffect(() => {\n const active = entries.filter((e) => ACTIVE.includes(e.status)).length;\n const next = entries.filter((e) => e.status === \"queued\").slice(0, Math.max(0, concurrency - active));\n if (!next.length) return;\n setEntries((es) => es.map((e) => (next.some((n) => n.id === e.id) ? { ...e, status: \"creating\" } : e)));\n for (const e of next) void run(e);\n }, [entries, concurrency, run]);\n\n const busy = entries.some((e) => ACTIVE.includes(e.status) || e.status === \"queued\");\n useEffect(() => {\n if (!busy) return;\n const ask = (e: BeforeUnloadEvent) => {\n e.preventDefault();\n e.returnValue = \"\";\n };\n addEventListener(\"beforeunload\", ask);\n return () => removeEventListener(\"beforeunload\", ask);\n }, [busy]);\n useEffect(() => {\n const running = controllers.current;\n return () => {\n for (const c of running.values()) c.abort();\n };\n }, []);\n\n const add = (files: Iterable<File>) => {\n const saved = readSaved(storageKey);\n const added: Entry[] = [];\n for (const file of files) {\n const key = fingerprint(file);\n if (entries.some((e) => e.key === key && e.status !== \"done\") || added.some((e) => e.key === key)) continue;\n added.push({ id: `u${++seq}`, file, key, status: \"queued\", ref: saved[key]?.ref, resumed: !!saved[key] });\n }\n if (!added.length) return;\n setWaiting((w) => w.filter((s) => !added.some((a) => a.file.name === s.name && a.file.size === s.size)));\n setEntries((es) => [...es, ...added]);\n };\n const pause = (id?: string) => {\n for (const [k, c] of controllers.current) if (!id || k === id) c.abort();\n setEntries((es) => es.map((e) => (e.status === \"queued\" && (!id || e.id === id) ? { ...e, status: \"paused\" } : e)));\n };\n const resume = (id?: string) => setEntries((es) => es.map((e) => ((e.status === \"paused\" || e.status === \"failed\") && (!id || e.id === id) ? { ...e, status: \"queued\", error: undefined } : e)));\n const remove = (entry: Entry) => {\n controllers.current.get(entry.id)?.abort();\n if (entry.status !== \"done\") writeSaved(storageKey, (all) => void delete all[entry.key]);\n setEntries((es) => es.filter((e) => e.id !== entry.id));\n };\n const forget = (s: Saved) => {\n writeSaved(storageKey, (all) => {\n for (const [k, v] of Object.entries(all)) if (v.name === s.name && v.size === s.size) delete all[k];\n });\n setWaiting((w) => w.filter((x) => x !== s));\n };\n\n const done = entries.filter((e) => e.status === \"done\").length;\n const pausable = entries.some((e) => ACTIVE.includes(e.status) || e.status === \"queued\");\n const resumable = entries.some((e) => e.status === \"paused\" || e.status === \"failed\");\n const percent = (e: Entry) => (e.progress?.total ? Math.round((e.progress.loaded / e.progress.total) * 100) : 0);\n return (\n <>\n <div\n className={cn(\"grid justify-items-center gap-2 rounded-lg border-2 border-dashed border-border p-6 text-center transition-colors\", over && \"border-primary bg-accent\")}\n data-ckui=\"upload-drop\"\n onDragOver={(e) => {\n e.preventDefault();\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n e.preventDefault();\n setOver(false);\n add(e.dataTransfer.files);\n }}\n >\n <p className=\"text-muted-foreground\">{t(\"uploadQueue.drop\")}</p>\n <Button size=\"sm\" onClick={() => input.current?.click()}>\n {t(\"uploadQueue.add\")}\n </Button>\n <input\n ref={input}\n type=\"file\"\n multiple\n hidden\n accept={accept}\n aria-label={t(\"uploadQueue.add\")}\n onChange={(e) => {\n add(e.target.files ?? []);\n e.target.value = \"\";\n }}\n />\n </div>\n {waiting.length > 0 && (\n <div className=\"grid gap-2 rounded-lg border border-border p-3\" data-ckui=\"upload-waiting\" role=\"status\">\n <p className=\"font-medium\">{t(\"uploadQueue.unfinished\")}</p>\n <ul className=\"grid gap-1\">\n {waiting.map((s) => (\n <li key={`${s.name}:${s.size}`} className=\"flex flex-wrap items-center gap-2\">\n <span className=\"min-w-0 flex-1 break-all\">{s.name}</span>\n <span className=\"text-xs text-muted-foreground\">{megabytes(s.size)}</span>\n <Button size=\"xs\" variant=\"ghost\" onClick={() => forget(s)}>\n {t(\"uploadQueue.forget\")}\n </Button>\n </li>\n ))}\n </ul>\n <p className=\"text-xs text-muted-foreground\">{t(\"uploadQueue.unfinishedHint\")}</p>\n </div>\n )}\n {entries.length > 0 && (\n <>\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-muted-foreground\" aria-live=\"polite\">\n {t(\"uploadQueue.summary\", { done, total: entries.length })}\n </p>\n <div className=\"ms-auto flex gap-2\">\n {pausable && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => pause()}>\n {t(\"uploadQueue.pauseAll\")}\n </Button>\n )}\n {resumable && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => resume()}>\n {t(\"uploadQueue.resumeAll\")}\n </Button>\n )}\n </div>\n </div>\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\">\n {entries.map((e) => (\n <li key={e.id} className=\"grid gap-1.5 p-3\" data-ckui=\"upload-entry\" data-status={e.status}>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"min-w-0 flex-1 font-medium break-all\">{e.file.name}</span>\n <span className=\"text-xs text-muted-foreground\">{megabytes(e.file.size)}</span>\n {e.resumed && e.status !== \"done\" && <Badge variant=\"secondary\">{t(\"uploadQueue.resumed\")}</Badge>}\n <Badge variant={e.status === \"failed\" ? \"destructive\" : e.status === \"done\" ? \"outline\" : \"secondary\"}>{t(`uploadQueue.${e.status}`)}</Badge>\n </div>\n {e.status === \"uploading\" && (\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\" role=\"progressbar\" aria-label={e.file.name} aria-valuenow={percent(e)} aria-valuemin={0} aria-valuemax={100}>\n <div className=\"h-full bg-primary transition-[width]\" style={{ width: `${percent(e)}%` }} />\n </div>\n )}\n {e.error && <ErrorLine>{m.error(e.error)}</ErrorLine>}\n <div className=\"flex flex-wrap gap-2\">\n {(ACTIVE.includes(e.status) || e.status === \"queued\") && (\n <Button size=\"xs\" variant=\"ghost\" onClick={() => pause(e.id)}>\n {t(\"uploadQueue.pause\")}\n </Button>\n )}\n {(e.status === \"paused\" || e.status === \"failed\") && (\n <Button size=\"xs\" variant=\"outline\" onClick={() => resume(e.id)}>\n {t(e.status === \"failed\" ? \"common.retry\" : \"uploadQueue.resume\")}\n </Button>\n )}\n {onOpen && e.ref && (e.status === \"done\" || e.status === \"failed\") && (\n <Button size=\"xs\" variant=\"outline\" onClick={() => onOpen(e.ref!)}>\n {t(\"uploadQueue.open\")}\n </Button>\n )}\n {e.status !== \"done\" && (\n <Button size=\"xs\" variant=\"ghost\" onClick={() => remove(e)}>\n {t(\"common.remove\")}\n </Button>\n )}\n </div>\n </li>\n ))}\n </ul>\n </>\n )}\n </>\n );\n}\n"],"mappings":"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"}
@@ -21,8 +21,15 @@ export interface ContentCardProps {
21
21
  loadMedia?: boolean;
22
22
  /** The group kinds whose chips show, in order; default every kind the card names. */
23
23
  groupKinds?: readonly string[];
24
- /** Chips per group kind. Default 3. */
25
- chips?: number;
24
+ /** Chips per group kind, or `{ base, lg }` to show a different number from the lg breakpoint. Default 3. */
25
+ chips?: number | {
26
+ base: number;
27
+ lg: number;
28
+ };
29
+ /** After a kind's chips, a "+N" chip counting the ones left out. Default false. */
30
+ chipOverflow?: boolean;
31
+ /** Inline chips on one line, as many whole chips as fit (at least one); the "+N" counts the rest. Default false. */
32
+ chipFit?: boolean;
26
33
  /** Select which entity chips appear, before the per-kind chip limit. */
27
34
  filterEntity?: (entity: EntityChip, kind: string) => boolean;
28
35
  /** Bordered chips (default) or plain linked text. */
@@ -43,6 +50,10 @@ export interface ContentCardProps {
43
50
  ownerHref?: (g: GroupLike) => string | undefined;
44
51
  /** Over the cover: the host's badges for the card, placed in the cover's box; clicks reach the card. */
45
52
  decorate?: (card: Card) => ReactNode;
53
+ /** Mark updated, completed and (for staff) deleted cards over the cover. Default true; false when the host draws cardStatus itself. */
54
+ status?: boolean;
55
+ /** Dim the cover of what the reader disliked. Default true. */
56
+ dimDisliked?: boolean;
46
57
  /** Below the meta line: the host's own. */
47
58
  children?: ReactNode;
48
59
  className?: string;
@@ -53,11 +64,28 @@ export interface CardMetadata {
53
64
  traits?: boolean | readonly string[];
54
65
  pages?: boolean;
55
66
  duration?: boolean;
67
+ /** Group kinds named last in the meta line: the first entity of the first kind the card has, linked. */
68
+ groups?: readonly string[];
56
69
  }
70
+ /** What a card says about the reader's standing and the work's state. */
71
+ export interface CardStatus {
72
+ /** A newer version since the reader last read it, else within the last two weeks. */
73
+ updated: boolean;
74
+ completed: boolean;
75
+ /** Only staff and the item's creators are told. */
76
+ deleted: boolean;
77
+ disliked: boolean;
78
+ }
79
+ export declare function cardStatus(card: Card, now?: number): CardStatus;
80
+ /** Quiet marks for a card's status: updated, completed and deleted. */
81
+ export declare function CardStatusBadges({ card, className }: {
82
+ card: Card;
83
+ className?: string;
84
+ }): import('react').JSX.Element | null;
57
85
  /** Why a card is locked, in words (its price when it is on sale); null when it opens. */
58
86
  export declare function lockLabel(m: Translator, a: CardAccess, tiers?: readonly string[], labels?: Labels): string | null;
59
87
  /** One content in a listing: cover (a video the reader opens previews over it on hover), title, version, groups, counts, the reader's progress and why it is locked; what its kind's locked view hides is absent. */
60
- export declare function ContentCard({ card, client, href, onOpen, shape, aspectRatio, titlePosition, loadMedia, groupKinds, chips, filterEntity, entityStyle, entityLayout, metadata, showCounts, tiers, labels, owner, ownerHref, decorate, children, className }: ContentCardProps): import('react').JSX.Element;
88
+ export declare function ContentCard({ card, client, href, onOpen, shape, aspectRatio, titlePosition, loadMedia, groupKinds, chips, chipOverflow, chipFit, filterEntity, entityStyle, entityLayout, metadata, showCounts, tiers, labels, owner, ownerHref, decorate, status, dimDisliked, children, className }: ContentCardProps): import('react').JSX.Element;
61
89
  /** The group that owns a content (a channel): its avatar and name, linking to its page. */
62
90
  export declare function OwnerChip({ group, href, className, loadMedia }: {
63
91
  group: GroupLike;