@strapi/upload 5.51.1 → 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 (369) hide show
  1. package/dist/admin/components/AssetCard/VideoPreview.js +1 -1
  2. package/dist/admin/components/AssetCard/VideoPreview.js.map +1 -1
  3. package/dist/admin/components/AssetCard/VideoPreview.mjs +1 -1
  4. package/dist/admin/components/AssetCard/VideoPreview.mjs.map +1 -1
  5. package/dist/admin/components/TableList/TableList.js +1 -1
  6. package/dist/admin/components/TableList/TableList.js.map +1 -1
  7. package/dist/admin/components/TableList/TableList.mjs +1 -1
  8. package/dist/admin/components/TableList/TableList.mjs.map +1 -1
  9. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js +21 -4
  10. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js.map +1 -1
  11. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs +21 -4
  12. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs.map +1 -1
  13. package/dist/admin/future/App.js +3 -3
  14. package/dist/admin/future/App.js.map +1 -1
  15. package/dist/admin/future/App.mjs +4 -4
  16. package/dist/admin/future/App.mjs.map +1 -1
  17. package/dist/admin/future/components/BetaNotice.js +72 -0
  18. package/dist/admin/future/components/BetaNotice.js.map +1 -0
  19. package/dist/admin/future/components/BetaNotice.mjs +70 -0
  20. package/dist/admin/future/components/BetaNotice.mjs.map +1 -0
  21. package/dist/admin/future/components/Drawer.js +18 -3
  22. package/dist/admin/future/components/Drawer.js.map +1 -1
  23. package/dist/admin/future/components/Drawer.mjs +18 -3
  24. package/dist/admin/future/components/Drawer.mjs.map +1 -1
  25. package/dist/admin/future/components/UploadProgressDialog.js +200 -27
  26. package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
  27. package/dist/admin/future/components/UploadProgressDialog.mjs +203 -30
  28. package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
  29. package/dist/admin/future/hooks/useAIMetadataEnabled.js +39 -0
  30. package/dist/admin/future/hooks/useAIMetadataEnabled.js.map +1 -0
  31. package/dist/admin/future/hooks/useAIMetadataEnabled.mjs +37 -0
  32. package/dist/admin/future/hooks/useAIMetadataEnabled.mjs.map +1 -0
  33. package/dist/admin/future/hooks/useMediaLibraryPermissions.js +35 -0
  34. package/dist/admin/future/hooks/useMediaLibraryPermissions.js.map +1 -0
  35. package/dist/admin/future/hooks/useMediaLibraryPermissions.mjs +33 -0
  36. package/dist/admin/future/hooks/useMediaLibraryPermissions.mjs.map +1 -0
  37. package/dist/admin/future/pages/Assets/AssetsPage.js +453 -170
  38. package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
  39. package/dist/admin/future/pages/Assets/AssetsPage.mjs +454 -171
  40. package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
  41. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.js +46 -0
  42. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.js.map +1 -0
  43. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.mjs +44 -0
  44. package/dist/admin/future/pages/Assets/components/ActionsMenuContent.mjs.map +1 -0
  45. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js +325 -0
  46. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js.map +1 -0
  47. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs +323 -0
  48. package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs.map +1 -0
  49. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js +110 -46
  50. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
  51. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +110 -46
  52. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
  53. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +95 -67
  54. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
  55. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +95 -68
  56. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
  57. package/dist/admin/future/pages/Assets/components/AssetsGrid.js +94 -26
  58. package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
  59. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +96 -28
  60. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
  61. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js +112 -0
  62. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js.map +1 -0
  63. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs +110 -0
  64. package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs.map +1 -0
  65. package/dist/admin/future/pages/Assets/components/AssetsTable.js +172 -56
  66. package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
  67. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +175 -59
  68. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
  69. package/dist/admin/future/pages/Assets/components/BulkActionsBar.js +188 -138
  70. package/dist/admin/future/pages/Assets/components/BulkActionsBar.js.map +1 -1
  71. package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs +191 -141
  72. package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs.map +1 -1
  73. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js +123 -76
  74. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js.map +1 -1
  75. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs +126 -79
  76. package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs.map +1 -1
  77. package/dist/admin/future/pages/Assets/components/BusyOverlay.js +36 -0
  78. package/dist/admin/future/pages/Assets/components/BusyOverlay.js.map +1 -0
  79. package/dist/admin/future/pages/Assets/components/BusyOverlay.mjs +34 -0
  80. package/dist/admin/future/pages/Assets/components/BusyOverlay.mjs.map +1 -0
  81. package/dist/admin/future/pages/Assets/components/DateRangeCalendar.js +193 -0
  82. package/dist/admin/future/pages/Assets/components/DateRangeCalendar.js.map +1 -0
  83. package/dist/admin/future/pages/Assets/components/DateRangeCalendar.mjs +191 -0
  84. package/dist/admin/future/pages/Assets/components/DateRangeCalendar.mjs.map +1 -0
  85. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.js +131 -0
  86. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.js.map +1 -0
  87. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.mjs +129 -0
  88. package/dist/admin/future/pages/Assets/components/DeleteItemsDialog.mjs.map +1 -0
  89. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js +45 -18
  90. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js.map +1 -1
  91. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs +46 -19
  92. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs.map +1 -1
  93. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js +28 -31
  94. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js.map +1 -1
  95. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs +28 -31
  96. package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs.map +1 -1
  97. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js +10 -15
  98. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js.map +1 -1
  99. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs +10 -15
  100. package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs.map +1 -1
  101. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.js +13 -3
  102. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.js.map +1 -1
  103. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.mjs +13 -3
  104. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.mjs.map +1 -1
  105. package/dist/admin/future/pages/Assets/components/EmptyState.js +62 -3
  106. package/dist/admin/future/pages/Assets/components/EmptyState.js.map +1 -1
  107. package/dist/admin/future/pages/Assets/components/EmptyState.mjs +62 -4
  108. package/dist/admin/future/pages/Assets/components/EmptyState.mjs.map +1 -1
  109. package/dist/admin/future/pages/Assets/components/FilterBadges.js +417 -0
  110. package/dist/admin/future/pages/Assets/components/FilterBadges.js.map +1 -0
  111. package/dist/admin/future/pages/Assets/components/FilterBadges.mjs +415 -0
  112. package/dist/admin/future/pages/Assets/components/FilterBadges.mjs.map +1 -0
  113. package/dist/admin/future/pages/Assets/components/FilterMenu.js +453 -0
  114. package/dist/admin/future/pages/Assets/components/FilterMenu.js.map +1 -0
  115. package/dist/admin/future/pages/Assets/components/FilterMenu.mjs +447 -0
  116. package/dist/admin/future/pages/Assets/components/FilterMenu.mjs.map +1 -0
  117. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.js +144 -0
  118. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.js.map +1 -0
  119. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.mjs +142 -0
  120. package/dist/admin/future/pages/Assets/components/FolderActionsMenu.mjs.map +1 -0
  121. package/dist/admin/future/pages/Assets/components/{CreateFolderDialog.js → FolderFormDialog.js} +50 -16
  122. package/dist/admin/future/pages/Assets/components/FolderFormDialog.js.map +1 -0
  123. package/dist/admin/future/pages/Assets/components/{CreateFolderDialog.mjs → FolderFormDialog.mjs} +52 -18
  124. package/dist/admin/future/pages/Assets/components/FolderFormDialog.mjs.map +1 -0
  125. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js +125 -148
  126. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
  127. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs +127 -150
  128. package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
  129. package/dist/admin/future/pages/Assets/components/SortMenu.js +21 -32
  130. package/dist/admin/future/pages/Assets/components/SortMenu.js.map +1 -1
  131. package/dist/admin/future/pages/Assets/components/SortMenu.mjs +22 -33
  132. package/dist/admin/future/pages/Assets/components/SortMenu.mjs.map +1 -1
  133. package/dist/admin/future/pages/Assets/hooks/useAssetSearch.js +40 -0
  134. package/dist/admin/future/pages/Assets/hooks/useAssetSearch.js.map +1 -0
  135. package/dist/admin/future/pages/Assets/hooks/useAssetSearch.mjs +38 -0
  136. package/dist/admin/future/pages/Assets/hooks/useAssetSearch.mjs.map +1 -0
  137. package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js +21 -5
  138. package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js.map +1 -1
  139. package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs +21 -5
  140. package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs.map +1 -1
  141. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.js +47 -0
  142. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.js.map +1 -0
  143. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.mjs +44 -0
  144. package/dist/admin/future/pages/Assets/hooks/useBusyAssets.mjs.map +1 -0
  145. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js +33 -12
  146. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js.map +1 -1
  147. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs +33 -12
  148. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs.map +1 -1
  149. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js +222 -45
  150. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js.map +1 -1
  151. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs +223 -46
  152. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs.map +1 -1
  153. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.js +75 -0
  154. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.js.map +1 -0
  155. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.mjs +73 -0
  156. package/dist/admin/future/pages/Assets/hooks/useInfiniteScrollSentinel.mjs.map +1 -0
  157. package/dist/admin/future/pages/Assets/hooks/useListFilters.js +168 -0
  158. package/dist/admin/future/pages/Assets/hooks/useListFilters.js.map +1 -0
  159. package/dist/admin/future/pages/Assets/hooks/useListFilters.mjs +162 -0
  160. package/dist/admin/future/pages/Assets/hooks/useListFilters.mjs.map +1 -0
  161. package/dist/admin/future/pages/Assets/utils/buildAssetFilters.js +201 -0
  162. package/dist/admin/future/pages/Assets/utils/buildAssetFilters.js.map +1 -0
  163. package/dist/admin/future/pages/Assets/utils/buildAssetFilters.mjs +198 -0
  164. package/dist/admin/future/pages/Assets/utils/buildAssetFilters.mjs.map +1 -0
  165. package/dist/admin/future/pages/Assets/utils/listQueryKey.js +2 -2
  166. package/dist/admin/future/pages/Assets/utils/listQueryKey.js.map +1 -1
  167. package/dist/admin/future/pages/Assets/utils/listQueryKey.mjs +2 -2
  168. package/dist/admin/future/pages/Assets/utils/listQueryKey.mjs.map +1 -1
  169. package/dist/admin/future/services/api.js +187 -28
  170. package/dist/admin/future/services/api.js.map +1 -1
  171. package/dist/admin/future/services/api.mjs +187 -30
  172. package/dist/admin/future/services/api.mjs.map +1 -1
  173. package/dist/admin/future/services/assets.js +40 -17
  174. package/dist/admin/future/services/assets.js.map +1 -1
  175. package/dist/admin/future/services/assets.mjs +40 -17
  176. package/dist/admin/future/services/assets.mjs.map +1 -1
  177. package/dist/admin/future/services/folders.js +66 -19
  178. package/dist/admin/future/services/folders.js.map +1 -1
  179. package/dist/admin/future/services/folders.mjs +66 -20
  180. package/dist/admin/future/services/folders.mjs.map +1 -1
  181. package/dist/admin/future/services/settings.js +10 -3
  182. package/dist/admin/future/services/settings.js.map +1 -1
  183. package/dist/admin/future/services/settings.mjs +10 -3
  184. package/dist/admin/future/services/settings.mjs.map +1 -1
  185. package/dist/admin/future/store/uploadProgress.js +109 -2
  186. package/dist/admin/future/store/uploadProgress.js.map +1 -1
  187. package/dist/admin/future/store/uploadProgress.mjs +105 -3
  188. package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
  189. package/dist/admin/future/utils/buildDragSetFromSelection.js +7 -5
  190. package/dist/admin/future/utils/buildDragSetFromSelection.js.map +1 -1
  191. package/dist/admin/future/utils/buildDragSetFromSelection.mjs +7 -5
  192. package/dist/admin/future/utils/buildDragSetFromSelection.mjs.map +1 -1
  193. package/dist/admin/future/utils/formatMoveSuccessMessage.js +12 -1
  194. package/dist/admin/future/utils/formatMoveSuccessMessage.js.map +1 -1
  195. package/dist/admin/future/utils/formatMoveSuccessMessage.mjs +12 -1
  196. package/dist/admin/future/utils/formatMoveSuccessMessage.mjs.map +1 -1
  197. package/dist/admin/future/utils/getRelationId.js +24 -0
  198. package/dist/admin/future/utils/getRelationId.js.map +1 -0
  199. package/dist/admin/future/utils/getRelationId.mjs +22 -0
  200. package/dist/admin/future/utils/getRelationId.mjs.map +1 -0
  201. package/dist/admin/future/utils/isEventFromWithin.js +14 -0
  202. package/dist/admin/future/utils/isEventFromWithin.js.map +1 -0
  203. package/dist/admin/future/utils/isEventFromWithin.mjs +12 -0
  204. package/dist/admin/future/utils/isEventFromWithin.mjs.map +1 -0
  205. package/dist/admin/future/utils/itemLocations.js +40 -0
  206. package/dist/admin/future/utils/itemLocations.js.map +1 -0
  207. package/dist/admin/future/utils/itemLocations.mjs +36 -0
  208. package/dist/admin/future/utils/itemLocations.mjs.map +1 -0
  209. package/dist/admin/future/utils/itemSource.js +36 -0
  210. package/dist/admin/future/utils/itemSource.js.map +1 -0
  211. package/dist/admin/future/utils/itemSource.mjs +32 -0
  212. package/dist/admin/future/utils/itemSource.mjs.map +1 -0
  213. package/dist/admin/future/utils/searchQueryParam.js +11 -0
  214. package/dist/admin/future/utils/searchQueryParam.js.map +1 -0
  215. package/dist/admin/future/utils/searchQueryParam.mjs +9 -0
  216. package/dist/admin/future/utils/searchQueryParam.mjs.map +1 -0
  217. package/dist/admin/hooks/useSelectionState.js +2 -4
  218. package/dist/admin/hooks/useSelectionState.js.map +1 -1
  219. package/dist/admin/hooks/useSelectionState.mjs +2 -4
  220. package/dist/admin/hooks/useSelectionState.mjs.map +1 -1
  221. package/dist/admin/index.js +11 -17
  222. package/dist/admin/index.js.map +1 -1
  223. package/dist/admin/index.mjs +12 -18
  224. package/dist/admin/index.mjs.map +1 -1
  225. package/dist/admin/shared/constants.js +43 -0
  226. package/dist/admin/shared/constants.js.map +1 -0
  227. package/dist/admin/shared/constants.mjs +38 -0
  228. package/dist/admin/shared/constants.mjs.map +1 -0
  229. package/dist/admin/src/future/App.d.ts +1 -1
  230. package/dist/admin/src/future/components/BetaNotice.d.ts +11 -0
  231. package/dist/admin/src/future/components/Drawer.d.ts +5 -1
  232. package/dist/admin/src/future/hooks/useAIMetadataEnabled.d.ts +26 -0
  233. package/dist/admin/src/future/hooks/useMediaLibraryPermissions.d.ts +21 -0
  234. package/dist/admin/src/future/pages/Assets/components/ActionsMenuContent.d.ts +33 -0
  235. package/dist/admin/src/future/pages/Assets/components/AssetActionsMenu.d.ts +24 -0
  236. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetCropEditor.d.ts +7 -1
  237. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.d.ts +16 -0
  238. package/dist/admin/src/future/pages/Assets/components/AssetsSearchInput.d.ts +5 -0
  239. package/dist/admin/src/future/pages/Assets/components/BulkActionsBar.d.ts +21 -1
  240. package/dist/admin/src/future/pages/Assets/components/BulkMoveDialog.d.ts +17 -6
  241. package/dist/admin/src/future/pages/Assets/components/BusyOverlay.d.ts +9 -0
  242. package/dist/admin/src/future/pages/Assets/components/DateRangeCalendar.d.ts +8 -0
  243. package/dist/admin/src/future/pages/Assets/components/DeleteItemsDialog.d.ts +27 -0
  244. package/dist/admin/src/future/pages/Assets/components/Dnd/AssetsDndProvider.d.ts +8 -1
  245. package/dist/admin/src/future/pages/Assets/components/Dnd/buildDragSet.d.ts +18 -3
  246. package/dist/admin/src/future/pages/Assets/components/Dnd/useAssetDnd.d.ts +2 -1
  247. package/dist/admin/src/future/pages/Assets/components/DropZone/UploadDropZoneContext.d.ts +8 -1
  248. package/dist/admin/src/future/pages/Assets/components/EmptyState.d.ts +18 -1
  249. package/dist/admin/src/future/pages/Assets/components/FilterBadges.d.ts +8 -0
  250. package/dist/admin/src/future/pages/Assets/components/FilterMenu.d.ts +26 -0
  251. package/dist/admin/src/future/pages/Assets/components/FolderActionsMenu.d.ts +20 -0
  252. package/dist/admin/src/future/pages/Assets/components/FolderFormDialog.d.ts +16 -0
  253. package/dist/admin/src/future/pages/Assets/components/FolderTree/FolderTree.d.ts +13 -4
  254. package/dist/admin/src/future/pages/Assets/components/SortMenu.d.ts +6 -0
  255. package/dist/admin/src/future/pages/Assets/hooks/useAssetSearch.d.ts +12 -0
  256. package/dist/admin/src/future/pages/Assets/hooks/useAssetSelection.d.ts +8 -1
  257. package/dist/admin/src/future/pages/Assets/hooks/useBusyAssets.d.ts +38 -0
  258. package/dist/admin/src/future/pages/Assets/hooks/useInfiniteAssets.d.ts +27 -3
  259. package/dist/admin/src/future/pages/Assets/hooks/useInfiniteScrollSentinel.d.ts +41 -0
  260. package/dist/admin/src/future/pages/Assets/hooks/useListFilters.d.ts +69 -0
  261. package/dist/admin/src/future/pages/Assets/utils/buildAssetFilters.d.ts +46 -0
  262. package/dist/admin/src/future/pages/Assets/utils/listQueryKey.d.ts +3 -4
  263. package/dist/admin/src/future/services/api.d.ts +53 -5
  264. package/dist/admin/src/future/services/assets.d.ts +9 -1
  265. package/dist/admin/src/future/services/folders.d.ts +9 -1
  266. package/dist/admin/src/future/services/settings.d.ts +2 -2
  267. package/dist/admin/src/future/store/uploadProgress.d.ts +83 -1
  268. package/dist/admin/src/future/utils/buildDragSetFromSelection.d.ts +4 -3
  269. package/dist/admin/src/future/utils/formatMoveSuccessMessage.d.ts +7 -1
  270. package/dist/admin/src/future/utils/getRelationId.d.ts +11 -0
  271. package/dist/admin/src/future/utils/isEventFromWithin.d.ts +11 -0
  272. package/dist/admin/src/future/utils/itemLocations.d.ts +31 -0
  273. package/dist/admin/src/future/utils/itemSource.d.ts +22 -0
  274. package/dist/admin/src/future/utils/searchQueryParam.d.ts +7 -0
  275. package/dist/admin/src/hooks/useAiAvailability.d.ts +1 -1
  276. package/dist/admin/src/hooks/useEditAsset.d.ts +4 -4
  277. package/dist/admin/src/hooks/useEditFolder.d.ts +4 -4
  278. package/dist/admin/src/hooks/useSettings.d.ts +1 -0
  279. package/dist/admin/src/hooks/useTracking.d.ts +1 -1
  280. package/dist/admin/src/hooks/useUpload.d.ts +1 -1
  281. package/dist/admin/translations/en.json.js +73 -2
  282. package/dist/admin/translations/en.json.js.map +1 -1
  283. package/dist/admin/translations/en.json.mjs +73 -2
  284. package/dist/admin/translations/en.json.mjs.map +1 -1
  285. package/dist/admin/utils/urlYupSchema.js +33 -9
  286. package/dist/admin/utils/urlYupSchema.js.map +1 -1
  287. package/dist/admin/utils/urlYupSchema.mjs +33 -9
  288. package/dist/admin/utils/urlYupSchema.mjs.map +1 -1
  289. package/dist/server/config.js +13 -5
  290. package/dist/server/config.js.map +1 -1
  291. package/dist/server/config.mjs +13 -5
  292. package/dist/server/config.mjs.map +1 -1
  293. package/dist/server/constants.js.map +1 -1
  294. package/dist/server/constants.mjs.map +1 -1
  295. package/dist/server/controllers/admin-file.js +30 -0
  296. package/dist/server/controllers/admin-file.js.map +1 -1
  297. package/dist/server/controllers/admin-file.mjs +30 -0
  298. package/dist/server/controllers/admin-file.mjs.map +1 -1
  299. package/dist/server/controllers/admin-settings.js +20 -2
  300. package/dist/server/controllers/admin-settings.js.map +1 -1
  301. package/dist/server/controllers/admin-settings.mjs +21 -3
  302. package/dist/server/controllers/admin-settings.mjs.map +1 -1
  303. package/dist/server/controllers/content-api.js +1 -2
  304. package/dist/server/controllers/content-api.js.map +1 -1
  305. package/dist/server/controllers/content-api.mjs +2 -2
  306. package/dist/server/controllers/content-api.mjs.map +1 -1
  307. package/dist/server/controllers/validation/admin/ai-metadata.js +12 -0
  308. package/dist/server/controllers/validation/admin/ai-metadata.js.map +1 -0
  309. package/dist/server/controllers/validation/admin/ai-metadata.mjs +10 -0
  310. package/dist/server/controllers/validation/admin/ai-metadata.mjs.map +1 -0
  311. package/dist/server/controllers/validation/admin/settings.js +4 -1
  312. package/dist/server/controllers/validation/admin/settings.js.map +1 -1
  313. package/dist/server/controllers/validation/admin/settings.mjs +1 -1
  314. package/dist/server/controllers/validation/admin/settings.mjs.map +1 -1
  315. package/dist/server/routes/admin.js +18 -0
  316. package/dist/server/routes/admin.js.map +1 -1
  317. package/dist/server/routes/admin.mjs +18 -0
  318. package/dist/server/routes/admin.mjs.map +1 -1
  319. package/dist/server/services/ai-metadata.js +108 -11
  320. package/dist/server/services/ai-metadata.js.map +1 -1
  321. package/dist/server/services/ai-metadata.mjs +108 -11
  322. package/dist/server/services/ai-metadata.mjs.map +1 -1
  323. package/dist/server/services/file.js +86 -13
  324. package/dist/server/services/file.js.map +1 -1
  325. package/dist/server/services/file.mjs +87 -14
  326. package/dist/server/services/file.mjs.map +1 -1
  327. package/dist/server/services/image-manipulation.js +4 -4
  328. package/dist/server/services/image-manipulation.js.map +1 -1
  329. package/dist/server/services/image-manipulation.mjs +4 -4
  330. package/dist/server/services/image-manipulation.mjs.map +1 -1
  331. package/dist/server/shared/constants.js +43 -0
  332. package/dist/server/shared/constants.js.map +1 -0
  333. package/dist/server/shared/constants.mjs +38 -0
  334. package/dist/server/shared/constants.mjs.map +1 -0
  335. package/dist/server/src/config.d.ts +2 -0
  336. package/dist/server/src/config.d.ts.map +1 -1
  337. package/dist/server/src/constants.d.ts +2 -1
  338. package/dist/server/src/constants.d.ts.map +1 -1
  339. package/dist/server/src/controllers/admin-file.d.ts +10 -0
  340. package/dist/server/src/controllers/admin-file.d.ts.map +1 -1
  341. package/dist/server/src/controllers/admin-settings.d.ts.map +1 -1
  342. package/dist/server/src/controllers/index.d.ts +1 -0
  343. package/dist/server/src/controllers/index.d.ts.map +1 -1
  344. package/dist/server/src/controllers/validation/admin/ai-metadata.d.ts +2 -0
  345. package/dist/server/src/controllers/validation/admin/ai-metadata.d.ts.map +1 -0
  346. package/dist/server/src/controllers/validation/admin/settings.d.ts +1 -1
  347. package/dist/server/src/controllers/validation/admin/settings.d.ts.map +1 -1
  348. package/dist/server/src/index.d.ts +7 -1
  349. package/dist/server/src/index.d.ts.map +1 -1
  350. package/dist/server/src/routes/admin.d.ts.map +1 -1
  351. package/dist/server/src/services/ai-metadata.d.ts +19 -0
  352. package/dist/server/src/services/ai-metadata.d.ts.map +1 -1
  353. package/dist/server/src/services/file.d.ts +12 -2
  354. package/dist/server/src/services/file.d.ts.map +1 -1
  355. package/dist/server/src/services/image-manipulation.d.ts +0 -5
  356. package/dist/server/src/services/image-manipulation.d.ts.map +1 -1
  357. package/dist/server/src/services/index.d.ts +4 -1
  358. package/dist/server/src/services/index.d.ts.map +1 -1
  359. package/dist/server/src/types.d.ts +3 -0
  360. package/dist/server/src/types.d.ts.map +1 -1
  361. package/dist/shared/constants.d.ts +35 -0
  362. package/dist/shared/constants.d.ts.map +1 -0
  363. package/dist/shared/contracts/files.d.ts +25 -0
  364. package/dist/shared/contracts/files.d.ts.map +1 -1
  365. package/dist/shared/contracts/settings.d.ts +7 -0
  366. package/package.json +10 -10
  367. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.js.map +0 -1
  368. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.mjs.map +0 -1
  369. package/dist/admin/src/future/pages/Assets/components/CreateFolderDialog.d.ts +0 -8
@@ -1,68 +1,245 @@
1
- import { useState, useRef, useMemo, useEffect, useCallback } from 'react';
1
+ import { useState, useCallback, createElement, Fragment, useRef, useEffect, useMemo } from 'react';
2
+ import { useDispatch, useStore } from 'react-redux';
3
+ import { uploadApi } from '../../../services/api.mjs';
2
4
  import { useGetAssetsQuery } from '../../../services/assets.mjs';
3
5
 
4
6
  const PAGE_SIZE = 20;
5
- const useInfiniteAssets = ({ folder = null, sort } = {})=>{
6
- const [page, setPage] = useState(1);
7
- const lastResultsRef = useRef([]);
8
- const isMountRef = useRef(true);
9
- const { currentData: data, isLoading, isFetching, error } = useGetAssetsQuery({
10
- folder,
7
+ /**
8
+ * Concatenates the pages in ascending order, one entry per asset id.
9
+ *
10
+ * `Map.set` keeps the first insertion position but takes the latest value, so
11
+ * an asset that moved between pages after a mutation holds its place and shows
12
+ * fresh data instead of rendering twice under a duplicate key.
13
+ */ const flattenPages = (pages)=>{
14
+ const byId = new Map();
15
+ for (const page of Object.keys(pages).map(Number).sort((a, b)=>a - b)){
16
+ for (const asset of pages[page]){
17
+ byId.set(asset.id, asset);
18
+ }
19
+ }
20
+ return [
21
+ ...byId.values()
22
+ ];
23
+ };
24
+ /**
25
+ * Keeps one earlier page's `getAssets` query subscribed and reports its results
26
+ * back up whenever they change.
27
+ *
28
+ * The hook itself only subscribes the current (latest) page. Without these, a
29
+ * mutation invalidating `{Asset, LIST}` refetches that page alone and earlier
30
+ * pages stay frozen — a rename or delete on an earlier page never reaches the
31
+ * list until a folder/sort/search change. One subscriber per earlier
32
+ * page keeps them all subscribed so the invalidation refetches the whole list.
33
+ * Renders nothing; it exists only for the subscription + `onRefreshed` report.
34
+ */ const PageSubscriber = ({ queryArgs, page, onRefreshed })=>{
35
+ const { currentData } = useGetAssetsQuery({
36
+ ...queryArgs,
11
37
  page,
12
- pageSize: PAGE_SIZE,
13
- sort
38
+ pageSize: PAGE_SIZE
14
39
  });
15
- const pagination = data?.pagination;
16
- // Accumulate pages. When cache is invalidated the current page is refetched
17
- // detect this and reset to avoid a gap in the results.
18
- const assets = useMemo(()=>{
19
- if (!data) {
20
- return lastResultsRef.current;
40
+ const results = currentData?.results;
41
+ useEffect(()=>{
42
+ if (results) {
43
+ onRefreshed(page, results);
21
44
  }
22
- const currentPageResults = data.results;
23
- if (page === 1) {
24
- lastResultsRef.current = currentPageResults;
25
- } else {
26
- // If accumulated length doesn't match expectation, cache was cleared
27
- const expectedPrior = (page - 1) * PAGE_SIZE;
28
- if (lastResultsRef.current.length < expectedPrior - PAGE_SIZE) {
29
- return lastResultsRef.current;
30
- }
31
- // Only append if these aren't already accumulated
32
- if (lastResultsRef.current.length < page * PAGE_SIZE) {
33
- lastResultsRef.current = [
34
- ...lastResultsRef.current,
35
- ...currentPageResults
36
- ];
45
+ // `results` is a stable reference per cache entry, so this only re-reports
46
+ // when a refetch actually replaces the page's data. Reporting the same
47
+ // reference the accumulator already holds is a no-op (see `onRefreshed`).
48
+ }, [
49
+ results,
50
+ page,
51
+ onRefreshed
52
+ ]);
53
+ return null;
54
+ };
55
+ /**
56
+ * Infinite scroll over `/upload/files`, keeping one accumulated slot per page.
57
+ *
58
+ * The caller MUST render the returned `subscribers` node — that is what keeps
59
+ * every loaded page subscribed, so `{Asset, LIST}` invalidations keep the whole
60
+ * list consistent rather than only the current page.
61
+ */ const useInfiniteAssets = ({ folder = null, sort, search, filters, enabled = true } = {})=>{
62
+ // Derived from the request args themselves, so a new filter can't reach the
63
+ // API without also invalidating what was accumulated under the old one.
64
+ const queryArgs = {
65
+ folder,
66
+ sort,
67
+ search,
68
+ filters
69
+ };
70
+ const queryKey = JSON.stringify(queryArgs);
71
+ // Identifies the list being viewed, ignoring the search term. Folder, sort
72
+ // and filter changes are single committed actions, so one clean reset per
73
+ // change is honest. Search is committed per keystroke on a 300ms debounce,
74
+ // and blanking the list on each one would strobe as the user types.
75
+ // The cost: through the search window the previous rows stay rendered and
76
+ // `hasNextPage` is false, so the list reads as complete until page 1 lands.
77
+ const listKey = JSON.stringify({
78
+ folder,
79
+ sort,
80
+ filters
81
+ });
82
+ const [pageState, setPageState] = useState({
83
+ queryKey,
84
+ page: 1
85
+ });
86
+ const [accumulated, setAccumulated] = useState({
87
+ queryKey,
88
+ listKey,
89
+ pages: {}
90
+ });
91
+ // Derived rather than read straight from state: the render that discovers
92
+ // the key change still reaches the query below, and a list with no page 1
93
+ // must never be requested at page 2.
94
+ const page = pageState.queryKey === queryKey ? pageState.page : 1;
95
+ if (pageState.queryKey !== queryKey) {
96
+ setPageState({
97
+ queryKey,
98
+ page: 1
99
+ });
100
+ }
101
+ const { currentData, isLoading, isFetching, error } = useGetAssetsQuery({
102
+ ...queryArgs,
103
+ page,
104
+ pageSize: PAGE_SIZE
105
+ }, {
106
+ skip: !enabled
107
+ });
108
+ const isSameQuery = accumulated.queryKey === queryKey;
109
+ // `currentData` is only ever the payload for the args just passed, so it
110
+ // belongs to this key at this page — nothing to infer. Its reference is
111
+ // stable per cache entry, so this settles after one extra render.
112
+ if (currentData && (!isSameQuery || accumulated.pages[page] !== currentData.results)) {
113
+ setAccumulated(isSameQuery ? {
114
+ ...accumulated,
115
+ pages: {
116
+ ...accumulated.pages,
117
+ [page]: currentData.results
118
+ },
119
+ pagination: currentData.pagination
120
+ } : {
121
+ queryKey,
122
+ listKey,
123
+ pages: {
124
+ [page]: currentData.results
125
+ },
126
+ pagination: currentData.pagination
127
+ });
128
+ }
129
+ // Merges a refetched earlier page back into the accumulator. Ignores reports
130
+ // for a superseded query, and no-ops when the reference is unchanged so a
131
+ // subscriber re-reporting its already-stored page can't loop.
132
+ const handlePageRefreshed = useCallback((refreshedPage, results)=>{
133
+ setAccumulated((prev)=>{
134
+ if (prev.queryKey !== queryKey || prev.pages[refreshedPage] === results) {
135
+ return prev;
37
136
  }
38
- }
39
- return lastResultsRef.current;
137
+ return {
138
+ ...prev,
139
+ pages: {
140
+ ...prev.pages,
141
+ [refreshedPage]: results
142
+ }
143
+ };
144
+ });
40
145
  }, [
41
- data,
42
- page
146
+ queryKey
43
147
  ]);
44
- // Reset on filter/sort change skip the initial mount since the memo
45
- // already handles page 1 correctly
148
+ // One subscriber per earlier page (the current page is already subscribed by
149
+ // the query above). Keyed by queryKey so a folder/sort/search change remounts
150
+ // them against the new args instead of reusing a stale cache entry.
151
+ // `createElement` rather than JSX so this stays a plain `.ts` hook file.
152
+ const subscribers = createElement(Fragment, null, Array.from({
153
+ length: Math.max(0, page - 1)
154
+ }, (_, index)=>index + 1).map((earlierPage)=>createElement(PageSubscriber, {
155
+ key: `${queryKey}:${earlierPage}`,
156
+ queryArgs,
157
+ page: earlierPage,
158
+ onRefreshed: handlePageRefreshed
159
+ })));
160
+ // Drop the left folder's cached `getAssets` pages when the folder changes.
161
+ //
162
+ // RTK Query 1.9.7 leaves a query's store subscription in place when its last
163
+ // subscriber unmounts while a refetch is still in flight — e.g. a delete
164
+ // invalidates `{Asset, LIST}`, every loaded page starts refetching, and the
165
+ // user navigates away before those settle. Those stale subscriptions make the
166
+ // next `{Asset, LIST}` invalidation (an upload in the new folder) refetch the
167
+ // *previous* folder's pages: one bogus `/upload/files` request per loaded
168
+ // page. There is no public per-entry unsubscribe, so evict the left folder's
169
+ // entries outright — returning reloads from page 1, which the accumulator
170
+ // already does on a list change.
171
+ const dispatch = useDispatch();
172
+ const store = useStore();
173
+ const prevFolderRef = useRef(folder);
46
174
  useEffect(()=>{
47
- if (isMountRef.current) {
48
- isMountRef.current = false;
175
+ const prevFolder = prevFolderRef.current;
176
+ prevFolderRef.current = folder;
177
+ if (prevFolder === folder) {
49
178
  return;
50
179
  }
51
- setPage(1);
52
- lastResultsRef.current = [];
180
+ const apiState = store.getState()[uploadApi.reducerPath];
181
+ const removeQueryResult = uploadApi.internalActions.removeQueryResult;
182
+ Object.keys(apiState?.queries ?? {}).forEach((cacheKey)=>{
183
+ if (!cacheKey.startsWith('getAssets(')) {
184
+ return;
185
+ }
186
+ let args;
187
+ try {
188
+ args = JSON.parse(cacheKey.slice('getAssets('.length, -1));
189
+ } catch {
190
+ return;
191
+ }
192
+ if (args.folder === prevFolder) {
193
+ dispatch(removeQueryResult({
194
+ queryCacheKey: cacheKey
195
+ }));
196
+ }
197
+ });
53
198
  }, [
54
199
  folder,
55
- sort
200
+ dispatch,
201
+ store
56
202
  ]);
57
- const hasNextPage = pagination ? page < pagination.pageCount : false;
203
+ // Until the new list's first page lands the accumulator still holds the
204
+ // previous folder. Reported as loading so the page shows a spinner instead
205
+ // of the outgoing folder's assets under the incoming folder's header.
206
+ const isChangingList = accumulated.listKey !== listKey;
207
+ const assets = useMemo(()=>isChangingList ? [] : flattenPages(accumulated.pages), [
208
+ isChangingList,
209
+ accumulated.pages
210
+ ]);
211
+ // Deliberately the live value, not the stale-tolerant one below: paging must
212
+ // never be driven by a total that belongs to the previous query.
213
+ const hasNextPage = currentData ? page < currentData.pagination.pageCount : false;
58
214
  const isFetchingMore = isFetching && page > 1;
59
215
  const fetchNextPage = useCallback(()=>{
60
- setPage((prev)=>prev + 1);
61
- }, []);
216
+ setPageState((prev)=>({
217
+ queryKey,
218
+ page: (prev.queryKey === queryKey ? prev.page : 1) + 1
219
+ }));
220
+ }, [
221
+ queryKey
222
+ ]);
223
+ if (!enabled) {
224
+ return {
225
+ assets: [],
226
+ // No list to keep fresh while disabled, so nothing to subscribe.
227
+ subscribers: null,
228
+ pagination: undefined,
229
+ isLoading: false,
230
+ isFetchingMore: false,
231
+ hasNextPage: false,
232
+ fetchNextPage,
233
+ error: undefined
234
+ };
235
+ }
62
236
  return {
63
237
  assets,
64
- pagination,
65
- isLoading,
238
+ subscribers,
239
+ // Falls back to the accumulated total so a consumer showing the count
240
+ // doesn't flash zero mid-transition, matching the possibly stale `assets`.
241
+ pagination: currentData?.pagination ?? accumulated.pagination,
242
+ isLoading: isLoading || isChangingList,
66
243
  isFetchingMore,
67
244
  hasNextPage,
68
245
  fetchNextPage,
@@ -1 +1 @@
1
- {"version":3,"file":"useInfiniteAssets.mjs","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useInfiniteAssets.ts"],"sourcesContent":["import { useState, useCallback, useMemo, useEffect, useRef } from 'react';\n\nimport { useGetAssetsQuery } from '../../../services/assets';\n\nimport type { File } from '../../../../../../shared/contracts/files';\n\nconst PAGE_SIZE = 20;\n\ninterface UseInfiniteAssetsOptions {\n folder?: number | null;\n sort?: string;\n}\n\nconst useInfiniteAssets = ({ folder = null, sort }: UseInfiniteAssetsOptions = {}) => {\n const [page, setPage] = useState(1);\n const lastResultsRef = useRef<File[]>([]);\n const isMountRef = useRef(true);\n\n const {\n currentData: data,\n isLoading,\n isFetching,\n error,\n } = useGetAssetsQuery({\n folder,\n page,\n pageSize: PAGE_SIZE,\n sort,\n });\n\n const pagination = data?.pagination;\n\n // Accumulate pages. When cache is invalidated the current page is refetched\n // detect this and reset to avoid a gap in the results.\n const assets = useMemo(() => {\n if (!data) {\n return lastResultsRef.current;\n }\n\n const currentPageResults = data.results;\n\n if (page === 1) {\n lastResultsRef.current = currentPageResults;\n } else {\n // If accumulated length doesn't match expectation, cache was cleared\n const expectedPrior = (page - 1) * PAGE_SIZE;\n if (lastResultsRef.current.length < expectedPrior - PAGE_SIZE) {\n return lastResultsRef.current;\n }\n\n // Only append if these aren't already accumulated\n if (lastResultsRef.current.length < page * PAGE_SIZE) {\n lastResultsRef.current = [...lastResultsRef.current, ...currentPageResults];\n }\n }\n\n return lastResultsRef.current;\n }, [data, page]);\n\n // Reset on filter/sort change — skip the initial mount since the memo\n // already handles page 1 correctly\n useEffect(() => {\n if (isMountRef.current) {\n isMountRef.current = false;\n\n return;\n }\n setPage(1);\n lastResultsRef.current = [];\n }, [folder, sort]);\n\n const hasNextPage = pagination ? page < pagination.pageCount : false;\n const isFetchingMore = isFetching && page > 1;\n\n const fetchNextPage = useCallback(() => {\n setPage((prev) => prev + 1);\n }, []);\n\n return { assets, pagination, isLoading, isFetchingMore, hasNextPage, fetchNextPage, error };\n};\n\nexport { useInfiniteAssets };\nexport { PAGE_SIZE };\n"],"names":["PAGE_SIZE","useInfiniteAssets","folder","sort","page","setPage","useState","lastResultsRef","useRef","isMountRef","currentData","data","isLoading","isFetching","error","useGetAssetsQuery","pageSize","pagination","assets","useMemo","current","currentPageResults","results","expectedPrior","length","useEffect","hasNextPage","pageCount","isFetchingMore","fetchNextPage","useCallback","prev"],"mappings":";;;AAMA,MAAMA,SAAAA,GAAY;AAOlB,MAAMC,iBAAAA,GAAoB,CAAC,EAAEC,MAAAA,GAAS,IAAI,EAAEC,IAAI,EAA4B,GAAG,EAAE,GAAA;AAC/E,IAAA,MAAM,CAACC,IAAAA,EAAMC,OAAAA,CAAQ,GAAGC,QAAAA,CAAS,CAAA,CAAA;IACjC,MAAMC,cAAAA,GAAiBC,OAAe,EAAE,CAAA;AACxC,IAAA,MAAMC,aAAaD,MAAAA,CAAO,IAAA,CAAA;IAE1B,MAAM,EACJE,WAAAA,EAAaC,IAAI,EACjBC,SAAS,EACTC,UAAU,EACVC,KAAK,EACN,GAAGC,iBAAAA,CAAkB;AACpBb,QAAAA,MAAAA;AACAE,QAAAA,IAAAA;QACAY,QAAAA,EAAUhB,SAAAA;AACVG,QAAAA;AACF,KAAA,CAAA;AAEA,IAAA,MAAMc,aAAaN,IAAAA,EAAMM,UAAAA;;;AAIzB,IAAA,MAAMC,SAASC,OAAAA,CAAQ,IAAA;AACrB,QAAA,IAAI,CAACR,IAAAA,EAAM;AACT,YAAA,OAAOJ,eAAea,OAAO;AAC/B,QAAA;QAEA,MAAMC,kBAAAA,GAAqBV,KAAKW,OAAO;AAEvC,QAAA,IAAIlB,SAAS,CAAA,EAAG;AACdG,YAAAA,cAAAA,CAAea,OAAO,GAAGC,kBAAAA;QAC3B,CAAA,MAAO;;AAEL,YAAA,MAAME,aAAAA,GAAiBnB,CAAAA,IAAAA,GAAO,CAAA,IAAKJ,SAAAA;AACnC,YAAA,IAAIO,eAAea,OAAO,CAACI,MAAM,GAAGD,gBAAgBvB,SAAAA,EAAW;AAC7D,gBAAA,OAAOO,eAAea,OAAO;AAC/B,YAAA;;AAGA,YAAA,IAAIb,eAAea,OAAO,CAACI,MAAM,GAAGpB,OAAOJ,SAAAA,EAAW;AACpDO,gBAAAA,cAAAA,CAAea,OAAO,GAAG;AAAIb,oBAAAA,GAAAA,cAAAA,CAAea,OAAO;AAAKC,oBAAAA,GAAAA;AAAmB,iBAAA;AAC7E,YAAA;AACF,QAAA;AAEA,QAAA,OAAOd,eAAea,OAAO;IAC/B,CAAA,EAAG;AAACT,QAAAA,IAAAA;AAAMP,QAAAA;AAAK,KAAA,CAAA;;;IAIfqB,SAAAA,CAAU,IAAA;QACR,IAAIhB,UAAAA,CAAWW,OAAO,EAAE;AACtBX,YAAAA,UAAAA,CAAWW,OAAO,GAAG,KAAA;AAErB,YAAA;AACF,QAAA;QACAf,OAAAA,CAAQ,CAAA,CAAA;QACRE,cAAAA,CAAea,OAAO,GAAG,EAAE;IAC7B,CAAA,EAAG;AAAClB,QAAAA,MAAAA;AAAQC,QAAAA;AAAK,KAAA,CAAA;AAEjB,IAAA,MAAMuB,WAAAA,GAAcT,UAAAA,GAAab,IAAAA,GAAOa,UAAAA,CAAWU,SAAS,GAAG,KAAA;IAC/D,MAAMC,cAAAA,GAAiBf,cAAcT,IAAAA,GAAO,CAAA;AAE5C,IAAA,MAAMyB,gBAAgBC,WAAAA,CAAY,IAAA;QAChCzB,OAAAA,CAAQ,CAAC0B,OAASA,IAAAA,GAAO,CAAA,CAAA;AAC3B,IAAA,CAAA,EAAG,EAAE,CAAA;IAEL,OAAO;AAAEb,QAAAA,MAAAA;AAAQD,QAAAA,UAAAA;AAAYL,QAAAA,SAAAA;AAAWgB,QAAAA,cAAAA;AAAgBF,QAAAA,WAAAA;AAAaG,QAAAA,aAAAA;AAAef,QAAAA;AAAM,KAAA;AAC5F;;;;"}
1
+ {"version":3,"file":"useInfiniteAssets.mjs","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useInfiniteAssets.ts"],"sourcesContent":["import { createElement, Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useDispatch, useStore } from 'react-redux';\n\nimport { uploadApi } from '../../../services/api';\nimport { useGetAssetsQuery } from '../../../services/assets';\n\nimport type { File, Pagination } from '../../../../../../shared/contracts/files';\n\nconst PAGE_SIZE = 20;\n\ninterface UseInfiniteAssetsOptions {\n folder?: number | null;\n sort?: string;\n search?: string;\n /** Extra `filters[$and]` entries for the files query. */\n filters?: Record<string, unknown>[];\n /** Structural switch: an `is [folder]`-only type badge means no file can match. */\n enabled?: boolean;\n}\n\ninterface QueryArgs {\n folder: number | null;\n sort?: string;\n search?: string;\n filters?: Record<string, unknown>[];\n}\n\n/**\n * Everything loaded for one query identity.\n *\n * `queryKey` lags the live key while a new query is in flight — that lag is\n * what keeps the previous results on screen across a search change.\n */\ninterface Accumulated {\n queryKey: string;\n listKey: string;\n pages: Record<number, File[]>;\n pagination?: Pagination;\n}\n\ninterface PageState {\n queryKey: string;\n page: number;\n}\n\ntype PageRefreshedHandler = (page: number, results: File[]) => void;\n\n/**\n * Concatenates the pages in ascending order, one entry per asset id.\n *\n * `Map.set` keeps the first insertion position but takes the latest value, so\n * an asset that moved between pages after a mutation holds its place and shows\n * fresh data instead of rendering twice under a duplicate key.\n */\nconst flattenPages = (pages: Record<number, File[]>): File[] => {\n const byId = new Map<number, File>();\n\n for (const page of Object.keys(pages)\n .map(Number)\n .sort((a, b) => a - b)) {\n for (const asset of pages[page]) {\n byId.set(asset.id, asset);\n }\n }\n\n return [...byId.values()];\n};\n\n/**\n * Keeps one earlier page's `getAssets` query subscribed and reports its results\n * back up whenever they change.\n *\n * The hook itself only subscribes the current (latest) page. Without these, a\n * mutation invalidating `{Asset, LIST}` refetches that page alone and earlier\n * pages stay frozen — a rename or delete on an earlier page never reaches the\n * list until a folder/sort/search change. One subscriber per earlier\n * page keeps them all subscribed so the invalidation refetches the whole list.\n * Renders nothing; it exists only for the subscription + `onRefreshed` report.\n */\nconst PageSubscriber = ({\n queryArgs,\n page,\n onRefreshed,\n}: {\n queryArgs: QueryArgs;\n page: number;\n onRefreshed: PageRefreshedHandler;\n}) => {\n const { currentData } = useGetAssetsQuery({ ...queryArgs, page, pageSize: PAGE_SIZE });\n const results = currentData?.results;\n\n useEffect(() => {\n if (results) {\n onRefreshed(page, results);\n }\n // `results` is a stable reference per cache entry, so this only re-reports\n // when a refetch actually replaces the page's data. Reporting the same\n // reference the accumulator already holds is a no-op (see `onRefreshed`).\n }, [results, page, onRefreshed]);\n\n return null;\n};\n\n/**\n * Infinite scroll over `/upload/files`, keeping one accumulated slot per page.\n *\n * The caller MUST render the returned `subscribers` node — that is what keeps\n * every loaded page subscribed, so `{Asset, LIST}` invalidations keep the whole\n * list consistent rather than only the current page.\n */\nconst useInfiniteAssets = ({\n folder = null,\n sort,\n search,\n filters,\n enabled = true,\n}: UseInfiniteAssetsOptions = {}) => {\n // Derived from the request args themselves, so a new filter can't reach the\n // API without also invalidating what was accumulated under the old one.\n const queryArgs: QueryArgs = { folder, sort, search, filters };\n const queryKey = JSON.stringify(queryArgs);\n // Identifies the list being viewed, ignoring the search term. Folder, sort\n // and filter changes are single committed actions, so one clean reset per\n // change is honest. Search is committed per keystroke on a 300ms debounce,\n // and blanking the list on each one would strobe as the user types.\n // The cost: through the search window the previous rows stay rendered and\n // `hasNextPage` is false, so the list reads as complete until page 1 lands.\n const listKey = JSON.stringify({ folder, sort, filters });\n\n const [pageState, setPageState] = useState<PageState>({ queryKey, page: 1 });\n const [accumulated, setAccumulated] = useState<Accumulated>({ queryKey, listKey, pages: {} });\n\n // Derived rather than read straight from state: the render that discovers\n // the key change still reaches the query below, and a list with no page 1\n // must never be requested at page 2.\n const page = pageState.queryKey === queryKey ? pageState.page : 1;\n\n if (pageState.queryKey !== queryKey) {\n setPageState({ queryKey, page: 1 });\n }\n\n const { currentData, isLoading, isFetching, error } = useGetAssetsQuery(\n {\n ...queryArgs,\n page,\n pageSize: PAGE_SIZE,\n },\n { skip: !enabled }\n );\n\n const isSameQuery = accumulated.queryKey === queryKey;\n\n // `currentData` is only ever the payload for the args just passed, so it\n // belongs to this key at this page — nothing to infer. Its reference is\n // stable per cache entry, so this settles after one extra render.\n if (currentData && (!isSameQuery || accumulated.pages[page] !== currentData.results)) {\n setAccumulated(\n isSameQuery\n ? {\n ...accumulated,\n pages: { ...accumulated.pages, [page]: currentData.results },\n pagination: currentData.pagination,\n }\n : {\n queryKey,\n listKey,\n pages: { [page]: currentData.results },\n pagination: currentData.pagination,\n }\n );\n }\n\n // Merges a refetched earlier page back into the accumulator. Ignores reports\n // for a superseded query, and no-ops when the reference is unchanged so a\n // subscriber re-reporting its already-stored page can't loop.\n const handlePageRefreshed = useCallback<PageRefreshedHandler>(\n (refreshedPage, results) => {\n setAccumulated((prev) => {\n if (prev.queryKey !== queryKey || prev.pages[refreshedPage] === results) {\n return prev;\n }\n\n return { ...prev, pages: { ...prev.pages, [refreshedPage]: results } };\n });\n },\n [queryKey]\n );\n\n // One subscriber per earlier page (the current page is already subscribed by\n // the query above). Keyed by queryKey so a folder/sort/search change remounts\n // them against the new args instead of reusing a stale cache entry.\n // `createElement` rather than JSX so this stays a plain `.ts` hook file.\n const subscribers = createElement(\n Fragment,\n null,\n Array.from({ length: Math.max(0, page - 1) }, (_, index) => index + 1).map((earlierPage) =>\n createElement(PageSubscriber, {\n key: `${queryKey}:${earlierPage}`,\n queryArgs,\n page: earlierPage,\n onRefreshed: handlePageRefreshed,\n })\n )\n );\n\n // Drop the left folder's cached `getAssets` pages when the folder changes.\n //\n // RTK Query 1.9.7 leaves a query's store subscription in place when its last\n // subscriber unmounts while a refetch is still in flight — e.g. a delete\n // invalidates `{Asset, LIST}`, every loaded page starts refetching, and the\n // user navigates away before those settle. Those stale subscriptions make the\n // next `{Asset, LIST}` invalidation (an upload in the new folder) refetch the\n // *previous* folder's pages: one bogus `/upload/files` request per loaded\n // page. There is no public per-entry unsubscribe, so evict the left folder's\n // entries outright — returning reloads from page 1, which the accumulator\n // already does on a list change.\n const dispatch = useDispatch();\n const store = useStore();\n const prevFolderRef = useRef(folder);\n useEffect(() => {\n const prevFolder = prevFolderRef.current;\n prevFolderRef.current = folder;\n if (prevFolder === folder) {\n return;\n }\n\n const apiState = (store.getState() as Record<string, { queries?: Record<string, unknown> }>)[\n uploadApi.reducerPath\n ];\n const removeQueryResult = (\n uploadApi as unknown as {\n internalActions: {\n removeQueryResult: (payload: { queryCacheKey: string }) => { type: string };\n };\n }\n ).internalActions.removeQueryResult;\n\n Object.keys(apiState?.queries ?? {}).forEach((cacheKey) => {\n if (!cacheKey.startsWith('getAssets(')) {\n return;\n }\n let args: { folder?: number | null };\n try {\n args = JSON.parse(cacheKey.slice('getAssets('.length, -1));\n } catch {\n return;\n }\n if (args.folder === prevFolder) {\n dispatch(removeQueryResult({ queryCacheKey: cacheKey }));\n }\n });\n }, [folder, dispatch, store]);\n\n // Until the new list's first page lands the accumulator still holds the\n // previous folder. Reported as loading so the page shows a spinner instead\n // of the outgoing folder's assets under the incoming folder's header.\n const isChangingList = accumulated.listKey !== listKey;\n\n const assets = useMemo(\n () => (isChangingList ? [] : flattenPages(accumulated.pages)),\n [isChangingList, accumulated.pages]\n );\n\n // Deliberately the live value, not the stale-tolerant one below: paging must\n // never be driven by a total that belongs to the previous query.\n const hasNextPage = currentData ? page < currentData.pagination.pageCount : false;\n const isFetchingMore = isFetching && page > 1;\n\n const fetchNextPage = useCallback(() => {\n setPageState((prev) => ({\n queryKey,\n page: (prev.queryKey === queryKey ? prev.page : 1) + 1,\n }));\n }, [queryKey]);\n\n if (!enabled) {\n return {\n assets: [] as File[],\n // No list to keep fresh while disabled, so nothing to subscribe.\n subscribers: null,\n pagination: undefined,\n isLoading: false,\n isFetchingMore: false,\n hasNextPage: false,\n fetchNextPage,\n error: undefined,\n };\n }\n\n return {\n assets,\n subscribers,\n // Falls back to the accumulated total so a consumer showing the count\n // doesn't flash zero mid-transition, matching the possibly stale `assets`.\n pagination: currentData?.pagination ?? accumulated.pagination,\n isLoading: isLoading || isChangingList,\n isFetchingMore,\n hasNextPage,\n fetchNextPage,\n error,\n };\n};\n\nexport { useInfiniteAssets };\nexport { PAGE_SIZE };\n"],"names":["PAGE_SIZE","flattenPages","pages","byId","Map","page","Object","keys","map","Number","sort","a","b","asset","set","id","values","PageSubscriber","queryArgs","onRefreshed","currentData","useGetAssetsQuery","pageSize","results","useEffect","useInfiniteAssets","folder","search","filters","enabled","queryKey","JSON","stringify","listKey","pageState","setPageState","useState","accumulated","setAccumulated","isLoading","isFetching","error","skip","isSameQuery","pagination","handlePageRefreshed","useCallback","refreshedPage","prev","subscribers","createElement","Fragment","Array","from","length","Math","max","_","index","earlierPage","key","dispatch","useDispatch","store","useStore","prevFolderRef","useRef","prevFolder","current","apiState","getState","uploadApi","reducerPath","removeQueryResult","internalActions","queries","forEach","cacheKey","startsWith","args","parse","slice","queryCacheKey","isChangingList","assets","useMemo","hasNextPage","pageCount","isFetchingMore","fetchNextPage","undefined"],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+
5
+ /**
6
+ * Infinite-scroll trigger for the media library list.
7
+ *
8
+ * Returns a callback ref to place on a sentinel element rendered after the last
9
+ * row. Three deliberate choices:
10
+ *
11
+ * 1. **Loading is driven only by fresh observer readings.** A fetch fires from
12
+ * the `IntersectionObserver` callback — never from a state/prop change — so
13
+ * it can't react to a stale `isIntersecting` from before newly loaded rows
14
+ * rendered (which fetched an extra page on every trigger).
15
+ *
16
+ * 2. **Each settled page re-observes the sentinel.** An `IntersectionObserver`
17
+ * only fires on a *transition*. When the sentinel stays on screen (a page
18
+ * that doesn't fill the viewport — tall viewport, short page, grid fitting
19
+ * the whole page) it would never fire again and loading would stall. So when
20
+ * `isFetchingMore` settles back to false we re-observe, forcing a fresh
21
+ * reading against the new layout: still on screen → load the next page;
22
+ * viewport now filled → stop.
23
+ *
24
+ * 3. **A callback ref, not a stable object ref.** The sentinel only mounts once
25
+ * the first page has rendered (the list shows a loader before that). A
26
+ * callback ref (re)binds the observer exactly when the node mounts/unmounts,
27
+ * so the observer can't miss a sentinel that appears after the hook first ran.
28
+ */ const useInfiniteScrollSentinel = ({ hasNextPage, isFetchingMore, onLoadMore, options })=>{
29
+ const observerRef = React.useRef(null);
30
+ const nodeRef = React.useRef(null);
31
+ // Read through refs so the callback ref stays stable and the observer callback
32
+ // always sees the latest values. `options` is read once at attach time (see
33
+ // its JSDoc); `onLoadMore` need not be stable.
34
+ const optionsRef = React.useRef(options);
35
+ optionsRef.current = options;
36
+ const onLoadMoreRef = React.useRef(onLoadMore);
37
+ onLoadMoreRef.current = onLoadMore;
38
+ const hasNextPageRef = React.useRef(hasNextPage);
39
+ hasNextPageRef.current = hasNextPage;
40
+ const isFetchingMoreRef = React.useRef(isFetchingMore);
41
+ isFetchingMoreRef.current = isFetchingMore;
42
+ const sentinelRef = React.useCallback((node)=>{
43
+ observerRef.current?.disconnect();
44
+ nodeRef.current = node;
45
+ if (!node) {
46
+ return;
47
+ }
48
+ const observer = new IntersectionObserver(([entry])=>{
49
+ if (entry.isIntersecting && hasNextPageRef.current && !isFetchingMoreRef.current) {
50
+ onLoadMoreRef.current();
51
+ }
52
+ }, optionsRef.current);
53
+ observer.observe(node);
54
+ observerRef.current = observer;
55
+ }, []);
56
+ React.useEffect(()=>()=>observerRef.current?.disconnect(), []);
57
+ // Once a page settles, re-observe so a *fresh* reading against the new layout
58
+ // decides whether to keep loading — rather than a stale `isIntersecting` from
59
+ // before the rows rendered. Re-observing re-emits the current intersection
60
+ // even without a transition, which is what keeps loading a viewport that a
61
+ // single page didn't fill.
62
+ React.useEffect(()=>{
63
+ if (isFetchingMore || !observerRef.current || !nodeRef.current) {
64
+ return;
65
+ }
66
+ observerRef.current.unobserve(nodeRef.current);
67
+ observerRef.current.observe(nodeRef.current);
68
+ }, [
69
+ isFetchingMore
70
+ ]);
71
+ return sentinelRef;
72
+ };
73
+
74
+ exports.useInfiniteScrollSentinel = useInfiniteScrollSentinel;
75
+ //# sourceMappingURL=useInfiniteScrollSentinel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInfiniteScrollSentinel.js","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useInfiniteScrollSentinel.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\ninterface UseInfiniteScrollSentinelArgs {\n /** Whether there is a further page to request. */\n hasNextPage: boolean;\n /** Whether a subsequent page is already in flight. */\n isFetchingMore: boolean;\n /** Called to request the next page. Held in a ref, so it need not be stable. */\n onLoadMore: () => void;\n /**\n * Passed to the `IntersectionObserver`. Read once, when the sentinel node\n * attaches — changing it afterwards does NOT rebuild the observer, so a\n * later `threshold`/`rootMargin` is silently ignored. Pass a stable, ideally\n * module-level, object.\n */\n options?: IntersectionObserverInit;\n}\n\n/**\n * Infinite-scroll trigger for the media library list.\n *\n * Returns a callback ref to place on a sentinel element rendered after the last\n * row. Three deliberate choices:\n *\n * 1. **Loading is driven only by fresh observer readings.** A fetch fires from\n * the `IntersectionObserver` callback — never from a state/prop change — so\n * it can't react to a stale `isIntersecting` from before newly loaded rows\n * rendered (which fetched an extra page on every trigger).\n *\n * 2. **Each settled page re-observes the sentinel.** An `IntersectionObserver`\n * only fires on a *transition*. When the sentinel stays on screen (a page\n * that doesn't fill the viewport — tall viewport, short page, grid fitting\n * the whole page) it would never fire again and loading would stall. So when\n * `isFetchingMore` settles back to false we re-observe, forcing a fresh\n * reading against the new layout: still on screen → load the next page;\n * viewport now filled → stop.\n *\n * 3. **A callback ref, not a stable object ref.** The sentinel only mounts once\n * the first page has rendered (the list shows a loader before that). A\n * callback ref (re)binds the observer exactly when the node mounts/unmounts,\n * so the observer can't miss a sentinel that appears after the hook first ran.\n */\nexport const useInfiniteScrollSentinel = ({\n hasNextPage,\n isFetchingMore,\n onLoadMore,\n options,\n}: UseInfiniteScrollSentinelArgs) => {\n const observerRef = useRef<IntersectionObserver | null>(null);\n const nodeRef = useRef<HTMLElement | null>(null);\n\n // Read through refs so the callback ref stays stable and the observer callback\n // always sees the latest values. `options` is read once at attach time (see\n // its JSDoc); `onLoadMore` need not be stable.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const onLoadMoreRef = useRef(onLoadMore);\n onLoadMoreRef.current = onLoadMore;\n const hasNextPageRef = useRef(hasNextPage);\n hasNextPageRef.current = hasNextPage;\n const isFetchingMoreRef = useRef(isFetchingMore);\n isFetchingMoreRef.current = isFetchingMore;\n\n const sentinelRef = useCallback((node: HTMLElement | null) => {\n observerRef.current?.disconnect();\n nodeRef.current = node;\n\n if (!node) {\n return;\n }\n\n const observer = new IntersectionObserver(([entry]) => {\n if (entry.isIntersecting && hasNextPageRef.current && !isFetchingMoreRef.current) {\n onLoadMoreRef.current();\n }\n }, optionsRef.current);\n\n observer.observe(node);\n observerRef.current = observer;\n }, []);\n\n useEffect(() => () => observerRef.current?.disconnect(), []);\n\n // Once a page settles, re-observe so a *fresh* reading against the new layout\n // decides whether to keep loading — rather than a stale `isIntersecting` from\n // before the rows rendered. Re-observing re-emits the current intersection\n // even without a transition, which is what keeps loading a viewport that a\n // single page didn't fill.\n useEffect(() => {\n if (isFetchingMore || !observerRef.current || !nodeRef.current) {\n return;\n }\n\n observerRef.current.unobserve(nodeRef.current);\n observerRef.current.observe(nodeRef.current);\n }, [isFetchingMore]);\n\n return sentinelRef;\n};\n"],"names":["useInfiniteScrollSentinel","hasNextPage","isFetchingMore","onLoadMore","options","observerRef","useRef","nodeRef","optionsRef","current","onLoadMoreRef","hasNextPageRef","isFetchingMoreRef","sentinelRef","useCallback","node","disconnect","observer","IntersectionObserver","entry","isIntersecting","observe","useEffect","unobserve"],"mappings":";;;;AAkBA;;;;;;;;;;;;;;;;;;;;;;;AAuBC,IACM,MAAMA,yBAAAA,GAA4B,CAAC,EACxCC,WAAW,EACXC,cAAc,EACdC,UAAU,EACVC,OAAO,EACuB,GAAA;AAC9B,IAAA,MAAMC,cAAcC,YAAAA,CAAoC,IAAA,CAAA;AACxD,IAAA,MAAMC,UAAUD,YAAAA,CAA2B,IAAA,CAAA;;;;AAK3C,IAAA,MAAME,aAAaF,YAAAA,CAAOF,OAAAA,CAAAA;AAC1BI,IAAAA,UAAAA,CAAWC,OAAO,GAAGL,OAAAA;AACrB,IAAA,MAAMM,gBAAgBJ,YAAAA,CAAOH,UAAAA,CAAAA;AAC7BO,IAAAA,aAAAA,CAAcD,OAAO,GAAGN,UAAAA;AACxB,IAAA,MAAMQ,iBAAiBL,YAAAA,CAAOL,WAAAA,CAAAA;AAC9BU,IAAAA,cAAAA,CAAeF,OAAO,GAAGR,WAAAA;AACzB,IAAA,MAAMW,oBAAoBN,YAAAA,CAAOJ,cAAAA,CAAAA;AACjCU,IAAAA,iBAAAA,CAAkBH,OAAO,GAAGP,cAAAA;IAE5B,MAAMW,WAAAA,GAAcC,kBAAY,CAACC,IAAAA,GAAAA;AAC/BV,QAAAA,WAAAA,CAAYI,OAAO,EAAEO,UAAAA,EAAAA;AACrBT,QAAAA,OAAAA,CAAQE,OAAO,GAAGM,IAAAA;AAElB,QAAA,IAAI,CAACA,IAAAA,EAAM;AACT,YAAA;AACF,QAAA;AAEA,QAAA,MAAME,QAAAA,GAAW,IAAIC,oBAAAA,CAAqB,CAAC,CAACC,KAAAA,CAAM,GAAA;YAChD,IAAIA,KAAAA,CAAMC,cAAc,IAAIT,cAAAA,CAAeF,OAAO,IAAI,CAACG,iBAAAA,CAAkBH,OAAO,EAAE;AAChFC,gBAAAA,aAAAA,CAAcD,OAAO,EAAA;AACvB,YAAA;AACF,QAAA,CAAA,EAAGD,WAAWC,OAAO,CAAA;AAErBQ,QAAAA,QAAAA,CAASI,OAAO,CAACN,IAAAA,CAAAA;AACjBV,QAAAA,WAAAA,CAAYI,OAAO,GAAGQ,QAAAA;AACxB,IAAA,CAAA,EAAG,EAAE,CAAA;AAELK,IAAAA,eAAAA,CAAU,IAAM,IAAMjB,WAAAA,CAAYI,OAAO,EAAEO,cAAc,EAAE,CAAA;;;;;;IAO3DM,eAAAA,CAAU,IAAA;QACR,IAAIpB,cAAAA,IAAkB,CAACG,WAAAA,CAAYI,OAAO,IAAI,CAACF,OAAAA,CAAQE,OAAO,EAAE;AAC9D,YAAA;AACF,QAAA;AAEAJ,QAAAA,WAAAA,CAAYI,OAAO,CAACc,SAAS,CAAChB,QAAQE,OAAO,CAAA;AAC7CJ,QAAAA,WAAAA,CAAYI,OAAO,CAACY,OAAO,CAACd,QAAQE,OAAO,CAAA;IAC7C,CAAA,EAAG;AAACP,QAAAA;AAAe,KAAA,CAAA;IAEnB,OAAOW,WAAAA;AACT;;;;"}
@@ -0,0 +1,73 @@
1
+ import { useRef, useCallback, useEffect } from 'react';
2
+
3
+ /**
4
+ * Infinite-scroll trigger for the media library list.
5
+ *
6
+ * Returns a callback ref to place on a sentinel element rendered after the last
7
+ * row. Three deliberate choices:
8
+ *
9
+ * 1. **Loading is driven only by fresh observer readings.** A fetch fires from
10
+ * the `IntersectionObserver` callback — never from a state/prop change — so
11
+ * it can't react to a stale `isIntersecting` from before newly loaded rows
12
+ * rendered (which fetched an extra page on every trigger).
13
+ *
14
+ * 2. **Each settled page re-observes the sentinel.** An `IntersectionObserver`
15
+ * only fires on a *transition*. When the sentinel stays on screen (a page
16
+ * that doesn't fill the viewport — tall viewport, short page, grid fitting
17
+ * the whole page) it would never fire again and loading would stall. So when
18
+ * `isFetchingMore` settles back to false we re-observe, forcing a fresh
19
+ * reading against the new layout: still on screen → load the next page;
20
+ * viewport now filled → stop.
21
+ *
22
+ * 3. **A callback ref, not a stable object ref.** The sentinel only mounts once
23
+ * the first page has rendered (the list shows a loader before that). A
24
+ * callback ref (re)binds the observer exactly when the node mounts/unmounts,
25
+ * so the observer can't miss a sentinel that appears after the hook first ran.
26
+ */ const useInfiniteScrollSentinel = ({ hasNextPage, isFetchingMore, onLoadMore, options })=>{
27
+ const observerRef = useRef(null);
28
+ const nodeRef = useRef(null);
29
+ // Read through refs so the callback ref stays stable and the observer callback
30
+ // always sees the latest values. `options` is read once at attach time (see
31
+ // its JSDoc); `onLoadMore` need not be stable.
32
+ const optionsRef = useRef(options);
33
+ optionsRef.current = options;
34
+ const onLoadMoreRef = useRef(onLoadMore);
35
+ onLoadMoreRef.current = onLoadMore;
36
+ const hasNextPageRef = useRef(hasNextPage);
37
+ hasNextPageRef.current = hasNextPage;
38
+ const isFetchingMoreRef = useRef(isFetchingMore);
39
+ isFetchingMoreRef.current = isFetchingMore;
40
+ const sentinelRef = useCallback((node)=>{
41
+ observerRef.current?.disconnect();
42
+ nodeRef.current = node;
43
+ if (!node) {
44
+ return;
45
+ }
46
+ const observer = new IntersectionObserver(([entry])=>{
47
+ if (entry.isIntersecting && hasNextPageRef.current && !isFetchingMoreRef.current) {
48
+ onLoadMoreRef.current();
49
+ }
50
+ }, optionsRef.current);
51
+ observer.observe(node);
52
+ observerRef.current = observer;
53
+ }, []);
54
+ useEffect(()=>()=>observerRef.current?.disconnect(), []);
55
+ // Once a page settles, re-observe so a *fresh* reading against the new layout
56
+ // decides whether to keep loading — rather than a stale `isIntersecting` from
57
+ // before the rows rendered. Re-observing re-emits the current intersection
58
+ // even without a transition, which is what keeps loading a viewport that a
59
+ // single page didn't fill.
60
+ useEffect(()=>{
61
+ if (isFetchingMore || !observerRef.current || !nodeRef.current) {
62
+ return;
63
+ }
64
+ observerRef.current.unobserve(nodeRef.current);
65
+ observerRef.current.observe(nodeRef.current);
66
+ }, [
67
+ isFetchingMore
68
+ ]);
69
+ return sentinelRef;
70
+ };
71
+
72
+ export { useInfiniteScrollSentinel };
73
+ //# sourceMappingURL=useInfiniteScrollSentinel.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInfiniteScrollSentinel.mjs","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useInfiniteScrollSentinel.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\ninterface UseInfiniteScrollSentinelArgs {\n /** Whether there is a further page to request. */\n hasNextPage: boolean;\n /** Whether a subsequent page is already in flight. */\n isFetchingMore: boolean;\n /** Called to request the next page. Held in a ref, so it need not be stable. */\n onLoadMore: () => void;\n /**\n * Passed to the `IntersectionObserver`. Read once, when the sentinel node\n * attaches — changing it afterwards does NOT rebuild the observer, so a\n * later `threshold`/`rootMargin` is silently ignored. Pass a stable, ideally\n * module-level, object.\n */\n options?: IntersectionObserverInit;\n}\n\n/**\n * Infinite-scroll trigger for the media library list.\n *\n * Returns a callback ref to place on a sentinel element rendered after the last\n * row. Three deliberate choices:\n *\n * 1. **Loading is driven only by fresh observer readings.** A fetch fires from\n * the `IntersectionObserver` callback — never from a state/prop change — so\n * it can't react to a stale `isIntersecting` from before newly loaded rows\n * rendered (which fetched an extra page on every trigger).\n *\n * 2. **Each settled page re-observes the sentinel.** An `IntersectionObserver`\n * only fires on a *transition*. When the sentinel stays on screen (a page\n * that doesn't fill the viewport — tall viewport, short page, grid fitting\n * the whole page) it would never fire again and loading would stall. So when\n * `isFetchingMore` settles back to false we re-observe, forcing a fresh\n * reading against the new layout: still on screen → load the next page;\n * viewport now filled → stop.\n *\n * 3. **A callback ref, not a stable object ref.** The sentinel only mounts once\n * the first page has rendered (the list shows a loader before that). A\n * callback ref (re)binds the observer exactly when the node mounts/unmounts,\n * so the observer can't miss a sentinel that appears after the hook first ran.\n */\nexport const useInfiniteScrollSentinel = ({\n hasNextPage,\n isFetchingMore,\n onLoadMore,\n options,\n}: UseInfiniteScrollSentinelArgs) => {\n const observerRef = useRef<IntersectionObserver | null>(null);\n const nodeRef = useRef<HTMLElement | null>(null);\n\n // Read through refs so the callback ref stays stable and the observer callback\n // always sees the latest values. `options` is read once at attach time (see\n // its JSDoc); `onLoadMore` need not be stable.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const onLoadMoreRef = useRef(onLoadMore);\n onLoadMoreRef.current = onLoadMore;\n const hasNextPageRef = useRef(hasNextPage);\n hasNextPageRef.current = hasNextPage;\n const isFetchingMoreRef = useRef(isFetchingMore);\n isFetchingMoreRef.current = isFetchingMore;\n\n const sentinelRef = useCallback((node: HTMLElement | null) => {\n observerRef.current?.disconnect();\n nodeRef.current = node;\n\n if (!node) {\n return;\n }\n\n const observer = new IntersectionObserver(([entry]) => {\n if (entry.isIntersecting && hasNextPageRef.current && !isFetchingMoreRef.current) {\n onLoadMoreRef.current();\n }\n }, optionsRef.current);\n\n observer.observe(node);\n observerRef.current = observer;\n }, []);\n\n useEffect(() => () => observerRef.current?.disconnect(), []);\n\n // Once a page settles, re-observe so a *fresh* reading against the new layout\n // decides whether to keep loading — rather than a stale `isIntersecting` from\n // before the rows rendered. Re-observing re-emits the current intersection\n // even without a transition, which is what keeps loading a viewport that a\n // single page didn't fill.\n useEffect(() => {\n if (isFetchingMore || !observerRef.current || !nodeRef.current) {\n return;\n }\n\n observerRef.current.unobserve(nodeRef.current);\n observerRef.current.observe(nodeRef.current);\n }, [isFetchingMore]);\n\n return sentinelRef;\n};\n"],"names":["useInfiniteScrollSentinel","hasNextPage","isFetchingMore","onLoadMore","options","observerRef","useRef","nodeRef","optionsRef","current","onLoadMoreRef","hasNextPageRef","isFetchingMoreRef","sentinelRef","useCallback","node","disconnect","observer","IntersectionObserver","entry","isIntersecting","observe","useEffect","unobserve"],"mappings":";;AAkBA;;;;;;;;;;;;;;;;;;;;;;;AAuBC,IACM,MAAMA,yBAAAA,GAA4B,CAAC,EACxCC,WAAW,EACXC,cAAc,EACdC,UAAU,EACVC,OAAO,EACuB,GAAA;AAC9B,IAAA,MAAMC,cAAcC,MAAAA,CAAoC,IAAA,CAAA;AACxD,IAAA,MAAMC,UAAUD,MAAAA,CAA2B,IAAA,CAAA;;;;AAK3C,IAAA,MAAME,aAAaF,MAAAA,CAAOF,OAAAA,CAAAA;AAC1BI,IAAAA,UAAAA,CAAWC,OAAO,GAAGL,OAAAA;AACrB,IAAA,MAAMM,gBAAgBJ,MAAAA,CAAOH,UAAAA,CAAAA;AAC7BO,IAAAA,aAAAA,CAAcD,OAAO,GAAGN,UAAAA;AACxB,IAAA,MAAMQ,iBAAiBL,MAAAA,CAAOL,WAAAA,CAAAA;AAC9BU,IAAAA,cAAAA,CAAeF,OAAO,GAAGR,WAAAA;AACzB,IAAA,MAAMW,oBAAoBN,MAAAA,CAAOJ,cAAAA,CAAAA;AACjCU,IAAAA,iBAAAA,CAAkBH,OAAO,GAAGP,cAAAA;IAE5B,MAAMW,WAAAA,GAAcC,YAAY,CAACC,IAAAA,GAAAA;AAC/BV,QAAAA,WAAAA,CAAYI,OAAO,EAAEO,UAAAA,EAAAA;AACrBT,QAAAA,OAAAA,CAAQE,OAAO,GAAGM,IAAAA;AAElB,QAAA,IAAI,CAACA,IAAAA,EAAM;AACT,YAAA;AACF,QAAA;AAEA,QAAA,MAAME,QAAAA,GAAW,IAAIC,oBAAAA,CAAqB,CAAC,CAACC,KAAAA,CAAM,GAAA;YAChD,IAAIA,KAAAA,CAAMC,cAAc,IAAIT,cAAAA,CAAeF,OAAO,IAAI,CAACG,iBAAAA,CAAkBH,OAAO,EAAE;AAChFC,gBAAAA,aAAAA,CAAcD,OAAO,EAAA;AACvB,YAAA;AACF,QAAA,CAAA,EAAGD,WAAWC,OAAO,CAAA;AAErBQ,QAAAA,QAAAA,CAASI,OAAO,CAACN,IAAAA,CAAAA;AACjBV,QAAAA,WAAAA,CAAYI,OAAO,GAAGQ,QAAAA;AACxB,IAAA,CAAA,EAAG,EAAE,CAAA;AAELK,IAAAA,SAAAA,CAAU,IAAM,IAAMjB,WAAAA,CAAYI,OAAO,EAAEO,cAAc,EAAE,CAAA;;;;;;IAO3DM,SAAAA,CAAU,IAAA;QACR,IAAIpB,cAAAA,IAAkB,CAACG,WAAAA,CAAYI,OAAO,IAAI,CAACF,OAAAA,CAAQE,OAAO,EAAE;AAC9D,YAAA;AACF,QAAA;AAEAJ,QAAAA,WAAAA,CAAYI,OAAO,CAACc,SAAS,CAAChB,QAAQE,OAAO,CAAA;AAC7CJ,QAAAA,WAAAA,CAAYI,OAAO,CAACY,OAAO,CAACd,QAAQE,OAAO,CAAA;IAC7C,CAAA,EAAG;AAACP,QAAAA;AAAe,KAAA,CAAA;IAEnB,OAAOW,WAAAA;AACT;;;;"}