@strapi/upload 5.52.0 → 5.52.2
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/TruncatedText.js +76 -0
- package/dist/admin/future/components/TruncatedText.js.map +1 -0
- package/dist/admin/future/components/TruncatedText.mjs +74 -0
- package/dist/admin/future/components/TruncatedText.mjs.map +1 -0
- package/dist/admin/future/components/UploadProgressDialog.js +36 -1
- package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
- package/dist/admin/future/components/UploadProgressDialog.mjs +37 -2
- package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
- package/dist/admin/future/hooks/useApiErrorMessage.js +61 -0
- package/dist/admin/future/hooks/useApiErrorMessage.js.map +1 -0
- package/dist/admin/future/hooks/useApiErrorMessage.mjs +39 -0
- package/dist/admin/future/hooks/useApiErrorMessage.mjs.map +1 -0
- package/dist/admin/future/hooks/useTracking.js +57 -0
- package/dist/admin/future/hooks/useTracking.js.map +1 -0
- package/dist/admin/future/hooks/useTracking.mjs +53 -0
- package/dist/admin/future/hooks/useTracking.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/AssetsPage.js +31 -0
- package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.mjs +31 -0
- package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js +13 -11
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs +13 -11
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +62 -51
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +62 -51
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js +3 -4
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +4 -5
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js +11 -1
- package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs +11 -1
- package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.js +6 -4
- package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +6 -4
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js +10 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs +10 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js +3 -2
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs +3 -2
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js +4 -2
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs +4 -2
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/FilterMenu.js +18 -2
- package/dist/admin/future/pages/Assets/components/FilterMenu.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/FilterMenu.mjs +18 -2
- package/dist/admin/future/pages/Assets/components/FilterMenu.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderActionsMenu.js +9 -7
- package/dist/admin/future/pages/Assets/components/FolderActionsMenu.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderActionsMenu.mjs +9 -7
- package/dist/admin/future/pages/Assets/components/FolderActionsMenu.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderFormDialog.js +12 -0
- package/dist/admin/future/pages/Assets/components/FolderFormDialog.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderFormDialog.mjs +12 -0
- package/dist/admin/future/pages/Assets/components/FolderFormDialog.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js +21 -38
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs +23 -40
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/SortMenu.js +14 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/SortMenu.mjs +14 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js +5 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs +6 -1
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useIsAssetDetailsOpen.js +30 -0
- package/dist/admin/future/pages/Assets/hooks/useIsAssetDetailsOpen.js.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useIsAssetDetailsOpen.mjs +26 -0
- package/dist/admin/future/pages/Assets/hooks/useIsAssetDetailsOpen.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/utils/selection.js +17 -0
- package/dist/admin/future/pages/Assets/utils/selection.js.map +1 -1
- package/dist/admin/future/pages/Assets/utils/selection.mjs +17 -1
- package/dist/admin/future/pages/Assets/utils/selection.mjs.map +1 -1
- package/dist/admin/future/services/api.js +123 -19
- package/dist/admin/future/services/api.js.map +1 -1
- package/dist/admin/future/services/api.mjs +125 -21
- package/dist/admin/future/services/api.mjs.map +1 -1
- package/dist/admin/future/services/assets.js +11 -23
- package/dist/admin/future/services/assets.js.map +1 -1
- package/dist/admin/future/services/assets.mjs +11 -23
- package/dist/admin/future/services/assets.mjs.map +1 -1
- package/dist/admin/future/services/uploadFileViaXHR.js +9 -3
- package/dist/admin/future/services/uploadFileViaXHR.js.map +1 -1
- package/dist/admin/future/services/uploadFileViaXHR.mjs +9 -3
- package/dist/admin/future/services/uploadFileViaXHR.mjs.map +1 -1
- package/dist/admin/future/store/uploadProgress.js +44 -5
- package/dist/admin/future/store/uploadProgress.js.map +1 -1
- package/dist/admin/future/store/uploadProgress.mjs +43 -6
- package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
- package/dist/admin/future/utils/getApiErrorMessage.js +20 -0
- package/dist/admin/future/utils/getApiErrorMessage.js.map +1 -0
- package/dist/admin/future/utils/getApiErrorMessage.mjs +18 -0
- package/dist/admin/future/utils/getApiErrorMessage.mjs.map +1 -0
- package/dist/admin/hooks/useAIMetadataJob.js +1 -1
- package/dist/admin/hooks/useAIMetadataJob.js.map +1 -1
- package/dist/admin/hooks/useAIMetadataJob.mjs +1 -1
- package/dist/admin/hooks/useAIMetadataJob.mjs.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.js +2 -2
- package/dist/admin/pages/SettingsPage/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.mjs +2 -2
- package/dist/admin/pages/SettingsPage/SettingsPage.mjs.map +1 -1
- package/dist/admin/src/future/components/TruncatedText.d.ts +32 -0
- package/dist/admin/src/future/hooks/useApiErrorMessage.d.ts +17 -0
- package/dist/admin/src/future/hooks/useTracking.d.ts +29 -0
- package/dist/admin/src/future/pages/Assets/components/AssetActionsMenu.d.ts +5 -3
- package/dist/admin/src/future/pages/Assets/components/FolderActionsMenu.d.ts +5 -4
- package/dist/admin/src/future/pages/Assets/hooks/useAssetSelection.d.ts +2 -0
- package/dist/admin/src/future/pages/Assets/hooks/useIsAssetDetailsOpen.d.ts +18 -0
- package/dist/admin/src/future/pages/Assets/utils/selection.d.ts +10 -0
- package/dist/admin/src/future/services/api.d.ts +6 -7
- package/dist/admin/src/future/services/uploadFileViaXHR.d.ts +6 -1
- package/dist/admin/src/future/store/uploadProgress.d.ts +16 -1
- package/dist/admin/src/future/utils/getApiErrorMessage.d.ts +1 -0
- package/dist/admin/translations/ca.json.js +1 -1
- package/dist/admin/translations/ca.json.mjs +1 -1
- package/dist/admin/translations/de.json.js +1 -1
- package/dist/admin/translations/de.json.mjs +1 -1
- package/dist/admin/translations/en.json.js +2 -1
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +2 -1
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/admin/translations/es.json.js +1 -1
- package/dist/admin/translations/es.json.mjs +1 -1
- package/dist/admin/translations/fr.json.js +1 -1
- package/dist/admin/translations/fr.json.mjs +1 -1
- package/dist/admin/translations/ja.json.js +1 -1
- package/dist/admin/translations/ja.json.mjs +1 -1
- package/dist/admin/translations/ko.json.js +1 -1
- package/dist/admin/translations/ko.json.mjs +1 -1
- package/dist/admin/translations/nl.json.js +1 -1
- package/dist/admin/translations/nl.json.mjs +1 -1
- package/dist/admin/translations/pl.json.js +1 -1
- package/dist/admin/translations/pl.json.mjs +1 -1
- package/dist/admin/translations/pt-BR.json.js +1 -1
- package/dist/admin/translations/pt-BR.json.mjs +1 -1
- package/dist/admin/translations/ru.json.js +1 -1
- package/dist/admin/translations/ru.json.mjs +1 -1
- package/dist/admin/translations/sk.json.js +1 -1
- package/dist/admin/translations/sk.json.mjs +1 -1
- package/dist/admin/translations/tr.json.js +1 -1
- package/dist/admin/translations/tr.json.mjs +1 -1
- package/dist/admin/translations/uk.json.js +1 -1
- package/dist/admin/translations/uk.json.mjs +1 -1
- package/dist/admin/translations/zh-Hans.json.js +1 -1
- package/dist/admin/translations/zh-Hans.json.mjs +1 -1
- package/dist/admin/translations/zh.json.js +1 -1
- package/dist/admin/translations/zh.json.mjs +1 -1
- package/dist/server/controllers/admin-file.js +27 -9
- package/dist/server/controllers/admin-file.js.map +1 -1
- package/dist/server/controllers/admin-file.mjs +27 -9
- package/dist/server/controllers/admin-file.mjs.map +1 -1
- package/dist/server/controllers/admin-upload.js +24 -8
- package/dist/server/controllers/admin-upload.js.map +1 -1
- package/dist/server/controllers/admin-upload.mjs +24 -8
- package/dist/server/controllers/admin-upload.mjs.map +1 -1
- package/dist/server/routes/admin.js +41 -21
- package/dist/server/routes/admin.js.map +1 -1
- package/dist/server/routes/admin.mjs +41 -21
- package/dist/server/routes/admin.mjs.map +1 -1
- package/dist/server/services/ai-metadata.js +25 -5
- package/dist/server/services/ai-metadata.js.map +1 -1
- package/dist/server/services/ai-metadata.mjs +1 -1
- package/dist/server/services/ai-metadata.mjs.map +1 -1
- package/dist/server/services/upload.js +4 -0
- package/dist/server/services/upload.js.map +1 -1
- package/dist/server/services/upload.mjs +4 -0
- package/dist/server/services/upload.mjs.map +1 -1
- package/dist/server/src/controllers/admin-file.d.ts +29 -8
- package/dist/server/src/controllers/admin-file.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-upload.d.ts +20 -4
- package/dist/server/src/controllers/admin-upload.d.ts.map +1 -1
- package/dist/server/src/controllers/index.d.ts +4 -4
- package/dist/server/src/index.d.ts +4 -4
- package/dist/server/src/routes/admin.d.ts.map +1 -1
- package/dist/server/src/services/ai-metadata.d.ts +2 -2
- package/dist/server/src/services/ai-metadata.d.ts.map +1 -1
- package/dist/server/src/services/upload.d.ts.map +1 -1
- package/dist/shared/contracts/ai-metadata-jobs.d.ts +10 -32
- package/dist/shared/contracts/ai-metadata-jobs.d.ts.map +1 -1
- package/dist/shared/contracts/files.d.ts +10 -10
- package/dist/shared/contracts/files.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/admin/future/utils/getBulkMoveErrorMessage.js +0 -23
- package/dist/admin/future/utils/getBulkMoveErrorMessage.js.map +0 -1
- package/dist/admin/future/utils/getBulkMoveErrorMessage.mjs +0 -21
- package/dist/admin/future/utils/getBulkMoveErrorMessage.mjs.map +0 -1
- package/dist/admin/src/future/utils/getBulkMoveErrorMessage.d.ts +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BulkActionsBar.js","sources":["../../../../../../admin/src/future/pages/Assets/components/BulkActionsBar.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport { Box, Button, Flex, IconButton, Tooltip, Typography } from '@strapi/design-system';\nimport { ArrowRight, Cross, Sparkle, Trash } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { 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 { 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';\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)`\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 ${({ theme }) => theme.breakpoints.medium} {\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 }\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\nexport const BulkActionsBar = ({\n assets = [],\n locations = emptyItemLocations,\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, clear } = useAssetSelection();\n const { currentFolderId } = useFolderNavigation();\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 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 if (count === 0 || !canUpdate) {\n return null;\n }\n\n return (\n <Bar\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 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 {/* Past the early return the user always has `assets.update`, so the\n individual actions no longer re-check it. */}\n <ActionCluster>\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 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","breakpoints","medium","borderRadius","ActionCluster","VerticalDivider","Box","BulkActionsBar","assets","locations","emptyItemLocations","formatMessage","useIntl","toggleNotification","useNotification","isAiMetadataEnabled","useAIMetadataEnabled","canUpdate","useMediaLibraryPermissions","selectedIds","selectedFolderIds","clear","useAssetSelection","currentFolderId","useFolderNavigation","generateAiMetadata","isLoading","isGeneratingMetadata","useGenerateAiMetadataMutation","isDeleteDialogOpen","setIsDeleteDialogOpen","useState","isMoveDialogOpen","setIsMoveDialogOpen","isDeleting","setIsDeleting","count","size","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","fontWeight","textColor","marginRight","Tooltip","label","Button","startIcon","Sparkle","disabled","loading","onClick","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.js","sources":["../../../../../../admin/src/future/pages/Assets/components/BulkActionsBar.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport { Box, Button, Flex, IconButton, Tooltip, Typography } from '@strapi/design-system';\nimport { ArrowRight, Cross, Sparkle, Trash } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { 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 { 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';\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)`\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 ${({ theme }) => theme.breakpoints.medium} {\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 }\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\nexport const BulkActionsBar = ({\n assets = [],\n locations = emptyItemLocations,\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, clear } = useAssetSelection();\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 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 // The details drawer owns the bottom of the screen while it is open. It is\n // fixed to `bottom: 0` too, and its z-index of 200 is pinned below the\n // overlay token on purpose, so the bar (popover, 500) covers the drawer's own\n // footer actions — and, because the drawer is non-modal, keeps taking clicks\n // through it. The selection itself is untouched: the bar returns unchanged\n // when the drawer closes.\n if (count === 0 || !canUpdate || isDetailsDrawerOpen) {\n return null;\n }\n\n return (\n <Bar\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 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 {/* Past the early return the user always has `assets.update`, so the\n individual actions no longer re-check it. */}\n <ActionCluster>\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 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","breakpoints","medium","borderRadius","ActionCluster","VerticalDivider","Box","BulkActionsBar","assets","locations","emptyItemLocations","formatMessage","useIntl","toggleNotification","useNotification","isAiMetadataEnabled","useAIMetadataEnabled","canUpdate","useMediaLibraryPermissions","selectedIds","selectedFolderIds","clear","useAssetSelection","currentFolderId","useFolderNavigation","isDetailsDrawerOpen","useIsAssetDetailsOpen","generateAiMetadata","isLoading","isGeneratingMetadata","useGenerateAiMetadataMutation","isDeleteDialogOpen","setIsDeleteDialogOpen","useState","isMoveDialogOpen","setIsMoveDialogOpen","isDeleting","setIsDeleting","count","size","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","fontWeight","textColor","marginRight","Tooltip","label","Button","startIcon","Sparkle","disabled","loading","onClick","IconButton","variant","ArrowRight","BulkMoveDialog","open","onClose","items","onSuccess","Trash","DeleteItemsDialog","target","folderIds","onPendingChange","aria-hidden","Cross"],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ import { emptyItemLocations } from '../../../utils/itemLocations.mjs';
|
|
|
14
14
|
import { getTranslationKey } from '../../../utils/translations.mjs';
|
|
15
15
|
import { useAssetSelection } from '../hooks/useAssetSelection.mjs';
|
|
16
16
|
import { useFolderNavigation } from '../hooks/useFolderNavigation.mjs';
|
|
17
|
+
import { useIsAssetDetailsOpen } from '../hooks/useIsAssetDetailsOpen.mjs';
|
|
17
18
|
import { BulkMoveDialog } from './BulkMoveDialog.mjs';
|
|
18
19
|
import { DeleteItemsDialog } from './DeleteItemsDialog.mjs';
|
|
19
20
|
import { useGenerateAiMetadataMutation } from '../../../services/api.mjs';
|
|
@@ -68,6 +69,7 @@ const BulkActionsBar = ({ assets = [], locations = emptyItemLocations })=>{
|
|
|
68
69
|
const { canUpdate } = useMediaLibraryPermissions();
|
|
69
70
|
const { selectedIds, selectedFolderIds, clear } = useAssetSelection();
|
|
70
71
|
const { currentFolderId } = useFolderNavigation();
|
|
72
|
+
const isDetailsDrawerOpen = useIsAssetDetailsOpen();
|
|
71
73
|
const [generateAiMetadata, { isLoading: isGeneratingMetadata }] = useGenerateAiMetadataMutation();
|
|
72
74
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
|
73
75
|
const [isMoveDialogOpen, setIsMoveDialogOpen] = useState(false);
|
|
@@ -201,7 +203,14 @@ const BulkActionsBar = ({ assets = [], locations = emptyItemLocations })=>{
|
|
|
201
203
|
// without the permission, the bar has nothing to offer — drop it entirely
|
|
202
204
|
// rather than show a count + "Clear selection" over no actions (mirrors the
|
|
203
205
|
// drawer footer, which hides when no permitted action survives).
|
|
204
|
-
|
|
206
|
+
//
|
|
207
|
+
// The details drawer owns the bottom of the screen while it is open. It is
|
|
208
|
+
// fixed to `bottom: 0` too, and its z-index of 200 is pinned below the
|
|
209
|
+
// overlay token on purpose, so the bar (popover, 500) covers the drawer's own
|
|
210
|
+
// footer actions — and, because the drawer is non-modal, keeps taking clicks
|
|
211
|
+
// through it. The selection itself is untouched: the bar returns unchanged
|
|
212
|
+
// when the drawer closes.
|
|
213
|
+
if (count === 0 || !canUpdate || isDetailsDrawerOpen) {
|
|
205
214
|
return null;
|
|
206
215
|
}
|
|
207
216
|
return /*#__PURE__*/ jsxs(Bar, {
|
|
@@ -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 { Box, Button, Flex, IconButton, Tooltip, Typography } from '@strapi/design-system';\nimport { ArrowRight, Cross, Sparkle, Trash } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { 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 { 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';\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)`\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 ${({ theme }) => theme.breakpoints.medium} {\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 }\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\nexport const BulkActionsBar = ({\n assets = [],\n locations = emptyItemLocations,\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, clear } = useAssetSelection();\n const { currentFolderId } = useFolderNavigation();\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 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 if (count === 0 || !canUpdate) {\n return null;\n }\n\n return (\n <Bar\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 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 {/* Past the early return the user always has `assets.update`, so the\n individual actions no longer re-check it. */}\n <ActionCluster>\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 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","breakpoints","medium","borderRadius","ActionCluster","VerticalDivider","Box","BulkActionsBar","assets","locations","emptyItemLocations","formatMessage","useIntl","toggleNotification","useNotification","isAiMetadataEnabled","useAIMetadataEnabled","canUpdate","useMediaLibraryPermissions","selectedIds","selectedFolderIds","clear","useAssetSelection","currentFolderId","useFolderNavigation","generateAiMetadata","isLoading","isGeneratingMetadata","useGenerateAiMetadataMutation","isDeleteDialogOpen","setIsDeleteDialogOpen","useState","isMoveDialogOpen","setIsMoveDialogOpen","isDeleting","setIsDeleting","count","size","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","fontWeight","textColor","marginRight","Tooltip","label","Button","startIcon","Sparkle","disabled","loading","onClick","IconButton","variant","ArrowRight","BulkMoveDialog","open","onClose","items","onSuccess","Trash","DeleteItemsDialog","target","folderIds","onPendingChange","aria-hidden","Cross"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA;;;;AAIC,IACD,MAAMA,GAAAA,GAAMC,MAAAA,CAAOC,IAAAA,CAAK;;WAEb,EAAE,CAAC,EAAEC,KAAK,EAAE,GAAKA,KAAAA,CAAMC,QAAQ,CAACC,OAAO,CAAC;;;;;OAK5C,EAAE,CAAC,EAAEF,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAAC,CAAA,CAAE,CAAC;AAC7B,WAAA,EAAE,CAAC,EAAEH,KAAK,EAAE,GACnB,CAAA,EAAGA,KAAAA,CAAMG,MAAM,CAAC,CAAA,CAAE,CAAC,CAAC,EAAEH,MAAMG,MAAM,CAAC,CAAA,CAAE,CAAC,CAAC,EAAEH,KAAAA,CAAMG,MAAM,CAAC,CAAA,CAAE,CAAC,CAAC,EAAEH,KAAAA,CAAMG,MAAM,CAAC,CAAA,CAAE,EAAE,CAAC;cACpE,EAAE,CAAC,EAAEH,KAAK,EAAE,GAAKA,KAAAA,CAAMI,MAAM,CAACC,QAAQ,CAAC;;wBAE7B,EAAE,CAAC,EAAEL,KAAK,EAAE,GAAKA,KAAAA,CAAMI,MAAM,CAACE,UAAU,CAAC;;cAEnD,EAAE,CAAC,EAAEN,KAAK,EAAE,GAAKA,KAAAA,CAAMO,OAAO,CAACC,WAAW,CAAC;;EAEvD,EAAE,CAAC,EAAER,KAAK,EAAE,GAAKA,KAAAA,CAAMS,WAAW,CAACC,MAAM,CAAC;;;YAGhC,EAAE,CAAC,EAAEV,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAAC,CAAA,CAAE,CAAC;;sBAEvB,EAAE,CAAC,EAAEH,KAAK,EAAE,GAAKA,KAAAA,CAAMI,MAAM,CAACE,UAAU,CAAC;AAC5C,mBAAA,EAAE,CAAC,EAAEN,KAAK,EAAE,GAAKA,KAAAA,CAAMW,YAAY,CAAC;;AAEvD,CAAC;AAED,MAAMC,aAAAA,GAAgBd,MAAAA,CAAOC,IAAAA,CAAK;;;OAG3B,EAAE,CAAC,EAAEC,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAAC,CAAA,CAAE,CAAC;AACxC,CAAC;AAED,MAAMU,eAAAA,GAAkBf,MAAAA,CAAOgB,GAAAA,CAAI;;;cAGrB,EAAE,CAAC,EAAEd,KAAK,EAAE,GAAKA,KAAAA,CAAMI,MAAM,CAACE,UAAU,CAAC;eACxC,EAAE,CAAC,EAAEN,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAAC,CAAA,CAAE,CAAC;AAChD,CAAC;AAoBM,MAAMY,iBAAiB,CAAC,EAC7BC,SAAS,EAAE,EACXC,SAAAA,GAAYC,kBAAkB,EACV,GAAA;IACpB,MAAM,EAAEC,aAAa,EAAE,GAAGC,OAAAA,EAAAA;IAC1B,MAAM,EAAEC,kBAAkB,EAAE,GAAGC,eAAAA,EAAAA;;;AAG/B,IAAA,MAAMC,mBAAAA,GAAsBC,oBAAAA,EAAAA;;;IAG5B,MAAM,EAAEC,SAAS,EAAE,GAAGC,0BAAAA,EAAAA;AACtB,IAAA,MAAM,EAAEC,WAAW,EAAEC,iBAAiB,EAAEC,KAAK,EAAE,GAAGC,iBAAAA,EAAAA;IAClD,MAAM,EAAEC,eAAe,EAAE,GAAGC,mBAAAA,EAAAA;AAC5B,IAAA,MAAM,CAACC,kBAAAA,EAAoB,EAAEC,WAAWC,oBAAoB,EAAE,CAAC,GAAGC,6BAAAA,EAAAA;AAClE,IAAA,MAAM,CAACC,kBAAAA,EAAoBC,qBAAAA,CAAsB,GAAGC,QAAAA,CAAS,KAAA,CAAA;AAC7D,IAAA,MAAM,CAACC,gBAAAA,EAAkBC,mBAAAA,CAAoB,GAAGF,QAAAA,CAAS,KAAA,CAAA;;;AAGzD,IAAA,MAAM,CAACG,UAAAA,EAAYC,aAAAA,CAAc,GAAGJ,QAAAA,CAAS,KAAA,CAAA;AAE7C,IAAA,MAAMK,KAAAA,GAAQjB,WAAAA,CAAYkB,IAAI,GAAGjB,kBAAkBiB,IAAI;AACvD,IAAA,MAAMC,SAASJ,UAAAA,IAAcP,oBAAAA;;;;;AAM7B,IAAA,MAAMY,YAAYC,OAAAA,CAChB,IAAMC,0BAA0BtB,WAAAA,EAAaC,iBAAAA,EAAmBX,WAAWc,eAAAA,CAAAA,EAC3E;AAACJ,QAAAA,WAAAA;AAAaC,QAAAA,iBAAAA;AAAmBX,QAAAA,SAAAA;AAAWc,QAAAA;AAAgB,KAAA,CAAA;AAG9D;;;;AAIC,MACD,MAAMmB,mBAAAA,GAAsBvB,WAAAA,CAAYkB,IAAI,GAAGM,qBAAAA;AAE/C;;;;;MAMA,MAAMC,gBAAgBJ,OAAAA,CAAQ,IAAA;AAC5B,QAAA,MAAMK,QAAAA,GAAW,IAAIC,GAAAA,CAAItC,MAAAA,CAAOuC,GAAG,CAAC,CAAC,EAAEC,EAAE,EAAEC,IAAI,EAAE,GAAK;AAACD,gBAAAA,EAAAA;AAAIC,gBAAAA;AAAK,aAAA,CAAA,CAAA;QAEhE,OAAO;AAAI9B,YAAAA,GAAAA;SAAY,CAAC+B,MAAM,CAAC,CAACF,EAAAA,GAAOG,0BAA0BN,QAAAA,CAASO,GAAG,CAACJ,EAAAA,CAAAA,CAAAA,CAAAA,CAAMK,MAAM;IAC5F,CAAA,EAAG;AAAC7C,QAAAA,MAAAA;AAAQW,QAAAA;AAAY,KAAA,CAAA;AAExB,IAAA,MAAMmC,mBAAAA,GAAsBnC,WAAAA,CAAYkB,IAAI,GAAG,KAAKO,aAAAA,KAAkB,CAAA;AAEtE;;;;AAIC,MACD,IAAIW,sBAAAA;AAEJ,IAAA,IAAIb,mBAAAA,EAAqB;AACvBa,QAAAA,sBAAAA,GAAyB5C,aAAAA,CACvB;AACEqC,YAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,4CAAA,CAAA;YACtBC,cAAAA,EACE;SACJ,EACA;YAAEC,GAAAA,EAAKf;AAAsB,SAAA,CAAA;AAEjC,IAAA,CAAA,MAAO,IAAIW,mBAAAA,EAAqB;AAC9BC,QAAAA,sBAAAA,GAAyB5C,aAAAA,CAAc;AACrCqC,YAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,+CAAA,CAAA;YACtBC,cAAAA,EACE;AACJ,SAAA,CAAA;AACF,IAAA;AAEA;;;AAGC,MACD,MAAME,oBAAAA,GAAuB,UAAA;;;QAG3B,IAAIhC,oBAAAA,IAAwBe,uBAAuBY,mBAAAA,EAAqB;AACtE,YAAA;AACF,QAAA;QAEA,MAAMM,OAAAA,GAAUC,KAAAA,CAAMC,IAAI,CAAC3C,WAAAA,CAAAA;QAC3B,MAAM4C,GAAAA,GAAM,MAAMtC,kBAAAA,CAAmB;AAAEmC,YAAAA;AAAQ,SAAA,CAAA;AAE/C,QAAA,IAAI,WAAWG,GAAAA,EAAK;;YAElBlD,kBAAAA,CAAmB;gBACjBmD,IAAAA,EAAM,QAAA;AACNC,gBAAAA,OAAAA,EAAStD,aAAAA,CAAc;AACrBqC,oBAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,yCAAA,CAAA;oBACtBC,cAAAA,EAAgB;AAClB,iBAAA;AACF,aAAA,CAAA;AACA,YAAA;AACF,QAAA;AAEA,QAAA,MAAMS,YAAAA,GAAeH,GAAAA,CAAII,IAAI,CAACjB,MAAM,CAAC,CAAC,EAAEkB,MAAM,EAAE,GAAKA,MAAAA,KAAW,WAAWf,MAAM;AACjF,QAAA,MAAMgB,YAAAA,GAAeN,GAAAA,CAAII,IAAI,CAACjB,MAAM,CAAC,CAAC,EAAEkB,MAAM,EAAE,GAAKA,MAAAA,KAAW,WAAWf,MAAM;AACjF,QAAA,MAAMiB,UAAAA,GAAaP,GAAAA,CAAII,IAAI,CAACjB,MAAM,CAAC,CAAC,EAAEkB,MAAM,EAAE,GAAKA,MAAAA,KAAW,SAASf,MAAM;;;QAG7E,MAAMkB,WAAAA,GAAcnD,kBAAkBiB,IAAI;AAE1C,QAAA,IAAIiC,UAAAA,KAAeP,GAAAA,CAAII,IAAI,CAACd,MAAM,EAAE;;;YAGlCxC,kBAAAA,CAAmB;gBACjBmD,IAAAA,EAAM,QAAA;AACNC,gBAAAA,OAAAA,EAAStD,aAAAA,CAAc;AACrBqC,oBAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,yCAAA,CAAA;oBACtBC,cAAAA,EAAgB;AAClB,iBAAA;AACF,aAAA,CAAA;AACA,YAAA;AACF,QAAA;AAEA,QAAA,IAAIY,YAAAA,KAAiB,CAAA,IAAKC,UAAAA,KAAe,CAAA,IAAKC,gBAAgB,CAAA,EAAG;YAC/D1D,kBAAAA,CAAmB;gBACjBmD,IAAAA,EAAM,SAAA;AACNC,gBAAAA,OAAAA,EAAStD,aAAAA,CACP;AACEqC,oBAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,2CAAA,CAAA;oBACtBC,cAAAA,EACE;iBACJ,EACA;oBAAErB,KAAAA,EAAO8B;AAAa,iBAAA;AAE1B,aAAA,CAAA;QACF,CAAA,MAAO;;;YAGLrD,kBAAAA,CAAmB;gBACjBmD,IAAAA,EAAM,SAAA;AACNC,gBAAAA,OAAAA,EAAStD,aAAAA,CACP;AACEqC,oBAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,2CAAA,CAAA;oBACtBC,cAAAA,EACE;iBACJ,EACA;AAAES,oBAAAA,YAAAA;AAAcG,oBAAAA,YAAAA;AAAcC,oBAAAA,UAAAA;AAAYC,oBAAAA;AAAY,iBAAA;AAE1D,aAAA,CAAA;AACF,QAAA;AAEAlD,QAAAA,KAAAA,EAAAA;AACF,IAAA,CAAA;;;;;IAMA,IAAIe,KAAAA,KAAU,CAAA,IAAK,CAACnB,SAAAA,EAAW;QAC7B,OAAO,IAAA;AACT,IAAA;AAEA,IAAA,qBACEuD,IAAA,CAACnF,GAAAA,EAAAA;QACCoF,GAAAA,EAAI,SAAA;QACJC,IAAAA,EAAK,QAAA;AACLC,QAAAA,YAAAA,EAAYhE,aAAAA,CAAc;AACxBqC,YAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,yBAAA,CAAA;YACtBC,cAAAA,EAAgB;AAClB,SAAA,CAAA;;0BAEAmB,GAAA,CAACC,UAAAA,EAAAA;gBAAWC,UAAAA,EAAW,MAAA;gBAAOC,SAAAA,EAAU,YAAA;gBAAaC,WAAAA,EAAa,CAAA;0BAC/DrE,aAAAA,CACC;AACEqC,oBAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,kCAAA,CAAA;oBACtBC,cAAAA,EAAgB;iBAClB,EACA;AAAErB,oBAAAA;AAAM,iBAAA;;0BAMZoC,IAAA,CAACpE,aAAAA,EAAAA;;AACEW,oBAAAA,mBAAAA,kBACC6D,GAAA,CAACK,OAAAA,EAAAA;wBAAQC,KAAAA,EAAO3B,sBAAAA;AAGd,wBAAA,QAAA,gBAAAqB,GAAA,CAACtE,GAAAA,EAAAA;AACC,4BAAA,QAAA,gBAAAsE,GAAA,CAACO,MAAAA,EAAAA;gCACC9C,IAAAA,EAAK,GAAA;AACL+C,gCAAAA,SAAAA,gBAAWR,GAAA,CAACS,OAAAA,EAAAA,EAAAA,CAAAA;AACZC,gCAAAA,QAAAA,EACEhD,MAAAA,IAAUnB,WAAAA,CAAYkB,IAAI,KAAK,KAAKK,mBAAAA,IAAuBY,mBAAAA;gCAE7DiC,OAAAA,EAAS5D,oBAAAA;gCACT6D,OAAAA,EAAS7B,oBAAAA;0CAERhD,aAAAA,CAAc;AACbqC,oCAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,mCAAA,CAAA;oCACtBC,cAAAA,EAAgB;AAClB,iCAAA;;;;kCAMRmB,GAAA,CAACa,UAAAA,EAAAA;wBACCC,OAAAA,EAAQ,UAAA;wBACRJ,QAAAA,EAAUhD,MAAAA;AACV4C,wBAAAA,KAAAA,EAAOvE,aAAAA,CAAc;AACnBqC,4BAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,wBAAA,CAAA;4BACtBC,cAAAA,EAAgB;AAClB,yBAAA,CAAA;AACA+B,wBAAAA,OAAAA,EAAS,IAAMvD,mBAAAA,CAAoB,IAAA,CAAA;AAEnC,wBAAA,QAAA,gBAAA2C,GAAA,CAACe,UAAAA,EAAAA,EAAAA;;kCAEHf,GAAA,CAACgB,cAAAA,EAAAA;wBACCC,IAAAA,EAAM7D,gBAAAA;AACN8D,wBAAAA,OAAAA,EAAS,IAAM7D,mBAAAA,CAAoB,KAAA,CAAA;wBACnC8D,KAAAA,EAAOxD,SAAAA;wBACPyD,SAAAA,EAAW3E;;kCAGbuD,GAAA,CAACa,UAAAA,EAAAA;wBACCC,OAAAA,EAAQ,cAAA;wBACRJ,QAAAA,EAAUhD,MAAAA;AACV4C,wBAAAA,KAAAA,EAAOvE,aAAAA,CAAc;AACnBqC,4BAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,0BAAA,CAAA;4BACtBC,cAAAA,EAAgB;AAClB,yBAAA,CAAA;AACA+B,wBAAAA,OAAAA,EAAS,IAAM1D,qBAAAA,CAAsB,IAAA,CAAA;AAErC,wBAAA,QAAA,gBAAA8C,GAAA,CAACqB,KAAAA,EAAAA,EAAAA;;kCAEHrB,GAAA,CAACsB,iBAAAA,EAAAA;wBACCL,IAAAA,EAAMhE,kBAAAA;AACNiE,wBAAAA,OAAAA,EAAS,IAAMhE,qBAAAA,CAAsB,KAAA,CAAA;wBACrCqE,MAAAA,EAAQ;4BACNvC,OAAAA,EAASC,KAAAA,CAAMC,IAAI,CAAC3C,WAAAA,CAAAA;4BACpBiF,SAAAA,EAAWvC,KAAAA,CAAMC,IAAI,CAAC1C,iBAAAA;AACxB,yBAAA;wBACA4E,SAAAA,EAAW3E,KAAAA;wBACXgF,eAAAA,EAAiBlE;;;;0BAIrByC,GAAA,CAACvE,eAAAA,EAAAA;gBAAgBiG,aAAW,EAAA;;0BAE5B1B,GAAA,CAACa,UAAAA,EAAAA;gBACCC,OAAAA,EAAQ,OAAA;AACRR,gBAAAA,KAAAA,EAAOvE,aAAAA,CAAc;AACnBqC,oBAAAA,EAAAA,EAAIQ,iBAAAA,CAAkB,yBAAA,CAAA;oBACtBC,cAAAA,EAAgB;AAClB,iBAAA,CAAA;gBACA+B,OAAAA,EAASnE,KAAAA;gBACTiE,QAAAA,EAAUhD,MAAAA;AAEV,gBAAA,QAAA,gBAAAsC,GAAA,CAAC2B,KAAAA,EAAAA,EAAAA;;;;AAIT;;;;"}
|
|
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 { Box, Button, Flex, IconButton, Tooltip, Typography } from '@strapi/design-system';\nimport { ArrowRight, Cross, Sparkle, Trash } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { 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 { 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';\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)`\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 ${({ theme }) => theme.breakpoints.medium} {\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 }\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\nexport const BulkActionsBar = ({\n assets = [],\n locations = emptyItemLocations,\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, clear } = useAssetSelection();\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 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 // The details drawer owns the bottom of the screen while it is open. It is\n // fixed to `bottom: 0` too, and its z-index of 200 is pinned below the\n // overlay token on purpose, so the bar (popover, 500) covers the drawer's own\n // footer actions — and, because the drawer is non-modal, keeps taking clicks\n // through it. The selection itself is untouched: the bar returns unchanged\n // when the drawer closes.\n if (count === 0 || !canUpdate || isDetailsDrawerOpen) {\n return null;\n }\n\n return (\n <Bar\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 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 {/* Past the early return the user always has `assets.update`, so the\n individual actions no longer re-check it. */}\n <ActionCluster>\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 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","breakpoints","medium","borderRadius","ActionCluster","VerticalDivider","Box","BulkActionsBar","assets","locations","emptyItemLocations","formatMessage","useIntl","toggleNotification","useNotification","isAiMetadataEnabled","useAIMetadataEnabled","canUpdate","useMediaLibraryPermissions","selectedIds","selectedFolderIds","clear","useAssetSelection","currentFolderId","useFolderNavigation","isDetailsDrawerOpen","useIsAssetDetailsOpen","generateAiMetadata","isLoading","isGeneratingMetadata","useGenerateAiMetadataMutation","isDeleteDialogOpen","setIsDeleteDialogOpen","useState","isMoveDialogOpen","setIsMoveDialogOpen","isDeleting","setIsDeleting","count","size","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","fontWeight","textColor","marginRight","Tooltip","label","Button","startIcon","Sparkle","disabled","loading","onClick","IconButton","variant","ArrowRight","BulkMoveDialog","open","onClose","items","onSuccess","Trash","DeleteItemsDialog","target","folderIds","onPendingChange","aria-hidden","Cross"],"mappings":"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"}
|
|
@@ -6,11 +6,11 @@ var strapiAdmin = require('@strapi/admin/strapi-admin');
|
|
|
6
6
|
var designSystem = require('@strapi/design-system');
|
|
7
7
|
var reactIntl = require('react-intl');
|
|
8
8
|
var styledComponents = require('styled-components');
|
|
9
|
+
var useApiErrorMessage = require('../../../hooks/useApiErrorMessage.js');
|
|
9
10
|
var folders = require('../../../services/folders.js');
|
|
10
11
|
var canDropItemOnFolder = require('../../../utils/canDropItemOnFolder.js');
|
|
11
12
|
var flattenFolderStructure = require('../../../utils/flattenFolderStructure.js');
|
|
12
13
|
var formatMoveSuccessMessage = require('../../../utils/formatMoveSuccessMessage.js');
|
|
13
|
-
var getBulkMoveErrorMessage = require('../../../utils/getBulkMoveErrorMessage.js');
|
|
14
14
|
var itemSource = require('../../../utils/itemSource.js');
|
|
15
15
|
var translations = require('../../../utils/translations.js');
|
|
16
16
|
|
|
@@ -28,6 +28,7 @@ const StyledModalContent = styledComponents.styled(designSystem.Modal.Content)`
|
|
|
28
28
|
* actions menu (that one folder).
|
|
29
29
|
*/ const BulkMoveDialog = ({ open, onClose, items, onSuccess })=>{
|
|
30
30
|
const { formatMessage } = reactIntl.useIntl();
|
|
31
|
+
const getErrorMessage = useApiErrorMessage.useApiErrorMessage();
|
|
31
32
|
const { toggleNotification } = strapiAdmin.useNotification();
|
|
32
33
|
const { data: folderStructure = [], isUninitialized: isStructureUninitialized, isLoading: isLoadingStructure, isError: isStructureError } = folders.useGetFolderStructureQuery(undefined, {
|
|
33
34
|
skip: !open
|
|
@@ -125,7 +126,7 @@ const StyledModalContent = styledComponents.styled(designSystem.Modal.Content)`
|
|
|
125
126
|
// folder into its own descendant or a name collision in the destination.
|
|
126
127
|
toggleNotification({
|
|
127
128
|
type: 'danger',
|
|
128
|
-
message:
|
|
129
|
+
message: getErrorMessage(error, formatMessage({
|
|
129
130
|
id: translations.getTranslationKey('list.bulk-actions.move.error'),
|
|
130
131
|
defaultMessage: 'An error occurred while moving the items.'
|
|
131
132
|
}))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BulkMoveDialog.js","sources":["../../../../../../admin/src/future/pages/Assets/components/BulkMoveDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport {\n Button,\n Field,\n Flex,\n Modal,\n SingleSelect,\n SingleSelectOption,\n Typography,\n} from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\nimport { styled } from 'styled-components';\n\nimport {\n useBulkMoveMutation,\n useGetFolderQuery,\n useGetFolderStructureQuery,\n} from '../../../services/folders';\nimport { canDropItemOnFolder } from '../../../utils/canDropItemOnFolder';\nimport { flattenFolderStructure } from '../../../utils/flattenFolderStructure';\nimport { formatMoveSuccessMessage } from '../../../utils/formatMoveSuccessMessage';\nimport { getBulkMoveErrorMessage } from '../../../utils/getBulkMoveErrorMessage';\nimport { hasUniformSource, uniformSourceFolderId } from '../../../utils/itemSource';\nimport { getTranslationKey } from '../../../utils/translations';\n\nimport type { DragItemData } from '../../../types/dnd';\n\nconst StyledModalContent = styled(Modal.Content)`\n max-width: 51.6rem;\n`;\n\ninterface BulkMoveDialogProps {\n open: boolean;\n onClose: () => void;\n /**\n * Items to move, as drag data so destination validation sees each item's real\n * parent — the current folder is not a reliable stand-in once results come\n * from a global search.\n */\n items: DragItemData[];\n onSuccess?: () => void;\n}\n\n/**\n * \"Move elements to\" modal. Moves the given items (assets and/or folders) into\n * the picked destination folder via `POST /upload/actions/bulk-move`. The\n * mutation invalidates the asset list, folder list and the sidebar\n * folder-structure, so both panes refresh on success. On failure the modal stays\n * open so the user can retry or cancel.\n *\n * Used both by the bulk actions bar (the whole selection) and by a folder's\n * actions menu (that one folder).\n */\nexport const BulkMoveDialog = ({ open, onClose, items, onSuccess }: BulkMoveDialogProps) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n const {\n data: folderStructure = [],\n isUninitialized: isStructureUninitialized,\n isLoading: isLoadingStructure,\n isError: isStructureError,\n } = useGetFolderStructureQuery(undefined, { skip: !open });\n const [bulkMove, { isLoading: isMoving }] = useBulkMoveMutation();\n\n const fileIds = useMemo(\n () => items.filter((item) => item.kind === 'file').map((item) => item.id),\n [items]\n );\n const folderIds = useMemo(\n () => items.filter((item) => item.kind === 'folder').map((item) => item.id),\n [items]\n );\n\n // A selection made under a global search can span folders, so the source is\n // nameable only when every item agrees on it — `null` otherwise, and the toast\n // then omits the source rather than crediting one item's folder to all of them.\n // The bar can also hand over an empty set, which is likewise unnameable.\n const isSourceUniform = hasUniformSource(items);\n const sourceFolderId = uniformSourceFolderId(items);\n const { data: sourceFolder } = useGetFolderQuery(\n { id: sourceFolderId! },\n { skip: sourceFolderId === null }\n );\n\n // '' is the DOM-only sentinel for the Media Library root (null everywhere else).\n const [destination, setDestination] = useState<string>('');\n\n const rootLabel = formatMessage({\n id: getTranslationKey('plugin.name'),\n defaultMessage: 'Media Library',\n });\n\n // Options carry the full ancestry (\"About / Images\" vs \"Tech / Images\") so\n // same-named folders stay distinguishable, listed depth-first under their\n // parent. The moved folders and their descendants are pruned during the same\n // single walk — an item can't be moved into itself or below itself (the\n // server would reject it). The folder the items already live in is filtered\n // out too via `canDropItemOnFolder`, so we never offer a no-op move.\n // Memoized: large libraries shouldn't re-walk the tree on every render.\n const destinationOptions = useMemo(\n () =>\n flattenFolderStructure(folderStructure, new Set(folderIds)).filter((option) =>\n canDropItemOnFolder({ items, targetFolderId: option.id, folderStructure })\n ),\n [folderStructure, folderIds, items]\n );\n\n // Hide the root option when the items already live at root — dropping there\n // would be a no-op. Uses the same predicate as the DnD highlight.\n const canMoveToRoot = useMemo(\n () => canDropItemOnFolder({ items, targetFolderId: null, folderStructure }),\n [items, folderStructure]\n );\n\n // The default has to be an option that is actually rendered, or the select\n // shows blank: root is absent when the items already live there, and the\n // folder options only exist once the structure query has resolved.\n const defaultDestination = canMoveToRoot ? '' : (destinationOptions[0]?.id.toString() ?? '');\n\n // Re-derived rather than only reset on open, because the options arrive after\n // the first render. It is a primitive, so a background refetch that produces\n // the same default won't discard a destination the user already picked.\n useEffect(() => {\n setDestination(defaultDestination);\n }, [open, defaultDestination]);\n\n // Until the structure resolves there are no options, so the default falls\n // back to root ('') — which for a root-level item is the no-op destination\n // that was deliberately filtered out. Nothing may be submitted before this.\n const isStructureReady = !isStructureUninitialized && !isLoadingStructure && !isStructureError;\n\n // Moving a single folder that already sits next to every other folder leaves\n // nowhere to go; say so instead of showing an empty select. Only once the\n // structure has arrived — an in-flight or failed fetch also has no options,\n // and a failure is a different story to tell.\n const hasNoDestination = isStructureReady && destinationOptions.length === 0 && !canMoveToRoot;\n\n const count = items.length;\n\n const handleMove = async () => {\n // The button is disabled in both cases; the dialog can also be submitted\n // from the keyboard, so guard here too.\n if (isMoving || !isStructureReady) {\n return;\n }\n\n const destinationFolderId = destination === '' ? null : Number(destination);\n\n try {\n await bulkMove({ fileIds, folderIds, destinationFolderId }).unwrap();\n } catch (error) {\n // Keep the modal open and the selection intact so the user can retry\n // (e.g. after picking a valid destination) or cancel. Surface the server\n // message when there is one — it carries actionable causes like moving a\n // folder into its own descendant or a name collision in the destination.\n toggleNotification({\n type: 'danger',\n message: getBulkMoveErrorMessage(\n error,\n formatMessage({\n id: getTranslationKey('list.bulk-actions.move.error'),\n defaultMessage: 'An error occurred while moving the items.',\n })\n ),\n });\n return;\n }\n\n // A uniform source with a `null` id is the Media Library root — a real name.\n // Only a set that genuinely spans folders has no source to report.\n const sourceName = !isSourceUniform\n ? null\n : sourceFolderId === null\n ? rootLabel\n : (sourceFolder?.name ?? rootLabel);\n const destinationName =\n destinationFolderId === null\n ? rootLabel\n : (destinationOptions.find((option) => option.id === destinationFolderId)?.label ??\n rootLabel);\n\n toggleNotification({\n type: 'success',\n message: formatMoveSuccessMessage({\n formatMessage,\n count,\n source: sourceName,\n destination: destinationName,\n }),\n });\n onSuccess?.();\n onClose();\n };\n\n // Three states: a failed structure request, a genuine dead end, and the\n // select — which also covers the loading window, so the hint and the error\n // don't flash in ahead of the options.\n const renderBody = () => {\n if (isStructureError) {\n return (\n <Typography textColor=\"danger600\">\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.load-error'),\n defaultMessage: \"Couldn't load the folder list. Please try again.\",\n })}\n </Typography>\n );\n }\n\n if (hasNoDestination) {\n return (\n <Typography textColor=\"neutral600\">\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.no-destination'),\n defaultMessage: 'There is no other folder to move this to.',\n })}\n </Typography>\n );\n }\n\n return (\n <Field.Root name=\"destination\">\n <Field.Label>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.location'),\n defaultMessage: 'Location',\n })}\n </Field.Label>\n <SingleSelect\n value={destination}\n onChange={(value) => setDestination(String(value))}\n disabled={isMoving || !isStructureReady}\n >\n {canMoveToRoot && <SingleSelectOption value=\"\">{rootLabel}</SingleSelectOption>}\n {destinationOptions.map((option) => (\n <SingleSelectOption key={option.id} value={String(option.id)}>\n {option.label}\n </SingleSelectOption>\n ))}\n </SingleSelect>\n </Field.Root>\n );\n };\n\n return (\n <Modal.Root\n open={open}\n onOpenChange={(nextOpen: boolean) => {\n // The modal must stay open while the request runs — the loader on the\n // Move button is the source of truth for the pending state.\n if (!nextOpen && !isMoving) {\n onClose();\n }\n }}\n >\n <StyledModalContent>\n <Modal.Header>\n <Modal.Title>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.title'),\n defaultMessage: 'Move elements to',\n })}\n </Modal.Title>\n </Modal.Header>\n <Modal.Body>{renderBody()}</Modal.Body>\n <Modal.Footer>\n <Flex gap={2} justifyContent=\"space-between\" width=\"100%\">\n <Button variant=\"tertiary\" onClick={onClose} disabled={isMoving} type=\"button\">\n {formatMessage({ id: 'app.components.Button.cancel', defaultMessage: 'Cancel' })}\n </Button>\n <Button\n onClick={handleMove}\n loading={isMoving}\n disabled={!isStructureReady || hasNoDestination}\n >\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.submit'),\n defaultMessage: 'Move',\n })}\n </Button>\n </Flex>\n </Modal.Footer>\n </StyledModalContent>\n </Modal.Root>\n );\n};\n"],"names":["StyledModalContent","styled","Modal","Content","BulkMoveDialog","open","onClose","items","onSuccess","formatMessage","useIntl","toggleNotification","useNotification","data","folderStructure","isUninitialized","isStructureUninitialized","isLoading","isLoadingStructure","isError","isStructureError","useGetFolderStructureQuery","undefined","skip","bulkMove","isMoving","useBulkMoveMutation","fileIds","useMemo","filter","item","kind","map","id","folderIds","isSourceUniform","hasUniformSource","sourceFolderId","uniformSourceFolderId","sourceFolder","useGetFolderQuery","destination","setDestination","useState","rootLabel","getTranslationKey","defaultMessage","destinationOptions","flattenFolderStructure","Set","option","canDropItemOnFolder","targetFolderId","canMoveToRoot","defaultDestination","toString","useEffect","isStructureReady","hasNoDestination","length","count","handleMove","destinationFolderId","Number","unwrap","error","type","message","getBulkMoveErrorMessage","sourceName","name","destinationName","find","label","formatMoveSuccessMessage","source","renderBody","_jsx","Typography","textColor","_jsxs","Field","Root","Label","SingleSelect","value","onChange","String","disabled","SingleSelectOption","onOpenChange","nextOpen","Header","Title","Body","Footer","Flex","gap","justifyContent","width","Button","variant","onClick","loading"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BulkMoveDialog.js","sources":["../../../../../../admin/src/future/pages/Assets/components/BulkMoveDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport {\n Button,\n Field,\n Flex,\n Modal,\n SingleSelect,\n SingleSelectOption,\n Typography,\n} from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\nimport { styled } from 'styled-components';\n\nimport { useApiErrorMessage } from '../../../hooks/useApiErrorMessage';\nimport {\n useBulkMoveMutation,\n useGetFolderQuery,\n useGetFolderStructureQuery,\n} from '../../../services/folders';\nimport { canDropItemOnFolder } from '../../../utils/canDropItemOnFolder';\nimport { flattenFolderStructure } from '../../../utils/flattenFolderStructure';\nimport { formatMoveSuccessMessage } from '../../../utils/formatMoveSuccessMessage';\nimport { hasUniformSource, uniformSourceFolderId } from '../../../utils/itemSource';\nimport { getTranslationKey } from '../../../utils/translations';\n\nimport type { DragItemData } from '../../../types/dnd';\n\nconst StyledModalContent = styled(Modal.Content)`\n max-width: 51.6rem;\n`;\n\ninterface BulkMoveDialogProps {\n open: boolean;\n onClose: () => void;\n /**\n * Items to move, as drag data so destination validation sees each item's real\n * parent — the current folder is not a reliable stand-in once results come\n * from a global search.\n */\n items: DragItemData[];\n onSuccess?: () => void;\n}\n\n/**\n * \"Move elements to\" modal. Moves the given items (assets and/or folders) into\n * the picked destination folder via `POST /upload/actions/bulk-move`. The\n * mutation invalidates the asset list, folder list and the sidebar\n * folder-structure, so both panes refresh on success. On failure the modal stays\n * open so the user can retry or cancel.\n *\n * Used both by the bulk actions bar (the whole selection) and by a folder's\n * actions menu (that one folder).\n */\nexport const BulkMoveDialog = ({ open, onClose, items, onSuccess }: BulkMoveDialogProps) => {\n const { formatMessage } = useIntl();\n const getErrorMessage = useApiErrorMessage();\n const { toggleNotification } = useNotification();\n const {\n data: folderStructure = [],\n isUninitialized: isStructureUninitialized,\n isLoading: isLoadingStructure,\n isError: isStructureError,\n } = useGetFolderStructureQuery(undefined, { skip: !open });\n const [bulkMove, { isLoading: isMoving }] = useBulkMoveMutation();\n\n const fileIds = useMemo(\n () => items.filter((item) => item.kind === 'file').map((item) => item.id),\n [items]\n );\n const folderIds = useMemo(\n () => items.filter((item) => item.kind === 'folder').map((item) => item.id),\n [items]\n );\n\n // A selection made under a global search can span folders, so the source is\n // nameable only when every item agrees on it — `null` otherwise, and the toast\n // then omits the source rather than crediting one item's folder to all of them.\n // The bar can also hand over an empty set, which is likewise unnameable.\n const isSourceUniform = hasUniformSource(items);\n const sourceFolderId = uniformSourceFolderId(items);\n const { data: sourceFolder } = useGetFolderQuery(\n { id: sourceFolderId! },\n { skip: sourceFolderId === null }\n );\n\n // '' is the DOM-only sentinel for the Media Library root (null everywhere else).\n const [destination, setDestination] = useState<string>('');\n\n const rootLabel = formatMessage({\n id: getTranslationKey('plugin.name'),\n defaultMessage: 'Media Library',\n });\n\n // Options carry the full ancestry (\"About / Images\" vs \"Tech / Images\") so\n // same-named folders stay distinguishable, listed depth-first under their\n // parent. The moved folders and their descendants are pruned during the same\n // single walk — an item can't be moved into itself or below itself (the\n // server would reject it). The folder the items already live in is filtered\n // out too via `canDropItemOnFolder`, so we never offer a no-op move.\n // Memoized: large libraries shouldn't re-walk the tree on every render.\n const destinationOptions = useMemo(\n () =>\n flattenFolderStructure(folderStructure, new Set(folderIds)).filter((option) =>\n canDropItemOnFolder({ items, targetFolderId: option.id, folderStructure })\n ),\n [folderStructure, folderIds, items]\n );\n\n // Hide the root option when the items already live at root — dropping there\n // would be a no-op. Uses the same predicate as the DnD highlight.\n const canMoveToRoot = useMemo(\n () => canDropItemOnFolder({ items, targetFolderId: null, folderStructure }),\n [items, folderStructure]\n );\n\n // The default has to be an option that is actually rendered, or the select\n // shows blank: root is absent when the items already live there, and the\n // folder options only exist once the structure query has resolved.\n const defaultDestination = canMoveToRoot ? '' : (destinationOptions[0]?.id.toString() ?? '');\n\n // Re-derived rather than only reset on open, because the options arrive after\n // the first render. It is a primitive, so a background refetch that produces\n // the same default won't discard a destination the user already picked.\n useEffect(() => {\n setDestination(defaultDestination);\n }, [open, defaultDestination]);\n\n // Until the structure resolves there are no options, so the default falls\n // back to root ('') — which for a root-level item is the no-op destination\n // that was deliberately filtered out. Nothing may be submitted before this.\n const isStructureReady = !isStructureUninitialized && !isLoadingStructure && !isStructureError;\n\n // Moving a single folder that already sits next to every other folder leaves\n // nowhere to go; say so instead of showing an empty select. Only once the\n // structure has arrived — an in-flight or failed fetch also has no options,\n // and a failure is a different story to tell.\n const hasNoDestination = isStructureReady && destinationOptions.length === 0 && !canMoveToRoot;\n\n const count = items.length;\n\n const handleMove = async () => {\n // The button is disabled in both cases; the dialog can also be submitted\n // from the keyboard, so guard here too.\n if (isMoving || !isStructureReady) {\n return;\n }\n\n const destinationFolderId = destination === '' ? null : Number(destination);\n\n try {\n await bulkMove({ fileIds, folderIds, destinationFolderId }).unwrap();\n } catch (error) {\n // Keep the modal open and the selection intact so the user can retry\n // (e.g. after picking a valid destination) or cancel. Surface the server\n // message when there is one — it carries actionable causes like moving a\n // folder into its own descendant or a name collision in the destination.\n toggleNotification({\n type: 'danger',\n message: getErrorMessage(\n error,\n formatMessage({\n id: getTranslationKey('list.bulk-actions.move.error'),\n defaultMessage: 'An error occurred while moving the items.',\n })\n ),\n });\n return;\n }\n\n // A uniform source with a `null` id is the Media Library root — a real name.\n // Only a set that genuinely spans folders has no source to report.\n const sourceName = !isSourceUniform\n ? null\n : sourceFolderId === null\n ? rootLabel\n : (sourceFolder?.name ?? rootLabel);\n const destinationName =\n destinationFolderId === null\n ? rootLabel\n : (destinationOptions.find((option) => option.id === destinationFolderId)?.label ??\n rootLabel);\n\n toggleNotification({\n type: 'success',\n message: formatMoveSuccessMessage({\n formatMessage,\n count,\n source: sourceName,\n destination: destinationName,\n }),\n });\n onSuccess?.();\n onClose();\n };\n\n // Three states: a failed structure request, a genuine dead end, and the\n // select — which also covers the loading window, so the hint and the error\n // don't flash in ahead of the options.\n const renderBody = () => {\n if (isStructureError) {\n return (\n <Typography textColor=\"danger600\">\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.load-error'),\n defaultMessage: \"Couldn't load the folder list. Please try again.\",\n })}\n </Typography>\n );\n }\n\n if (hasNoDestination) {\n return (\n <Typography textColor=\"neutral600\">\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.no-destination'),\n defaultMessage: 'There is no other folder to move this to.',\n })}\n </Typography>\n );\n }\n\n return (\n <Field.Root name=\"destination\">\n <Field.Label>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.location'),\n defaultMessage: 'Location',\n })}\n </Field.Label>\n <SingleSelect\n value={destination}\n onChange={(value) => setDestination(String(value))}\n disabled={isMoving || !isStructureReady}\n >\n {canMoveToRoot && <SingleSelectOption value=\"\">{rootLabel}</SingleSelectOption>}\n {destinationOptions.map((option) => (\n <SingleSelectOption key={option.id} value={String(option.id)}>\n {option.label}\n </SingleSelectOption>\n ))}\n </SingleSelect>\n </Field.Root>\n );\n };\n\n return (\n <Modal.Root\n open={open}\n onOpenChange={(nextOpen: boolean) => {\n // The modal must stay open while the request runs — the loader on the\n // Move button is the source of truth for the pending state.\n if (!nextOpen && !isMoving) {\n onClose();\n }\n }}\n >\n <StyledModalContent>\n <Modal.Header>\n <Modal.Title>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.title'),\n defaultMessage: 'Move elements to',\n })}\n </Modal.Title>\n </Modal.Header>\n <Modal.Body>{renderBody()}</Modal.Body>\n <Modal.Footer>\n <Flex gap={2} justifyContent=\"space-between\" width=\"100%\">\n <Button variant=\"tertiary\" onClick={onClose} disabled={isMoving} type=\"button\">\n {formatMessage({ id: 'app.components.Button.cancel', defaultMessage: 'Cancel' })}\n </Button>\n <Button\n onClick={handleMove}\n loading={isMoving}\n disabled={!isStructureReady || hasNoDestination}\n >\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.submit'),\n defaultMessage: 'Move',\n })}\n </Button>\n </Flex>\n </Modal.Footer>\n </StyledModalContent>\n </Modal.Root>\n );\n};\n"],"names":["StyledModalContent","styled","Modal","Content","BulkMoveDialog","open","onClose","items","onSuccess","formatMessage","useIntl","getErrorMessage","useApiErrorMessage","toggleNotification","useNotification","data","folderStructure","isUninitialized","isStructureUninitialized","isLoading","isLoadingStructure","isError","isStructureError","useGetFolderStructureQuery","undefined","skip","bulkMove","isMoving","useBulkMoveMutation","fileIds","useMemo","filter","item","kind","map","id","folderIds","isSourceUniform","hasUniformSource","sourceFolderId","uniformSourceFolderId","sourceFolder","useGetFolderQuery","destination","setDestination","useState","rootLabel","getTranslationKey","defaultMessage","destinationOptions","flattenFolderStructure","Set","option","canDropItemOnFolder","targetFolderId","canMoveToRoot","defaultDestination","toString","useEffect","isStructureReady","hasNoDestination","length","count","handleMove","destinationFolderId","Number","unwrap","error","type","message","sourceName","name","destinationName","find","label","formatMoveSuccessMessage","source","renderBody","_jsx","Typography","textColor","_jsxs","Field","Root","Label","SingleSelect","value","onChange","String","disabled","SingleSelectOption","onOpenChange","nextOpen","Header","Title","Body","Footer","Flex","gap","justifyContent","width","Button","variant","onClick","loading"],"mappings":";;;;;;;;;;;;;;;;AA6BA,MAAMA,kBAAAA,GAAqBC,uBAAAA,CAAOC,kBAAAA,CAAMC,OAAO,CAAC;;AAEhD,CAAC;AAcD;;;;;;;;;AASC,IACM,MAAMC,cAAAA,GAAiB,CAAC,EAAEC,IAAI,EAAEC,OAAO,EAAEC,KAAK,EAAEC,SAAS,EAAuB,GAAA;IACrF,MAAM,EAAEC,aAAa,EAAE,GAAGC,iBAAAA,EAAAA;AAC1B,IAAA,MAAMC,eAAAA,GAAkBC,qCAAAA,EAAAA;IACxB,MAAM,EAAEC,kBAAkB,EAAE,GAAGC,2BAAAA,EAAAA;AAC/B,IAAA,MAAM,EACJC,IAAAA,EAAMC,eAAAA,GAAkB,EAAE,EAC1BC,iBAAiBC,wBAAwB,EACzCC,SAAAA,EAAWC,kBAAkB,EAC7BC,OAAAA,EAASC,gBAAgB,EAC1B,GAAGC,mCAA2BC,SAAAA,EAAW;AAAEC,QAAAA,IAAAA,EAAM,CAACpB;AAAK,KAAA,CAAA;AACxD,IAAA,MAAM,CAACqB,QAAAA,EAAU,EAAEP,WAAWQ,QAAQ,EAAE,CAAC,GAAGC,2BAAAA,EAAAA;AAE5C,IAAA,MAAMC,UAAUC,aAAAA,CACd,IAAMvB,MAAMwB,MAAM,CAAC,CAACC,IAAAA,GAASA,IAAAA,CAAKC,IAAI,KAAK,QAAQC,GAAG,CAAC,CAACF,IAAAA,GAASA,IAAAA,CAAKG,EAAE,CAAA,EACxE;AAAC5B,QAAAA;AAAM,KAAA,CAAA;AAET,IAAA,MAAM6B,YAAYN,aAAAA,CAChB,IAAMvB,MAAMwB,MAAM,CAAC,CAACC,IAAAA,GAASA,IAAAA,CAAKC,IAAI,KAAK,UAAUC,GAAG,CAAC,CAACF,IAAAA,GAASA,IAAAA,CAAKG,EAAE,CAAA,EAC1E;AAAC5B,QAAAA;AAAM,KAAA,CAAA;;;;;AAOT,IAAA,MAAM8B,kBAAkBC,2BAAAA,CAAiB/B,KAAAA,CAAAA;AACzC,IAAA,MAAMgC,iBAAiBC,gCAAAA,CAAsBjC,KAAAA,CAAAA;AAC7C,IAAA,MAAM,EAAEQ,IAAAA,EAAM0B,YAAY,EAAE,GAAGC,yBAAAA,CAC7B;QAAEP,EAAAA,EAAII;KAAgB,EACtB;AAAEd,QAAAA,IAAAA,EAAMc,cAAAA,KAAmB;AAAK,KAAA,CAAA;;AAIlC,IAAA,MAAM,CAACI,WAAAA,EAAaC,cAAAA,CAAe,GAAGC,cAAAA,CAAiB,EAAA,CAAA;AAEvD,IAAA,MAAMC,YAAYrC,aAAAA,CAAc;AAC9B0B,QAAAA,EAAAA,EAAIY,8BAAAA,CAAkB,aAAA,CAAA;QACtBC,cAAAA,EAAgB;AAClB,KAAA,CAAA;;;;;;;;AASA,IAAA,MAAMC,kBAAAA,GAAqBnB,aAAAA,CACzB,IACEoB,6CAAAA,CAAuBlC,eAAAA,EAAiB,IAAImC,GAAAA,CAAIf,SAAAA,CAAAA,CAAAA,CAAYL,MAAM,CAAC,CAACqB,MAAAA,GAClEC,uCAAAA,CAAoB;AAAE9C,gBAAAA,KAAAA;AAAO+C,gBAAAA,cAAAA,EAAgBF,OAAOjB,EAAE;AAAEnB,gBAAAA;aAAgB,CAAA,CAAA,EAE5E;AAACA,QAAAA,eAAAA;AAAiBoB,QAAAA,SAAAA;AAAW7B,QAAAA;AAAM,KAAA,CAAA;;;IAKrC,MAAMgD,aAAAA,GAAgBzB,aAAAA,CACpB,IAAMuB,uCAAAA,CAAoB;AAAE9C,YAAAA,KAAAA;YAAO+C,cAAAA,EAAgB,IAAA;AAAMtC,YAAAA;SAAgB,CAAA,EACzE;AAACT,QAAAA,KAAAA;AAAOS,QAAAA;AAAgB,KAAA,CAAA;;;;IAM1B,MAAMwC,kBAAAA,GAAqBD,gBAAgB,EAAA,GAAMN,kBAAkB,CAAC,CAAA,CAAE,EAAEd,GAAGsB,QAAAA,EAAAA,IAAc,EAAA;;;;IAKzFC,eAAAA,CAAU,IAAA;QACRd,cAAAA,CAAeY,kBAAAA,CAAAA;IACjB,CAAA,EAAG;AAACnD,QAAAA,IAAAA;AAAMmD,QAAAA;AAAmB,KAAA,CAAA;;;;AAK7B,IAAA,MAAMG,gBAAAA,GAAmB,CAACzC,wBAAAA,IAA4B,CAACE,sBAAsB,CAACE,gBAAAA;;;;;AAM9E,IAAA,MAAMsC,mBAAmBD,gBAAAA,IAAoBV,kBAAAA,CAAmBY,MAAM,KAAK,KAAK,CAACN,aAAAA;IAEjF,MAAMO,KAAAA,GAAQvD,MAAMsD,MAAM;AAE1B,IAAA,MAAME,UAAAA,GAAa,UAAA;;;QAGjB,IAAIpC,QAAAA,IAAY,CAACgC,gBAAAA,EAAkB;AACjC,YAAA;AACF,QAAA;AAEA,QAAA,MAAMK,mBAAAA,GAAsBrB,WAAAA,KAAgB,EAAA,GAAK,IAAA,GAAOsB,MAAAA,CAAOtB,WAAAA,CAAAA;QAE/D,IAAI;AACF,YAAA,MAAMjB,QAAAA,CAAS;AAAEG,gBAAAA,OAAAA;AAASO,gBAAAA,SAAAA;AAAW4B,gBAAAA;AAAoB,aAAA,CAAA,CAAGE,MAAM,EAAA;AACpE,QAAA,CAAA,CAAE,OAAOC,KAAAA,EAAO;;;;;YAKdtD,kBAAAA,CAAmB;gBACjBuD,IAAAA,EAAM,QAAA;gBACNC,OAAAA,EAAS1D,eAAAA,CACPwD,OACA1D,aAAAA,CAAc;AACZ0B,oBAAAA,EAAAA,EAAIY,8BAAAA,CAAkB,8BAAA,CAAA;oBACtBC,cAAAA,EAAgB;AAClB,iBAAA,CAAA;AAEJ,aAAA,CAAA;AACA,YAAA;AACF,QAAA;;;QAIA,MAAMsB,UAAAA,GAAa,CAACjC,eAAAA,GAChB,IAAA,GACAE,mBAAmB,IAAA,GACjBO,SAAAA,GACCL,cAAc8B,IAAAA,IAAQzB,SAAAA;AAC7B,QAAA,MAAM0B,eAAAA,GACJR,mBAAAA,KAAwB,IAAA,GACpBlB,SAAAA,GACCG,kBAAAA,CAAmBwB,IAAI,CAAC,CAACrB,MAAAA,GAAWA,MAAAA,CAAOjB,EAAE,KAAK6B,sBAAsBU,KAAAA,IACzE5B,SAAAA;QAENjC,kBAAAA,CAAmB;YACjBuD,IAAAA,EAAM,SAAA;AACNC,YAAAA,OAAAA,EAASM,iDAAAA,CAAyB;AAChClE,gBAAAA,aAAAA;AACAqD,gBAAAA,KAAAA;gBACAc,MAAAA,EAAQN,UAAAA;gBACR3B,WAAAA,EAAa6B;AACf,aAAA;AACF,SAAA,CAAA;AACAhE,QAAAA,SAAAA,IAAAA;AACAF,QAAAA,OAAAA,EAAAA;AACF,IAAA,CAAA;;;;AAKA,IAAA,MAAMuE,UAAAA,GAAa,IAAA;AACjB,QAAA,IAAIvD,gBAAAA,EAAkB;AACpB,YAAA,qBACEwD,cAAA,CAACC,uBAAAA,EAAAA;gBAAWC,SAAAA,EAAU,WAAA;0BACnBvE,aAAAA,CAAc;AACb0B,oBAAAA,EAAAA,EAAIY,8BAAAA,CAAkB,mCAAA,CAAA;oBACtBC,cAAAA,EAAgB;AAClB,iBAAA;;AAGN,QAAA;AAEA,QAAA,IAAIY,gBAAAA,EAAkB;AACpB,YAAA,qBACEkB,cAAA,CAACC,uBAAAA,EAAAA;gBAAWC,SAAAA,EAAU,YAAA;0BACnBvE,aAAAA,CAAc;AACb0B,oBAAAA,EAAAA,EAAIY,8BAAAA,CAAkB,uCAAA,CAAA;oBACtBC,cAAAA,EAAgB;AAClB,iBAAA;;AAGN,QAAA;QAEA,qBACEiC,eAAA,CAACC,mBAAMC,IAAI,EAAA;YAACZ,IAAAA,EAAK,aAAA;;AACf,8BAAAO,cAAA,CAACI,mBAAME,KAAK,EAAA;8BACT3E,aAAAA,CAAc;AACb0B,wBAAAA,EAAAA,EAAIY,8BAAAA,CAAkB,iCAAA,CAAA;wBACtBC,cAAAA,EAAgB;AAClB,qBAAA;;8BAEFiC,eAAA,CAACI,yBAAAA,EAAAA;oBACCC,KAAAA,EAAO3C,WAAAA;oBACP4C,QAAAA,EAAU,CAACD,KAAAA,GAAU1C,cAAAA,CAAe4C,MAAAA,CAAOF,KAAAA,CAAAA,CAAAA;AAC3CG,oBAAAA,QAAAA,EAAU9D,YAAY,CAACgC,gBAAAA;;AAEtBJ,wBAAAA,aAAAA,kBAAiBuB,cAAA,CAACY,+BAAAA,EAAAA;4BAAmBJ,KAAAA,EAAM,EAAA;AAAIxC,4BAAAA,QAAAA,EAAAA;;AAC/CG,wBAAAA,kBAAAA,CAAmBf,GAAG,CAAC,CAACkB,MAAAA,iBACvB0B,cAAA,CAACY,+BAAAA,EAAAA;gCAAmCJ,KAAAA,EAAOE,MAAAA,CAAOpC,OAAOjB,EAAE,CAAA;AACxDiB,gCAAAA,QAAAA,EAAAA,MAAAA,CAAOsB;AADetB,6BAAAA,EAAAA,MAAAA,CAAOjB,EAAE,CAAA;;;;;AAO5C,IAAA,CAAA;IAEA,qBACE2C,cAAA,CAAC5E,mBAAMiF,IAAI,EAAA;QACT9E,IAAAA,EAAMA,IAAAA;AACNsF,QAAAA,YAAAA,EAAc,CAACC,QAAAA,GAAAA;;;YAGb,IAAI,CAACA,QAAAA,IAAY,CAACjE,QAAAA,EAAU;AAC1BrB,gBAAAA,OAAAA,EAAAA;AACF,YAAA;AACF,QAAA,CAAA;AAEA,QAAA,QAAA,gBAAA2E,eAAA,CAACjF,kBAAAA,EAAAA;;AACC,8BAAA8E,cAAA,CAAC5E,mBAAM2F,MAAM,EAAA;4CACXf,cAAA,CAAC5E,mBAAM4F,KAAK,EAAA;kCACTrF,aAAAA,CAAc;AACb0B,4BAAAA,EAAAA,EAAIY,8BAAAA,CAAkB,8BAAA,CAAA;4BACtBC,cAAAA,EAAgB;AAClB,yBAAA;;;AAGJ,8BAAA8B,cAAA,CAAC5E,mBAAM6F,IAAI,EAAA;AAAElB,oBAAAA,QAAAA,EAAAA,UAAAA;;AACb,8BAAAC,cAAA,CAAC5E,mBAAM8F,MAAM,EAAA;AACX,oBAAA,QAAA,gBAAAf,eAAA,CAACgB,iBAAAA,EAAAA;wBAAKC,GAAAA,EAAK,CAAA;wBAAGC,cAAAA,EAAe,eAAA;wBAAgBC,KAAAA,EAAM,MAAA;;0CACjDtB,cAAA,CAACuB,mBAAAA,EAAAA;gCAAOC,OAAAA,EAAQ,UAAA;gCAAWC,OAAAA,EAASjG,OAAAA;gCAASmF,QAAAA,EAAU9D,QAAAA;gCAAUyC,IAAAA,EAAK,QAAA;0CACnE3D,aAAAA,CAAc;oCAAE0B,EAAAA,EAAI,8BAAA;oCAAgCa,cAAAA,EAAgB;AAAS,iCAAA;;0CAEhF8B,cAAA,CAACuB,mBAAAA,EAAAA;gCACCE,OAAAA,EAASxC,UAAAA;gCACTyC,OAAAA,EAAS7E,QAAAA;AACT8D,gCAAAA,QAAAA,EAAU,CAAC9B,gBAAAA,IAAoBC,gBAAAA;0CAE9BnD,aAAAA,CAAc;AACb0B,oCAAAA,EAAAA,EAAIY,8BAAAA,CAAkB,+BAAA,CAAA;oCACtBC,cAAAA,EAAgB;AAClB,iCAAA;;;;;;;;AAOd;;;;"}
|
|
@@ -4,11 +4,11 @@ import { useNotification } from '@strapi/admin/strapi-admin';
|
|
|
4
4
|
import { Modal, Flex, Button, Typography, Field, SingleSelect, SingleSelectOption } from '@strapi/design-system';
|
|
5
5
|
import { useIntl } from 'react-intl';
|
|
6
6
|
import { styled } from 'styled-components';
|
|
7
|
+
import { useApiErrorMessage } from '../../../hooks/useApiErrorMessage.mjs';
|
|
7
8
|
import { useGetFolderStructureQuery, useBulkMoveMutation, useGetFolderQuery } from '../../../services/folders.mjs';
|
|
8
9
|
import { canDropItemOnFolder } from '../../../utils/canDropItemOnFolder.mjs';
|
|
9
10
|
import { flattenFolderStructure } from '../../../utils/flattenFolderStructure.mjs';
|
|
10
11
|
import { formatMoveSuccessMessage } from '../../../utils/formatMoveSuccessMessage.mjs';
|
|
11
|
-
import { getBulkMoveErrorMessage } from '../../../utils/getBulkMoveErrorMessage.mjs';
|
|
12
12
|
import { hasUniformSource, uniformSourceFolderId } from '../../../utils/itemSource.mjs';
|
|
13
13
|
import { getTranslationKey } from '../../../utils/translations.mjs';
|
|
14
14
|
|
|
@@ -26,6 +26,7 @@ const StyledModalContent = styled(Modal.Content)`
|
|
|
26
26
|
* actions menu (that one folder).
|
|
27
27
|
*/ const BulkMoveDialog = ({ open, onClose, items, onSuccess })=>{
|
|
28
28
|
const { formatMessage } = useIntl();
|
|
29
|
+
const getErrorMessage = useApiErrorMessage();
|
|
29
30
|
const { toggleNotification } = useNotification();
|
|
30
31
|
const { data: folderStructure = [], isUninitialized: isStructureUninitialized, isLoading: isLoadingStructure, isError: isStructureError } = useGetFolderStructureQuery(undefined, {
|
|
31
32
|
skip: !open
|
|
@@ -123,7 +124,7 @@ const StyledModalContent = styled(Modal.Content)`
|
|
|
123
124
|
// folder into its own descendant or a name collision in the destination.
|
|
124
125
|
toggleNotification({
|
|
125
126
|
type: 'danger',
|
|
126
|
-
message:
|
|
127
|
+
message: getErrorMessage(error, formatMessage({
|
|
127
128
|
id: getTranslationKey('list.bulk-actions.move.error'),
|
|
128
129
|
defaultMessage: 'An error occurred while moving the items.'
|
|
129
130
|
}))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BulkMoveDialog.mjs","sources":["../../../../../../admin/src/future/pages/Assets/components/BulkMoveDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport {\n Button,\n Field,\n Flex,\n Modal,\n SingleSelect,\n SingleSelectOption,\n Typography,\n} from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\nimport { styled } from 'styled-components';\n\nimport {\n useBulkMoveMutation,\n useGetFolderQuery,\n useGetFolderStructureQuery,\n} from '../../../services/folders';\nimport { canDropItemOnFolder } from '../../../utils/canDropItemOnFolder';\nimport { flattenFolderStructure } from '../../../utils/flattenFolderStructure';\nimport { formatMoveSuccessMessage } from '../../../utils/formatMoveSuccessMessage';\nimport { getBulkMoveErrorMessage } from '../../../utils/getBulkMoveErrorMessage';\nimport { hasUniformSource, uniformSourceFolderId } from '../../../utils/itemSource';\nimport { getTranslationKey } from '../../../utils/translations';\n\nimport type { DragItemData } from '../../../types/dnd';\n\nconst StyledModalContent = styled(Modal.Content)`\n max-width: 51.6rem;\n`;\n\ninterface BulkMoveDialogProps {\n open: boolean;\n onClose: () => void;\n /**\n * Items to move, as drag data so destination validation sees each item's real\n * parent — the current folder is not a reliable stand-in once results come\n * from a global search.\n */\n items: DragItemData[];\n onSuccess?: () => void;\n}\n\n/**\n * \"Move elements to\" modal. Moves the given items (assets and/or folders) into\n * the picked destination folder via `POST /upload/actions/bulk-move`. The\n * mutation invalidates the asset list, folder list and the sidebar\n * folder-structure, so both panes refresh on success. On failure the modal stays\n * open so the user can retry or cancel.\n *\n * Used both by the bulk actions bar (the whole selection) and by a folder's\n * actions menu (that one folder).\n */\nexport const BulkMoveDialog = ({ open, onClose, items, onSuccess }: BulkMoveDialogProps) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n const {\n data: folderStructure = [],\n isUninitialized: isStructureUninitialized,\n isLoading: isLoadingStructure,\n isError: isStructureError,\n } = useGetFolderStructureQuery(undefined, { skip: !open });\n const [bulkMove, { isLoading: isMoving }] = useBulkMoveMutation();\n\n const fileIds = useMemo(\n () => items.filter((item) => item.kind === 'file').map((item) => item.id),\n [items]\n );\n const folderIds = useMemo(\n () => items.filter((item) => item.kind === 'folder').map((item) => item.id),\n [items]\n );\n\n // A selection made under a global search can span folders, so the source is\n // nameable only when every item agrees on it — `null` otherwise, and the toast\n // then omits the source rather than crediting one item's folder to all of them.\n // The bar can also hand over an empty set, which is likewise unnameable.\n const isSourceUniform = hasUniformSource(items);\n const sourceFolderId = uniformSourceFolderId(items);\n const { data: sourceFolder } = useGetFolderQuery(\n { id: sourceFolderId! },\n { skip: sourceFolderId === null }\n );\n\n // '' is the DOM-only sentinel for the Media Library root (null everywhere else).\n const [destination, setDestination] = useState<string>('');\n\n const rootLabel = formatMessage({\n id: getTranslationKey('plugin.name'),\n defaultMessage: 'Media Library',\n });\n\n // Options carry the full ancestry (\"About / Images\" vs \"Tech / Images\") so\n // same-named folders stay distinguishable, listed depth-first under their\n // parent. The moved folders and their descendants are pruned during the same\n // single walk — an item can't be moved into itself or below itself (the\n // server would reject it). The folder the items already live in is filtered\n // out too via `canDropItemOnFolder`, so we never offer a no-op move.\n // Memoized: large libraries shouldn't re-walk the tree on every render.\n const destinationOptions = useMemo(\n () =>\n flattenFolderStructure(folderStructure, new Set(folderIds)).filter((option) =>\n canDropItemOnFolder({ items, targetFolderId: option.id, folderStructure })\n ),\n [folderStructure, folderIds, items]\n );\n\n // Hide the root option when the items already live at root — dropping there\n // would be a no-op. Uses the same predicate as the DnD highlight.\n const canMoveToRoot = useMemo(\n () => canDropItemOnFolder({ items, targetFolderId: null, folderStructure }),\n [items, folderStructure]\n );\n\n // The default has to be an option that is actually rendered, or the select\n // shows blank: root is absent when the items already live there, and the\n // folder options only exist once the structure query has resolved.\n const defaultDestination = canMoveToRoot ? '' : (destinationOptions[0]?.id.toString() ?? '');\n\n // Re-derived rather than only reset on open, because the options arrive after\n // the first render. It is a primitive, so a background refetch that produces\n // the same default won't discard a destination the user already picked.\n useEffect(() => {\n setDestination(defaultDestination);\n }, [open, defaultDestination]);\n\n // Until the structure resolves there are no options, so the default falls\n // back to root ('') — which for a root-level item is the no-op destination\n // that was deliberately filtered out. Nothing may be submitted before this.\n const isStructureReady = !isStructureUninitialized && !isLoadingStructure && !isStructureError;\n\n // Moving a single folder that already sits next to every other folder leaves\n // nowhere to go; say so instead of showing an empty select. Only once the\n // structure has arrived — an in-flight or failed fetch also has no options,\n // and a failure is a different story to tell.\n const hasNoDestination = isStructureReady && destinationOptions.length === 0 && !canMoveToRoot;\n\n const count = items.length;\n\n const handleMove = async () => {\n // The button is disabled in both cases; the dialog can also be submitted\n // from the keyboard, so guard here too.\n if (isMoving || !isStructureReady) {\n return;\n }\n\n const destinationFolderId = destination === '' ? null : Number(destination);\n\n try {\n await bulkMove({ fileIds, folderIds, destinationFolderId }).unwrap();\n } catch (error) {\n // Keep the modal open and the selection intact so the user can retry\n // (e.g. after picking a valid destination) or cancel. Surface the server\n // message when there is one — it carries actionable causes like moving a\n // folder into its own descendant or a name collision in the destination.\n toggleNotification({\n type: 'danger',\n message: getBulkMoveErrorMessage(\n error,\n formatMessage({\n id: getTranslationKey('list.bulk-actions.move.error'),\n defaultMessage: 'An error occurred while moving the items.',\n })\n ),\n });\n return;\n }\n\n // A uniform source with a `null` id is the Media Library root — a real name.\n // Only a set that genuinely spans folders has no source to report.\n const sourceName = !isSourceUniform\n ? null\n : sourceFolderId === null\n ? rootLabel\n : (sourceFolder?.name ?? rootLabel);\n const destinationName =\n destinationFolderId === null\n ? rootLabel\n : (destinationOptions.find((option) => option.id === destinationFolderId)?.label ??\n rootLabel);\n\n toggleNotification({\n type: 'success',\n message: formatMoveSuccessMessage({\n formatMessage,\n count,\n source: sourceName,\n destination: destinationName,\n }),\n });\n onSuccess?.();\n onClose();\n };\n\n // Three states: a failed structure request, a genuine dead end, and the\n // select — which also covers the loading window, so the hint and the error\n // don't flash in ahead of the options.\n const renderBody = () => {\n if (isStructureError) {\n return (\n <Typography textColor=\"danger600\">\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.load-error'),\n defaultMessage: \"Couldn't load the folder list. Please try again.\",\n })}\n </Typography>\n );\n }\n\n if (hasNoDestination) {\n return (\n <Typography textColor=\"neutral600\">\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.no-destination'),\n defaultMessage: 'There is no other folder to move this to.',\n })}\n </Typography>\n );\n }\n\n return (\n <Field.Root name=\"destination\">\n <Field.Label>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.location'),\n defaultMessage: 'Location',\n })}\n </Field.Label>\n <SingleSelect\n value={destination}\n onChange={(value) => setDestination(String(value))}\n disabled={isMoving || !isStructureReady}\n >\n {canMoveToRoot && <SingleSelectOption value=\"\">{rootLabel}</SingleSelectOption>}\n {destinationOptions.map((option) => (\n <SingleSelectOption key={option.id} value={String(option.id)}>\n {option.label}\n </SingleSelectOption>\n ))}\n </SingleSelect>\n </Field.Root>\n );\n };\n\n return (\n <Modal.Root\n open={open}\n onOpenChange={(nextOpen: boolean) => {\n // The modal must stay open while the request runs — the loader on the\n // Move button is the source of truth for the pending state.\n if (!nextOpen && !isMoving) {\n onClose();\n }\n }}\n >\n <StyledModalContent>\n <Modal.Header>\n <Modal.Title>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.title'),\n defaultMessage: 'Move elements to',\n })}\n </Modal.Title>\n </Modal.Header>\n <Modal.Body>{renderBody()}</Modal.Body>\n <Modal.Footer>\n <Flex gap={2} justifyContent=\"space-between\" width=\"100%\">\n <Button variant=\"tertiary\" onClick={onClose} disabled={isMoving} type=\"button\">\n {formatMessage({ id: 'app.components.Button.cancel', defaultMessage: 'Cancel' })}\n </Button>\n <Button\n onClick={handleMove}\n loading={isMoving}\n disabled={!isStructureReady || hasNoDestination}\n >\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.submit'),\n defaultMessage: 'Move',\n })}\n </Button>\n </Flex>\n </Modal.Footer>\n </StyledModalContent>\n </Modal.Root>\n );\n};\n"],"names":["StyledModalContent","styled","Modal","Content","BulkMoveDialog","open","onClose","items","onSuccess","formatMessage","useIntl","toggleNotification","useNotification","data","folderStructure","isUninitialized","isStructureUninitialized","isLoading","isLoadingStructure","isError","isStructureError","useGetFolderStructureQuery","undefined","skip","bulkMove","isMoving","useBulkMoveMutation","fileIds","useMemo","filter","item","kind","map","id","folderIds","isSourceUniform","hasUniformSource","sourceFolderId","uniformSourceFolderId","sourceFolder","useGetFolderQuery","destination","setDestination","useState","rootLabel","getTranslationKey","defaultMessage","destinationOptions","flattenFolderStructure","Set","option","canDropItemOnFolder","targetFolderId","canMoveToRoot","defaultDestination","toString","useEffect","isStructureReady","hasNoDestination","length","count","handleMove","destinationFolderId","Number","unwrap","error","type","message","getBulkMoveErrorMessage","sourceName","name","destinationName","find","label","formatMoveSuccessMessage","source","renderBody","_jsx","Typography","textColor","_jsxs","Field","Root","Label","SingleSelect","value","onChange","String","disabled","SingleSelectOption","onOpenChange","nextOpen","Header","Title","Body","Footer","Flex","gap","justifyContent","width","Button","variant","onClick","loading"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BulkMoveDialog.mjs","sources":["../../../../../../admin/src/future/pages/Assets/components/BulkMoveDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport {\n Button,\n Field,\n Flex,\n Modal,\n SingleSelect,\n SingleSelectOption,\n Typography,\n} from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\nimport { styled } from 'styled-components';\n\nimport { useApiErrorMessage } from '../../../hooks/useApiErrorMessage';\nimport {\n useBulkMoveMutation,\n useGetFolderQuery,\n useGetFolderStructureQuery,\n} from '../../../services/folders';\nimport { canDropItemOnFolder } from '../../../utils/canDropItemOnFolder';\nimport { flattenFolderStructure } from '../../../utils/flattenFolderStructure';\nimport { formatMoveSuccessMessage } from '../../../utils/formatMoveSuccessMessage';\nimport { hasUniformSource, uniformSourceFolderId } from '../../../utils/itemSource';\nimport { getTranslationKey } from '../../../utils/translations';\n\nimport type { DragItemData } from '../../../types/dnd';\n\nconst StyledModalContent = styled(Modal.Content)`\n max-width: 51.6rem;\n`;\n\ninterface BulkMoveDialogProps {\n open: boolean;\n onClose: () => void;\n /**\n * Items to move, as drag data so destination validation sees each item's real\n * parent — the current folder is not a reliable stand-in once results come\n * from a global search.\n */\n items: DragItemData[];\n onSuccess?: () => void;\n}\n\n/**\n * \"Move elements to\" modal. Moves the given items (assets and/or folders) into\n * the picked destination folder via `POST /upload/actions/bulk-move`. The\n * mutation invalidates the asset list, folder list and the sidebar\n * folder-structure, so both panes refresh on success. On failure the modal stays\n * open so the user can retry or cancel.\n *\n * Used both by the bulk actions bar (the whole selection) and by a folder's\n * actions menu (that one folder).\n */\nexport const BulkMoveDialog = ({ open, onClose, items, onSuccess }: BulkMoveDialogProps) => {\n const { formatMessage } = useIntl();\n const getErrorMessage = useApiErrorMessage();\n const { toggleNotification } = useNotification();\n const {\n data: folderStructure = [],\n isUninitialized: isStructureUninitialized,\n isLoading: isLoadingStructure,\n isError: isStructureError,\n } = useGetFolderStructureQuery(undefined, { skip: !open });\n const [bulkMove, { isLoading: isMoving }] = useBulkMoveMutation();\n\n const fileIds = useMemo(\n () => items.filter((item) => item.kind === 'file').map((item) => item.id),\n [items]\n );\n const folderIds = useMemo(\n () => items.filter((item) => item.kind === 'folder').map((item) => item.id),\n [items]\n );\n\n // A selection made under a global search can span folders, so the source is\n // nameable only when every item agrees on it — `null` otherwise, and the toast\n // then omits the source rather than crediting one item's folder to all of them.\n // The bar can also hand over an empty set, which is likewise unnameable.\n const isSourceUniform = hasUniformSource(items);\n const sourceFolderId = uniformSourceFolderId(items);\n const { data: sourceFolder } = useGetFolderQuery(\n { id: sourceFolderId! },\n { skip: sourceFolderId === null }\n );\n\n // '' is the DOM-only sentinel for the Media Library root (null everywhere else).\n const [destination, setDestination] = useState<string>('');\n\n const rootLabel = formatMessage({\n id: getTranslationKey('plugin.name'),\n defaultMessage: 'Media Library',\n });\n\n // Options carry the full ancestry (\"About / Images\" vs \"Tech / Images\") so\n // same-named folders stay distinguishable, listed depth-first under their\n // parent. The moved folders and their descendants are pruned during the same\n // single walk — an item can't be moved into itself or below itself (the\n // server would reject it). The folder the items already live in is filtered\n // out too via `canDropItemOnFolder`, so we never offer a no-op move.\n // Memoized: large libraries shouldn't re-walk the tree on every render.\n const destinationOptions = useMemo(\n () =>\n flattenFolderStructure(folderStructure, new Set(folderIds)).filter((option) =>\n canDropItemOnFolder({ items, targetFolderId: option.id, folderStructure })\n ),\n [folderStructure, folderIds, items]\n );\n\n // Hide the root option when the items already live at root — dropping there\n // would be a no-op. Uses the same predicate as the DnD highlight.\n const canMoveToRoot = useMemo(\n () => canDropItemOnFolder({ items, targetFolderId: null, folderStructure }),\n [items, folderStructure]\n );\n\n // The default has to be an option that is actually rendered, or the select\n // shows blank: root is absent when the items already live there, and the\n // folder options only exist once the structure query has resolved.\n const defaultDestination = canMoveToRoot ? '' : (destinationOptions[0]?.id.toString() ?? '');\n\n // Re-derived rather than only reset on open, because the options arrive after\n // the first render. It is a primitive, so a background refetch that produces\n // the same default won't discard a destination the user already picked.\n useEffect(() => {\n setDestination(defaultDestination);\n }, [open, defaultDestination]);\n\n // Until the structure resolves there are no options, so the default falls\n // back to root ('') — which for a root-level item is the no-op destination\n // that was deliberately filtered out. Nothing may be submitted before this.\n const isStructureReady = !isStructureUninitialized && !isLoadingStructure && !isStructureError;\n\n // Moving a single folder that already sits next to every other folder leaves\n // nowhere to go; say so instead of showing an empty select. Only once the\n // structure has arrived — an in-flight or failed fetch also has no options,\n // and a failure is a different story to tell.\n const hasNoDestination = isStructureReady && destinationOptions.length === 0 && !canMoveToRoot;\n\n const count = items.length;\n\n const handleMove = async () => {\n // The button is disabled in both cases; the dialog can also be submitted\n // from the keyboard, so guard here too.\n if (isMoving || !isStructureReady) {\n return;\n }\n\n const destinationFolderId = destination === '' ? null : Number(destination);\n\n try {\n await bulkMove({ fileIds, folderIds, destinationFolderId }).unwrap();\n } catch (error) {\n // Keep the modal open and the selection intact so the user can retry\n // (e.g. after picking a valid destination) or cancel. Surface the server\n // message when there is one — it carries actionable causes like moving a\n // folder into its own descendant or a name collision in the destination.\n toggleNotification({\n type: 'danger',\n message: getErrorMessage(\n error,\n formatMessage({\n id: getTranslationKey('list.bulk-actions.move.error'),\n defaultMessage: 'An error occurred while moving the items.',\n })\n ),\n });\n return;\n }\n\n // A uniform source with a `null` id is the Media Library root — a real name.\n // Only a set that genuinely spans folders has no source to report.\n const sourceName = !isSourceUniform\n ? null\n : sourceFolderId === null\n ? rootLabel\n : (sourceFolder?.name ?? rootLabel);\n const destinationName =\n destinationFolderId === null\n ? rootLabel\n : (destinationOptions.find((option) => option.id === destinationFolderId)?.label ??\n rootLabel);\n\n toggleNotification({\n type: 'success',\n message: formatMoveSuccessMessage({\n formatMessage,\n count,\n source: sourceName,\n destination: destinationName,\n }),\n });\n onSuccess?.();\n onClose();\n };\n\n // Three states: a failed structure request, a genuine dead end, and the\n // select — which also covers the loading window, so the hint and the error\n // don't flash in ahead of the options.\n const renderBody = () => {\n if (isStructureError) {\n return (\n <Typography textColor=\"danger600\">\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.load-error'),\n defaultMessage: \"Couldn't load the folder list. Please try again.\",\n })}\n </Typography>\n );\n }\n\n if (hasNoDestination) {\n return (\n <Typography textColor=\"neutral600\">\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.no-destination'),\n defaultMessage: 'There is no other folder to move this to.',\n })}\n </Typography>\n );\n }\n\n return (\n <Field.Root name=\"destination\">\n <Field.Label>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.location'),\n defaultMessage: 'Location',\n })}\n </Field.Label>\n <SingleSelect\n value={destination}\n onChange={(value) => setDestination(String(value))}\n disabled={isMoving || !isStructureReady}\n >\n {canMoveToRoot && <SingleSelectOption value=\"\">{rootLabel}</SingleSelectOption>}\n {destinationOptions.map((option) => (\n <SingleSelectOption key={option.id} value={String(option.id)}>\n {option.label}\n </SingleSelectOption>\n ))}\n </SingleSelect>\n </Field.Root>\n );\n };\n\n return (\n <Modal.Root\n open={open}\n onOpenChange={(nextOpen: boolean) => {\n // The modal must stay open while the request runs — the loader on the\n // Move button is the source of truth for the pending state.\n if (!nextOpen && !isMoving) {\n onClose();\n }\n }}\n >\n <StyledModalContent>\n <Modal.Header>\n <Modal.Title>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.title'),\n defaultMessage: 'Move elements to',\n })}\n </Modal.Title>\n </Modal.Header>\n <Modal.Body>{renderBody()}</Modal.Body>\n <Modal.Footer>\n <Flex gap={2} justifyContent=\"space-between\" width=\"100%\">\n <Button variant=\"tertiary\" onClick={onClose} disabled={isMoving} type=\"button\">\n {formatMessage({ id: 'app.components.Button.cancel', defaultMessage: 'Cancel' })}\n </Button>\n <Button\n onClick={handleMove}\n loading={isMoving}\n disabled={!isStructureReady || hasNoDestination}\n >\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move.submit'),\n defaultMessage: 'Move',\n })}\n </Button>\n </Flex>\n </Modal.Footer>\n </StyledModalContent>\n </Modal.Root>\n );\n};\n"],"names":["StyledModalContent","styled","Modal","Content","BulkMoveDialog","open","onClose","items","onSuccess","formatMessage","useIntl","getErrorMessage","useApiErrorMessage","toggleNotification","useNotification","data","folderStructure","isUninitialized","isStructureUninitialized","isLoading","isLoadingStructure","isError","isStructureError","useGetFolderStructureQuery","undefined","skip","bulkMove","isMoving","useBulkMoveMutation","fileIds","useMemo","filter","item","kind","map","id","folderIds","isSourceUniform","hasUniformSource","sourceFolderId","uniformSourceFolderId","sourceFolder","useGetFolderQuery","destination","setDestination","useState","rootLabel","getTranslationKey","defaultMessage","destinationOptions","flattenFolderStructure","Set","option","canDropItemOnFolder","targetFolderId","canMoveToRoot","defaultDestination","toString","useEffect","isStructureReady","hasNoDestination","length","count","handleMove","destinationFolderId","Number","unwrap","error","type","message","sourceName","name","destinationName","find","label","formatMoveSuccessMessage","source","renderBody","_jsx","Typography","textColor","_jsxs","Field","Root","Label","SingleSelect","value","onChange","String","disabled","SingleSelectOption","onOpenChange","nextOpen","Header","Title","Body","Footer","Flex","gap","justifyContent","width","Button","variant","onClick","loading"],"mappings":"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"}
|
|
@@ -6,12 +6,12 @@ var core = require('@dnd-kit/core');
|
|
|
6
6
|
var strapiAdmin = require('@strapi/admin/strapi-admin');
|
|
7
7
|
var designSystem = require('@strapi/design-system');
|
|
8
8
|
var reactIntl = require('react-intl');
|
|
9
|
+
var useApiErrorMessage = require('../../../../hooks/useApiErrorMessage.js');
|
|
9
10
|
var useMediaLibraryPermissions = require('../../../../hooks/useMediaLibraryPermissions.js');
|
|
10
11
|
var folders = require('../../../../services/folders.js');
|
|
11
12
|
var buildBulkMovePayload = require('../../../../utils/buildBulkMovePayload.js');
|
|
12
13
|
var canDropItemOnFolder = require('../../../../utils/canDropItemOnFolder.js');
|
|
13
14
|
var formatMoveSuccessMessage = require('../../../../utils/formatMoveSuccessMessage.js');
|
|
14
|
-
var getBulkMoveErrorMessage = require('../../../../utils/getBulkMoveErrorMessage.js');
|
|
15
15
|
var getFolderLabel = require('../../../../utils/getFolderLabel.js');
|
|
16
16
|
var itemLocations = require('../../../../utils/itemLocations.js');
|
|
17
17
|
var translations = require('../../../../utils/translations.js');
|
|
@@ -50,6 +50,7 @@ const resolveDestination = (overId)=>{
|
|
|
50
50
|
const DRAG_DISABLED_DISTANCE = Number.MAX_SAFE_INTEGER;
|
|
51
51
|
const AssetsDndProvider = ({ children, locations = itemLocations.emptyItemLocations })=>{
|
|
52
52
|
const { formatMessage } = reactIntl.useIntl();
|
|
53
|
+
const getErrorMessage = useApiErrorMessage.useApiErrorMessage();
|
|
53
54
|
const { toggleNotification } = strapiAdmin.useNotification();
|
|
54
55
|
const selection = useAssetSelection.useAssetSelectionOptional();
|
|
55
56
|
const { currentFolderId } = useFolderNavigation.useFolderNavigation();
|
|
@@ -180,7 +181,7 @@ const AssetsDndProvider = ({ children, locations = itemLocations.emptyItemLocati
|
|
|
180
181
|
message: successMessage
|
|
181
182
|
});
|
|
182
183
|
} catch (error) {
|
|
183
|
-
const errorMessage =
|
|
184
|
+
const errorMessage = getErrorMessage(error, errorFallback);
|
|
184
185
|
announceToLiveRegion(formatMessage({
|
|
185
186
|
id: translations.getTranslationKey('dnd.announce.move-failure'),
|
|
186
187
|
defaultMessage: 'Move failed. {message}'
|
|
@@ -198,6 +199,7 @@ const AssetsDndProvider = ({ children, locations = itemLocations.emptyItemLocati
|
|
|
198
199
|
clearDragState,
|
|
199
200
|
folderStructure,
|
|
200
201
|
formatMessage,
|
|
202
|
+
getErrorMessage,
|
|
201
203
|
isMovePending,
|
|
202
204
|
rootLabel,
|
|
203
205
|
selection,
|