@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,10 +1,10 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useState } from 'react';
|
|
3
3
|
import { defineMessages, useIntl } from 'react-intl';
|
|
4
|
-
import { Button, ConfirmDialog, Drawer, DrawerContent, DrawerTitle, cn, toast } from '@orthacms/design-system';
|
|
5
|
-
import { PanelLeft } from 'lucide-react';
|
|
4
|
+
import { Button, ConfirmDialog, Drawer, DrawerContent, DrawerTitle, ResizeHandle, cn, toast, useResizablePanel } from '@orthacms/design-system';
|
|
5
|
+
import { FolderPlus, PanelLeft, UploadCloud } from 'lucide-react';
|
|
6
6
|
import { useHasPermission } from '@orthacms/identity-admin';
|
|
7
|
-
import { MEDIA_CREATE, MEDIA_DELETE, MEDIA_READ, MEDIA_UPDATE, ROOT_FOLDER_ID } from '../../constants';
|
|
7
|
+
import { MEDIA_CREATE, MEDIA_DELETE, MEDIA_FOLDERS_PANEL, MEDIA_READ, MEDIA_UPDATE, MEDIA_VIEW, MEDIA_VIEW_STORAGE_KEY, ROOT_FOLDER_ID } from '../../constants';
|
|
8
8
|
import { useMediaLibrary } from '../../hooks/useMediaLibrary';
|
|
9
9
|
import { MediaFoldersNav } from '../../components/MediaFoldersNav';
|
|
10
10
|
import { MediaTopBar } from '../../components/MediaTopBar';
|
|
@@ -12,9 +12,10 @@ import { MediaToolbar } from '../../components/MediaToolbar';
|
|
|
12
12
|
import { MediaSelectionBar } from '../../components/MediaSelectionBar';
|
|
13
13
|
import { MediaUploadBanner } from '../../components/MediaUploadBanner';
|
|
14
14
|
import { MediaGrid } from '../../components/MediaGrid';
|
|
15
|
+
import { MediaAssetTable } from '../../components/MediaAssetTable';
|
|
15
16
|
import { MediaPagination } from '../../components/MediaPagination';
|
|
16
17
|
import { MediaEmptyState } from '../../components/MediaEmptyState';
|
|
17
|
-
import {
|
|
18
|
+
import { AssetDetailPanel } from '../../components/AssetDetailPanel';
|
|
18
19
|
import { NewFolderDialog } from '../../components/NewFolderDialog';
|
|
19
20
|
import { RenameDialog } from '../../components/RenameDialog';
|
|
20
21
|
import { MoveAssetsDialog } from '../../components/MoveAssetsDialog';
|
|
@@ -24,10 +25,12 @@ import { useDocumentTitle } from '@orthacms/utils-admin';
|
|
|
24
25
|
/** Intl descriptors for the Media Library page + its toasts, co-located. */
|
|
25
26
|
const messages = defineMessages({
|
|
26
27
|
title: { id: 'media.page.title', defaultMessage: 'Media Library' },
|
|
27
|
-
|
|
28
|
-
id: 'media.page.
|
|
29
|
-
defaultMessage: '{count, plural, one {#
|
|
28
|
+
fileCount: {
|
|
29
|
+
id: 'media.page.fileCount',
|
|
30
|
+
defaultMessage: '{count, plural, one {# file} other {# files}}'
|
|
30
31
|
},
|
|
32
|
+
newFolder: { id: 'media.page.newFolder', defaultMessage: 'New folder' },
|
|
33
|
+
upload: { id: 'media.page.upload', defaultMessage: 'Upload' },
|
|
31
34
|
allMedia: { id: 'media.page.allMedia', defaultMessage: 'All media' },
|
|
32
35
|
noAccess: {
|
|
33
36
|
id: 'media.page.noAccess',
|
|
@@ -39,6 +42,7 @@ const messages = defineMessages({
|
|
|
39
42
|
},
|
|
40
43
|
retry: { id: 'media.page.retry', defaultMessage: 'Try again' },
|
|
41
44
|
openNav: { id: 'media.page.openNav', defaultMessage: 'Folders' },
|
|
45
|
+
resizeNav: { id: 'media.page.resizeNav', defaultMessage: 'Resize folders' },
|
|
42
46
|
navTitle: {
|
|
43
47
|
id: 'media.page.navTitle',
|
|
44
48
|
defaultMessage: 'Folder navigation'
|
|
@@ -132,19 +136,31 @@ const messages = defineMessages({
|
|
|
132
136
|
* nothing.
|
|
133
137
|
*/
|
|
134
138
|
const FOCUS_RECLAIM_MS = 1_000;
|
|
139
|
+
/** The remembered view, or the grid — storage can be absent or refuse. */
|
|
140
|
+
function readView() {
|
|
141
|
+
try {
|
|
142
|
+
const stored = window.localStorage.getItem(MEDIA_VIEW_STORAGE_KEY);
|
|
143
|
+
return stored === MEDIA_VIEW.List ? MEDIA_VIEW.List : MEDIA_VIEW.Grid;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return MEDIA_VIEW.Grid;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
135
149
|
/**
|
|
136
150
|
* The Media Library, mounted inside the workspace shell at
|
|
137
151
|
* `/workspaces/:id/media`. `useMediaLibrary` is the store — folders and the open
|
|
138
152
|
* folder's assets come from `@orthacms/media-server`, and every action (browse
|
|
139
153
|
* folders, upload, create folder, rename, duplicate, move, delete, filter, sort,
|
|
140
|
-
* open the
|
|
154
|
+
* open the details panel) runs against the API, firing a toast per action —
|
|
141
155
|
* except uploads, whose status lives in the `MediaUploadBanner`. Controls gate
|
|
142
156
|
* on the real `media:*` matrix via `useHasPermission`.
|
|
143
157
|
*
|
|
144
158
|
* A sticky `MediaTopBar` heads the page (the shared icon-tile + breadcrumb
|
|
145
|
-
* header every admin page carries
|
|
146
|
-
*
|
|
147
|
-
*
|
|
159
|
+
* header every admin page carries, with New folder / Upload at its right end),
|
|
160
|
+
* spanning every pane. Below it: the folder tree, the file browser (title, type
|
|
161
|
+
* chips + search + sort + Grid/List, the selection bar, then tiles or the
|
|
162
|
+
* table) and — while a file is open — the non-modal details panel as a third
|
|
163
|
+
* column; plus the create/rename/move/upload dialogs. The panes sit
|
|
148
164
|
* **flush on the canvas** (no muted board, no bordered island), mirroring the
|
|
149
165
|
* Content Library's work area: the sidebar is divided by a hairline `border-r`
|
|
150
166
|
* and each pane scrolls independently. Sized with `flex-1` against the workspace
|
|
@@ -170,6 +186,7 @@ export function MediaLibraryPage() {
|
|
|
170
186
|
/** Set by a confirmed delete, read once by the dialog's close-focus hook. */
|
|
171
187
|
const focusGridOnCloseRef = useRef(false);
|
|
172
188
|
const [navOpen, setNavOpen] = useState(false);
|
|
189
|
+
const foldersPanel = useResizablePanel(MEDIA_FOLDERS_PANEL);
|
|
173
190
|
const [newFolderOpen, setNewFolderOpen] = useState(false);
|
|
174
191
|
const [uploadOpen, setUploadOpen] = useState(false);
|
|
175
192
|
const [renameTarget, setRenameTarget] = useState(null);
|
|
@@ -177,6 +194,16 @@ export function MediaLibraryPage() {
|
|
|
177
194
|
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
178
195
|
/** A confirmed delete is in flight — the dialog shows a spinner meanwhile. */
|
|
179
196
|
const [deleting, setDeleting] = useState(false);
|
|
197
|
+
const [view, setViewState] = useState(readView);
|
|
198
|
+
const setView = (next) => {
|
|
199
|
+
setViewState(next);
|
|
200
|
+
try {
|
|
201
|
+
window.localStorage.setItem(MEDIA_VIEW_STORAGE_KEY, next);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// A private window or blocked storage: the choice lasts the visit.
|
|
205
|
+
}
|
|
206
|
+
};
|
|
180
207
|
/**
|
|
181
208
|
* Puts focus on the grid and **keeps** it there for a few frames.
|
|
182
209
|
*
|
|
@@ -211,14 +238,29 @@ export function MediaLibraryPage() {
|
|
|
211
238
|
? store.currentFolder.name
|
|
212
239
|
: intl.formatMessage(messages.allMedia);
|
|
213
240
|
const hasFilters = store.search.trim() !== '' || store.kindFilter !== 'all';
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
241
|
+
// Emptiness is a fact about the whole folder, not about what happened to
|
|
242
|
+
// load: `total` counts every asset matching the search and filter across
|
|
243
|
+
// it. Sub-folders don't count — they are drawn in the tree, not the grid.
|
|
244
|
+
const isEmpty = store.total === 0;
|
|
245
|
+
// The details panel steps through the files on screen, in their order.
|
|
246
|
+
const detailIndex = store.detailAsset
|
|
247
|
+
? store.visibleAssets.findIndex((a) => a.id === store.detailAsset?.id)
|
|
248
|
+
: -1;
|
|
249
|
+
const previousAsset = detailIndex > 0 ? store.visibleAssets[detailIndex - 1] : undefined;
|
|
250
|
+
const nextAsset = detailIndex >= 0 ? store.visibleAssets[detailIndex + 1] : undefined;
|
|
251
|
+
// The table's header checkbox: select the page, or clear it when it's all
|
|
252
|
+
// selected already.
|
|
253
|
+
const toggleAllOnPage = () => {
|
|
254
|
+
const page = store.visibleAssets;
|
|
255
|
+
if (page.length > 0 && page.every((a) => store.selectedIds.has(a.id))) {
|
|
256
|
+
store.clearSelection();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
for (const asset of page) {
|
|
260
|
+
if (!store.selectedIds.has(asset.id))
|
|
261
|
+
store.toggleSelect(asset.id);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
222
264
|
// Triggers a browser download for one asset (its bytes stream from the
|
|
223
265
|
// `media:read`-gated raw route). Shared by the row action and bulk download.
|
|
224
266
|
const downloadAsset = (asset) => {
|
|
@@ -229,7 +271,7 @@ export function MediaLibraryPage() {
|
|
|
229
271
|
link.click();
|
|
230
272
|
link.remove();
|
|
231
273
|
};
|
|
232
|
-
// A single dispatcher every
|
|
274
|
+
// A single dispatcher every tile / row / panel forwards asset actions to.
|
|
233
275
|
const handleAssetAction = (kind, asset) => {
|
|
234
276
|
switch (kind) {
|
|
235
277
|
case 'open':
|
|
@@ -346,14 +388,13 @@ export function MediaLibraryPage() {
|
|
|
346
388
|
if (store.isError) {
|
|
347
389
|
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 p-6 text-center", children: [_jsx("p", { className: "max-w-sm text-sm text-muted-foreground", role: "alert", children: intl.formatMessage(messages.loadError) }), _jsx(Button, { variant: "outline", onClick: store.reload, children: intl.formatMessage(messages.retry) })] }));
|
|
348
390
|
}
|
|
349
|
-
return (_jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [_jsx(MediaTopBar, { breadcrumbs: store.breadcrumbs, onNavigate: store.navigateTo }), _jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col lg:flex-row", children: [_jsx("div", { className: "border-b p-3 lg:hidden", children: _jsxs(Button, { variant: "outline", size: "sm", className: "shadow-none", onClick: () => setNavOpen(true), children: [_jsx(PanelLeft, { "aria-hidden": true }), intl.formatMessage(messages.openNav)] }) }),
|
|
391
|
+
return (_jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [_jsx(MediaTopBar, { breadcrumbs: store.breadcrumbs, onNavigate: store.navigateTo, actions: canCreate ? (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", className: "shadow-none", onClick: () => setNewFolderOpen(true), children: [_jsx(FolderPlus, { "aria-hidden": true }), _jsx("span", { className: "hidden sm:inline", children: intl.formatMessage(messages.newFolder) })] }), _jsxs(Button, { size: "sm", onClick: () => setUploadOpen(true), children: [_jsx(UploadCloud, { "aria-hidden": true }), intl.formatMessage(messages.upload)] })] })) : null }), _jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col lg:flex-row", children: [_jsx("div", { className: "border-b p-3 lg:hidden", children: _jsxs(Button, { variant: "outline", size: "sm", className: "shadow-none", onClick: () => setNavOpen(true), children: [_jsx(PanelLeft, { "aria-hidden": true }), intl.formatMessage(messages.openNav)] }) }), _jsxs("div", { ref: foldersPanel.ref, style: foldersPanel.style, className: "relative hidden shrink-0 lg:flex", children: [_jsx(MediaFoldersNav, { className: "w-(--media-folders-width) border-r", folders: store.folders, currentFolderId: store.currentFolderId, counts: store.folderCounts, rootCount: store.folderCounts.get(ROOT_FOLDER_ID) ?? 0, onNavigate: store.navigateTo, onNewFolder: () => setNewFolderOpen(true), onRenameFolder: handleRenameFolder, onDeleteFolder: handleDeleteFolder, canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete }), _jsx(ResizeHandle, { ...foldersPanel.handleProps, edge: "right", label: intl.formatMessage(messages.resizeNav) })] }), _jsx("div", { className: "min-w-0 flex-1 overflow-auto", children: _jsxs("div", { className: cn('px-4 py-6 sm:px-8', selectedIds.length > 0 && 'pb-24'), children: [_jsxs("div", { className: "mb-4", children: [_jsx("h1", { className: "text-2xl font-semibold tracking-[-0.01em]", children: store.currentFolder
|
|
350
392
|
? store.currentFolder.name
|
|
351
|
-
: intl.formatMessage(messages.title) }), _jsx("p", { className: "mt-0.5 text-
|
|
352
|
-
count: store.total
|
|
353
|
-
|
|
354
|
-
}) })] }), _jsx(MediaToolbar, { search: store.search, onSearchChange: store.setSearch, kindFilter: store.kindFilter, onKindFilterChange: store.setKindFilter, sort: store.sort, onSortChange: store.setSort, onNewFolder: () => setNewFolderOpen(true), onUpload: () => setUploadOpen(true), canCreate: canCreate }), _jsx("p", { className: "sr-only", "aria-live": "polite", children: intl.formatMessage(messages.selection, {
|
|
393
|
+
: intl.formatMessage(messages.title) }), _jsx("p", { className: "mt-0.5 text-[13px] text-muted-foreground", children: intl.formatMessage(messages.fileCount, {
|
|
394
|
+
count: store.total
|
|
395
|
+
}) })] }), _jsx(MediaToolbar, { search: store.search, onSearchChange: store.setSearch, kindFilter: store.kindFilter, onKindFilterChange: store.setKindFilter, sort: store.sort, onSortChange: store.setSort, view: view, onViewChange: setView }), _jsx("p", { className: "sr-only", "aria-live": "polite", children: intl.formatMessage(messages.selection, {
|
|
355
396
|
count: selectedIds.length
|
|
356
|
-
}) }), _jsx(MediaUploadBanner, { items: store.uploadItems, summary: store.uploadSummary, onRetry: store.retryUpload, onCancel: store.cancelUpload, onDismiss: store.dismissUploads }), selectedIds.length > 0 ? (_jsx(MediaSelectionBar, { count: selectedIds.length, onDownload: () => {
|
|
397
|
+
}) }), _jsx(MediaUploadBanner, { items: store.uploadItems, summary: store.uploadSummary, onRetry: store.retryUpload, onCancel: store.cancelUpload, onDismiss: store.dismissUploads }), selectedIds.length > 0 ? (_jsx(MediaSelectionBar, { count: selectedIds.length, total: store.total, onDownload: () => {
|
|
357
398
|
store.selectedAssets.forEach(downloadAsset);
|
|
358
399
|
toast.success(intl.formatMessage(messages.tDownload, {
|
|
359
400
|
name: `${selectedIds.length} files`
|
|
@@ -370,24 +411,31 @@ export function MediaLibraryPage() {
|
|
|
370
411
|
}, onMove: () => setMoveIds(selectedIds), onDelete: () => setDeleteTarget({
|
|
371
412
|
kind: 'assets',
|
|
372
413
|
ids: selectedIds
|
|
373
|
-
}), onClear: store.clearSelection, canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete })) : null, _jsx("section", { ref: gridRef, tabIndex: -1, "aria-label": intl.formatMessage(messages.assetsRegion), "aria-busy": store.isRefreshing, className: cn('transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', store.isRefreshing && 'opacity-60'), children: isEmpty ? (_jsx(MediaEmptyState, { hasFilters: hasFilters, canCreate: canCreate, onUpload: () => setUploadOpen(true), onClearFilters: () => {
|
|
414
|
+
}), onClear: store.clearSelection, canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete })) : null, _jsx("section", { ref: gridRef, tabIndex: -1, "aria-label": intl.formatMessage(messages.assetsRegion), "aria-busy": store.isRefreshing, className: cn('transition-opacity duration-(--ds-duration-base) motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', store.isRefreshing && 'opacity-60 delay-150'), children: isEmpty ? (_jsx(MediaEmptyState, { hasFilters: hasFilters, canCreate: canCreate, onUpload: () => setUploadOpen(true), onClearFilters: () => {
|
|
374
415
|
store.setSearch('');
|
|
375
416
|
store.setKindFilter('all');
|
|
376
|
-
} })) : (_jsx(
|
|
417
|
+
} })) : view === MEDIA_VIEW.List ? (_jsx(MediaAssetTable, { assets: store.visibleAssets, selectedIds: store.selectedIds, activeId: store.detailAsset?.id, onToggleSelect: store.toggleSelect, onToggleAll: toggleAllOnPage, onAction: handleAssetAction, canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete })) : (_jsx(MediaGrid, { assets: store.visibleAssets, selectedIds: store.selectedIds, activeId: store.detailAsset?.id, onToggleSelect: store.toggleSelect, onAssetAction: handleAssetAction, canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete })) }), store.total > 0 ? (_jsx(MediaPagination, { page: store.page, pageCount: store.pageCount, pageSize: store.pageSize, total: store.total, onPageChange: store.setPage, onPageSizeChange: store.setPageSize })) : null] }) }), store.detailAsset ? (_jsx(AssetDetailPanel, { className: "fixed inset-0 z-40 lg:static lg:z-auto lg:w-[340px] lg:shrink-0 lg:border-l", asset: store.detailAsset, locationLabel: locationLabel, onClose: store.closeDetail, onPrevious: previousAsset
|
|
418
|
+
? () => store.openDetail(previousAsset.id)
|
|
419
|
+
: undefined, onNext: nextAsset
|
|
420
|
+
? () => store.openDetail(nextAsset.id)
|
|
421
|
+
: undefined, onAction: handleAssetAction, onSaveAlt: (id, alt) => store.setAssetAlt(id, alt).then((ok) => {
|
|
422
|
+
if (ok) {
|
|
423
|
+
toast.success(intl.formatMessage(messages.tAltSaved));
|
|
424
|
+
}
|
|
425
|
+
return ok;
|
|
426
|
+
}), canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete })) : null] }), _jsx(Drawer, { direction: "left", open: navOpen, onOpenChange: setNavOpen, shouldScaleBackground: false, children: _jsxs(DrawerContent, { className: "data-[vaul-drawer-direction=left]:w-72", children: [_jsx(DrawerTitle, { className: "sr-only", children: intl.formatMessage(messages.navTitle) }), _jsx(MediaFoldersNav, { className: "w-full", folders: store.folders, currentFolderId: store.currentFolderId, counts: store.folderCounts, rootCount: store.folderCounts.get(ROOT_FOLDER_ID) ?? 0, onNavigate: (folderId) => {
|
|
377
427
|
store.navigateTo(folderId);
|
|
378
428
|
setNavOpen(false);
|
|
379
429
|
}, onNewFolder: () => {
|
|
380
430
|
setNavOpen(false);
|
|
381
431
|
setNewFolderOpen(true);
|
|
382
|
-
},
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return ok;
|
|
390
|
-
}), canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete }), _jsx(NewFolderDialog, { open: newFolderOpen, onOpenChange: setNewFolderOpen, locationLabel: locationLabel, onCreate: (name) => {
|
|
432
|
+
}, onRenameFolder: (folder) => {
|
|
433
|
+
setNavOpen(false);
|
|
434
|
+
handleRenameFolder(folder);
|
|
435
|
+
}, onDeleteFolder: (folder) => {
|
|
436
|
+
setNavOpen(false);
|
|
437
|
+
handleDeleteFolder(folder);
|
|
438
|
+
}, canCreate: canCreate, canUpdate: canUpdate, canDelete: canDelete })] }) }), _jsx(NewFolderDialog, { open: newFolderOpen, onOpenChange: setNewFolderOpen, locationLabel: locationLabel, onCreate: (name) => {
|
|
391
439
|
void store.createFolder(name).then((ok) => {
|
|
392
440
|
if (ok) {
|
|
393
441
|
toast.success(intl.formatMessage(messages.tCreated, { name }));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The short, upper-cased extension of a file name (`report-q3.pdf` → `PDF`),
|
|
3
|
+
* or `null` when there is none worth showing — no dot, a leading-dot name
|
|
4
|
+
* like `.env`, or a "extension" too long to be one. Labels the tiles and rows
|
|
5
|
+
* of files that have no picture to show, where a glyph alone can't tell a
|
|
6
|
+
* PDF from a DOCX.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fileExtension(name: string): string | null;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/utils/fileExtension/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKzD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The short, upper-cased extension of a file name (`report-q3.pdf` → `PDF`),
|
|
3
|
+
* or `null` when there is none worth showing — no dot, a leading-dot name
|
|
4
|
+
* like `.env`, or a "extension" too long to be one. Labels the tiles and rows
|
|
5
|
+
* of files that have no picture to show, where a glyph alone can't tell a
|
|
6
|
+
* PDF from a DOCX.
|
|
7
|
+
*/
|
|
8
|
+
export function fileExtension(name) {
|
|
9
|
+
const dot = name.lastIndexOf('.');
|
|
10
|
+
if (dot <= 0 || dot === name.length - 1)
|
|
11
|
+
return null;
|
|
12
|
+
const ext = name.slice(dot + 1);
|
|
13
|
+
return ext.length > 5 ? null : ext.toUpperCase();
|
|
14
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/utils/mediaPlugin/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/utils/mediaPlugin/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAyC7D;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,CAiJ9C"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Suspense
|
|
2
|
+
import { Suspense } from 'react';
|
|
3
|
+
import { lazyPage } from '@orthacms/utils-admin';
|
|
3
4
|
import { WORKSPACE_ROUTE_SLOT, WORKSPACE_NAV_SLOT } from '@orthacms/workspaces-admin';
|
|
4
|
-
import { CONTENT_FIELD_TYPE,
|
|
5
|
+
import { CONTENT_FIELD_TYPE, ENTRY_FIELD_CONTROL_SLOT, ENTRY_PRESAVE_SLOT } from '@orthacms/content-admin';
|
|
5
6
|
import { WYSIWYG_MEDIA_SLOT } from '@orthacms/wysiwyg-admin';
|
|
6
7
|
import { INSIGHTS_SECTION_IDS, INSIGHTS_WIDGET_SLOT } from '@orthacms/insights-admin';
|
|
7
8
|
import { Image, Upload } from 'lucide-react';
|
|
@@ -9,18 +10,16 @@ import { MediaStorageStat } from '../../components/MediaStorageStat';
|
|
|
9
10
|
import { MediaStorageWidget } from '../../components/MediaStorageWidget';
|
|
10
11
|
import { MediaUploadsWidget } from '../../components/MediaUploadsWidget';
|
|
11
12
|
import { MediaAltTextWidget } from '../../components/MediaAltTextWidget';
|
|
12
|
-
import {
|
|
13
|
+
import { MediaFieldInput } from '../../components/MediaFieldInput';
|
|
13
14
|
import { MediaLibraryPageSkeleton } from '../../components/MediaLibrarySkeleton';
|
|
14
15
|
import { WysiwygLibrarySource } from '../../components/WysiwygLibrarySource';
|
|
15
16
|
import { WysiwygUploadSource } from '../../components/WysiwygUploadSource';
|
|
16
17
|
import { MEDIA_PRESAVE_ID, usePendingMediaUploads } from '../../hooks/usePendingMediaUploads';
|
|
17
18
|
import { MEDIA_READ } from '../../constants';
|
|
18
|
-
const MediaLibraryPage =
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function hasMediaField(schema) {
|
|
23
|
-
return schema.fields.some((field) => field.type === CONTENT_FIELD_TYPE.Media);
|
|
19
|
+
const MediaLibraryPage = lazyPage(() => import('../../pages/MediaLibraryPage'), 'MediaLibraryPage');
|
|
20
|
+
/** The media control owns every `media` field, wherever the form draws it. */
|
|
21
|
+
function isMediaField(field) {
|
|
22
|
+
return field.type === CONTENT_FIELD_TYPE.Media;
|
|
24
23
|
}
|
|
25
24
|
/**
|
|
26
25
|
* Creates the admin-side Media Library plugin. It lives **strictly inside a
|
|
@@ -103,23 +102,19 @@ export function MediaPlugin() {
|
|
|
103
102
|
}
|
|
104
103
|
]
|
|
105
104
|
},
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
105
|
+
// A media field's control in the entry form. It used to be a Media
|
|
106
|
+
// tab of its own; the field now sits in the schema's order with the
|
|
107
|
+
// rest (ADR-0022), and content draws its label row and error.
|
|
108
|
+
// `composite`: the control is several buttons, so the field is
|
|
109
|
+
// named as a group rather than by a label on one of them.
|
|
110
110
|
{
|
|
111
|
-
slot:
|
|
111
|
+
slot: ENTRY_FIELD_CONTROL_SLOT,
|
|
112
112
|
items: [
|
|
113
113
|
{
|
|
114
|
-
id: 'media.entry.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
defaultMessage: 'Media'
|
|
119
|
-
},
|
|
120
|
-
order: 10,
|
|
121
|
-
appliesTo: hasMediaField,
|
|
122
|
-
Component: EntryMediaTab
|
|
114
|
+
id: 'media.entry.field',
|
|
115
|
+
appliesTo: isMediaField,
|
|
116
|
+
composite: true,
|
|
117
|
+
Component: MediaFieldInput
|
|
123
118
|
}
|
|
124
119
|
]
|
|
125
120
|
},
|
|
@@ -162,7 +157,7 @@ export function MediaPlugin() {
|
|
|
162
157
|
// runs inside the record's save and puts them in the library then,
|
|
163
158
|
// so an abandoned edit leaves no orphan assets behind. It also owns
|
|
164
159
|
// the staging state, mounted above the tab body (a route) so it
|
|
165
|
-
// survives
|
|
160
|
+
// survives the form remounting.
|
|
166
161
|
{
|
|
167
162
|
slot: ENTRY_PRESAVE_SLOT,
|
|
168
163
|
items: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orthacms/media-admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "@orthacms/media-admin — part of Ortha CMS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://orthacms.com",
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@orthacms/bootstrap-admin": "^0.
|
|
29
|
-
"@orthacms/content-admin": "^0.
|
|
30
|
-
"@orthacms/content-domain": "^0.
|
|
31
|
-
"@orthacms/design-system": "^0.
|
|
32
|
-
"@orthacms/identity-admin": "^0.
|
|
33
|
-
"@orthacms/insights-admin": "^0.
|
|
34
|
-
"@orthacms/utils-admin": "^0.
|
|
35
|
-
"@orthacms/workspaces-admin": "^0.
|
|
36
|
-
"@orthacms/wysiwyg-admin": "^0.
|
|
28
|
+
"@orthacms/bootstrap-admin": "^0.12.0",
|
|
29
|
+
"@orthacms/content-admin": "^0.12.0",
|
|
30
|
+
"@orthacms/content-domain": "^0.12.0",
|
|
31
|
+
"@orthacms/design-system": "^0.12.0",
|
|
32
|
+
"@orthacms/identity-admin": "^0.12.0",
|
|
33
|
+
"@orthacms/insights-admin": "^0.12.0",
|
|
34
|
+
"@orthacms/utils-admin": "^0.12.0",
|
|
35
|
+
"@orthacms/workspaces-admin": "^0.12.0",
|
|
36
|
+
"@orthacms/wysiwyg-admin": "^0.12.0",
|
|
37
37
|
"@tanstack/react-query": "^5.0.0",
|
|
38
38
|
"lucide-react": "^1.17.0",
|
|
39
39
|
"react-intl": "^7.0.0",
|