@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,99 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+ import { Button, Progress } from '@orthacms/design-system';
5
+ import { UploadCloud, X } from 'lucide-react';
6
+ import { UPLOAD_STATUS } from '../../constants';
7
+ import { MediaUploadRow } from './MediaUploadRow';
8
+ /** Intl descriptors for {@link MediaUploadBanner}, co-located. */
9
+ const messages = defineMessages({
10
+ region: { id: 'media.uploadBanner.region', defaultMessage: 'Uploads' },
11
+ uploading: {
12
+ id: 'media.uploadBanner.uploading',
13
+ defaultMessage: 'Uploading {done} of {total} · {percent}%'
14
+ },
15
+ complete: {
16
+ id: 'media.uploadBanner.complete',
17
+ defaultMessage: '{count, plural, one {# file uploaded} other {# files uploaded}}'
18
+ },
19
+ someFailed: {
20
+ id: 'media.uploadBanner.someFailed',
21
+ defaultMessage: '{done, plural, one {# file uploaded} other {# files uploaded}}, {failed} failed'
22
+ },
23
+ allFailed: {
24
+ id: 'media.uploadBanner.allFailed',
25
+ defaultMessage: '{failed, plural, one {# upload failed} other {# uploads failed}}'
26
+ },
27
+ overall: {
28
+ id: 'media.uploadBanner.overall',
29
+ defaultMessage: 'Overall upload progress'
30
+ },
31
+ dismiss: { id: 'media.uploadBanner.dismiss', defaultMessage: 'Dismiss' },
32
+ /** Announced when one file in a batch fails — the row itself is silent. */
33
+ failureAnnouncement: {
34
+ id: 'media.uploadBanner.failureAnnouncement',
35
+ defaultMessage: '{name} failed to upload. {reason}'
36
+ }
37
+ });
38
+ /**
39
+ * The upload progress banner — the batch headline ("Uploading 3 of 5 · 47%"), an
40
+ * overall size-weighted bar while anything is running, and one
41
+ * {@link MediaUploadRow} per file. It sits above the browser rather than inside
42
+ * the upload dialog, so the dialog can close on submit and the user keeps
43
+ * browsing (and can queue more) while bytes move.
44
+ *
45
+ * `role="status"` sits on the **headline alone**, not the whole banner: a polite
46
+ * live region re-announces its entire contents on every change, so wrapping the
47
+ * file list would read all N rows aloud on each progress tick. Scoped to the
48
+ * headline, a screen-reader user hears "Uploading 3 of 5 · 47%" and then the
49
+ * completion, while per-file detail stays available to read on demand.
50
+ *
51
+ * Renders nothing when the queue is empty.
52
+ */
53
+ export function MediaUploadBanner({ items, summary, onRetry, onCancel, onDismiss }) {
54
+ const intl = useIntl();
55
+ // Failures are announced from their own region. The headline's counts move
56
+ // when a file fails, but "3 of 5" never says *which* file or why — and the
57
+ // rows carrying that sentence sit outside the live region on purpose (a
58
+ // polite region over the list would re-read all N rows on every progress
59
+ // tick). One line per newly-failed file is the middle ground.
60
+ const announcedRef = useRef(new Set());
61
+ const [failureNotice, setFailureNotice] = useState('');
62
+ useEffect(() => {
63
+ const announced = announcedRef.current;
64
+ const fresh = items.filter((item) => item.status === UPLOAD_STATUS.Failed && !announced.has(item.id));
65
+ // A retry can fail again, so a row leaving `Failed` becomes announceable
66
+ // once more.
67
+ for (const item of items) {
68
+ if (item.status !== UPLOAD_STATUS.Failed)
69
+ announced.delete(item.id);
70
+ }
71
+ if (fresh.length === 0)
72
+ return;
73
+ for (const item of fresh)
74
+ announced.add(item.id);
75
+ setFailureNotice(fresh
76
+ .map((item) => intl.formatMessage(messages.failureAnnouncement, {
77
+ name: item.name,
78
+ reason: item.error ?? ''
79
+ }))
80
+ .join(' '));
81
+ }, [items, intl]);
82
+ if (items.length === 0)
83
+ return null;
84
+ const headline = summary.active
85
+ ? intl.formatMessage(messages.uploading, {
86
+ done: summary.done,
87
+ total: summary.total,
88
+ percent: summary.percent
89
+ })
90
+ : summary.failed > 0 && summary.done > 0
91
+ ? intl.formatMessage(messages.someFailed, {
92
+ done: summary.done,
93
+ failed: summary.failed
94
+ })
95
+ : summary.failed > 0
96
+ ? intl.formatMessage(messages.allFailed, { failed: summary.failed })
97
+ : intl.formatMessage(messages.complete, { count: summary.done });
98
+ return (_jsxs("section", { "aria-label": intl.formatMessage(messages.region), className: "mb-4 rounded-xl border bg-muted/40 px-4 py-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(UploadCloud, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true }), _jsx("p", { className: "min-w-0 flex-1 text-sm font-medium", role: "status", children: headline }), summary.active ? null : (_jsx(Button, { variant: "ghost", size: "icon", className: "size-7 shrink-0 shadow-none", "aria-label": intl.formatMessage(messages.dismiss), onClick: onDismiss, children: _jsx(X, { "aria-hidden": true }) }))] }), summary.active ? (_jsx(Progress, { value: summary.percent, className: "mt-2 h-1.5", "aria-label": intl.formatMessage(messages.overall) })) : null, _jsx("p", { className: "sr-only", role: "status", children: failureNotice }), _jsx("ul", { className: "mt-1 max-h-48 divide-y overflow-auto", children: items.map((item) => (_jsx(MediaUploadRow, { item: item, onRetry: onRetry, onCancel: onCancel }, item.id))) })] }));
99
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Assets added per time bucket. Columns rather than a line: uploads are discrete
3
+ * tallies, and a line between two weekly counts would imply values that were
4
+ * never measured.
5
+ */
6
+ export declare function MediaUploadsWidget(): import("react").JSX.Element;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaUploadsWidget/index.tsx"],"names":[],"mappings":"AAuDA;;;;GAIG;AACH,wBAAgB,kBAAkB,gCAqCjC"}
@@ -0,0 +1,70 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { ColumnTrend, WidgetCard } from '@orthacms/insights-admin';
4
+ import { useMediaUploads } from '../../hooks/useMediaInsights';
5
+ /** Intl descriptors for the uploads widget, co-located here. */
6
+ const messages = defineMessages({
7
+ title: {
8
+ id: 'media.insights.uploads.title',
9
+ defaultMessage: 'Uploads'
10
+ },
11
+ descriptionDay: {
12
+ id: 'media.insights.uploads.descriptionDay',
13
+ defaultMessage: 'Assets added per day'
14
+ },
15
+ descriptionWeek: {
16
+ id: 'media.insights.uploads.descriptionWeek',
17
+ defaultMessage: 'Assets added per week'
18
+ },
19
+ descriptionMonth: {
20
+ id: 'media.insights.uploads.descriptionMonth',
21
+ defaultMessage: 'Assets added per month'
22
+ },
23
+ tip: {
24
+ id: 'media.insights.uploads.tip',
25
+ defaultMessage: '{period} — {count, number} uploads'
26
+ },
27
+ ariaLabel: {
28
+ id: 'media.insights.uploads.ariaLabel',
29
+ defaultMessage: 'Assets uploaded over time, {total, number} across {points, number} periods.'
30
+ },
31
+ footer: {
32
+ id: 'media.insights.uploads.footer',
33
+ defaultMessage: '{total, number} uploaded this period.'
34
+ }
35
+ });
36
+ /** Description per bucket width, so the subtitle matches what is plotted. */
37
+ const DESCRIPTION = {
38
+ day: messages.descriptionDay,
39
+ week: messages.descriptionWeek,
40
+ month: messages.descriptionMonth
41
+ };
42
+ /** Date format per bucket width — a month bucket labelled `12 Mar` reads wrong. */
43
+ const LABEL_FORMAT = {
44
+ day: { day: 'numeric', month: 'short' },
45
+ week: { day: 'numeric', month: 'short' },
46
+ month: { month: 'short', year: '2-digit' }
47
+ };
48
+ /**
49
+ * Assets added per time bucket. Columns rather than a line: uploads are discrete
50
+ * tallies, and a line between two weekly counts would imply values that were
51
+ * never measured.
52
+ */
53
+ export function MediaUploadsWidget() {
54
+ const intl = useIntl();
55
+ const { data, isPending, isError } = useMediaUploads();
56
+ const granularity = data?.granularity ?? 'week';
57
+ const points = (data?.points ?? []).map((point) => ({
58
+ label: intl.formatDate(point.bucket, LABEL_FORMAT[granularity]),
59
+ value: point.value
60
+ }));
61
+ return (_jsx(WidgetCard, { title: intl.formatMessage(messages.title), description: intl.formatMessage(DESCRIPTION[granularity]), footer: intl.formatMessage(messages.footer, {
62
+ total: data?.total ?? 0
63
+ }), isPending: isPending, isError: isError, isEmpty: points.length === 0, skeletonRows: 5, children: _jsx(ColumnTrend, { points: points, ariaLabel: intl.formatMessage(messages.ariaLabel, {
64
+ total: data?.total ?? 0,
65
+ points: points.length
66
+ }), describe: (point) => intl.formatMessage(messages.tip, {
67
+ period: point.label,
68
+ count: point.value
69
+ }) }) }));
70
+ }
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * One selectable destination row inside {@link MoveAssetsDialog}'s radio list —
4
+ * an indented label wrapping a radio, a folder icon, and the name. The assets'
5
+ * current folder renders dimmed to signal it's a no-op target.
6
+ */
7
+ export declare function DestinationOption({ value, depth, icon, label, current }: {
8
+ /** Folder id (or root id) this option selects. */
9
+ value: string;
10
+ /** Indent level below the root. */
11
+ depth: number;
12
+ /** Leading glyph (home for root, folder otherwise). */
13
+ icon: ReactNode;
14
+ /** Folder name. */
15
+ label: string;
16
+ /** Whether this is the assets' current folder (dimmed, no-op). */
17
+ current: boolean;
18
+ }): import("react").JSX.Element;
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/MoveAssetsDialog/DestinationOption/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,KAAK,EACL,KAAK,EACL,IAAI,EACJ,KAAK,EACL,OAAO,EACV,EAAE;IACC,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,IAAI,EAAE,SAAS,CAAC;IAChB,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;CACpB,+BAeA"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Label, RadioGroupItem, cn } from '@orthacms/design-system';
3
+ /**
4
+ * One selectable destination row inside {@link MoveAssetsDialog}'s radio list —
5
+ * an indented label wrapping a radio, a folder icon, and the name. The assets'
6
+ * current folder renders dimmed to signal it's a no-op target.
7
+ */
8
+ export function DestinationOption({ value, depth, icon, label, current }) {
9
+ return (_jsxs(Label, { htmlFor: `media-move-${value}`, className: cn('flex cursor-pointer items-center gap-2 rounded-md px-2 py-2 text-sm font-normal hover:bg-accent', current && 'opacity-60'), style: { paddingLeft: `${depth * 16 + 8}px` }, children: [_jsx(RadioGroupItem, { id: `media-move-${value}`, value: value }), icon, _jsx("span", { className: "truncate", children: label })] }));
10
+ }
@@ -0,0 +1,18 @@
1
+ import type { MediaFolder } from '../../types/mediaFolder';
2
+ /**
3
+ * Modal that picks a destination folder for the given assets. Offers the root
4
+ * ("All media") plus every folder as an indented radio list; the confirm is
5
+ * disabled until a destination other than the assets' current folder is chosen.
6
+ * The parent owns `open` and performs the move in `onMove`.
7
+ */
8
+ export declare function MoveAssetsDialog({ open, onOpenChange, folders, count, currentFolderId, onMove }: {
9
+ open: boolean;
10
+ onOpenChange: (open: boolean) => void;
11
+ folders: MediaFolder[];
12
+ /** Number of assets being moved (for the description). */
13
+ count: number;
14
+ /** Folder the assets currently live in — preselected, and a no-op target. */
15
+ currentFolderId: string;
16
+ onMove: (folderId: string) => void;
17
+ }): import("react").JSX.Element;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MoveAssetsDialog/index.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAiB3D;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,KAAK,EACL,eAAe,EACf,MAAM,EACT,EAAE;IACC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,+BA6EA"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, RadioGroup } from '@orthacms/design-system';
5
+ import { Folder, Home } from 'lucide-react';
6
+ import { ROOT_FOLDER_ID } from '../../constants';
7
+ import { flattenFolderTree } from '../../utils/folderTree';
8
+ import { DestinationOption } from './DestinationOption';
9
+ /** Intl descriptors for {@link MoveAssetsDialog}, co-located. */
10
+ const messages = defineMessages({
11
+ title: { id: 'media.move.title', defaultMessage: 'Move items' },
12
+ description: {
13
+ id: 'media.move.description',
14
+ defaultMessage: 'Choose a destination for {count, plural, one {# item} other {# items}}.'
15
+ },
16
+ root: { id: 'media.move.root', defaultMessage: 'All media' },
17
+ cancel: { id: 'media.move.cancel', defaultMessage: 'Cancel' },
18
+ move: { id: 'media.move.confirm', defaultMessage: 'Move here' }
19
+ });
20
+ /**
21
+ * Modal that picks a destination folder for the given assets. Offers the root
22
+ * ("All media") plus every folder as an indented radio list; the confirm is
23
+ * disabled until a destination other than the assets' current folder is chosen.
24
+ * The parent owns `open` and performs the move in `onMove`.
25
+ */
26
+ export function MoveAssetsDialog({ open, onOpenChange, folders, count, currentFolderId, onMove }) {
27
+ const intl = useIntl();
28
+ const [target, setTarget] = useState(currentFolderId);
29
+ useEffect(() => {
30
+ if (open)
31
+ setTarget(currentFolderId);
32
+ }, [open, currentFolderId]);
33
+ const tree = flattenFolderTree(folders);
34
+ const submit = () => {
35
+ if (target === currentFolderId)
36
+ return;
37
+ onMove(target);
38
+ onOpenChange(false);
39
+ };
40
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: intl.formatMessage(messages.title) }), _jsx(DialogDescription, { children: intl.formatMessage(messages.description, { count }) })] }), _jsxs(RadioGroup, { value: target, onValueChange: setTarget, className: "max-h-72 gap-0.5 overflow-auto rounded-lg border p-1", children: [_jsx(DestinationOption, { value: ROOT_FOLDER_ID, depth: 0, icon: _jsx(Home, { className: "size-4 text-muted-foreground", "aria-hidden": true }), label: intl.formatMessage(messages.root), current: currentFolderId === ROOT_FOLDER_ID }), tree.map(({ folder, depth }) => (_jsx(DestinationOption, { value: folder.id, depth: depth + 1, icon: _jsx(Folder, { className: "size-4 text-muted-foreground", "aria-hidden": true }), label: folder.name, current: currentFolderId === folder.id }, folder.id)))] }), _jsxs(DialogFooter, { className: "mt-2", children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: intl.formatMessage(messages.cancel) }), _jsx(Button, { onClick: submit, disabled: target === currentFolderId, children: intl.formatMessage(messages.move) })] })] }) }));
41
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Modal that collects a name and creates a folder in the current location. The
3
+ * parent owns `open` and performs the create in `onCreate`; the field seeds
4
+ * empty on each open and the confirm is disabled until it is non-blank.
5
+ */
6
+ export declare function NewFolderDialog({ open, onOpenChange, locationLabel, onCreate }: {
7
+ open: boolean;
8
+ onOpenChange: (open: boolean) => void;
9
+ /** Human name of the folder the new one lands in (e.g. "All media"). */
10
+ locationLabel: string;
11
+ onCreate: (name: string) => void;
12
+ }): import("react").JSX.Element;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/NewFolderDialog/index.tsx"],"names":[],"mappings":"AA8BA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,aAAa,EACb,QAAQ,EACX,EAAE;IACC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,+BA2DA"}
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, InputField } from '@orthacms/design-system';
5
+ /** Intl descriptors for {@link NewFolderDialog}, co-located. */
6
+ const messages = defineMessages({
7
+ title: { id: 'media.newFolder.title', defaultMessage: 'New folder' },
8
+ description: {
9
+ id: 'media.newFolder.description',
10
+ defaultMessage: 'Folders help you organise assets. It will be created in {location}.'
11
+ },
12
+ label: { id: 'media.newFolder.label', defaultMessage: 'Folder name' },
13
+ placeholder: {
14
+ id: 'media.newFolder.placeholder',
15
+ defaultMessage: 'e.g. Campaign assets'
16
+ },
17
+ cancel: { id: 'media.newFolder.cancel', defaultMessage: 'Cancel' },
18
+ create: { id: 'media.newFolder.create', defaultMessage: 'Create folder' }
19
+ });
20
+ /**
21
+ * Modal that collects a name and creates a folder in the current location. The
22
+ * parent owns `open` and performs the create in `onCreate`; the field seeds
23
+ * empty on each open and the confirm is disabled until it is non-blank.
24
+ */
25
+ export function NewFolderDialog({ open, onOpenChange, locationLabel, onCreate }) {
26
+ const intl = useIntl();
27
+ const [name, setName] = useState('');
28
+ useEffect(() => {
29
+ if (open)
30
+ setName('');
31
+ }, [open]);
32
+ const trimmed = name.trim();
33
+ const submit = () => {
34
+ if (!trimmed)
35
+ return;
36
+ onCreate(trimmed);
37
+ onOpenChange(false);
38
+ };
39
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: intl.formatMessage(messages.title) }), _jsx(DialogDescription, { children: intl.formatMessage(messages.description, {
40
+ location: locationLabel
41
+ }) })] }), _jsxs("form", { onSubmit: (event) => {
42
+ event.preventDefault();
43
+ submit();
44
+ }, children: [_jsx(InputField, { id: "media-new-folder-name", label: intl.formatMessage(messages.label), placeholder: intl.formatMessage(messages.placeholder), value: name, autoFocus: true, onChange: (event) => setName(event.target.value) }), _jsxs(DialogFooter, { className: "mt-6", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: intl.formatMessage(messages.cancel) }), _jsx(Button, { type: "submit", disabled: !trimmed, children: intl.formatMessage(messages.create) })] })] })] }) }));
45
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * A single-field rename modal reused for assets and folders. The parent supplies
3
+ * the current name and performs the rename in `onRename`; the field seeds from
4
+ * `currentName` each time it opens and the confirm is disabled while blank or
5
+ * unchanged. Selecting the text on open makes an in-place edit quick.
6
+ */
7
+ export declare function RenameDialog({ open, onOpenChange, currentName, onRename }: {
8
+ open: boolean;
9
+ onOpenChange: (open: boolean) => void;
10
+ currentName: string;
11
+ onRename: (name: string) => void;
12
+ }): import("react").JSX.Element;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/RenameDialog/index.tsx"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,QAAQ,EACX,EAAE;IACC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,+BAuDA"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+ import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, InputField } from '@orthacms/design-system';
5
+ /** Intl descriptors for {@link RenameDialog}, co-located. */
6
+ const messages = defineMessages({
7
+ title: { id: 'media.rename.title', defaultMessage: 'Rename' },
8
+ label: { id: 'media.rename.label', defaultMessage: 'Name' },
9
+ cancel: { id: 'media.rename.cancel', defaultMessage: 'Cancel' },
10
+ save: { id: 'media.rename.save', defaultMessage: 'Save' }
11
+ });
12
+ /**
13
+ * A single-field rename modal reused for assets and folders. The parent supplies
14
+ * the current name and performs the rename in `onRename`; the field seeds from
15
+ * `currentName` each time it opens and the confirm is disabled while blank or
16
+ * unchanged. Selecting the text on open makes an in-place edit quick.
17
+ */
18
+ export function RenameDialog({ open, onOpenChange, currentName, onRename }) {
19
+ const intl = useIntl();
20
+ const [name, setName] = useState(currentName);
21
+ useEffect(() => {
22
+ if (open)
23
+ setName(currentName);
24
+ }, [open, currentName]);
25
+ const trimmed = name.trim();
26
+ const changed = trimmed.length > 0 && trimmed !== currentName;
27
+ const submit = () => {
28
+ if (!changed)
29
+ return;
30
+ onRename(trimmed);
31
+ onOpenChange(false);
32
+ };
33
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: intl.formatMessage(messages.title) }) }), _jsxs("form", { onSubmit: (event) => {
34
+ event.preventDefault();
35
+ submit();
36
+ }, children: [_jsx(InputField, { id: "media-rename-name", label: intl.formatMessage(messages.label), value: name, autoFocus: true, onFocus: (event) => event.target.select(), onChange: (event) => setName(event.target.value) }), _jsxs(DialogFooter, { className: "mt-6", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: intl.formatMessage(messages.cancel) }), _jsx(Button, { type: "submit", disabled: !changed, children: intl.formatMessage(messages.save) })] })] })] }) }));
37
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * One staged (not yet uploaded) file in the upload dialog: a real thumbnail for
3
+ * an image, a kind glyph for anything else, the name + size, a remove button,
4
+ * and — when the caller collects it — an **Alt text** input for an image.
5
+ *
6
+ * Alt is asked for *here* rather than left to a later edit because this is the
7
+ * one moment the author is looking at the picture. The field is optional and
8
+ * never blocks the upload: an author who skips it lands where they landed
9
+ * before, and can still describe the asset from its detail drawer.
10
+ *
11
+ * The thumbnail is an **object URL** over the local `File` — no upload, no
12
+ * network. It is revoked when the row unmounts or the file changes; skipping
13
+ * that leaks the whole blob for the tab's lifetime, which matters when someone
14
+ * stages a few hundred megabytes of video and then removes it.
15
+ */
16
+ export declare function StagedFileRow({ file, alt, collectAlt, onAltChange, onRemove }: {
17
+ file: File;
18
+ /** Current alt text for this staged file. */
19
+ alt?: string;
20
+ /** Renders the alt input (images only); off for callers that drop it. */
21
+ collectAlt?: boolean;
22
+ onAltChange?: (alt: string) => void;
23
+ onRemove: () => void;
24
+ }): import("react").JSX.Element;
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/UploadDialog/StagedFileRow/index.tsx"],"names":[],"mappings":"AAiDA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,EAC1B,IAAI,EACJ,GAAQ,EACR,UAAkB,EAClB,WAAW,EACX,QAAQ,EACX,EAAE;IACC,IAAI,EAAE,IAAI,CAAC;IACX,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB,+BAyEA"}
@@ -0,0 +1,82 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useState } from 'react';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+ import { Button, InputField } from '@orthacms/design-system';
5
+ import { FileText, Film, Music, Package, X } from 'lucide-react';
6
+ import { formatBytes } from '../../../utils/formatBytes';
7
+ /** Intl descriptors for {@link StagedFileRow}, co-located. */
8
+ const messages = defineMessages({
9
+ remove: { id: 'media.upload.removeFile', defaultMessage: 'Remove {name}' },
10
+ altLabel: { id: 'media.upload.altLabel', defaultMessage: 'Alt text' },
11
+ /**
12
+ * The accessible name, which repeats the file so a run of staged rows isn't
13
+ * five identically-named inputs. It **contains** the visible label, which is
14
+ * what WCAG 2.5.3 (Label in Name) asks for.
15
+ */
16
+ altLabelFor: {
17
+ id: 'media.upload.altLabelFor',
18
+ defaultMessage: 'Alt text for {name}'
19
+ },
20
+ altPlaceholder: {
21
+ id: 'media.upload.altPlaceholder',
22
+ defaultMessage: 'Describe this image'
23
+ },
24
+ altHint: {
25
+ id: 'media.upload.altHint',
26
+ defaultMessage: 'Optional. Leave blank only if the image is decorative — you can add it later from the asset’s details.'
27
+ }
28
+ });
29
+ /**
30
+ * The glyph shown for a non-image file, chosen from its MIME type. Deliberately
31
+ * a **local** coarse mapping and not a shared `kindFromMime`: the server owns
32
+ * the real classification (it's what ends up on the row), and this only has to
33
+ * pick an icon for a file that hasn't been uploaded yet.
34
+ */
35
+ function glyphFor(type) {
36
+ if (type.startsWith('video/'))
37
+ return Film;
38
+ if (type.startsWith('audio/'))
39
+ return Music;
40
+ if (type === 'application/zip' ||
41
+ type === 'application/x-tar' ||
42
+ type === 'application/gzip') {
43
+ return Package;
44
+ }
45
+ return FileText;
46
+ }
47
+ /**
48
+ * One staged (not yet uploaded) file in the upload dialog: a real thumbnail for
49
+ * an image, a kind glyph for anything else, the name + size, a remove button,
50
+ * and — when the caller collects it — an **Alt text** input for an image.
51
+ *
52
+ * Alt is asked for *here* rather than left to a later edit because this is the
53
+ * one moment the author is looking at the picture. The field is optional and
54
+ * never blocks the upload: an author who skips it lands where they landed
55
+ * before, and can still describe the asset from its detail drawer.
56
+ *
57
+ * The thumbnail is an **object URL** over the local `File` — no upload, no
58
+ * network. It is revoked when the row unmounts or the file changes; skipping
59
+ * that leaks the whole blob for the tab's lifetime, which matters when someone
60
+ * stages a few hundred megabytes of video and then removes it.
61
+ */
62
+ export function StagedFileRow({ file, alt = '', collectAlt = false, onAltChange, onRemove }) {
63
+ const intl = useIntl();
64
+ const altId = useId();
65
+ const [previewUrl, setPreviewUrl] = useState(null);
66
+ const isImage = file.type.startsWith('image/');
67
+ useEffect(() => {
68
+ if (!isImage) {
69
+ setPreviewUrl(null);
70
+ return;
71
+ }
72
+ const url = URL.createObjectURL(file);
73
+ setPreviewUrl(url);
74
+ return () => URL.revokeObjectURL(url);
75
+ }, [file, isImage]);
76
+ const Glyph = glyphFor(file.type);
77
+ return (_jsxs("li", { className: "flex flex-col gap-2 rounded-lg border bg-background px-3 py-2 text-sm", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted", children: previewUrl ? (_jsx("img", { src: previewUrl, alt: "", className: "size-full object-cover" })) : (_jsx(Glyph, { className: "size-4 text-muted-foreground", "aria-hidden": true })) }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: file.name }), _jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: formatBytes(file.size) }), _jsx(Button, { variant: "ghost", size: "icon", className: "size-7 shrink-0 shadow-none", "aria-label": intl.formatMessage(messages.remove, {
78
+ name: file.name
79
+ }), onClick: onRemove, children: _jsx(X, { "aria-hidden": true }) })] }), collectAlt && isImage ? (_jsx(InputField, { id: `staged-alt-${altId}`, label: intl.formatMessage(messages.altLabel), "aria-label": intl.formatMessage(messages.altLabelFor, {
80
+ name: file.name
81
+ }), description: intl.formatMessage(messages.altHint), placeholder: intl.formatMessage(messages.altPlaceholder), value: alt, maxLength: 1000, onChange: (event) => onAltChange?.(event.target.value), fieldClassName: "gap-1", className: "h-8 text-sm" })) : null] }));
82
+ }
@@ -0,0 +1,50 @@
1
+ import type { StagedUpload } from '../../infrastructure/mediaGateway';
2
+ /**
3
+ * The upload modal — a drag-and-drop zone plus a multi-file picker that stages
4
+ * the chosen files into a removable, **previewed** list before committing:
5
+ * images render a real thumbnail off a local object URL (see
6
+ * {@link StagedFileRow}), other kinds a glyph, so a wrong file is caught before
7
+ * a byte moves. Files accumulate across several drops/picks.
8
+ *
9
+ * On confirm the parent's `onUpload` receives the real `File` objects and the
10
+ * dialog closes immediately — progress then lives in the caller's upload banner,
11
+ * not here, so the user can keep browsing (and queue more) while bytes move.
12
+ * Staged files reset whenever the dialog reopens, seeded from `initialFiles`.
13
+ *
14
+ * **Shared by both upload entry points** — the Media Library's toolbar and a
15
+ * content record's media field — so staging, previewing, and the copy are
16
+ * identical in the two places. The field narrows it with `multiple` / `accept` /
17
+ * its own `description` + `hint`; everything else is the same dialog.
18
+ */
19
+ export declare function UploadDialog({ open, onOpenChange, locationLabel, description, hint, confirmLabel, accept, multiple, initialFiles, collectAlt, onUpload }: {
20
+ open: boolean;
21
+ onOpenChange: (open: boolean) => void;
22
+ /** Human name of the destination folder (e.g. "All media"). */
23
+ locationLabel: string;
24
+ /** Overrides the default "Files upload to {location}." sentence. */
25
+ description?: string;
26
+ /** Overrides the default size/type hint under the browse button. */
27
+ hint?: string;
28
+ /**
29
+ * Overrides the confirm button's "Upload {n} files". A media field stages the
30
+ * files (they upload with the record), so there the button attaches.
31
+ */
32
+ confirmLabel?: string;
33
+ /** `accept` attribute for the file input (best-effort; the server enforces). */
34
+ accept?: string;
35
+ /** False stages exactly one file — a later pick replaces it. */
36
+ multiple?: boolean;
37
+ /** Files to stage as the dialog opens (e.g. dropped onto a media field). */
38
+ initialFiles?: File[];
39
+ /**
40
+ * Offers an **Alt text** input on each staged image, sent with the upload.
41
+ *
42
+ * Off by default, and deliberately: only the library's queue forwards the
43
+ * description to the API. A caller that would drop it (a media field, whose
44
+ * files upload later with the record) must not ask for it — prompting for a
45
+ * description and then discarding it is worse than never asking.
46
+ */
47
+ collectAlt?: boolean;
48
+ onUpload: (uploads: StagedUpload[]) => void;
49
+ }): import("react").JSX.Element;
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/UploadDialog/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAmDtE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,aAAa,EACb,WAAW,EACX,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,QAAe,EACf,YAAuB,EACvB,UAAkB,EAClB,QAAQ,EACX,EAAE;IACC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,+DAA+D;IAC/D,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;CAC/C,+BAmJA"}