@orthacms/media-admin 0.11.0 → 0.13.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/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/MediaAltDrill/index.d.ts +9 -0
- package/dist/lib/components/MediaAltDrill/index.d.ts.map +1 -0
- package/dist/lib/components/MediaAltDrill/index.js +60 -0
- package/dist/lib/components/MediaAltSignal/index.d.ts +8 -0
- package/dist/lib/components/MediaAltSignal/index.d.ts.map +1 -0
- package/dist/lib/components/MediaAltSignal/index.js +35 -0
- 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/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 +9 -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 +10 -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/MediaStorageWidget/index.js +2 -2
- package/dist/lib/components/MediaThumbnail/index.d.ts +9 -6
- package/dist/lib/components/MediaThumbnail/index.d.ts.map +1 -1
- package/dist/lib/components/MediaThumbnail/index.js +7 -6
- 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/constants/index.d.ts +13 -0
- package/dist/lib/constants/index.d.ts.map +1 -1
- package/dist/lib/constants/index.js +11 -0
- package/dist/lib/hooks/useMediaInsights/index.d.ts +8 -0
- package/dist/lib/hooks/useMediaInsights/index.d.ts.map +1 -1
- package/dist/lib/hooks/useMediaInsights/index.js +20 -1
- package/dist/lib/infrastructure/httpMediaInsightsGateway/index.d.ts.map +1 -1
- package/dist/lib/infrastructure/httpMediaInsightsGateway/index.js +9 -0
- package/dist/lib/infrastructure/mediaInsightsGateway/index.d.ts +17 -0
- package/dist/lib/infrastructure/mediaInsightsGateway/index.d.ts.map +1 -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 +82 -36
- 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 +35 -32
- 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
4
|
import { Button, ConfirmDialog, Drawer, DrawerContent, DrawerTitle, ResizeHandle, cn, toast, useResizablePanel } from '@orthacms/design-system';
|
|
5
|
-
import { PanelLeft } from 'lucide-react';
|
|
5
|
+
import { FolderPlus, PanelLeft, UploadCloud } from 'lucide-react';
|
|
6
6
|
import { useHasPermission } from '@orthacms/identity-admin';
|
|
7
|
-
import { MEDIA_CREATE, MEDIA_DELETE, MEDIA_FOLDERS_PANEL, 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',
|
|
@@ -133,19 +136,31 @@ const messages = defineMessages({
|
|
|
133
136
|
* nothing.
|
|
134
137
|
*/
|
|
135
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
|
+
}
|
|
136
149
|
/**
|
|
137
150
|
* The Media Library, mounted inside the workspace shell at
|
|
138
151
|
* `/workspaces/:id/media`. `useMediaLibrary` is the store — folders and the open
|
|
139
152
|
* folder's assets come from `@orthacms/media-server`, and every action (browse
|
|
140
153
|
* folders, upload, create folder, rename, duplicate, move, delete, filter, sort,
|
|
141
|
-
* open the
|
|
154
|
+
* open the details panel) runs against the API, firing a toast per action —
|
|
142
155
|
* except uploads, whose status lives in the `MediaUploadBanner`. Controls gate
|
|
143
156
|
* on the real `media:*` matrix via `useHasPermission`.
|
|
144
157
|
*
|
|
145
158
|
* A sticky `MediaTopBar` heads the page (the shared icon-tile + breadcrumb
|
|
146
|
-
* header every admin page carries
|
|
147
|
-
*
|
|
148
|
-
*
|
|
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
|
|
149
164
|
* **flush on the canvas** (no muted board, no bordered island), mirroring the
|
|
150
165
|
* Content Library's work area: the sidebar is divided by a hairline `border-r`
|
|
151
166
|
* and each pane scrolls independently. Sized with `flex-1` against the workspace
|
|
@@ -179,6 +194,16 @@ export function MediaLibraryPage() {
|
|
|
179
194
|
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
180
195
|
/** A confirmed delete is in flight — the dialog shows a spinner meanwhile. */
|
|
181
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
|
+
};
|
|
182
207
|
/**
|
|
183
208
|
* Puts focus on the grid and **keeps** it there for a few frames.
|
|
184
209
|
*
|
|
@@ -213,14 +238,29 @@ export function MediaLibraryPage() {
|
|
|
213
238
|
? store.currentFolder.name
|
|
214
239
|
: intl.formatMessage(messages.allMedia);
|
|
215
240
|
const hasFilters = store.search.trim() !== '' || store.kindFilter !== 'all';
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
+
};
|
|
224
264
|
// Triggers a browser download for one asset (its bytes stream from the
|
|
225
265
|
// `media:read`-gated raw route). Shared by the row action and bulk download.
|
|
226
266
|
const downloadAsset = (asset) => {
|
|
@@ -231,7 +271,7 @@ export function MediaLibraryPage() {
|
|
|
231
271
|
link.click();
|
|
232
272
|
link.remove();
|
|
233
273
|
};
|
|
234
|
-
// A single dispatcher every
|
|
274
|
+
// A single dispatcher every tile / row / panel forwards asset actions to.
|
|
235
275
|
const handleAssetAction = (kind, asset) => {
|
|
236
276
|
switch (kind) {
|
|
237
277
|
case 'open':
|
|
@@ -348,14 +388,13 @@ export function MediaLibraryPage() {
|
|
|
348
388
|
if (store.isError) {
|
|
349
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) })] }));
|
|
350
390
|
}
|
|
351
|
-
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)] }) }), _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), canCreate: canCreate }), _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:
|
|
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
|
|
352
392
|
? store.currentFolder.name
|
|
353
|
-
: intl.formatMessage(messages.title) }), _jsx("p", { className: "mt-0.5 text-
|
|
354
|
-
count: store.total
|
|
355
|
-
|
|
356
|
-
}) })] }), _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, {
|
|
357
396
|
count: selectedIds.length
|
|
358
|
-
}) }), _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: () => {
|
|
359
398
|
store.selectedAssets.forEach(downloadAsset);
|
|
360
399
|
toast.success(intl.formatMessage(messages.tDownload, {
|
|
361
400
|
name: `${selectedIds.length} files`
|
|
@@ -375,21 +414,28 @@ export function MediaLibraryPage() {
|
|
|
375
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: () => {
|
|
376
415
|
store.setSearch('');
|
|
377
416
|
store.setKindFilter('all');
|
|
378
|
-
} })) : (_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) => {
|
|
379
427
|
store.navigateTo(folderId);
|
|
380
428
|
setNavOpen(false);
|
|
381
429
|
}, onNewFolder: () => {
|
|
382
430
|
setNavOpen(false);
|
|
383
431
|
setNewFolderOpen(true);
|
|
384
|
-
},
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return ok;
|
|
392
|
-
}), 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) => {
|
|
393
439
|
void store.createFolder(name).then((ok) => {
|
|
394
440
|
if (ok) {
|
|
395
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":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;
|
|
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;AA2C7D;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,CAuJ9C"}
|
|
@@ -2,24 +2,25 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Suspense } from 'react';
|
|
3
3
|
import { lazyPage } from '@orthacms/utils-admin';
|
|
4
4
|
import { WORKSPACE_ROUTE_SLOT, WORKSPACE_NAV_SLOT } from '@orthacms/workspaces-admin';
|
|
5
|
-
import { CONTENT_FIELD_TYPE,
|
|
5
|
+
import { CONTENT_FIELD_TYPE, ENTRY_FIELD_CONTROL_SLOT, ENTRY_PRESAVE_SLOT } from '@orthacms/content-admin';
|
|
6
6
|
import { WYSIWYG_MEDIA_SLOT } from '@orthacms/wysiwyg-admin';
|
|
7
|
-
import { INSIGHTS_SECTION_IDS, INSIGHTS_WIDGET_SLOT } from '@orthacms/insights-admin';
|
|
7
|
+
import { INSIGHTS_SECTION_IDS, INSIGHTS_SIGNAL_SLOT, INSIGHTS_WIDGET_SLOT } from '@orthacms/insights-admin';
|
|
8
8
|
import { Image, Upload } from 'lucide-react';
|
|
9
9
|
import { MediaStorageStat } from '../../components/MediaStorageStat';
|
|
10
10
|
import { MediaStorageWidget } from '../../components/MediaStorageWidget';
|
|
11
11
|
import { MediaUploadsWidget } from '../../components/MediaUploadsWidget';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { MediaAltSignal } from '../../components/MediaAltSignal';
|
|
13
|
+
import { MediaAltDrill } from '../../components/MediaAltDrill';
|
|
14
|
+
import { MediaFieldInput } from '../../components/MediaFieldInput';
|
|
14
15
|
import { MediaLibraryPageSkeleton } from '../../components/MediaLibrarySkeleton';
|
|
15
16
|
import { WysiwygLibrarySource } from '../../components/WysiwygLibrarySource';
|
|
16
17
|
import { WysiwygUploadSource } from '../../components/WysiwygUploadSource';
|
|
17
18
|
import { MEDIA_PRESAVE_ID, usePendingMediaUploads } from '../../hooks/usePendingMediaUploads';
|
|
18
19
|
import { MEDIA_READ } from '../../constants';
|
|
19
20
|
const MediaLibraryPage = lazyPage(() => import('../../pages/MediaLibraryPage'), 'MediaLibraryPage');
|
|
20
|
-
/** The
|
|
21
|
-
function
|
|
22
|
-
return
|
|
21
|
+
/** The media control owns every `media` field, wherever the form draws it. */
|
|
22
|
+
function isMediaField(field) {
|
|
23
|
+
return field.type === CONTENT_FIELD_TYPE.Media;
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Creates the admin-side Media Library plugin. It lives **strictly inside a
|
|
@@ -52,31 +53,37 @@ export function MediaPlugin() {
|
|
|
52
53
|
id: 'insights.media.storageBreakdown',
|
|
53
54
|
section: INSIGHTS_SECTION_IDS.Reach,
|
|
54
55
|
order: 10,
|
|
55
|
-
size: '
|
|
56
|
+
size: 'md',
|
|
56
57
|
permission: MEDIA_READ,
|
|
57
58
|
titleId: 'media.insights.storageBreakdown.title',
|
|
58
|
-
defaultTitle:
|
|
59
|
+
defaultTitle: 'Storage',
|
|
59
60
|
Component: MediaStorageWidget
|
|
60
61
|
},
|
|
61
62
|
{
|
|
62
63
|
id: 'insights.media.uploads',
|
|
63
64
|
section: INSIGHTS_SECTION_IDS.Reach,
|
|
64
65
|
order: 20,
|
|
65
|
-
size: '
|
|
66
|
+
size: 'md',
|
|
66
67
|
permission: MEDIA_READ,
|
|
67
68
|
titleId: 'media.insights.uploads.title',
|
|
68
69
|
defaultTitle: 'Uploads',
|
|
69
70
|
Component: MediaUploadsWidget
|
|
70
|
-
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
// The alt-text debt is a Needs-attention line now, opening the
|
|
76
|
+
// images behind it, rather than a card of its own.
|
|
77
|
+
slot: INSIGHTS_SIGNAL_SLOT,
|
|
78
|
+
items: [
|
|
71
79
|
{
|
|
72
|
-
id: '
|
|
73
|
-
|
|
74
|
-
order: 30,
|
|
75
|
-
size: 'sm',
|
|
80
|
+
id: 'media.alt',
|
|
81
|
+
order: 40,
|
|
76
82
|
permission: MEDIA_READ,
|
|
77
|
-
titleId: 'media.insights.alt.title',
|
|
83
|
+
titleId: 'media.insights.signal.alt.title',
|
|
78
84
|
defaultTitle: 'Images missing alt text',
|
|
79
|
-
Component:
|
|
85
|
+
Component: MediaAltSignal,
|
|
86
|
+
Drill: MediaAltDrill
|
|
80
87
|
}
|
|
81
88
|
]
|
|
82
89
|
},
|
|
@@ -102,23 +109,19 @@ export function MediaPlugin() {
|
|
|
102
109
|
}
|
|
103
110
|
]
|
|
104
111
|
},
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
112
|
+
// A media field's control in the entry form. It used to be a Media
|
|
113
|
+
// tab of its own; the field now sits in the schema's order with the
|
|
114
|
+
// rest (ADR-0022), and content draws its label row and error.
|
|
115
|
+
// `composite`: the control is several buttons, so the field is
|
|
116
|
+
// named as a group rather than by a label on one of them.
|
|
109
117
|
{
|
|
110
|
-
slot:
|
|
118
|
+
slot: ENTRY_FIELD_CONTROL_SLOT,
|
|
111
119
|
items: [
|
|
112
120
|
{
|
|
113
|
-
id: 'media.entry.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
defaultMessage: 'Media'
|
|
118
|
-
},
|
|
119
|
-
order: 10,
|
|
120
|
-
appliesTo: hasMediaField,
|
|
121
|
-
Component: EntryMediaTab
|
|
121
|
+
id: 'media.entry.field',
|
|
122
|
+
appliesTo: isMediaField,
|
|
123
|
+
composite: true,
|
|
124
|
+
Component: MediaFieldInput
|
|
122
125
|
}
|
|
123
126
|
]
|
|
124
127
|
},
|
|
@@ -161,7 +164,7 @@ export function MediaPlugin() {
|
|
|
161
164
|
// runs inside the record's save and puts them in the library then,
|
|
162
165
|
// so an abandoned edit leaves no orphan assets behind. It also owns
|
|
163
166
|
// the staging state, mounted above the tab body (a route) so it
|
|
164
|
-
// survives
|
|
167
|
+
// survives the form remounting.
|
|
165
168
|
{
|
|
166
169
|
slot: ENTRY_PRESAVE_SLOT,
|
|
167
170
|
items: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orthacms/media-admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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.13.0",
|
|
29
|
+
"@orthacms/content-admin": "^0.13.0",
|
|
30
|
+
"@orthacms/content-domain": "^0.13.0",
|
|
31
|
+
"@orthacms/design-system": "^0.13.0",
|
|
32
|
+
"@orthacms/identity-admin": "^0.13.0",
|
|
33
|
+
"@orthacms/insights-admin": "^0.13.0",
|
|
34
|
+
"@orthacms/utils-admin": "^0.13.0",
|
|
35
|
+
"@orthacms/workspaces-admin": "^0.13.0",
|
|
36
|
+
"@orthacms/wysiwyg-admin": "^0.13.0",
|
|
37
37
|
"@tanstack/react-query": "^5.0.0",
|
|
38
38
|
"lucide-react": "^1.17.0",
|
|
39
39
|
"react-intl": "^7.0.0",
|