@strapi/upload 5.52.1 → 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.
Files changed (102) hide show
  1. package/dist/admin/future/components/UploadProgressDialog.js +36 -1
  2. package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
  3. package/dist/admin/future/components/UploadProgressDialog.mjs +37 -2
  4. package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
  5. package/dist/admin/future/hooks/useApiErrorMessage.js +61 -0
  6. package/dist/admin/future/hooks/useApiErrorMessage.js.map +1 -0
  7. package/dist/admin/future/hooks/useApiErrorMessage.mjs +39 -0
  8. package/dist/admin/future/hooks/useApiErrorMessage.mjs.map +1 -0
  9. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js +4 -5
  10. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js.map +1 -1
  11. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs +4 -5
  12. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs.map +1 -1
  13. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +41 -50
  14. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
  15. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +41 -50
  16. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
  17. package/dist/admin/future/pages/Assets/components/BulkActionsBar.js +10 -1
  18. package/dist/admin/future/pages/Assets/components/BulkActionsBar.js.map +1 -1
  19. package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs +10 -1
  20. package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs.map +1 -1
  21. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js +3 -2
  22. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js.map +1 -1
  23. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs +3 -2
  24. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs.map +1 -1
  25. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js +4 -2
  26. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js.map +1 -1
  27. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs +4 -2
  28. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs.map +1 -1
  29. package/dist/admin/future/pages/Assets/hooks/useIsAssetDetailsOpen.js +30 -0
  30. package/dist/admin/future/pages/Assets/hooks/useIsAssetDetailsOpen.js.map +1 -0
  31. package/dist/admin/future/pages/Assets/hooks/useIsAssetDetailsOpen.mjs +26 -0
  32. package/dist/admin/future/pages/Assets/hooks/useIsAssetDetailsOpen.mjs.map +1 -0
  33. package/dist/admin/future/services/api.js +116 -11
  34. package/dist/admin/future/services/api.js.map +1 -1
  35. package/dist/admin/future/services/api.mjs +118 -13
  36. package/dist/admin/future/services/api.mjs.map +1 -1
  37. package/dist/admin/future/services/uploadFileViaXHR.js +9 -3
  38. package/dist/admin/future/services/uploadFileViaXHR.js.map +1 -1
  39. package/dist/admin/future/services/uploadFileViaXHR.mjs +9 -3
  40. package/dist/admin/future/services/uploadFileViaXHR.mjs.map +1 -1
  41. package/dist/admin/future/store/uploadProgress.js +44 -5
  42. package/dist/admin/future/store/uploadProgress.js.map +1 -1
  43. package/dist/admin/future/store/uploadProgress.mjs +43 -6
  44. package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
  45. package/dist/admin/future/utils/getApiErrorMessage.js +20 -0
  46. package/dist/admin/future/utils/getApiErrorMessage.js.map +1 -0
  47. package/dist/admin/future/utils/getApiErrorMessage.mjs +18 -0
  48. package/dist/admin/future/utils/getApiErrorMessage.mjs.map +1 -0
  49. package/dist/admin/src/future/hooks/useApiErrorMessage.d.ts +17 -0
  50. package/dist/admin/src/future/pages/Assets/hooks/useIsAssetDetailsOpen.d.ts +18 -0
  51. package/dist/admin/src/future/services/uploadFileViaXHR.d.ts +6 -1
  52. package/dist/admin/src/future/store/uploadProgress.d.ts +16 -1
  53. package/dist/admin/src/future/utils/getApiErrorMessage.d.ts +1 -0
  54. package/dist/admin/translations/ca.json.js +1 -1
  55. package/dist/admin/translations/ca.json.mjs +1 -1
  56. package/dist/admin/translations/de.json.js +1 -1
  57. package/dist/admin/translations/de.json.mjs +1 -1
  58. package/dist/admin/translations/en.json.js +2 -1
  59. package/dist/admin/translations/en.json.js.map +1 -1
  60. package/dist/admin/translations/en.json.mjs +2 -1
  61. package/dist/admin/translations/en.json.mjs.map +1 -1
  62. package/dist/admin/translations/es.json.js +1 -1
  63. package/dist/admin/translations/es.json.mjs +1 -1
  64. package/dist/admin/translations/fr.json.js +1 -1
  65. package/dist/admin/translations/fr.json.mjs +1 -1
  66. package/dist/admin/translations/ja.json.js +1 -1
  67. package/dist/admin/translations/ja.json.mjs +1 -1
  68. package/dist/admin/translations/ko.json.js +1 -1
  69. package/dist/admin/translations/ko.json.mjs +1 -1
  70. package/dist/admin/translations/nl.json.js +1 -1
  71. package/dist/admin/translations/nl.json.mjs +1 -1
  72. package/dist/admin/translations/pl.json.js +1 -1
  73. package/dist/admin/translations/pl.json.mjs +1 -1
  74. package/dist/admin/translations/pt-BR.json.js +1 -1
  75. package/dist/admin/translations/pt-BR.json.mjs +1 -1
  76. package/dist/admin/translations/ru.json.js +1 -1
  77. package/dist/admin/translations/ru.json.mjs +1 -1
  78. package/dist/admin/translations/sk.json.js +1 -1
  79. package/dist/admin/translations/sk.json.mjs +1 -1
  80. package/dist/admin/translations/tr.json.js +1 -1
  81. package/dist/admin/translations/tr.json.mjs +1 -1
  82. package/dist/admin/translations/uk.json.js +1 -1
  83. package/dist/admin/translations/uk.json.mjs +1 -1
  84. package/dist/admin/translations/zh-Hans.json.js +1 -1
  85. package/dist/admin/translations/zh-Hans.json.mjs +1 -1
  86. package/dist/admin/translations/zh.json.js +1 -1
  87. package/dist/admin/translations/zh.json.mjs +1 -1
  88. package/dist/server/services/ai-metadata.js +25 -5
  89. package/dist/server/services/ai-metadata.js.map +1 -1
  90. package/dist/server/services/ai-metadata.mjs +1 -1
  91. package/dist/server/services/ai-metadata.mjs.map +1 -1
  92. package/dist/server/services/upload.js +4 -0
  93. package/dist/server/services/upload.js.map +1 -1
  94. package/dist/server/services/upload.mjs +4 -0
  95. package/dist/server/services/upload.mjs.map +1 -1
  96. package/dist/server/src/services/upload.d.ts.map +1 -1
  97. package/package.json +7 -7
  98. package/dist/admin/future/utils/getBulkMoveErrorMessage.js +0 -23
  99. package/dist/admin/future/utils/getBulkMoveErrorMessage.js.map +0 -1
  100. package/dist/admin/future/utils/getBulkMoveErrorMessage.mjs +0 -21
  101. package/dist/admin/future/utils/getBulkMoveErrorMessage.mjs.map +0 -1
  102. package/dist/admin/src/future/utils/getBulkMoveErrorMessage.d.ts +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AssetsDndProvider.mjs","sources":["../../../../../../../admin/src/future/pages/Assets/components/Dnd/AssetsDndProvider.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport {\n DndContext,\n DragOverlay,\n PointerSensor,\n pointerWithin,\n useSensor,\n useSensors,\n type DragEndEvent,\n type DragStartEvent,\n} from '@dnd-kit/core';\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport { Flex, VisuallyHidden } from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\n\nimport { useMediaLibraryPermissions } from '../../../../hooks/useMediaLibraryPermissions';\nimport { useBulkMoveMutation, useGetFolderStructureQuery } from '../../../../services/folders';\nimport { buildBulkMovePayload } from '../../../../utils/buildBulkMovePayload';\nimport { canDropItemOnFolder } from '../../../../utils/canDropItemOnFolder';\nimport { formatMoveSuccessMessage } from '../../../../utils/formatMoveSuccessMessage';\nimport { getBulkMoveErrorMessage } from '../../../../utils/getBulkMoveErrorMessage';\nimport { getFolderLabel } from '../../../../utils/getFolderLabel';\nimport { emptyItemLocations, type ItemLocations } from '../../../../utils/itemLocations';\nimport { getTranslationKey } from '../../../../utils/translations';\nimport { useAssetSelectionOptional } from '../../hooks/useAssetSelection';\nimport { useFolderNavigation } from '../../hooks/useFolderNavigation';\n\nimport { buildDragSet } from './buildDragSet';\nimport { computeValidDropTargets } from './computeValidDropTargets';\nimport { parseFolderTargetId, parseFolderTreeTargetId } from './dndIds';\nimport { DragOverlayChip } from './DragOverlayChip';\n\nimport type { DragItemData } from '../../../../types/dnd';\n\n/* -------------------------------------------------------------------------------------------------\n * Context — shared with UploadDropZoneProvider for bidirectional drag guard.\n * See UploadDropZoneContext.tsx TODO linking both providers.\n * -----------------------------------------------------------------------------------------------*/\n\ninterface AssetsDndContextValue {\n isInternalDragActive: boolean;\n isMovePending: boolean;\n /** O(1) lookup of whether the current drag set may drop on this destination. */\n isValidDropTarget: (targetFolderId: number | null) => boolean;\n}\n\nconst AssetsDndContext = createContext<AssetsDndContextValue | null>(null);\n\nexport const useAssetsDnd = () => {\n const context = useContext(AssetsDndContext);\n\n if (!context) {\n throw new Error('useAssetsDnd must be used within AssetsDndProvider');\n }\n\n return context;\n};\n\nexport const useAssetsDndOptional = () => useContext(AssetsDndContext);\n\n/* -------------------------------------------------------------------------------------------------\n * Provider\n * -----------------------------------------------------------------------------------------------*/\n\ninterface AssetsDndProviderProps {\n children: ReactNode;\n /**\n * Real location of every loaded row, so a multi-item drag validates against\n * each item's own parent. Defaults to a lookup that misses everything, which\n * falls back to the folder currently open.\n */\n locations?: ItemLocations;\n}\n\ninterface DragSession {\n items: DragItemData[];\n fromSelection: boolean;\n /** Location of the grabbed row — names the source in the success toast. */\n activeSourceFolderId: number | null;\n /** True when the set spans several source folders, so none can be named. */\n spansMultipleSources: boolean;\n}\n\nconst resolveDestination = (\n overId: string | number\n): { destinationFolderId: number | null } | null => {\n const inView = parseFolderTargetId(overId);\n\n if (inView != null) {\n return { destinationFolderId: inView };\n }\n\n const tree = parseFolderTreeTargetId(overId);\n\n if (tree === 'root') {\n return { destinationFolderId: null };\n }\n\n if (typeof tree === 'number') {\n return { destinationFolderId: tree };\n }\n\n return null;\n};\n\n// Activation distance no real pointer gesture will ever reach — used to keep a\n// constant-length sensor array while disabling drag for users without the move\n// permission (see the note in the provider).\nconst DRAG_DISABLED_DISTANCE = Number.MAX_SAFE_INTEGER;\n\nexport const AssetsDndProvider = ({\n children,\n locations = emptyItemLocations,\n}: AssetsDndProviderProps) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n const selection = useAssetSelectionOptional();\n const { currentFolderId } = useFolderNavigation();\n const { data: folderStructure = [] } = useGetFolderStructureQuery();\n const rootLabel = formatMessage({\n id: getTranslationKey('plugin.name'),\n defaultMessage: 'Media Library',\n });\n const [bulkMove, { isLoading: isMovePending }] = useBulkMoveMutation();\n const [dragItems, setDragItems] = useState<DragItemData[]>([]);\n const [liveAnnouncement, setLiveAnnouncement] = useState('');\n // Sync session for drag-end — state alone can lag one render behind dragStart.\n const dragSessionRef = useRef<DragSession>({\n items: [],\n fromSelection: false,\n activeSourceFolderId: null,\n spansMultipleSources: false,\n });\n\n const announceToLiveRegion = useCallback((message: string) => {\n setLiveAnnouncement('');\n requestAnimationFrame(() => setLiveAnnouncement(message));\n }, []);\n\n // Moving is a mutation (bulk-move → `assets.update`); without the permission\n // pointer drag must never activate. Keyboard moves go through BulkMoveDialog,\n // which is itself gated.\n //\n // The gate has to keep BOTH the `useSensor`/`useSensors` calls AND the sensor\n // array's length constant across renders: `canUpdate` starts false and flips\n // true once the RBAC check resolves, and dnd-kit keys internal memos/effects\n // on the sensor list, erroring if its size changes (\"argument changed size\n // between renders\"). So adding/removing a sensor is out — instead keep one\n // PointerSensor always and push its activation distance out of reach when the\n // user can't move, which no real drag gesture will ever satisfy.\n const { canUpdate } = useMediaLibraryPermissions();\n const sensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: { distance: canUpdate ? 8 : DRAG_DISABLED_DISTANCE },\n })\n );\n\n const validDropTargets = useMemo(\n () => computeValidDropTargets(dragItems, folderStructure),\n [dragItems, folderStructure]\n );\n\n const isValidDropTarget = useCallback(\n (targetFolderId: number | null) => validDropTargets.has(targetFolderId),\n [validDropTargets]\n );\n\n const contextValue = useMemo<AssetsDndContextValue>(\n () => ({\n isInternalDragActive: dragItems.length > 0,\n isMovePending,\n isValidDropTarget,\n }),\n [dragItems.length, isMovePending, isValidDropTarget]\n );\n\n const clearDragState = useCallback(() => {\n dragSessionRef.current = {\n items: [],\n fromSelection: false,\n activeSourceFolderId: null,\n spansMultipleSources: false,\n };\n setDragItems([]);\n }, []);\n\n const handleDragStart = useCallback(\n (event: DragStartEvent) => {\n const data = event.active.data.current as DragItemData | undefined;\n\n if (!data) {\n clearDragState();\n return;\n }\n\n const dragSet = buildDragSet(data, selection?.selectedKeys, locations, currentFolderId);\n dragSessionRef.current = dragSet;\n setDragItems(dragSet.items);\n },\n [clearDragState, currentFolderId, locations, selection?.selectedKeys]\n );\n\n const handleDragEnd = useCallback(\n async (event: DragEndEvent) => {\n const { over } = event;\n const { items, fromSelection, activeSourceFolderId, spansMultipleSources } =\n dragSessionRef.current;\n clearDragState();\n\n if (isMovePending || !over || items.length === 0) {\n return;\n }\n\n const destination = resolveDestination(over.id);\n\n if (!destination) {\n return;\n }\n\n const { destinationFolderId: targetFolderId } = destination;\n\n if (\n !canDropItemOnFolder({\n items,\n targetFolderId,\n folderStructure,\n })\n ) {\n return;\n }\n\n const payload = buildBulkMovePayload(items);\n // Source is the grabbed row's own location, not the folder currently open:\n // a global search result can be dragged out of a folder the user isn't in.\n // A search-built selection can also span folders, and then no single\n // source is true for the whole set — `null` drops it from the wording\n // instead of naming one item's folder for all of them. Destination is the\n // drop target. Both use leaf names so the DnD toast reads identically to\n // the dialog's.\n const successMessage = formatMoveSuccessMessage({\n formatMessage,\n count: items.length,\n source: spansMultipleSources\n ? null\n : getFolderLabel(folderStructure, activeSourceFolderId, rootLabel),\n destination: getFolderLabel(folderStructure, targetFolderId, rootLabel),\n });\n const errorFallback = formatMessage({\n id: getTranslationKey('list.bulk-actions.move.error'),\n defaultMessage: 'An error occurred while moving the items.',\n });\n\n try {\n await bulkMove({ ...payload, destinationFolderId: targetFolderId }).unwrap();\n\n if (fromSelection) {\n selection?.clear();\n }\n\n announceToLiveRegion(successMessage);\n\n toggleNotification({\n type: 'success',\n message: successMessage,\n });\n } catch (error) {\n const errorMessage = getBulkMoveErrorMessage(error, errorFallback);\n\n announceToLiveRegion(\n formatMessage(\n {\n id: getTranslationKey('dnd.announce.move-failure'),\n defaultMessage: 'Move failed. {message}',\n },\n { message: errorMessage }\n )\n );\n\n toggleNotification({\n type: 'danger',\n message: errorMessage,\n });\n }\n },\n [\n announceToLiveRegion,\n bulkMove,\n clearDragState,\n folderStructure,\n formatMessage,\n isMovePending,\n rootLabel,\n selection,\n toggleNotification,\n ]\n );\n\n const handleDragCancel = useCallback(() => {\n clearDragState();\n }, [clearDragState]);\n\n const announcements = useMemo(\n () => ({\n onDragStart: ({ active }: DragStartEvent) => {\n const data = active.data.current as DragItemData | undefined;\n return data\n ? formatMessage(\n {\n id: getTranslationKey('dnd.announce.drag-start'),\n defaultMessage: 'Picked up {name}. Drop on a folder to move.',\n },\n { name: data.name }\n )\n : '';\n },\n onDragOver: () => '',\n onDragEnd: ({ active, over }: DragEndEvent) => {\n if (!over) {\n return formatMessage({\n id: getTranslationKey('dnd.announce.cancel'),\n defaultMessage: 'Drag cancelled.',\n });\n }\n\n const destination = resolveDestination(over.id);\n const activeData = active.data.current as DragItemData | undefined;\n\n if (!destination || !activeData) {\n return '';\n }\n\n const { items } = buildDragSet(\n activeData,\n selection?.selectedKeys,\n locations,\n currentFolderId\n );\n\n if (\n !canDropItemOnFolder({\n items,\n targetFolderId: destination.destinationFolderId,\n folderStructure,\n })\n ) {\n return formatMessage({\n id: getTranslationKey('dnd.announce.invalid-drop'),\n defaultMessage: 'Cannot move item to this folder.',\n });\n }\n\n return '';\n },\n onDragCancel: () =>\n formatMessage({\n id: getTranslationKey('dnd.announce.cancel'),\n defaultMessage: 'Drag cancelled.',\n }),\n }),\n [currentFolderId, folderStructure, formatMessage, locations, selection?.selectedKeys]\n );\n\n return (\n <AssetsDndContext.Provider value={contextValue}>\n <DndContext\n sensors={sensors}\n collisionDetection={pointerWithin}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n onDragCancel={handleDragCancel}\n accessibility={{ announcements }}\n >\n <VisuallyHidden aria-live=\"polite\" aria-atomic=\"true\">\n {liveAnnouncement}\n </VisuallyHidden>\n <Flex position=\"relative\" alignItems=\"stretch\" direction=\"column\" height=\"100%\">\n {children}\n </Flex>\n <DragOverlay dropAnimation={null}>\n {dragItems.length > 0 ? <DragOverlayChip items={dragItems} /> : null}\n </DragOverlay>\n </DndContext>\n </AssetsDndContext.Provider>\n );\n};\n\n// Keyboard-accessible moves are handled by `BulkMoveDialog` (reachable from the\n// bulk-actions bar): pointer drag is a mouse-only enhancement, the dialog is the\n// a11y path. No `KeyboardSensor` by design.\n"],"names":["AssetsDndContext","createContext","useAssetsDndOptional","useContext","resolveDestination","overId","inView","parseFolderTargetId","destinationFolderId","tree","parseFolderTreeTargetId","DRAG_DISABLED_DISTANCE","Number","MAX_SAFE_INTEGER","AssetsDndProvider","children","locations","emptyItemLocations","formatMessage","useIntl","toggleNotification","useNotification","selection","useAssetSelectionOptional","currentFolderId","useFolderNavigation","data","folderStructure","useGetFolderStructureQuery","rootLabel","id","getTranslationKey","defaultMessage","bulkMove","isLoading","isMovePending","useBulkMoveMutation","dragItems","setDragItems","useState","liveAnnouncement","setLiveAnnouncement","dragSessionRef","useRef","items","fromSelection","activeSourceFolderId","spansMultipleSources","announceToLiveRegion","useCallback","message","requestAnimationFrame","canUpdate","useMediaLibraryPermissions","sensors","useSensors","useSensor","PointerSensor","activationConstraint","distance","validDropTargets","useMemo","computeValidDropTargets","isValidDropTarget","targetFolderId","has","contextValue","isInternalDragActive","length","clearDragState","current","handleDragStart","event","active","dragSet","buildDragSet","selectedKeys","handleDragEnd","over","destination","canDropItemOnFolder","payload","buildBulkMovePayload","successMessage","formatMoveSuccessMessage","count","source","getFolderLabel","errorFallback","unwrap","clear","type","error","errorMessage","getBulkMoveErrorMessage","handleDragCancel","announcements","onDragStart","name","onDragOver","onDragEnd","activeData","onDragCancel","_jsx","Provider","value","_jsxs","DndContext","collisionDetection","pointerWithin","accessibility","VisuallyHidden","aria-live","aria-atomic","Flex","position","alignItems","direction","height","DragOverlay","dropAnimation","DragOverlayChip"],"mappings":"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"}
1
+ {"version":3,"file":"AssetsDndProvider.mjs","sources":["../../../../../../../admin/src/future/pages/Assets/components/Dnd/AssetsDndProvider.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport {\n DndContext,\n DragOverlay,\n PointerSensor,\n pointerWithin,\n useSensor,\n useSensors,\n type DragEndEvent,\n type DragStartEvent,\n} from '@dnd-kit/core';\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport { Flex, VisuallyHidden } from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\n\nimport { useApiErrorMessage } from '../../../../hooks/useApiErrorMessage';\nimport { useMediaLibraryPermissions } from '../../../../hooks/useMediaLibraryPermissions';\nimport { useBulkMoveMutation, useGetFolderStructureQuery } from '../../../../services/folders';\nimport { buildBulkMovePayload } from '../../../../utils/buildBulkMovePayload';\nimport { canDropItemOnFolder } from '../../../../utils/canDropItemOnFolder';\nimport { formatMoveSuccessMessage } from '../../../../utils/formatMoveSuccessMessage';\nimport { getFolderLabel } from '../../../../utils/getFolderLabel';\nimport { emptyItemLocations, type ItemLocations } from '../../../../utils/itemLocations';\nimport { getTranslationKey } from '../../../../utils/translations';\nimport { useAssetSelectionOptional } from '../../hooks/useAssetSelection';\nimport { useFolderNavigation } from '../../hooks/useFolderNavigation';\n\nimport { buildDragSet } from './buildDragSet';\nimport { computeValidDropTargets } from './computeValidDropTargets';\nimport { parseFolderTargetId, parseFolderTreeTargetId } from './dndIds';\nimport { DragOverlayChip } from './DragOverlayChip';\n\nimport type { DragItemData } from '../../../../types/dnd';\n\n/* -------------------------------------------------------------------------------------------------\n * Context — shared with UploadDropZoneProvider for bidirectional drag guard.\n * See UploadDropZoneContext.tsx TODO linking both providers.\n * -----------------------------------------------------------------------------------------------*/\n\ninterface AssetsDndContextValue {\n isInternalDragActive: boolean;\n isMovePending: boolean;\n /** O(1) lookup of whether the current drag set may drop on this destination. */\n isValidDropTarget: (targetFolderId: number | null) => boolean;\n}\n\nconst AssetsDndContext = createContext<AssetsDndContextValue | null>(null);\n\nexport const useAssetsDnd = () => {\n const context = useContext(AssetsDndContext);\n\n if (!context) {\n throw new Error('useAssetsDnd must be used within AssetsDndProvider');\n }\n\n return context;\n};\n\nexport const useAssetsDndOptional = () => useContext(AssetsDndContext);\n\n/* -------------------------------------------------------------------------------------------------\n * Provider\n * -----------------------------------------------------------------------------------------------*/\n\ninterface AssetsDndProviderProps {\n children: ReactNode;\n /**\n * Real location of every loaded row, so a multi-item drag validates against\n * each item's own parent. Defaults to a lookup that misses everything, which\n * falls back to the folder currently open.\n */\n locations?: ItemLocations;\n}\n\ninterface DragSession {\n items: DragItemData[];\n fromSelection: boolean;\n /** Location of the grabbed row — names the source in the success toast. */\n activeSourceFolderId: number | null;\n /** True when the set spans several source folders, so none can be named. */\n spansMultipleSources: boolean;\n}\n\nconst resolveDestination = (\n overId: string | number\n): { destinationFolderId: number | null } | null => {\n const inView = parseFolderTargetId(overId);\n\n if (inView != null) {\n return { destinationFolderId: inView };\n }\n\n const tree = parseFolderTreeTargetId(overId);\n\n if (tree === 'root') {\n return { destinationFolderId: null };\n }\n\n if (typeof tree === 'number') {\n return { destinationFolderId: tree };\n }\n\n return null;\n};\n\n// Activation distance no real pointer gesture will ever reach — used to keep a\n// constant-length sensor array while disabling drag for users without the move\n// permission (see the note in the provider).\nconst DRAG_DISABLED_DISTANCE = Number.MAX_SAFE_INTEGER;\n\nexport const AssetsDndProvider = ({\n children,\n locations = emptyItemLocations,\n}: AssetsDndProviderProps) => {\n const { formatMessage } = useIntl();\n const getErrorMessage = useApiErrorMessage();\n const { toggleNotification } = useNotification();\n const selection = useAssetSelectionOptional();\n const { currentFolderId } = useFolderNavigation();\n const { data: folderStructure = [] } = useGetFolderStructureQuery();\n const rootLabel = formatMessage({\n id: getTranslationKey('plugin.name'),\n defaultMessage: 'Media Library',\n });\n const [bulkMove, { isLoading: isMovePending }] = useBulkMoveMutation();\n const [dragItems, setDragItems] = useState<DragItemData[]>([]);\n const [liveAnnouncement, setLiveAnnouncement] = useState('');\n // Sync session for drag-end — state alone can lag one render behind dragStart.\n const dragSessionRef = useRef<DragSession>({\n items: [],\n fromSelection: false,\n activeSourceFolderId: null,\n spansMultipleSources: false,\n });\n\n const announceToLiveRegion = useCallback((message: string) => {\n setLiveAnnouncement('');\n requestAnimationFrame(() => setLiveAnnouncement(message));\n }, []);\n\n // Moving is a mutation (bulk-move → `assets.update`); without the permission\n // pointer drag must never activate. Keyboard moves go through BulkMoveDialog,\n // which is itself gated.\n //\n // The gate has to keep BOTH the `useSensor`/`useSensors` calls AND the sensor\n // array's length constant across renders: `canUpdate` starts false and flips\n // true once the RBAC check resolves, and dnd-kit keys internal memos/effects\n // on the sensor list, erroring if its size changes (\"argument changed size\n // between renders\"). So adding/removing a sensor is out — instead keep one\n // PointerSensor always and push its activation distance out of reach when the\n // user can't move, which no real drag gesture will ever satisfy.\n const { canUpdate } = useMediaLibraryPermissions();\n const sensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: { distance: canUpdate ? 8 : DRAG_DISABLED_DISTANCE },\n })\n );\n\n const validDropTargets = useMemo(\n () => computeValidDropTargets(dragItems, folderStructure),\n [dragItems, folderStructure]\n );\n\n const isValidDropTarget = useCallback(\n (targetFolderId: number | null) => validDropTargets.has(targetFolderId),\n [validDropTargets]\n );\n\n const contextValue = useMemo<AssetsDndContextValue>(\n () => ({\n isInternalDragActive: dragItems.length > 0,\n isMovePending,\n isValidDropTarget,\n }),\n [dragItems.length, isMovePending, isValidDropTarget]\n );\n\n const clearDragState = useCallback(() => {\n dragSessionRef.current = {\n items: [],\n fromSelection: false,\n activeSourceFolderId: null,\n spansMultipleSources: false,\n };\n setDragItems([]);\n }, []);\n\n const handleDragStart = useCallback(\n (event: DragStartEvent) => {\n const data = event.active.data.current as DragItemData | undefined;\n\n if (!data) {\n clearDragState();\n return;\n }\n\n const dragSet = buildDragSet(data, selection?.selectedKeys, locations, currentFolderId);\n dragSessionRef.current = dragSet;\n setDragItems(dragSet.items);\n },\n [clearDragState, currentFolderId, locations, selection?.selectedKeys]\n );\n\n const handleDragEnd = useCallback(\n async (event: DragEndEvent) => {\n const { over } = event;\n const { items, fromSelection, activeSourceFolderId, spansMultipleSources } =\n dragSessionRef.current;\n clearDragState();\n\n if (isMovePending || !over || items.length === 0) {\n return;\n }\n\n const destination = resolveDestination(over.id);\n\n if (!destination) {\n return;\n }\n\n const { destinationFolderId: targetFolderId } = destination;\n\n if (\n !canDropItemOnFolder({\n items,\n targetFolderId,\n folderStructure,\n })\n ) {\n return;\n }\n\n const payload = buildBulkMovePayload(items);\n // Source is the grabbed row's own location, not the folder currently open:\n // a global search result can be dragged out of a folder the user isn't in.\n // A search-built selection can also span folders, and then no single\n // source is true for the whole set — `null` drops it from the wording\n // instead of naming one item's folder for all of them. Destination is the\n // drop target. Both use leaf names so the DnD toast reads identically to\n // the dialog's.\n const successMessage = formatMoveSuccessMessage({\n formatMessage,\n count: items.length,\n source: spansMultipleSources\n ? null\n : getFolderLabel(folderStructure, activeSourceFolderId, rootLabel),\n destination: getFolderLabel(folderStructure, targetFolderId, rootLabel),\n });\n const errorFallback = formatMessage({\n id: getTranslationKey('list.bulk-actions.move.error'),\n defaultMessage: 'An error occurred while moving the items.',\n });\n\n try {\n await bulkMove({ ...payload, destinationFolderId: targetFolderId }).unwrap();\n\n if (fromSelection) {\n selection?.clear();\n }\n\n announceToLiveRegion(successMessage);\n\n toggleNotification({\n type: 'success',\n message: successMessage,\n });\n } catch (error) {\n const errorMessage = getErrorMessage(error, errorFallback);\n\n announceToLiveRegion(\n formatMessage(\n {\n id: getTranslationKey('dnd.announce.move-failure'),\n defaultMessage: 'Move failed. {message}',\n },\n { message: errorMessage }\n )\n );\n\n toggleNotification({\n type: 'danger',\n message: errorMessage,\n });\n }\n },\n [\n announceToLiveRegion,\n bulkMove,\n clearDragState,\n folderStructure,\n formatMessage,\n getErrorMessage,\n isMovePending,\n rootLabel,\n selection,\n toggleNotification,\n ]\n );\n\n const handleDragCancel = useCallback(() => {\n clearDragState();\n }, [clearDragState]);\n\n const announcements = useMemo(\n () => ({\n onDragStart: ({ active }: DragStartEvent) => {\n const data = active.data.current as DragItemData | undefined;\n return data\n ? formatMessage(\n {\n id: getTranslationKey('dnd.announce.drag-start'),\n defaultMessage: 'Picked up {name}. Drop on a folder to move.',\n },\n { name: data.name }\n )\n : '';\n },\n onDragOver: () => '',\n onDragEnd: ({ active, over }: DragEndEvent) => {\n if (!over) {\n return formatMessage({\n id: getTranslationKey('dnd.announce.cancel'),\n defaultMessage: 'Drag cancelled.',\n });\n }\n\n const destination = resolveDestination(over.id);\n const activeData = active.data.current as DragItemData | undefined;\n\n if (!destination || !activeData) {\n return '';\n }\n\n const { items } = buildDragSet(\n activeData,\n selection?.selectedKeys,\n locations,\n currentFolderId\n );\n\n if (\n !canDropItemOnFolder({\n items,\n targetFolderId: destination.destinationFolderId,\n folderStructure,\n })\n ) {\n return formatMessage({\n id: getTranslationKey('dnd.announce.invalid-drop'),\n defaultMessage: 'Cannot move item to this folder.',\n });\n }\n\n return '';\n },\n onDragCancel: () =>\n formatMessage({\n id: getTranslationKey('dnd.announce.cancel'),\n defaultMessage: 'Drag cancelled.',\n }),\n }),\n [currentFolderId, folderStructure, formatMessage, locations, selection?.selectedKeys]\n );\n\n return (\n <AssetsDndContext.Provider value={contextValue}>\n <DndContext\n sensors={sensors}\n collisionDetection={pointerWithin}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n onDragCancel={handleDragCancel}\n accessibility={{ announcements }}\n >\n <VisuallyHidden aria-live=\"polite\" aria-atomic=\"true\">\n {liveAnnouncement}\n </VisuallyHidden>\n <Flex position=\"relative\" alignItems=\"stretch\" direction=\"column\" height=\"100%\">\n {children}\n </Flex>\n <DragOverlay dropAnimation={null}>\n {dragItems.length > 0 ? <DragOverlayChip items={dragItems} /> : null}\n </DragOverlay>\n </DndContext>\n </AssetsDndContext.Provider>\n );\n};\n\n// Keyboard-accessible moves are handled by `BulkMoveDialog` (reachable from the\n// bulk-actions bar): pointer drag is a mouse-only enhancement, the dialog is the\n// a11y path. No `KeyboardSensor` by design.\n"],"names":["AssetsDndContext","createContext","useAssetsDndOptional","useContext","resolveDestination","overId","inView","parseFolderTargetId","destinationFolderId","tree","parseFolderTreeTargetId","DRAG_DISABLED_DISTANCE","Number","MAX_SAFE_INTEGER","AssetsDndProvider","children","locations","emptyItemLocations","formatMessage","useIntl","getErrorMessage","useApiErrorMessage","toggleNotification","useNotification","selection","useAssetSelectionOptional","currentFolderId","useFolderNavigation","data","folderStructure","useGetFolderStructureQuery","rootLabel","id","getTranslationKey","defaultMessage","bulkMove","isLoading","isMovePending","useBulkMoveMutation","dragItems","setDragItems","useState","liveAnnouncement","setLiveAnnouncement","dragSessionRef","useRef","items","fromSelection","activeSourceFolderId","spansMultipleSources","announceToLiveRegion","useCallback","message","requestAnimationFrame","canUpdate","useMediaLibraryPermissions","sensors","useSensors","useSensor","PointerSensor","activationConstraint","distance","validDropTargets","useMemo","computeValidDropTargets","isValidDropTarget","targetFolderId","has","contextValue","isInternalDragActive","length","clearDragState","current","handleDragStart","event","active","dragSet","buildDragSet","selectedKeys","handleDragEnd","over","destination","canDropItemOnFolder","payload","buildBulkMovePayload","successMessage","formatMoveSuccessMessage","count","source","getFolderLabel","errorFallback","unwrap","clear","type","error","errorMessage","handleDragCancel","announcements","onDragStart","name","onDragOver","onDragEnd","activeData","onDragCancel","_jsx","Provider","value","_jsxs","DndContext","collisionDetection","pointerWithin","accessibility","VisuallyHidden","aria-live","aria-atomic","Flex","position","alignItems","direction","height","DragOverlay","dropAnimation","DragOverlayChip"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ var strapiAdmin = require('@strapi/admin/strapi-admin');
4
+
5
+ /** Name of the parameter to look for in the URL to open the asset details drawer. */ const ASSET_DETAILS_URL_PARAM = 'assetId';
6
+ /**
7
+ * The asset id in the URL, or `null` when the param is absent or unparseable.
8
+ *
9
+ * One definition shared by the drawer (which needs the id) and the "is it open?"
10
+ * hook below, so the two can never disagree about what counts as open.
11
+ */ const parseAssetDetailsId = (raw)=>{
12
+ const parsed = raw ? parseInt(raw, 10) : NaN;
13
+ return Number.isNaN(parsed) ? null : parsed;
14
+ };
15
+ /**
16
+ * Whether the asset details drawer is open, derived purely from the URL.
17
+ *
18
+ * Deliberately separate from `useAssetDetailsParam`, which additionally owns the
19
+ * drawer's mount lifecycle: its `shouldRenderDrawer` only stays correct in the
20
+ * component wired to the close animation, so a second caller would strand it at
21
+ * `true`. Anything that just needs the answer reads this instead.
22
+ */ const useIsAssetDetailsOpen = ()=>{
23
+ const [{ query }] = strapiAdmin.useQueryParams();
24
+ return parseAssetDetailsId(query?.[ASSET_DETAILS_URL_PARAM]) !== null;
25
+ };
26
+
27
+ exports.ASSET_DETAILS_URL_PARAM = ASSET_DETAILS_URL_PARAM;
28
+ exports.parseAssetDetailsId = parseAssetDetailsId;
29
+ exports.useIsAssetDetailsOpen = useIsAssetDetailsOpen;
30
+ //# sourceMappingURL=useIsAssetDetailsOpen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIsAssetDetailsOpen.js","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useIsAssetDetailsOpen.ts"],"sourcesContent":["import { useQueryParams } from '@strapi/admin/strapi-admin';\n\n/** Name of the parameter to look for in the URL to open the asset details drawer. */\nexport const ASSET_DETAILS_URL_PARAM = 'assetId';\n\n/**\n * The asset id in the URL, or `null` when the param is absent or unparseable.\n *\n * One definition shared by the drawer (which needs the id) and the \"is it open?\"\n * hook below, so the two can never disagree about what counts as open.\n */\nexport const parseAssetDetailsId = (raw: string | undefined): number | null => {\n const parsed = raw ? parseInt(raw, 10) : NaN;\n\n return Number.isNaN(parsed) ? null : parsed;\n};\n\n/**\n * Whether the asset details drawer is open, derived purely from the URL.\n *\n * Deliberately separate from `useAssetDetailsParam`, which additionally owns the\n * drawer's mount lifecycle: its `shouldRenderDrawer` only stays correct in the\n * component wired to the close animation, so a second caller would strand it at\n * `true`. Anything that just needs the answer reads this instead.\n */\nexport const useIsAssetDetailsOpen = (): boolean => {\n const [{ query }] = useQueryParams<{ [ASSET_DETAILS_URL_PARAM]?: string }>();\n\n return parseAssetDetailsId(query?.[ASSET_DETAILS_URL_PARAM]) !== null;\n};\n"],"names":["ASSET_DETAILS_URL_PARAM","parseAssetDetailsId","raw","parsed","parseInt","NaN","Number","isNaN","useIsAssetDetailsOpen","query","useQueryParams"],"mappings":";;;;AAEA,sFACO,MAAMA,uBAAAA,GAA0B;AAEvC;;;;;IAMO,MAAMC,mBAAAA,GAAsB,CAACC,GAAAA,GAAAA;AAClC,IAAA,MAAMC,MAAAA,GAASD,GAAAA,GAAME,QAAAA,CAASF,GAAAA,EAAK,EAAA,CAAA,GAAMG,GAAAA;AAEzC,IAAA,OAAOC,MAAAA,CAAOC,KAAK,CAACJ,MAAAA,CAAAA,GAAU,IAAA,GAAOA,MAAAA;AACvC;AAEA;;;;;;;UAQaK,qBAAAA,GAAwB,IAAA;AACnC,IAAA,MAAM,CAAC,EAAEC,KAAK,EAAE,CAAC,GAAGC,0BAAAA,EAAAA;AAEpB,IAAA,OAAOT,mBAAAA,CAAoBQ,KAAAA,GAAQT,uBAAAA,CAAwB,CAAA,KAAM,IAAA;AACnE;;;;;;"}
@@ -0,0 +1,26 @@
1
+ import { useQueryParams } from '@strapi/admin/strapi-admin';
2
+
3
+ /** Name of the parameter to look for in the URL to open the asset details drawer. */ const ASSET_DETAILS_URL_PARAM = 'assetId';
4
+ /**
5
+ * The asset id in the URL, or `null` when the param is absent or unparseable.
6
+ *
7
+ * One definition shared by the drawer (which needs the id) and the "is it open?"
8
+ * hook below, so the two can never disagree about what counts as open.
9
+ */ const parseAssetDetailsId = (raw)=>{
10
+ const parsed = raw ? parseInt(raw, 10) : NaN;
11
+ return Number.isNaN(parsed) ? null : parsed;
12
+ };
13
+ /**
14
+ * Whether the asset details drawer is open, derived purely from the URL.
15
+ *
16
+ * Deliberately separate from `useAssetDetailsParam`, which additionally owns the
17
+ * drawer's mount lifecycle: its `shouldRenderDrawer` only stays correct in the
18
+ * component wired to the close animation, so a second caller would strand it at
19
+ * `true`. Anything that just needs the answer reads this instead.
20
+ */ const useIsAssetDetailsOpen = ()=>{
21
+ const [{ query }] = useQueryParams();
22
+ return parseAssetDetailsId(query?.[ASSET_DETAILS_URL_PARAM]) !== null;
23
+ };
24
+
25
+ export { ASSET_DETAILS_URL_PARAM, parseAssetDetailsId, useIsAssetDetailsOpen };
26
+ //# sourceMappingURL=useIsAssetDetailsOpen.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIsAssetDetailsOpen.mjs","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useIsAssetDetailsOpen.ts"],"sourcesContent":["import { useQueryParams } from '@strapi/admin/strapi-admin';\n\n/** Name of the parameter to look for in the URL to open the asset details drawer. */\nexport const ASSET_DETAILS_URL_PARAM = 'assetId';\n\n/**\n * The asset id in the URL, or `null` when the param is absent or unparseable.\n *\n * One definition shared by the drawer (which needs the id) and the \"is it open?\"\n * hook below, so the two can never disagree about what counts as open.\n */\nexport const parseAssetDetailsId = (raw: string | undefined): number | null => {\n const parsed = raw ? parseInt(raw, 10) : NaN;\n\n return Number.isNaN(parsed) ? null : parsed;\n};\n\n/**\n * Whether the asset details drawer is open, derived purely from the URL.\n *\n * Deliberately separate from `useAssetDetailsParam`, which additionally owns the\n * drawer's mount lifecycle: its `shouldRenderDrawer` only stays correct in the\n * component wired to the close animation, so a second caller would strand it at\n * `true`. Anything that just needs the answer reads this instead.\n */\nexport const useIsAssetDetailsOpen = (): boolean => {\n const [{ query }] = useQueryParams<{ [ASSET_DETAILS_URL_PARAM]?: string }>();\n\n return parseAssetDetailsId(query?.[ASSET_DETAILS_URL_PARAM]) !== null;\n};\n"],"names":["ASSET_DETAILS_URL_PARAM","parseAssetDetailsId","raw","parsed","parseInt","NaN","Number","isNaN","useIsAssetDetailsOpen","query","useQueryParams"],"mappings":";;AAEA,sFACO,MAAMA,uBAAAA,GAA0B;AAEvC;;;;;IAMO,MAAMC,mBAAAA,GAAsB,CAACC,GAAAA,GAAAA;AAClC,IAAA,MAAMC,MAAAA,GAASD,GAAAA,GAAME,QAAAA,CAASF,GAAAA,EAAK,EAAA,CAAA,GAAMG,GAAAA;AAEzC,IAAA,OAAOC,MAAAA,CAAOC,KAAK,CAACJ,MAAAA,CAAAA,GAAU,IAAA,GAAOA,MAAAA;AACvC;AAEA;;;;;;;UAQaK,qBAAAA,GAAwB,IAAA;AACnC,IAAA,MAAM,CAAC,EAAEC,KAAK,EAAE,CAAC,GAAGC,cAAAA,EAAAA;AAEpB,IAAA,OAAOT,mBAAAA,CAAoBQ,KAAAA,GAAQT,uBAAAA,CAAwB,CAAA,KAAM,IAAA;AACnE;;;;"}
@@ -43,6 +43,22 @@ const unregisterAbortController = (uploadId)=>{
43
43
  unregisterAbortController(uploadId);
44
44
  }
45
45
  };
46
+ /**
47
+ * Runs are chained per batch rather than started in parallel: a second pool
48
+ * alongside the first would double the files in flight and make `concurrency`
49
+ * stop meaning anything.
50
+ */ const poolTails = new Map();
51
+ const trackPoolRun = (uploadId, run)=>{
52
+ // Not swallowed: the caller still gets the real result from `run`.
53
+ const tail = run.catch(()=>undefined);
54
+ poolTails.set(uploadId, tail);
55
+ tail.then(()=>{
56
+ // Leave the entry alone if another run has queued behind us in the meantime.
57
+ if (poolTails.get(uploadId) === tail) {
58
+ poolTails.delete(uploadId);
59
+ }
60
+ });
61
+ };
46
62
  /** Maps a server per-file outcome onto the row's terminal metadata status. */ const METADATA_STATUS_BY_RESULT = {
47
63
  success: 'generated',
48
64
  skipped: 'skipped',
@@ -116,7 +132,8 @@ const unregisterAbortController = (uploadId)=>{
116
132
  dispatch(uploadProgress.setFileUploading({
117
133
  name: fileName,
118
134
  index,
119
- size: entry.file.size
135
+ size: entry.file.size,
136
+ uploadId
120
137
  }));
121
138
  const formData = new FormData();
122
139
  formData.append('files', entry.file);
@@ -125,7 +142,8 @@ const unregisterAbortController = (uploadId)=>{
125
142
  const batcher = createRafBatcher.createRafBatcher((bytes)=>{
126
143
  dispatch(uploadProgress.setFileProgress({
127
144
  index,
128
- bytes
145
+ bytes,
146
+ uploadId
129
147
  }));
130
148
  });
131
149
  try {
@@ -134,7 +152,8 @@ const unregisterAbortController = (uploadId)=>{
134
152
  uploaded.push(file);
135
153
  dispatch(uploadProgress.setFileComplete({
136
154
  index,
137
- file
155
+ file,
156
+ uploadId
138
157
  }));
139
158
  // Not awaited: overlaps with the next file's upload and can't fail the batch.
140
159
  maybeGenerateMetadata({
@@ -155,7 +174,8 @@ const unregisterAbortController = (uploadId)=>{
155
174
  dispatch(uploadProgress.setFileError({
156
175
  index,
157
176
  name: fileName,
158
- message
177
+ message,
178
+ uploadId
159
179
  }));
160
180
  }
161
181
  };
@@ -185,6 +205,44 @@ const unregisterAbortController = (uploadId)=>{
185
205
  data: uploaded
186
206
  };
187
207
  };
208
+ /**
209
+ * Reuses the batch's AbortController so one Cancel stops both drops.
210
+ * `runUploadPool` unregisters it on finish, hence the re-register below.
211
+ *
212
+ * Resolves with the whole batch's files, not just this leg's: the mutation
213
+ * models one drop, and after a merge the drop is the batch. The earlier leg's
214
+ * files come from the tail this run queued behind.
215
+ */ const runMergedUploadPool = async ({ entries, indices, token, uploadId, dispatch, concurrency, generateAiMetadata })=>{
216
+ const abortController = abortControllers.get(uploadId) ?? new AbortController();
217
+ const run = (poolTails.get(uploadId) ?? Promise.resolve(undefined)).then((previous)=>{
218
+ // A failed earlier leg contributes no files; its error already reached the
219
+ // store, and this leg still has files of its own to upload.
220
+ const earlier = previous?.data ?? [];
221
+ if (abortController.signal.aborted) {
222
+ return {
223
+ data: earlier
224
+ };
225
+ }
226
+ registerAbortController(uploadId, abortController);
227
+ return runUploadPool({
228
+ entries,
229
+ indices,
230
+ token,
231
+ uploadId,
232
+ abortController,
233
+ dispatch,
234
+ concurrency,
235
+ generateAiMetadata
236
+ }).then((result)=>result.error ? result : {
237
+ data: [
238
+ ...earlier,
239
+ ...result.data ?? []
240
+ ]
241
+ });
242
+ });
243
+ trackPoolRun(uploadId, run);
244
+ return run;
245
+ };
188
246
  /* -------------------------------------------------------------------------------------------------
189
247
  * URL upload flow (SSE) — kept as-is for this iteration
190
248
  * -----------------------------------------------------------------------------------------------*/ /**
@@ -271,7 +329,8 @@ const unregisterAbortController = (uploadId)=>{
271
329
  dispatch(uploadProgress.setFileUploading({
272
330
  name: parsed.url,
273
331
  index,
274
- size: 0
332
+ size: 0,
333
+ uploadId
275
334
  }));
276
335
  break;
277
336
  }
@@ -281,7 +340,8 @@ const unregisterAbortController = (uploadId)=>{
281
340
  dispatch(uploadProgress.setFileUploading({
282
341
  name: payload.name,
283
342
  index,
284
- size: payload.size
343
+ size: payload.size,
344
+ uploadId
285
345
  }));
286
346
  break;
287
347
  }
@@ -290,7 +350,8 @@ const unregisterAbortController = (uploadId)=>{
290
350
  const payload = parsed;
291
351
  dispatch(uploadProgress.setFileComplete({
292
352
  index,
293
- file: payload.file
353
+ file: payload.file,
354
+ uploadId
294
355
  }));
295
356
  // Same fire-and-forget semantics as the file flow.
296
357
  maybeGenerateMetadata({
@@ -308,7 +369,8 @@ const unregisterAbortController = (uploadId)=>{
308
369
  dispatch(uploadProgress.setFileError({
309
370
  index,
310
371
  name: payload.name,
311
- message: payload.message
372
+ message: payload.message,
373
+ uploadId
312
374
  }));
313
375
  break;
314
376
  }
@@ -357,6 +419,38 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
357
419
  }));
358
420
  const fileNames = entries.map((entry)=>entry.fileInfo.name ?? entry.file.name);
359
421
  const fileSizes = entries.map((entry)=>entry.file.size);
422
+ // `uploadFromUrls` also leaves rows `pending`, but registers no batch —
423
+ // so an unregistered batch is not ours to merge into, and appending would
424
+ // land on top of its rows.
425
+ const progress = getState().uploadProgress;
426
+ const batch = getUploadEntries(progress.uploadId);
427
+ const isMerge = batch !== undefined && uploadProgress.isUploadInFlight(progress.files);
428
+ if (isMerge) {
429
+ const { uploadId } = progress;
430
+ const offset = batch.entries.length;
431
+ // No `totalFiles`: the reducer derives it from the rows it appends.
432
+ dispatch(uploadProgress.appendUploadFiles({
433
+ uploadId,
434
+ fileNames,
435
+ fileSizes
436
+ }));
437
+ const mergedEntries = [
438
+ ...batch.entries,
439
+ ...entries
440
+ ];
441
+ // Merged files adopt the batch's flags: a new concurrency would raise
442
+ // the ceiling it exists to impose.
443
+ registerUploadEntries(uploadId, mergedEntries, batch.generateAiMetadata, batch.concurrency);
444
+ return runMergedUploadPool({
445
+ entries: mergedEntries,
446
+ indices: entries.map((_, index)=>offset + index),
447
+ token,
448
+ uploadId,
449
+ dispatch,
450
+ concurrency: batch.concurrency,
451
+ generateAiMetadata: batch.generateAiMetadata
452
+ });
453
+ }
360
454
  // Open the progress dialog
361
455
  dispatch(uploadProgress.openUploadProgress({
362
456
  totalFiles,
@@ -370,7 +464,7 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
370
464
  // One AbortController per batch
371
465
  const abortController = new AbortController();
372
466
  registerAbortController(uploadId, abortController);
373
- return runUploadPool({
467
+ const run = runUploadPool({
374
468
  entries,
375
469
  indices: entries.map((_, index)=>index),
376
470
  token,
@@ -380,6 +474,8 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
380
474
  concurrency,
381
475
  generateAiMetadata
382
476
  });
477
+ trackPoolRun(uploadId, run);
478
+ return run;
383
479
  },
384
480
  // `Folder, LIST` refreshes the folder header count, which changes when
385
481
  // files are added to it.
@@ -413,7 +509,11 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
413
509
  data: uploaded
414
510
  };
415
511
  } catch (err) {
416
- const message = err instanceof Error ? err.message : 'Upload failed';
512
+ // Only pass on text the server actually sent. The XHR layer's own
513
+ // fallbacks are hardcoded English, and callers here (crop → "Save
514
+ // as copy") have a localized message of their own; an empty string
515
+ // reads as "nothing usable" and lets theirs win.
516
+ const message = err instanceof uploadFileViaXHR.UploadFileError && err.isServerMessage ? err.message : '';
417
517
  return {
418
518
  error: {
419
519
  name: 'UnknownError',
@@ -466,7 +566,7 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
466
566
  // Fresh AbortController for the retry run
467
567
  const abortController = new AbortController();
468
568
  registerAbortController(uploadId, abortController);
469
- return runUploadPool({
569
+ const run = runUploadPool({
470
570
  entries: batch.entries,
471
571
  indices: cancelledIndices,
472
572
  token,
@@ -477,6 +577,11 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
477
577
  concurrency: batch.concurrency,
478
578
  generateAiMetadata: batch.generateAiMetadata
479
579
  });
580
+ // Retried rows are `pending` again, so a drop lands on the merge branch.
581
+ // Untracked, that merge would find no tail to queue behind and run its
582
+ // pool beside this one — two pools of `concurrency` each.
583
+ trackPoolRun(uploadId, run);
584
+ return run;
480
585
  },
481
586
  // `Folder, LIST` refreshes the folder header count, which changes when
482
587
  // files are added to it.