@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.
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/components/AssetActionsMenu/index.d.ts +37 -0
- package/dist/lib/components/AssetActionsMenu/index.d.ts.map +1 -0
- package/dist/lib/components/AssetActionsMenu/index.js +57 -0
- package/dist/lib/components/AssetDetailDrawer/MetaRow/index.d.ts +11 -0
- package/dist/lib/components/AssetDetailDrawer/MetaRow/index.d.ts.map +1 -0
- package/dist/lib/components/AssetDetailDrawer/MetaRow/index.js +9 -0
- package/dist/lib/components/AssetDetailDrawer/index.d.ts +28 -0
- package/dist/lib/components/AssetDetailDrawer/index.d.ts.map +1 -0
- package/dist/lib/components/AssetDetailDrawer/index.js +93 -0
- package/dist/lib/components/EntryMediaTab/MediaFieldSection/index.d.ts +28 -0
- package/dist/lib/components/EntryMediaTab/MediaFieldSection/index.d.ts.map +1 -0
- package/dist/lib/components/EntryMediaTab/MediaFieldSection/index.js +52 -0
- package/dist/lib/components/EntryMediaTab/index.d.ts +11 -0
- package/dist/lib/components/EntryMediaTab/index.d.ts.map +1 -0
- package/dist/lib/components/EntryMediaTab/index.js +31 -0
- package/dist/lib/components/FolderActionsMenu/index.d.ts +21 -0
- package/dist/lib/components/FolderActionsMenu/index.d.ts.map +1 -0
- package/dist/lib/components/FolderActionsMenu/index.js +25 -0
- package/dist/lib/components/MediaAltTextWidget/index.d.ts +9 -0
- package/dist/lib/components/MediaAltTextWidget/index.d.ts.map +1 -0
- package/dist/lib/components/MediaAltTextWidget/index.js +66 -0
- package/dist/lib/components/MediaBreadcrumbs/index.d.ts +11 -0
- package/dist/lib/components/MediaBreadcrumbs/index.d.ts.map +1 -0
- package/dist/lib/components/MediaEmptyState/index.d.ts +13 -0
- package/dist/lib/components/MediaEmptyState/index.d.ts.map +1 -0
- package/dist/lib/components/MediaEmptyState/index.js +38 -0
- package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.d.ts +34 -0
- package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.d.ts.map +1 -0
- package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.js +117 -0
- package/dist/lib/components/MediaFieldControl/index.d.ts +61 -0
- package/dist/lib/components/MediaFieldControl/index.d.ts.map +1 -0
- package/dist/lib/components/MediaFieldControl/index.js +422 -0
- package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.d.ts +17 -0
- package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.d.ts.map +1 -0
- package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.js +37 -0
- package/dist/lib/components/MediaFoldersNav/index.d.ts +22 -0
- package/dist/lib/components/MediaFoldersNav/index.d.ts.map +1 -0
- package/dist/lib/components/MediaFoldersNav/index.js +40 -0
- package/dist/lib/components/MediaGrid/MediaAssetCard/index.d.ts +18 -0
- package/dist/lib/components/MediaGrid/MediaAssetCard/index.d.ts.map +1 -0
- package/dist/lib/components/MediaGrid/MediaAssetCard/index.js +29 -0
- package/dist/lib/components/MediaGrid/MediaFolderCard/index.d.ts +16 -0
- package/dist/lib/components/MediaGrid/MediaFolderCard/index.d.ts.map +1 -0
- package/dist/lib/components/MediaGrid/MediaFolderCard/index.js +25 -0
- package/dist/lib/components/MediaGrid/index.d.ts +24 -0
- package/dist/lib/components/MediaGrid/index.d.ts.map +1 -0
- package/dist/lib/components/MediaGrid/index.js +20 -0
- package/dist/lib/components/MediaKindBadge/index.d.ts +10 -0
- package/dist/lib/components/MediaKindBadge/index.d.ts.map +1 -0
- package/dist/lib/components/MediaKindBadge/index.js +30 -0
- package/dist/lib/components/MediaKindIcon/index.d.ts +11 -0
- package/dist/lib/components/MediaKindIcon/index.d.ts.map +1 -0
- package/dist/lib/components/MediaKindIcon/index.js +20 -0
- package/dist/lib/components/MediaLibrarySkeleton/index.d.ts +18 -0
- package/dist/lib/components/MediaLibrarySkeleton/index.d.ts.map +1 -0
- package/dist/lib/components/MediaLibrarySkeleton/index.js +43 -0
- package/dist/lib/components/MediaPagination/index.d.ts +26 -0
- package/dist/lib/components/MediaPagination/index.d.ts.map +1 -0
- package/dist/lib/components/MediaPagination/index.js +52 -0
- package/dist/lib/components/MediaPickerDialog/MediaPickerTile/index.d.ts +17 -0
- package/dist/lib/components/MediaPickerDialog/MediaPickerTile/index.d.ts.map +1 -0
- package/dist/lib/components/MediaPickerDialog/MediaPickerTile/index.js +43 -0
- package/dist/lib/components/MediaPickerDialog/index.d.ts +21 -0
- package/dist/lib/components/MediaPickerDialog/index.d.ts.map +1 -0
- package/dist/lib/components/MediaPickerDialog/index.js +208 -0
- package/dist/lib/components/MediaSelectionBar/index.d.ts +18 -0
- package/dist/lib/components/MediaSelectionBar/index.d.ts.map +1 -0
- package/dist/lib/components/MediaSelectionBar/index.js +26 -0
- package/dist/lib/components/MediaStorageStat/index.d.ts +9 -0
- package/dist/lib/components/MediaStorageStat/index.d.ts.map +1 -0
- package/dist/lib/components/MediaStorageStat/index.js +31 -0
- package/dist/lib/components/MediaStorageWidget/index.d.ts +10 -0
- package/dist/lib/components/MediaStorageWidget/index.d.ts.map +1 -0
- package/dist/lib/components/MediaStorageWidget/index.js +93 -0
- package/dist/lib/components/MediaTable/MediaAssetRow/index.d.ts +17 -0
- package/dist/lib/components/MediaTable/MediaAssetRow/index.d.ts.map +1 -0
- package/dist/lib/components/MediaTable/MediaFolderRow/index.d.ts +16 -0
- package/dist/lib/components/MediaTable/MediaFolderRow/index.d.ts.map +1 -0
- package/dist/lib/components/MediaTable/index.d.ts +26 -0
- package/dist/lib/components/MediaTable/index.d.ts.map +1 -0
- package/dist/lib/components/MediaThumbnail/index.d.ts +19 -0
- package/dist/lib/components/MediaThumbnail/index.d.ts.map +1 -0
- package/dist/lib/components/MediaThumbnail/index.js +26 -0
- package/dist/lib/components/MediaToolbar/index.d.ts +19 -0
- package/dist/lib/components/MediaToolbar/index.d.ts.map +1 -0
- package/dist/lib/components/MediaToolbar/index.js +62 -0
- package/dist/lib/components/MediaTopBar/index.d.ts +20 -0
- package/dist/lib/components/MediaTopBar/index.d.ts.map +1 -0
- package/dist/lib/components/MediaTopBar/index.js +35 -0
- package/dist/lib/components/MediaUploadBanner/MediaUploadRow/index.d.ts +14 -0
- package/dist/lib/components/MediaUploadBanner/MediaUploadRow/index.d.ts.map +1 -0
- package/dist/lib/components/MediaUploadBanner/MediaUploadRow/index.js +51 -0
- package/dist/lib/components/MediaUploadBanner/index.d.ts +24 -0
- package/dist/lib/components/MediaUploadBanner/index.d.ts.map +1 -0
- package/dist/lib/components/MediaUploadBanner/index.js +99 -0
- package/dist/lib/components/MediaUploadsWidget/index.d.ts +7 -0
- package/dist/lib/components/MediaUploadsWidget/index.d.ts.map +1 -0
- package/dist/lib/components/MediaUploadsWidget/index.js +70 -0
- package/dist/lib/components/MoveAssetsDialog/DestinationOption/index.d.ts +19 -0
- package/dist/lib/components/MoveAssetsDialog/DestinationOption/index.d.ts.map +1 -0
- package/dist/lib/components/MoveAssetsDialog/DestinationOption/index.js +10 -0
- package/dist/lib/components/MoveAssetsDialog/index.d.ts +18 -0
- package/dist/lib/components/MoveAssetsDialog/index.d.ts.map +1 -0
- package/dist/lib/components/MoveAssetsDialog/index.js +41 -0
- package/dist/lib/components/NewFolderDialog/index.d.ts +13 -0
- package/dist/lib/components/NewFolderDialog/index.d.ts.map +1 -0
- package/dist/lib/components/NewFolderDialog/index.js +45 -0
- package/dist/lib/components/RenameDialog/index.d.ts +13 -0
- package/dist/lib/components/RenameDialog/index.d.ts.map +1 -0
- package/dist/lib/components/RenameDialog/index.js +37 -0
- package/dist/lib/components/UploadDialog/StagedFileRow/index.d.ts +25 -0
- package/dist/lib/components/UploadDialog/StagedFileRow/index.d.ts.map +1 -0
- package/dist/lib/components/UploadDialog/StagedFileRow/index.js +82 -0
- package/dist/lib/components/UploadDialog/index.d.ts +50 -0
- package/dist/lib/components/UploadDialog/index.d.ts.map +1 -0
- package/dist/lib/components/UploadDialog/index.js +120 -0
- package/dist/lib/components/WysiwygLibrarySource/index.d.ts +14 -0
- package/dist/lib/components/WysiwygLibrarySource/index.d.ts.map +1 -0
- package/dist/lib/components/WysiwygLibrarySource/index.js +28 -0
- package/dist/lib/components/WysiwygUploadSource/index.d.ts +25 -0
- package/dist/lib/components/WysiwygUploadSource/index.d.ts.map +1 -0
- package/dist/lib/components/WysiwygUploadSource/index.js +102 -0
- package/dist/lib/constants/index.d.ts +82 -0
- package/dist/lib/constants/index.d.ts.map +1 -0
- package/dist/lib/constants/index.js +75 -0
- package/dist/lib/hooks/useMediaInsights/index.d.ts +32 -0
- package/dist/lib/hooks/useMediaInsights/index.d.ts.map +1 -0
- package/dist/lib/hooks/useMediaInsights/index.js +85 -0
- package/dist/lib/hooks/useMediaLibrary/index.d.ts +83 -0
- package/dist/lib/hooks/useMediaLibrary/index.d.ts.map +1 -0
- package/dist/lib/hooks/useMediaLibrary/index.js +341 -0
- package/dist/lib/hooks/usePendingMediaUploads/index.d.ts +26 -0
- package/dist/lib/hooks/usePendingMediaUploads/index.d.ts.map +1 -0
- package/dist/lib/hooks/usePendingMediaUploads/index.js +217 -0
- package/dist/lib/hooks/useUploadQueue/index.d.ts +33 -0
- package/dist/lib/hooks/useUploadQueue/index.d.ts.map +1 -0
- package/dist/lib/hooks/useUploadQueue/index.js +218 -0
- package/dist/lib/hooks/useWysiwygAssetPicker/index.d.ts +23 -0
- package/dist/lib/hooks/useWysiwygAssetPicker/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/apiMessage/index.d.ts +17 -0
- package/dist/lib/infrastructure/apiMessage/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/apiMessage/index.js +30 -0
- package/dist/lib/infrastructure/httpMediaGateway/index.d.ts +9 -0
- package/dist/lib/infrastructure/httpMediaGateway/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/httpMediaGateway/index.js +157 -0
- package/dist/lib/infrastructure/httpMediaInsightsGateway/index.d.ts +8 -0
- package/dist/lib/infrastructure/httpMediaInsightsGateway/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/httpMediaInsightsGateway/index.js +35 -0
- package/dist/lib/infrastructure/mediaGateway/index.d.ts +120 -0
- package/dist/lib/infrastructure/mediaGateway/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/mediaGateway/index.js +1 -0
- package/dist/lib/infrastructure/mediaInsightsGateway/index.d.ts +46 -0
- package/dist/lib/infrastructure/mediaInsightsGateway/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/mediaInsightsGateway/index.js +6 -0
- package/dist/lib/infrastructure/mediaKeys/index.d.ts +23 -0
- package/dist/lib/infrastructure/mediaKeys/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/mediaKeys/index.js +11 -0
- package/dist/lib/infrastructure/mediaMapper/index.d.ts +50 -0
- package/dist/lib/infrastructure/mediaMapper/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/mediaMapper/index.js +39 -0
- package/dist/lib/pages/MediaLibraryPage/index.d.ts +23 -0
- package/dist/lib/pages/MediaLibraryPage/index.d.ts.map +1 -0
- package/dist/lib/pages/MediaLibraryPage/index.js +425 -0
- package/dist/lib/types/mediaAsset/index.d.ts +44 -0
- package/dist/lib/types/mediaAsset/index.d.ts.map +1 -0
- package/dist/lib/types/mediaAsset/index.js +1 -0
- package/dist/lib/types/mediaFieldDisplay/index.d.ts +48 -0
- package/dist/lib/types/mediaFieldDisplay/index.d.ts.map +1 -0
- package/dist/lib/types/mediaFieldDisplay/index.js +1 -0
- package/dist/lib/types/mediaFolder/index.d.ts +16 -0
- package/dist/lib/types/mediaFolder/index.d.ts.map +1 -0
- package/dist/lib/types/mediaFolder/index.js +1 -0
- package/dist/lib/types/pendingUpload/index.d.ts +38 -0
- package/dist/lib/types/pendingUpload/index.d.ts.map +1 -0
- package/dist/lib/types/pendingUpload/index.js +1 -0
- package/dist/lib/types/upload/index.d.ts +37 -0
- package/dist/lib/types/upload/index.d.ts.map +1 -0
- package/dist/lib/types/upload/index.js +1 -0
- package/dist/lib/utils/assetGradient/index.d.ts +8 -0
- package/dist/lib/utils/assetGradient/index.d.ts.map +1 -0
- package/dist/lib/utils/assetGradient/index.js +16 -0
- package/dist/lib/utils/assetHandlers/index.d.ts +11 -0
- package/dist/lib/utils/assetHandlers/index.d.ts.map +1 -0
- package/dist/lib/utils/assetHandlers/index.js +17 -0
- package/dist/lib/utils/folderContents/index.d.ts +17 -0
- package/dist/lib/utils/folderContents/index.d.ts.map +1 -0
- package/dist/lib/utils/folderContents/index.js +35 -0
- package/dist/lib/utils/folderTree/index.d.ts +13 -0
- package/dist/lib/utils/folderTree/index.d.ts.map +1 -0
- package/dist/lib/utils/folderTree/index.js +15 -0
- package/dist/lib/utils/formatBytes/index.d.ts +6 -0
- package/dist/lib/utils/formatBytes/index.d.ts.map +1 -0
- package/dist/lib/utils/formatBytes/index.js +17 -0
- package/dist/lib/utils/formatDuration/index.d.ts +6 -0
- package/dist/lib/utils/formatDuration/index.d.ts.map +1 -0
- package/dist/lib/utils/formatDuration/index.js +13 -0
- package/dist/lib/utils/kindFromMime/index.d.ts +10 -0
- package/dist/lib/utils/kindFromMime/index.d.ts.map +1 -0
- package/dist/lib/utils/kindFromMime/index.js +27 -0
- package/dist/lib/utils/mediaAccept/index.d.ts +30 -0
- package/dist/lib/utils/mediaAccept/index.d.ts.map +1 -0
- package/dist/lib/utils/mediaAccept/index.js +63 -0
- package/dist/lib/utils/mediaPlugin/index.d.ts +15 -0
- package/dist/lib/utils/mediaPlugin/index.d.ts.map +1 -0
- package/dist/lib/utils/mediaPlugin/index.js +177 -0
- package/dist/lib/utils/toWysiwygEmbed/index.d.ts +23 -0
- package/dist/lib/utils/toWysiwygEmbed/index.d.ts.map +1 -0
- package/dist/lib/utils/toWysiwygEmbed/index.js +44 -0
- package/package.json +50 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
4
|
+
import { Button, ConfirmDialog, Drawer, DrawerContent, DrawerTitle, cn, toast } from '@orthacms/design-system';
|
|
5
|
+
import { PanelLeft } from 'lucide-react';
|
|
6
|
+
import { useHasPermission } from '@orthacms/identity-admin';
|
|
7
|
+
import { MEDIA_CREATE, MEDIA_DELETE, MEDIA_READ, MEDIA_UPDATE, ROOT_FOLDER_ID } from '../../constants';
|
|
8
|
+
import { useMediaLibrary } from '../../hooks/useMediaLibrary';
|
|
9
|
+
import { MediaFoldersNav } from '../../components/MediaFoldersNav';
|
|
10
|
+
import { MediaTopBar } from '../../components/MediaTopBar';
|
|
11
|
+
import { MediaToolbar } from '../../components/MediaToolbar';
|
|
12
|
+
import { MediaSelectionBar } from '../../components/MediaSelectionBar';
|
|
13
|
+
import { MediaUploadBanner } from '../../components/MediaUploadBanner';
|
|
14
|
+
import { MediaGrid } from '../../components/MediaGrid';
|
|
15
|
+
import { MediaPagination } from '../../components/MediaPagination';
|
|
16
|
+
import { MediaEmptyState } from '../../components/MediaEmptyState';
|
|
17
|
+
import { AssetDetailDrawer } from '../../components/AssetDetailDrawer';
|
|
18
|
+
import { NewFolderDialog } from '../../components/NewFolderDialog';
|
|
19
|
+
import { RenameDialog } from '../../components/RenameDialog';
|
|
20
|
+
import { MoveAssetsDialog } from '../../components/MoveAssetsDialog';
|
|
21
|
+
import { UploadDialog } from '../../components/UploadDialog';
|
|
22
|
+
import { folderContents } from '../../utils/folderContents';
|
|
23
|
+
import { useDocumentTitle } from '@orthacms/utils-admin';
|
|
24
|
+
/** Intl descriptors for the Media Library page + its toasts, co-located. */
|
|
25
|
+
const messages = defineMessages({
|
|
26
|
+
title: { id: 'media.page.title', defaultMessage: 'Media Library' },
|
|
27
|
+
subtitle: {
|
|
28
|
+
id: 'media.page.subtitle',
|
|
29
|
+
defaultMessage: '{count, plural, one {# item} other {# items}} in {location}'
|
|
30
|
+
},
|
|
31
|
+
allMedia: { id: 'media.page.allMedia', defaultMessage: 'All media' },
|
|
32
|
+
noAccess: {
|
|
33
|
+
id: 'media.page.noAccess',
|
|
34
|
+
defaultMessage: 'You don’t have permission to view the media library.'
|
|
35
|
+
},
|
|
36
|
+
loadError: {
|
|
37
|
+
id: 'media.page.loadError',
|
|
38
|
+
defaultMessage: 'We couldn’t load the media library.'
|
|
39
|
+
},
|
|
40
|
+
retry: { id: 'media.page.retry', defaultMessage: 'Try again' },
|
|
41
|
+
openNav: { id: 'media.page.openNav', defaultMessage: 'Folders' },
|
|
42
|
+
navTitle: {
|
|
43
|
+
id: 'media.page.navTitle',
|
|
44
|
+
defaultMessage: 'Folder navigation'
|
|
45
|
+
},
|
|
46
|
+
selection: {
|
|
47
|
+
id: 'media.page.selection',
|
|
48
|
+
defaultMessage: '{count, plural, =0 {No items selected} one {# item selected} other {# items selected}}'
|
|
49
|
+
},
|
|
50
|
+
deleteAssetsTitle: {
|
|
51
|
+
id: 'media.page.deleteAssetsTitle',
|
|
52
|
+
defaultMessage: 'Delete {count, plural, one {# item} other {# items}}?'
|
|
53
|
+
},
|
|
54
|
+
deleteAssetsBody: {
|
|
55
|
+
id: 'media.page.deleteAssetsBody',
|
|
56
|
+
defaultMessage: 'This can’t be undone. The selected assets will be permanently removed.'
|
|
57
|
+
},
|
|
58
|
+
deleteFolderTitle: {
|
|
59
|
+
id: 'media.page.deleteFolderTitle',
|
|
60
|
+
defaultMessage: 'Delete “{name}”?'
|
|
61
|
+
},
|
|
62
|
+
deleteFolderTitleFull: {
|
|
63
|
+
id: 'media.page.deleteFolderTitleFull',
|
|
64
|
+
defaultMessage: 'Delete “{name}” and everything inside?'
|
|
65
|
+
},
|
|
66
|
+
deleteFolderBody: {
|
|
67
|
+
id: 'media.page.deleteFolderBody',
|
|
68
|
+
defaultMessage: 'This folder is empty. It will be permanently removed — this can’t be undone.'
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* Names exactly what goes with the folder. Both counts are always spelled
|
|
72
|
+
* out (`=0` included) so the sentence reads the same shape however the
|
|
73
|
+
* folder is filled, and nobody has to infer that "3 assets" also means the
|
|
74
|
+
* subfolders they sit in.
|
|
75
|
+
*/
|
|
76
|
+
deleteFolderBodyFull: {
|
|
77
|
+
id: 'media.page.deleteFolderBodyFull',
|
|
78
|
+
defaultMessage: 'Deleting it also deletes {assets, plural, =0 {no assets} one {# asset} other {# assets}} and {folders, plural, =0 {no subfolders} one {# subfolder} other {# subfolders}} inside it. This can’t be undone.'
|
|
79
|
+
},
|
|
80
|
+
confirmDelete: { id: 'media.page.confirmDelete', defaultMessage: 'Delete' },
|
|
81
|
+
// toasts
|
|
82
|
+
tCreated: {
|
|
83
|
+
id: 'media.page.toast.created',
|
|
84
|
+
defaultMessage: 'Folder “{name}” created'
|
|
85
|
+
},
|
|
86
|
+
tRenamed: {
|
|
87
|
+
id: 'media.page.toast.renamed',
|
|
88
|
+
defaultMessage: 'Renamed to “{name}”'
|
|
89
|
+
},
|
|
90
|
+
tDuplicated: {
|
|
91
|
+
id: 'media.page.toast.duplicated',
|
|
92
|
+
defaultMessage: '{count, plural, one {# asset duplicated} other {# assets duplicated}}'
|
|
93
|
+
},
|
|
94
|
+
tMoved: {
|
|
95
|
+
id: 'media.page.toast.moved',
|
|
96
|
+
defaultMessage: '{count, plural, one {# asset moved} other {# assets moved}}'
|
|
97
|
+
},
|
|
98
|
+
tDeletedAssets: {
|
|
99
|
+
id: 'media.page.toast.deletedAssets',
|
|
100
|
+
defaultMessage: '{count, plural, one {# asset deleted} other {# assets deleted}}'
|
|
101
|
+
},
|
|
102
|
+
tDeletedFolder: {
|
|
103
|
+
id: 'media.page.toast.deletedFolder',
|
|
104
|
+
defaultMessage: 'Folder “{name}” deleted'
|
|
105
|
+
},
|
|
106
|
+
tDownload: {
|
|
107
|
+
id: 'media.page.toast.download',
|
|
108
|
+
defaultMessage: 'Downloading “{name}”'
|
|
109
|
+
},
|
|
110
|
+
tCopied: {
|
|
111
|
+
id: 'media.page.toast.copied',
|
|
112
|
+
defaultMessage: 'Link copied to clipboard'
|
|
113
|
+
},
|
|
114
|
+
tCopyFailed: {
|
|
115
|
+
id: 'media.page.toast.copyFailed',
|
|
116
|
+
defaultMessage: 'Couldn’t copy the link'
|
|
117
|
+
},
|
|
118
|
+
tAltSaved: {
|
|
119
|
+
id: 'media.page.toast.altSaved',
|
|
120
|
+
defaultMessage: 'Alt text saved'
|
|
121
|
+
},
|
|
122
|
+
assetsRegion: { id: 'media.page.assetsRegion', defaultMessage: 'Assets' }
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
125
|
+
* How long a confirmed delete keeps trying to hold focus on the grid.
|
|
126
|
+
*
|
|
127
|
+
* Two overlays unwind after a delete and each restores focus to its own
|
|
128
|
+
* trigger, both of which sat on the tile that just went away — so whichever
|
|
129
|
+
* settles last can drop focus on `<body>` well after the dialog closed. The
|
|
130
|
+
* window is generous because that timing moves with machine load; nothing is
|
|
131
|
+
* taken from a control the user actually focused, so a long window costs
|
|
132
|
+
* nothing.
|
|
133
|
+
*/
|
|
134
|
+
const FOCUS_RECLAIM_MS = 1_000;
|
|
135
|
+
/**
|
|
136
|
+
* The Media Library, mounted inside the workspace shell at
|
|
137
|
+
* `/workspaces/:id/media`. `useMediaLibrary` is the store — folders and the open
|
|
138
|
+
* folder's assets come from `@orthacms/media-server`, and every action (browse
|
|
139
|
+
* folders, upload, create folder, rename, duplicate, move, delete, filter, sort,
|
|
140
|
+
* open the detail drawer) runs against the API, firing a toast per action —
|
|
141
|
+
* except uploads, whose status lives in the `MediaUploadBanner`. Controls gate
|
|
142
|
+
* on the real `media:*` matrix via `useHasPermission`.
|
|
143
|
+
*
|
|
144
|
+
* A sticky `MediaTopBar` heads the page (the shared icon-tile + breadcrumb
|
|
145
|
+
* header every admin page carries), spanning both panes. Below it sits a
|
|
146
|
+
* two-pane layout — a folders sidebar beside the asset browser — plus the detail
|
|
147
|
+
* drawer and the create/rename/move/upload dialogs. Both panes sit
|
|
148
|
+
* **flush on the canvas** (no muted board, no bordered island), mirroring the
|
|
149
|
+
* Content Library's work area: the sidebar is divided by a hairline `border-r`
|
|
150
|
+
* and each pane scrolls independently. Sized with `flex-1` against the workspace
|
|
151
|
+
* shell's `min-h-svh` column — NOT its own `svh` calc — so there is exactly one
|
|
152
|
+
* viewport measurement in the chain; a second, independently rounded one can end
|
|
153
|
+
* up a pixel taller (visible at browser zoom ≠ 100%) and give the document a
|
|
154
|
+
* phantom scrollbar beside the pane's own.
|
|
155
|
+
*/
|
|
156
|
+
export function MediaLibraryPage() {
|
|
157
|
+
const intl = useIntl();
|
|
158
|
+
// Names this route in the tab strip, the window list, the history
|
|
159
|
+
// and a screen reader's window announcement. Every private route but
|
|
160
|
+
// Workspaces was still titled a bare "Admin" (WCAG 2.4.2, `ORT-140`).
|
|
161
|
+
useDocumentTitle(intl.formatMessage(messages.title));
|
|
162
|
+
// Mirror the server's RBAC: reads gate the queries, writes gate controls.
|
|
163
|
+
const canRead = useHasPermission(MEDIA_READ);
|
|
164
|
+
const canCreate = useHasPermission(MEDIA_CREATE);
|
|
165
|
+
const canUpdate = useHasPermission(MEDIA_UPDATE);
|
|
166
|
+
const canDelete = useHasPermission(MEDIA_DELETE);
|
|
167
|
+
const store = useMediaLibrary(canRead);
|
|
168
|
+
/** Focus target after a delete removes the tile whose menu opened it. */
|
|
169
|
+
const gridRef = useRef(null);
|
|
170
|
+
/** Set by a confirmed delete, read once by the dialog's close-focus hook. */
|
|
171
|
+
const focusGridOnCloseRef = useRef(false);
|
|
172
|
+
const [navOpen, setNavOpen] = useState(false);
|
|
173
|
+
const [newFolderOpen, setNewFolderOpen] = useState(false);
|
|
174
|
+
const [uploadOpen, setUploadOpen] = useState(false);
|
|
175
|
+
const [renameTarget, setRenameTarget] = useState(null);
|
|
176
|
+
const [moveIds, setMoveIds] = useState(null);
|
|
177
|
+
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
178
|
+
/**
|
|
179
|
+
* Puts focus on the grid and **keeps** it there for a few frames.
|
|
180
|
+
*
|
|
181
|
+
* A delete travels through two overlays — the tile's ⋯ menu and the confirm
|
|
182
|
+
* dialog — and each restores focus to its own trigger when it closes, on its
|
|
183
|
+
* own schedule. Both triggers live on the tile the delete just removed, so
|
|
184
|
+
* whichever lands last drops focus on `<body>`. Claiming the dialog's
|
|
185
|
+
* `onCloseAutoFocus` handles one of them; this reclaims from the other,
|
|
186
|
+
* bounded so it can never fight a focus the user chose themselves (it only
|
|
187
|
+
* acts while nothing at all is focused).
|
|
188
|
+
*/
|
|
189
|
+
const focusGrid = () => {
|
|
190
|
+
const deadline = Date.now() + FOCUS_RECLAIM_MS;
|
|
191
|
+
const reclaim = () => {
|
|
192
|
+
// Re-read the ref every tick: a refetch can re-render the region.
|
|
193
|
+
const grid = gridRef.current;
|
|
194
|
+
if (!grid || document.activeElement === grid)
|
|
195
|
+
return;
|
|
196
|
+
const active = document.activeElement;
|
|
197
|
+
// Only step in when nothing holds focus: `<body>` after a layer
|
|
198
|
+
// gave up, or a node that has since left the document. A control
|
|
199
|
+
// the user actually moved to is left alone.
|
|
200
|
+
if (!active || active === document.body || !active.isConnected) {
|
|
201
|
+
grid.focus();
|
|
202
|
+
}
|
|
203
|
+
if (Date.now() < deadline)
|
|
204
|
+
window.setTimeout(reclaim, 32);
|
|
205
|
+
};
|
|
206
|
+
reclaim();
|
|
207
|
+
};
|
|
208
|
+
const locationLabel = store.currentFolder
|
|
209
|
+
? store.currentFolder.name
|
|
210
|
+
: intl.formatMessage(messages.allMedia);
|
|
211
|
+
const hasFilters = store.search.trim() !== '' || store.kindFilter !== 'all';
|
|
212
|
+
// Sub-folders belong to the folder, not to a page of its assets, so they are
|
|
213
|
+
// drawn once on the first page rather than repeating above every page of
|
|
214
|
+
// files. On any later page the grid is assets alone.
|
|
215
|
+
const showFolders = store.page === 1;
|
|
216
|
+
const visibleFolders = showFolders ? store.childFolders : [];
|
|
217
|
+
// Emptiness is now a fact about the whole folder, not about what happened to
|
|
218
|
+
// load: `total` counts every asset matching the search and filter across it.
|
|
219
|
+
const isEmpty = visibleFolders.length === 0 && store.total === 0;
|
|
220
|
+
// Triggers a browser download for one asset (its bytes stream from the
|
|
221
|
+
// `media:read`-gated raw route). Shared by the row action and bulk download.
|
|
222
|
+
const downloadAsset = (asset) => {
|
|
223
|
+
const link = document.createElement('a');
|
|
224
|
+
link.href = asset.url;
|
|
225
|
+
link.download = asset.name;
|
|
226
|
+
document.body.appendChild(link);
|
|
227
|
+
link.click();
|
|
228
|
+
link.remove();
|
|
229
|
+
};
|
|
230
|
+
// A single dispatcher every card / row / drawer forwards asset actions to.
|
|
231
|
+
const handleAssetAction = (kind, asset) => {
|
|
232
|
+
switch (kind) {
|
|
233
|
+
case 'open':
|
|
234
|
+
store.openDetail(asset.id);
|
|
235
|
+
break;
|
|
236
|
+
case 'download': {
|
|
237
|
+
downloadAsset(asset);
|
|
238
|
+
toast.success(intl.formatMessage(messages.tDownload, { name: asset.name }));
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case 'copyLink':
|
|
242
|
+
// Confirm only once the clipboard actually took it — a denied
|
|
243
|
+
// permission used to still say "Link copied".
|
|
244
|
+
void navigator.clipboard
|
|
245
|
+
?.writeText(new URL(asset.url, window.location.origin).href)
|
|
246
|
+
.then(() => toast.success(intl.formatMessage(messages.tCopied)), () => toast.error(intl.formatMessage(messages.tCopyFailed)));
|
|
247
|
+
break;
|
|
248
|
+
case 'duplicate': {
|
|
249
|
+
void store.duplicateAssets([asset.id]).then((ok) => {
|
|
250
|
+
if (ok) {
|
|
251
|
+
toast.success(intl.formatMessage(messages.tDuplicated, {
|
|
252
|
+
count: 1
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case 'rename':
|
|
259
|
+
setRenameTarget({
|
|
260
|
+
kind: 'asset',
|
|
261
|
+
id: asset.id,
|
|
262
|
+
name: asset.name
|
|
263
|
+
});
|
|
264
|
+
break;
|
|
265
|
+
case 'move':
|
|
266
|
+
setMoveIds([asset.id]);
|
|
267
|
+
break;
|
|
268
|
+
case 'delete':
|
|
269
|
+
setDeleteTarget({ kind: 'assets', ids: [asset.id] });
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
const handleRenameFolder = (folder) => setRenameTarget({ kind: 'folder', id: folder.id, name: folder.name });
|
|
274
|
+
const handleDeleteFolder = (folder) => setDeleteTarget({ kind: 'folder', folder });
|
|
275
|
+
const submitRename = (name) => {
|
|
276
|
+
if (!renameTarget)
|
|
277
|
+
return;
|
|
278
|
+
const done = renameTarget.kind === 'asset'
|
|
279
|
+
? store.renameAsset(renameTarget.id, name)
|
|
280
|
+
: store.renameFolder(renameTarget.id, name);
|
|
281
|
+
void done.then((ok) => {
|
|
282
|
+
if (ok) {
|
|
283
|
+
toast.success(intl.formatMessage(messages.tRenamed, { name }));
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
const submitMove = (folderId) => {
|
|
288
|
+
if (!moveIds)
|
|
289
|
+
return;
|
|
290
|
+
const count = moveIds.length;
|
|
291
|
+
void store.moveAssets(moveIds, folderId).then((ok) => {
|
|
292
|
+
if (ok) {
|
|
293
|
+
toast.success(intl.formatMessage(messages.tMoved, { count }));
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
store.clearSelection();
|
|
297
|
+
};
|
|
298
|
+
const confirmDelete = () => {
|
|
299
|
+
if (!deleteTarget)
|
|
300
|
+
return;
|
|
301
|
+
if (deleteTarget.kind === 'assets') {
|
|
302
|
+
const count = deleteTarget.ids.length;
|
|
303
|
+
void store.deleteAssets(deleteTarget.ids).then((ok) => {
|
|
304
|
+
if (ok) {
|
|
305
|
+
toast.success(intl.formatMessage(messages.tDeletedAssets, { count }));
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
const name = deleteTarget.folder.name;
|
|
311
|
+
void store.deleteFolder(deleteTarget.folder.id).then((ok) => {
|
|
312
|
+
if (ok) {
|
|
313
|
+
toast.success(intl.formatMessage(messages.tDeletedFolder, { name }));
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
setDeleteTarget(null);
|
|
318
|
+
// The dialog restores focus to whatever opened it — a tile's ⋯ trigger
|
|
319
|
+
// the delete is about to remove from the DOM, leaving focus on `<body>`.
|
|
320
|
+
// Claim the close so it lands on the grid instead. Cancelling leaves the
|
|
321
|
+
// flag false, so the trigger (which still exists) keeps focus.
|
|
322
|
+
focusGridOnCloseRef.current = true;
|
|
323
|
+
};
|
|
324
|
+
// What the pending folder delete would take with it, so the confirmation
|
|
325
|
+
// can say so — a folder delete cascades now, and an unqualified "Delete?"
|
|
326
|
+
// over a populated tree is the kind of prompt people regret answering.
|
|
327
|
+
const doomed = deleteTarget?.kind === 'folder'
|
|
328
|
+
? folderContents(store.folders, store.folderCounts, deleteTarget.folder.id)
|
|
329
|
+
: null;
|
|
330
|
+
const isEmptyFolder = !!doomed && !doomed.assets && !doomed.folders;
|
|
331
|
+
const selectedIds = store.selectedAssets.map((asset) => asset.id);
|
|
332
|
+
if (!canRead) {
|
|
333
|
+
return (_jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center p-6 text-center", children: _jsx("p", { className: "max-w-sm text-sm text-muted-foreground", children: intl.formatMessage(messages.noAccess) }) }));
|
|
334
|
+
}
|
|
335
|
+
// A failed load gets its own state with a retry — never the empty state,
|
|
336
|
+
// which would misread a server error as "this folder is empty".
|
|
337
|
+
if (store.isError) {
|
|
338
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 p-6 text-center", children: [_jsx("p", { className: "max-w-sm text-sm text-muted-foreground", role: "alert", children: intl.formatMessage(messages.loadError) }), _jsx(Button, { variant: "outline", onClick: store.reload, children: intl.formatMessage(messages.retry) })] }));
|
|
339
|
+
}
|
|
340
|
+
return (_jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [_jsx(MediaTopBar, { breadcrumbs: store.breadcrumbs, onNavigate: store.navigateTo }), _jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col lg:flex-row", children: [_jsx("div", { className: "border-b p-3 lg:hidden", children: _jsxs(Button, { variant: "outline", size: "sm", className: "shadow-none", onClick: () => setNavOpen(true), children: [_jsx(PanelLeft, { "aria-hidden": true }), intl.formatMessage(messages.openNav)] }) }), _jsx(MediaFoldersNav, { className: "hidden lg:flex lg:border-r", folders: store.folders, currentFolderId: store.currentFolderId, counts: store.folderCounts, rootCount: store.folderCounts.get(ROOT_FOLDER_ID) ?? 0, onNavigate: store.navigateTo, onNewFolder: () => setNewFolderOpen(true), canCreate: canCreate }), _jsx("div", { className: "min-w-0 flex-1 overflow-auto", children: _jsxs("div", { className: "p-4 sm:p-6", children: [_jsxs("div", { className: "mb-4", children: [_jsx("h1", { className: "text-2xl font-semibold tracking-[-0.01em]", children: store.currentFolder
|
|
341
|
+
? store.currentFolder.name
|
|
342
|
+
: intl.formatMessage(messages.title) }), _jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: intl.formatMessage(messages.subtitle, {
|
|
343
|
+
count: store.total,
|
|
344
|
+
location: locationLabel
|
|
345
|
+
}) })] }), _jsx(MediaToolbar, { search: store.search, onSearchChange: store.setSearch, kindFilter: store.kindFilter, onKindFilterChange: store.setKindFilter, sort: store.sort, onSortChange: store.setSort, onNewFolder: () => setNewFolderOpen(true), onUpload: () => setUploadOpen(true), canCreate: canCreate }), _jsx("p", { className: "sr-only", "aria-live": "polite", children: intl.formatMessage(messages.selection, {
|
|
346
|
+
count: selectedIds.length
|
|
347
|
+
}) }), _jsx(MediaUploadBanner, { items: store.uploadItems, summary: store.uploadSummary, onRetry: store.retryUpload, onCancel: store.cancelUpload, onDismiss: store.dismissUploads }), selectedIds.length > 0 ? (_jsx(MediaSelectionBar, { count: selectedIds.length, onDownload: () => {
|
|
348
|
+
store.selectedAssets.forEach(downloadAsset);
|
|
349
|
+
toast.success(intl.formatMessage(messages.tDownload, {
|
|
350
|
+
name: `${selectedIds.length} files`
|
|
351
|
+
}));
|
|
352
|
+
}, onDuplicate: () => {
|
|
353
|
+
const count = selectedIds.length;
|
|
354
|
+
void store
|
|
355
|
+
.duplicateAssets(selectedIds)
|
|
356
|
+
.then((ok) => {
|
|
357
|
+
if (ok) {
|
|
358
|
+
toast.success(intl.formatMessage(messages.tDuplicated, { count }));
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}, onMove: () => setMoveIds(selectedIds), onDelete: () => setDeleteTarget({
|
|
362
|
+
kind: 'assets',
|
|
363
|
+
ids: selectedIds
|
|
364
|
+
}), onClear: store.clearSelection, canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete })) : null, _jsx("section", { ref: gridRef, tabIndex: -1, "aria-label": intl.formatMessage(messages.assetsRegion), "aria-busy": store.isRefreshing, className: cn('transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', store.isRefreshing && 'opacity-60'), children: isEmpty ? (_jsx(MediaEmptyState, { hasFilters: hasFilters, canCreate: canCreate, onUpload: () => setUploadOpen(true), onClearFilters: () => {
|
|
365
|
+
store.setSearch('');
|
|
366
|
+
store.setKindFilter('all');
|
|
367
|
+
} })) : (_jsx(MediaGrid, { folders: visibleFolders, assets: store.visibleAssets, folderCounts: store.folderCounts, selectedIds: store.selectedIds, onOpenFolder: store.navigateTo, onRenameFolder: handleRenameFolder, onDeleteFolder: handleDeleteFolder, onToggleSelect: store.toggleSelect, onAssetAction: handleAssetAction, canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete })) }), store.total > 0 ? (_jsx(MediaPagination, { page: store.page, pageCount: store.pageCount, pageSize: store.pageSize, total: store.total, onPageChange: store.setPage, onPageSizeChange: store.setPageSize })) : null] }) })] }), _jsx(Drawer, { direction: "left", open: navOpen, onOpenChange: setNavOpen, shouldScaleBackground: false, children: _jsxs(DrawerContent, { className: "data-[vaul-drawer-direction=left]:w-72", children: [_jsx(DrawerTitle, { className: "sr-only", children: intl.formatMessage(messages.navTitle) }), _jsx(MediaFoldersNav, { className: "w-full", folders: store.folders, currentFolderId: store.currentFolderId, counts: store.folderCounts, rootCount: store.folderCounts.get(ROOT_FOLDER_ID) ?? 0, onNavigate: (folderId) => {
|
|
368
|
+
store.navigateTo(folderId);
|
|
369
|
+
setNavOpen(false);
|
|
370
|
+
}, onNewFolder: () => {
|
|
371
|
+
setNavOpen(false);
|
|
372
|
+
setNewFolderOpen(true);
|
|
373
|
+
}, canCreate: canCreate })] }) }), _jsx(AssetDetailDrawer, { asset: store.detailAsset, locationLabel: locationLabel, onOpenChange: (open) => {
|
|
374
|
+
if (!open)
|
|
375
|
+
store.closeDetail();
|
|
376
|
+
}, onAction: handleAssetAction, onSaveAlt: (id, alt) => store.setAssetAlt(id, alt).then((ok) => {
|
|
377
|
+
if (ok) {
|
|
378
|
+
toast.success(intl.formatMessage(messages.tAltSaved));
|
|
379
|
+
}
|
|
380
|
+
return ok;
|
|
381
|
+
}), canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete }), _jsx(NewFolderDialog, { open: newFolderOpen, onOpenChange: setNewFolderOpen, locationLabel: locationLabel, onCreate: (name) => {
|
|
382
|
+
void store.createFolder(name).then((ok) => {
|
|
383
|
+
if (ok) {
|
|
384
|
+
toast.success(intl.formatMessage(messages.tCreated, { name }));
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
} }), _jsx(UploadDialog, { open: uploadOpen, onOpenChange: setUploadOpen, locationLabel: locationLabel,
|
|
388
|
+
// No toast here: the upload has only been *queued*. Progress and
|
|
389
|
+
// the per-file outcome are the banner's job — a success toast
|
|
390
|
+
// fired at submit time would claim a result nobody has yet.
|
|
391
|
+
onUpload: store.uploadFiles,
|
|
392
|
+
// The library's queue forwards alt to the API, so this is the
|
|
393
|
+
// one caller that may ask the author to describe the image.
|
|
394
|
+
collectAlt: true }), _jsx(RenameDialog, { open: renameTarget !== null, onOpenChange: (open) => {
|
|
395
|
+
if (!open)
|
|
396
|
+
setRenameTarget(null);
|
|
397
|
+
}, currentName: renameTarget?.name ?? '', onRename: submitRename }), _jsx(MoveAssetsDialog, { open: moveIds !== null, onOpenChange: (open) => {
|
|
398
|
+
if (!open)
|
|
399
|
+
setMoveIds(null);
|
|
400
|
+
}, folders: store.folders, count: moveIds?.length ?? 0, currentFolderId: store.currentFolderId, onMove: submitMove }), _jsx(ConfirmDialog, { open: deleteTarget !== null, onOpenChange: (open) => {
|
|
401
|
+
if (!open)
|
|
402
|
+
setDeleteTarget(null);
|
|
403
|
+
}, title: deleteTarget?.kind === 'folder'
|
|
404
|
+
? intl.formatMessage(isEmptyFolder
|
|
405
|
+
? messages.deleteFolderTitle
|
|
406
|
+
: messages.deleteFolderTitleFull, { name: deleteTarget.folder.name })
|
|
407
|
+
: intl.formatMessage(messages.deleteAssetsTitle, {
|
|
408
|
+
count: deleteTarget?.kind === 'assets'
|
|
409
|
+
? deleteTarget.ids.length
|
|
410
|
+
: 0
|
|
411
|
+
}), description: deleteTarget?.kind === 'folder'
|
|
412
|
+
? isEmptyFolder
|
|
413
|
+
? intl.formatMessage(messages.deleteFolderBody)
|
|
414
|
+
: intl.formatMessage(messages.deleteFolderBodyFull, {
|
|
415
|
+
assets: doomed?.assets ?? 0,
|
|
416
|
+
folders: doomed?.folders ?? 0
|
|
417
|
+
})
|
|
418
|
+
: intl.formatMessage(messages.deleteAssetsBody), confirmLabel: intl.formatMessage(messages.confirmDelete), confirmVariant: "destructive", onCloseAutoFocus: (event) => {
|
|
419
|
+
if (!focusGridOnCloseRef.current)
|
|
420
|
+
return;
|
|
421
|
+
focusGridOnCloseRef.current = false;
|
|
422
|
+
event.preventDefault();
|
|
423
|
+
focusGrid();
|
|
424
|
+
}, onConfirm: confirmDelete })] }));
|
|
425
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { MediaKind } from '../../constants';
|
|
2
|
+
/**
|
|
3
|
+
* A single media asset in the library — an uploaded file (image, video, audio,
|
|
4
|
+
* document, or archive) with its display metadata. This is the admin's model;
|
|
5
|
+
* once a media server exists it will be mapped from that API's wire shape.
|
|
6
|
+
*/
|
|
7
|
+
export type MediaAsset = {
|
|
8
|
+
/** Stable unique id. */
|
|
9
|
+
id: string;
|
|
10
|
+
/** File name shown to the user (includes extension). */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Coarse category driving the icon, badge, and kind filter. */
|
|
13
|
+
kind: MediaKind;
|
|
14
|
+
/** Route the browser fetches to stream the original bytes (download). */
|
|
15
|
+
url: string;
|
|
16
|
+
/** Small (~320px) WebP derivative for grid tiles; `undefined` if none. */
|
|
17
|
+
thumbUrl?: string;
|
|
18
|
+
/** Larger (~1280px) WebP derivative for the detail drawer; `undefined` if none. */
|
|
19
|
+
previewUrl?: string;
|
|
20
|
+
/** MIME type, e.g. `image/png`. */
|
|
21
|
+
mimeType: string;
|
|
22
|
+
/** File size in bytes. */
|
|
23
|
+
size: number;
|
|
24
|
+
/** Id of the folder the asset lives in (`ROOT_FOLDER_ID` at the top level). */
|
|
25
|
+
folderId: string;
|
|
26
|
+
/** Pixel dimensions for visual assets (`undefined` for audio/documents). */
|
|
27
|
+
dimensions?: {
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
};
|
|
31
|
+
/** Playback duration in seconds for audio/video. */
|
|
32
|
+
duration?: number;
|
|
33
|
+
/** Free-form labels for filtering and organisation. */
|
|
34
|
+
tags: string[];
|
|
35
|
+
/** Alternative text (accessibility) for images. */
|
|
36
|
+
alt?: string;
|
|
37
|
+
/** Display name of the member who uploaded it. */
|
|
38
|
+
uploadedBy: string;
|
|
39
|
+
/** ISO timestamp the asset was created/uploaded. */
|
|
40
|
+
createdAt: string;
|
|
41
|
+
/** ISO timestamp the asset was last modified. */
|
|
42
|
+
updatedAt: string;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/types/mediaAsset/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,IAAI,EAAE,SAAS,CAAC;IAChB,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,mDAAmD;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;CACrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One attached asset as a media **field** renders it — the merge of what the
|
|
3
|
+
* server resolved (`MediaRef`) and what the editor learned locally by picking or
|
|
4
|
+
* uploading (`MediaAsset`). A ref carries no size/dimensions, so those are
|
|
5
|
+
* optional: the tile shows whichever facts it has.
|
|
6
|
+
*/
|
|
7
|
+
export type MediaFieldDisplay = {
|
|
8
|
+
/** The asset id stored in the field value. */
|
|
9
|
+
id: string;
|
|
10
|
+
/** File name, or the raw id while nothing else is known yet. */
|
|
11
|
+
name: string;
|
|
12
|
+
/**
|
|
13
|
+
* What the tile renders — the small derivative when the asset has one, else
|
|
14
|
+
* the original. A tile is ~180px wide, so pulling a full-size original for
|
|
15
|
+
* it is pure waste; a staged (not yet uploaded) file uses its local object
|
|
16
|
+
* URL here, which is the same idea from the other direction.
|
|
17
|
+
*/
|
|
18
|
+
url: string;
|
|
19
|
+
/** The original bytes — the "open in a new tab" target, never the tile's `src`. */
|
|
20
|
+
originalUrl?: string;
|
|
21
|
+
/** Coarse kind; empty string when the asset hasn't been resolved yet. */
|
|
22
|
+
kind: string;
|
|
23
|
+
mimeType: string;
|
|
24
|
+
/** File size in bytes — known only for a picked/uploaded asset. */
|
|
25
|
+
size?: number;
|
|
26
|
+
/** Pixel dimensions — known only for a picked/uploaded visual asset. */
|
|
27
|
+
dimensions?: {
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
};
|
|
31
|
+
/** The id resolved to nothing: deleted, or in another workspace. */
|
|
32
|
+
missing?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* A file staged on the field but **not uploaded yet** — it goes up with the
|
|
35
|
+
* record on Save. `url` is a local object URL (images only) and `id` is the
|
|
36
|
+
* placeholder standing in for the asset.
|
|
37
|
+
*/
|
|
38
|
+
pending?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The id is real but nothing is known about it yet — the entry's media read
|
|
41
|
+
* is still in flight. The tile renders a placeholder rather than guessing
|
|
42
|
+
* the asset's raw route: guessing meant every edit-mode open fetched
|
|
43
|
+
* **full-size originals** for the moment before the refs landed (and
|
|
44
|
+
* labelled each tile with a uuid).
|
|
45
|
+
*/
|
|
46
|
+
resolving?: boolean;
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/types/mediaFieldDisplay/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC5B,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A folder grouping assets in the library. Folders form a tree via `parentId`;
|
|
3
|
+
* the top level is the synthetic root (`ROOT_FOLDER_ID`), which has no folder
|
|
4
|
+
* record of its own. This is the admin's model, mirrored from the future API.
|
|
5
|
+
*/
|
|
6
|
+
export type MediaFolder = {
|
|
7
|
+
/** Stable unique id. */
|
|
8
|
+
id: string;
|
|
9
|
+
/** Folder name shown to the user. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Parent folder id (`ROOT_FOLDER_ID` for a top-level folder). */
|
|
12
|
+
parentId: string;
|
|
13
|
+
/** ISO timestamp the folder was created. */
|
|
14
|
+
createdAt: string;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/types/mediaFolder/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACtB,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;CACrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One file staged on a media field and **not yet uploaded** — the record is
|
|
3
|
+
* saved first, and only then do the bytes move (see `usePendingMediaUploads`).
|
|
4
|
+
*/
|
|
5
|
+
export type PendingUpload = {
|
|
6
|
+
/**
|
|
7
|
+
* The placeholder id standing in for the asset in the form value. A real
|
|
8
|
+
* uuid on purpose: the shared kernel validates a media value as a uuid (or
|
|
9
|
+
* uuid[]), so a staged file satisfies client validation and the publish gate
|
|
10
|
+
* exactly like an attached asset would.
|
|
11
|
+
*/
|
|
12
|
+
id: string;
|
|
13
|
+
/** The bytes to send when the record is saved. */
|
|
14
|
+
file: File;
|
|
15
|
+
/** Object URL for an image preview; revoked when the entry is dropped. */
|
|
16
|
+
previewUrl?: string;
|
|
17
|
+
/**
|
|
18
|
+
* The real asset id, once this file has been uploaded. Set when a save's
|
|
19
|
+
* upload step succeeded but the write behind it did not — so a retry attaches
|
|
20
|
+
* the asset already in the library instead of uploading it a second time.
|
|
21
|
+
*/
|
|
22
|
+
uploadedId?: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* What a media field needs in order to stage files — the handle
|
|
26
|
+
* `usePendingMediaUploads` publishes through the entry editor's presave slot, so
|
|
27
|
+
* the staging outlives the Media tab's body (tabs are routes; the panel unmounts
|
|
28
|
+
* on every tab switch).
|
|
29
|
+
*/
|
|
30
|
+
export type MediaPendingUploads = {
|
|
31
|
+
/** Every staged file, keyed by its placeholder id. */
|
|
32
|
+
pending: ReadonlyMap<string, PendingUpload>;
|
|
33
|
+
/** Stage files and return their placeholder ids, in order. */
|
|
34
|
+
stage: (files: File[]) => string[];
|
|
35
|
+
/** Forget one staged file (removing it from a field also drops it here). */
|
|
36
|
+
drop: (id: string) => void;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/types/pendingUpload/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG;IACxB;;;;;OAKG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,IAAI,EAAE,IAAI,CAAC;IACX,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B,sDAAsD;IACtD,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC5C,8DAA8D;IAC9D,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC;IACnC,4EAA4E;IAC5E,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { UploadStatus } from '../../constants';
|
|
2
|
+
/**
|
|
3
|
+
* One file in the upload queue — the view model the progress banner renders.
|
|
4
|
+
* The `File` itself is **not** here: it lives in the queue hook's ref map, so a
|
|
5
|
+
* progress tick re-renders a small serializable row instead of dragging a blob
|
|
6
|
+
* through React state.
|
|
7
|
+
*/
|
|
8
|
+
export type UploadItem = {
|
|
9
|
+
/** Queue-local id (files have no stable identity of their own). */
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
/** Bytes, used both for display and to weight the batch percentage. */
|
|
13
|
+
size: number;
|
|
14
|
+
/** Whole-number percent of bytes sent, 0–100. */
|
|
15
|
+
progress: number;
|
|
16
|
+
status: UploadStatus;
|
|
17
|
+
/** Localized failure reason; set only when `status` is `failed`. */
|
|
18
|
+
error?: string;
|
|
19
|
+
};
|
|
20
|
+
/** The batch roll-up the banner headline reads from. */
|
|
21
|
+
export type UploadSummary = {
|
|
22
|
+
/** Every item still pending or uploading. */
|
|
23
|
+
inFlight: number;
|
|
24
|
+
done: number;
|
|
25
|
+
failed: number;
|
|
26
|
+
/** Items in the batch, excluding cancelled ones. */
|
|
27
|
+
total: number;
|
|
28
|
+
/**
|
|
29
|
+
* Whole-number percent across the batch, **weighted by file size** — ten
|
|
30
|
+
* thumbnails finishing shouldn't read as 90% while a 200 MB video is still
|
|
31
|
+
* going.
|
|
32
|
+
*/
|
|
33
|
+
percent: number;
|
|
34
|
+
/** True while anything is pending or uploading. */
|
|
35
|
+
active: boolean;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/types/upload/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,YAAY,CAAC;IACrB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,aAAa,GAAG;IACxB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,MAAM,EAAE,OAAO,CAAC;CACnB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|