@openrails/contentkit-ui 0.0.0-stage → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/README.md +425 -2
  2. package/dist/appearance.d.ts +38 -0
  3. package/dist/appearance.js +15 -0
  4. package/dist/appearance.js.map +1 -0
  5. package/dist/assets/hash.worker-BYfo-7MQ.js.map +1 -0
  6. package/dist/client/aspect.d.ts +6 -0
  7. package/dist/client/aspect.js +17 -0
  8. package/dist/client/aspect.js.map +1 -0
  9. package/dist/client/client.d.ts +51 -0
  10. package/dist/client/client.js +35 -0
  11. package/dist/client/client.js.map +1 -0
  12. package/dist/client/codes.d.ts +12 -0
  13. package/dist/client/codes.js +19 -0
  14. package/dist/client/codes.js.map +1 -0
  15. package/dist/client/content/bans.d.ts +15 -0
  16. package/dist/client/content/bans.js +40 -0
  17. package/dist/client/content/bans.js.map +1 -0
  18. package/dist/client/content/comments.d.ts +36 -0
  19. package/dist/client/content/comments.js +93 -0
  20. package/dist/client/content/comments.js.map +1 -0
  21. package/dist/client/content/favorites.d.ts +14 -0
  22. package/dist/client/content/favorites.js +42 -0
  23. package/dist/client/content/favorites.js.map +1 -0
  24. package/dist/client/content/moderation.d.ts +18 -0
  25. package/dist/client/content/moderation.js +37 -0
  26. package/dist/client/content/moderation.js.map +1 -0
  27. package/dist/client/content/polls.d.ts +47 -0
  28. package/dist/client/content/polls.js +148 -0
  29. package/dist/client/content/polls.js.map +1 -0
  30. package/dist/client/content/posts.d.ts +44 -0
  31. package/dist/client/content/posts.js +91 -0
  32. package/dist/client/content/posts.js.map +1 -0
  33. package/dist/client/content/reactions.d.ts +12 -0
  34. package/dist/client/content/reactions.js +36 -0
  35. package/dist/client/content/reactions.js.map +1 -0
  36. package/dist/client/content/types.d.ts +91 -0
  37. package/dist/client/content/types.js +8 -0
  38. package/dist/client/content/types.js.map +1 -0
  39. package/dist/client/crop.d.ts +32 -0
  40. package/dist/client/crop.js +103 -0
  41. package/dist/client/crop.js.map +1 -0
  42. package/dist/client/errors.d.ts +54 -0
  43. package/dist/client/errors.js +80 -0
  44. package/dist/client/errors.js.map +1 -0
  45. package/dist/client/gallery.d.ts +43 -0
  46. package/dist/client/gallery.js +86 -0
  47. package/dist/client/gallery.js.map +1 -0
  48. package/dist/client/generated/error-codes.d.ts +5 -0
  49. package/dist/client/generated/error-codes.js +131 -0
  50. package/dist/client/generated/error-codes.js.map +1 -0
  51. package/dist/client/generated/routes.d.ts +1086 -0
  52. package/dist/client/generated/routes.js +1400 -0
  53. package/dist/client/generated/routes.js.map +1 -0
  54. package/dist/client/generated/wire.d.ts +630 -0
  55. package/dist/client/generated/wire.js +6 -0
  56. package/dist/client/generated/wire.js.map +1 -0
  57. package/dist/client/http.d.ts +79 -0
  58. package/dist/client/http.js +74 -0
  59. package/dist/client/http.js.map +1 -0
  60. package/dist/client/image.d.ts +24 -0
  61. package/dist/client/image.js +65 -0
  62. package/dist/client/image.js.map +1 -0
  63. package/dist/client/index.d.ts +33 -0
  64. package/dist/client/media/api.d.ts +36 -0
  65. package/dist/client/media/api.js +61 -0
  66. package/dist/client/media/api.js.map +1 -0
  67. package/dist/client/media/client.d.ts +247 -0
  68. package/dist/client/media/client.js +407 -0
  69. package/dist/client/media/client.js.map +1 -0
  70. package/dist/client/media/encode.d.ts +3 -0
  71. package/dist/client/media/encode.js +8 -0
  72. package/dist/client/media/encode.js.map +1 -0
  73. package/dist/client/media/hash-stream.d.ts +6 -0
  74. package/dist/client/media/hash-stream.js +16 -0
  75. package/dist/client/media/hash-stream.js.map +1 -0
  76. package/dist/client/media/hash.d.ts +12 -0
  77. package/dist/client/media/hash.js +48 -0
  78. package/dist/client/media/hash.js.map +1 -0
  79. package/dist/client/media/hash.worker-inline.js +18 -0
  80. package/dist/client/media/hash.worker-inline.js.map +1 -0
  81. package/dist/client/media/pacer.d.ts +21 -0
  82. package/dist/client/media/pacer.js +25 -0
  83. package/dist/client/media/pacer.js.map +1 -0
  84. package/dist/client/media/queue.d.ts +112 -0
  85. package/dist/client/media/queue.js +274 -0
  86. package/dist/client/media/queue.js.map +1 -0
  87. package/dist/client/media/ref.d.ts +6 -0
  88. package/dist/client/media/ref.js +10 -0
  89. package/dist/client/media/ref.js.map +1 -0
  90. package/dist/client/media/rules.d.ts +52 -0
  91. package/dist/client/media/rules.js +139 -0
  92. package/dist/client/media/rules.js.map +1 -0
  93. package/dist/client/media/transport.d.ts +13 -0
  94. package/dist/client/media/transport.js +33 -0
  95. package/dist/client/media/transport.js.map +1 -0
  96. package/dist/client/media/windows.d.ts +15 -0
  97. package/dist/client/media/windows.js +39 -0
  98. package/dist/client/media/windows.js.map +1 -0
  99. package/dist/client/playback.d.ts +200 -0
  100. package/dist/client/playback.js +142 -0
  101. package/dist/client/playback.js.map +1 -0
  102. package/dist/client/player.d.ts +102 -0
  103. package/dist/client/player.js +155 -0
  104. package/dist/client/player.js.map +1 -0
  105. package/dist/client/public.d.ts +25 -0
  106. package/dist/client/public.js +18 -0
  107. package/dist/client/public.js.map +1 -0
  108. package/dist/client/rendition.d.ts +19 -0
  109. package/dist/client/rendition.js +16 -0
  110. package/dist/client/rendition.js.map +1 -0
  111. package/dist/client/route.d.ts +39 -0
  112. package/dist/client/route.js +31 -0
  113. package/dist/client/route.js.map +1 -0
  114. package/dist/client/taxonomy.d.ts +30 -0
  115. package/dist/client/taxonomy.js +77 -0
  116. package/dist/client/taxonomy.js.map +1 -0
  117. package/dist/client.js +33 -0
  118. package/dist/components/encode-progress.d.ts +15 -0
  119. package/dist/components/encode-progress.js +49 -0
  120. package/dist/components/encode-progress.js.map +1 -0
  121. package/dist/components/hover-preview.d.ts +29 -0
  122. package/dist/components/hover-preview.js +49 -0
  123. package/dist/components/hover-preview.js.map +1 -0
  124. package/dist/components/image-crop-dialog.d.ts +48 -0
  125. package/dist/components/image-crop-dialog.js +359 -0
  126. package/dist/components/image-crop-dialog.js.map +1 -0
  127. package/dist/components/lazy-mount.d.ts +10 -0
  128. package/dist/components/lazy-mount.js +22 -0
  129. package/dist/components/lazy-mount.js.map +1 -0
  130. package/dist/components/media-folder-editor.d.ts +45 -0
  131. package/dist/components/media-folder-editor.js +564 -0
  132. package/dist/components/media-folder-editor.js.map +1 -0
  133. package/dist/components/media-gallery.d.ts +54 -0
  134. package/dist/components/media-gallery.js +597 -0
  135. package/dist/components/media-gallery.js.map +1 -0
  136. package/dist/components/media-readiness-notice.d.ts +24 -0
  137. package/dist/components/media-readiness-notice.js +61 -0
  138. package/dist/components/media-readiness-notice.js.map +1 -0
  139. package/dist/components/player-controls.d.ts +71 -0
  140. package/dist/components/player-controls.js +264 -0
  141. package/dist/components/player-controls.js.map +1 -0
  142. package/dist/components/progress.d.ts +5 -0
  143. package/dist/components/progress.js +14 -0
  144. package/dist/components/progress.js.map +1 -0
  145. package/dist/components/rendition-img.d.ts +34 -0
  146. package/dist/components/rendition-img.js +78 -0
  147. package/dist/components/rendition-img.js.map +1 -0
  148. package/dist/components/slot-editor.d.ts +95 -0
  149. package/dist/components/slot-editor.js +182 -0
  150. package/dist/components/slot-editor.js.map +1 -0
  151. package/dist/components/slot-image.d.ts +23 -0
  152. package/dist/components/slot-image.js +48 -0
  153. package/dist/components/slot-image.js.map +1 -0
  154. package/dist/components/slot-upload.d.ts +41 -0
  155. package/dist/components/slot-upload.js +213 -0
  156. package/dist/components/slot-upload.js.map +1 -0
  157. package/dist/components/social/bans.d.ts +42 -0
  158. package/dist/components/social/bans.js +300 -0
  159. package/dist/components/social/bans.js.map +1 -0
  160. package/dist/components/social/comments.d.ts +42 -0
  161. package/dist/components/social/comments.js +517 -0
  162. package/dist/components/social/comments.js.map +1 -0
  163. package/dist/components/social/engagement.d.ts +45 -0
  164. package/dist/components/social/engagement.js +129 -0
  165. package/dist/components/social/engagement.js.map +1 -0
  166. package/dist/components/social/index.d.ts +6 -0
  167. package/dist/components/social/moderation.d.ts +32 -0
  168. package/dist/components/social/moderation.js +383 -0
  169. package/dist/components/social/moderation.js.map +1 -0
  170. package/dist/components/social/parts.d.ts +50 -0
  171. package/dist/components/social/parts.js +106 -0
  172. package/dist/components/social/parts.js.map +1 -0
  173. package/dist/components/social/poll-editor.d.ts +34 -0
  174. package/dist/components/social/poll-editor.js +567 -0
  175. package/dist/components/social/poll-editor.js.map +1 -0
  176. package/dist/components/social/poll.d.ts +30 -0
  177. package/dist/components/social/poll.js +252 -0
  178. package/dist/components/social/poll.js.map +1 -0
  179. package/dist/components/sortable-list.d.ts +29 -0
  180. package/dist/components/sortable-list.js +121 -0
  181. package/dist/components/sortable-list.js.map +1 -0
  182. package/dist/components/video-mini-player.d.ts +35 -0
  183. package/dist/components/video-mini-player.js +236 -0
  184. package/dist/components/video-mini-player.js.map +1 -0
  185. package/dist/components/video-player.d.ts +146 -0
  186. package/dist/components/video-player.js +630 -0
  187. package/dist/components/video-player.js.map +1 -0
  188. package/dist/components/video-poster-picker.d.ts +59 -0
  189. package/dist/components/video-poster-picker.js +335 -0
  190. package/dist/components/video-poster-picker.js.map +1 -0
  191. package/dist/components/video-poster.d.ts +24 -0
  192. package/dist/components/video-poster.js +46 -0
  193. package/dist/components/video-poster.js.map +1 -0
  194. package/dist/i18n/context.d.ts +4 -0
  195. package/dist/i18n/context.js +11 -0
  196. package/dist/i18n/context.js.map +1 -0
  197. package/dist/i18n/messages.d.ts +40 -0
  198. package/dist/i18n/messages.js +94 -0
  199. package/dist/i18n/messages.js.map +1 -0
  200. package/dist/index.d.ts +23 -0
  201. package/dist/index.js +41 -0
  202. package/dist/locales/de.d.ts +2 -0
  203. package/dist/locales/de.js +271 -0
  204. package/dist/locales/de.js.map +1 -0
  205. package/dist/locales/en.d.ts +457 -0
  206. package/dist/locales/en.js +271 -0
  207. package/dist/locales/en.js.map +1 -0
  208. package/dist/locales/es.d.ts +2 -0
  209. package/dist/locales/es.js +271 -0
  210. package/dist/locales/es.js.map +1 -0
  211. package/dist/locales/ja.d.ts +2 -0
  212. package/dist/locales/ja.js +271 -0
  213. package/dist/locales/ja.js.map +1 -0
  214. package/dist/locales/ko.d.ts +2 -0
  215. package/dist/locales/ko.js +271 -0
  216. package/dist/locales/ko.js.map +1 -0
  217. package/dist/locales/social/de.d.ts +2 -0
  218. package/dist/locales/social/de.js +198 -0
  219. package/dist/locales/social/de.js.map +1 -0
  220. package/dist/locales/social/en.d.ts +195 -0
  221. package/dist/locales/social/en.js +198 -0
  222. package/dist/locales/social/en.js.map +1 -0
  223. package/dist/locales/social/es.d.ts +2 -0
  224. package/dist/locales/social/es.js +198 -0
  225. package/dist/locales/social/es.js.map +1 -0
  226. package/dist/locales/social/ja.d.ts +2 -0
  227. package/dist/locales/social/ja.js +198 -0
  228. package/dist/locales/social/ja.js.map +1 -0
  229. package/dist/locales/social/ko.d.ts +2 -0
  230. package/dist/locales/social/ko.js +198 -0
  231. package/dist/locales/social/ko.js.map +1 -0
  232. package/dist/locales/social/zh.d.ts +2 -0
  233. package/dist/locales/social/zh.js +198 -0
  234. package/dist/locales/social/zh.js.map +1 -0
  235. package/dist/locales/zh.d.ts +2 -0
  236. package/dist/locales/zh.js +271 -0
  237. package/dist/locales/zh.js.map +1 -0
  238. package/dist/provider.d.ts +22 -0
  239. package/dist/provider.js +59 -0
  240. package/dist/provider.js.map +1 -0
  241. package/dist/react/canonical.d.ts +33 -0
  242. package/dist/react/canonical.js +88 -0
  243. package/dist/react/canonical.js.map +1 -0
  244. package/dist/react/comments.d.ts +82 -0
  245. package/dist/react/comments.js +147 -0
  246. package/dist/react/comments.js.map +1 -0
  247. package/dist/react/config.d.ts +13 -0
  248. package/dist/react/config.js +14 -0
  249. package/dist/react/config.js.map +1 -0
  250. package/dist/react/context.d.ts +48 -0
  251. package/dist/react/context.js +37 -0
  252. package/dist/react/context.js.map +1 -0
  253. package/dist/react/crop.d.ts +49 -0
  254. package/dist/react/crop.js +107 -0
  255. package/dist/react/crop.js.map +1 -0
  256. package/dist/react/engagement.d.ts +38 -0
  257. package/dist/react/engagement.js +88 -0
  258. package/dist/react/engagement.js.map +1 -0
  259. package/dist/react/folder.d.ts +83 -0
  260. package/dist/react/folder.js +183 -0
  261. package/dist/react/folder.js.map +1 -0
  262. package/dist/react/gallery.d.ts +140 -0
  263. package/dist/react/gallery.js +477 -0
  264. package/dist/react/gallery.js.map +1 -0
  265. package/dist/react/index.d.ts +21 -0
  266. package/dist/react/inline-preview.d.ts +24 -0
  267. package/dist/react/inline-preview.js +76 -0
  268. package/dist/react/inline-preview.js.map +1 -0
  269. package/dist/react/player.d.ts +70 -0
  270. package/dist/react/player.js +214 -0
  271. package/dist/react/player.js.map +1 -0
  272. package/dist/react/polls.d.ts +74 -0
  273. package/dist/react/polls.js +176 -0
  274. package/dist/react/polls.js.map +1 -0
  275. package/dist/react/posts.d.ts +59 -0
  276. package/dist/react/posts.js +149 -0
  277. package/dist/react/posts.js.map +1 -0
  278. package/dist/react/provider.d.ts +23 -0
  279. package/dist/react/provider.js +35 -0
  280. package/dist/react/provider.js.map +1 -0
  281. package/dist/react/public.d.ts +39 -0
  282. package/dist/react/public.js +70 -0
  283. package/dist/react/public.js.map +1 -0
  284. package/dist/react/read.d.ts +52 -0
  285. package/dist/react/read.js +143 -0
  286. package/dist/react/read.js.map +1 -0
  287. package/dist/react/resources.d.ts +70 -0
  288. package/dist/react/resources.js +247 -0
  289. package/dist/react/resources.js.map +1 -0
  290. package/dist/react/slot.d.ts +102 -0
  291. package/dist/react/slot.js +181 -0
  292. package/dist/react/slot.js.map +1 -0
  293. package/dist/react/store.d.ts +51 -0
  294. package/dist/react/store.js +164 -0
  295. package/dist/react/store.js.map +1 -0
  296. package/dist/react/upload.d.ts +85 -0
  297. package/dist/react/upload.js +150 -0
  298. package/dist/react/upload.js.map +1 -0
  299. package/dist/react/use-resource.d.ts +43 -0
  300. package/dist/react/use-resource.js +66 -0
  301. package/dist/react/use-resource.js.map +1 -0
  302. package/dist/react/video.d.ts +118 -0
  303. package/dist/react/video.js +184 -0
  304. package/dist/react/video.js.map +1 -0
  305. package/dist/react/viewport.d.ts +19 -0
  306. package/dist/react/viewport.js +30 -0
  307. package/dist/react/viewport.js.map +1 -0
  308. package/dist/react.js +21 -0
  309. package/dist/scope.d.ts +14 -0
  310. package/dist/scope.js +30 -0
  311. package/dist/scope.js.map +1 -0
  312. package/dist/styles.css +3 -0
  313. package/dist/ui/alert-dialog.d.ts +18 -0
  314. package/dist/ui/alert-dialog.js +86 -0
  315. package/dist/ui/alert-dialog.js.map +1 -0
  316. package/dist/ui/alert.d.ts +10 -0
  317. package/dist/ui/alert.js +38 -0
  318. package/dist/ui/alert.js.map +1 -0
  319. package/dist/ui/avatar.d.ts +11 -0
  320. package/dist/ui/avatar.js +31 -0
  321. package/dist/ui/avatar.js.map +1 -0
  322. package/dist/ui/badge.d.ts +7 -0
  323. package/dist/ui/badge.js +31 -0
  324. package/dist/ui/badge.js.map +1 -0
  325. package/dist/ui/button.d.ts +8 -0
  326. package/dist/ui/button.js +46 -0
  327. package/dist/ui/button.js.map +1 -0
  328. package/dist/ui/dialog.d.ts +17 -0
  329. package/dist/ui/dialog.js +91 -0
  330. package/dist/ui/dialog.js.map +1 -0
  331. package/dist/ui/dropdown-menu.d.ts +15 -0
  332. package/dist/ui/dropdown-menu.js +105 -0
  333. package/dist/ui/dropdown-menu.js.map +1 -0
  334. package/dist/ui/input.d.ts +3 -0
  335. package/dist/ui/input.js +17 -0
  336. package/dist/ui/input.js.map +1 -0
  337. package/dist/ui/label.d.ts +3 -0
  338. package/dist/ui/label.js +15 -0
  339. package/dist/ui/label.js.map +1 -0
  340. package/dist/ui/progress.d.ts +7 -0
  341. package/dist/ui/progress.js +31 -0
  342. package/dist/ui/progress.js.map +1 -0
  343. package/dist/ui/select.d.ts +15 -0
  344. package/dist/ui/select.js +98 -0
  345. package/dist/ui/select.js.map +1 -0
  346. package/dist/ui/skeleton.d.ts +2 -0
  347. package/dist/ui/skeleton.js +14 -0
  348. package/dist/ui/skeleton.js.map +1 -0
  349. package/dist/ui/slider.d.ts +3 -0
  350. package/dist/ui/slider.js +35 -0
  351. package/dist/ui/slider.js.map +1 -0
  352. package/dist/ui/switch.d.ts +5 -0
  353. package/dist/ui/switch.js +20 -0
  354. package/dist/ui/switch.js.map +1 -0
  355. package/dist/ui/tabs.d.ts +10 -0
  356. package/dist/ui/tabs.js +47 -0
  357. package/dist/ui/tabs.js.map +1 -0
  358. package/dist/ui/textarea.d.ts +3 -0
  359. package/dist/ui/textarea.js +15 -0
  360. package/dist/ui/textarea.js.map +1 -0
  361. package/dist/ui/toggle-group.d.ts +5 -0
  362. package/dist/ui/toggle-group.js +23 -0
  363. package/dist/ui/toggle-group.js.map +1 -0
  364. package/dist/urls/index.d.ts +100 -0
  365. package/dist/urls.js +118 -0
  366. package/dist/urls.js.map +1 -0
  367. package/package.json +118 -3
@@ -0,0 +1,150 @@
1
+ import { toContentKitError as e } from "../client/errors.js";
2
+ import { ratio as t } from "../client/aspect.js";
3
+ import { centeredCrop as n, constrainCrop as r, editOf as i, rotation as a, sameEdit as o, toOriginal as s } from "../client/crop.js";
4
+ import { encodeRemaining as c } from "../client/media/encode.js";
5
+ import { UploadQueue as l } from "../client/media/queue.js";
6
+ import { useContentKitClient as u } from "./context.js";
7
+ import { useCallback as d, useEffect as f, useMemo as p, useRef as m, useState as h, useSyncExternalStore as g } from "react";
8
+ //#region src/react/upload.ts
9
+ function _({ client: e, ...t }) {
10
+ let n = u(e), [r] = h(() => new l(n.media, {
11
+ ...t,
12
+ autoStart: !1
13
+ })), i = t.autoStart ?? !0;
14
+ return f(() => (i && r.start(), () => r.pause()), [r, i]), {
15
+ ...g(r.subscribe, r.getSnapshot, r.getSnapshot),
16
+ queue: r,
17
+ add: r.add.bind(r),
18
+ move: r.move.bind(r),
19
+ update: r.update.bind(r),
20
+ remove: r.remove.bind(r),
21
+ retry: r.retry.bind(r),
22
+ start: r.start.bind(r),
23
+ pause: r.pause.bind(r),
24
+ commit: r.commit.bind(r)
25
+ };
26
+ }
27
+ function v({ client: t } = {}) {
28
+ let { media: n } = u(t), [r, i] = h({ status: "idle" }), a = m(null);
29
+ f(() => () => a.current?.abort(), []);
30
+ let o = d(async (t, { put: r, ...o }) => {
31
+ a.current?.abort();
32
+ let s = a.current = new AbortController();
33
+ i({ status: "uploading" });
34
+ let c = {
35
+ ...o,
36
+ signal: s.signal,
37
+ onProgress: (e) => s === a.current && i({
38
+ status: "uploading",
39
+ progress: e
40
+ })
41
+ };
42
+ try {
43
+ let e;
44
+ if (r) {
45
+ let i, a = await n.put(t, {
46
+ ...c,
47
+ ...r,
48
+ onUploaded: (e) => i = e
49
+ });
50
+ e = {
51
+ ...i,
52
+ file: a
53
+ };
54
+ } else e = await n.upload(t, c);
55
+ return s === a.current && i((t) => ({
56
+ status: "done",
57
+ progress: t.progress,
58
+ result: e
59
+ })), e;
60
+ } catch (t) {
61
+ let n = e(t);
62
+ throw s === a.current && i(n.code === "aborted" ? { status: "idle" } : {
63
+ status: "error",
64
+ error: n
65
+ }), n;
66
+ }
67
+ }, [n]), s = d(() => a.current?.abort(), []);
68
+ return {
69
+ ...r,
70
+ upload: o,
71
+ cancel: s
72
+ };
73
+ }
74
+ function y({ source: e, aspect: c, initial: l }) {
75
+ let u = t(c), { width: f, height: g } = e, _ = p(() => ({
76
+ width: f,
77
+ height: g
78
+ }), [f, g]), [v, y] = h(() => {
79
+ let e = a(l?.rotate ?? 0), t = l?.crop;
80
+ return [t ? r(t, _, u, e) : n(_, u, e), e];
81
+ }), [b, x] = v, S = d((e) => y((t) => {
82
+ let n = typeof e == "function" ? e(t) : e;
83
+ return o({
84
+ crop: n[0],
85
+ rotate: n[1]
86
+ }, {
87
+ crop: t[0],
88
+ rotate: t[1]
89
+ }) ? t : n;
90
+ }), []), C = d((e) => S(([, t]) => [r(e, _, u, t), t]), [
91
+ S,
92
+ _,
93
+ u
94
+ ]), w = d((e, t) => S(([, n]) => [r(s(e, t, _, n), _, u, n), n]), [
95
+ S,
96
+ _,
97
+ u
98
+ ]), T = d((e) => S(([t, n]) => {
99
+ let i = a(n + e);
100
+ return [r(t, _, u, i), i];
101
+ }), [
102
+ S,
103
+ _,
104
+ u
105
+ ]), E = d(() => S([n(_, u), 0]), [
106
+ S,
107
+ _,
108
+ u
109
+ ]), D = m(null);
110
+ return {
111
+ crop: b,
112
+ rotate: x,
113
+ edit: p(() => {
114
+ let e = i(b, _, x);
115
+ return o(e, D.current) ? D.current : D.current = e;
116
+ }, [
117
+ b,
118
+ _,
119
+ x
120
+ ]),
121
+ setCrop: C,
122
+ setFromDisplay: w,
123
+ rotateBy: T,
124
+ reset: E
125
+ };
126
+ }
127
+ function b(e) {
128
+ let [t, n] = h(() => ({
129
+ at: e?.at,
130
+ received: Date.now()
131
+ }));
132
+ e?.at !== t.at && n({
133
+ at: e?.at,
134
+ received: Date.now()
135
+ });
136
+ let [r, i] = h(() => Date.now()), a = !!e?.eta && !e.stalled;
137
+ return f(() => {
138
+ if (!a) return;
139
+ i(Date.now());
140
+ let e = setInterval(() => i(Date.now()), 1e3);
141
+ return () => clearInterval(e);
142
+ }, [a, t]), {
143
+ progress: e ?? void 0,
144
+ remaining: c(e, t.received, Math.max(r, t.received))
145
+ };
146
+ }
147
+ //#endregion
148
+ export { y as useCrop, b as useEncodeProgress, v as useUpload, _ as useUploadQueue };
149
+
150
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","names":[],"sources":["../../src/react/upload.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { centeredCrop, constrainCrop, editOf, rotation, sameEdit, toOriginal, type Rotation, type Size } from \"../client/crop.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Crop, Edit, EncodeProgress, FileInfo } from \"../client/generated/wire.js\";\nimport type { Progress, PutOptions, UploadedFile, UploadOptions } from \"../client/media/client.js\";\nimport { encodeRemaining } from \"../client/media/encode.js\";\nimport { UploadQueue, type QueueOptions, type QueueSnapshot } from \"../client/media/queue.js\";\nimport { useContentKitClient } from \"./context.js\";\n\nexport interface UseUploadQueue extends QueueSnapshot {\n queue: UploadQueue;\n add: UploadQueue[\"add\"];\n move: UploadQueue[\"move\"];\n update: UploadQueue[\"update\"];\n remove: UploadQueue[\"remove\"];\n retry: UploadQueue[\"retry\"];\n start: UploadQueue[\"start\"];\n pause: UploadQueue[\"pause\"];\n commit: UploadQueue[\"commit\"];\n}\n\nexport interface UploadQueueOptions extends QueueOptions {\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\n/**\n * A file queue for one item (options are read once; key the component by\n * ref to switch items). Unmounting pauses running uploads.\n */\nexport function useUploadQueue({ client: own, ...options }: UploadQueueOptions): UseUploadQueue {\n const client = useContentKitClient(own);\n const [queue] = useState(() => new UploadQueue(client.media, { ...options, autoStart: false }));\n const autoStart = options.autoStart ?? true;\n useEffect(() => {\n if (autoStart) queue.start();\n return () => queue.pause();\n }, [queue, autoStart]);\n const snap = useSyncExternalStore(queue.subscribe, queue.getSnapshot, queue.getSnapshot);\n return {\n ...snap,\n queue,\n add: queue.add.bind(queue),\n move: queue.move.bind(queue),\n update: queue.update.bind(queue),\n remove: queue.remove.bind(queue),\n retry: queue.retry.bind(queue),\n start: queue.start.bind(queue),\n pause: queue.pause.bind(queue),\n commit: queue.commit.bind(queue),\n };\n}\n\nexport interface UploadStatus {\n status: \"idle\" | \"uploading\" | \"done\" | \"error\";\n progress?: Progress;\n error?: ContentKitError;\n result?: UploadedFile & { file?: FileInfo };\n}\n\nexport interface UseUpload extends UploadStatus {\n /**\n * Uploads one file; with put it also commits it to its path (put's edit,\n * meta and index) and waits until it is processed (result.file).\n */\n upload: (file: File, opts: Omit<UploadOptions, \"signal\" | \"onProgress\"> & { put?: PutOptions }) => Promise<UploadedFile & { file?: FileInfo }>;\n cancel: () => void;\n}\n\n/** One upload at a time (a cover, an inline image); a new upload cancels the previous. */\nexport function useUpload({ client: own }: { client?: ContentKitClient } = {}): UseUpload {\n const { media } = useContentKitClient(own);\n const [s, set] = useState<UploadStatus>({ status: \"idle\" });\n const ctl = useRef<AbortController | null>(null);\n useEffect(() => () => ctl.current?.abort(), []);\n\n const upload = useCallback<UseUpload[\"upload\"]>(\n async (file, { put, ...opts }) => {\n ctl.current?.abort();\n const c = (ctl.current = new AbortController());\n set({ status: \"uploading\" });\n const o: UploadOptions = {\n ...opts,\n signal: c.signal,\n onProgress: (progress) => c === ctl.current && set({ status: \"uploading\", progress }),\n };\n try {\n let result: UploadedFile & { file?: FileInfo };\n if (put) {\n let up: UploadedFile | undefined;\n const f = await media.put(file, { ...o, ...put, onUploaded: (u) => (up = u) });\n result = { ...up!, file: f };\n } else result = await media.upload(file, o);\n if (c === ctl.current) set((prev) => ({ status: \"done\", progress: prev.progress, result }));\n return result;\n } catch (e) {\n const error = toContentKitError(e);\n if (c === ctl.current) set(error.code === \"aborted\" ? { status: \"idle\" } : { status: \"error\", error });\n throw error;\n }\n },\n [media],\n );\n const cancel = useCallback(() => ctl.current?.abort(), []);\n return { ...s, upload, cancel };\n}\n\nexport interface UseCropOptions {\n /** The upload's size (an editor read's w and h). */\n source: Size;\n /** The edited image's \"W:H\", e.g. the public preset's aspect; \"\" or omitted is free. */\n aspect?: AspectRatio;\n /** The current edit to start from. */\n initial?: Edit | null;\n}\n\nexport interface UseCrop {\n /** In original pixels, inside the source and at the aspect. */\n crop: Crop;\n rotate: Rotation;\n /** Ready for an edit or put op; null when nothing changes. Its identity changes only with its value. */\n edit: Edit | null;\n /** A rect in original pixels. */\n setCrop: (rect: Crop) => void;\n /** A rect on the image as displayed (after the rotation) at display size: a cropper's output. */\n setFromDisplay: (rect: Crop, display: Size) => void;\n /** Turns clockwise by deg (a multiple of 90); the crop keeps the aspect. */\n rotateBy: (deg: number) => void;\n reset: () => void;\n}\n\n/** Headless crop state for any cropper UI: a rect in original pixels, a rotation and the resulting edit. */\nexport function useCrop({ source: given, aspect: shape, initial }: UseCropOptions): UseCrop {\n const aspect = ratio(shape);\n const { width, height } = given;\n const source = useMemo(() => ({ width, height }), [width, height]);\n const start = (): [Crop, Rotation] => {\n const rotate = rotation(initial?.rotate ?? 0);\n const c = initial?.crop;\n return [c ? constrainCrop(c, source, aspect, rotate) : centeredCrop(source, aspect, rotate), rotate];\n };\n const [state, setState] = useState(start);\n const [crop, rotate] = state;\n const set = useCallback(\n (next: [Crop, Rotation] | ((s: [Crop, Rotation]) => [Crop, Rotation])) =>\n setState((s) => {\n const n = typeof next === \"function\" ? next(s) : next;\n return sameEdit({ crop: n[0], rotate: n[1] }, { crop: s[0], rotate: s[1] }) ? s : n;\n }),\n [],\n );\n const setCrop = useCallback((r: Crop) => set(([, rot]) => [constrainCrop(r, source, aspect, rot), rot]), [set, source, aspect]);\n const setFromDisplay = useCallback(\n (r: Crop, display: Size) => set(([, rot]) => [constrainCrop(toOriginal(r, display, source, rot), source, aspect, rot), rot]),\n [set, source, aspect],\n );\n const rotateBy = useCallback(\n (deg: number) =>\n set(([c, rot]) => {\n const next = rotation(rot + deg);\n return [constrainCrop(c, source, aspect, next), next];\n }),\n [set, source, aspect],\n );\n const reset = useCallback(() => set([centeredCrop(source, aspect), 0]), [set, source, aspect]);\n const last = useRef<Edit | null>(null);\n const edit = useMemo(() => {\n const e = editOf(crop, source, rotate);\n return sameEdit(e, last.current) ? last.current : (last.current = e);\n }, [crop, source, rotate]);\n return { crop, rotate, edit, setCrop, setFromDisplay, rotateBy, reset };\n}\n\nexport interface UseEncodeProgress {\n progress?: EncodeProgress;\n /** Seconds left, counting down each second between reports; undefined while unknown. */\n remaining?: number;\n}\n\n/**\n * A pending video file's encode progress from the read API (`files[i].progress`),\n * with the ETA counted down locally between polls.\n */\nexport function useEncodeProgress(progress?: EncodeProgress | null): UseEncodeProgress {\n const [base, setBase] = useState(() => ({ at: progress?.at, received: Date.now() }));\n if (progress?.at !== base.at) setBase({ at: progress?.at, received: Date.now() });\n const [now, setNow] = useState(() => Date.now());\n const ticking = !!progress?.eta && !progress.stalled;\n useEffect(() => {\n if (!ticking) return;\n setNow(Date.now());\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, [ticking, base]);\n return { progress: progress ?? undefined, remaining: encodeRemaining(progress, base.received, Math.max(now, base.received)) };\n}\n"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { ContentKitClient } from '../client/client.js';
2
+ import { ContentKitError } from '../client/errors.js';
3
+ import { Fetcher, Page, Resource, ResourceStore, Tag } from './resources.js';
4
+ /** The client, its shared store, and the provider's viewer and sign-in. */
5
+ export declare function useContentScope(own?: ContentKitClient | null): {
6
+ client: ContentKitClient;
7
+ store: ResourceStore;
8
+ viewer: string | null | undefined;
9
+ onSignIn: (() => void) | undefined;
10
+ };
11
+ /** A shared read: fetched once per key while shown; initial is shown (and seeds the store) until it loads. */
12
+ export declare function useResource<T>(store: ResourceStore, key: string | null, tag: Tag, fetcher: Fetcher<T>, o?: {
13
+ initial?: T;
14
+ more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;
15
+ }): Resource<T> & {
16
+ reload: () => void;
17
+ };
18
+ /** A shared list, a page at a time. */
19
+ export interface UseList<T> {
20
+ items: T[];
21
+ /** The first page is loading (also on reload). */
22
+ loading: boolean;
23
+ loadingMore: boolean;
24
+ error?: ContentKitError;
25
+ hasMore: boolean;
26
+ loadMore: () => Promise<void>;
27
+ reload: () => void;
28
+ }
29
+ export type PageFetcher<T> = (cursor: string | number | undefined, signal: AbortSignal) => Promise<Page<T>>;
30
+ export declare function useList<T extends {
31
+ id: string;
32
+ }>(store: ResourceStore, key: string | null, tag: Tag, page: PageFetcher<T>): UseList<T>;
33
+ /**
34
+ * A limit/offset list as pages. A full page means more may follow; with
35
+ * underfill (the latest feed drops items the caller may not see) only an
36
+ * empty page ends it.
37
+ */
38
+ export declare function offsetPages<T>(limit: number, list: (q: {
39
+ limit: number;
40
+ offset: number;
41
+ }, signal: AbortSignal) => Promise<T[]>, underfill?: boolean): PageFetcher<T>;
42
+ /** A stable store key. */
43
+ export declare const keyOf: (...parts: unknown[]) => string;
@@ -0,0 +1,66 @@
1
+ import { ContentKitContext as e, useContentKitClient as t } from "./context.js";
2
+ import { resourcesFor as n } from "./resources.js";
3
+ import { useCallback as r, useContext as i, useEffect as a, useRef as o, useSyncExternalStore as s } from "react";
4
+ //#region src/react/use-resource.ts
5
+ function c(r) {
6
+ let a = i(e), o = t(r);
7
+ return {
8
+ client: o,
9
+ store: n(o),
10
+ viewer: a?.viewer,
11
+ onSignIn: a?.onSignIn
12
+ };
13
+ }
14
+ var l = {
15
+ data: void 0,
16
+ loading: !1,
17
+ loaded: !1
18
+ }, u = () => () => {};
19
+ function d(e, t, n, i, c = {}) {
20
+ let d = o(i), f = o(c.more), p = o(c.initial);
21
+ a(() => {
22
+ d.current = i, f.current = c.more;
23
+ });
24
+ let m = r((r) => t ? e.subscribe(t, n, r) : u(), [e, t]), h = r(() => t ? e.snapshot(t) : l, [e, t]), g = s(m, h, h);
25
+ a(() => {
26
+ t && (e.bind(t, (e) => d.current(e), f.current && ((e, t) => f.current(e, t))), p.current !== void 0 && !e.snapshot(t).loaded && !e.snapshot(t).loading ? e.set(t, p.current) : e.load(t));
27
+ }, [e, t]);
28
+ let _ = r(() => t && e.load(t, !0), [e, t]);
29
+ return !g.loaded && c.initial !== void 0 ? {
30
+ ...g,
31
+ data: c.initial,
32
+ reload: _
33
+ } : {
34
+ ...g,
35
+ reload: _
36
+ };
37
+ }
38
+ function f(e, t, n, i) {
39
+ let a = d(e, t, n, (e) => i(void 0, e), { more: (e, t) => i(e, t) }), o = r(() => t ? e.loadMore(t) : Promise.resolve(), [e, t]);
40
+ return {
41
+ items: a.data?.items ?? [],
42
+ loading: a.loading || !!t && !a.loaded,
43
+ loadingMore: !!a.loadingMore,
44
+ error: a.error,
45
+ hasMore: a.data ? a.data.next !== null : !1,
46
+ loadMore: o,
47
+ reload: a.reload
48
+ };
49
+ }
50
+ function p(e, t, n = !1) {
51
+ return async (r, i) => {
52
+ let a = typeof r == "number" ? r : 0, o = await t({
53
+ limit: e,
54
+ offset: a
55
+ }, i);
56
+ return {
57
+ items: o,
58
+ next: (n ? o.length === 0 : o.length < e) ? null : a + e
59
+ };
60
+ };
61
+ }
62
+ var m = (...e) => JSON.stringify(e);
63
+ //#endregion
64
+ export { m as keyOf, p as offsetPages, c as useContentScope, f as useList, d as useResource };
65
+
66
+ //# sourceMappingURL=use-resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-resource.js","names":[],"sources":["../../src/react/use-resource.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport { ContentKitContext, useContentKitClient } from \"./context.js\";\nimport { resourcesFor, type Fetcher, type Page, type Resource, type ResourceStore, type Tag } from \"./resources.js\";\n\n/** The client, its shared store, and the provider's viewer and sign-in. */\nexport function useContentScope(own?: ContentKitClient | null) {\n const ctx = useContext(ContentKitContext);\n const client = useContentKitClient(own);\n return { client, store: resourcesFor(client), viewer: ctx?.viewer, onSignIn: ctx?.onSignIn };\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\nconst none = () => () => {};\n\n/** A shared read: fetched once per key while shown; initial is shown (and seeds the store) until it loads. */\nexport function useResource<T>(\n store: ResourceStore,\n key: string | null,\n tag: Tag,\n fetcher: Fetcher<T>,\n o: { initial?: T; more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>> } = {},\n): Resource<T> & { reload: () => void } {\n const fetch = useRef(fetcher);\n const more = useRef(o.more);\n const initial = useRef(o.initial);\n useEffect(() => {\n fetch.current = fetcher;\n more.current = o.more;\n });\n const subscribe = useCallback(\n (l: () => void) => (key ? store.subscribe(key, tag, l) : none()),\n // The tag is part of the key.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [store, key],\n );\n const snapshot = useCallback(() => (key ? store.snapshot<T>(key) : IDLE), [store, key]);\n const entry = useSyncExternalStore(subscribe, snapshot, snapshot);\n useEffect(() => {\n if (!key) return;\n store.bind(key, (s) => fetch.current(s), more.current && ((c, s) => more.current!(c, s)));\n if (initial.current !== undefined && !store.snapshot(key).loaded && !store.snapshot(key).loading) store.set(key, initial.current);\n else store.load(key);\n }, [store, key]);\n const reload = useCallback(() => key && store.load(key, true), [store, key]);\n if (!entry.loaded && o.initial !== undefined) return { ...entry, data: o.initial, reload };\n return { ...entry, reload };\n}\n\n/** A shared list, a page at a time. */\nexport interface UseList<T> {\n items: T[];\n /** The first page is loading (also on reload). */\n loading: boolean;\n loadingMore: boolean;\n error?: ContentKitError;\n hasMore: boolean;\n loadMore: () => Promise<void>;\n reload: () => void;\n}\n\nexport type PageFetcher<T> = (cursor: string | number | undefined, signal: AbortSignal) => Promise<Page<T>>;\n\nexport function useList<T extends { id: string }>(store: ResourceStore, key: string | null, tag: Tag, page: PageFetcher<T>): UseList<T> {\n const r = useResource<Page<T>>(store, key, tag, (s) => page(undefined, s), { more: (c, s) => page(c, s) });\n const loadMore = useCallback(() => (key ? store.loadMore(key) : Promise.resolve()), [store, key]);\n return {\n items: r.data?.items ?? [],\n loading: r.loading || (!!key && !r.loaded),\n loadingMore: !!r.loadingMore,\n error: r.error,\n hasMore: r.data ? r.data.next !== null : false,\n loadMore,\n reload: r.reload,\n };\n}\n\n/**\n * A limit/offset list as pages. A full page means more may follow; with\n * underfill (the latest feed drops items the caller may not see) only an\n * empty page ends it.\n */\nexport function offsetPages<T>(limit: number, list: (q: { limit: number; offset: number }, signal: AbortSignal) => Promise<T[]>, underfill = false): PageFetcher<T> {\n return async (cursor, signal) => {\n const offset = typeof cursor === \"number\" ? cursor : 0;\n const items = await list({ limit, offset }, signal);\n const end = underfill ? items.length === 0 : items.length < limit;\n return { items, next: end ? null : offset + limit };\n };\n}\n\n/** A stable store key. */\nexport const keyOf = (...parts: unknown[]) => JSON.stringify(parts);\n"],"mappings":";;;;AAOA,SAAgB,EAAgB,GAA+B;CAC7D,IAAM,IAAM,EAAW,CAAiB,GAClC,IAAS,EAAoB,CAAG;CACtC,OAAO;EAAE;EAAQ,OAAO,EAAa,CAAM;EAAG,QAAQ,GAAK;EAAQ,UAAU,GAAK;CAAS;AAC7F;AAEA,IAAM,IAAwB;CAAE,MAAM,KAAA;CAAW,SAAS;CAAO,QAAQ;AAAM,GACzE,gBAAmB,CAAC;AAG1B,SAAgB,EACd,GACA,GACA,GACA,GACA,IAAsG,CAAC,GACjE;CACtC,IAAM,IAAQ,EAAO,CAAO,GACtB,IAAO,EAAO,EAAE,IAAI,GACpB,IAAU,EAAO,EAAE,OAAO;CAChC,QAAgB;EAEd,AADA,EAAM,UAAU,GAChB,EAAK,UAAU,EAAE;CACnB,CAAC;CACD,IAAM,IAAY,GACf,MAAmB,IAAM,EAAM,UAAU,GAAK,GAAK,CAAC,IAAI,EAAK,GAG9D,CAAC,GAAO,CAAG,CACb,GACM,IAAW,QAAmB,IAAM,EAAM,SAAY,CAAG,IAAI,GAAO,CAAC,GAAO,CAAG,CAAC,GAChF,IAAQ,EAAqB,GAAW,GAAU,CAAQ;CAChE,QAAgB;EACT,MACL,EAAM,KAAK,IAAM,MAAM,EAAM,QAAQ,CAAC,GAAG,EAAK,aAAa,GAAG,MAAM,EAAK,QAAS,GAAG,CAAC,EAAE,GACpF,EAAQ,YAAY,KAAA,KAAa,CAAC,EAAM,SAAS,CAAG,CAAC,CAAC,UAAU,CAAC,EAAM,SAAS,CAAG,CAAC,CAAC,UAAS,EAAM,IAAI,GAAK,EAAQ,OAAO,IAC3H,EAAM,KAAK,CAAG;CACrB,GAAG,CAAC,GAAO,CAAG,CAAC;CACf,IAAM,IAAS,QAAkB,KAAO,EAAM,KAAK,GAAK,EAAI,GAAG,CAAC,GAAO,CAAG,CAAC;CAE3E,OADI,CAAC,EAAM,UAAU,EAAE,YAAY,KAAA,IAAkB;EAAE,GAAG;EAAO,MAAM,EAAE;EAAS;CAAO,IAClF;EAAE,GAAG;EAAO;CAAO;AAC5B;AAgBA,SAAgB,EAAkC,GAAsB,GAAoB,GAAU,GAAkC;CACtI,IAAM,IAAI,EAAqB,GAAO,GAAK,IAAM,MAAM,EAAK,KAAA,GAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAK,GAAG,CAAC,EAAE,CAAC,GACnG,IAAW,QAAmB,IAAM,EAAM,SAAS,CAAG,IAAI,QAAQ,QAAQ,GAAI,CAAC,GAAO,CAAG,CAAC;CAChG,OAAO;EACL,OAAO,EAAE,MAAM,SAAS,CAAC;EACzB,SAAS,EAAE,WAAY,CAAC,CAAC,KAAO,CAAC,EAAE;EACnC,aAAa,CAAC,CAAC,EAAE;EACjB,OAAO,EAAE;EACT,SAAS,EAAE,OAAO,EAAE,KAAK,SAAS,OAAO;EACzC;EACA,QAAQ,EAAE;CACZ;AACF;AAOA,SAAgB,EAAe,GAAe,GAAmF,IAAY,IAAuB;CAClK,OAAO,OAAO,GAAQ,MAAW;EAC/B,IAAM,IAAS,OAAO,KAAW,WAAW,IAAS,GAC/C,IAAQ,MAAM,EAAK;GAAE;GAAO;EAAO,GAAG,CAAM;EAElD,OAAO;GAAE;GAAO,OADJ,IAAY,EAAM,WAAW,IAAI,EAAM,SAAS,KAChC,OAAO,IAAS;EAAM;CACpD;AACF;AAGA,IAAa,KAAS,GAAG,MAAqB,KAAK,UAAU,CAAK"}
@@ -0,0 +1,118 @@
1
+ import { ContentKitClient } from '../client/client.js';
2
+ import { ContentKitError } from '../client/errors.js';
3
+ import { Edit, FileInfo, ReadResult, RefBody } from '../client/generated/wire.js';
4
+ import { Progress } from '../client/media/client.js';
5
+ export interface VideoImagesOptions {
6
+ ref: RefBody;
7
+ /** The video upload path. Default "source". */
8
+ video?: string;
9
+ /** The poster upload path, grabbed from the video's frames. Default "poster". */
10
+ poster?: string;
11
+ /** An editor read of the item the host already has; skips the fetch. */
12
+ read?: ReadResult | null;
13
+ /** Overrides the provider's client; without one only a supplied read shows. */
14
+ client?: ContentKitClient | null;
15
+ }
16
+ export interface UseVideoImages {
17
+ /** The video upload (its duration and size once probed); null without one. */
18
+ video: FileInfo | null;
19
+ /** The poster upload: a frame ({t} or {auto}) or an uploaded image; null without one. */
20
+ poster: FileInfo | null;
21
+ loading: boolean;
22
+ error?: ContentKitError;
23
+ reload: () => void;
24
+ /** Replaces the poster after a save without refetching. */
25
+ set: (poster: FileInfo) => void;
26
+ }
27
+ /** A video item's video and poster uploads (an editor read). */
28
+ export declare function useVideoImages(o: VideoImagesOptions): UseVideoImages;
29
+ export interface FrameStripOptions {
30
+ ref: RefBody;
31
+ /** The video upload's path (a read's, with its extension). */
32
+ path?: string;
33
+ /** Seconds; nothing is fetched until it is known. */
34
+ duration?: number;
35
+ /** Frames across the video. Default 8. */
36
+ count?: number;
37
+ /** Pixels. Default 160. */
38
+ width?: number;
39
+ /** The first failed grab (the strip keeps going without it). */
40
+ onError?: (e: ContentKitError) => void;
41
+ /** Overrides the provider's client. */
42
+ client?: ContentKitClient;
43
+ }
44
+ export interface StripFrame {
45
+ time: number;
46
+ /** An object URL once fetched. */
47
+ url?: string;
48
+ }
49
+ /**
50
+ * Evenly spaced frames for coarse browsing, fetched one at a time (the frame
51
+ * endpoint allows two grabs at once per host), revoked on change or unmount.
52
+ */
53
+ export declare function useFrameStrip(o: FrameStripOptions): StripFrame[];
54
+ export interface VideoFrameOptions {
55
+ ref: RefBody;
56
+ /** The video upload's path; nothing is fetched without it. */
57
+ path?: string;
58
+ /** Seconds; undefined fetches nothing. */
59
+ time?: number;
60
+ /** Pixels. Default 640. */
61
+ width?: number;
62
+ /** Debounce while scrubbing, ms. Default 120. */
63
+ delay?: number;
64
+ onError?: (e: ContentKitError) => void;
65
+ /** Overrides the provider's client. */
66
+ client?: ContentKitClient;
67
+ }
68
+ export interface UseVideoFrame {
69
+ /** The latest fetched frame; kept while the next one loads. */
70
+ url?: string;
71
+ /** The time url shows. */
72
+ time?: number;
73
+ loading: boolean;
74
+ error?: ContentKitError;
75
+ }
76
+ /** The exact frame at time, debounced while it changes. */
77
+ export declare function useVideoFrame(o: VideoFrameOptions): UseVideoFrame;
78
+ export type VideoSaveState = {
79
+ status: "idle";
80
+ } | {
81
+ status: "saving";
82
+ progress?: Progress;
83
+ rendering?: boolean;
84
+ } | {
85
+ status: "error";
86
+ error: ContentKitError;
87
+ };
88
+ export interface VideoPosterOptions {
89
+ ref: RefBody;
90
+ /** The poster upload path. Default "poster". */
91
+ path?: string;
92
+ /** With the processed poster upload after each save. */
93
+ onSaved?: (poster: FileInfo) => void;
94
+ /** Polling limit for the render, ms. Default 120000. */
95
+ timeout?: number;
96
+ /** Every failed save: the request, the render or the wait. */
97
+ onError?: (e: ContentKitError) => void;
98
+ /** Overrides the provider's client. */
99
+ client?: ContentKitClient;
100
+ }
101
+ export interface UseVideoPoster {
102
+ state: VideoSaveState;
103
+ /** A frame at time (seconds), cropped by edit in the frame's pixels (the video's w×h). */
104
+ saveFrame: (time: number, edit?: Edit | null) => Promise<FileInfo | undefined>;
105
+ /** An uploaded image, cropped by edit in its own pixels. */
106
+ saveUpload: (image: Blob, edit?: Edit | null) => Promise<FileInfo | undefined>;
107
+ /** The worker's choice of frame. */
108
+ saveAuto: () => Promise<FileInfo | undefined>;
109
+ /**
110
+ * A frame of another item's video upload (at path, time seconds) uploaded
111
+ * as the poster image, cropped by edit in the frame's pixels.
112
+ */
113
+ saveFrameFrom: (video: RefBody, path: string, time: number, edit?: Edit | null) => Promise<FileInfo | undefined>;
114
+ reset: () => void;
115
+ }
116
+ /** Headless poster selection: a frame op or an uploaded image, then the wait for the worker to render it. */
117
+ export declare function useVideoPoster(o: VideoPosterOptions): UseVideoPoster;
118
+ export declare const round3: (v: number) => number;
@@ -0,0 +1,184 @@
1
+ import { toContentKitError as e } from "../client/errors.js";
2
+ import { samePath as t } from "../client/media/client.js";
3
+ import { useContentKitClient as n } from "./context.js";
4
+ import { withUpload as r } from "./store.js";
5
+ import { useMediaRead as i } from "./read.js";
6
+ import { useCallback as a, useEffect as o, useRef as s, useState as c } from "react";
7
+ //#region src/react/video.ts
8
+ var l = e, u = (e) => `${e.kind}/${e.id}`;
9
+ function d(e) {
10
+ let n = i(e.ref, {
11
+ editor: !0,
12
+ read: e.read,
13
+ client: e.client
14
+ }), o = (e) => n.read?.files.find((n) => n.upload && t(n.path, e)) ?? null, s = e.poster ?? "poster", { read: c, set: l } = n, u = a((e) => l(r(c, s, e)), [
15
+ c,
16
+ s,
17
+ l
18
+ ]);
19
+ return {
20
+ video: o(e.video ?? "source"),
21
+ poster: o(s),
22
+ loading: n.loading,
23
+ error: n.error,
24
+ reload: n.reload,
25
+ set: u
26
+ };
27
+ }
28
+ function f(e) {
29
+ let { media: t } = n(e.client), r = e.count ?? 8, i = e.width ?? 160, a = e.duration ?? 0, d = `${u(e.ref)}#${e.path ?? ""}#${a}#${r}#${i}`, [f, p] = c({
30
+ key: "",
31
+ frames: []
32
+ }), m = s(e.ref);
33
+ m.current = e.ref;
34
+ let g = s(e.onError);
35
+ return g.current = e.onError, o(() => {
36
+ if (!(a > 0) || !e.path) return;
37
+ let n = e.path, o = new AbortController(), s = !1, c = Array.from({ length: r }, (e, t) => h((t + .5) / r * a)), u = [];
38
+ return p({
39
+ key: d,
40
+ frames: c.map((e) => ({ time: e }))
41
+ }), (async () => {
42
+ for (let [e, r] of c.entries()) try {
43
+ let a = await t.getFrame(m.current, n, r, i, o.signal);
44
+ if (o.signal.aborted) return;
45
+ let s = URL.createObjectURL(a);
46
+ u.push(s), p((t) => t.key === d ? {
47
+ key: d,
48
+ frames: t.frames.map((t, n) => n === e ? {
49
+ ...t,
50
+ url: s
51
+ } : t)
52
+ } : t);
53
+ } catch (e) {
54
+ if (o.signal.aborted) return;
55
+ s || g.current?.(l(e)), s = !0;
56
+ }
57
+ })(), () => {
58
+ o.abort(), u.forEach((e) => URL.revokeObjectURL(e));
59
+ };
60
+ }, [
61
+ t,
62
+ d,
63
+ a,
64
+ r,
65
+ i,
66
+ e.path
67
+ ]), f.key === d ? f.frames : [];
68
+ }
69
+ function p(e) {
70
+ let { media: t } = n(e.client), r = e.width ?? 640, i = e.delay ?? 120, [a, d] = c({ loading: !1 }), f = s(e.ref);
71
+ f.current = e.ref;
72
+ let p = s(void 0), m = s(e.onError);
73
+ m.current = e.onError;
74
+ let h = u(e.ref);
75
+ return o(() => {
76
+ if (e.time === void 0 || !e.path) return;
77
+ let n = e.time, a = e.path, o = new AbortController();
78
+ d((e) => ({
79
+ ...e,
80
+ loading: !0,
81
+ error: void 0
82
+ }));
83
+ let s = setTimeout(() => {
84
+ t.getFrame(f.current, a, n, r, o.signal).then((e) => {
85
+ if (o.signal.aborted) return;
86
+ p.current && URL.revokeObjectURL(p.current);
87
+ let t = p.current = URL.createObjectURL(e);
88
+ d({
89
+ url: t,
90
+ time: n,
91
+ loading: !1
92
+ });
93
+ }, (e) => {
94
+ o.signal.aborted || (d((t) => ({
95
+ ...t,
96
+ loading: !1,
97
+ error: l(e)
98
+ })), m.current?.(l(e)));
99
+ });
100
+ }, i);
101
+ return () => {
102
+ clearTimeout(s), o.abort();
103
+ };
104
+ }, [
105
+ t,
106
+ h,
107
+ e.path,
108
+ e.time,
109
+ r,
110
+ i
111
+ ]), o(() => () => {
112
+ p.current && URL.revokeObjectURL(p.current);
113
+ }, []), a;
114
+ }
115
+ function m(e) {
116
+ let { media: t } = n(e.client), [r, i] = c({ status: "idle" }), o = s(e);
117
+ o.current = e;
118
+ let u = a(async (e) => {
119
+ i({ status: "saving" });
120
+ try {
121
+ let t = await e((e) => i({
122
+ status: "saving",
123
+ progress: e,
124
+ rendering: e.phase === "processing"
125
+ }));
126
+ return i({ status: "idle" }), o.current.onSaved?.(t), t;
127
+ } catch (e) {
128
+ let t = l(e);
129
+ i({
130
+ status: "error",
131
+ error: t
132
+ }), o.current.onError?.(t);
133
+ return;
134
+ }
135
+ }, []), d = a((e) => u(async (n) => {
136
+ let { ref: r, path: i = "poster", timeout: a } = o.current;
137
+ return await t.commit(r, [{
138
+ op: "frame",
139
+ path: i,
140
+ ...e
141
+ }]), n({
142
+ phase: "processing",
143
+ loaded: 0,
144
+ total: 0
145
+ }), t.waitFor(r, i, { timeout: a });
146
+ }), [t, u]);
147
+ return {
148
+ state: r,
149
+ saveFrame: a((e, t) => d({
150
+ t: h(e),
151
+ ...t ? { edit: t } : {}
152
+ }), [d]),
153
+ saveUpload: a((e, n) => {
154
+ let { ref: r, path: i = "poster", timeout: a } = o.current;
155
+ return u((o) => t.put(e, {
156
+ ref: r,
157
+ path: i,
158
+ edit: n,
159
+ timeout: a,
160
+ onProgress: o
161
+ }));
162
+ }, [t, u]),
163
+ saveAuto: a(() => d({ auto: !0 }), [d]),
164
+ saveFrameFrom: a((e, n, r, i) => {
165
+ let { ref: a, path: s = "poster", timeout: c } = o.current;
166
+ return u(async (o) => {
167
+ let l = await t.getFrame(e, n, h(r)), u = new File([l], "frame.jpg", { type: "image/jpeg" });
168
+ return t.put(u, {
169
+ ref: a,
170
+ path: s,
171
+ edit: i,
172
+ timeout: c,
173
+ onProgress: o
174
+ });
175
+ });
176
+ }, [t, u]),
177
+ reset: a(() => i({ status: "idle" }), [])
178
+ };
179
+ }
180
+ var h = (e) => Math.round(e * 1e3) / 1e3;
181
+ //#endregion
182
+ export { h as round3, f as useFrameStrip, p as useVideoFrame, d as useVideoImages, m as useVideoPoster };
183
+
184
+ //# sourceMappingURL=video.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video.js","names":[],"sources":["../../src/react/video.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, Op, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { samePath, type Progress } from \"../client/media/client.js\";\nimport { useContentKitClient } from \"./context.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { withUpload } from \"./store.js\";\n\nconst asError = toContentKitError;\nconst refKey = (ref: RefBody) => `${ref.kind}/${ref.id}`;\n\nexport interface VideoImagesOptions {\n ref: RefBody;\n /** The video upload path. Default \"source\". */\n video?: string;\n /** The poster upload path, grabbed from the video's frames. Default \"poster\". */\n poster?: string;\n /** An editor read of the item the host already has; skips the fetch. */\n read?: ReadResult | null;\n /** Overrides the provider's client; without one only a supplied read shows. */\n client?: ContentKitClient | null;\n}\n\nexport interface UseVideoImages {\n /** The video upload (its duration and size once probed); null without one. */\n video: FileInfo | null;\n /** The poster upload: a frame ({t} or {auto}) or an uploaded image; null without one. */\n poster: FileInfo | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Replaces the poster after a save without refetching. */\n set: (poster: FileInfo) => void;\n}\n\n/** A video item's video and poster uploads (an editor read). */\nexport function useVideoImages(o: VideoImagesOptions): UseVideoImages {\n const r = useMediaRead(o.ref, { editor: true, read: o.read, client: o.client });\n const find = (p: string) => r.read?.files.find((f) => f.upload && samePath(f.path, p)) ?? null;\n const posterPath = o.poster ?? \"poster\";\n const { read, set: update } = r;\n const set = useCallback((poster: FileInfo) => update(withUpload(read, posterPath, poster)), [read, posterPath, update]);\n return { video: find(o.video ?? \"source\"), poster: find(posterPath), loading: r.loading, error: r.error, reload: r.reload, set };\n}\n\nexport interface FrameStripOptions {\n ref: RefBody;\n /** The video upload's path (a read's, with its extension). */\n path?: string;\n /** Seconds; nothing is fetched until it is known. */\n duration?: number;\n /** Frames across the video. Default 8. */\n count?: number;\n /** Pixels. Default 160. */\n width?: number;\n /** The first failed grab (the strip keeps going without it). */\n onError?: (e: ContentKitError) => void;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface StripFrame {\n time: number;\n /** An object URL once fetched. */\n url?: string;\n}\n\n/**\n * Evenly spaced frames for coarse browsing, fetched one at a time (the frame\n * endpoint allows two grabs at once per host), revoked on change or unmount.\n */\nexport function useFrameStrip(o: FrameStripOptions): StripFrame[] {\n const { media } = useContentKitClient(o.client);\n const count = o.count ?? 8;\n const width = o.width ?? 160;\n const duration = o.duration ?? 0;\n const key = `${refKey(o.ref)}#${o.path ?? \"\"}#${duration}#${count}#${width}`;\n const [state, setState] = useState<{ key: string; frames: StripFrame[] }>({ key: \"\", frames: [] });\n const ref = useRef(o.ref);\n ref.current = o.ref;\n const onError = useRef(o.onError);\n onError.current = o.onError;\n useEffect(() => {\n if (!(duration > 0) || !o.path) return;\n const path = o.path;\n const ctl = new AbortController();\n let reported = false;\n const times = Array.from({ length: count }, (_, i) => round3(((i + 0.5) / count) * duration));\n const urls: string[] = [];\n setState({ key, frames: times.map((time) => ({ time })) });\n void (async () => {\n for (const [i, time] of times.entries()) {\n try {\n const blob = await media.getFrame(ref.current, path, time, width, ctl.signal);\n if (ctl.signal.aborted) return;\n const url = URL.createObjectURL(blob);\n urls.push(url);\n setState((s) => (s.key === key ? { key, frames: s.frames.map((f, j) => (j === i ? { ...f, url } : f)) } : s));\n } catch (e) {\n if (ctl.signal.aborted) return;\n if (!reported) onError.current?.(asError(e));\n reported = true;\n }\n }\n })();\n return () => {\n ctl.abort();\n urls.forEach((u) => URL.revokeObjectURL(u));\n };\n }, [media, key, duration, count, width, o.path]);\n return state.key === key ? state.frames : [];\n}\n\nexport interface VideoFrameOptions {\n ref: RefBody;\n /** The video upload's path; nothing is fetched without it. */\n path?: string;\n /** Seconds; undefined fetches nothing. */\n time?: number;\n /** Pixels. Default 640. */\n width?: number;\n /** Debounce while scrubbing, ms. Default 120. */\n delay?: number;\n onError?: (e: ContentKitError) => void;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface UseVideoFrame {\n /** The latest fetched frame; kept while the next one loads. */\n url?: string;\n /** The time url shows. */\n time?: number;\n loading: boolean;\n error?: ContentKitError;\n}\n\n/** The exact frame at time, debounced while it changes. */\nexport function useVideoFrame(o: VideoFrameOptions): UseVideoFrame {\n const { media } = useContentKitClient(o.client);\n const width = o.width ?? 640;\n const delay = o.delay ?? 120;\n const [state, setState] = useState<UseVideoFrame>({ loading: false });\n const ref = useRef(o.ref);\n ref.current = o.ref;\n const last = useRef<string | undefined>(undefined);\n const onError = useRef(o.onError);\n onError.current = o.onError;\n const key = refKey(o.ref);\n useEffect(() => {\n if (o.time === undefined || !o.path) return;\n const time = o.time;\n const path = o.path;\n const ctl = new AbortController();\n setState((s) => ({ ...s, loading: true, error: undefined }));\n const t = setTimeout(() => {\n media.getFrame(ref.current, path, time, width, ctl.signal).then(\n (blob) => {\n if (ctl.signal.aborted) return;\n if (last.current) URL.revokeObjectURL(last.current);\n const url = (last.current = URL.createObjectURL(blob));\n setState({ url, time, loading: false });\n },\n (e) => {\n if (ctl.signal.aborted) return;\n setState((s) => ({ ...s, loading: false, error: asError(e) }));\n onError.current?.(asError(e));\n },\n );\n }, delay);\n return () => {\n clearTimeout(t);\n ctl.abort();\n };\n }, [media, key, o.path, o.time, width, delay]);\n useEffect(\n () => () => {\n if (last.current) URL.revokeObjectURL(last.current);\n },\n [],\n );\n return state;\n}\n\nexport type VideoSaveState =\n | { status: \"idle\" }\n | { status: \"saving\"; progress?: Progress; rendering?: boolean }\n | { status: \"error\"; error: ContentKitError };\n\nexport interface VideoPosterOptions {\n ref: RefBody;\n /** The poster upload path. Default \"poster\". */\n path?: string;\n /** With the processed poster upload after each save. */\n onSaved?: (poster: FileInfo) => void;\n /** Polling limit for the render, ms. Default 120000. */\n timeout?: number;\n /** Every failed save: the request, the render or the wait. */\n onError?: (e: ContentKitError) => void;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface UseVideoPoster {\n state: VideoSaveState;\n /** A frame at time (seconds), cropped by edit in the frame's pixels (the video's w×h). */\n saveFrame: (time: number, edit?: Edit | null) => Promise<FileInfo | undefined>;\n /** An uploaded image, cropped by edit in its own pixels. */\n saveUpload: (image: Blob, edit?: Edit | null) => Promise<FileInfo | undefined>;\n /** The worker's choice of frame. */\n saveAuto: () => Promise<FileInfo | undefined>;\n /**\n * A frame of another item's video upload (at path, time seconds) uploaded\n * as the poster image, cropped by edit in the frame's pixels.\n */\n saveFrameFrom: (video: RefBody, path: string, time: number, edit?: Edit | null) => Promise<FileInfo | undefined>;\n reset: () => void;\n}\n\n/** Headless poster selection: a frame op or an uploaded image, then the wait for the worker to render it. */\nexport function useVideoPoster(o: VideoPosterOptions): UseVideoPoster {\n const { media } = useContentKitClient(o.client);\n const [state, setState] = useState<VideoSaveState>({ status: \"idle\" });\n const opts = useRef(o);\n opts.current = o;\n const run = useCallback(async (save: (onProgress: (p: Progress) => void) => Promise<FileInfo>) => {\n setState({ status: \"saving\" });\n try {\n const f = await save((progress) => setState({ status: \"saving\", progress, rendering: progress.phase === \"processing\" }));\n setState({ status: \"idle\" });\n opts.current.onSaved?.(f);\n return f;\n } catch (e) {\n const error = asError(e);\n setState({ status: \"error\", error });\n opts.current.onError?.(error);\n return undefined;\n }\n }, []);\n const frame = useCallback(\n (op: Omit<Op, \"op\" | \"path\">) =>\n run(async (onProgress) => {\n const { ref, path = \"poster\", timeout } = opts.current;\n await media.commit(ref, [{ op: \"frame\", path, ...op }]);\n onProgress({ phase: \"processing\", loaded: 0, total: 0 });\n return media.waitFor(ref, path, { timeout });\n }),\n [media, run],\n );\n return {\n state,\n saveFrame: useCallback((time, edit) => frame({ t: round3(time), ...(edit ? { edit } : {}) }), [frame]),\n saveUpload: useCallback(\n (image, edit) => {\n const { ref, path = \"poster\", timeout } = opts.current;\n return run((onProgress) => media.put(image, { ref, path, edit, timeout, onProgress }));\n },\n [media, run],\n ),\n saveAuto: useCallback(() => frame({ auto: true }), [frame]),\n saveFrameFrom: useCallback(\n (video, path, time, edit) => {\n const { ref, path: to = \"poster\", timeout } = opts.current;\n return run(async (onProgress) => {\n const still = await media.getFrame(video, path, round3(time));\n // GET /frame answers a JPEG.\n const file = new File([still], \"frame.jpg\", { type: \"image/jpeg\" });\n return media.put(file, { ref, path: to, edit, timeout, onProgress });\n });\n },\n [media, run],\n ),\n reset: useCallback(() => setState({ status: \"idle\" }), []),\n };\n}\n\nexport const round3 = (v: number) => Math.round(v * 1000) / 1000;\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface NearViewportOptions {
2
+ /** How near (IntersectionObserver rootMargin) counts as near. Default "800px 0px". */
3
+ margin?: string;
4
+ /**
5
+ * Not near again once beyond this margin (wider than margin, so scrolling
6
+ * back and forth over the edge does not thrash). Default: once near, always near.
7
+ */
8
+ leaveMargin?: string;
9
+ /** Near from the start (content above the fold). */
10
+ eager?: boolean;
11
+ /** The scroll container; default the viewport. */
12
+ root?: Element | null;
13
+ }
14
+ /**
15
+ * Whether the element (attach the returned callback ref) is near the
16
+ * viewport: mount media as it approaches, unmount it far away. Without
17
+ * IntersectionObserver everything is near.
18
+ */
19
+ export declare function useNearViewport(o?: NearViewportOptions): [ref: (el: Element | null) => void, near: boolean];