@orthacms/media-admin 0.10.3 → 0.12.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/dist/lib/components/AssetDetailPanel/MetaRow/index.d.ts +11 -0
- package/dist/lib/components/AssetDetailPanel/MetaRow/index.d.ts.map +1 -0
- package/dist/lib/components/AssetDetailPanel/MetaRow/index.js +9 -0
- package/dist/lib/components/AssetDetailPanel/index.d.ts +35 -0
- package/dist/lib/components/AssetDetailPanel/index.d.ts.map +1 -0
- package/dist/lib/components/AssetDetailPanel/index.js +158 -0
- package/dist/lib/components/FadeInImage/index.d.ts +24 -0
- package/dist/lib/components/FadeInImage/index.d.ts.map +1 -0
- package/dist/lib/components/FadeInImage/index.js +34 -0
- package/dist/lib/components/FolderActionsMenu/index.d.ts +3 -1
- package/dist/lib/components/FolderActionsMenu/index.d.ts.map +1 -1
- package/dist/lib/components/FolderActionsMenu/index.js +3 -3
- package/dist/lib/components/MediaAssetTable/index.d.ts +25 -0
- package/dist/lib/components/MediaAssetTable/index.d.ts.map +1 -0
- package/dist/lib/components/MediaAssetTable/index.js +62 -0
- package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.d.ts.map +1 -1
- package/dist/lib/components/MediaFieldControl/MediaFieldItem/index.js +2 -1
- package/dist/lib/components/MediaFieldControl/index.d.ts.map +1 -1
- package/dist/lib/components/MediaFieldControl/index.js +19 -14
- package/dist/lib/components/MediaFieldInput/index.d.ts +19 -0
- package/dist/lib/components/MediaFieldInput/index.d.ts.map +1 -0
- package/dist/lib/components/MediaFieldInput/index.js +23 -0
- package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.d.ts +17 -3
- package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.d.ts.map +1 -1
- package/dist/lib/components/MediaFoldersNav/MediaFolderTreeItem/index.js +16 -6
- package/dist/lib/components/MediaFoldersNav/index.d.ts +10 -5
- package/dist/lib/components/MediaFoldersNav/index.d.ts.map +1 -1
- package/dist/lib/components/MediaFoldersNav/index.js +17 -9
- package/dist/lib/components/MediaGrid/MediaAssetCard/index.d.ts +15 -4
- package/dist/lib/components/MediaGrid/MediaAssetCard/index.d.ts.map +1 -1
- package/dist/lib/components/MediaGrid/MediaAssetCard/index.js +27 -11
- package/dist/lib/components/MediaGrid/index.d.ts +10 -10
- package/dist/lib/components/MediaGrid/index.d.ts.map +1 -1
- package/dist/lib/components/MediaGrid/index.js +13 -15
- package/dist/lib/components/MediaSelectionBar/index.d.ts +9 -5
- package/dist/lib/components/MediaSelectionBar/index.d.ts.map +1 -1
- package/dist/lib/components/MediaSelectionBar/index.js +16 -10
- package/dist/lib/components/MediaThumbnail/index.d.ts +11 -7
- package/dist/lib/components/MediaThumbnail/index.d.ts.map +1 -1
- package/dist/lib/components/MediaThumbnail/index.js +10 -7
- package/dist/lib/components/MediaToolbar/index.d.ts +10 -8
- package/dist/lib/components/MediaToolbar/index.d.ts.map +1 -1
- package/dist/lib/components/MediaToolbar/index.js +37 -13
- package/dist/lib/components/MediaTopBar/index.d.ts +7 -1
- package/dist/lib/components/MediaTopBar/index.d.ts.map +1 -1
- package/dist/lib/components/MediaTopBar/index.js +6 -3
- package/dist/lib/components/MediaUploadBanner/index.d.ts +7 -5
- package/dist/lib/components/MediaUploadBanner/index.d.ts.map +1 -1
- package/dist/lib/components/MediaUploadBanner/index.js +15 -8
- package/dist/lib/components/UploadDialog/StagedFileRow/index.d.ts.map +1 -1
- package/dist/lib/components/UploadDialog/StagedFileRow/index.js +2 -1
- package/dist/lib/constants/index.d.ts +26 -0
- package/dist/lib/constants/index.d.ts.map +1 -1
- package/dist/lib/constants/index.js +24 -0
- package/dist/lib/hooks/useUploadQueue/index.d.ts +2 -0
- package/dist/lib/hooks/useUploadQueue/index.d.ts.map +1 -1
- package/dist/lib/hooks/useUploadQueue/index.js +15 -1
- package/dist/lib/pages/MediaLibraryPage/index.d.ts +6 -4
- package/dist/lib/pages/MediaLibraryPage/index.d.ts.map +1 -1
- package/dist/lib/pages/MediaLibraryPage/index.js +86 -38
- package/dist/lib/utils/fileExtension/index.d.ts +9 -0
- package/dist/lib/utils/fileExtension/index.d.ts.map +1 -0
- package/dist/lib/utils/fileExtension/index.js +14 -0
- package/dist/lib/utils/mediaPlugin/index.d.ts.map +1 -1
- package/dist/lib/utils/mediaPlugin/index.js +19 -24
- package/package.json +10 -10
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { KIND_FILTER_ALL, MEDIA_KIND, MEDIA_SORT } from '../../constants';
|
|
3
|
+
import { DensityProvider, InputGroup, InputGroupAddon, InputGroupInput, SegmentedControl, SegmentedControlItem, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, cn } from '@orthacms/design-system';
|
|
4
|
+
import { LayoutGrid, List, Search } from 'lucide-react';
|
|
5
|
+
import { KIND_FILTER_ALL, MEDIA_KIND, MEDIA_SORT, MEDIA_VIEW } from '../../constants';
|
|
6
6
|
/** Intl descriptors for {@link MediaToolbar}, co-located. */
|
|
7
7
|
const messages = defineMessages({
|
|
8
8
|
search: { id: 'media.toolbar.search', defaultMessage: 'Search assets' },
|
|
@@ -14,9 +14,9 @@ const messages = defineMessages({
|
|
|
14
14
|
id: 'media.toolbar.kindLabel',
|
|
15
15
|
defaultMessage: 'Filter by type'
|
|
16
16
|
},
|
|
17
|
-
kindAll: { id: 'media.toolbar.kindAll', defaultMessage: 'All
|
|
17
|
+
kindAll: { id: 'media.toolbar.kindAll', defaultMessage: 'All' },
|
|
18
18
|
kindImage: { id: 'media.toolbar.kindImage', defaultMessage: 'Images' },
|
|
19
|
-
kindVideo: { id: 'media.toolbar.kindVideo', defaultMessage: '
|
|
19
|
+
kindVideo: { id: 'media.toolbar.kindVideo', defaultMessage: 'Video' },
|
|
20
20
|
kindAudio: { id: 'media.toolbar.kindAudio', defaultMessage: 'Audio' },
|
|
21
21
|
kindDocument: {
|
|
22
22
|
id: 'media.toolbar.kindDocument',
|
|
@@ -48,15 +48,39 @@ const messages = defineMessages({
|
|
|
48
48
|
id: 'media.toolbar.sortSmallest',
|
|
49
49
|
defaultMessage: 'Smallest'
|
|
50
50
|
},
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
viewLabel: { id: 'media.toolbar.viewLabel', defaultMessage: 'View' },
|
|
52
|
+
viewGrid: { id: 'media.toolbar.viewGrid', defaultMessage: 'Grid view' },
|
|
53
|
+
viewList: { id: 'media.toolbar.viewList', defaultMessage: 'List view' }
|
|
53
54
|
});
|
|
55
|
+
/** The type chips, in reading order — "All" first, the kinds after it. */
|
|
56
|
+
const KIND_CHIPS = [
|
|
57
|
+
{ value: KIND_FILTER_ALL, label: 'kindAll' },
|
|
58
|
+
{ value: MEDIA_KIND.Image, label: 'kindImage' },
|
|
59
|
+
{ value: MEDIA_KIND.Video, label: 'kindVideo' },
|
|
60
|
+
{ value: MEDIA_KIND.Audio, label: 'kindAudio' },
|
|
61
|
+
{ value: MEDIA_KIND.Document, label: 'kindDocument' },
|
|
62
|
+
{ value: MEDIA_KIND.Archive, label: 'kindArchive' }
|
|
63
|
+
];
|
|
54
64
|
/**
|
|
55
|
-
* The Media Library's control bar —
|
|
56
|
-
*
|
|
57
|
-
*
|
|
65
|
+
* The Media Library's control bar — the **type chips** (All · Images · Video ·
|
|
66
|
+
* Audio · Documents · Archives, one pressed at a time; the same pill the
|
|
67
|
+
* records table's status chips are) on the left, then a search box, a sort
|
|
68
|
+
* select and the Grid / List switch on the right. Fully controlled: every value
|
|
69
|
+
* lives in the page and changes dispatch straight back up. New folder and
|
|
70
|
+
* Upload are the page's top-bar actions, not this bar's.
|
|
58
71
|
*/
|
|
59
|
-
export function MediaToolbar({ search, onSearchChange, kindFilter, onKindFilterChange, sort, onSortChange,
|
|
72
|
+
export function MediaToolbar({ search, onSearchChange, kindFilter, onKindFilterChange, sort, onSortChange, view, onViewChange }) {
|
|
60
73
|
const intl = useIntl();
|
|
61
|
-
return (_jsx(DensityProvider, { density: "compact", children: _jsxs("div", { className: "mb-4 flex flex-wrap items-center gap-2", children: [
|
|
74
|
+
return (_jsx(DensityProvider, { density: "compact", children: _jsxs("div", { className: "mb-4 flex flex-wrap items-center gap-2", children: [_jsx("div", { role: "group", "aria-label": intl.formatMessage(messages.kindLabel), className: "flex flex-wrap items-center gap-1.5", children: KIND_CHIPS.map(({ value, label }) => {
|
|
75
|
+
const on = kindFilter === value;
|
|
76
|
+
return (_jsx("button", { type: "button", "aria-pressed": on, onClick: () => onKindFilterChange(value), className: cn('inline-flex h-7 items-center rounded-full border px-2.5 text-xs font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring', on
|
|
77
|
+
? 'border-foreground bg-foreground text-background'
|
|
78
|
+
: 'bg-background text-foreground hover:bg-muted'), children: intl.formatMessage(messages[label]) }, value));
|
|
79
|
+
}) }), _jsxs("div", { className: "flex w-full flex-wrap items-center gap-2 sm:ml-auto sm:w-auto", children: [_jsxs(InputGroup, { className: "w-full shadow-none sm:w-[200px]", 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: sort, onValueChange: (value) => onSortChange(value), children: [_jsx(SelectTrigger, { className: "w-[132px] 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) })] })] }), _jsxs(SegmentedControl, { variant: "muted", value: view,
|
|
80
|
+
// A single toggle group reports '' when its pressed
|
|
81
|
+
// item is pressed again; a view switch can't be off.
|
|
82
|
+
onValueChange: (value) => {
|
|
83
|
+
if (value)
|
|
84
|
+
onViewChange(value);
|
|
85
|
+
}, "aria-label": intl.formatMessage(messages.viewLabel), children: [_jsx(SegmentedControlItem, { value: MEDIA_VIEW.Grid, "aria-label": intl.formatMessage(messages.viewGrid), className: "px-2", children: _jsx(LayoutGrid, { className: "size-4", "aria-hidden": true }) }), _jsx(SegmentedControlItem, { value: MEDIA_VIEW.List, "aria-label": intl.formatMessage(messages.viewList), className: "px-2", children: _jsx(List, { className: "size-4", "aria-hidden": true }) })] })] })] }) }));
|
|
62
86
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
1
2
|
import type { MediaFolder } from '../../types/mediaFolder';
|
|
2
3
|
/**
|
|
3
4
|
* The page top bar over the Media Library — the teal `Image` tile (matching the
|
|
@@ -11,10 +12,15 @@ import type { MediaFolder } from '../../types/mediaFolder';
|
|
|
11
12
|
* the store's `breadcrumbs` and each one dispatches `onNavigate` rather than
|
|
12
13
|
* linking. They are still `BreadcrumbLink`s over real `<button>`s — a control
|
|
13
14
|
* that changes view without changing location is a button, not a link.
|
|
15
|
+
*
|
|
16
|
+
* `actions` (New folder, Upload) sit at the bar's right end, where every other
|
|
17
|
+
* admin page keeps its page-level actions.
|
|
14
18
|
*/
|
|
15
|
-
export declare function MediaTopBar({ breadcrumbs, onNavigate }: {
|
|
19
|
+
export declare function MediaTopBar({ breadcrumbs, onNavigate, actions }: {
|
|
16
20
|
/** Ancestor chain of the open folder, root-first (excludes the root itself). */
|
|
17
21
|
breadcrumbs: MediaFolder[];
|
|
18
22
|
onNavigate: (folderId: string) => void;
|
|
23
|
+
/** Page-level actions, drawn at the bar's right end. */
|
|
24
|
+
actions?: ReactNode;
|
|
19
25
|
}): import("react").JSX.Element;
|
|
20
26
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaTopBar/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/MediaTopBar/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAevC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAQ3D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,EACxB,WAAW,EACX,UAAU,EACV,OAAO,EACV,EAAE;IACC,gFAAgF;IAChF,WAAW,EAAE,WAAW,EAAE,CAAC;IAC3B,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,wDAAwD;IACxD,OAAO,CAAC,EAAE,SAAS,CAAC;CACvB,+BA6CA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { defineMessages, useIntl } from 'react-intl';
|
|
3
3
|
import { Image } from 'lucide-react';
|
|
4
|
-
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, TopBar, TopBarIcon } from '@orthacms/design-system';
|
|
4
|
+
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, TopBar, TopBarActions, TopBarIcon } from '@orthacms/design-system';
|
|
5
5
|
import { ROOT_FOLDER_ID } from '../../constants';
|
|
6
6
|
/** Intl descriptors for {@link MediaTopBar}, co-located. */
|
|
7
7
|
const messages = defineMessages({
|
|
@@ -20,8 +20,11 @@ const messages = defineMessages({
|
|
|
20
20
|
* the store's `breadcrumbs` and each one dispatches `onNavigate` rather than
|
|
21
21
|
* linking. They are still `BreadcrumbLink`s over real `<button>`s — a control
|
|
22
22
|
* that changes view without changing location is a button, not a link.
|
|
23
|
+
*
|
|
24
|
+
* `actions` (New folder, Upload) sit at the bar's right end, where every other
|
|
25
|
+
* admin page keeps its page-level actions.
|
|
23
26
|
*/
|
|
24
|
-
export function MediaTopBar({ breadcrumbs, onNavigate }) {
|
|
27
|
+
export function MediaTopBar({ breadcrumbs, onNavigate, actions }) {
|
|
25
28
|
const intl = useIntl();
|
|
26
29
|
const crumbs = [
|
|
27
30
|
{ id: ROOT_FOLDER_ID, name: intl.formatMessage(messages.root) },
|
|
@@ -31,5 +34,5 @@ export function MediaTopBar({ breadcrumbs, onNavigate }) {
|
|
|
31
34
|
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
35
|
index > 0 ? (_jsx(BreadcrumbSeparator, {}, `${crumb.id}-sep`)) : null,
|
|
33
36
|
_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
|
-
]) }) })] }));
|
|
37
|
+
]) }) }), actions ? _jsx(TopBarActions, { children: actions }) : null] }));
|
|
35
38
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { UploadItem, UploadSummary } from '../../types/upload';
|
|
2
2
|
/**
|
|
3
|
-
* The upload
|
|
4
|
-
*
|
|
5
|
-
* {@link MediaUploadRow} per file
|
|
6
|
-
* the
|
|
7
|
-
*
|
|
3
|
+
* The upload tray — a small card pinned to the **bottom-right** of the window
|
|
4
|
+
* with the batch headline ("Uploading 3 of 5 · 47%"), an overall size-weighted
|
|
5
|
+
* bar while anything is running, and one {@link MediaUploadRow} per file (the
|
|
6
|
+
* chevron folds the list away, leaving the headline). It floats outside the
|
|
7
|
+
* upload dialog and over the browser rather than pushing the grid down, so the
|
|
8
|
+
* dialog can close on submit and the user keeps browsing, selecting and
|
|
9
|
+
* queueing more while bytes move.
|
|
8
10
|
*
|
|
9
11
|
* `role="status"` sits on the **headline alone**, not the whole banner: a polite
|
|
10
12
|
* live region re-announces its entire contents on every change, so wrapping the
|
|
@@ -1 +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;
|
|
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;AAyCpE;;;;;;;;;;;;;;;;GAgBG;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,sCA8HA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { defineMessages, useIntl } from 'react-intl';
|
|
4
|
-
import { Button, Progress } from '@orthacms/design-system';
|
|
5
|
-
import { UploadCloud, X } from 'lucide-react';
|
|
4
|
+
import { Button, Progress, cn } from '@orthacms/design-system';
|
|
5
|
+
import { ChevronDown, UploadCloud, X } from 'lucide-react';
|
|
6
6
|
import { UPLOAD_STATUS } from '../../constants';
|
|
7
7
|
import { MediaUploadRow } from './MediaUploadRow';
|
|
8
8
|
/** Intl descriptors for {@link MediaUploadBanner}, co-located. */
|
|
@@ -29,6 +29,10 @@ const messages = defineMessages({
|
|
|
29
29
|
defaultMessage: 'Overall upload progress'
|
|
30
30
|
},
|
|
31
31
|
dismiss: { id: 'media.uploadBanner.dismiss', defaultMessage: 'Dismiss' },
|
|
32
|
+
collapse: {
|
|
33
|
+
id: 'media.uploadBanner.collapse',
|
|
34
|
+
defaultMessage: 'Show the files'
|
|
35
|
+
},
|
|
32
36
|
/** Announced when one file in a batch fails — the row itself is silent. */
|
|
33
37
|
failureAnnouncement: {
|
|
34
38
|
id: 'media.uploadBanner.failureAnnouncement',
|
|
@@ -36,11 +40,13 @@ const messages = defineMessages({
|
|
|
36
40
|
}
|
|
37
41
|
});
|
|
38
42
|
/**
|
|
39
|
-
* The upload
|
|
40
|
-
*
|
|
41
|
-
* {@link MediaUploadRow} per file
|
|
42
|
-
* the
|
|
43
|
-
*
|
|
43
|
+
* The upload tray — a small card pinned to the **bottom-right** of the window
|
|
44
|
+
* with the batch headline ("Uploading 3 of 5 · 47%"), an overall size-weighted
|
|
45
|
+
* bar while anything is running, and one {@link MediaUploadRow} per file (the
|
|
46
|
+
* chevron folds the list away, leaving the headline). It floats outside the
|
|
47
|
+
* upload dialog and over the browser rather than pushing the grid down, so the
|
|
48
|
+
* dialog can close on submit and the user keeps browsing, selecting and
|
|
49
|
+
* queueing more while bytes move.
|
|
44
50
|
*
|
|
45
51
|
* `role="status"` sits on the **headline alone**, not the whole banner: a polite
|
|
46
52
|
* live region re-announces its entire contents on every change, so wrapping the
|
|
@@ -52,6 +58,7 @@ const messages = defineMessages({
|
|
|
52
58
|
*/
|
|
53
59
|
export function MediaUploadBanner({ items, summary, onRetry, onCancel, onDismiss }) {
|
|
54
60
|
const intl = useIntl();
|
|
61
|
+
const [open, setOpen] = useState(true);
|
|
55
62
|
// Failures are announced from their own region. The headline's counts move
|
|
56
63
|
// when a file fails, but "3 of 5" never says *which* file or why — and the
|
|
57
64
|
// rows carrying that sentence sit outside the live region on purpose (a
|
|
@@ -95,5 +102,5 @@ export function MediaUploadBanner({ items, summary, onRetry, onCancel, onDismiss
|
|
|
95
102
|
: summary.failed > 0
|
|
96
103
|
? intl.formatMessage(messages.allFailed, { failed: summary.failed })
|
|
97
104
|
: intl.formatMessage(messages.complete, { count: summary.done });
|
|
98
|
-
return (_jsxs("section", { "aria-label": intl.formatMessage(messages.region), className: "
|
|
105
|
+
return (_jsxs("section", { "aria-label": intl.formatMessage(messages.region), className: "fixed bottom-4 right-4 z-30 w-80 max-w-[calc(100vw-2rem)] rounded-xl border bg-popover px-4 py-3 text-popover-foreground shadow-lg", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(UploadCloud, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true }), _jsx("p", { className: "min-w-0 flex-1 text-sm font-medium", role: "status", children: headline }), _jsx(Button, { variant: "ghost", size: "icon", className: "size-7 shrink-0 shadow-none", "aria-label": intl.formatMessage(messages.collapse), "aria-expanded": open, onClick: () => setOpen((value) => !value), children: _jsx(ChevronDown, { "aria-hidden": true, className: cn('transition-transform duration-(--ds-duration-base) motion-reduce:transition-none', !open && 'rotate-180') }) }), summary.active ? null : (_jsx(Button, { variant: "ghost", size: "icon", className: "size-7 shrink-0 shadow-none", "aria-label": intl.formatMessage(messages.dismiss), onClick: onDismiss, children: _jsx(X, { "aria-hidden": true }) }))] }), summary.active ? (_jsx(Progress, { value: summary.percent, className: "mt-2 h-1.5", "aria-label": intl.formatMessage(messages.overall) })) : null, _jsx("p", { className: "sr-only", role: "status", children: failureNotice }), _jsx("ul", { className: cn('mt-1 max-h-48 divide-y overflow-auto', !open && 'hidden'), children: items.map((item) => (_jsx(MediaUploadRow, { item: item, onRetry: onRetry, onCancel: onCancel }, item.id))) })] }));
|
|
99
106
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/UploadDialog/StagedFileRow/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/UploadDialog/StagedFileRow/index.tsx"],"names":[],"mappings":"AAkDA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,EAC1B,IAAI,EACJ,GAAQ,EACR,UAAkB,EAClB,WAAW,EACX,QAAQ,EACX,EAAE;IACC,IAAI,EAAE,IAAI,CAAC;IACX,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB,+BAyEA"}
|
|
@@ -4,6 +4,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
|
|
4
4
|
import { Button, InputField } from '@orthacms/design-system';
|
|
5
5
|
import { FileText, Film, Music, Package, X } from 'lucide-react';
|
|
6
6
|
import { formatBytes } from '../../../utils/formatBytes';
|
|
7
|
+
import { FadeInImage } from '../../FadeInImage';
|
|
7
8
|
/** Intl descriptors for {@link StagedFileRow}, co-located. */
|
|
8
9
|
const messages = defineMessages({
|
|
9
10
|
remove: { id: 'media.upload.removeFile', defaultMessage: 'Remove {name}' },
|
|
@@ -74,7 +75,7 @@ export function StagedFileRow({ file, alt = '', collectAlt = false, onAltChange,
|
|
|
74
75
|
return () => URL.revokeObjectURL(url);
|
|
75
76
|
}, [file, isImage]);
|
|
76
77
|
const Glyph = glyphFor(file.type);
|
|
77
|
-
return (_jsxs("li", { className: "flex flex-col gap-2 rounded-lg border bg-background px-3 py-2 text-sm", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted", children: previewUrl ? (_jsx(
|
|
78
|
+
return (_jsxs("li", { className: "flex flex-col gap-2 rounded-lg border bg-background px-3 py-2 text-sm", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted", children: previewUrl ? (_jsx(FadeInImage, { src: previewUrl, alt: "", className: "size-full object-cover" })) : (_jsx(Glyph, { className: "size-4 text-muted-foreground", "aria-hidden": true })) }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: file.name }), _jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: formatBytes(file.size) }), _jsx(Button, { variant: "ghost", size: "icon", className: "size-7 shrink-0 shadow-none", "aria-label": intl.formatMessage(messages.remove, {
|
|
78
79
|
name: file.name
|
|
79
80
|
}), onClick: onRemove, children: _jsx(X, { "aria-hidden": true }) })] }), collectAlt && isImage ? (_jsx(InputField, { id: `staged-alt-${altId}`, label: intl.formatMessage(messages.altLabel), "aria-label": intl.formatMessage(messages.altLabelFor, {
|
|
80
81
|
name: file.name
|
|
@@ -79,4 +79,30 @@ export declare const MEDIA_SORT: {
|
|
|
79
79
|
};
|
|
80
80
|
/** One asset-ordering option. */
|
|
81
81
|
export type MediaSort = (typeof MEDIA_SORT)[keyof typeof MEDIA_SORT];
|
|
82
|
+
/**
|
|
83
|
+
* The folders column's entry in the shell's panel layout, and its bounds in
|
|
84
|
+
* pixels. Resizable on a desktop only — below `lg` the folders open in a
|
|
85
|
+
* drawer, which has no edge to drag.
|
|
86
|
+
*/
|
|
87
|
+
export declare const MEDIA_FOLDERS_PANEL: {
|
|
88
|
+
readonly id: "media.folders";
|
|
89
|
+
readonly defaultWidth: 240;
|
|
90
|
+
readonly minWidth: 192;
|
|
91
|
+
readonly maxWidth: 384;
|
|
92
|
+
readonly maxViewportRatio: 0.2;
|
|
93
|
+
readonly cssVar: "--media-folders-width";
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* How the library draws the open folder's files: tiles, or the design-system
|
|
97
|
+
* table. Remembered per browser under {@link MEDIA_VIEW_STORAGE_KEY} — a
|
|
98
|
+
* reading preference, not shared state.
|
|
99
|
+
*/
|
|
100
|
+
export declare const MEDIA_VIEW: {
|
|
101
|
+
readonly Grid: "grid";
|
|
102
|
+
readonly List: "list";
|
|
103
|
+
};
|
|
104
|
+
/** One way of drawing the files. */
|
|
105
|
+
export type MediaView = (typeof MEDIA_VIEW)[keyof typeof MEDIA_VIEW];
|
|
106
|
+
/** Where the chosen {@link MediaView} is kept (`localStorage`). */
|
|
107
|
+
export declare const MEDIA_VIEW_STORAGE_KEY = "orthacms.media.view";
|
|
82
108
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +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"}
|
|
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;AAErE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;CAOtB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;CAGb,CAAC;AAEX,oCAAoC;AACpC,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAErE,mEAAmE;AACnE,eAAO,MAAM,sBAAsB,wBAAwB,CAAC"}
|
|
@@ -73,3 +73,27 @@ export const MEDIA_SORT = {
|
|
|
73
73
|
Largest: 'largest',
|
|
74
74
|
Smallest: 'smallest'
|
|
75
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* The folders column's entry in the shell's panel layout, and its bounds in
|
|
78
|
+
* pixels. Resizable on a desktop only — below `lg` the folders open in a
|
|
79
|
+
* drawer, which has no edge to drag.
|
|
80
|
+
*/
|
|
81
|
+
export const MEDIA_FOLDERS_PANEL = {
|
|
82
|
+
id: 'media.folders',
|
|
83
|
+
defaultWidth: 240,
|
|
84
|
+
minWidth: 192,
|
|
85
|
+
maxWidth: 384,
|
|
86
|
+
maxViewportRatio: 0.2,
|
|
87
|
+
cssVar: '--media-folders-width'
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* How the library draws the open folder's files: tiles, or the design-system
|
|
91
|
+
* table. Remembered per browser under {@link MEDIA_VIEW_STORAGE_KEY} — a
|
|
92
|
+
* reading preference, not shared state.
|
|
93
|
+
*/
|
|
94
|
+
export const MEDIA_VIEW = {
|
|
95
|
+
Grid: 'grid',
|
|
96
|
+
List: 'list'
|
|
97
|
+
};
|
|
98
|
+
/** Where the chosen {@link MediaView} is kept (`localStorage`). */
|
|
99
|
+
export const MEDIA_VIEW_STORAGE_KEY = 'orthacms.media.view';
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { UploadItem, UploadSummary } from '../../types/upload';
|
|
2
2
|
import type { StagedUpload } from '../../infrastructure/mediaGateway';
|
|
3
|
+
/** The `justCreated` scope of the library's asset tiles. */
|
|
4
|
+
export declare const MEDIA_ASSETS_SCOPE = "media-assets";
|
|
3
5
|
/**
|
|
4
6
|
* The Media Library's upload queue: turns a set of picked files into a list of
|
|
5
7
|
* progress-tracked items, uploading at most {@link UPLOAD_CONCURRENCY} at once.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/useUploadQueue/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/hooks/useUploadQueue/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAiBtE,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,IAAI;;;uBA4HrD,YAAY,EAAE;gBAsBnB,MAAM;iBAeN,MAAM;;EAuElB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import { markJustCreated } from '@orthacms/utils-admin';
|
|
3
4
|
import { UPLOAD_CONCURRENCY, UPLOAD_STATUS } from '../../constants';
|
|
4
5
|
import { httpMediaGateway } from '../../infrastructure/httpMediaGateway';
|
|
5
6
|
import { apiMessage } from '../../infrastructure/apiMessage';
|
|
@@ -16,6 +17,8 @@ const SETTLED = [
|
|
|
16
17
|
UPLOAD_STATUS.Failed,
|
|
17
18
|
UPLOAD_STATUS.Cancelled
|
|
18
19
|
];
|
|
20
|
+
/** The `justCreated` scope of the library's asset tiles. */
|
|
21
|
+
export const MEDIA_ASSETS_SCOPE = 'media-assets';
|
|
19
22
|
/**
|
|
20
23
|
* The Media Library's upload queue: turns a set of picked files into a list of
|
|
21
24
|
* progress-tracked items, uploading at most {@link UPLOAD_CONCURRENCY} at once.
|
|
@@ -51,6 +54,8 @@ export function useUploadQueue(folderId, onUploaded) {
|
|
|
51
54
|
const activeRef = useRef(0);
|
|
52
55
|
/** Whether the current batch has produced a success worth invalidating for. */
|
|
53
56
|
const successRef = useRef(false);
|
|
57
|
+
/** Ids of the assets this batch created, marked when the batch settles. */
|
|
58
|
+
const createdRef = useRef([]);
|
|
54
59
|
const nextIdRef = useRef(0);
|
|
55
60
|
// `pump` and `runOne` call each other; the ref breaks the cycle without
|
|
56
61
|
// rebuilding either callback on every render.
|
|
@@ -73,6 +78,14 @@ export function useUploadQueue(folderId, onUploaded) {
|
|
|
73
78
|
if (activeRef.current === 0 && waitingRef.current.length === 0) {
|
|
74
79
|
if (successRef.current) {
|
|
75
80
|
successRef.current = false;
|
|
81
|
+
// Marked here rather than per file: the mark lasts a few
|
|
82
|
+
// seconds, and the grid only learns of the files from the one
|
|
83
|
+
// refetch this triggers — a long batch would outlive marks
|
|
84
|
+
// set as each file landed.
|
|
85
|
+
for (const assetId of createdRef.current) {
|
|
86
|
+
markJustCreated(MEDIA_ASSETS_SCOPE, assetId);
|
|
87
|
+
}
|
|
88
|
+
createdRef.current = [];
|
|
76
89
|
onUploadedRef.current();
|
|
77
90
|
}
|
|
78
91
|
}
|
|
@@ -93,8 +106,9 @@ export function useUploadQueue(folderId, onUploaded) {
|
|
|
93
106
|
signal: controller.signal,
|
|
94
107
|
onProgress: (percent) => patch(id, { progress: percent })
|
|
95
108
|
})
|
|
96
|
-
.then(() => {
|
|
109
|
+
.then((asset) => {
|
|
97
110
|
successRef.current = true;
|
|
111
|
+
createdRef.current.push(asset.id);
|
|
98
112
|
patch(id, {
|
|
99
113
|
status: UPLOAD_STATUS.Done,
|
|
100
114
|
progress: 100,
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
* `/workspaces/:id/media`. `useMediaLibrary` is the store — folders and the open
|
|
4
4
|
* folder's assets come from `@orthacms/media-server`, and every action (browse
|
|
5
5
|
* folders, upload, create folder, rename, duplicate, move, delete, filter, sort,
|
|
6
|
-
* open the
|
|
6
|
+
* open the details panel) runs against the API, firing a toast per action —
|
|
7
7
|
* except uploads, whose status lives in the `MediaUploadBanner`. Controls gate
|
|
8
8
|
* on the real `media:*` matrix via `useHasPermission`.
|
|
9
9
|
*
|
|
10
10
|
* A sticky `MediaTopBar` heads the page (the shared icon-tile + breadcrumb
|
|
11
|
-
* header every admin page carries
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* header every admin page carries, with New folder / Upload at its right end),
|
|
12
|
+
* spanning every pane. Below it: the folder tree, the file browser (title, type
|
|
13
|
+
* chips + search + sort + Grid/List, the selection bar, then tiles or the
|
|
14
|
+
* table) and — while a file is open — the non-modal details panel as a third
|
|
15
|
+
* column; plus the create/rename/move/upload dialogs. The panes sit
|
|
14
16
|
* **flush on the canvas** (no muted board, no bordered island), mirroring the
|
|
15
17
|
* Content Library's work area: the sidebar is divided by a hairline `border-r`
|
|
16
18
|
* and each pane scrolls independently. Sized with `flex-1` against the workspace
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/pages/MediaLibraryPage/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/pages/MediaLibraryPage/index.tsx"],"names":[],"mappings":"AA8LA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,gCAorB/B"}
|