@strapi/upload 5.52.3 → 5.53.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/admin/future/components/Drawer.js +6 -3
- package/dist/admin/future/components/Drawer.js.map +1 -1
- package/dist/admin/future/components/Drawer.mjs +6 -3
- package/dist/admin/future/components/Drawer.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.js +16 -2
- package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.mjs +16 -2
- package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js +5 -0
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs +5 -0
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js +19 -4
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +19 -4
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +35 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +35 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js +10 -0
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +10 -0
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.js +9 -0
- package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +10 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js +132 -88
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs +135 -91
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/MainAreaContextMenu.js +181 -0
- package/dist/admin/future/pages/Assets/components/MainAreaContextMenu.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/MainAreaContextMenu.mjs +179 -0
- package/dist/admin/future/pages/Assets/components/MainAreaContextMenu.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/constants.js +22 -0
- package/dist/admin/future/pages/Assets/constants.js.map +1 -1
- package/dist/admin/future/pages/Assets/constants.mjs +19 -1
- package/dist/admin/future/pages/Assets/constants.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js +65 -10
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs +66 -11
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useListScrollRestoration.js +111 -0
- package/dist/admin/future/pages/Assets/hooks/useListScrollRestoration.js.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useListScrollRestoration.mjs +109 -0
- package/dist/admin/future/pages/Assets/hooks/useListScrollRestoration.mjs.map +1 -0
- package/dist/admin/src/future/components/Drawer.d.ts +10 -0
- package/dist/admin/src/future/pages/Assets/components/MainAreaContextMenu.d.ts +28 -0
- package/dist/admin/src/future/pages/Assets/constants.d.ts +20 -0
- package/dist/admin/src/future/pages/Assets/hooks/useInfiniteAssets.d.ts +4 -0
- package/dist/admin/src/future/pages/Assets/hooks/useListScrollRestoration.d.ts +15 -0
- package/dist/admin/translations/en.json.js +1 -0
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +1 -0
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/server/services/upload.js +20 -8
- package/dist/server/services/upload.js.map +1 -1
- package/dist/server/services/upload.mjs +20 -8
- package/dist/server/services/upload.mjs.map +1 -1
- package/dist/server/src/index.d.ts +2 -0
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +2 -0
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/upload.d.ts +2 -0
- package/dist/server/src/services/upload.d.ts.map +1 -1
- package/package.json +6 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useState, useMemo } from 'react';
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
3
3
|
import { useNotification } from '@strapi/admin/strapi-admin';
|
|
4
|
-
import { Typography, TextButton, Tooltip,
|
|
4
|
+
import { Box, Typography, TextButton, Tooltip, Button, IconButton, Flex } from '@strapi/design-system';
|
|
5
5
|
import { Sparkle, ArrowRight, Trash, Cross } from '@strapi/icons';
|
|
6
6
|
import { useIntl } from 'react-intl';
|
|
7
7
|
import { css, styled } from 'styled-components';
|
|
@@ -136,6 +136,37 @@ const BulkActionsBar = ({ assets = [], locations = emptyItemLocations, renderedK
|
|
|
136
136
|
// state so the rest of the controls stay disabled for the duration.
|
|
137
137
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
138
138
|
const count = selectedIds.size + selectedFolderIds.size;
|
|
139
|
+
// `innerHeight - top` rather than the bar's height: it also spans the gap the
|
|
140
|
+
// bar floats above the viewport edge (0 docked on mobile, `spaces[4]` for the
|
|
141
|
+
// desktop pill), so one measurement covers both layouts and no height is
|
|
142
|
+
// hardcoded.
|
|
143
|
+
const [barEl, setBarEl] = useState(null);
|
|
144
|
+
const [reservedSpace, setReservedSpace] = useState(0);
|
|
145
|
+
useEffect(()=>{
|
|
146
|
+
if (!barEl) {
|
|
147
|
+
setReservedSpace(0);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const measure = ()=>{
|
|
151
|
+
const rect = barEl.getBoundingClientRect();
|
|
152
|
+
// Zero height means the CSS has hidden the bar (drawer open on mobile) —
|
|
153
|
+
// reserving anything then would leave a gap under nothing.
|
|
154
|
+
setReservedSpace(rect.height === 0 ? 0 : Math.max(0, window.innerHeight - rect.top));
|
|
155
|
+
};
|
|
156
|
+
measure();
|
|
157
|
+
const observer = new ResizeObserver(measure);
|
|
158
|
+
observer.observe(barEl);
|
|
159
|
+
window.addEventListener('resize', measure);
|
|
160
|
+
return ()=>{
|
|
161
|
+
observer.disconnect();
|
|
162
|
+
window.removeEventListener('resize', measure);
|
|
163
|
+
};
|
|
164
|
+
// `isDetailsDrawerOpen` toggles the bar's `display`, which a ResizeObserver
|
|
165
|
+
// is not guaranteed to report.
|
|
166
|
+
}, [
|
|
167
|
+
barEl,
|
|
168
|
+
isDetailsDrawerOpen
|
|
169
|
+
]);
|
|
139
170
|
const handleSelectAll = ()=>{
|
|
140
171
|
trackUsage('didSelectAllMediaLibraryElements');
|
|
141
172
|
selectAll(renderedKeys);
|
|
@@ -275,107 +306,120 @@ const BulkActionsBar = ({ assets = [], locations = emptyItemLocations, renderedK
|
|
|
275
306
|
if (count === 0 || !canUpdate) {
|
|
276
307
|
return null;
|
|
277
308
|
}
|
|
278
|
-
return /*#__PURE__*/ jsxs(
|
|
279
|
-
$isDrawerOpen: isDetailsDrawerOpen,
|
|
280
|
-
$isStacked: isAiMetadataEnabled,
|
|
281
|
-
tag: "section",
|
|
282
|
-
role: "region",
|
|
283
|
-
"aria-label": formatMessage({
|
|
284
|
-
id: getTranslationKey('list.bulk-actions.label'),
|
|
285
|
-
defaultMessage: 'Bulk actions'
|
|
286
|
-
}),
|
|
309
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
287
310
|
children: [
|
|
288
|
-
/*#__PURE__*/ jsx(
|
|
289
|
-
"
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
marginRight: 4,
|
|
293
|
-
children: formatMessage({
|
|
294
|
-
id: getTranslationKey('list.bulk-actions.selected-count'),
|
|
295
|
-
defaultMessage: '{count, plural, =1 {# item selected} other {# items selected}}'
|
|
296
|
-
}, {
|
|
297
|
-
count
|
|
298
|
-
})
|
|
311
|
+
/*#__PURE__*/ jsx(Box, {
|
|
312
|
+
"aria-hidden": true,
|
|
313
|
+
height: `${reservedSpace}px`,
|
|
314
|
+
"data-bar-spacer": true
|
|
299
315
|
}),
|
|
300
|
-
/*#__PURE__*/
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
316
|
+
/*#__PURE__*/ jsxs(Bar, {
|
|
317
|
+
ref: setBarEl,
|
|
318
|
+
$isDrawerOpen: isDetailsDrawerOpen,
|
|
319
|
+
$isStacked: isAiMetadataEnabled,
|
|
320
|
+
tag: "section",
|
|
321
|
+
role: "region",
|
|
322
|
+
// The bar floats over the list but is not part of its background: a
|
|
323
|
+
// right-click on it keeps the browser's own menu. See MainAreaContextMenu.
|
|
324
|
+
"data-native-context-menu": true,
|
|
325
|
+
"aria-label": formatMessage({
|
|
326
|
+
id: getTranslationKey('list.bulk-actions.label'),
|
|
327
|
+
defaultMessage: 'Bulk actions'
|
|
328
|
+
}),
|
|
311
329
|
children: [
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
id: getTranslationKey('list.bulk-actions.create-metadata'),
|
|
323
|
-
defaultMessage: 'Create metadata'
|
|
324
|
-
})
|
|
325
|
-
})
|
|
330
|
+
/*#__PURE__*/ jsx(Typography, {
|
|
331
|
+
"data-bar-slot": "count",
|
|
332
|
+
fontWeight: "bold",
|
|
333
|
+
textColor: "neutral800",
|
|
334
|
+
marginRight: 4,
|
|
335
|
+
children: formatMessage({
|
|
336
|
+
id: getTranslationKey('list.bulk-actions.selected-count'),
|
|
337
|
+
defaultMessage: '{count, plural, =1 {# item selected} other {# items selected}}'
|
|
338
|
+
}, {
|
|
339
|
+
count
|
|
326
340
|
})
|
|
327
341
|
}),
|
|
328
|
-
/*#__PURE__*/ jsx(
|
|
329
|
-
|
|
342
|
+
/*#__PURE__*/ jsx(TextButton, {
|
|
343
|
+
onClick: handleSelectAll,
|
|
344
|
+
marginRight: 4,
|
|
330
345
|
disabled: isBusy,
|
|
331
|
-
|
|
332
|
-
id: getTranslationKey('list.bulk-actions.
|
|
333
|
-
defaultMessage: '
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
|
|
346
|
+
children: formatMessage({
|
|
347
|
+
id: getTranslationKey('list.bulk-actions.select-all'),
|
|
348
|
+
defaultMessage: 'Select all'
|
|
349
|
+
})
|
|
350
|
+
}),
|
|
351
|
+
/*#__PURE__*/ jsxs(ActionCluster, {
|
|
352
|
+
"data-bar-slot": "actions",
|
|
353
|
+
children: [
|
|
354
|
+
isAiMetadataEnabled && /*#__PURE__*/ jsx(Tooltip, {
|
|
355
|
+
label: metadataDisabledReason,
|
|
356
|
+
children: /*#__PURE__*/ jsx(Box, {
|
|
357
|
+
children: /*#__PURE__*/ jsx(Button, {
|
|
358
|
+
size: "S",
|
|
359
|
+
startIcon: /*#__PURE__*/ jsx(Sparkle, {}),
|
|
360
|
+
disabled: isBusy || selectedIds.size === 0 || isOverMetadataLimit || hasNoEligibleAssets,
|
|
361
|
+
loading: isGeneratingMetadata,
|
|
362
|
+
onClick: handleCreateMetadata,
|
|
363
|
+
children: formatMessage({
|
|
364
|
+
id: getTranslationKey('list.bulk-actions.create-metadata'),
|
|
365
|
+
defaultMessage: 'Create metadata'
|
|
366
|
+
})
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
}),
|
|
370
|
+
/*#__PURE__*/ jsx(IconButton, {
|
|
371
|
+
variant: "tertiary",
|
|
372
|
+
disabled: isBusy,
|
|
373
|
+
label: formatMessage({
|
|
374
|
+
id: getTranslationKey('list.bulk-actions.move'),
|
|
375
|
+
defaultMessage: 'Move'
|
|
376
|
+
}),
|
|
377
|
+
onClick: ()=>setIsMoveDialogOpen(true),
|
|
378
|
+
children: /*#__PURE__*/ jsx(ArrowRight, {})
|
|
379
|
+
}),
|
|
380
|
+
/*#__PURE__*/ jsx(BulkMoveDialog, {
|
|
381
|
+
open: isMoveDialogOpen,
|
|
382
|
+
onClose: ()=>setIsMoveDialogOpen(false),
|
|
383
|
+
items: moveItems,
|
|
384
|
+
onSuccess: clear
|
|
385
|
+
}),
|
|
386
|
+
/*#__PURE__*/ jsx(IconButton, {
|
|
387
|
+
variant: "danger-light",
|
|
388
|
+
disabled: isBusy,
|
|
389
|
+
label: formatMessage({
|
|
390
|
+
id: getTranslationKey('list.bulk-actions.delete'),
|
|
391
|
+
defaultMessage: 'Delete'
|
|
392
|
+
}),
|
|
393
|
+
onClick: ()=>setIsDeleteDialogOpen(true),
|
|
394
|
+
children: /*#__PURE__*/ jsx(Trash, {})
|
|
395
|
+
}),
|
|
396
|
+
/*#__PURE__*/ jsx(DeleteItemsDialog, {
|
|
397
|
+
open: isDeleteDialogOpen,
|
|
398
|
+
onClose: ()=>setIsDeleteDialogOpen(false),
|
|
399
|
+
target: {
|
|
400
|
+
fileIds: Array.from(selectedIds),
|
|
401
|
+
folderIds: Array.from(selectedFolderIds)
|
|
402
|
+
},
|
|
403
|
+
onSuccess: clear,
|
|
404
|
+
onPendingChange: setIsDeleting
|
|
405
|
+
})
|
|
406
|
+
]
|
|
337
407
|
}),
|
|
338
|
-
/*#__PURE__*/ jsx(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
items: moveItems,
|
|
342
|
-
onSuccess: clear
|
|
408
|
+
/*#__PURE__*/ jsx(VerticalDivider, {
|
|
409
|
+
"data-bar-slot": "divider",
|
|
410
|
+
"aria-hidden": true
|
|
343
411
|
}),
|
|
344
412
|
/*#__PURE__*/ jsx(IconButton, {
|
|
345
|
-
variant: "
|
|
346
|
-
disabled: isBusy,
|
|
413
|
+
variant: "ghost",
|
|
347
414
|
label: formatMessage({
|
|
348
|
-
id: getTranslationKey('list.bulk-actions.
|
|
349
|
-
defaultMessage: '
|
|
415
|
+
id: getTranslationKey('list.bulk-actions.clear'),
|
|
416
|
+
defaultMessage: 'Clear selection'
|
|
350
417
|
}),
|
|
351
|
-
onClick:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
/*#__PURE__*/ jsx(DeleteItemsDialog, {
|
|
355
|
-
open: isDeleteDialogOpen,
|
|
356
|
-
onClose: ()=>setIsDeleteDialogOpen(false),
|
|
357
|
-
target: {
|
|
358
|
-
fileIds: Array.from(selectedIds),
|
|
359
|
-
folderIds: Array.from(selectedFolderIds)
|
|
360
|
-
},
|
|
361
|
-
onSuccess: clear,
|
|
362
|
-
onPendingChange: setIsDeleting
|
|
418
|
+
onClick: clear,
|
|
419
|
+
disabled: isBusy,
|
|
420
|
+
children: /*#__PURE__*/ jsx(Cross, {})
|
|
363
421
|
})
|
|
364
422
|
]
|
|
365
|
-
}),
|
|
366
|
-
/*#__PURE__*/ jsx(VerticalDivider, {
|
|
367
|
-
"data-bar-slot": "divider",
|
|
368
|
-
"aria-hidden": true
|
|
369
|
-
}),
|
|
370
|
-
/*#__PURE__*/ jsx(IconButton, {
|
|
371
|
-
variant: "ghost",
|
|
372
|
-
label: formatMessage({
|
|
373
|
-
id: getTranslationKey('list.bulk-actions.clear'),
|
|
374
|
-
defaultMessage: 'Clear selection'
|
|
375
|
-
}),
|
|
376
|
-
onClick: clear,
|
|
377
|
-
disabled: isBusy,
|
|
378
|
-
children: /*#__PURE__*/ jsx(Cross, {})
|
|
379
423
|
})
|
|
380
424
|
]
|
|
381
425
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BulkActionsBar.mjs","sources":["../../../../../../admin/src/future/pages/Assets/components/BulkActionsBar.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport {\n Box,\n Button,\n Flex,\n IconButton,\n TextButton,\n Tooltip,\n Typography,\n} from '@strapi/design-system';\nimport { ArrowRight, Cross, Sparkle, Trash } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { css, styled } from 'styled-components';\n\nimport {\n AI_METADATA_MAX_FILES,\n isAIMetadataSupportedMime,\n} from '../../../../../../shared/constants';\nimport { useAIMetadataEnabled } from '../../../hooks/useAIMetadataEnabled';\nimport { useMediaLibraryPermissions } from '../../../hooks/useMediaLibraryPermissions';\nimport { useTracking } from '../../../hooks/useTracking';\nimport { useGenerateAiMetadataMutation } from '../../../services/assets';\nimport { buildDragSetFromSelection } from '../../../utils/buildDragSetFromSelection';\nimport { emptyItemLocations, type ItemLocations } from '../../../utils/itemLocations';\nimport { getTranslationKey } from '../../../utils/translations';\nimport { useAssetSelection } from '../hooks/useAssetSelection';\nimport { useFolderNavigation } from '../hooks/useFolderNavigation';\nimport { useIsAssetDetailsOpen } from '../hooks/useIsAssetDetailsOpen';\nimport { type ItemKey } from '../utils/selection';\n\nimport { BulkMoveDialog } from './BulkMoveDialog';\nimport { DeleteItemsDialog } from './DeleteItemsDialog';\n\nimport type { File } from '../../../../../../shared/contracts/files';\n\n/**\n * Bulk action bar for the future Media Library. Mobile: docked full-bleed to the\n * bottom edge (no radius, top border only). Desktop (medium+): a floating,\n * centered pill.\n */\nconst Bar = styled(Flex)<{ $isDrawerOpen: boolean; $isStacked: boolean }>`\n position: fixed;\n z-index: ${({ theme }) => theme.zIndices.popover};\n left: 0;\n right: 0;\n bottom: 0;\n align-items: center;\n gap: ${({ theme }) => theme.spaces[2]};\n padding: ${({ theme }) =>\n `${theme.spaces[3]} ${theme.spaces[2]} ${theme.spaces[3]} ${theme.spaces[6]}`};\n background: ${({ theme }) => theme.colors.neutral0};\n border: 0;\n border-top: 1px solid ${({ theme }) => theme.colors.neutral150};\n border-radius: 0;\n box-shadow: ${({ theme }) => theme.shadows.popupShadow};\n\n /* Docked full-bleed at the bottom on mobile, which is exactly where the open\n drawer keeps its own actions — so it steps aside there, and only there. */\n display: ${({ $isDrawerOpen }) => ($isDrawerOpen ? 'none' : 'flex')};\n\n /* Mobile with the metadata action present: the labelled button plus the icons\n no longer fit beside the count on one line, so the count takes a row of its\n own and every button drops to the next.\n\n Addressed by slot rather than by position: these rules used to use\n nth-child, which silently retargeted the moment a control was inserted\n into the row. */\n ${({ $isStacked }) =>\n $isStacked &&\n css`\n flex-wrap: wrap;\n justify-content: space-between;\n\n > [data-bar-slot='count'] {\n flex-basis: 100%;\n margin-right: 0;\n }\n\n > [data-bar-slot='actions'] {\n margin-left: 0;\n }\n\n /* The divider only existed to set the clear action apart from the rest;\n with the row spread it would hang in mid-air between them. */\n > [data-bar-slot='divider'] {\n display: none;\n }\n `}\n\n ${({ theme }) => theme.breakpoints.medium} {\n display: flex;\n left: 50%;\n right: auto;\n bottom: ${({ theme }) => theme.spaces[4]};\n transform: translateX(-50%);\n border: 1px solid ${({ theme }) => theme.colors.neutral150};\n border-radius: ${({ theme }) => theme.borderRadius};\n /* Sized by its content, capped so the pill can never span the whole\n viewport. The nowrap is what lets the content set that width — without it\n the labels wrap and the bar reads as narrow and tall. Inherited, so it\n covers every label inside.\n\n Deliberately not applied on mobile: there the bar is full-bleed and\n cannot grow, so refusing to wrap would clip the last action on a narrow\n phone rather than widen anything. */\n white-space: nowrap;\n max-width: 90%;\n\n /* One line again from tablet up, where it fits. */\n flex-wrap: nowrap;\n\n > [data-bar-slot='count'] {\n flex-basis: auto;\n }\n\n > [data-bar-slot='actions'] {\n margin-left: auto;\n }\n\n > [data-bar-slot='divider'] {\n display: block;\n }\n }\n`;\n\nconst ActionCluster = styled(Flex)`\n margin-left: auto;\n align-items: center;\n gap: ${({ theme }) => theme.spaces[2]};\n`;\n\nconst VerticalDivider = styled(Box)`\n width: 1px;\n align-self: stretch;\n background: ${({ theme }) => theme.colors.neutral150};\n margin-left: ${({ theme }) => theme.spaces[1]};\n`;\n\ninterface BulkActionsBarProps {\n /**\n * The assets currently rendered in the list, used to resolve the mime types\n * of the selection — the bar needs them to know which selected assets the AI\n * provider can actually read.\n *\n * Selection is page-scoped and cleared whenever the list identity changes, so\n * every selected asset id is guaranteed to appear here.\n */\n assets?: File[];\n /**\n * Real location of every loaded row, so the move dialog validates each\n * selected item against its own parent. Defaults to a lookup that misses\n * everything, which falls back to the folder currently open.\n */\n locations?: ItemLocations;\n /**\n * Keys of the items on screen, in render order. Owned by the view so\n * select-all covers exactly what the user can see — in mixed mode that is not\n * every folder.\n */\n renderedKeys?: ItemKey[];\n}\n\nexport const BulkActionsBar = ({\n assets = [],\n locations = emptyItemLocations,\n renderedKeys = [],\n}: BulkActionsBarProps) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n // No mime argument: the action is selection-scoped, so the per-asset\n // allowlist check happens below in `eligibleCount`.\n const isAiMetadataEnabled = useAIMetadataEnabled();\n // Every bulk action (move, delete, metadata) is an `assets.update` mutation\n // server-side — one flag gates the whole cluster.\n const { canUpdate } = useMediaLibraryPermissions();\n const { selectedIds, selectedFolderIds, selectAll, clear } = useAssetSelection();\n const { trackUsage } = useTracking();\n const { currentFolderId } = useFolderNavigation();\n const isDetailsDrawerOpen = useIsAssetDetailsOpen();\n const [generateAiMetadata, { isLoading: isGeneratingMetadata }] = useGenerateAiMetadataMutation();\n const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);\n const [isMoveDialogOpen, setIsMoveDialogOpen] = useState(false);\n // The delete request lives in DeleteItemsDialog; the bar mirrors its pending\n // state so the rest of the controls stay disabled for the duration.\n const [isDeleting, setIsDeleting] = useState(false);\n\n const count = selectedIds.size + selectedFolderIds.size;\n\n const handleSelectAll = () => {\n trackUsage('didSelectAllMediaLibraryElements');\n selectAll(renderedKeys);\n };\n const isBusy = isDeleting || isGeneratingMetadata;\n\n // Stable identity: the move dialog memoizes its destination walk on it. Each\n // item's real location comes from `locations`, so a selection made under a\n // global search validates against each item's own parent rather than the\n // folder currently open.\n const moveItems = useMemo(\n () => buildDragSetFromSelection(selectedIds, selectedFolderIds, locations, currentFolderId),\n [selectedIds, selectedFolderIds, locations, currentFolderId]\n );\n\n /**\n * The server processes the whole selection inside a single request and\n * rejects anything above the cap with a 400. Disable the action up front so\n * the limit is discoverable before the user commits to a request.\n */\n const isOverMetadataLimit = selectedIds.size > AI_METADATA_MAX_FILES;\n\n /**\n * How many selected assets the AI provider can actually read. The server\n * stays authoritative — it still reports what it skipped — but a selection\n * with nothing eligible (a lone PDF, say) would only ever come back fully\n * skipped, so there is no point sending it.\n */\n const eligibleCount = useMemo(() => {\n const mimeById = new Map(assets.map(({ id, mime }) => [id, mime]));\n\n return [...selectedIds].filter((id) => isAIMetadataSupportedMime(mimeById.get(id))).length;\n }, [assets, selectedIds]);\n\n const hasNoEligibleAssets = selectedIds.size > 0 && eligibleCount === 0;\n\n /**\n * Why the action is unavailable, surfaced on hover. `undefined` leaves the\n * button without a tooltip — either it is usable, or it is disabled for a\n * reason the selection count already makes obvious.\n */\n let metadataDisabledReason: string | undefined;\n\n if (isOverMetadataLimit) {\n metadataDisabledReason = formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.create-metadata.too-many'),\n defaultMessage:\n 'Metadata can be generated for up to {max} assets at a time. Select fewer assets to continue.',\n },\n { max: AI_METADATA_MAX_FILES }\n );\n } else if (hasNoEligibleAssets) {\n metadataDisabledReason = formatMessage({\n id: getTranslationKey('list.bulk-actions.create-metadata.no-eligible'),\n defaultMessage:\n 'Metadata can only be generated for images. None of the selected assets are supported.',\n });\n }\n\n /**\n * Metadata generation only applies to images — folders and unsupported files\n * in the selection are reported back rather than blocking the action.\n */\n const handleCreateMetadata = async () => {\n // Guard re-entry while pending, and never send a selection the server\n // would reject outright or skip in full.\n if (isGeneratingMetadata || isOverMetadataLimit || hasNoEligibleAssets) {\n return;\n }\n\n const fileIds = Array.from(selectedIds);\n const res = await generateAiMetadata({ fileIds });\n\n if ('error' in res) {\n // Keep the selection intact so the user can retry.\n toggleNotification({\n type: 'danger',\n message: formatMessage({\n id: getTranslationKey('list.bulk-actions.create-metadata.error'),\n defaultMessage: 'An error occurred while generating metadata.',\n }),\n });\n return;\n }\n\n const successCount = res.data.filter(({ status }) => status === 'success').length;\n const skippedCount = res.data.filter(({ status }) => status === 'skipped').length;\n const errorCount = res.data.filter(({ status }) => status === 'error').length;\n // Folders are never sent, so they are not part of the response — but the\n // user selected them, and a silent omission reads as a bug.\n const folderCount = selectedFolderIds.size;\n\n if (errorCount === res.data.length) {\n // Nothing was written — treat it like a request-level failure and keep\n // the selection so the user can retry.\n toggleNotification({\n type: 'danger',\n message: formatMessage({\n id: getTranslationKey('list.bulk-actions.create-metadata.error'),\n defaultMessage: 'An error occurred while generating metadata.',\n }),\n });\n return;\n }\n\n if (skippedCount === 0 && errorCount === 0 && folderCount === 0) {\n toggleNotification({\n type: 'success',\n message: formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.create-metadata.success'),\n defaultMessage:\n '{count, plural, =1 {Metadata generated for # asset} other {Metadata generated for # assets}}',\n },\n { count: successCount }\n ),\n });\n } else {\n // Partial outcome: report every bucket so the user knows what was left\n // out — including folders, which were never eligible in the first place.\n toggleNotification({\n type: 'warning',\n message: formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.create-metadata.partial'),\n defaultMessage:\n '{successCount} generated, {skippedCount} skipped (unsupported file type), {errorCount} failed{folderCount, plural, =0 {} one {, # folder ignored} other {, # folders ignored}}',\n },\n { successCount, skippedCount, errorCount, folderCount }\n ),\n });\n }\n\n clear();\n };\n\n // Every bulk action is behind `assets.update`; with nothing selected, or\n // without the permission, the bar has nothing to offer — drop it entirely\n // rather than show a count + \"Clear selection\" over no actions (mirrors the\n // drawer footer, which hides when no permitted action survives).\n //\n // With the drawer open the bar is hidden on mobile only, in CSS: there the two\n // are stacked at `bottom: 0` and the bar (popover, 500) would cover the\n // drawer's footer actions, since the drawer sits below the overlay token at\n // 200. From `medium` up the drawer is a side panel and the bar is a centered\n // pill, so they no longer collide.\n if (count === 0 || !canUpdate) {\n return null;\n }\n\n return (\n <Bar\n $isDrawerOpen={isDetailsDrawerOpen}\n $isStacked={isAiMetadataEnabled}\n tag=\"section\"\n role=\"region\"\n aria-label={formatMessage({\n id: getTranslationKey('list.bulk-actions.label'),\n defaultMessage: 'Bulk actions',\n })}\n >\n <Typography data-bar-slot=\"count\" fontWeight=\"bold\" textColor=\"neutral800\" marginRight={4}>\n {formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.selected-count'),\n defaultMessage: '{count, plural, =1 {# item selected} other {# items selected}}',\n },\n { count }\n )}\n </Typography>\n\n <TextButton onClick={handleSelectAll} marginRight={4} disabled={isBusy}>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.select-all'),\n defaultMessage: 'Select all',\n })}\n </TextButton>\n\n {/* Past the early return the user always has `assets.update`, so the\n individual actions no longer re-check it. */}\n <ActionCluster data-bar-slot=\"actions\">\n {isAiMetadataEnabled && (\n <Tooltip label={metadataDisabledReason}>\n {/* Wrapped so the tooltip still receives pointer events while the\n button itself is disabled. */}\n <Box>\n <Button\n size=\"S\"\n startIcon={<Sparkle />}\n disabled={\n isBusy || selectedIds.size === 0 || isOverMetadataLimit || hasNoEligibleAssets\n }\n loading={isGeneratingMetadata}\n onClick={handleCreateMetadata}\n >\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.create-metadata'),\n defaultMessage: 'Create metadata',\n })}\n </Button>\n </Box>\n </Tooltip>\n )}\n\n <IconButton\n variant=\"tertiary\"\n disabled={isBusy}\n label={formatMessage({\n id: getTranslationKey('list.bulk-actions.move'),\n defaultMessage: 'Move',\n })}\n onClick={() => setIsMoveDialogOpen(true)}\n >\n <ArrowRight />\n </IconButton>\n <BulkMoveDialog\n open={isMoveDialogOpen}\n onClose={() => setIsMoveDialogOpen(false)}\n items={moveItems}\n onSuccess={clear}\n />\n\n <IconButton\n variant=\"danger-light\"\n disabled={isBusy}\n label={formatMessage({\n id: getTranslationKey('list.bulk-actions.delete'),\n defaultMessage: 'Delete',\n })}\n onClick={() => setIsDeleteDialogOpen(true)}\n >\n <Trash />\n </IconButton>\n <DeleteItemsDialog\n open={isDeleteDialogOpen}\n onClose={() => setIsDeleteDialogOpen(false)}\n target={{\n fileIds: Array.from(selectedIds),\n folderIds: Array.from(selectedFolderIds),\n }}\n onSuccess={clear}\n onPendingChange={setIsDeleting}\n />\n </ActionCluster>\n\n <VerticalDivider data-bar-slot=\"divider\" aria-hidden />\n\n <IconButton\n variant=\"ghost\"\n label={formatMessage({\n id: getTranslationKey('list.bulk-actions.clear'),\n defaultMessage: 'Clear selection',\n })}\n onClick={clear}\n disabled={isBusy}\n >\n <Cross />\n </IconButton>\n </Bar>\n );\n};\n"],"names":["Bar","styled","Flex","theme","zIndices","popover","spaces","colors","neutral0","neutral150","shadows","popupShadow","$isDrawerOpen","$isStacked","css","breakpoints","medium","borderRadius","ActionCluster","VerticalDivider","Box","BulkActionsBar","assets","locations","emptyItemLocations","renderedKeys","formatMessage","useIntl","toggleNotification","useNotification","isAiMetadataEnabled","useAIMetadataEnabled","canUpdate","useMediaLibraryPermissions","selectedIds","selectedFolderIds","selectAll","clear","useAssetSelection","trackUsage","useTracking","currentFolderId","useFolderNavigation","isDetailsDrawerOpen","useIsAssetDetailsOpen","generateAiMetadata","isLoading","isGeneratingMetadata","useGenerateAiMetadataMutation","isDeleteDialogOpen","setIsDeleteDialogOpen","useState","isMoveDialogOpen","setIsMoveDialogOpen","isDeleting","setIsDeleting","count","size","handleSelectAll","isBusy","moveItems","useMemo","buildDragSetFromSelection","isOverMetadataLimit","AI_METADATA_MAX_FILES","eligibleCount","mimeById","Map","map","id","mime","filter","isAIMetadataSupportedMime","get","length","hasNoEligibleAssets","metadataDisabledReason","getTranslationKey","defaultMessage","max","handleCreateMetadata","fileIds","Array","from","res","type","message","successCount","data","status","skippedCount","errorCount","folderCount","_jsxs","tag","role","aria-label","_jsx","Typography","data-bar-slot","fontWeight","textColor","marginRight","TextButton","onClick","disabled","Tooltip","label","Button","startIcon","Sparkle","loading","IconButton","variant","ArrowRight","BulkMoveDialog","open","onClose","items","onSuccess","Trash","DeleteItemsDialog","target","folderIds","onPendingChange","aria-hidden","Cross"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BulkActionsBar.mjs","sources":["../../../../../../admin/src/future/pages/Assets/components/BulkActionsBar.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport {\n Box,\n Button,\n Flex,\n IconButton,\n TextButton,\n Tooltip,\n Typography,\n} from '@strapi/design-system';\nimport { ArrowRight, Cross, Sparkle, Trash } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { css, styled } from 'styled-components';\n\nimport {\n AI_METADATA_MAX_FILES,\n isAIMetadataSupportedMime,\n} from '../../../../../../shared/constants';\nimport { useAIMetadataEnabled } from '../../../hooks/useAIMetadataEnabled';\nimport { useMediaLibraryPermissions } from '../../../hooks/useMediaLibraryPermissions';\nimport { useTracking } from '../../../hooks/useTracking';\nimport { useGenerateAiMetadataMutation } from '../../../services/assets';\nimport { buildDragSetFromSelection } from '../../../utils/buildDragSetFromSelection';\nimport { emptyItemLocations, type ItemLocations } from '../../../utils/itemLocations';\nimport { getTranslationKey } from '../../../utils/translations';\nimport { useAssetSelection } from '../hooks/useAssetSelection';\nimport { useFolderNavigation } from '../hooks/useFolderNavigation';\nimport { useIsAssetDetailsOpen } from '../hooks/useIsAssetDetailsOpen';\nimport { type ItemKey } from '../utils/selection';\n\nimport { BulkMoveDialog } from './BulkMoveDialog';\nimport { DeleteItemsDialog } from './DeleteItemsDialog';\n\nimport type { File } from '../../../../../../shared/contracts/files';\n\n/**\n * Bulk action bar for the future Media Library. Mobile: docked full-bleed to the\n * bottom edge (no radius, top border only). Desktop (medium+): a floating,\n * centered pill.\n */\nconst Bar = styled(Flex)<{ $isDrawerOpen: boolean; $isStacked: boolean }>`\n position: fixed;\n z-index: ${({ theme }) => theme.zIndices.popover};\n left: 0;\n right: 0;\n bottom: 0;\n align-items: center;\n gap: ${({ theme }) => theme.spaces[2]};\n padding: ${({ theme }) =>\n `${theme.spaces[3]} ${theme.spaces[2]} ${theme.spaces[3]} ${theme.spaces[6]}`};\n background: ${({ theme }) => theme.colors.neutral0};\n border: 0;\n border-top: 1px solid ${({ theme }) => theme.colors.neutral150};\n border-radius: 0;\n box-shadow: ${({ theme }) => theme.shadows.popupShadow};\n\n /* Docked full-bleed at the bottom on mobile, which is exactly where the open\n drawer keeps its own actions — so it steps aside there, and only there. */\n display: ${({ $isDrawerOpen }) => ($isDrawerOpen ? 'none' : 'flex')};\n\n /* Mobile with the metadata action present: the labelled button plus the icons\n no longer fit beside the count on one line, so the count takes a row of its\n own and every button drops to the next.\n\n Addressed by slot rather than by position: these rules used to use\n nth-child, which silently retargeted the moment a control was inserted\n into the row. */\n ${({ $isStacked }) =>\n $isStacked &&\n css`\n flex-wrap: wrap;\n justify-content: space-between;\n\n > [data-bar-slot='count'] {\n flex-basis: 100%;\n margin-right: 0;\n }\n\n > [data-bar-slot='actions'] {\n margin-left: 0;\n }\n\n /* The divider only existed to set the clear action apart from the rest;\n with the row spread it would hang in mid-air between them. */\n > [data-bar-slot='divider'] {\n display: none;\n }\n `}\n\n ${({ theme }) => theme.breakpoints.medium} {\n display: flex;\n left: 50%;\n right: auto;\n bottom: ${({ theme }) => theme.spaces[4]};\n transform: translateX(-50%);\n border: 1px solid ${({ theme }) => theme.colors.neutral150};\n border-radius: ${({ theme }) => theme.borderRadius};\n /* Sized by its content, capped so the pill can never span the whole\n viewport. The nowrap is what lets the content set that width — without it\n the labels wrap and the bar reads as narrow and tall. Inherited, so it\n covers every label inside.\n\n Deliberately not applied on mobile: there the bar is full-bleed and\n cannot grow, so refusing to wrap would clip the last action on a narrow\n phone rather than widen anything. */\n white-space: nowrap;\n max-width: 90%;\n\n /* One line again from tablet up, where it fits. */\n flex-wrap: nowrap;\n\n > [data-bar-slot='count'] {\n flex-basis: auto;\n }\n\n > [data-bar-slot='actions'] {\n margin-left: auto;\n }\n\n > [data-bar-slot='divider'] {\n display: block;\n }\n }\n`;\n\nconst ActionCluster = styled(Flex)`\n margin-left: auto;\n align-items: center;\n gap: ${({ theme }) => theme.spaces[2]};\n`;\n\nconst VerticalDivider = styled(Box)`\n width: 1px;\n align-self: stretch;\n background: ${({ theme }) => theme.colors.neutral150};\n margin-left: ${({ theme }) => theme.spaces[1]};\n`;\n\ninterface BulkActionsBarProps {\n /**\n * The assets currently rendered in the list, used to resolve the mime types\n * of the selection — the bar needs them to know which selected assets the AI\n * provider can actually read.\n *\n * Selection is page-scoped and cleared whenever the list identity changes, so\n * every selected asset id is guaranteed to appear here.\n */\n assets?: File[];\n /**\n * Real location of every loaded row, so the move dialog validates each\n * selected item against its own parent. Defaults to a lookup that misses\n * everything, which falls back to the folder currently open.\n */\n locations?: ItemLocations;\n /**\n * Keys of the items on screen, in render order. Owned by the view so\n * select-all covers exactly what the user can see — in mixed mode that is not\n * every folder.\n */\n renderedKeys?: ItemKey[];\n}\n\nexport const BulkActionsBar = ({\n assets = [],\n locations = emptyItemLocations,\n renderedKeys = [],\n}: BulkActionsBarProps) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n // No mime argument: the action is selection-scoped, so the per-asset\n // allowlist check happens below in `eligibleCount`.\n const isAiMetadataEnabled = useAIMetadataEnabled();\n // Every bulk action (move, delete, metadata) is an `assets.update` mutation\n // server-side — one flag gates the whole cluster.\n const { canUpdate } = useMediaLibraryPermissions();\n const { selectedIds, selectedFolderIds, selectAll, clear } = useAssetSelection();\n const { trackUsage } = useTracking();\n const { currentFolderId } = useFolderNavigation();\n const isDetailsDrawerOpen = useIsAssetDetailsOpen();\n const [generateAiMetadata, { isLoading: isGeneratingMetadata }] = useGenerateAiMetadataMutation();\n const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);\n const [isMoveDialogOpen, setIsMoveDialogOpen] = useState(false);\n // The delete request lives in DeleteItemsDialog; the bar mirrors its pending\n // state so the rest of the controls stay disabled for the duration.\n const [isDeleting, setIsDeleting] = useState(false);\n\n const count = selectedIds.size + selectedFolderIds.size;\n\n // `innerHeight - top` rather than the bar's height: it also spans the gap the\n // bar floats above the viewport edge (0 docked on mobile, `spaces[4]` for the\n // desktop pill), so one measurement covers both layouts and no height is\n // hardcoded.\n const [barEl, setBarEl] = useState<HTMLElement | null>(null);\n const [reservedSpace, setReservedSpace] = useState(0);\n\n useEffect(() => {\n if (!barEl) {\n setReservedSpace(0);\n return;\n }\n\n const measure = () => {\n const rect = barEl.getBoundingClientRect();\n // Zero height means the CSS has hidden the bar (drawer open on mobile) —\n // reserving anything then would leave a gap under nothing.\n setReservedSpace(rect.height === 0 ? 0 : Math.max(0, window.innerHeight - rect.top));\n };\n\n measure();\n\n const observer = new ResizeObserver(measure);\n observer.observe(barEl);\n window.addEventListener('resize', measure);\n\n return () => {\n observer.disconnect();\n window.removeEventListener('resize', measure);\n };\n // `isDetailsDrawerOpen` toggles the bar's `display`, which a ResizeObserver\n // is not guaranteed to report.\n }, [barEl, isDetailsDrawerOpen]);\n\n const handleSelectAll = () => {\n trackUsage('didSelectAllMediaLibraryElements');\n selectAll(renderedKeys);\n };\n const isBusy = isDeleting || isGeneratingMetadata;\n\n // Stable identity: the move dialog memoizes its destination walk on it. Each\n // item's real location comes from `locations`, so a selection made under a\n // global search validates against each item's own parent rather than the\n // folder currently open.\n const moveItems = useMemo(\n () => buildDragSetFromSelection(selectedIds, selectedFolderIds, locations, currentFolderId),\n [selectedIds, selectedFolderIds, locations, currentFolderId]\n );\n\n /**\n * The server processes the whole selection inside a single request and\n * rejects anything above the cap with a 400. Disable the action up front so\n * the limit is discoverable before the user commits to a request.\n */\n const isOverMetadataLimit = selectedIds.size > AI_METADATA_MAX_FILES;\n\n /**\n * How many selected assets the AI provider can actually read. The server\n * stays authoritative — it still reports what it skipped — but a selection\n * with nothing eligible (a lone PDF, say) would only ever come back fully\n * skipped, so there is no point sending it.\n */\n const eligibleCount = useMemo(() => {\n const mimeById = new Map(assets.map(({ id, mime }) => [id, mime]));\n\n return [...selectedIds].filter((id) => isAIMetadataSupportedMime(mimeById.get(id))).length;\n }, [assets, selectedIds]);\n\n const hasNoEligibleAssets = selectedIds.size > 0 && eligibleCount === 0;\n\n /**\n * Why the action is unavailable, surfaced on hover. `undefined` leaves the\n * button without a tooltip — either it is usable, or it is disabled for a\n * reason the selection count already makes obvious.\n */\n let metadataDisabledReason: string | undefined;\n\n if (isOverMetadataLimit) {\n metadataDisabledReason = formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.create-metadata.too-many'),\n defaultMessage:\n 'Metadata can be generated for up to {max} assets at a time. Select fewer assets to continue.',\n },\n { max: AI_METADATA_MAX_FILES }\n );\n } else if (hasNoEligibleAssets) {\n metadataDisabledReason = formatMessage({\n id: getTranslationKey('list.bulk-actions.create-metadata.no-eligible'),\n defaultMessage:\n 'Metadata can only be generated for images. None of the selected assets are supported.',\n });\n }\n\n /**\n * Metadata generation only applies to images — folders and unsupported files\n * in the selection are reported back rather than blocking the action.\n */\n const handleCreateMetadata = async () => {\n // Guard re-entry while pending, and never send a selection the server\n // would reject outright or skip in full.\n if (isGeneratingMetadata || isOverMetadataLimit || hasNoEligibleAssets) {\n return;\n }\n\n const fileIds = Array.from(selectedIds);\n const res = await generateAiMetadata({ fileIds });\n\n if ('error' in res) {\n // Keep the selection intact so the user can retry.\n toggleNotification({\n type: 'danger',\n message: formatMessage({\n id: getTranslationKey('list.bulk-actions.create-metadata.error'),\n defaultMessage: 'An error occurred while generating metadata.',\n }),\n });\n return;\n }\n\n const successCount = res.data.filter(({ status }) => status === 'success').length;\n const skippedCount = res.data.filter(({ status }) => status === 'skipped').length;\n const errorCount = res.data.filter(({ status }) => status === 'error').length;\n // Folders are never sent, so they are not part of the response — but the\n // user selected them, and a silent omission reads as a bug.\n const folderCount = selectedFolderIds.size;\n\n if (errorCount === res.data.length) {\n // Nothing was written — treat it like a request-level failure and keep\n // the selection so the user can retry.\n toggleNotification({\n type: 'danger',\n message: formatMessage({\n id: getTranslationKey('list.bulk-actions.create-metadata.error'),\n defaultMessage: 'An error occurred while generating metadata.',\n }),\n });\n return;\n }\n\n if (skippedCount === 0 && errorCount === 0 && folderCount === 0) {\n toggleNotification({\n type: 'success',\n message: formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.create-metadata.success'),\n defaultMessage:\n '{count, plural, =1 {Metadata generated for # asset} other {Metadata generated for # assets}}',\n },\n { count: successCount }\n ),\n });\n } else {\n // Partial outcome: report every bucket so the user knows what was left\n // out — including folders, which were never eligible in the first place.\n toggleNotification({\n type: 'warning',\n message: formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.create-metadata.partial'),\n defaultMessage:\n '{successCount} generated, {skippedCount} skipped (unsupported file type), {errorCount} failed{folderCount, plural, =0 {} one {, # folder ignored} other {, # folders ignored}}',\n },\n { successCount, skippedCount, errorCount, folderCount }\n ),\n });\n }\n\n clear();\n };\n\n // Every bulk action is behind `assets.update`; with nothing selected, or\n // without the permission, the bar has nothing to offer — drop it entirely\n // rather than show a count + \"Clear selection\" over no actions (mirrors the\n // drawer footer, which hides when no permitted action survives).\n //\n // With the drawer open the bar is hidden on mobile only, in CSS: there the two\n // are stacked at `bottom: 0` and the bar (popover, 500) would cover the\n // drawer's footer actions, since the drawer sits below the overlay token at\n // 200. From `medium` up the drawer is a side panel and the bar is a centered\n // pill, so they no longer collide.\n if (count === 0 || !canUpdate) {\n return null;\n }\n\n return (\n <>\n <Box aria-hidden height={`${reservedSpace}px`} data-bar-spacer />\n <Bar\n ref={setBarEl}\n $isDrawerOpen={isDetailsDrawerOpen}\n $isStacked={isAiMetadataEnabled}\n tag=\"section\"\n role=\"region\"\n // The bar floats over the list but is not part of its background: a\n // right-click on it keeps the browser's own menu. See MainAreaContextMenu.\n data-native-context-menu\n aria-label={formatMessage({\n id: getTranslationKey('list.bulk-actions.label'),\n defaultMessage: 'Bulk actions',\n })}\n >\n <Typography data-bar-slot=\"count\" fontWeight=\"bold\" textColor=\"neutral800\" marginRight={4}>\n {formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.selected-count'),\n defaultMessage: '{count, plural, =1 {# item selected} other {# items selected}}',\n },\n { count }\n )}\n </Typography>\n\n <TextButton onClick={handleSelectAll} marginRight={4} disabled={isBusy}>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.select-all'),\n defaultMessage: 'Select all',\n })}\n </TextButton>\n\n {/* Past the early return the user always has `assets.update`, so the\n individual actions no longer re-check it. */}\n <ActionCluster data-bar-slot=\"actions\">\n {isAiMetadataEnabled && (\n <Tooltip label={metadataDisabledReason}>\n {/* Wrapped so the tooltip still receives pointer events while the\n button itself is disabled. */}\n <Box>\n <Button\n size=\"S\"\n startIcon={<Sparkle />}\n disabled={\n isBusy || selectedIds.size === 0 || isOverMetadataLimit || hasNoEligibleAssets\n }\n loading={isGeneratingMetadata}\n onClick={handleCreateMetadata}\n >\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.create-metadata'),\n defaultMessage: 'Create metadata',\n })}\n </Button>\n </Box>\n </Tooltip>\n )}\n\n <IconButton\n variant=\"tertiary\"\n disabled={isBusy}\n label={formatMessage({\n id: getTranslationKey('list.bulk-actions.move'),\n defaultMessage: 'Move',\n })}\n onClick={() => setIsMoveDialogOpen(true)}\n >\n <ArrowRight />\n </IconButton>\n <BulkMoveDialog\n open={isMoveDialogOpen}\n onClose={() => setIsMoveDialogOpen(false)}\n items={moveItems}\n onSuccess={clear}\n />\n\n <IconButton\n variant=\"danger-light\"\n disabled={isBusy}\n label={formatMessage({\n id: getTranslationKey('list.bulk-actions.delete'),\n defaultMessage: 'Delete',\n })}\n onClick={() => setIsDeleteDialogOpen(true)}\n >\n <Trash />\n </IconButton>\n <DeleteItemsDialog\n open={isDeleteDialogOpen}\n onClose={() => setIsDeleteDialogOpen(false)}\n target={{\n fileIds: Array.from(selectedIds),\n folderIds: Array.from(selectedFolderIds),\n }}\n onSuccess={clear}\n onPendingChange={setIsDeleting}\n />\n </ActionCluster>\n\n <VerticalDivider data-bar-slot=\"divider\" aria-hidden />\n\n <IconButton\n variant=\"ghost\"\n label={formatMessage({\n id: getTranslationKey('list.bulk-actions.clear'),\n defaultMessage: 'Clear selection',\n })}\n onClick={clear}\n disabled={isBusy}\n >\n <Cross />\n </IconButton>\n </Bar>\n </>\n );\n};\n"],"names":["Bar","styled","Flex","theme","zIndices","popover","spaces","colors","neutral0","neutral150","shadows","popupShadow","$isDrawerOpen","$isStacked","css","breakpoints","medium","borderRadius","ActionCluster","VerticalDivider","Box","BulkActionsBar","assets","locations","emptyItemLocations","renderedKeys","formatMessage","useIntl","toggleNotification","useNotification","isAiMetadataEnabled","useAIMetadataEnabled","canUpdate","useMediaLibraryPermissions","selectedIds","selectedFolderIds","selectAll","clear","useAssetSelection","trackUsage","useTracking","currentFolderId","useFolderNavigation","isDetailsDrawerOpen","useIsAssetDetailsOpen","generateAiMetadata","isLoading","isGeneratingMetadata","useGenerateAiMetadataMutation","isDeleteDialogOpen","setIsDeleteDialogOpen","useState","isMoveDialogOpen","setIsMoveDialogOpen","isDeleting","setIsDeleting","count","size","barEl","setBarEl","reservedSpace","setReservedSpace","useEffect","measure","rect","getBoundingClientRect","height","Math","max","window","innerHeight","top","observer","ResizeObserver","observe","addEventListener","disconnect","removeEventListener","handleSelectAll","isBusy","moveItems","useMemo","buildDragSetFromSelection","isOverMetadataLimit","AI_METADATA_MAX_FILES","eligibleCount","mimeById","Map","map","id","mime","filter","isAIMetadataSupportedMime","get","length","hasNoEligibleAssets","metadataDisabledReason","getTranslationKey","defaultMessage","handleCreateMetadata","fileIds","Array","from","res","type","message","successCount","data","status","skippedCount","errorCount","folderCount","_jsxs","_Fragment","_jsx","aria-hidden","data-bar-spacer","ref","tag","role","data-native-context-menu","aria-label","Typography","data-bar-slot","fontWeight","textColor","marginRight","TextButton","onClick","disabled","Tooltip","label","Button","startIcon","Sparkle","loading","IconButton","variant","ArrowRight","BulkMoveDialog","open","onClose","items","onSuccess","Trash","DeleteItemsDialog","target","folderIds","onPendingChange","Cross"],"mappings":"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"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var designSystem = require('@strapi/design-system');
|
|
6
|
+
var icons = require('@strapi/icons');
|
|
7
|
+
var reactIntl = require('react-intl');
|
|
8
|
+
var translations = require('../../../utils/translations.js');
|
|
9
|
+
var ActionsMenuContent = require('./ActionsMenuContent.js');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The admin layout marks the element that scrolls the main column. Listening on
|
|
13
|
+
* it rather than a wrapper of our own is what makes the whole column part of the
|
|
14
|
+
* hit area: it carries a responsive `padding-bottom` (16 / 24 / 40px) that no
|
|
15
|
+
* descendant can cover, and that strip is exactly where a right-click "below the
|
|
16
|
+
* list" lands.
|
|
17
|
+
*/ const SCROLL_ROOT_SELECTOR = '[data-strapi-main-content]';
|
|
18
|
+
/**
|
|
19
|
+
* Subtrees that keep the browser's own context menu instead of opening the
|
|
20
|
+
* create menu.
|
|
21
|
+
*
|
|
22
|
+
* `data-native-context-menu` is the explicit opt-out carried by the list items
|
|
23
|
+
* themselves (asset cards, folder cards, table rows) and by the bulk actions
|
|
24
|
+
* bar — right-clicking one of those must behave exactly as it does today. The
|
|
25
|
+
* rest are the generic cases where the native menu is the useful one: links,
|
|
26
|
+
* buttons and anything the user can type into or select text in.
|
|
27
|
+
*
|
|
28
|
+
* `thead` covers the table's header row, which is chrome rather than empty
|
|
29
|
+
* background even though it carries no `data-native-context-menu` item.
|
|
30
|
+
*/ const NATIVE_CONTEXT_MENU_SELECTOR = [
|
|
31
|
+
'[data-native-context-menu]',
|
|
32
|
+
'thead',
|
|
33
|
+
'a',
|
|
34
|
+
'button',
|
|
35
|
+
'input',
|
|
36
|
+
'textarea',
|
|
37
|
+
'select',
|
|
38
|
+
'[contenteditable="true"]'
|
|
39
|
+
].join(', ');
|
|
40
|
+
/**
|
|
41
|
+
* The menu is anchored to a zero-sized element parked at the cursor, because
|
|
42
|
+
* the design system's `Menu` is a dropdown: it positions its content against a
|
|
43
|
+
* trigger, and there is no real trigger for a right-click on empty space.
|
|
44
|
+
*
|
|
45
|
+
* Inline styles rather than a `styled` wrapper — the trigger renders a design
|
|
46
|
+
* system `Button`, and losing the specificity race with its own class would put
|
|
47
|
+
* a stray button on the page.
|
|
48
|
+
*/ const CURSOR_ANCHOR_STYLE = {
|
|
49
|
+
position: 'fixed',
|
|
50
|
+
width: 0,
|
|
51
|
+
height: 0,
|
|
52
|
+
minWidth: 0,
|
|
53
|
+
minHeight: 0,
|
|
54
|
+
padding: 0,
|
|
55
|
+
border: 0,
|
|
56
|
+
opacity: 0,
|
|
57
|
+
overflow: 'hidden',
|
|
58
|
+
pointerEvents: 'none'
|
|
59
|
+
};
|
|
60
|
+
const LOCATOR_STYLE = {
|
|
61
|
+
height: 0
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Drive/Dropbox-style right-click on the empty parts of the assets area: the
|
|
65
|
+
* same creation actions the header "New" menu offers, acting on the folder
|
|
66
|
+
* currently open.
|
|
67
|
+
*
|
|
68
|
+
* Renders no wrapper — it finds the scrolling column and listens there, so the
|
|
69
|
+
* hit area is the whole column including the padding below the list.
|
|
70
|
+
*
|
|
71
|
+
* Deliberately background-only. Right-clicking an asset, a folder or a table
|
|
72
|
+
* row is left alone — those carry `data-native-context-menu` and keep the
|
|
73
|
+
* browser's menu (which is what you want over a thumbnail or a filename).
|
|
74
|
+
*/ const MainAreaContextMenu = ({ onCreateFolder, onImportFiles, onImportFromUrl, disabled })=>{
|
|
75
|
+
const { formatMessage } = reactIntl.useIntl();
|
|
76
|
+
const [position, setPosition] = React.useState(null);
|
|
77
|
+
const [locator, setLocator] = React.useState(null);
|
|
78
|
+
const anchorRef = React.useCallback((node)=>setLocator(node), []);
|
|
79
|
+
React.useEffect(()=>{
|
|
80
|
+
const container = locator?.closest(SCROLL_ROOT_SELECTOR);
|
|
81
|
+
if (!container || disabled) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const handleContextMenu = (event)=>{
|
|
85
|
+
if (!(event.target instanceof Element)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (event.target.closest(NATIVE_CONTEXT_MENU_SELECTOR)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
// A right-click while the menu is open reaches Radix's dismiss listener
|
|
93
|
+
// first, which closes it; re-anchoring here reopens it under the new
|
|
94
|
+
// cursor rather than leaving the user with a menu that just vanished.
|
|
95
|
+
setPosition({
|
|
96
|
+
x: event.clientX,
|
|
97
|
+
y: event.clientY
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
container.addEventListener('contextmenu', handleContextMenu);
|
|
101
|
+
return ()=>container.removeEventListener('contextmenu', handleContextMenu);
|
|
102
|
+
}, [
|
|
103
|
+
locator,
|
|
104
|
+
disabled
|
|
105
|
+
]);
|
|
106
|
+
return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
107
|
+
children: [
|
|
108
|
+
/*#__PURE__*/ jsxRuntime.jsx("div", {
|
|
109
|
+
ref: anchorRef,
|
|
110
|
+
style: LOCATOR_STYLE,
|
|
111
|
+
"aria-hidden": true
|
|
112
|
+
}),
|
|
113
|
+
!disabled && /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Menu.Root, {
|
|
114
|
+
modal: false,
|
|
115
|
+
open: position !== null,
|
|
116
|
+
onOpenChange: (open)=>{
|
|
117
|
+
if (!open) {
|
|
118
|
+
setPosition(null);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
children: [
|
|
122
|
+
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Trigger, {
|
|
123
|
+
tabIndex: -1,
|
|
124
|
+
// No visible content, so the name has to come from `aria-label` —
|
|
125
|
+
// the trigger's `label` prop renders as button text, which would
|
|
126
|
+
// defeat the point of an invisible anchor. Radix points the menu's
|
|
127
|
+
// `aria-labelledby` at this element, so it names the menu too.
|
|
128
|
+
endIcon: null,
|
|
129
|
+
"aria-label": formatMessage({
|
|
130
|
+
id: translations.getTranslationKey('list.context-menu.label'),
|
|
131
|
+
defaultMessage: 'Media library actions'
|
|
132
|
+
}),
|
|
133
|
+
style: {
|
|
134
|
+
...CURSOR_ANCHOR_STYLE,
|
|
135
|
+
top: position?.y ?? 0,
|
|
136
|
+
left: position?.x ?? 0
|
|
137
|
+
}
|
|
138
|
+
}),
|
|
139
|
+
/*#__PURE__*/ jsxRuntime.jsxs(ActionsMenuContent.ActionsMenuContent, {
|
|
140
|
+
popoverPlacement: "bottom-start",
|
|
141
|
+
zIndex: 2,
|
|
142
|
+
minWidth: "22rem",
|
|
143
|
+
// The anchor is invisible and sits wherever the cursor was, so
|
|
144
|
+
// handing focus back to it on close would be a focus ring nobody can
|
|
145
|
+
// see. Let it fall to the body instead.
|
|
146
|
+
onCloseAutoFocus: (event)=>event.preventDefault(),
|
|
147
|
+
children: [
|
|
148
|
+
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Item, {
|
|
149
|
+
onSelect: onCreateFolder,
|
|
150
|
+
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.Folder, {}),
|
|
151
|
+
children: formatMessage({
|
|
152
|
+
id: translations.getTranslationKey('folder.create.title'),
|
|
153
|
+
defaultMessage: 'New folder'
|
|
154
|
+
})
|
|
155
|
+
}),
|
|
156
|
+
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Item, {
|
|
157
|
+
onSelect: onImportFiles,
|
|
158
|
+
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.Files, {}),
|
|
159
|
+
children: formatMessage({
|
|
160
|
+
id: translations.getTranslationKey('import-files'),
|
|
161
|
+
defaultMessage: 'File upload'
|
|
162
|
+
})
|
|
163
|
+
}),
|
|
164
|
+
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Item, {
|
|
165
|
+
onSelect: onImportFromUrl,
|
|
166
|
+
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.Link, {}),
|
|
167
|
+
children: formatMessage({
|
|
168
|
+
id: translations.getTranslationKey('import-from-url'),
|
|
169
|
+
defaultMessage: 'File upload from URL'
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
]
|
|
173
|
+
})
|
|
174
|
+
]
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
exports.MainAreaContextMenu = MainAreaContextMenu;
|
|
181
|
+
//# sourceMappingURL=MainAreaContextMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MainAreaContextMenu.js","sources":["../../../../../../admin/src/future/pages/Assets/components/MainAreaContextMenu.tsx"],"sourcesContent":["import { useCallback, useEffect, useState, type CSSProperties } from 'react';\n\nimport { Menu } from '@strapi/design-system';\nimport { Files, Folder as FolderIcon, Link } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport { getTranslationKey } from '../../../utils/translations';\n\nimport { ActionsMenuContent } from './ActionsMenuContent';\n\n/**\n * The admin layout marks the element that scrolls the main column. Listening on\n * it rather than a wrapper of our own is what makes the whole column part of the\n * hit area: it carries a responsive `padding-bottom` (16 / 24 / 40px) that no\n * descendant can cover, and that strip is exactly where a right-click \"below the\n * list\" lands.\n */\nconst SCROLL_ROOT_SELECTOR = '[data-strapi-main-content]';\n\n/**\n * Subtrees that keep the browser's own context menu instead of opening the\n * create menu.\n *\n * `data-native-context-menu` is the explicit opt-out carried by the list items\n * themselves (asset cards, folder cards, table rows) and by the bulk actions\n * bar — right-clicking one of those must behave exactly as it does today. The\n * rest are the generic cases where the native menu is the useful one: links,\n * buttons and anything the user can type into or select text in.\n *\n * `thead` covers the table's header row, which is chrome rather than empty\n * background even though it carries no `data-native-context-menu` item.\n */\nconst NATIVE_CONTEXT_MENU_SELECTOR = [\n '[data-native-context-menu]',\n 'thead',\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n '[contenteditable=\"true\"]',\n].join(', ');\n\n/**\n * The menu is anchored to a zero-sized element parked at the cursor, because\n * the design system's `Menu` is a dropdown: it positions its content against a\n * trigger, and there is no real trigger for a right-click on empty space.\n *\n * Inline styles rather than a `styled` wrapper — the trigger renders a design\n * system `Button`, and losing the specificity race with its own class would put\n * a stray button on the page.\n */\nconst CURSOR_ANCHOR_STYLE: CSSProperties = {\n position: 'fixed',\n width: 0,\n height: 0,\n minWidth: 0,\n minHeight: 0,\n padding: 0,\n border: 0,\n opacity: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n};\n\nconst LOCATOR_STYLE: CSSProperties = { height: 0 };\n\ninterface CursorPosition {\n x: number;\n y: number;\n}\n\ninterface MainAreaContextMenuProps {\n /** Opens the \"new folder\" dialog in the folder currently on screen. */\n onCreateFolder: () => void;\n /** Opens the native file picker — same entry point as the header \"New\" menu. */\n onImportFiles: () => void;\n /** Opens the \"import from URL\" dialog. */\n onImportFromUrl: () => void;\n /**\n * RBAC gate — without `assets.create` there is nothing to offer, so the\n * right-click falls through to the browser. Required (not defaulted) so a\n * permission gate can't silently regress to permissive.\n */\n disabled: boolean;\n}\n\n/**\n * Drive/Dropbox-style right-click on the empty parts of the assets area: the\n * same creation actions the header \"New\" menu offers, acting on the folder\n * currently open.\n *\n * Renders no wrapper — it finds the scrolling column and listens there, so the\n * hit area is the whole column including the padding below the list.\n *\n * Deliberately background-only. Right-clicking an asset, a folder or a table\n * row is left alone — those carry `data-native-context-menu` and keep the\n * browser's menu (which is what you want over a thumbnail or a filename).\n */\nexport const MainAreaContextMenu = ({\n onCreateFolder,\n onImportFiles,\n onImportFromUrl,\n disabled,\n}: MainAreaContextMenuProps) => {\n const { formatMessage } = useIntl();\n const [position, setPosition] = useState<CursorPosition | null>(null);\n const [locator, setLocator] = useState<HTMLElement | null>(null);\n\n const anchorRef = useCallback((node: HTMLElement | null) => setLocator(node), []);\n\n useEffect(() => {\n const container = locator?.closest<HTMLElement>(SCROLL_ROOT_SELECTOR);\n\n if (!container || disabled) {\n return;\n }\n\n const handleContextMenu = (event: MouseEvent) => {\n if (!(event.target instanceof Element)) {\n return;\n }\n\n if (event.target.closest(NATIVE_CONTEXT_MENU_SELECTOR)) {\n return;\n }\n\n event.preventDefault();\n // A right-click while the menu is open reaches Radix's dismiss listener\n // first, which closes it; re-anchoring here reopens it under the new\n // cursor rather than leaving the user with a menu that just vanished.\n setPosition({ x: event.clientX, y: event.clientY });\n };\n\n container.addEventListener('contextmenu', handleContextMenu);\n\n return () => container.removeEventListener('contextmenu', handleContextMenu);\n }, [locator, disabled]);\n\n return (\n <>\n <div ref={anchorRef} style={LOCATOR_STYLE} aria-hidden />\n {/* Non-modal, like the row-level menus: a right-click elsewhere is both\n \"close this\" and \"open that\", which a modal layer would swallow.\n Skipped entirely without the permission — the anchor is a real\n (if invisible) button, and one that can never open is noise in the\n accessibility tree. */}\n {!disabled && (\n <Menu.Root\n modal={false}\n open={position !== null}\n onOpenChange={(open) => {\n if (!open) {\n setPosition(null);\n }\n }}\n >\n <Menu.Trigger\n tabIndex={-1}\n // No visible content, so the name has to come from `aria-label` —\n // the trigger's `label` prop renders as button text, which would\n // defeat the point of an invisible anchor. Radix points the menu's\n // `aria-labelledby` at this element, so it names the menu too.\n endIcon={null}\n aria-label={formatMessage({\n id: getTranslationKey('list.context-menu.label'),\n defaultMessage: 'Media library actions',\n })}\n style={{ ...CURSOR_ANCHOR_STYLE, top: position?.y ?? 0, left: position?.x ?? 0 }}\n />\n <ActionsMenuContent\n popoverPlacement=\"bottom-start\"\n zIndex={2}\n minWidth=\"22rem\"\n // The anchor is invisible and sits wherever the cursor was, so\n // handing focus back to it on close would be a focus ring nobody can\n // see. Let it fall to the body instead.\n onCloseAutoFocus={(event) => event.preventDefault()}\n >\n <Menu.Item onSelect={onCreateFolder} startIcon={<FolderIcon />}>\n {formatMessage({\n id: getTranslationKey('folder.create.title'),\n defaultMessage: 'New folder',\n })}\n </Menu.Item>\n <Menu.Item onSelect={onImportFiles} startIcon={<Files />}>\n {formatMessage({\n id: getTranslationKey('import-files'),\n defaultMessage: 'File upload',\n })}\n </Menu.Item>\n <Menu.Item onSelect={onImportFromUrl} startIcon={<Link />}>\n {formatMessage({\n id: getTranslationKey('import-from-url'),\n defaultMessage: 'File upload from URL',\n })}\n </Menu.Item>\n </ActionsMenuContent>\n </Menu.Root>\n )}\n </>\n );\n};\n"],"names":["SCROLL_ROOT_SELECTOR","NATIVE_CONTEXT_MENU_SELECTOR","join","CURSOR_ANCHOR_STYLE","position","width","height","minWidth","minHeight","padding","border","opacity","overflow","pointerEvents","LOCATOR_STYLE","MainAreaContextMenu","onCreateFolder","onImportFiles","onImportFromUrl","disabled","formatMessage","useIntl","setPosition","useState","locator","setLocator","anchorRef","useCallback","node","useEffect","container","closest","handleContextMenu","event","target","Element","preventDefault","x","clientX","y","clientY","addEventListener","removeEventListener","_jsxs","_Fragment","_jsx","div","ref","style","aria-hidden","Menu","Root","modal","open","onOpenChange","Trigger","tabIndex","endIcon","aria-label","id","getTranslationKey","defaultMessage","top","left","ActionsMenuContent","popoverPlacement","zIndex","onCloseAutoFocus","Item","onSelect","startIcon","FolderIcon","Files","Link"],"mappings":"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"}
|