@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,120 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
4
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, cn } from '@orthacms/design-system';
|
|
5
|
+
import { UploadCloud } from 'lucide-react';
|
|
6
|
+
import { StagedFileRow } from './StagedFileRow';
|
|
7
|
+
/** Stable default for `initialFiles`, so the seeding effect isn't re-run. */
|
|
8
|
+
const NO_FILES = [];
|
|
9
|
+
/** Intl descriptors for {@link UploadDialog}, co-located. */
|
|
10
|
+
const messages = defineMessages({
|
|
11
|
+
title: { id: 'media.upload.title', defaultMessage: 'Upload assets' },
|
|
12
|
+
description: {
|
|
13
|
+
id: 'media.upload.description',
|
|
14
|
+
defaultMessage: 'Files upload to {location}.'
|
|
15
|
+
},
|
|
16
|
+
dropHere: {
|
|
17
|
+
id: 'media.upload.dropHere',
|
|
18
|
+
defaultMessage: 'Drag & drop files here'
|
|
19
|
+
},
|
|
20
|
+
or: { id: 'media.upload.or', defaultMessage: 'or' },
|
|
21
|
+
browse: { id: 'media.upload.browse', defaultMessage: 'Browse files' },
|
|
22
|
+
/**
|
|
23
|
+
* The `sr-only` input is still a tab stop, so it needs a name of its own —
|
|
24
|
+
* axe flagged it as an unlabelled form element the moment the dialog's
|
|
25
|
+
* staged state was scanned. Distinct from the visible "Browse files" button
|
|
26
|
+
* that clicks it, so the two don't read as duplicates.
|
|
27
|
+
*/
|
|
28
|
+
fileInput: {
|
|
29
|
+
id: 'media.upload.fileInput',
|
|
30
|
+
defaultMessage: 'Choose files to upload'
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* No size is quoted. The cap is `config.plugins.media.maxUploadBytes`
|
|
34
|
+
* (50 MB by default) and the admin has no route that reports it, so the old
|
|
35
|
+
* "up to 250 MB each" was a number nothing checked — a 52 MB file staged
|
|
36
|
+
* happily and then 413'd after transferring in full.
|
|
37
|
+
*/
|
|
38
|
+
hint: {
|
|
39
|
+
id: 'media.upload.hint',
|
|
40
|
+
defaultMessage: 'Images, video, audio, and documents.'
|
|
41
|
+
},
|
|
42
|
+
staged: {
|
|
43
|
+
id: 'media.upload.staged',
|
|
44
|
+
defaultMessage: '{count, plural, one {# file ready} other {# files ready}}'
|
|
45
|
+
},
|
|
46
|
+
cancel: { id: 'media.upload.cancel', defaultMessage: 'Cancel' },
|
|
47
|
+
confirm: {
|
|
48
|
+
id: 'media.upload.confirm',
|
|
49
|
+
defaultMessage: 'Upload {count, plural, one {# file} other {# files}}'
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* The upload modal — a drag-and-drop zone plus a multi-file picker that stages
|
|
54
|
+
* the chosen files into a removable, **previewed** list before committing:
|
|
55
|
+
* images render a real thumbnail off a local object URL (see
|
|
56
|
+
* {@link StagedFileRow}), other kinds a glyph, so a wrong file is caught before
|
|
57
|
+
* a byte moves. Files accumulate across several drops/picks.
|
|
58
|
+
*
|
|
59
|
+
* On confirm the parent's `onUpload` receives the real `File` objects and the
|
|
60
|
+
* dialog closes immediately — progress then lives in the caller's upload banner,
|
|
61
|
+
* not here, so the user can keep browsing (and queue more) while bytes move.
|
|
62
|
+
* Staged files reset whenever the dialog reopens, seeded from `initialFiles`.
|
|
63
|
+
*
|
|
64
|
+
* **Shared by both upload entry points** — the Media Library's toolbar and a
|
|
65
|
+
* content record's media field — so staging, previewing, and the copy are
|
|
66
|
+
* identical in the two places. The field narrows it with `multiple` / `accept` /
|
|
67
|
+
* its own `description` + `hint`; everything else is the same dialog.
|
|
68
|
+
*/
|
|
69
|
+
export function UploadDialog({ open, onOpenChange, locationLabel, description, hint, confirmLabel, accept, multiple = true, initialFiles = NO_FILES, collectAlt = false, onUpload }) {
|
|
70
|
+
const intl = useIntl();
|
|
71
|
+
const inputRef = useRef(null);
|
|
72
|
+
const [staged, setStaged] = useState([]);
|
|
73
|
+
const [dragging, setDragging] = useState(false);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (open) {
|
|
76
|
+
setStaged(initialFiles.map((file) => ({ file })));
|
|
77
|
+
setDragging(false);
|
|
78
|
+
}
|
|
79
|
+
}, [open, initialFiles]);
|
|
80
|
+
const addFiles = (files) => {
|
|
81
|
+
if (!files)
|
|
82
|
+
return;
|
|
83
|
+
const picked = Array.from(files).map((file) => ({ file }));
|
|
84
|
+
// A single-asset field holds one file: the newest pick replaces the last.
|
|
85
|
+
setStaged((prev) => multiple ? [...prev, ...picked] : picked.slice(0, 1));
|
|
86
|
+
};
|
|
87
|
+
const removeAt = (index) => {
|
|
88
|
+
setStaged((prev) => prev.filter((_, i) => i !== index));
|
|
89
|
+
};
|
|
90
|
+
const setAltAt = (index, alt) => {
|
|
91
|
+
setStaged((prev) => prev.map((item, i) => (i === index ? { ...item, alt } : item)));
|
|
92
|
+
};
|
|
93
|
+
const submit = () => {
|
|
94
|
+
if (staged.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
onUpload(staged);
|
|
97
|
+
onOpenChange(false);
|
|
98
|
+
};
|
|
99
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: intl.formatMessage(messages.title) }), _jsx(DialogDescription, { children: description ??
|
|
100
|
+
intl.formatMessage(messages.description, {
|
|
101
|
+
location: locationLabel
|
|
102
|
+
}) })] }), _jsxs("div", { onDragOver: (event) => {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
setDragging(true);
|
|
105
|
+
}, onDragLeave: () => setDragging(false), onDrop: (event) => {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
setDragging(false);
|
|
108
|
+
addFiles(event.dataTransfer.files);
|
|
109
|
+
}, className: cn('flex flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed px-6 py-10 text-center transition-colors', dragging
|
|
110
|
+
? 'border-primary bg-primary/5'
|
|
111
|
+
: 'border-border bg-muted/30'), children: [_jsx(UploadCloud, { className: "size-8 text-muted-foreground", "aria-hidden": true }), _jsx("p", { className: "text-sm font-medium", children: intl.formatMessage(messages.dropHere) }), _jsx("p", { className: "text-xs text-muted-foreground", children: intl.formatMessage(messages.or) }), _jsx(Button, { variant: "outline", size: "sm", className: "shadow-none", onClick: () => inputRef.current?.click(), children: intl.formatMessage(messages.browse) }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: hint ?? intl.formatMessage(messages.hint) }), _jsx("input", { ref: inputRef, type: "file", accept: accept, multiple: multiple, "aria-label": intl.formatMessage(messages.fileInput), className: "sr-only", onChange: (event) => {
|
|
112
|
+
addFiles(event.target.files);
|
|
113
|
+
event.target.value = '';
|
|
114
|
+
} })] }), staged.length > 0 ? (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-medium text-muted-foreground", children: intl.formatMessage(messages.staged, {
|
|
115
|
+
count: staged.length
|
|
116
|
+
}) }), _jsx("ul", { className: "flex max-h-56 flex-col gap-1 overflow-auto", children: staged.map((item, index) => (_jsx(StagedFileRow, { file: item.file, alt: item.alt ?? '', collectAlt: collectAlt, onAltChange: (value) => setAltAt(index, value), onRemove: () => removeAt(index) }, `${item.file.name}-${item.file.lastModified}-${index}`))) })] })) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: intl.formatMessage(messages.cancel) }), _jsx(Button, { onClick: submit, disabled: staged.length === 0, children: confirmLabel ??
|
|
117
|
+
intl.formatMessage(messages.confirm, {
|
|
118
|
+
count: staged.length
|
|
119
|
+
}) })] })] }) }));
|
|
120
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WysiwygMediaSourceContext } from '@orthacms/wysiwyg-admin';
|
|
2
|
+
/**
|
|
3
|
+
* The Media Library as a **rich-text media source** — the plugin's contribution
|
|
4
|
+
* to `WYSIWYG_MEDIA_SLOT`.
|
|
5
|
+
*
|
|
6
|
+
* It is the same {@link MediaPickerDialog} a media *field* opens, which is the
|
|
7
|
+
* point: browsing, searching, and folder navigation are one implementation, and
|
|
8
|
+
* an author moving between a media field and a body meets the same picker.
|
|
9
|
+
*
|
|
10
|
+
* `multiple` is on, because placing three images in a row is a normal thing to
|
|
11
|
+
* do in a body and re-opening the picker twice for it is not.
|
|
12
|
+
*/
|
|
13
|
+
export declare function WysiwygLibrarySource({ open, onOpenChange, accept, onInsert }: WysiwygMediaSourceContext): 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/WysiwygLibrarySource/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAKzE;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,QAAQ,EACX,EAAE,yBAAyB,+BAmB3B"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { MediaPickerDialog } from '../MediaPickerDialog';
|
|
3
|
+
import { toWysiwygEmbed } from '../../utils/toWysiwygEmbed';
|
|
4
|
+
/**
|
|
5
|
+
* The Media Library as a **rich-text media source** — the plugin's contribution
|
|
6
|
+
* to `WYSIWYG_MEDIA_SLOT`.
|
|
7
|
+
*
|
|
8
|
+
* It is the same {@link MediaPickerDialog} a media *field* opens, which is the
|
|
9
|
+
* point: browsing, searching, and folder navigation are one implementation, and
|
|
10
|
+
* an author moving between a media field and a body meets the same picker.
|
|
11
|
+
*
|
|
12
|
+
* `multiple` is on, because placing three images in a row is a normal thing to
|
|
13
|
+
* do in a body and re-opening the picker twice for it is not.
|
|
14
|
+
*/
|
|
15
|
+
export function WysiwygLibrarySource({ open, onOpenChange, accept, onInsert }) {
|
|
16
|
+
return (_jsx(MediaPickerDialog, { open: open, onOpenChange: onOpenChange, multiple: true,
|
|
17
|
+
// The editor's kinds, handed to the picker as its own restriction —
|
|
18
|
+
// so an audio file or a PDF is filtered out of the grid rather than
|
|
19
|
+
// being pickable and then silently dropped on insert.
|
|
20
|
+
accept: { kinds: [...accept] }, onConfirm: (assets) => {
|
|
21
|
+
const embeds = assets
|
|
22
|
+
.map(toWysiwygEmbed)
|
|
23
|
+
.filter((embed) => embed !== null);
|
|
24
|
+
if (embeds.length > 0)
|
|
25
|
+
onInsert(embeds);
|
|
26
|
+
onOpenChange(false);
|
|
27
|
+
} }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WysiwygMediaSourceContext } from '@orthacms/wysiwyg-admin';
|
|
2
|
+
/**
|
|
3
|
+
* Upload as a **rich-text media source** — the plugin's second contribution to
|
|
4
|
+
* `WYSIWYG_MEDIA_SLOT`.
|
|
5
|
+
*
|
|
6
|
+
* Files go **into the Media Library** and are then placed in the text, rather
|
|
7
|
+
* than being embedded as data URIs or attached to the record alone. That is the
|
|
8
|
+
* behaviour a CMS wants: an image used in a body is an asset like any other —
|
|
9
|
+
* findable, reusable, replaceable — and a body full of base64 is unshippable.
|
|
10
|
+
*
|
|
11
|
+
* ### Why this uploads immediately, unlike a media *field*
|
|
12
|
+
*
|
|
13
|
+
* A media field defers its uploads to the record's save
|
|
14
|
+
* (`usePendingMediaUploads`), because a field holds an id and an abandoned edit
|
|
15
|
+
* would otherwise litter the library. A body holds a **URL**, and there is no
|
|
16
|
+
* URL until the bytes exist — so the upload has to happen now. The trade is
|
|
17
|
+
* real and one-directional: abandoning the edit leaves the asset in the
|
|
18
|
+
* library, where it is visible and deletable, rather than leaving the body
|
|
19
|
+
* pointing at nothing.
|
|
20
|
+
*
|
|
21
|
+
* Each file is its own request, so one failure costs one file: the rest still
|
|
22
|
+
* insert, and the toast names the one that didn't.
|
|
23
|
+
*/
|
|
24
|
+
export declare function WysiwygUploadSource({ open, onOpenChange, accept, onInsert }: WysiwygMediaSourceContext): import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/WysiwygUploadSource/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAoCzE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,QAAQ,EACX,EAAE,yBAAyB,+BAgE3B"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
4
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
5
|
+
import { toast } from '@orthacms/design-system';
|
|
6
|
+
import { useCurrentWorkspace } from '@orthacms/workspaces-admin';
|
|
7
|
+
import { ROOT_FOLDER_ID } from '../../constants';
|
|
8
|
+
import { httpMediaGateway } from '../../infrastructure/httpMediaGateway';
|
|
9
|
+
import { mediaKeys } from '../../infrastructure/mediaKeys';
|
|
10
|
+
import { acceptsFile } from '../../utils/mediaAccept';
|
|
11
|
+
import { toWysiwygEmbed } from '../../utils/toWysiwygEmbed';
|
|
12
|
+
import { UploadDialog } from '../UploadDialog';
|
|
13
|
+
const messages = defineMessages({
|
|
14
|
+
location: {
|
|
15
|
+
id: 'media.wysiwygUpload.location',
|
|
16
|
+
defaultMessage: 'All media'
|
|
17
|
+
},
|
|
18
|
+
description: {
|
|
19
|
+
id: 'media.wysiwygUpload.description',
|
|
20
|
+
defaultMessage: 'Files are added to the Media Library, then placed in the text.'
|
|
21
|
+
},
|
|
22
|
+
confirm: {
|
|
23
|
+
id: 'media.wysiwygUpload.confirm',
|
|
24
|
+
defaultMessage: 'Upload and insert'
|
|
25
|
+
},
|
|
26
|
+
rejected: {
|
|
27
|
+
id: 'media.wysiwygUpload.rejected',
|
|
28
|
+
defaultMessage: '{name} isn’t an image or a video, so it can’t go in the text.'
|
|
29
|
+
},
|
|
30
|
+
failed: {
|
|
31
|
+
id: 'media.wysiwygUpload.failed',
|
|
32
|
+
defaultMessage: 'Couldn’t upload {name}. Nothing was inserted for it.'
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
/** The file-picker `accept` matching the kinds a body can hold. */
|
|
36
|
+
const FILE_ACCEPT = 'image/*,video/*';
|
|
37
|
+
/**
|
|
38
|
+
* Upload as a **rich-text media source** — the plugin's second contribution to
|
|
39
|
+
* `WYSIWYG_MEDIA_SLOT`.
|
|
40
|
+
*
|
|
41
|
+
* Files go **into the Media Library** and are then placed in the text, rather
|
|
42
|
+
* than being embedded as data URIs or attached to the record alone. That is the
|
|
43
|
+
* behaviour a CMS wants: an image used in a body is an asset like any other —
|
|
44
|
+
* findable, reusable, replaceable — and a body full of base64 is unshippable.
|
|
45
|
+
*
|
|
46
|
+
* ### Why this uploads immediately, unlike a media *field*
|
|
47
|
+
*
|
|
48
|
+
* A media field defers its uploads to the record's save
|
|
49
|
+
* (`usePendingMediaUploads`), because a field holds an id and an abandoned edit
|
|
50
|
+
* would otherwise litter the library. A body holds a **URL**, and there is no
|
|
51
|
+
* URL until the bytes exist — so the upload has to happen now. The trade is
|
|
52
|
+
* real and one-directional: abandoning the edit leaves the asset in the
|
|
53
|
+
* library, where it is visible and deletable, rather than leaving the body
|
|
54
|
+
* pointing at nothing.
|
|
55
|
+
*
|
|
56
|
+
* Each file is its own request, so one failure costs one file: the rest still
|
|
57
|
+
* insert, and the toast names the one that didn't.
|
|
58
|
+
*/
|
|
59
|
+
export function WysiwygUploadSource({ open, onOpenChange, accept, onInsert }) {
|
|
60
|
+
const intl = useIntl();
|
|
61
|
+
const workspace = useCurrentWorkspace();
|
|
62
|
+
const queryClient = useQueryClient();
|
|
63
|
+
const [busy, setBusy] = useState(false);
|
|
64
|
+
const upload = async (files) => {
|
|
65
|
+
onOpenChange(false);
|
|
66
|
+
if (busy)
|
|
67
|
+
return;
|
|
68
|
+
setBusy(true);
|
|
69
|
+
// Checked before anything is sent: a PDF would upload fine and then be
|
|
70
|
+
// silently unusable, which reads as the editor losing the file.
|
|
71
|
+
const usable = files.filter((file) => {
|
|
72
|
+
if (acceptsFile({ kinds: [...accept] }, file))
|
|
73
|
+
return true;
|
|
74
|
+
toast.error(intl.formatMessage(messages.rejected, { name: file.name }));
|
|
75
|
+
return false;
|
|
76
|
+
});
|
|
77
|
+
let uploaded = false;
|
|
78
|
+
for (const file of usable) {
|
|
79
|
+
try {
|
|
80
|
+
const asset = await httpMediaGateway.uploadFile(ROOT_FOLDER_ID, file);
|
|
81
|
+
uploaded = true;
|
|
82
|
+
const embed = toWysiwygEmbed(asset);
|
|
83
|
+
// Inserted one at a time, in order, so a long batch appears as
|
|
84
|
+
// it lands rather than all at the end.
|
|
85
|
+
if (embed)
|
|
86
|
+
onInsert([embed]);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
toast.error(intl.formatMessage(messages.failed, { name: file.name }));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// The library is stale the moment anything landed — even if a later
|
|
93
|
+
// file failed.
|
|
94
|
+
if (uploaded) {
|
|
95
|
+
void queryClient.invalidateQueries({
|
|
96
|
+
queryKey: mediaKeys.all(workspace.id)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
setBusy(false);
|
|
100
|
+
};
|
|
101
|
+
return (_jsx(UploadDialog, { open: open, onOpenChange: onOpenChange, locationLabel: intl.formatMessage(messages.location), description: intl.formatMessage(messages.description), confirmLabel: intl.formatMessage(messages.confirm), accept: FILE_ACCEPT, multiple: true, onUpload: (uploads) => void upload(uploads.map((staged) => staged.file)) }));
|
|
102
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared string constants for the Media Library, kept here so route segments,
|
|
3
|
+
* layout keys, and enum-like option values live in one place instead of being
|
|
4
|
+
* repeated as magic literals across the page, sidebar, toolbar, and dialogs.
|
|
5
|
+
*/
|
|
6
|
+
/** Permission required to browse media. Mirrors the (future) server matrix. */
|
|
7
|
+
export declare const MEDIA_READ = "media:read";
|
|
8
|
+
/** Permission required to upload / create assets. */
|
|
9
|
+
export declare const MEDIA_CREATE = "media:create";
|
|
10
|
+
/** Permission required to rename / move / replace assets. */
|
|
11
|
+
export declare const MEDIA_UPDATE = "media:update";
|
|
12
|
+
/** Permission required to delete assets and folders. */
|
|
13
|
+
export declare const MEDIA_DELETE = "media:delete";
|
|
14
|
+
/**
|
|
15
|
+
* Path segment under the workspace shell where the Media Library mounts
|
|
16
|
+
* (`/workspaces/:id/media`). Shared by the plugin's slot contribution and the
|
|
17
|
+
* page so they can never drift apart.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MEDIA_SEGMENT = "media";
|
|
20
|
+
/** The synthetic root folder id — the library's top level ("All media"). */
|
|
21
|
+
export declare const ROOT_FOLDER_ID = "root";
|
|
22
|
+
/** Where one queued upload has got to. */
|
|
23
|
+
export declare const UPLOAD_STATUS: {
|
|
24
|
+
/** Queued, waiting for a concurrency slot. */
|
|
25
|
+
readonly Pending: "pending";
|
|
26
|
+
/** Bytes are on the wire. */
|
|
27
|
+
readonly Uploading: "uploading";
|
|
28
|
+
readonly Done: "done";
|
|
29
|
+
readonly Failed: "failed";
|
|
30
|
+
/** Aborted by the user before it finished. */
|
|
31
|
+
readonly Cancelled: "cancelled";
|
|
32
|
+
};
|
|
33
|
+
/** One queued upload's state. */
|
|
34
|
+
export type UploadStatus = (typeof UPLOAD_STATUS)[keyof typeof UPLOAD_STATUS];
|
|
35
|
+
/**
|
|
36
|
+
* How many files upload at once. Above ~3 the browser queues the rest anyway
|
|
37
|
+
* (per-host connection limits) while each one's progress bar crawls, which
|
|
38
|
+
* reads as a stall; a small pool keeps every active bar visibly moving.
|
|
39
|
+
*/
|
|
40
|
+
export declare const UPLOAD_CONCURRENCY = 3;
|
|
41
|
+
/** The kinds of asset the library recognises, driving icons and filters. */
|
|
42
|
+
export declare const MEDIA_KIND: {
|
|
43
|
+
readonly Image: "image";
|
|
44
|
+
readonly Video: "video";
|
|
45
|
+
readonly Audio: "audio";
|
|
46
|
+
readonly Document: "document";
|
|
47
|
+
readonly Archive: "archive";
|
|
48
|
+
};
|
|
49
|
+
/** One recognised asset kind. */
|
|
50
|
+
export type MediaKind = (typeof MEDIA_KIND)[keyof typeof MEDIA_KIND];
|
|
51
|
+
/** The special "no filter" sentinel for the kind filter control. */
|
|
52
|
+
export declare const KIND_FILTER_ALL = "all";
|
|
53
|
+
/**
|
|
54
|
+
* Assets per page in the library grid, and the sizes the pager offers.
|
|
55
|
+
*
|
|
56
|
+
* 24 matches the server's own default and divides evenly by the grid's column
|
|
57
|
+
* counts (2/3/4/5), so a full page never leaves a ragged last row. The server
|
|
58
|
+
* caps a page at 100, so nothing above it is offered.
|
|
59
|
+
*/
|
|
60
|
+
export declare const DEFAULT_ASSETS_PAGE_SIZE = 24;
|
|
61
|
+
/** The page sizes the library's pager offers, smallest first. */
|
|
62
|
+
export declare const ASSETS_PAGE_SIZE_OPTIONS: number[];
|
|
63
|
+
/**
|
|
64
|
+
* Assets fetched by a surface that browses without a pager — the picker
|
|
65
|
+
* dialog, which narrows by searching rather than by paging. The server's
|
|
66
|
+
* ceiling, so it is one request whatever the folder holds.
|
|
67
|
+
*/
|
|
68
|
+
export declare const PICKER_ASSETS_PAGE_SIZE = 100;
|
|
69
|
+
/** How long the search box waits after a keystroke before it queries. */
|
|
70
|
+
export declare const SEARCH_DEBOUNCE_MS = 300;
|
|
71
|
+
/** How the visible assets are ordered. */
|
|
72
|
+
export declare const MEDIA_SORT: {
|
|
73
|
+
readonly NameAsc: "name-asc";
|
|
74
|
+
readonly NameDesc: "name-desc";
|
|
75
|
+
readonly Newest: "newest";
|
|
76
|
+
readonly Oldest: "oldest";
|
|
77
|
+
readonly Largest: "largest";
|
|
78
|
+
readonly Smallest: "smallest";
|
|
79
|
+
};
|
|
80
|
+
/** One asset-ordering option. */
|
|
81
|
+
export type MediaSort = (typeof MEDIA_SORT)[keyof typeof MEDIA_SORT];
|
|
82
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/constants/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,UAAU,eAAe,CAAC;AAEvC,qDAAqD;AACrD,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C,6DAA6D;AAC7D,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C,wDAAwD;AACxD,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,aAAa,UAAU,CAAC;AAErC,4EAA4E;AAC5E,eAAO,MAAM,cAAc,SAAS,CAAC;AAErC,0CAA0C;AAC1C,eAAO,MAAM,aAAa;IACtB,8CAA8C;;IAE9C,6BAA6B;;;;IAI7B,8CAA8C;;CAExC,CAAC;AAEX,iCAAiC;AACjC,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC,4EAA4E;AAC5E,eAAO,MAAM,UAAU;;;;;;CAMb,CAAC;AAEX,iCAAiC;AACjC,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAErE,oEAAoE;AACpE,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,UAAe,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C,yEAAyE;AACzE,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,0CAA0C;AAC1C,eAAO,MAAM,UAAU;;;;;;;CAOb,CAAC;AAEX,iCAAiC;AACjC,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared string constants for the Media Library, kept here so route segments,
|
|
3
|
+
* layout keys, and enum-like option values live in one place instead of being
|
|
4
|
+
* repeated as magic literals across the page, sidebar, toolbar, and dialogs.
|
|
5
|
+
*/
|
|
6
|
+
/** Permission required to browse media. Mirrors the (future) server matrix. */
|
|
7
|
+
export const MEDIA_READ = 'media:read';
|
|
8
|
+
/** Permission required to upload / create assets. */
|
|
9
|
+
export const MEDIA_CREATE = 'media:create';
|
|
10
|
+
/** Permission required to rename / move / replace assets. */
|
|
11
|
+
export const MEDIA_UPDATE = 'media:update';
|
|
12
|
+
/** Permission required to delete assets and folders. */
|
|
13
|
+
export const MEDIA_DELETE = 'media:delete';
|
|
14
|
+
/**
|
|
15
|
+
* Path segment under the workspace shell where the Media Library mounts
|
|
16
|
+
* (`/workspaces/:id/media`). Shared by the plugin's slot contribution and the
|
|
17
|
+
* page so they can never drift apart.
|
|
18
|
+
*/
|
|
19
|
+
export const MEDIA_SEGMENT = 'media';
|
|
20
|
+
/** The synthetic root folder id — the library's top level ("All media"). */
|
|
21
|
+
export const ROOT_FOLDER_ID = 'root';
|
|
22
|
+
/** Where one queued upload has got to. */
|
|
23
|
+
export const UPLOAD_STATUS = {
|
|
24
|
+
/** Queued, waiting for a concurrency slot. */
|
|
25
|
+
Pending: 'pending',
|
|
26
|
+
/** Bytes are on the wire. */
|
|
27
|
+
Uploading: 'uploading',
|
|
28
|
+
Done: 'done',
|
|
29
|
+
Failed: 'failed',
|
|
30
|
+
/** Aborted by the user before it finished. */
|
|
31
|
+
Cancelled: 'cancelled'
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* How many files upload at once. Above ~3 the browser queues the rest anyway
|
|
35
|
+
* (per-host connection limits) while each one's progress bar crawls, which
|
|
36
|
+
* reads as a stall; a small pool keeps every active bar visibly moving.
|
|
37
|
+
*/
|
|
38
|
+
export const UPLOAD_CONCURRENCY = 3;
|
|
39
|
+
/** The kinds of asset the library recognises, driving icons and filters. */
|
|
40
|
+
export const MEDIA_KIND = {
|
|
41
|
+
Image: 'image',
|
|
42
|
+
Video: 'video',
|
|
43
|
+
Audio: 'audio',
|
|
44
|
+
Document: 'document',
|
|
45
|
+
Archive: 'archive'
|
|
46
|
+
};
|
|
47
|
+
/** The special "no filter" sentinel for the kind filter control. */
|
|
48
|
+
export const KIND_FILTER_ALL = 'all';
|
|
49
|
+
/**
|
|
50
|
+
* Assets per page in the library grid, and the sizes the pager offers.
|
|
51
|
+
*
|
|
52
|
+
* 24 matches the server's own default and divides evenly by the grid's column
|
|
53
|
+
* counts (2/3/4/5), so a full page never leaves a ragged last row. The server
|
|
54
|
+
* caps a page at 100, so nothing above it is offered.
|
|
55
|
+
*/
|
|
56
|
+
export const DEFAULT_ASSETS_PAGE_SIZE = 24;
|
|
57
|
+
/** The page sizes the library's pager offers, smallest first. */
|
|
58
|
+
export const ASSETS_PAGE_SIZE_OPTIONS = [24, 48, 96];
|
|
59
|
+
/**
|
|
60
|
+
* Assets fetched by a surface that browses without a pager — the picker
|
|
61
|
+
* dialog, which narrows by searching rather than by paging. The server's
|
|
62
|
+
* ceiling, so it is one request whatever the folder holds.
|
|
63
|
+
*/
|
|
64
|
+
export const PICKER_ASSETS_PAGE_SIZE = 100;
|
|
65
|
+
/** How long the search box waits after a keystroke before it queries. */
|
|
66
|
+
export const SEARCH_DEBOUNCE_MS = 300;
|
|
67
|
+
/** How the visible assets are ordered. */
|
|
68
|
+
export const MEDIA_SORT = {
|
|
69
|
+
NameAsc: 'name-asc',
|
|
70
|
+
NameDesc: 'name-desc',
|
|
71
|
+
Newest: 'newest',
|
|
72
|
+
Oldest: 'oldest',
|
|
73
|
+
Largest: 'largest',
|
|
74
|
+
Smallest: 'smallest'
|
|
75
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query keys for the media Insights caches.
|
|
3
|
+
*
|
|
4
|
+
* Workspace-scoped for the same reason as every other media key: the workspace
|
|
5
|
+
* only reaches the server as an ambient header, which is never sent on a cache
|
|
6
|
+
* hit — so without the id in the key, switching workspaces would serve the
|
|
7
|
+
* previous one's numbers and never refetch.
|
|
8
|
+
*/
|
|
9
|
+
export declare const mediaInsightsKeys: {
|
|
10
|
+
all: (workspaceId: string) => readonly ["media-insights", string];
|
|
11
|
+
storage: (workspaceId: string) => readonly ["media-insights", string, "storage"];
|
|
12
|
+
uploads: (workspaceId: string, days: number) => readonly ["media-insights", string, "uploads", number];
|
|
13
|
+
alt: (workspaceId: string) => readonly ["media-insights", string, "alt"];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Loads assets and bytes per media kind.
|
|
17
|
+
*
|
|
18
|
+
* Both the Overview storage tile and the full storage widget call this; one
|
|
19
|
+
* query key means one request serves both while each keeps its own state.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useMediaStorage(): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../infrastructure/mediaInsightsGateway").MediaStorage>, Error>;
|
|
22
|
+
/** Loads assets uploaded per time bucket across the selected range. */
|
|
23
|
+
export declare function useMediaUploads(): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../infrastructure/mediaInsightsGateway").MediaUploads>, Error>;
|
|
24
|
+
/**
|
|
25
|
+
* Loads alt-text coverage across the workspace's images.
|
|
26
|
+
*
|
|
27
|
+
* Takes no range: accessibility debt is a standing total, not something that
|
|
28
|
+
* happened in the last 30 days, and windowing it would make the number shrink
|
|
29
|
+
* whenever someone narrowed the range.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useMediaAltCoverage(): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../infrastructure/mediaInsightsGateway").MediaAltCoverage>, Error>;
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/useMediaInsights/index.ts"],"names":[],"mappings":"AAUA;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;uBACP,MAAM;2BACF,MAAM;2BAEN,MAAM,QAAQ,MAAM;uBAExB,MAAM;CAE5B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,eAAe,qIAgB9B;AAED,uEAAuE;AACvE,wBAAgB,eAAe,qIAiB9B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,yIAgBlC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { STALE_TIME } from '@orthacms/utils-admin';
|
|
3
|
+
import { useCurrentWorkspace } from '@orthacms/workspaces-admin';
|
|
4
|
+
import { useHasPermission } from '@orthacms/identity-admin';
|
|
5
|
+
import { useInsightsRange } from '@orthacms/insights-admin';
|
|
6
|
+
import { httpMediaInsightsGateway } from '../../infrastructure/httpMediaInsightsGateway';
|
|
7
|
+
/** Permission every media Insights read is gated on. */
|
|
8
|
+
const MEDIA_READ = 'media:read';
|
|
9
|
+
/**
|
|
10
|
+
* Query keys for the media Insights caches.
|
|
11
|
+
*
|
|
12
|
+
* Workspace-scoped for the same reason as every other media key: the workspace
|
|
13
|
+
* only reaches the server as an ambient header, which is never sent on a cache
|
|
14
|
+
* hit — so without the id in the key, switching workspaces would serve the
|
|
15
|
+
* previous one's numbers and never refetch.
|
|
16
|
+
*/
|
|
17
|
+
export const mediaInsightsKeys = {
|
|
18
|
+
all: (workspaceId) => ['media-insights', workspaceId],
|
|
19
|
+
storage: (workspaceId) => ['media-insights', workspaceId, 'storage'],
|
|
20
|
+
uploads: (workspaceId, days) => ['media-insights', workspaceId, 'uploads', days],
|
|
21
|
+
alt: (workspaceId) => ['media-insights', workspaceId, 'alt']
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Loads assets and bytes per media kind.
|
|
25
|
+
*
|
|
26
|
+
* Both the Overview storage tile and the full storage widget call this; one
|
|
27
|
+
* query key means one request serves both while each keeps its own state.
|
|
28
|
+
*/
|
|
29
|
+
export function useMediaStorage() {
|
|
30
|
+
const workspace = useCurrentWorkspace();
|
|
31
|
+
const canRead = useHasPermission(MEDIA_READ);
|
|
32
|
+
return useQuery({
|
|
33
|
+
queryKey: mediaInsightsKeys.storage(workspace.id),
|
|
34
|
+
queryFn: () => httpMediaInsightsGateway.storage(),
|
|
35
|
+
staleTime: STALE_TIME.Standard,
|
|
36
|
+
// One retry, not TanStack's default three. A widget that cannot load
|
|
37
|
+
// must SAY so, and three attempts with exponential backoff leave it
|
|
38
|
+
// sitting on a skeleton for about seven seconds first — on a page whose
|
|
39
|
+
// whole premise is that one failing card degrades alone, that reads as
|
|
40
|
+
// a hang rather than a failure. One retry still covers a transient blip.
|
|
41
|
+
retry: 1,
|
|
42
|
+
enabled: canRead
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Loads assets uploaded per time bucket across the selected range. */
|
|
46
|
+
export function useMediaUploads() {
|
|
47
|
+
const workspace = useCurrentWorkspace();
|
|
48
|
+
const { days } = useInsightsRange();
|
|
49
|
+
const canRead = useHasPermission(MEDIA_READ);
|
|
50
|
+
return useQuery({
|
|
51
|
+
queryKey: mediaInsightsKeys.uploads(workspace.id, days),
|
|
52
|
+
queryFn: () => httpMediaInsightsGateway.uploads(days),
|
|
53
|
+
staleTime: STALE_TIME.Standard,
|
|
54
|
+
// One retry, not TanStack's default three. A widget that cannot load
|
|
55
|
+
// must SAY so, and three attempts with exponential backoff leave it
|
|
56
|
+
// sitting on a skeleton for about seven seconds first — on a page whose
|
|
57
|
+
// whole premise is that one failing card degrades alone, that reads as
|
|
58
|
+
// a hang rather than a failure. One retry still covers a transient blip.
|
|
59
|
+
retry: 1,
|
|
60
|
+
enabled: canRead
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Loads alt-text coverage across the workspace's images.
|
|
65
|
+
*
|
|
66
|
+
* Takes no range: accessibility debt is a standing total, not something that
|
|
67
|
+
* happened in the last 30 days, and windowing it would make the number shrink
|
|
68
|
+
* whenever someone narrowed the range.
|
|
69
|
+
*/
|
|
70
|
+
export function useMediaAltCoverage() {
|
|
71
|
+
const workspace = useCurrentWorkspace();
|
|
72
|
+
const canRead = useHasPermission(MEDIA_READ);
|
|
73
|
+
return useQuery({
|
|
74
|
+
queryKey: mediaInsightsKeys.alt(workspace.id),
|
|
75
|
+
queryFn: () => httpMediaInsightsGateway.altCoverage(),
|
|
76
|
+
staleTime: STALE_TIME.Standard,
|
|
77
|
+
// One retry, not TanStack's default three. A widget that cannot load
|
|
78
|
+
// must SAY so, and three attempts with exponential backoff leave it
|
|
79
|
+
// sitting on a skeleton for about seven seconds first — on a page whose
|
|
80
|
+
// whole premise is that one failing card degrades alone, that reads as
|
|
81
|
+
// a hang rather than a failure. One retry still covers a transient blip.
|
|
82
|
+
retry: 1,
|
|
83
|
+
enabled: canRead
|
|
84
|
+
});
|
|
85
|
+
}
|