@strapi/upload 5.51.2 → 5.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/dist/admin/future/App.js +3 -3
  2. package/dist/admin/future/App.js.map +1 -1
  3. package/dist/admin/future/App.mjs +4 -4
  4. package/dist/admin/future/App.mjs.map +1 -1
  5. package/dist/admin/future/components/BetaNotice.js +72 -0
  6. package/dist/admin/future/components/BetaNotice.js.map +1 -0
  7. package/dist/admin/future/components/BetaNotice.mjs +70 -0
  8. package/dist/admin/future/components/BetaNotice.mjs.map +1 -0
  9. package/dist/admin/future/components/Drawer.js +16 -3
  10. package/dist/admin/future/components/Drawer.js.map +1 -1
  11. package/dist/admin/future/components/Drawer.mjs +16 -3
  12. package/dist/admin/future/components/Drawer.mjs.map +1 -1
  13. package/dist/admin/future/hooks/useAIMetadataEnabled.js +39 -0
  14. package/dist/admin/future/hooks/useAIMetadataEnabled.js.map +1 -0
  15. package/dist/admin/future/hooks/useAIMetadataEnabled.mjs +37 -0
  16. package/dist/admin/future/hooks/useAIMetadataEnabled.mjs.map +1 -0
  17. package/dist/admin/future/pages/Assets/AssetsPage.js +297 -139
  18. package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
  19. package/dist/admin/future/pages/Assets/AssetsPage.mjs +301 -143
  20. package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
  21. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.js +46 -0
  22. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.js.map +1 -0
  23. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.mjs +44 -0
  24. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.mjs.map +1 -0
  25. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js +325 -0
  26. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js.map +1 -0
  27. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs +323 -0
  28. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs.map +1 -0
  29. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js +35 -14
  30. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
  31. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +35 -14
  32. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
  33. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +54 -53
  34. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
  35. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +54 -54
  36. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
  37. package/dist/admin/future/pages/Assets/components/AssetsGrid.js +89 -24
  38. package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
  39. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +91 -26
  40. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
  41. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js +15 -3
  42. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js.map +1 -1
  43. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs +16 -4
  44. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs.map +1 -1
  45. package/dist/admin/future/pages/Assets/components/AssetsTable.js +168 -58
  46. package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
  47. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +171 -61
  48. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
  49. package/dist/admin/future/pages/Assets/components/BulkActionsBar.js +64 -132
  50. package/dist/admin/future/pages/Assets/components/BulkActionsBar.js.map +1 -1
  51. package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs +64 -132
  52. package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs.map +1 -1
  53. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js +123 -76
  54. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js.map +1 -1
  55. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs +126 -79
  56. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs.map +1 -1
  57. package/dist/admin/future/pages/Assets/components/BusyOverlay.js +36 -0
  58. package/dist/admin/future/pages/Assets/components/BusyOverlay.js.map +1 -0
  59. package/dist/admin/future/pages/Assets/components/BusyOverlay.mjs +34 -0
  60. package/dist/admin/future/pages/Assets/components/BusyOverlay.mjs.map +1 -0
  61. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.js +131 -0
  62. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.js.map +1 -0
  63. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.mjs +129 -0
  64. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.mjs.map +1 -0
  65. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js +27 -17
  66. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js.map +1 -1
  67. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs +28 -18
  68. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs.map +1 -1
  69. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js +28 -31
  70. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js.map +1 -1
  71. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs +28 -31
  72. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs.map +1 -1
  73. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js +10 -15
  74. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js.map +1 -1
  75. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs +10 -15
  76. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs.map +1 -1
  77. package/dist/admin/future/pages/Assets/components/FilterBadges.js +13 -4
  78. package/dist/admin/future/pages/Assets/components/FilterBadges.js.map +1 -1
  79. package/dist/admin/future/pages/Assets/components/FilterBadges.mjs +13 -4
  80. package/dist/admin/future/pages/Assets/components/FilterBadges.mjs.map +1 -1
  81. package/dist/admin/future/pages/Assets/components/FilterMenu.js +241 -61
  82. package/dist/admin/future/pages/Assets/components/FilterMenu.js.map +1 -1
  83. package/dist/admin/future/pages/Assets/components/FilterMenu.mjs +244 -64
  84. package/dist/admin/future/pages/Assets/components/FilterMenu.mjs.map +1 -1
  85. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.js +144 -0
  86. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.js.map +1 -0
  87. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.mjs +142 -0
  88. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.mjs.map +1 -0
  89. package/dist/admin/future/pages/Assets/components/{CreateFolderDialog.js → FolderFormDialog.js} +50 -16
  90. package/dist/admin/future/pages/Assets/components/FolderFormDialog.js.map +1 -0
  91. package/dist/admin/future/pages/Assets/components/{CreateFolderDialog.mjs → FolderFormDialog.mjs} +52 -18
  92. package/dist/admin/future/pages/Assets/components/FolderFormDialog.mjs.map +1 -0
  93. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js +111 -191
  94. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
  95. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs +114 -194
  96. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
  97. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.js +47 -0
  98. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.js.map +1 -0
  99. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.mjs +44 -0
  100. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.mjs.map +1 -0
  101. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js +203 -62
  102. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js.map +1 -1
  103. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs +204 -63
  104. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs.map +1 -1
  105. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.js +75 -0
  106. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.js.map +1 -0
  107. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.mjs +73 -0
  108. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.mjs.map +1 -0
  109. package/dist/admin/future/services/api.js +24 -0
  110. package/dist/admin/future/services/api.js.map +1 -1
  111. package/dist/admin/future/services/api.mjs +24 -0
  112. package/dist/admin/future/services/api.mjs.map +1 -1
  113. package/dist/admin/future/services/assets.js +11 -0
  114. package/dist/admin/future/services/assets.js.map +1 -1
  115. package/dist/admin/future/services/assets.mjs +11 -0
  116. package/dist/admin/future/services/assets.mjs.map +1 -1
  117. package/dist/admin/future/services/folders.js +37 -2
  118. package/dist/admin/future/services/folders.js.map +1 -1
  119. package/dist/admin/future/services/folders.mjs +37 -3
  120. package/dist/admin/future/services/folders.mjs.map +1 -1
  121. package/dist/admin/future/utils/buildDragSetFromSelection.js +7 -5
  122. package/dist/admin/future/utils/buildDragSetFromSelection.js.map +1 -1
  123. package/dist/admin/future/utils/buildDragSetFromSelection.mjs +7 -5
  124. package/dist/admin/future/utils/buildDragSetFromSelection.mjs.map +1 -1
  125. package/dist/admin/future/utils/formatMoveSuccessMessage.js +12 -1
  126. package/dist/admin/future/utils/formatMoveSuccessMessage.js.map +1 -1
  127. package/dist/admin/future/utils/formatMoveSuccessMessage.mjs +12 -1
  128. package/dist/admin/future/utils/formatMoveSuccessMessage.mjs.map +1 -1
  129. package/dist/admin/future/utils/getRelationId.js +24 -0
  130. package/dist/admin/future/utils/getRelationId.js.map +1 -0
  131. package/dist/admin/future/utils/getRelationId.mjs +22 -0
  132. package/dist/admin/future/utils/getRelationId.mjs.map +1 -0
  133. package/dist/admin/future/utils/isEventFromWithin.js +14 -0
  134. package/dist/admin/future/utils/isEventFromWithin.js.map +1 -0
  135. package/dist/admin/future/utils/isEventFromWithin.mjs +12 -0
  136. package/dist/admin/future/utils/isEventFromWithin.mjs.map +1 -0
  137. package/dist/admin/future/utils/itemLocations.js +40 -0
  138. package/dist/admin/future/utils/itemLocations.js.map +1 -0
  139. package/dist/admin/future/utils/itemLocations.mjs +36 -0
  140. package/dist/admin/future/utils/itemLocations.mjs.map +1 -0
  141. package/dist/admin/future/utils/itemSource.js +36 -0
  142. package/dist/admin/future/utils/itemSource.js.map +1 -0
  143. package/dist/admin/future/utils/itemSource.mjs +32 -0
  144. package/dist/admin/future/utils/itemSource.mjs.map +1 -0
  145. package/dist/admin/index.js +11 -17
  146. package/dist/admin/index.js.map +1 -1
  147. package/dist/admin/index.mjs +12 -18
  148. package/dist/admin/index.mjs.map +1 -1
  149. package/dist/admin/src/future/App.d.ts +1 -1
  150. package/dist/admin/src/future/components/BetaNotice.d.ts +11 -0
  151. package/dist/admin/src/future/components/Drawer.d.ts +5 -1
  152. package/dist/admin/src/future/hooks/useAIMetadataEnabled.d.ts +26 -0
  153. package/dist/admin/src/future/pages/Assets/components/ActionsMenuContent.d.ts +33 -0
  154. package/dist/admin/src/future/pages/Assets/components/AssetActionsMenu.d.ts +24 -0
  155. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.d.ts +16 -0
  156. package/dist/admin/src/future/pages/Assets/components/BulkActionsBar.d.ts +8 -1
  157. package/dist/admin/src/future/pages/Assets/components/BulkMoveDialog.d.ts +17 -6
  158. package/dist/admin/src/future/pages/Assets/components/BusyOverlay.d.ts +9 -0
  159. package/dist/admin/src/future/pages/Assets/components/DeleteItemsDialog.d.ts +27 -0
  160. package/dist/admin/src/future/pages/Assets/components/Dnd/AssetsDndProvider.d.ts +8 -1
  161. package/dist/admin/src/future/pages/Assets/components/Dnd/buildDragSet.d.ts +18 -3
  162. package/dist/admin/src/future/pages/Assets/components/Dnd/useAssetDnd.d.ts +2 -1
  163. package/dist/admin/src/future/pages/Assets/components/FilterBadges.d.ts +3 -1
  164. package/dist/admin/src/future/pages/Assets/components/FilterMenu.d.ts +4 -0
  165. package/dist/admin/src/future/pages/Assets/components/FolderActionsMenu.d.ts +20 -0
  166. package/dist/admin/src/future/pages/Assets/components/FolderFormDialog.d.ts +16 -0
  167. package/dist/admin/src/future/pages/Assets/hooks/useBusyAssets.d.ts +38 -0
  168. package/dist/admin/src/future/pages/Assets/hooks/useInfiniteAssets.d.ts +20 -6
  169. package/dist/admin/src/future/pages/Assets/hooks/useInfiniteScrollSentinel.d.ts +41 -0
  170. package/dist/admin/src/future/services/folders.d.ts +6 -1
  171. package/dist/admin/src/future/utils/buildDragSetFromSelection.d.ts +4 -3
  172. package/dist/admin/src/future/utils/formatMoveSuccessMessage.d.ts +7 -1
  173. package/dist/admin/src/future/utils/getRelationId.d.ts +11 -0
  174. package/dist/admin/src/future/utils/isEventFromWithin.d.ts +11 -0
  175. package/dist/admin/src/future/utils/itemLocations.d.ts +31 -0
  176. package/dist/admin/src/future/utils/itemSource.d.ts +22 -0
  177. package/dist/admin/translations/en.json.js +25 -4
  178. package/dist/admin/translations/en.json.js.map +1 -1
  179. package/dist/admin/translations/en.json.mjs +25 -4
  180. package/dist/admin/translations/en.json.mjs.map +1 -1
  181. package/dist/server/services/image-manipulation.js.map +1 -1
  182. package/dist/server/services/image-manipulation.mjs.map +1 -1
  183. package/dist/server/src/services/image-manipulation.d.ts +0 -5
  184. package/dist/server/src/services/image-manipulation.d.ts.map +1 -1
  185. package/package.json +9 -9
  186. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.js.map +0 -1
  187. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.mjs.map +0 -1
  188. package/dist/admin/future/pages/Assets/components/FolderTree/filterFolderTree.js +0 -33
  189. package/dist/admin/future/pages/Assets/components/FolderTree/filterFolderTree.js.map +0 -1
  190. package/dist/admin/future/pages/Assets/components/FolderTree/filterFolderTree.mjs +0 -31
  191. package/dist/admin/future/pages/Assets/components/FolderTree/filterFolderTree.mjs.map +0 -1
  192. package/dist/admin/src/future/pages/Assets/components/CreateFolderDialog.d.ts +0 -8
  193. package/dist/admin/src/future/pages/Assets/components/FolderTree/filterFolderTree.d.ts +0 -16
@@ -1,46 +1,60 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
2
  import { useEffect, useMemo, useState, useRef, useCallback } from 'react';
3
3
  import * as ToggleGroup from '@radix-ui/react-toggle-group';
4
- import { usePersistentState, Layouts, useElementOnScreen } from '@strapi/admin/strapi-admin';
5
- import { Box, VisuallyHidden, Flex, SimpleMenu, MenuItem, Loader, Typography } from '@strapi/design-system';
6
- import { List, GridFour, Folder, Files, Link, ChevronDown } from '@strapi/icons';
4
+ import { usePersistentState, useElementOnScreen, Layouts, Page } from '@strapi/admin/strapi-admin';
5
+ import { Box, VisuallyHidden, Typography, SimpleMenu, MenuItem, Flex, Loader } from '@strapi/design-system';
6
+ import { Folder, Files, Link, ChevronDown, List, GridFour } from '@strapi/icons';
7
7
  import { useIntl } from 'react-intl';
8
- import { styled } from 'styled-components';
9
- import { useAIAvailability } from '../../../hooks/useAiAvailability.mjs';
8
+ import { css, styled } from 'styled-components';
9
+ import { BetaNotice } from '../../components/BetaNotice.mjs';
10
+ import { useAIMetadataEnabled } from '../../hooks/useAIMetadataEnabled.mjs';
10
11
  import { useMediaLibraryPermissions } from '../../hooks/useMediaLibraryPermissions.mjs';
11
12
  import { useUploadFilesMutation, useUploadFromUrlsMutation } from '../../services/api.mjs';
12
13
  import { useGetFolderQuery, useGetFoldersQuery } from '../../services/folders.mjs';
13
14
  import { useGetUploadSettingsQuery } from '../../services/settings.mjs';
15
+ import { buildItemLocations } from '../../utils/itemLocations.mjs';
14
16
  import { getTranslationKey } from '../../utils/translations.mjs';
15
17
  import { useAssetDetailsParam, AssetDetailsDrawer } from './components/AssetDetails/AssetDetailsDrawer.mjs';
16
18
  import { AssetsGrid } from './components/AssetsGrid.mjs';
17
19
  import { AssetsSearchInput } from './components/AssetsSearchInput.mjs';
18
20
  import { AssetsTable } from './components/AssetsTable.mjs';
19
21
  import { BulkActionsBar } from './components/BulkActionsBar.mjs';
20
- import { CreateFolderDialog } from './components/CreateFolderDialog.mjs';
21
22
  import { AssetsDndProvider } from './components/Dnd/AssetsDndProvider.mjs';
22
23
  import { DropZoneWithOverlay, DropFilesMessage } from './components/DropZone/UploadDropZone.mjs';
23
24
  import { UploadDropZoneProvider } from './components/DropZone/UploadDropZoneContext.mjs';
24
25
  import { FilteredEmptyState, EmptyState } from './components/EmptyState.mjs';
25
26
  import { FilterBadges } from './components/FilterBadges.mjs';
26
27
  import { FilterMenu } from './components/FilterMenu.mjs';
28
+ import { FolderFormDialog } from './components/FolderFormDialog.mjs';
27
29
  import { FolderTree } from './components/FolderTree/FolderTree.mjs';
28
30
  import { ImportFromUrlDialog } from './components/ImportFromUrlDialog.mjs';
29
31
  import { SortMenu } from './components/SortMenu.mjs';
30
32
  import { localStorageKeys, viewOptions } from './constants.mjs';
31
33
  import { useAssetSearch } from './hooks/useAssetSearch.mjs';
32
34
  import { AssetSelectionProvider, useAssetSelection } from './hooks/useAssetSelection.mjs';
35
+ import { BusyAssetsProvider } from './hooks/useBusyAssets.mjs';
33
36
  import { useFolderInfo } from './hooks/useFolderInfo.mjs';
34
37
  import { useFolderNavigation } from './hooks/useFolderNavigation.mjs';
35
38
  import { useInfiniteAssets } from './hooks/useInfiniteAssets.mjs';
39
+ import { useInfiniteScrollSentinel } from './hooks/useInfiniteScrollSentinel.mjs';
36
40
  import { useListFilters } from './hooks/useListFilters.mjs';
37
41
  import { useListSort } from './hooks/useListSort.mjs';
38
42
  import { buildAssetFilters } from './utils/buildAssetFilters.mjs';
39
43
  import { getListQueryKey } from './utils/listQueryKey.mjs';
40
44
  import { mergeMixedList } from './utils/mergeMixedList.mjs';
41
45
 
46
+ // The negative bottom margin shrinks the trigger area by a pixel so the 1px
47
+ // sentinel sitting exactly at the fold (when a page's rows happen to fill the
48
+ // viewport) reads as "not visible" and doesn't pull an extra page. It still
49
+ // triggers as soon as the sentinel scrolls a hair into view. Tune the bottom
50
+ // value if the list over- or under-fetches near the fold.
42
51
  const INTERSECTION_OPTIONS = {
43
- threshold: 0.1
52
+ threshold: 0,
53
+ rootMargin: '0px 0px -1px 0px'
54
+ };
55
+ // Module-level so the header sentinel's observer isn't rebuilt every render.
56
+ const HEADER_SENTINEL_OPTIONS = {
57
+ threshold: 0
44
58
  };
45
59
  const ITEM_COUNT_MESSAGE = {
46
60
  id: getTranslationKey('header.content.item-count'),
@@ -69,7 +83,7 @@ const getSearchResultsCountMessage = (numberFolders, numberAssets)=>{
69
83
  }
70
84
  return SEARCH_RESULTS_COUNT_MESSAGES.both;
71
85
  };
72
- const AssetsView = ({ view, folders, isLoadingFolders, assets, isLoadingAssets, isFetchingMore, hasNextPage, fetchNextPage, error, searchQuery, assetsSort, foldersPosition, hasActiveFilters, onClearFilters, onAssetItemClick, onAddAssets, canAddAssets, onClearSearch })=>{
86
+ const AssetsView = ({ view, folders, isLoadingFolders, assets, isLoadingAssets, isFetchingMore, hasNextPage, fetchNextPage, error, locations, searchQuery, assetsSort, foldersPosition, hasActiveFilters, onClearFilters, onAssetItemClick, onAddAssets, canAddAssets, onClearSearch })=>{
73
87
  const { formatMessage } = useIntl();
74
88
  const isGridView = view === viewOptions.GRID;
75
89
  const isLoading = isLoadingAssets || isLoadingFolders;
@@ -89,15 +103,12 @@ const AssetsView = ({ view, folders, isLoadingFolders, assets, isLoadingAssets,
89
103
  assetsSort,
90
104
  hasNextPage
91
105
  ]);
92
- const loadMoreRef = useElementOnScreen(useCallback((isVisible)=>{
93
- if (isVisible && hasNextPage && !isFetchingMore) {
94
- fetchNextPage();
95
- }
96
- }, [
106
+ const loadMoreRef = useInfiniteScrollSentinel({
97
107
  hasNextPage,
98
108
  isFetchingMore,
99
- fetchNextPage
100
- ]), INTERSECTION_OPTIONS);
109
+ onLoadMore: fetchNextPage,
110
+ options: INTERSECTION_OPTIONS
111
+ });
101
112
  if (isLoading) {
102
113
  return /*#__PURE__*/ jsx(Flex, {
103
114
  justifyContent: "center",
@@ -163,7 +174,8 @@ const AssetsView = ({ view, folders, isLoadingFolders, assets, isLoadingAssets,
163
174
  })
164
175
  }),
165
176
  /*#__PURE__*/ jsx(BulkActionsBar, {
166
- assets: assets
177
+ assets: assets,
178
+ locations: locations
167
179
  })
168
180
  ]
169
181
  });
@@ -223,13 +235,122 @@ const StyledToggleItem = styled(ToggleGroup.Item)`
223
235
  height: 1.6rem;
224
236
  }
225
237
  `;
226
- const HeaderWrapper = styled(Box)`
227
- [data-strapi-header] {
228
- background: ${({ theme })=>theme.colors.neutral0};
238
+ // A single sticky header — same layout scrolled or not, so the toolbar
239
+ // alignment can't reflow the way the shared sticky header did. Once the list
240
+ // scrolls under it, it shrinks (reduced vertical padding + a shadow to lift it
241
+ // off the rows), like the sticky headers elsewhere in the admin. Horizontal
242
+ // padding stays responsive (passed as props); only the vertical rhythm + shadow
243
+ // change here so they can animate.
244
+ const StickyHeader = styled(Box)`
245
+ position: sticky;
246
+ top: 0;
247
+ z-index: 2;
248
+ /* Transparent at rest (the grey page shows through); an opaque background +
249
+ shadow appear only once it sticks and content scrolls under it. */
250
+ background: transparent;
251
+ /* Horizontal padding matches the list's default spacing (Layouts.Content /
252
+ RESPONSIVE_DEFAULT_SPACING: 4 / 6 / 10) so the header lines up with the rows. */
253
+ padding: ${({ theme })=>`${theme.spaces[6]} ${theme.spaces[4]}`};
254
+ transition:
255
+ padding 0.2s ease,
256
+ background 0.2s ease,
257
+ box-shadow 0.2s ease;
229
258
 
230
- h1 {
231
- font-size: 1.8rem;
232
- }
259
+ ${({ theme })=>theme.breakpoints.medium} {
260
+ padding-left: ${({ theme })=>theme.spaces[6]};
261
+ padding-right: ${({ theme })=>theme.spaces[6]};
262
+ }
263
+ ${({ theme })=>theme.breakpoints.large} {
264
+ padding-left: ${({ theme })=>theme.spaces[10]};
265
+ padding-right: ${({ theme })=>theme.spaces[10]};
266
+ }
267
+
268
+ /* Compacting is scoped to medium and up, where the header actually sticks. On
269
+ mobile it scrolls away with the list, so shrinking it mid-scroll animated a
270
+ header the user could no longer see — the transition read as a glitch on the
271
+ way back up rather than as the header settling. */
272
+ ${({ $compact, theme })=>$compact && css`
273
+ ${theme.breakpoints.medium} {
274
+ padding-top: ${theme.spaces[3]};
275
+ padding-bottom: ${theme.spaces[3]};
276
+ padding-left: ${theme.spaces[4]};
277
+ padding-right: ${theme.spaces[4]};
278
+ background: ${theme.colors.neutral0};
279
+ box-shadow: ${theme.shadows.tableShadow};
280
+ }
281
+ ${theme.breakpoints.large} {
282
+ padding-left: ${theme.spaces[6]};
283
+ padding-right: ${theme.spaces[6]};
284
+ }
285
+ `}
286
+ `;
287
+ // New button aligns with the title, not the toolbar.
288
+ const TitleRow = styled(Flex)`
289
+ justify-content: space-between;
290
+ align-items: flex-start;
291
+ gap: ${({ theme })=>theme.spaces[4]};
292
+
293
+ h1 {
294
+ font-size: 1.8rem;
295
+ }
296
+ `;
297
+ // Two groups in source order (Filter, Search / Sort, Toggle). Mobile: stacked —
298
+ // Filter+Search on row 1, Sort+Toggle on row 2. Desktop (large+): one row,
299
+ // Filter+Search on the left, Sort+Toggle pushed right.
300
+ const Toolbar = styled(Flex)`
301
+ margin-top: ${({ theme })=>theme.spaces[5]};
302
+ flex-direction: column;
303
+ align-items: stretch;
304
+ gap: ${({ theme })=>theme.spaces[3]};
305
+ transition: margin-top 0.2s ease;
306
+
307
+ /* Tightening the gap to the title belongs to the compact header, so it is
308
+ scoped to the breakpoints that compact. On mobile the header never sticks,
309
+ and this was the last thing still shifting as the page scrolled. */
310
+ ${({ $compact, theme })=>$compact && css`
311
+ ${theme.breakpoints.medium} {
312
+ margin-top: ${theme.spaces[2]};
313
+ }
314
+ `}
315
+
316
+ ${({ theme })=>theme.breakpoints.large} {
317
+ flex-direction: row;
318
+ align-items: center;
319
+ justify-content: space-between;
320
+ }
321
+ `;
322
+ const ToolbarGroup = styled(Flex)`
323
+ align-items: center;
324
+ gap: ${({ theme })=>theme.spaces[3]};
325
+ `;
326
+ // Filter + Search. On mobile the group fills the row (search grows into it);
327
+ // on desktop it keeps its intrinsic size and sits at the left edge.
328
+ const FilterSearchGroup = styled(ToolbarGroup)``;
329
+ // Sort + Toggle. Mobile: spread across the row (Sort left, Toggle right).
330
+ // Desktop: sit together at the right edge (space-between on the toolbar leaves
331
+ // the empty space between the search and here).
332
+ const SortToggleGroup = styled(ToolbarGroup)`
333
+ justify-content: space-between;
334
+
335
+ ${({ theme })=>theme.breakpoints.large} {
336
+ justify-content: flex-end;
337
+ flex: 0 0 auto;
338
+ }
339
+ `;
340
+ // Fills the row on mobile; intrinsic width on desktop.
341
+ const SearchSlot = styled(Box)`
342
+ flex: 1;
343
+
344
+ ${({ theme })=>theme.breakpoints.large} {
345
+ flex: 0 1 auto;
346
+ }
347
+ `;
348
+ // Toggle labels are hidden below desktop — icons only (buttons keep aria-label).
349
+ const ToggleLabel = styled.span`
350
+ display: none;
351
+
352
+ ${({ theme })=>theme.breakpoints.large} {
353
+ display: inline;
233
354
  }
234
355
  `;
235
356
  const AssetsPage = ()=>{
@@ -262,7 +383,7 @@ const AssetsPage = ()=>{
262
383
  [
263
384
  listFilters.serialized
264
385
  ]);
265
- const { assets, pagination, isLoading: isLoadingAssets, isFetchingMore, hasNextPage, fetchNextPage, error: assetsError } = useInfiniteAssets({
386
+ const { assets, subscribers: assetPageSubscribers, pagination, isLoading: isLoadingAssets, isFetchingMore, hasNextPage, fetchNextPage, error: assetsError } = useInfiniteAssets({
266
387
  // The real folder is passed even while searching: the service drops the folder
267
388
  // filter when `search` is set, so results are still global. List filters
268
389
  // compose with both modes.
@@ -270,7 +391,6 @@ const AssetsPage = ()=>{
270
391
  search: searchQuery || undefined,
271
392
  sort: listSort.assetsSort,
272
393
  filters: builtFilters.fileClauses,
273
- filtersKey: listFilters.serialized,
274
394
  enabled: builtFilters.showFiles
275
395
  });
276
396
  const { data: fetchedFolders = [], isLoading: isLoadingFolders } = useGetFoldersQuery({
@@ -286,6 +406,13 @@ const AssetsPage = ()=>{
286
406
  builtFilters.showFolders,
287
407
  fetchedFolders
288
408
  ]);
409
+ // Both move affordances (drag and the bulk bar) resolve each item's parent
410
+ // from the rows on screen — while searching, results are global and the
411
+ // folder currently open says nothing about where an item lives.
412
+ const itemLocations = useMemo(()=>buildItemLocations(assets, folders), [
413
+ assets,
414
+ folders
415
+ ]);
289
416
  const itemCountLabel = formatMessage(ITEM_COUNT_MESSAGE, {
290
417
  count: itemCount
291
418
  });
@@ -321,6 +448,12 @@ const AssetsPage = ()=>{
321
448
  // Refs
322
449
  const fileInputRef = useRef(null);
323
450
  const uploadDropZoneRef = useRef(null);
451
+ // Shrink the sticky header once the list scrolls beneath it. A zero-height
452
+ // sentinel sits at the very top of the scroll content; when it leaves the
453
+ // viewport we're scrolled → compact.
454
+ const [isHeaderCompact, setIsHeaderCompact] = useState(false);
455
+ const onHeaderSentinelVisibility = useCallback((isVisible)=>setIsHeaderCompact(!isVisible), []);
456
+ const headerSentinelRef = useElementOnScreen(onHeaderSentinelVisibility, HEADER_SENTINEL_OPTIONS);
324
457
  // Upload handlers
325
458
  const [uploadFiles] = useUploadFilesMutation();
326
459
  const [uploadFromUrls] = useUploadFromUrlsMutation();
@@ -329,8 +462,10 @@ const AssetsPage = ()=>{
329
462
  // server asked for.
330
463
  const { data: settings } = useGetUploadSettingsQuery();
331
464
  const concurrency = settings?.data?.concurrentUploadRequests ?? 1;
332
- // Drives the post-upload AI metadata phase shown per row in the progress dialog.
333
- const { isEnabled: isAiMetadataEnabled } = useAIAvailability();
465
+ // Drives the post-upload AI metadata phase shown per row in the progress
466
+ // dialog. No mime argument: the files are not known until they are picked,
467
+ // and the server filters the batch on the same allowlist anyway.
468
+ const isAiMetadataEnabled = useAIMetadataEnabled();
334
469
  const uploadFilesToFolder = async (files, folderId)=>{
335
470
  if (files.length === 0) return;
336
471
  const formData = new FormData();
@@ -399,93 +534,105 @@ const AssetsPage = ()=>{
399
534
  disabled: !canCreate,
400
535
  children: /*#__PURE__*/ jsx(AssetSelectionProvider, {
401
536
  disabled: !canUpdate,
402
- children: /*#__PURE__*/ jsxs(AssetsDndProvider, {
403
- children: [
404
- /*#__PURE__*/ jsx(ClearSelectionOnChange, {
405
- listQueryKey: listQueryKey
406
- }),
407
- /*#__PURE__*/ jsx(Box, {
408
- ref: uploadDropZoneRef,
409
- children: /*#__PURE__*/ jsxs(Layouts.Root, {
410
- minHeight: "100vh",
411
- background: "neutral0",
537
+ children: /*#__PURE__*/ jsx(BusyAssetsProvider, {
538
+ children: /*#__PURE__*/ jsxs(AssetsDndProvider, {
539
+ locations: itemLocations,
540
+ children: [
541
+ /*#__PURE__*/ jsx(ClearSelectionOnChange, {
542
+ listQueryKey: listQueryKey
543
+ }),
544
+ /*#__PURE__*/ jsx(Layouts.Root, {
412
545
  sideNav: /*#__PURE__*/ jsx(FolderTree, {
413
546
  currentFolderId: currentFolderId,
414
547
  showActiveFolder: !isSearching,
415
548
  onSelectFolder: navigateToFolderId
416
549
  }),
417
- children: [
418
- /*#__PURE__*/ jsx(VisuallyHidden, {
419
- children: /*#__PURE__*/ jsx("input", {
420
- type: "file",
421
- ref: fileInputRef,
422
- onChange: handleFileChange,
423
- multiple: true
424
- })
425
- }),
426
- /*#__PURE__*/ jsx(HeaderWrapper, {
427
- children: /*#__PURE__*/ jsx(Layouts.Header, {
428
- title: pageHeaderTitle,
429
- primaryAction: canCreate && /*#__PURE__*/ jsxs(SimpleMenu, {
430
- popoverPlacement: "bottom-end",
431
- variant: "default",
432
- endIcon: /*#__PURE__*/ jsx(ChevronDown, {}),
433
- label: formatMessage({
434
- id: getTranslationKey('new'),
435
- defaultMessage: 'New'
436
- }),
437
- children: [
438
- /*#__PURE__*/ jsx(MenuItem, {
439
- onSelect: ()=>setIsCreateFolderDialogOpen(true),
440
- startIcon: /*#__PURE__*/ jsx(Folder, {}),
441
- children: formatMessage({
442
- id: getTranslationKey('folder.create.title'),
443
- defaultMessage: 'New folder'
444
- })
445
- }),
446
- /*#__PURE__*/ jsx(MenuItem, {
447
- onSelect: handleFileSelect,
448
- startIcon: /*#__PURE__*/ jsx(Files, {}),
449
- children: formatMessage({
450
- id: getTranslationKey('import-files'),
451
- defaultMessage: 'Import files'
452
- })
453
- }),
454
- /*#__PURE__*/ jsx(MenuItem, {
455
- onSelect: ()=>setIsUrlDialogOpen(true),
456
- startIcon: /*#__PURE__*/ jsx(Link, {}),
457
- children: formatMessage({
458
- id: getTranslationKey('import-from-url'),
459
- defaultMessage: 'Import from URL'
460
- })
461
- })
462
- ]
550
+ children: /*#__PURE__*/ jsx(Page.Main, {
551
+ children: /*#__PURE__*/ jsxs(Box, {
552
+ ref: uploadDropZoneRef,
553
+ children: [
554
+ /*#__PURE__*/ jsx(VisuallyHidden, {
555
+ children: /*#__PURE__*/ jsx("input", {
556
+ type: "file",
557
+ ref: fileInputRef,
558
+ onChange: handleFileChange,
559
+ multiple: true
560
+ })
463
561
  }),
464
- subtitle: /*#__PURE__*/ jsxs(Fragment, {
562
+ /*#__PURE__*/ jsx(Box, {
563
+ ref: headerSentinelRef,
564
+ height: 0,
565
+ "aria-hidden": true
566
+ }),
567
+ /*#__PURE__*/ jsxs(StickyHeader, {
568
+ $compact: isHeaderCompact,
465
569
  children: [
466
- /*#__PURE__*/ jsxs(Flex, {
467
- justifyContent: "space-between",
468
- alignItems: "center",
469
- gap: 4,
470
- width: "100%",
570
+ /*#__PURE__*/ jsxs(TitleRow, {
571
+ children: [
572
+ /*#__PURE__*/ jsx(Typography, {
573
+ variant: "alpha",
574
+ tag: "h1",
575
+ children: pageHeaderTitle
576
+ }),
577
+ canCreate && /*#__PURE__*/ jsxs(SimpleMenu, {
578
+ popoverPlacement: "bottom-end",
579
+ variant: "default",
580
+ endIcon: /*#__PURE__*/ jsx(ChevronDown, {}),
581
+ label: formatMessage({
582
+ id: getTranslationKey('new'),
583
+ defaultMessage: 'New'
584
+ }),
585
+ children: [
586
+ /*#__PURE__*/ jsx(MenuItem, {
587
+ onSelect: ()=>setIsCreateFolderDialogOpen(true),
588
+ startIcon: /*#__PURE__*/ jsx(Folder, {}),
589
+ children: formatMessage({
590
+ id: getTranslationKey('folder.create.title'),
591
+ defaultMessage: 'New folder'
592
+ })
593
+ }),
594
+ /*#__PURE__*/ jsx(MenuItem, {
595
+ onSelect: handleFileSelect,
596
+ startIcon: /*#__PURE__*/ jsx(Files, {}),
597
+ children: formatMessage({
598
+ id: getTranslationKey('import-files'),
599
+ defaultMessage: 'Import files'
600
+ })
601
+ }),
602
+ /*#__PURE__*/ jsx(MenuItem, {
603
+ onSelect: ()=>setIsUrlDialogOpen(true),
604
+ startIcon: /*#__PURE__*/ jsx(Link, {}),
605
+ children: formatMessage({
606
+ id: getTranslationKey('import-from-url'),
607
+ defaultMessage: 'Import from URL'
608
+ })
609
+ })
610
+ ]
611
+ })
612
+ ]
613
+ }),
614
+ /*#__PURE__*/ jsxs(Toolbar, {
615
+ $compact: isHeaderCompact,
471
616
  children: [
472
- /*#__PURE__*/ jsxs(Flex, {
473
- gap: 4,
474
- alignItems: "center",
617
+ /*#__PURE__*/ jsxs(FilterSearchGroup, {
475
618
  children: [
476
- /*#__PURE__*/ jsx(FilterMenu, {
477
- listFilters: listFilters
619
+ /*#__PURE__*/ jsx(Box, {
620
+ children: /*#__PURE__*/ jsx(FilterMenu, {
621
+ listFilters: listFilters
622
+ })
478
623
  }),
479
- /*#__PURE__*/ jsx(AssetsSearchInput, {})
624
+ /*#__PURE__*/ jsx(SearchSlot, {
625
+ children: /*#__PURE__*/ jsx(AssetsSearchInput, {})
626
+ })
480
627
  ]
481
628
  }),
482
- /*#__PURE__*/ jsxs(Flex, {
483
- gap: 4,
484
- alignItems: "center",
629
+ /*#__PURE__*/ jsxs(SortToggleGroup, {
485
630
  children: [
486
- /*#__PURE__*/ jsx(SortMenu, {
487
- sort: listSort,
488
- showFoldersGroup: !isGridView
631
+ /*#__PURE__*/ jsx(Box, {
632
+ children: /*#__PURE__*/ jsx(SortMenu, {
633
+ sort: listSort,
634
+ showFoldersGroup: !isGridView
635
+ })
489
636
  }),
490
637
  /*#__PURE__*/ jsxs(StyledToggleGroup, {
491
638
  type: "single",
@@ -504,9 +651,11 @@ const AssetsPage = ()=>{
504
651
  }),
505
652
  children: [
506
653
  /*#__PURE__*/ jsx(List, {}),
507
- formatMessage({
508
- id: getTranslationKey('view.table'),
509
- defaultMessage: 'Table view'
654
+ /*#__PURE__*/ jsx(ToggleLabel, {
655
+ children: formatMessage({
656
+ id: getTranslationKey('view.table'),
657
+ defaultMessage: 'Table view'
658
+ })
510
659
  })
511
660
  ]
512
661
  }),
@@ -518,9 +667,11 @@ const AssetsPage = ()=>{
518
667
  }),
519
668
  children: [
520
669
  /*#__PURE__*/ jsx(GridFour, {}),
521
- formatMessage({
522
- id: getTranslationKey('view.grid'),
523
- defaultMessage: 'Grid view'
670
+ /*#__PURE__*/ jsx(ToggleLabel, {
671
+ children: formatMessage({
672
+ id: getTranslationKey('view.grid'),
673
+ defaultMessage: 'Grid view'
674
+ })
524
675
  })
525
676
  ]
526
677
  })
@@ -531,52 +682,59 @@ const AssetsPage = ()=>{
531
682
  ]
532
683
  }),
533
684
  /*#__PURE__*/ jsx(FilterBadges, {
534
- listFilters: listFilters
685
+ listFilters: listFilters,
686
+ compact: isHeaderCompact
687
+ })
688
+ ]
689
+ }),
690
+ /*#__PURE__*/ jsxs(Layouts.Content, {
691
+ children: [
692
+ /*#__PURE__*/ jsx(BetaNotice, {}),
693
+ assetPageSubscribers,
694
+ /*#__PURE__*/ jsxs(DropZoneWithOverlay, {
695
+ children: [
696
+ /*#__PURE__*/ jsx(DropFilesMessage, {
697
+ uploadDropZoneRef: uploadDropZoneRef,
698
+ folderName: title
699
+ }),
700
+ /*#__PURE__*/ jsx(AssetsView, {
701
+ view: view,
702
+ folders: folders,
703
+ isLoadingFolders: isLoadingFolders,
704
+ assets: assets,
705
+ isLoadingAssets: isLoadingAssets,
706
+ isFetchingMore: isFetchingMore,
707
+ hasNextPage: hasNextPage,
708
+ fetchNextPage: fetchNextPage,
709
+ error: assetsError,
710
+ locations: itemLocations,
711
+ searchQuery: searchQuery,
712
+ assetsSort: listSort.assetsSort,
713
+ foldersPosition: listSort.foldersPosition,
714
+ hasActiveFilters: listFilters.filters.length > 0,
715
+ onClearFilters: listFilters.clearFilters,
716
+ onAssetItemClick: openDetails,
717
+ onAddAssets: handleFileSelect,
718
+ canAddAssets: canCreate,
719
+ onClearSearch: clearSearch
720
+ })
721
+ ]
535
722
  })
536
723
  ]
537
724
  })
538
- })
539
- }),
540
- /*#__PURE__*/ jsx(Layouts.Content, {
541
- children: /*#__PURE__*/ jsxs(DropZoneWithOverlay, {
542
- children: [
543
- /*#__PURE__*/ jsx(DropFilesMessage, {
544
- uploadDropZoneRef: uploadDropZoneRef,
545
- folderName: title
546
- }),
547
- /*#__PURE__*/ jsx(AssetsView, {
548
- view: view,
549
- folders: folders,
550
- isLoadingFolders: isLoadingFolders,
551
- assets: assets,
552
- isLoadingAssets: isLoadingAssets,
553
- isFetchingMore: isFetchingMore,
554
- hasNextPage: hasNextPage,
555
- fetchNextPage: fetchNextPage,
556
- error: assetsError,
557
- searchQuery: searchQuery,
558
- assetsSort: listSort.assetsSort,
559
- foldersPosition: listSort.foldersPosition,
560
- hasActiveFilters: listFilters.filters.length > 0,
561
- onClearFilters: listFilters.clearFilters,
562
- onAssetItemClick: openDetails,
563
- onAddAssets: handleFileSelect,
564
- canAddAssets: canCreate,
565
- onClearSearch: clearSearch
566
- })
567
- ]
568
- })
725
+ ]
569
726
  })
570
- ]
727
+ })
571
728
  })
572
- })
573
- ]
729
+ ]
730
+ })
574
731
  })
575
732
  })
576
733
  }),
577
- /*#__PURE__*/ jsx(CreateFolderDialog, {
734
+ /*#__PURE__*/ jsx(FolderFormDialog, {
578
735
  open: isCreateFolderDialogOpen,
579
- folderName: title,
736
+ mode: "create",
737
+ parentFolderName: title,
580
738
  parentFolderId: currentFolderId,
581
739
  onClose: ()=>setIsCreateFolderDialogOpen(false)
582
740
  }),