@orthacms/media-admin 0.0.0-reserve.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 (214) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/lib/components/AssetActionsMenu/index.d.ts +37 -0
  7. package/dist/lib/components/AssetActionsMenu/index.d.ts.map +1 -0
  8. package/dist/lib/components/AssetActionsMenu/index.js +57 -0
  9. package/dist/lib/components/AssetDetailDrawer/MetaRow/index.d.ts +11 -0
  10. package/dist/lib/components/AssetDetailDrawer/MetaRow/index.d.ts.map +1 -0
  11. package/dist/lib/components/AssetDetailDrawer/MetaRow/index.js +9 -0
  12. package/dist/lib/components/AssetDetailDrawer/index.d.ts +28 -0
  13. package/dist/lib/components/AssetDetailDrawer/index.d.ts.map +1 -0
  14. package/dist/lib/components/AssetDetailDrawer/index.js +93 -0
  15. package/dist/lib/components/EntryMediaTab/MediaFieldSection/index.d.ts +28 -0
  16. package/dist/lib/components/EntryMediaTab/MediaFieldSection/index.d.ts.map +1 -0
  17. package/dist/lib/components/EntryMediaTab/MediaFieldSection/index.js +52 -0
  18. package/dist/lib/components/EntryMediaTab/index.d.ts +11 -0
  19. package/dist/lib/components/EntryMediaTab/index.d.ts.map +1 -0
  20. package/dist/lib/components/EntryMediaTab/index.js +31 -0
  21. package/dist/lib/components/FolderActionsMenu/index.d.ts +21 -0
  22. package/dist/lib/components/FolderActionsMenu/index.d.ts.map +1 -0
  23. package/dist/lib/components/FolderActionsMenu/index.js +25 -0
  24. package/dist/lib/components/MediaAltTextWidget/index.d.ts +9 -0
  25. package/dist/lib/components/MediaAltTextWidget/index.d.ts.map +1 -0
  26. package/dist/lib/components/MediaAltTextWidget/index.js +66 -0
  27. package/dist/lib/components/MediaBreadcrumbs/index.d.ts +11 -0
  28. package/dist/lib/components/MediaBreadcrumbs/index.d.ts.map +1 -0
  29. package/dist/lib/components/MediaEmptyState/index.d.ts +13 -0
  30. package/dist/lib/components/MediaEmptyState/index.d.ts.map +1 -0
  31. package/dist/lib/components/MediaEmptyState/index.js +38 -0
  32. package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.d.ts +34 -0
  33. package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.d.ts.map +1 -0
  34. package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.js +117 -0
  35. package/dist/lib/components/MediaFieldControl/index.d.ts +61 -0
  36. package/dist/lib/components/MediaFieldControl/index.d.ts.map +1 -0
  37. package/dist/lib/components/MediaFieldControl/index.js +422 -0
  38. package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.d.ts +17 -0
  39. package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.d.ts.map +1 -0
  40. package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.js +37 -0
  41. package/dist/lib/components/MediaFoldersNav/index.d.ts +22 -0
  42. package/dist/lib/components/MediaFoldersNav/index.d.ts.map +1 -0
  43. package/dist/lib/components/MediaFoldersNav/index.js +40 -0
  44. package/dist/lib/components/MediaGrid/MediaAssetCard/index.d.ts +18 -0
  45. package/dist/lib/components/MediaGrid/MediaAssetCard/index.d.ts.map +1 -0
  46. package/dist/lib/components/MediaGrid/MediaAssetCard/index.js +29 -0
  47. package/dist/lib/components/MediaGrid/MediaFolderCard/index.d.ts +16 -0
  48. package/dist/lib/components/MediaGrid/MediaFolderCard/index.d.ts.map +1 -0
  49. package/dist/lib/components/MediaGrid/MediaFolderCard/index.js +25 -0
  50. package/dist/lib/components/MediaGrid/index.d.ts +24 -0
  51. package/dist/lib/components/MediaGrid/index.d.ts.map +1 -0
  52. package/dist/lib/components/MediaGrid/index.js +20 -0
  53. package/dist/lib/components/MediaKindBadge/index.d.ts +10 -0
  54. package/dist/lib/components/MediaKindBadge/index.d.ts.map +1 -0
  55. package/dist/lib/components/MediaKindBadge/index.js +30 -0
  56. package/dist/lib/components/MediaKindIcon/index.d.ts +11 -0
  57. package/dist/lib/components/MediaKindIcon/index.d.ts.map +1 -0
  58. package/dist/lib/components/MediaKindIcon/index.js +20 -0
  59. package/dist/lib/components/MediaLibrarySkeleton/index.d.ts +18 -0
  60. package/dist/lib/components/MediaLibrarySkeleton/index.d.ts.map +1 -0
  61. package/dist/lib/components/MediaLibrarySkeleton/index.js +43 -0
  62. package/dist/lib/components/MediaPagination/index.d.ts +26 -0
  63. package/dist/lib/components/MediaPagination/index.d.ts.map +1 -0
  64. package/dist/lib/components/MediaPagination/index.js +52 -0
  65. package/dist/lib/components/MediaPickerDialog/MediaPickerTile/index.d.ts +17 -0
  66. package/dist/lib/components/MediaPickerDialog/MediaPickerTile/index.d.ts.map +1 -0
  67. package/dist/lib/components/MediaPickerDialog/MediaPickerTile/index.js +43 -0
  68. package/dist/lib/components/MediaPickerDialog/index.d.ts +21 -0
  69. package/dist/lib/components/MediaPickerDialog/index.d.ts.map +1 -0
  70. package/dist/lib/components/MediaPickerDialog/index.js +208 -0
  71. package/dist/lib/components/MediaSelectionBar/index.d.ts +18 -0
  72. package/dist/lib/components/MediaSelectionBar/index.d.ts.map +1 -0
  73. package/dist/lib/components/MediaSelectionBar/index.js +26 -0
  74. package/dist/lib/components/MediaStorageStat/index.d.ts +9 -0
  75. package/dist/lib/components/MediaStorageStat/index.d.ts.map +1 -0
  76. package/dist/lib/components/MediaStorageStat/index.js +31 -0
  77. package/dist/lib/components/MediaStorageWidget/index.d.ts +10 -0
  78. package/dist/lib/components/MediaStorageWidget/index.d.ts.map +1 -0
  79. package/dist/lib/components/MediaStorageWidget/index.js +93 -0
  80. package/dist/lib/components/MediaTable/MediaAssetRow/index.d.ts +17 -0
  81. package/dist/lib/components/MediaTable/MediaAssetRow/index.d.ts.map +1 -0
  82. package/dist/lib/components/MediaTable/MediaFolderRow/index.d.ts +16 -0
  83. package/dist/lib/components/MediaTable/MediaFolderRow/index.d.ts.map +1 -0
  84. package/dist/lib/components/MediaTable/index.d.ts +26 -0
  85. package/dist/lib/components/MediaTable/index.d.ts.map +1 -0
  86. package/dist/lib/components/MediaThumbnail/index.d.ts +19 -0
  87. package/dist/lib/components/MediaThumbnail/index.d.ts.map +1 -0
  88. package/dist/lib/components/MediaThumbnail/index.js +26 -0
  89. package/dist/lib/components/MediaToolbar/index.d.ts +19 -0
  90. package/dist/lib/components/MediaToolbar/index.d.ts.map +1 -0
  91. package/dist/lib/components/MediaToolbar/index.js +62 -0
  92. package/dist/lib/components/MediaTopBar/index.d.ts +20 -0
  93. package/dist/lib/components/MediaTopBar/index.d.ts.map +1 -0
  94. package/dist/lib/components/MediaTopBar/index.js +35 -0
  95. package/dist/lib/components/MediaUploadBanner/MediaUploadRow/index.d.ts +14 -0
  96. package/dist/lib/components/MediaUploadBanner/MediaUploadRow/index.d.ts.map +1 -0
  97. package/dist/lib/components/MediaUploadBanner/MediaUploadRow/index.js +51 -0
  98. package/dist/lib/components/MediaUploadBanner/index.d.ts +24 -0
  99. package/dist/lib/components/MediaUploadBanner/index.d.ts.map +1 -0
  100. package/dist/lib/components/MediaUploadBanner/index.js +99 -0
  101. package/dist/lib/components/MediaUploadsWidget/index.d.ts +7 -0
  102. package/dist/lib/components/MediaUploadsWidget/index.d.ts.map +1 -0
  103. package/dist/lib/components/MediaUploadsWidget/index.js +70 -0
  104. package/dist/lib/components/MoveAssetsDialog/DestinationOption/index.d.ts +19 -0
  105. package/dist/lib/components/MoveAssetsDialog/DestinationOption/index.d.ts.map +1 -0
  106. package/dist/lib/components/MoveAssetsDialog/DestinationOption/index.js +10 -0
  107. package/dist/lib/components/MoveAssetsDialog/index.d.ts +18 -0
  108. package/dist/lib/components/MoveAssetsDialog/index.d.ts.map +1 -0
  109. package/dist/lib/components/MoveAssetsDialog/index.js +41 -0
  110. package/dist/lib/components/NewFolderDialog/index.d.ts +13 -0
  111. package/dist/lib/components/NewFolderDialog/index.d.ts.map +1 -0
  112. package/dist/lib/components/NewFolderDialog/index.js +45 -0
  113. package/dist/lib/components/RenameDialog/index.d.ts +13 -0
  114. package/dist/lib/components/RenameDialog/index.d.ts.map +1 -0
  115. package/dist/lib/components/RenameDialog/index.js +37 -0
  116. package/dist/lib/components/UploadDialog/StagedFileRow/index.d.ts +25 -0
  117. package/dist/lib/components/UploadDialog/StagedFileRow/index.d.ts.map +1 -0
  118. package/dist/lib/components/UploadDialog/StagedFileRow/index.js +82 -0
  119. package/dist/lib/components/UploadDialog/index.d.ts +50 -0
  120. package/dist/lib/components/UploadDialog/index.d.ts.map +1 -0
  121. package/dist/lib/components/UploadDialog/index.js +120 -0
  122. package/dist/lib/components/WysiwygLibrarySource/index.d.ts +14 -0
  123. package/dist/lib/components/WysiwygLibrarySource/index.d.ts.map +1 -0
  124. package/dist/lib/components/WysiwygLibrarySource/index.js +28 -0
  125. package/dist/lib/components/WysiwygUploadSource/index.d.ts +25 -0
  126. package/dist/lib/components/WysiwygUploadSource/index.d.ts.map +1 -0
  127. package/dist/lib/components/WysiwygUploadSource/index.js +102 -0
  128. package/dist/lib/constants/index.d.ts +82 -0
  129. package/dist/lib/constants/index.d.ts.map +1 -0
  130. package/dist/lib/constants/index.js +75 -0
  131. package/dist/lib/hooks/useMediaInsights/index.d.ts +32 -0
  132. package/dist/lib/hooks/useMediaInsights/index.d.ts.map +1 -0
  133. package/dist/lib/hooks/useMediaInsights/index.js +85 -0
  134. package/dist/lib/hooks/useMediaLibrary/index.d.ts +83 -0
  135. package/dist/lib/hooks/useMediaLibrary/index.d.ts.map +1 -0
  136. package/dist/lib/hooks/useMediaLibrary/index.js +341 -0
  137. package/dist/lib/hooks/usePendingMediaUploads/index.d.ts +26 -0
  138. package/dist/lib/hooks/usePendingMediaUploads/index.d.ts.map +1 -0
  139. package/dist/lib/hooks/usePendingMediaUploads/index.js +217 -0
  140. package/dist/lib/hooks/useUploadQueue/index.d.ts +33 -0
  141. package/dist/lib/hooks/useUploadQueue/index.d.ts.map +1 -0
  142. package/dist/lib/hooks/useUploadQueue/index.js +218 -0
  143. package/dist/lib/hooks/useWysiwygAssetPicker/index.d.ts +23 -0
  144. package/dist/lib/hooks/useWysiwygAssetPicker/index.d.ts.map +1 -0
  145. package/dist/lib/infrastructure/apiMessage/index.d.ts +17 -0
  146. package/dist/lib/infrastructure/apiMessage/index.d.ts.map +1 -0
  147. package/dist/lib/infrastructure/apiMessage/index.js +30 -0
  148. package/dist/lib/infrastructure/httpMediaGateway/index.d.ts +9 -0
  149. package/dist/lib/infrastructure/httpMediaGateway/index.d.ts.map +1 -0
  150. package/dist/lib/infrastructure/httpMediaGateway/index.js +157 -0
  151. package/dist/lib/infrastructure/httpMediaInsightsGateway/index.d.ts +8 -0
  152. package/dist/lib/infrastructure/httpMediaInsightsGateway/index.d.ts.map +1 -0
  153. package/dist/lib/infrastructure/httpMediaInsightsGateway/index.js +35 -0
  154. package/dist/lib/infrastructure/mediaGateway/index.d.ts +120 -0
  155. package/dist/lib/infrastructure/mediaGateway/index.d.ts.map +1 -0
  156. package/dist/lib/infrastructure/mediaGateway/index.js +1 -0
  157. package/dist/lib/infrastructure/mediaInsightsGateway/index.d.ts +46 -0
  158. package/dist/lib/infrastructure/mediaInsightsGateway/index.d.ts.map +1 -0
  159. package/dist/lib/infrastructure/mediaInsightsGateway/index.js +6 -0
  160. package/dist/lib/infrastructure/mediaKeys/index.d.ts +23 -0
  161. package/dist/lib/infrastructure/mediaKeys/index.d.ts.map +1 -0
  162. package/dist/lib/infrastructure/mediaKeys/index.js +11 -0
  163. package/dist/lib/infrastructure/mediaMapper/index.d.ts +50 -0
  164. package/dist/lib/infrastructure/mediaMapper/index.d.ts.map +1 -0
  165. package/dist/lib/infrastructure/mediaMapper/index.js +39 -0
  166. package/dist/lib/pages/MediaLibraryPage/index.d.ts +23 -0
  167. package/dist/lib/pages/MediaLibraryPage/index.d.ts.map +1 -0
  168. package/dist/lib/pages/MediaLibraryPage/index.js +425 -0
  169. package/dist/lib/types/mediaAsset/index.d.ts +44 -0
  170. package/dist/lib/types/mediaAsset/index.d.ts.map +1 -0
  171. package/dist/lib/types/mediaAsset/index.js +1 -0
  172. package/dist/lib/types/mediaFieldDisplay/index.d.ts +48 -0
  173. package/dist/lib/types/mediaFieldDisplay/index.d.ts.map +1 -0
  174. package/dist/lib/types/mediaFieldDisplay/index.js +1 -0
  175. package/dist/lib/types/mediaFolder/index.d.ts +16 -0
  176. package/dist/lib/types/mediaFolder/index.d.ts.map +1 -0
  177. package/dist/lib/types/mediaFolder/index.js +1 -0
  178. package/dist/lib/types/pendingUpload/index.d.ts +38 -0
  179. package/dist/lib/types/pendingUpload/index.d.ts.map +1 -0
  180. package/dist/lib/types/pendingUpload/index.js +1 -0
  181. package/dist/lib/types/upload/index.d.ts +37 -0
  182. package/dist/lib/types/upload/index.d.ts.map +1 -0
  183. package/dist/lib/types/upload/index.js +1 -0
  184. package/dist/lib/utils/assetGradient/index.d.ts +8 -0
  185. package/dist/lib/utils/assetGradient/index.d.ts.map +1 -0
  186. package/dist/lib/utils/assetGradient/index.js +16 -0
  187. package/dist/lib/utils/assetHandlers/index.d.ts +11 -0
  188. package/dist/lib/utils/assetHandlers/index.d.ts.map +1 -0
  189. package/dist/lib/utils/assetHandlers/index.js +17 -0
  190. package/dist/lib/utils/folderContents/index.d.ts +17 -0
  191. package/dist/lib/utils/folderContents/index.d.ts.map +1 -0
  192. package/dist/lib/utils/folderContents/index.js +35 -0
  193. package/dist/lib/utils/folderTree/index.d.ts +13 -0
  194. package/dist/lib/utils/folderTree/index.d.ts.map +1 -0
  195. package/dist/lib/utils/folderTree/index.js +15 -0
  196. package/dist/lib/utils/formatBytes/index.d.ts +6 -0
  197. package/dist/lib/utils/formatBytes/index.d.ts.map +1 -0
  198. package/dist/lib/utils/formatBytes/index.js +17 -0
  199. package/dist/lib/utils/formatDuration/index.d.ts +6 -0
  200. package/dist/lib/utils/formatDuration/index.d.ts.map +1 -0
  201. package/dist/lib/utils/formatDuration/index.js +13 -0
  202. package/dist/lib/utils/kindFromMime/index.d.ts +10 -0
  203. package/dist/lib/utils/kindFromMime/index.d.ts.map +1 -0
  204. package/dist/lib/utils/kindFromMime/index.js +27 -0
  205. package/dist/lib/utils/mediaAccept/index.d.ts +30 -0
  206. package/dist/lib/utils/mediaAccept/index.d.ts.map +1 -0
  207. package/dist/lib/utils/mediaAccept/index.js +63 -0
  208. package/dist/lib/utils/mediaPlugin/index.d.ts +15 -0
  209. package/dist/lib/utils/mediaPlugin/index.d.ts.map +1 -0
  210. package/dist/lib/utils/mediaPlugin/index.js +177 -0
  211. package/dist/lib/utils/toWysiwygEmbed/index.d.ts +23 -0
  212. package/dist/lib/utils/toWysiwygEmbed/index.d.ts.map +1 -0
  213. package/dist/lib/utils/toWysiwygEmbed/index.js +44 -0
  214. package/package.json +50 -0
@@ -0,0 +1,33 @@
1
+ import type { UploadItem, UploadSummary } from '../../types/upload';
2
+ import type { StagedUpload } from '../../infrastructure/mediaGateway';
3
+ /**
4
+ * The Media Library's upload queue: turns a set of picked files into a list of
5
+ * progress-tracked items, uploading at most {@link UPLOAD_CONCURRENCY} at once.
6
+ *
7
+ * Each file is **its own request** (the gateway is one-file-per-call), so one
8
+ * rejection fails exactly one row and leaves the rest of the batch running —
9
+ * the old `Promise.all` lost the whole batch to a single bad file. Failed rows
10
+ * are individually retryable; in-flight ones are cancellable through an
11
+ * `AbortController`.
12
+ *
13
+ * The `File` objects and the runner's bookkeeping live in refs, not state: a
14
+ * progress event fires many times a second per file, and only the small
15
+ * {@link UploadItem} rows belong in a re-render.
16
+ *
17
+ * @param folderId - destination folder, captured **per file at enqueue time**
18
+ * so navigating away mid-upload can't redirect in-flight files to a folder
19
+ * the user merely happens to be looking at.
20
+ * @param onUploaded - called once per settled batch that may have changed the
21
+ * library (the caller invalidates its queries there — once, not per file).
22
+ * That includes a **cancelled** file: an abort only stops the client, so a
23
+ * body the server already finished reading is committed either way.
24
+ */
25
+ export declare function useUploadQueue(folderId: string, onUploaded: () => void): {
26
+ items: UploadItem[];
27
+ summary: UploadSummary;
28
+ enqueue: (uploads: StagedUpload[]) => void;
29
+ retry: (id: string) => void;
30
+ cancel: (id: string) => void;
31
+ dismissSettled: () => void;
32
+ };
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/useUploadQueue/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAiBtE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,IAAI;;;uBAiHrD,YAAY,EAAE;gBAsBnB,MAAM;iBAeN,MAAM;;EAuElB"}
@@ -0,0 +1,218 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { UPLOAD_CONCURRENCY, UPLOAD_STATUS } from '../../constants';
4
+ import { httpMediaGateway } from '../../infrastructure/httpMediaGateway';
5
+ import { apiMessage } from '../../infrastructure/apiMessage';
6
+ /** Intl descriptor for an upload failure with no server-supplied reason. */
7
+ const messages = defineMessages({
8
+ failed: {
9
+ id: 'media.upload.failed',
10
+ defaultMessage: 'Upload failed'
11
+ }
12
+ });
13
+ /** Statuses that are over — the ones "Dismiss" clears. */
14
+ const SETTLED = [
15
+ UPLOAD_STATUS.Done,
16
+ UPLOAD_STATUS.Failed,
17
+ UPLOAD_STATUS.Cancelled
18
+ ];
19
+ /**
20
+ * The Media Library's upload queue: turns a set of picked files into a list of
21
+ * progress-tracked items, uploading at most {@link UPLOAD_CONCURRENCY} at once.
22
+ *
23
+ * Each file is **its own request** (the gateway is one-file-per-call), so one
24
+ * rejection fails exactly one row and leaves the rest of the batch running —
25
+ * the old `Promise.all` lost the whole batch to a single bad file. Failed rows
26
+ * are individually retryable; in-flight ones are cancellable through an
27
+ * `AbortController`.
28
+ *
29
+ * The `File` objects and the runner's bookkeeping live in refs, not state: a
30
+ * progress event fires many times a second per file, and only the small
31
+ * {@link UploadItem} rows belong in a re-render.
32
+ *
33
+ * @param folderId - destination folder, captured **per file at enqueue time**
34
+ * so navigating away mid-upload can't redirect in-flight files to a folder
35
+ * the user merely happens to be looking at.
36
+ * @param onUploaded - called once per settled batch that may have changed the
37
+ * library (the caller invalidates its queries there — once, not per file).
38
+ * That includes a **cancelled** file: an abort only stops the client, so a
39
+ * body the server already finished reading is committed either way.
40
+ */
41
+ export function useUploadQueue(folderId, onUploaded) {
42
+ const intl = useIntl();
43
+ const [items, setItems] = useState([]);
44
+ // --- Runner bookkeeping (refs: mutated mid-flight, never rendered) --------
45
+ /** id → the file to send, its description, and the folder it was queued for. */
46
+ const filesRef = useRef(new Map());
47
+ /** id → abort handle, present only while that file is on the wire. */
48
+ const abortsRef = useRef(new Map());
49
+ /** Ids waiting for a slot, oldest first. */
50
+ const waitingRef = useRef([]);
51
+ const activeRef = useRef(0);
52
+ /** Whether the current batch has produced a success worth invalidating for. */
53
+ const successRef = useRef(false);
54
+ const nextIdRef = useRef(0);
55
+ // `pump` and `runOne` call each other; the ref breaks the cycle without
56
+ // rebuilding either callback on every render.
57
+ const runOneRef = useRef(() => undefined);
58
+ const onUploadedRef = useRef(onUploaded);
59
+ useEffect(() => {
60
+ onUploadedRef.current = onUploaded;
61
+ }, [onUploaded]);
62
+ const patch = useCallback((id, next) => {
63
+ setItems((prev) => prev.map((item) => (item.id === id ? { ...item, ...next } : item)));
64
+ }, []);
65
+ /** Fills every free slot, then settles the batch if nothing is left. */
66
+ const pump = useCallback(() => {
67
+ while (activeRef.current < UPLOAD_CONCURRENCY &&
68
+ waitingRef.current.length > 0) {
69
+ const id = waitingRef.current.shift();
70
+ if (id)
71
+ runOneRef.current(id);
72
+ }
73
+ if (activeRef.current === 0 && waitingRef.current.length === 0) {
74
+ if (successRef.current) {
75
+ successRef.current = false;
76
+ onUploadedRef.current();
77
+ }
78
+ }
79
+ }, []);
80
+ const runOne = useCallback((id) => {
81
+ const entry = filesRef.current.get(id);
82
+ if (!entry)
83
+ return;
84
+ const controller = new AbortController();
85
+ abortsRef.current.set(id, controller);
86
+ // Claim the slot synchronously — `pump`'s loop checks this counter
87
+ // again on its very next iteration.
88
+ activeRef.current += 1;
89
+ patch(id, { status: UPLOAD_STATUS.Uploading, progress: 0 });
90
+ void httpMediaGateway
91
+ .uploadFile(entry.folderId, entry.file, {
92
+ alt: entry.alt,
93
+ signal: controller.signal,
94
+ onProgress: (percent) => patch(id, { progress: percent })
95
+ })
96
+ .then(() => {
97
+ successRef.current = true;
98
+ patch(id, {
99
+ status: UPLOAD_STATUS.Done,
100
+ progress: 100,
101
+ error: undefined
102
+ });
103
+ })
104
+ .catch((error) => {
105
+ if (controller.signal.aborted) {
106
+ patch(id, { status: UPLOAD_STATUS.Cancelled });
107
+ // Cancelling aborts the *client*. If the server had
108
+ // already received the whole body it commits anyway, so
109
+ // the asset exists — it was simply never fetched, and
110
+ // stayed invisible until a manual reload. Refetch on a
111
+ // cancel too, so the grid tells the truth. (Deleting the
112
+ // committed asset isn't possible from here: the response
113
+ // that carries its id is what the abort threw away.)
114
+ successRef.current = true;
115
+ return;
116
+ }
117
+ patch(id, {
118
+ status: UPLOAD_STATUS.Failed,
119
+ progress: 0,
120
+ // The API's own sentence ("File exceeds the maximum
121
+ // upload size.") rather than the transport's status
122
+ // line, which told the author nothing actionable.
123
+ error: apiMessage(error) ??
124
+ intl.formatMessage(messages.failed)
125
+ });
126
+ })
127
+ .finally(() => {
128
+ abortsRef.current.delete(id);
129
+ activeRef.current -= 1;
130
+ pump();
131
+ });
132
+ }, [intl, patch, pump]);
133
+ useEffect(() => {
134
+ runOneRef.current = runOne;
135
+ }, [runOne]);
136
+ /** Queues files for the folder open **now** and starts uploading. */
137
+ const enqueue = useCallback((uploads) => {
138
+ if (uploads.length === 0)
139
+ return;
140
+ const queued = uploads.map(({ file, alt }) => {
141
+ const id = `upload-${nextIdRef.current++}`;
142
+ filesRef.current.set(id, { file, alt, folderId });
143
+ waitingRef.current.push(id);
144
+ return {
145
+ id,
146
+ name: file.name,
147
+ size: file.size,
148
+ progress: 0,
149
+ status: UPLOAD_STATUS.Pending
150
+ };
151
+ });
152
+ setItems((prev) => [...prev, ...queued]);
153
+ pump();
154
+ }, [folderId, pump]);
155
+ /** Re-queues one failed or cancelled file. */
156
+ const retry = useCallback((id) => {
157
+ if (!filesRef.current.has(id))
158
+ return;
159
+ patch(id, {
160
+ status: UPLOAD_STATUS.Pending,
161
+ progress: 0,
162
+ error: undefined
163
+ });
164
+ waitingRef.current.push(id);
165
+ pump();
166
+ }, [patch, pump]);
167
+ /** Aborts an in-flight file, or drops one that hasn't started. */
168
+ const cancel = useCallback((id) => {
169
+ const controller = abortsRef.current.get(id);
170
+ if (controller) {
171
+ // The abort rejects the request; `runOne`'s catch marks it.
172
+ controller.abort();
173
+ return;
174
+ }
175
+ waitingRef.current = waitingRef.current.filter((queued) => queued !== id);
176
+ patch(id, { status: UPLOAD_STATUS.Cancelled });
177
+ }, [patch]);
178
+ /** Clears every finished row, leaving anything still uploading. */
179
+ const dismissSettled = useCallback(() => {
180
+ setItems((prev) => {
181
+ for (const item of prev) {
182
+ if (SETTLED.includes(item.status)) {
183
+ filesRef.current.delete(item.id);
184
+ }
185
+ }
186
+ return prev.filter((item) => !SETTLED.includes(item.status));
187
+ });
188
+ }, []);
189
+ // Abort anything still in flight if the library unmounts, so a navigation
190
+ // away doesn't leave orphan requests writing to a dead component.
191
+ useEffect(() => {
192
+ const aborts = abortsRef.current;
193
+ return () => {
194
+ for (const controller of aborts.values())
195
+ controller.abort();
196
+ };
197
+ }, []);
198
+ const summary = useMemo(() => {
199
+ const counted = items.filter((item) => item.status !== UPLOAD_STATUS.Cancelled);
200
+ const totalBytes = counted.reduce((sum, item) => sum + item.size, 0);
201
+ const sentBytes = counted.reduce((sum, item) => sum + (item.size * item.progress) / 100, 0);
202
+ return {
203
+ inFlight: counted.filter((item) => item.status === UPLOAD_STATUS.Pending ||
204
+ item.status === UPLOAD_STATUS.Uploading).length,
205
+ done: counted.filter((item) => item.status === UPLOAD_STATUS.Done)
206
+ .length,
207
+ failed: counted.filter((item) => item.status === UPLOAD_STATUS.Failed).length,
208
+ total: counted.length,
209
+ // A zero-byte file would make this 0/0; treat an empty batch as done.
210
+ percent: totalBytes === 0
211
+ ? 100
212
+ : Math.round((sentBytes / totalBytes) * 100),
213
+ active: counted.some((item) => item.status === UPLOAD_STATUS.Pending ||
214
+ item.status === UPLOAD_STATUS.Uploading)
215
+ };
216
+ }, [items]);
217
+ return { items, summary, enqueue, retry, cancel, dismissSettled };
218
+ }
@@ -0,0 +1,23 @@
1
+ import type { AssetPicker } from '@ortha-cms/content-admin';
2
+ /** Stable id of this contribution, so the slot registration reads once. */
3
+ export declare const WYSIWYG_ASSET_PICKER_ID = "media.assetPicker";
4
+ /**
5
+ * The Media Library, offered to content-admin's rich-text field as an
6
+ * {@link AssetPicker} — how an image block in a `wysiwyg` field gets a URL from
7
+ * the library instead of only from a pasted link.
8
+ *
9
+ * The editor's port is a **promise**, and the library's picker is a **dialog**,
10
+ * so this adapts one to the other: `pick()` opens the dialog and parks the
11
+ * resolver until the author confirms or dismisses. Everything a dialog needs to
12
+ * exist — the open flag, the mounted element — stays here; the editor sees one
13
+ * async function and knows nothing about React trees.
14
+ *
15
+ * The picked asset's URL is stored **as the image's `src`**, not as an asset id.
16
+ * That is the whole contract of the field: its value is HTML any consumer can
17
+ * render, with nothing to resolve. It also means an asset deleted from the
18
+ * library leaves a dead image in a document — worth stating plainly, because the
19
+ * alternative (an id the delivery layer resolves) would make the stored value
20
+ * unusable without this CMS.
21
+ */
22
+ export declare function useWysiwygAssetPicker(): AssetPicker;
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/useWysiwygAssetPicker/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,0BAA0B,CAAC;AAK/E,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAK3D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,IAAI,WAAW,CA4CnD"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The server's own explanation of a failure, or `undefined` when there isn't
3
+ * one.
4
+ *
5
+ * `toApiError` puts the **transport's** description in `ApiError.message`
6
+ * ("Request failed with status code 413") and the parsed **body** in
7
+ * `details` — so a call site that reads `error.message` shows the user a status
8
+ * code where the API sent a sentence ("File exceeds the maximum upload size.",
9
+ * "Invalid file name: a/b.txt"). Everything media renders to a human goes
10
+ * through here instead, falling back to its own copy when the body carries
11
+ * nothing usable.
12
+ *
13
+ * A `ValidationPipe` rejection puts several strings in `message`; the first is
14
+ * the one that names the offending field, so that is what is shown.
15
+ */
16
+ export declare function apiMessage(error: unknown): string | undefined;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/infrastructure/apiMessage/index.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAY7D"}
@@ -0,0 +1,30 @@
1
+ import { ApiError } from '@orthacms/utils-admin';
2
+ /**
3
+ * The server's own explanation of a failure, or `undefined` when there isn't
4
+ * one.
5
+ *
6
+ * `toApiError` puts the **transport's** description in `ApiError.message`
7
+ * ("Request failed with status code 413") and the parsed **body** in
8
+ * `details` — so a call site that reads `error.message` shows the user a status
9
+ * code where the API sent a sentence ("File exceeds the maximum upload size.",
10
+ * "Invalid file name: a/b.txt"). Everything media renders to a human goes
11
+ * through here instead, falling back to its own copy when the body carries
12
+ * nothing usable.
13
+ *
14
+ * A `ValidationPipe` rejection puts several strings in `message`; the first is
15
+ * the one that names the offending field, so that is what is shown.
16
+ */
17
+ export function apiMessage(error) {
18
+ if (!(error instanceof ApiError))
19
+ return undefined;
20
+ const body = error.details;
21
+ const message = body?.message;
22
+ if (typeof message === 'string' && message.trim() !== '')
23
+ return message;
24
+ if (Array.isArray(message)) {
25
+ const first = message.find((entry) => typeof entry === 'string' && entry.trim() !== '');
26
+ if (typeof first === 'string')
27
+ return first;
28
+ }
29
+ return undefined;
30
+ }
@@ -0,0 +1,9 @@
1
+ import type { MediaGateway } from '../mediaGateway';
2
+ /**
3
+ * The media {@link MediaGateway} over `apiClient`. The one place the plugin
4
+ * touches HTTP: it maps responses through the ACL, normalizes failures to
5
+ * `ApiError`, and translates the root sentinel to/from the wire's `null`.
6
+ * `apiClient` attaches `X-Workspace-Id` automatically inside the workspace shell.
7
+ */
8
+ export declare const httpMediaGateway: MediaGateway;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/infrastructure/httpMediaGateway/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAKR,YAAY,EAKf,MAAM,iBAAiB,CAAC;AAQzB;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAuK9B,CAAC"}
@@ -0,0 +1,157 @@
1
+ import { apiClient, toApiError } from '@orthacms/utils-admin';
2
+ import { KIND_FILTER_ALL, ROOT_FOLDER_ID } from '../../constants';
3
+ import { toMediaAsset, toMediaFolder } from '../mediaMapper';
4
+ /** Translates the admin root sentinel to `undefined` (backend root = no id). */
5
+ function folderParam(folderId) {
6
+ return folderId === ROOT_FOLDER_ID ? undefined : folderId;
7
+ }
8
+ /**
9
+ * The media {@link MediaGateway} over `apiClient`. The one place the plugin
10
+ * touches HTTP: it maps responses through the ACL, normalizes failures to
11
+ * `ApiError`, and translates the root sentinel to/from the wire's `null`.
12
+ * `apiClient` attaches `X-Workspace-Id` automatically inside the workspace shell.
13
+ */
14
+ export const httpMediaGateway = {
15
+ async listFolders() {
16
+ try {
17
+ const { data } = await apiClient.get('/media/folders');
18
+ const folderCounts = new Map();
19
+ for (const folder of data.folders) {
20
+ folderCounts.set(folder.id, folder.assetCount);
21
+ }
22
+ folderCounts.set(ROOT_FOLDER_ID, data.rootAssetCount);
23
+ return { folders: data.folders.map(toMediaFolder), folderCounts };
24
+ }
25
+ catch (error) {
26
+ throw toApiError(error);
27
+ }
28
+ },
29
+ async listAssets({ folderId, search, kind, sort, page, pageSize }) {
30
+ try {
31
+ const term = search?.trim();
32
+ const { data } = await apiClient.get('/media/assets', {
33
+ // Omitted rather than sent empty: `axios` drops an
34
+ // `undefined` param, and the server reads a missing `search`
35
+ // as "no search" while `search=` would be a blank term it
36
+ // has to decide about. `kind` is omitted on the `all`
37
+ // sentinel for the same reason — the wire says nothing
38
+ // rather than saying "every kind".
39
+ params: {
40
+ folderId: folderParam(folderId),
41
+ search: term || undefined,
42
+ kind: kind === KIND_FILTER_ALL ? undefined : kind,
43
+ sort,
44
+ page,
45
+ pageSize
46
+ }
47
+ });
48
+ return {
49
+ items: data.items.map(toMediaAsset),
50
+ total: data.total,
51
+ page: data.page,
52
+ pageSize: data.pageSize
53
+ };
54
+ }
55
+ catch (error) {
56
+ throw toApiError(error);
57
+ }
58
+ },
59
+ async createFolder({ name, parentId }) {
60
+ try {
61
+ await apiClient.post('/media/folders', {
62
+ name,
63
+ parentId: folderParam(parentId)
64
+ });
65
+ }
66
+ catch (error) {
67
+ throw toApiError(error);
68
+ }
69
+ },
70
+ async renameFolder({ id, name }) {
71
+ try {
72
+ await apiClient.patch(`/media/folders/${id}`, { name });
73
+ }
74
+ catch (error) {
75
+ throw toApiError(error);
76
+ }
77
+ },
78
+ async deleteFolder(id) {
79
+ try {
80
+ await apiClient.delete(`/media/folders/${id}`);
81
+ }
82
+ catch (error) {
83
+ throw toApiError(error);
84
+ }
85
+ },
86
+ async uploadFile(folderId, file, options) {
87
+ const target = folderParam(folderId);
88
+ const form = new FormData();
89
+ form.append('file', file);
90
+ if (target)
91
+ form.append('folderId', target);
92
+ // Only sent when the author wrote one — an empty part would be a blank
93
+ // description, which the server normalizes away but which would still
94
+ // read as "described" to anyone auditing the request.
95
+ if (options?.alt && options.alt.trim() !== '') {
96
+ form.append('alt', options.alt);
97
+ }
98
+ try {
99
+ const { data } = await apiClient.post('/media/assets', form, {
100
+ signal: options?.signal,
101
+ onUploadProgress: (event) => {
102
+ // `total` is absent on some proxies/streams; without it a
103
+ // percentage would be a lie, so report nothing and let the
104
+ // caller keep showing indeterminate progress.
105
+ if (!event.total)
106
+ return;
107
+ options?.onProgress?.(Math.round((event.loaded / event.total) * 100));
108
+ }
109
+ });
110
+ return toMediaAsset(data);
111
+ }
112
+ catch (error) {
113
+ throw toApiError(error);
114
+ }
115
+ },
116
+ async renameAsset({ id, name }) {
117
+ try {
118
+ await apiClient.patch(`/media/assets/${id}`, { name });
119
+ }
120
+ catch (error) {
121
+ throw toApiError(error);
122
+ }
123
+ },
124
+ async updateAsset({ id, alt }) {
125
+ try {
126
+ await apiClient.patch(`/media/assets/${id}`, { alt });
127
+ }
128
+ catch (error) {
129
+ throw toApiError(error);
130
+ }
131
+ },
132
+ async moveAssets({ ids, folderId }) {
133
+ const target = folderId === ROOT_FOLDER_ID ? null : folderId;
134
+ try {
135
+ await Promise.all(ids.map((id) => apiClient.patch(`/media/assets/${id}`, { folderId: target })));
136
+ }
137
+ catch (error) {
138
+ throw toApiError(error);
139
+ }
140
+ },
141
+ async duplicateAssets(ids) {
142
+ try {
143
+ await Promise.all(ids.map((id) => apiClient.post(`/media/assets/${id}/duplicate`)));
144
+ }
145
+ catch (error) {
146
+ throw toApiError(error);
147
+ }
148
+ },
149
+ async deleteAssets(ids) {
150
+ try {
151
+ await apiClient.delete('/media/assets', { data: { ids } });
152
+ }
153
+ catch (error) {
154
+ throw toApiError(error);
155
+ }
156
+ }
157
+ };
@@ -0,0 +1,8 @@
1
+ import type { MediaInsightsGateway } from '../mediaInsightsGateway';
2
+ /**
3
+ * HTTP implementation of {@link MediaInsightsGateway} over the shared
4
+ * `apiClient` — same-origin, cookie-authed, implicitly workspace-scoped via the
5
+ * ambient `X-Workspace-Id` header. Failures normalise to `ApiError`.
6
+ */
7
+ export declare const httpMediaInsightsGateway: MediaInsightsGateway;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/infrastructure/httpMediaInsightsGateway/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAER,oBAAoB,EAGvB,MAAM,yBAAyB,CAAC;AAEjC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,oBAkCtC,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { apiClient, toApiError } from '@orthacms/utils-admin';
2
+ /**
3
+ * HTTP implementation of {@link MediaInsightsGateway} over the shared
4
+ * `apiClient` — same-origin, cookie-authed, implicitly workspace-scoped via the
5
+ * ambient `X-Workspace-Id` header. Failures normalise to `ApiError`.
6
+ */
7
+ export const httpMediaInsightsGateway = {
8
+ async storage() {
9
+ try {
10
+ const { data } = await apiClient.get('/insights/media/storage');
11
+ return data;
12
+ }
13
+ catch (error) {
14
+ throw toApiError(error);
15
+ }
16
+ },
17
+ async uploads(days) {
18
+ try {
19
+ const { data } = await apiClient.get('/insights/media/uploads', { params: { days } });
20
+ return data;
21
+ }
22
+ catch (error) {
23
+ throw toApiError(error);
24
+ }
25
+ },
26
+ async altCoverage() {
27
+ try {
28
+ const { data } = await apiClient.get('/insights/media/alt');
29
+ return data;
30
+ }
31
+ catch (error) {
32
+ throw toApiError(error);
33
+ }
34
+ }
35
+ };