@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,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bulk-action bar shown once one or more assets are selected — a "{n}
|
|
3
|
+
* selected" count and, permission-gated, Download / Duplicate / Move / Delete,
|
|
4
|
+
* with a Clear control. The count is visual only (the bar mounts/unmounts with
|
|
5
|
+
* the selection); the page owns any spoken announcement.
|
|
6
|
+
*/
|
|
7
|
+
export declare function MediaSelectionBar({ count, onDownload, onDuplicate, onMove, onDelete, onClear, canCreate, canUpdate, canDelete }: {
|
|
8
|
+
count: number;
|
|
9
|
+
onDownload: () => void;
|
|
10
|
+
onDuplicate: () => void;
|
|
11
|
+
onMove: () => void;
|
|
12
|
+
onDelete: () => void;
|
|
13
|
+
onClear: () => void;
|
|
14
|
+
canCreate: boolean;
|
|
15
|
+
canUpdate: boolean;
|
|
16
|
+
canDelete: boolean;
|
|
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/MediaSelectionBar/index.tsx"],"names":[],"mappings":"AAiBA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,KAAK,EACL,UAAU,EACV,WAAW,EACX,MAAM,EACN,QAAQ,EACR,OAAO,EACP,SAAS,EACT,SAAS,EACT,SAAS,EACZ,EAAE;IACC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACtB,+BAgEA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import { Button, Separator } from '@orthacms/design-system';
|
|
4
|
+
import { Copy, Download, FolderInput, Trash2, X } from 'lucide-react';
|
|
5
|
+
/** Intl descriptors for {@link MediaSelectionBar}, co-located. */
|
|
6
|
+
const messages = defineMessages({
|
|
7
|
+
count: {
|
|
8
|
+
id: 'media.selection.count',
|
|
9
|
+
defaultMessage: '{count, plural, one {# selected} other {# selected}}'
|
|
10
|
+
},
|
|
11
|
+
download: { id: 'media.selection.download', defaultMessage: 'Download' },
|
|
12
|
+
duplicate: { id: 'media.selection.duplicate', defaultMessage: 'Duplicate' },
|
|
13
|
+
move: { id: 'media.selection.move', defaultMessage: 'Move' },
|
|
14
|
+
delete: { id: 'media.selection.delete', defaultMessage: 'Delete' },
|
|
15
|
+
clear: { id: 'media.selection.clear', defaultMessage: 'Clear selection' }
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* The bulk-action bar shown once one or more assets are selected — a "{n}
|
|
19
|
+
* selected" count and, permission-gated, Download / Duplicate / Move / Delete,
|
|
20
|
+
* with a Clear control. The count is visual only (the bar mounts/unmounts with
|
|
21
|
+
* the selection); the page owns any spoken announcement.
|
|
22
|
+
*/
|
|
23
|
+
export function MediaSelectionBar({ count, onDownload, onDuplicate, onMove, onDelete, onClear, canCreate, canUpdate, canDelete }) {
|
|
24
|
+
const intl = useIntl();
|
|
25
|
+
return (_jsxs("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-3 rounded-xl border bg-muted/40 px-4 py-2", children: [_jsx("span", { className: "text-sm font-medium", children: intl.formatMessage(messages.count, { count }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", className: "shadow-none", onClick: onDownload, children: [_jsx(Download, { "aria-hidden": true }), intl.formatMessage(messages.download)] }), canCreate ? (_jsxs(Button, { variant: "outline", size: "sm", className: "shadow-none", onClick: onDuplicate, children: [_jsx(Copy, { "aria-hidden": true }), intl.formatMessage(messages.duplicate)] })) : null, canUpdate ? (_jsxs(Button, { variant: "outline", size: "sm", className: "shadow-none", onClick: onMove, children: [_jsx(FolderInput, { "aria-hidden": true }), intl.formatMessage(messages.move)] })) : null, canDelete ? (_jsxs(Button, { variant: "outline", size: "sm", className: "text-destructive shadow-none hover:text-destructive", onClick: onDelete, children: [_jsx(Trash2, { "aria-hidden": true }), intl.formatMessage(messages.delete)] })) : null, _jsx(Separator, { orientation: "vertical", className: "h-6" }), _jsxs(Button, { variant: "ghost", size: "sm", className: "shadow-none", onClick: onClear, children: [_jsx(X, { "aria-hidden": true }), intl.formatMessage(messages.clear)] })] })] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Total media storage for the workspace.
|
|
3
|
+
*
|
|
4
|
+
* `formatBytes` returns a value and its unit as one string, so the figure and
|
|
5
|
+
* the unit are split here rather than passed to `unit` — keeping the number in
|
|
6
|
+
* the tabular figure style while the unit stays secondary.
|
|
7
|
+
*/
|
|
8
|
+
export declare function MediaStorageStat(): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaStorageStat/index.tsx"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,wBAAgB,gBAAgB,gCAmB/B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import { StatWidget } from '@orthacms/insights-admin';
|
|
4
|
+
import { useMediaStorage } from '../../hooks/useMediaInsights';
|
|
5
|
+
import { formatBytes } from '../../utils/formatBytes';
|
|
6
|
+
/** Intl descriptors for the storage stat tile, co-located here. */
|
|
7
|
+
const messages = defineMessages({
|
|
8
|
+
label: {
|
|
9
|
+
id: 'media.insights.storage.label',
|
|
10
|
+
defaultMessage: 'Media storage'
|
|
11
|
+
},
|
|
12
|
+
assets: {
|
|
13
|
+
id: 'media.insights.storage.assets',
|
|
14
|
+
defaultMessage: '{count, number} assets'
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Total media storage for the workspace.
|
|
19
|
+
*
|
|
20
|
+
* `formatBytes` returns a value and its unit as one string, so the figure and
|
|
21
|
+
* the unit are split here rather than passed to `unit` — keeping the number in
|
|
22
|
+
* the tabular figure style while the unit stays secondary.
|
|
23
|
+
*/
|
|
24
|
+
export function MediaStorageStat() {
|
|
25
|
+
const intl = useIntl();
|
|
26
|
+
const { data, isPending, isError } = useMediaStorage();
|
|
27
|
+
const [amount, unit] = formatBytes(data?.totalBytes ?? 0).split(' ');
|
|
28
|
+
return (_jsx(StatWidget, { label: intl.formatMessage(messages.label), value: amount, unit: unit, delta: intl.formatMessage(messages.assets, {
|
|
29
|
+
count: data?.totalCount ?? 0
|
|
30
|
+
}), deltaTone: "flat", isPending: isPending, isError: isError }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage by media kind, measured in **bytes**.
|
|
3
|
+
*
|
|
4
|
+
* Bars are bytes and only bytes. An earlier draft scaled the bar by asset count
|
|
5
|
+
* while the readout showed size, which put two different measures on one row and
|
|
6
|
+
* made the widget's whole point — that a few videos outweigh thousands of images
|
|
7
|
+
* — impossible to see.
|
|
8
|
+
*/
|
|
9
|
+
export declare function MediaStorageWidget(): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaStorageWidget/index.tsx"],"names":[],"mappings":"AAsDA;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,gCA4DjC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import { BarRows, WidgetCard } from '@orthacms/insights-admin';
|
|
4
|
+
import { useMediaStorage } from '../../hooks/useMediaInsights';
|
|
5
|
+
import { formatBytes } from '../../utils/formatBytes';
|
|
6
|
+
/** Intl descriptors for the storage widget, co-located here. */
|
|
7
|
+
const messages = defineMessages({
|
|
8
|
+
title: {
|
|
9
|
+
id: 'media.insights.storageBreakdown.title',
|
|
10
|
+
defaultMessage: "What's using the storage"
|
|
11
|
+
},
|
|
12
|
+
description: {
|
|
13
|
+
id: 'media.insights.storageBreakdown.description',
|
|
14
|
+
defaultMessage: '{bytes} across {count, number} assets'
|
|
15
|
+
},
|
|
16
|
+
tip: {
|
|
17
|
+
id: 'media.insights.storageBreakdown.tip',
|
|
18
|
+
defaultMessage: '{kind} — {bytes} across {count, number} assets'
|
|
19
|
+
},
|
|
20
|
+
footer: {
|
|
21
|
+
id: 'media.insights.storageBreakdown.footer',
|
|
22
|
+
defaultMessage: '{kind} is {countShare}% of the assets and {byteShare}% of the storage.'
|
|
23
|
+
},
|
|
24
|
+
image: { id: 'media.insights.kind.image', defaultMessage: 'Image' },
|
|
25
|
+
video: { id: 'media.insights.kind.video', defaultMessage: 'Video' },
|
|
26
|
+
audio: { id: 'media.insights.kind.audio', defaultMessage: 'Audio' },
|
|
27
|
+
document: {
|
|
28
|
+
id: 'media.insights.kind.document',
|
|
29
|
+
defaultMessage: 'Document'
|
|
30
|
+
},
|
|
31
|
+
archive: { id: 'media.insights.kind.archive', defaultMessage: 'Archive' }
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Ramp steps by rank, deepest first.
|
|
35
|
+
*
|
|
36
|
+
* Here the ramp genuinely does encode magnitude — the rows arrive sorted by
|
|
37
|
+
* bytes — so colour and length say the same thing. That is the one case where
|
|
38
|
+
* ranking the ramp is correct rather than misleading.
|
|
39
|
+
*/
|
|
40
|
+
const RANK_TONES = ['q5', 'q4', 'q3', 'q2', 'q1'];
|
|
41
|
+
/** Falls back to the raw kind if the server ever adds one the UI doesn't know. */
|
|
42
|
+
function kindLabel(kind, intl) {
|
|
43
|
+
const descriptor = messages[kind];
|
|
44
|
+
return descriptor ? intl.formatMessage(descriptor) : kind;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Storage by media kind, measured in **bytes**.
|
|
48
|
+
*
|
|
49
|
+
* Bars are bytes and only bytes. An earlier draft scaled the bar by asset count
|
|
50
|
+
* while the readout showed size, which put two different measures on one row and
|
|
51
|
+
* made the widget's whole point — that a few videos outweigh thousands of images
|
|
52
|
+
* — impossible to see.
|
|
53
|
+
*/
|
|
54
|
+
export function MediaStorageWidget() {
|
|
55
|
+
const intl = useIntl();
|
|
56
|
+
const { data, isPending, isError } = useMediaStorage();
|
|
57
|
+
const kinds = data?.kinds ?? [];
|
|
58
|
+
const max = Math.max(...kinds.map((kind) => kind.bytes), 0);
|
|
59
|
+
const rows = kinds.map((kind, index) => {
|
|
60
|
+
const label = kindLabel(kind.kind, intl);
|
|
61
|
+
return {
|
|
62
|
+
id: kind.kind,
|
|
63
|
+
label,
|
|
64
|
+
segments: [
|
|
65
|
+
{
|
|
66
|
+
id: kind.kind,
|
|
67
|
+
value: kind.bytes,
|
|
68
|
+
tone: RANK_TONES[index] ?? 'q1',
|
|
69
|
+
label: intl.formatMessage(messages.tip, {
|
|
70
|
+
kind: label,
|
|
71
|
+
bytes: formatBytes(kind.bytes),
|
|
72
|
+
count: kind.count
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
readout: formatBytes(kind.bytes)
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
// The heaviest kind is the one worth calling out, and it is first because
|
|
80
|
+
// the server sorts by bytes.
|
|
81
|
+
const heaviest = kinds[0];
|
|
82
|
+
const footer = heaviest && data && data.totalBytes > 0 && data.totalCount > 0
|
|
83
|
+
? intl.formatMessage(messages.footer, {
|
|
84
|
+
kind: kindLabel(heaviest.kind, intl),
|
|
85
|
+
countShare: Math.round((heaviest.count / data.totalCount) * 100),
|
|
86
|
+
byteShare: Math.round((heaviest.bytes / data.totalBytes) * 100)
|
|
87
|
+
})
|
|
88
|
+
: undefined;
|
|
89
|
+
return (_jsx(WidgetCard, { title: intl.formatMessage(messages.title), description: intl.formatMessage(messages.description, {
|
|
90
|
+
bytes: formatBytes(data?.totalBytes ?? 0),
|
|
91
|
+
count: data?.totalCount ?? 0
|
|
92
|
+
}), footer: footer, isPending: isPending, isError: isError, isEmpty: kinds.length === 0, skeletonRows: 5, children: _jsx(BarRows, { rows: rows, max: max }) }));
|
|
93
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MediaAsset } from '../../../types/mediaAsset';
|
|
2
|
+
import { type AssetActionKind } from '../../AssetActionsMenu';
|
|
3
|
+
/**
|
|
4
|
+
* One asset row in the list view — a leading select checkbox, a mini thumbnail +
|
|
5
|
+
* clickable name, the kind badge, size, dimensions, and last-modified date, plus
|
|
6
|
+
* the shared ⋯ menu. Clicking the name opens the detail drawer.
|
|
7
|
+
*/
|
|
8
|
+
export declare function MediaAssetRow({ asset, selected, onToggleSelect, onAction, canCreate, canUpdate, canDelete }: {
|
|
9
|
+
asset: MediaAsset;
|
|
10
|
+
selected: boolean;
|
|
11
|
+
onToggleSelect: (id: string) => void;
|
|
12
|
+
onAction: (kind: AssetActionKind, asset: MediaAsset) => void;
|
|
13
|
+
canCreate: boolean;
|
|
14
|
+
canUpdate: boolean;
|
|
15
|
+
canDelete: boolean;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/MediaTable/MediaAssetRow/index.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAG5D,OAAO,EAEH,KAAK,eAAe,EACvB,MAAM,wBAAwB,CAAC;AAWhC;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,SAAS,EACT,SAAS,EACT,SAAS,EACZ,EAAE;IACC,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAC7D,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACtB,+BA6EA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MediaFolder } from '../../../types/mediaFolder';
|
|
2
|
+
/**
|
|
3
|
+
* One folder row in the list view — a folder glyph + clickable name that opens
|
|
4
|
+
* the folder, a "Folder" badge, and its item count, with the shared folder ⋯
|
|
5
|
+
* menu. Folders aren't selectable, so the leading checkbox cell stays empty.
|
|
6
|
+
*/
|
|
7
|
+
export declare function MediaFolderRow({ folder, assetCount, onOpen, onRename, onDelete, canUpdate, canDelete }: {
|
|
8
|
+
folder: MediaFolder;
|
|
9
|
+
assetCount: number;
|
|
10
|
+
onOpen: (folderId: string) => void;
|
|
11
|
+
onRename: (folder: MediaFolder) => void;
|
|
12
|
+
onDelete: (folder: MediaFolder) => void;
|
|
13
|
+
canUpdate: boolean;
|
|
14
|
+
canDelete: boolean;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/MediaTable/MediaFolderRow/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAc9D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC3B,MAAM,EACN,UAAU,EACV,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACZ,EAAE;IACC,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACtB,+BA+DA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MediaAsset } from '../../types/mediaAsset';
|
|
2
|
+
import type { MediaFolder } from '../../types/mediaFolder';
|
|
3
|
+
import type { AssetActionKind } from '../AssetActionsMenu';
|
|
4
|
+
/**
|
|
5
|
+
* The list view of the open folder — a table with folder rows above asset rows.
|
|
6
|
+
* The header carries a select-all checkbox (toggling every visible asset) and
|
|
7
|
+
* columns that progressively hide on narrower viewports. Purely presentational:
|
|
8
|
+
* selection and actions dispatch up to the page.
|
|
9
|
+
*/
|
|
10
|
+
export declare function MediaTable({ folders, assets, folderCounts, selectedIds, allSelected, onToggleAll, onOpenFolder, onRenameFolder, onDeleteFolder, onToggleSelect, onAssetAction, canCreate, canUpdate, canDelete }: {
|
|
11
|
+
folders: MediaFolder[];
|
|
12
|
+
assets: MediaAsset[];
|
|
13
|
+
folderCounts: Map<string, number>;
|
|
14
|
+
selectedIds: Set<string>;
|
|
15
|
+
allSelected: boolean;
|
|
16
|
+
onToggleAll: () => void;
|
|
17
|
+
onOpenFolder: (folderId: string) => void;
|
|
18
|
+
onRenameFolder: (folder: MediaFolder) => void;
|
|
19
|
+
onDeleteFolder: (folder: MediaFolder) => void;
|
|
20
|
+
onToggleSelect: (id: string) => void;
|
|
21
|
+
onAssetAction: (kind: AssetActionKind, asset: MediaAsset) => void;
|
|
22
|
+
canCreate: boolean;
|
|
23
|
+
canUpdate: boolean;
|
|
24
|
+
canDelete: boolean;
|
|
25
|
+
}): import("react").JSX.Element;
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaTable/index.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAe3D;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACvB,OAAO,EACP,MAAM,EACN,YAAY,EACZ,WAAW,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACZ,cAAc,EACd,cAAc,EACd,cAAc,EACd,aAAa,EACb,SAAS,EACT,SAAS,EACT,SAAS,EACZ,EAAE;IACC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,cAAc,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,cAAc,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,aAAa,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAClE,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACtB,+BAuEA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MediaAsset } from '../../types/mediaAsset';
|
|
2
|
+
/**
|
|
3
|
+
* The visual preview for an asset. Image assets render a real thumbnail served
|
|
4
|
+
* from the media API — the small `thumb` derivative by default, or the larger
|
|
5
|
+
* `preview` when `size="preview"` (the detail drawer) — falling back to the
|
|
6
|
+
* original when no derivative exists (e.g. SVG). A deterministic gradient tile
|
|
7
|
+
* sits underneath as the backdrop / loading state; non-image kinds show it with
|
|
8
|
+
* a centred kind glyph. Video/audio get a play affordance and a duration pill.
|
|
9
|
+
* Sized entirely by the parent via `className` (aspect + radius), so it serves
|
|
10
|
+
* tiles, list rows, and the drawer.
|
|
11
|
+
*/
|
|
12
|
+
export declare function MediaThumbnail({ asset, className, iconClassName, size }: {
|
|
13
|
+
asset: MediaAsset;
|
|
14
|
+
className?: string;
|
|
15
|
+
iconClassName?: string;
|
|
16
|
+
/** Which derivative to prefer — the grid uses `thumb`, the drawer `preview`. */
|
|
17
|
+
size?: 'thumb' | 'preview';
|
|
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/MediaThumbnail/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAKzD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,SAAS,EACT,aAAa,EACb,IAAc,EACjB,EAAE;IACC,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B,+BA4CA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Play } from 'lucide-react';
|
|
3
|
+
import { cn } from '@orthacms/design-system';
|
|
4
|
+
import { MEDIA_KIND } from '../../constants';
|
|
5
|
+
import { assetGradient } from '../../utils/assetGradient';
|
|
6
|
+
import { formatDuration } from '../../utils/formatDuration';
|
|
7
|
+
import { MediaKindIcon } from '../MediaKindIcon';
|
|
8
|
+
/**
|
|
9
|
+
* The visual preview for an asset. Image assets render a real thumbnail served
|
|
10
|
+
* from the media API — the small `thumb` derivative by default, or the larger
|
|
11
|
+
* `preview` when `size="preview"` (the detail drawer) — falling back to the
|
|
12
|
+
* original when no derivative exists (e.g. SVG). A deterministic gradient tile
|
|
13
|
+
* sits underneath as the backdrop / loading state; non-image kinds show it with
|
|
14
|
+
* a centred kind glyph. Video/audio get a play affordance and a duration pill.
|
|
15
|
+
* Sized entirely by the parent via `className` (aspect + radius), so it serves
|
|
16
|
+
* tiles, list rows, and the drawer.
|
|
17
|
+
*/
|
|
18
|
+
export function MediaThumbnail({ asset, className, iconClassName, size = 'thumb' }) {
|
|
19
|
+
const isImage = asset.kind === MEDIA_KIND.Image;
|
|
20
|
+
const isPlayable = asset.kind === MEDIA_KIND.Video || asset.kind === MEDIA_KIND.Audio;
|
|
21
|
+
// Prefer the requested derivative, then the other, then the original.
|
|
22
|
+
const imageSrc = size === 'preview'
|
|
23
|
+
? (asset.previewUrl ?? asset.thumbUrl ?? asset.url)
|
|
24
|
+
: (asset.thumbUrl ?? asset.previewUrl ?? asset.url);
|
|
25
|
+
return (_jsxs("div", { className: cn('relative flex items-center justify-center overflow-hidden bg-muted', className), style: { backgroundImage: assetGradient(asset.id) }, children: [_jsx("div", { className: "absolute inset-0 bg-black/5", "aria-hidden": true }), isImage ? (_jsx("img", { src: imageSrc, alt: asset.alt ?? '', loading: "lazy", className: "absolute inset-0 size-full object-cover" })) : (_jsx(MediaKindIcon, { kind: asset.kind, className: cn('relative size-8 text-white/90 drop-shadow-sm', iconClassName) })), isPlayable ? (_jsxs("span", { className: "absolute bottom-1.5 right-1.5 inline-flex items-center gap-1 rounded-md bg-black/55 px-1.5 py-0.5 text-[11px] font-medium text-white", children: [_jsx(Play, { className: "size-3 fill-current", "aria-hidden": true }), asset.duration ? formatDuration(asset.duration) : null] })) : null] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type MediaSort } from '../../constants';
|
|
2
|
+
import type { KindFilter } from '../../hooks/useMediaLibrary';
|
|
3
|
+
/**
|
|
4
|
+
* The Media Library's control bar — a search box, a type filter, a sort select,
|
|
5
|
+
* and (permission-gated) New folder + Upload actions. Fully controlled: every
|
|
6
|
+
* value lives in the page's store and changes dispatch straight back up.
|
|
7
|
+
*/
|
|
8
|
+
export declare function MediaToolbar({ search, onSearchChange, kindFilter, onKindFilterChange, sort, onSortChange, onNewFolder, onUpload, canCreate }: {
|
|
9
|
+
search: string;
|
|
10
|
+
onSearchChange: (value: string) => void;
|
|
11
|
+
kindFilter: KindFilter;
|
|
12
|
+
onKindFilterChange: (value: KindFilter) => void;
|
|
13
|
+
sort: MediaSort;
|
|
14
|
+
onSortChange: (value: MediaSort) => void;
|
|
15
|
+
onNewFolder: () => void;
|
|
16
|
+
onUpload: () => void;
|
|
17
|
+
canCreate: 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/MediaToolbar/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAIH,KAAK,SAAS,EACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAmD9D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EACzB,MAAM,EACN,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,SAAS,EACZ,EAAE;IACC,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,UAAU,EAAE,UAAU,CAAC;IACvB,kBAAkB,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;CACtB,+BA0GA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import { Button, InputGroup, InputGroupAddon, InputGroupInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@orthacms/design-system';
|
|
4
|
+
import { FolderPlus, Search, UploadCloud } from 'lucide-react';
|
|
5
|
+
import { KIND_FILTER_ALL, MEDIA_KIND, MEDIA_SORT } from '../../constants';
|
|
6
|
+
/** Intl descriptors for {@link MediaToolbar}, co-located. */
|
|
7
|
+
const messages = defineMessages({
|
|
8
|
+
search: { id: 'media.toolbar.search', defaultMessage: 'Search assets' },
|
|
9
|
+
searchPlaceholder: {
|
|
10
|
+
id: 'media.toolbar.searchPlaceholder',
|
|
11
|
+
defaultMessage: 'Search name or tag…'
|
|
12
|
+
},
|
|
13
|
+
kindLabel: {
|
|
14
|
+
id: 'media.toolbar.kindLabel',
|
|
15
|
+
defaultMessage: 'Filter by type'
|
|
16
|
+
},
|
|
17
|
+
kindAll: { id: 'media.toolbar.kindAll', defaultMessage: 'All types' },
|
|
18
|
+
kindImage: { id: 'media.toolbar.kindImage', defaultMessage: 'Images' },
|
|
19
|
+
kindVideo: { id: 'media.toolbar.kindVideo', defaultMessage: 'Videos' },
|
|
20
|
+
kindAudio: { id: 'media.toolbar.kindAudio', defaultMessage: 'Audio' },
|
|
21
|
+
kindDocument: {
|
|
22
|
+
id: 'media.toolbar.kindDocument',
|
|
23
|
+
defaultMessage: 'Documents'
|
|
24
|
+
},
|
|
25
|
+
kindArchive: {
|
|
26
|
+
id: 'media.toolbar.kindArchive',
|
|
27
|
+
defaultMessage: 'Archives'
|
|
28
|
+
},
|
|
29
|
+
sortLabel: { id: 'media.toolbar.sortLabel', defaultMessage: 'Sort' },
|
|
30
|
+
sortNewest: {
|
|
31
|
+
id: 'media.toolbar.sortNewest',
|
|
32
|
+
defaultMessage: 'Newest first'
|
|
33
|
+
},
|
|
34
|
+
sortOldest: {
|
|
35
|
+
id: 'media.toolbar.sortOldest',
|
|
36
|
+
defaultMessage: 'Oldest first'
|
|
37
|
+
},
|
|
38
|
+
sortNameAsc: {
|
|
39
|
+
id: 'media.toolbar.sortNameAsc',
|
|
40
|
+
defaultMessage: 'Name A–Z'
|
|
41
|
+
},
|
|
42
|
+
sortNameDesc: {
|
|
43
|
+
id: 'media.toolbar.sortNameDesc',
|
|
44
|
+
defaultMessage: 'Name Z–A'
|
|
45
|
+
},
|
|
46
|
+
sortLargest: { id: 'media.toolbar.sortLargest', defaultMessage: 'Largest' },
|
|
47
|
+
sortSmallest: {
|
|
48
|
+
id: 'media.toolbar.sortSmallest',
|
|
49
|
+
defaultMessage: 'Smallest'
|
|
50
|
+
},
|
|
51
|
+
newFolder: { id: 'media.toolbar.newFolder', defaultMessage: 'New folder' },
|
|
52
|
+
upload: { id: 'media.toolbar.upload', defaultMessage: 'Upload' }
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* The Media Library's control bar — a search box, a type filter, a sort select,
|
|
56
|
+
* and (permission-gated) New folder + Upload actions. Fully controlled: every
|
|
57
|
+
* value lives in the page's store and changes dispatch straight back up.
|
|
58
|
+
*/
|
|
59
|
+
export function MediaToolbar({ search, onSearchChange, kindFilter, onKindFilterChange, sort, onSortChange, onNewFolder, onUpload, canCreate }) {
|
|
60
|
+
const intl = useIntl();
|
|
61
|
+
return (_jsxs("div", { className: "mb-4 flex flex-wrap items-center gap-2 sm:gap-3", children: [_jsxs(InputGroup, { className: "w-full shadow-none sm:w-auto sm:max-w-[280px] sm:flex-1", children: [_jsx(InputGroupAddon, { children: _jsx(Search, {}) }), _jsx(InputGroupInput, { type: "search", value: search, onChange: (event) => onSearchChange(event.target.value), "aria-label": intl.formatMessage(messages.search), placeholder: intl.formatMessage(messages.searchPlaceholder) })] }), _jsxs(Select, { value: kindFilter, onValueChange: (value) => onKindFilterChange(value), children: [_jsx(SelectTrigger, { className: "w-[150px] shadow-none", "aria-label": intl.formatMessage(messages.kindLabel), children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: KIND_FILTER_ALL, children: intl.formatMessage(messages.kindAll) }), _jsx(SelectItem, { value: MEDIA_KIND.Image, children: intl.formatMessage(messages.kindImage) }), _jsx(SelectItem, { value: MEDIA_KIND.Video, children: intl.formatMessage(messages.kindVideo) }), _jsx(SelectItem, { value: MEDIA_KIND.Audio, children: intl.formatMessage(messages.kindAudio) }), _jsx(SelectItem, { value: MEDIA_KIND.Document, children: intl.formatMessage(messages.kindDocument) }), _jsx(SelectItem, { value: MEDIA_KIND.Archive, children: intl.formatMessage(messages.kindArchive) })] })] }), _jsxs(Select, { value: sort, onValueChange: (value) => onSortChange(value), children: [_jsx(SelectTrigger, { className: "w-[150px] shadow-none", "aria-label": intl.formatMessage(messages.sortLabel), children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: MEDIA_SORT.Newest, children: intl.formatMessage(messages.sortNewest) }), _jsx(SelectItem, { value: MEDIA_SORT.Oldest, children: intl.formatMessage(messages.sortOldest) }), _jsx(SelectItem, { value: MEDIA_SORT.NameAsc, children: intl.formatMessage(messages.sortNameAsc) }), _jsx(SelectItem, { value: MEDIA_SORT.NameDesc, children: intl.formatMessage(messages.sortNameDesc) }), _jsx(SelectItem, { value: MEDIA_SORT.Largest, children: intl.formatMessage(messages.sortLargest) }), _jsx(SelectItem, { value: MEDIA_SORT.Smallest, children: intl.formatMessage(messages.sortSmallest) })] })] }), _jsx("div", { className: "ml-auto flex items-center gap-2 sm:gap-3", children: canCreate ? (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", className: "shadow-none", onClick: onNewFolder, children: [_jsx(FolderPlus, { "aria-hidden": true }), _jsx("span", { className: "hidden sm:inline", children: intl.formatMessage(messages.newFolder) })] }), _jsxs(Button, { onClick: onUpload, children: [_jsx(UploadCloud, { "aria-hidden": true }), intl.formatMessage(messages.upload)] })] })) : null })] }));
|
|
62
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MediaFolder } from '../../types/mediaFolder';
|
|
2
|
+
/**
|
|
3
|
+
* The page top bar over the Media Library — the teal `Image` tile (matching the
|
|
4
|
+
* plugin's workspace-nav entry) beside a breadcrumb tracing the open location:
|
|
5
|
+
* Media Library › each ancestor folder › the open folder. Sticky to the top of
|
|
6
|
+
* the page, spanning the folder sidebar and the browser both, the same
|
|
7
|
+
* page-context header every other admin page carries.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the Content Library's bar this one is **not** route-derived: folder
|
|
10
|
+
* navigation here is component state, not a URL segment, so the crumbs come from
|
|
11
|
+
* the store's `breadcrumbs` and each one dispatches `onNavigate` rather than
|
|
12
|
+
* linking. They are still `BreadcrumbLink`s over real `<button>`s — a control
|
|
13
|
+
* that changes view without changing location is a button, not a link.
|
|
14
|
+
*/
|
|
15
|
+
export declare function MediaTopBar({ breadcrumbs, onNavigate }: {
|
|
16
|
+
/** Ancestor chain of the open folder, root-first (excludes the root itself). */
|
|
17
|
+
breadcrumbs: MediaFolder[];
|
|
18
|
+
onNavigate: (folderId: string) => void;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaTopBar/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAQ3D;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EACxB,WAAW,EACX,UAAU,EACb,EAAE;IACC,gFAAgF;IAChF,WAAW,EAAE,WAAW,EAAE,CAAC;IAC3B,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C,+BA4CA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import { Image } from 'lucide-react';
|
|
4
|
+
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, TopBar, TopBarIcon } from '@orthacms/design-system';
|
|
5
|
+
import { ROOT_FOLDER_ID } from '../../constants';
|
|
6
|
+
/** Intl descriptors for {@link MediaTopBar}, co-located. */
|
|
7
|
+
const messages = defineMessages({
|
|
8
|
+
nav: { id: 'media.topbar.nav', defaultMessage: 'Breadcrumb' },
|
|
9
|
+
root: { id: 'media.topbar.root', defaultMessage: 'Media Library' }
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* The page top bar over the Media Library — the teal `Image` tile (matching the
|
|
13
|
+
* plugin's workspace-nav entry) beside a breadcrumb tracing the open location:
|
|
14
|
+
* Media Library › each ancestor folder › the open folder. Sticky to the top of
|
|
15
|
+
* the page, spanning the folder sidebar and the browser both, the same
|
|
16
|
+
* page-context header every other admin page carries.
|
|
17
|
+
*
|
|
18
|
+
* Unlike the Content Library's bar this one is **not** route-derived: folder
|
|
19
|
+
* navigation here is component state, not a URL segment, so the crumbs come from
|
|
20
|
+
* the store's `breadcrumbs` and each one dispatches `onNavigate` rather than
|
|
21
|
+
* linking. They are still `BreadcrumbLink`s over real `<button>`s — a control
|
|
22
|
+
* that changes view without changing location is a button, not a link.
|
|
23
|
+
*/
|
|
24
|
+
export function MediaTopBar({ breadcrumbs, onNavigate }) {
|
|
25
|
+
const intl = useIntl();
|
|
26
|
+
const crumbs = [
|
|
27
|
+
{ id: ROOT_FOLDER_ID, name: intl.formatMessage(messages.root) },
|
|
28
|
+
...breadcrumbs.map((folder) => ({ id: folder.id, name: folder.name }))
|
|
29
|
+
];
|
|
30
|
+
const last = crumbs.length - 1;
|
|
31
|
+
return (_jsxs(TopBar, { children: [_jsx(TopBarIcon, { className: "bg-teal-soft text-teal-soft-foreground", children: _jsx(Image, {}) }), _jsx(Breadcrumb, { "aria-label": intl.formatMessage(messages.nav), children: _jsx(BreadcrumbList, { className: "flex-nowrap font-medium", children: crumbs.map((crumb, index) => [
|
|
32
|
+
index > 0 ? (_jsx(BreadcrumbSeparator, {}, `${crumb.id}-sep`)) : null,
|
|
33
|
+
_jsx(BreadcrumbItem, { className: "min-w-0 whitespace-nowrap", children: index === last ? (_jsx(BreadcrumbPage, { className: "flex min-w-0 items-center font-medium", children: crumb.name })) : (_jsx(BreadcrumbLink, { asChild: true, children: _jsx("button", { type: "button", onClick: () => onNavigate(crumb.id), children: crumb.name }) })) }, crumb.id)
|
|
34
|
+
]) }) })] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { UploadItem } from '../../../types/upload';
|
|
2
|
+
/**
|
|
3
|
+
* One file's row in the upload banner: its name and size, a right-hand status
|
|
4
|
+
* (a live percentage, a tick, or the failure reason), and — while it is running
|
|
5
|
+
* — a thin progress bar. A failed or cancelled row offers **Retry**; a pending
|
|
6
|
+
* or in-flight one offers **Cancel**. A settled row offers neither, so the
|
|
7
|
+
* control column doesn't flicker as the batch drains.
|
|
8
|
+
*/
|
|
9
|
+
export declare function MediaUploadRow({ item, onRetry, onCancel }: {
|
|
10
|
+
item: UploadItem;
|
|
11
|
+
onRetry: (id: string) => void;
|
|
12
|
+
onCancel: (id: string) => void;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/MediaUploadBanner/MediaUploadRow/index.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAiBxD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAC3B,IAAI,EACJ,OAAO,EACP,QAAQ,EACX,EAAE;IACC,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,+BA6FA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import { Button, Progress, Spinner, cn } from '@orthacms/design-system';
|
|
4
|
+
import { AlertCircle, Check, RotateCcw, X } from 'lucide-react';
|
|
5
|
+
import { UPLOAD_STATUS } from '../../../constants';
|
|
6
|
+
import { formatBytes } from '../../../utils/formatBytes';
|
|
7
|
+
/** Intl descriptors for {@link MediaUploadRow}, co-located. */
|
|
8
|
+
const messages = defineMessages({
|
|
9
|
+
waiting: { id: 'media.uploadRow.waiting', defaultMessage: 'Waiting…' },
|
|
10
|
+
done: { id: 'media.uploadRow.done', defaultMessage: 'Uploaded' },
|
|
11
|
+
cancelled: { id: 'media.uploadRow.cancelled', defaultMessage: 'Cancelled' },
|
|
12
|
+
percent: { id: 'media.uploadRow.percent', defaultMessage: '{percent}%' },
|
|
13
|
+
progressLabel: {
|
|
14
|
+
id: 'media.uploadRow.progressLabel',
|
|
15
|
+
defaultMessage: 'Upload progress for {name}'
|
|
16
|
+
},
|
|
17
|
+
retry: { id: 'media.uploadRow.retry', defaultMessage: 'Retry {name}' },
|
|
18
|
+
cancel: { id: 'media.uploadRow.cancel', defaultMessage: 'Cancel {name}' }
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* One file's row in the upload banner: its name and size, a right-hand status
|
|
22
|
+
* (a live percentage, a tick, or the failure reason), and — while it is running
|
|
23
|
+
* — a thin progress bar. A failed or cancelled row offers **Retry**; a pending
|
|
24
|
+
* or in-flight one offers **Cancel**. A settled row offers neither, so the
|
|
25
|
+
* control column doesn't flicker as the batch drains.
|
|
26
|
+
*/
|
|
27
|
+
export function MediaUploadRow({ item, onRetry, onCancel }) {
|
|
28
|
+
const intl = useIntl();
|
|
29
|
+
const isFailed = item.status === UPLOAD_STATUS.Failed;
|
|
30
|
+
const isDone = item.status === UPLOAD_STATUS.Done;
|
|
31
|
+
const isCancelled = item.status === UPLOAD_STATUS.Cancelled;
|
|
32
|
+
const isUploading = item.status === UPLOAD_STATUS.Uploading;
|
|
33
|
+
const isPending = item.status === UPLOAD_STATUS.Pending;
|
|
34
|
+
return (_jsxs("li", { className: "flex flex-col gap-1 py-1.5", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [isDone ? (_jsx(Check, { className: "size-4 shrink-0 text-primary", "aria-hidden": true })) : isFailed ? (_jsx(AlertCircle, { className: "size-4 shrink-0 text-destructive", "aria-hidden": true })) : isUploading ? (_jsx(Spinner, { className: "size-4 shrink-0", "aria-hidden": true })) : (_jsx("span", { className: "size-4 shrink-0", "aria-hidden": true })), _jsx("span", { className: cn('min-w-0 flex-1 truncate', isCancelled && 'text-muted-foreground line-through'), children: item.name }), _jsx("span", { className: cn('shrink-0 text-xs tabular-nums text-muted-foreground', isFailed && 'text-destructive'), children: isFailed
|
|
35
|
+
? (item.error ?? formatBytes(item.size))
|
|
36
|
+
: isDone
|
|
37
|
+
? intl.formatMessage(messages.done)
|
|
38
|
+
: isCancelled
|
|
39
|
+
? intl.formatMessage(messages.cancelled)
|
|
40
|
+
: isPending
|
|
41
|
+
? intl.formatMessage(messages.waiting)
|
|
42
|
+
: intl.formatMessage(messages.percent, {
|
|
43
|
+
percent: item.progress
|
|
44
|
+
}) }), isFailed || isCancelled ? (_jsx(Button, { variant: "ghost", size: "icon", className: "size-7 shrink-0 shadow-none", "aria-label": intl.formatMessage(messages.retry, {
|
|
45
|
+
name: item.name
|
|
46
|
+
}), onClick: () => onRetry(item.id), children: _jsx(RotateCcw, { "aria-hidden": true }) })) : isDone ? null : (_jsx(Button, { variant: "ghost", size: "icon", className: "size-7 shrink-0 shadow-none", "aria-label": intl.formatMessage(messages.cancel, {
|
|
47
|
+
name: item.name
|
|
48
|
+
}), onClick: () => onCancel(item.id), children: _jsx(X, { "aria-hidden": true }) }))] }), isUploading ? (_jsx(Progress, { value: item.progress, className: "h-1", "aria-label": intl.formatMessage(messages.progressLabel, {
|
|
49
|
+
name: item.name
|
|
50
|
+
}) })) : null] }));
|
|
51
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { UploadItem, UploadSummary } from '../../types/upload';
|
|
2
|
+
/**
|
|
3
|
+
* The upload progress banner — the batch headline ("Uploading 3 of 5 · 47%"), an
|
|
4
|
+
* overall size-weighted bar while anything is running, and one
|
|
5
|
+
* {@link MediaUploadRow} per file. It sits above the browser rather than inside
|
|
6
|
+
* the upload dialog, so the dialog can close on submit and the user keeps
|
|
7
|
+
* browsing (and can queue more) while bytes move.
|
|
8
|
+
*
|
|
9
|
+
* `role="status"` sits on the **headline alone**, not the whole banner: a polite
|
|
10
|
+
* live region re-announces its entire contents on every change, so wrapping the
|
|
11
|
+
* file list would read all N rows aloud on each progress tick. Scoped to the
|
|
12
|
+
* headline, a screen-reader user hears "Uploading 3 of 5 · 47%" and then the
|
|
13
|
+
* completion, while per-file detail stays available to read on demand.
|
|
14
|
+
*
|
|
15
|
+
* Renders nothing when the queue is empty.
|
|
16
|
+
*/
|
|
17
|
+
export declare function MediaUploadBanner({ items, summary, onRetry, onCancel, onDismiss }: {
|
|
18
|
+
items: UploadItem[];
|
|
19
|
+
summary: UploadSummary;
|
|
20
|
+
onRetry: (id: string) => void;
|
|
21
|
+
onCancel: (id: string) => void;
|
|
22
|
+
onDismiss: () => void;
|
|
23
|
+
}): import("react").JSX.Element | null;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaUploadBanner/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAqCpE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,KAAK,EACL,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACZ,EAAE;IACC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,SAAS,EAAE,MAAM,IAAI,CAAC;CACzB,sCAwGA"}
|