@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,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var searchQueryParam = require('../utils/searchQueryParam.js');
3
4
  var api = require('./api.js');
4
5
 
5
6
  const isDataEnvelope = (response)=>typeof response === 'object' && response !== null && 'data' in response;
@@ -8,31 +9,45 @@ const foldersApi = api.uploadApi.injectEndpoints({
8
9
  endpoints: (builder)=>({
9
10
  getFolders: builder.query({
10
11
  query: (params = {})=>{
11
- const { parentId, sort } = params;
12
+ const { parentId, sort, search, filters = [] } = params;
12
13
  const queryParams = {
13
14
  // Default matches sidebar FolderTree order (server getStructure uses sortBy('name')).
14
- sort: sort ?? 'name:ASC'
15
+ sort: sort ?? 'name:ASC',
16
+ populate: {
17
+ parent: true
18
+ }
15
19
  };
16
- if (parentId != null) {
17
- queryParams['filters'] = {
18
- $and: [
19
- {
20
- parent: {
21
- id: parentId
22
- }
20
+ // List filters (dates) apply in BOTH modes — search composes with them,
21
+ // only the parent scope is dropped while searching.
22
+ if (search) {
23
+ // Search is global: the parent filter is dropped so matching folders
24
+ // anywhere in the library surface. The endpoint is unpaginated — it
25
+ // returns every match — so callers can treat the array length as the
26
+ // true total. Bounding it is a separate decision.
27
+ queryParams['_q'] = searchQueryParam.encodeSearchQuery(search);
28
+ if (filters.length > 0) {
29
+ queryParams['filters'] = {
30
+ $and: [
31
+ ...filters
32
+ ]
33
+ };
34
+ }
35
+ } else {
36
+ const parentScope = parentId != null ? {
37
+ parent: {
38
+ id: parentId
39
+ }
40
+ } : {
41
+ parent: {
42
+ id: {
43
+ $null: true
23
44
  }
24
- ]
45
+ }
25
46
  };
26
- } else {
27
47
  queryParams['filters'] = {
28
48
  $and: [
29
- {
30
- parent: {
31
- id: {
32
- $null: true
33
- }
34
- }
35
- }
49
+ parentScope,
50
+ ...filters
36
51
  ]
37
52
  };
38
53
  }
@@ -84,6 +99,28 @@ const foldersApi = api.uploadApi.injectEndpoints({
84
99
  }
85
100
  ]
86
101
  }),
102
+ updateFolder: builder.mutation({
103
+ query: ({ id, ...body })=>({
104
+ url: `/upload/folders/${id}`,
105
+ method: 'PUT',
106
+ data: body
107
+ }),
108
+ transformResponse: (response)=>response.data,
109
+ invalidatesTags: (_result, _error, { id })=>[
110
+ {
111
+ type: 'Folder',
112
+ id
113
+ },
114
+ {
115
+ type: 'Folder',
116
+ id: 'LIST'
117
+ },
118
+ {
119
+ type: 'Folder',
120
+ id: 'STRUCTURE'
121
+ }
122
+ ]
123
+ }),
87
124
  /**
88
125
  * Hierarchical folder tree used by the FolderTree sidebar. Returned as a
89
126
  * single nested array — the server walks the folders table once and
@@ -160,10 +197,19 @@ const foldersApi = api.uploadApi.injectEndpoints({
160
197
  }
161
198
  }),
162
199
  transformResponse: (response)=>response.data,
200
+ // Also carries the LIST tag so a mutation that changes a folder's file
201
+ // count (upload / delete into it) can refresh the header count by
202
+ // invalidating `{ Folder, LIST }`, without needing the folder id at the
203
+ // mutation site (it's buried in the upload FormData / not passed to the
204
+ // delete).
163
205
  providesTags: (_result, _error, { id })=>[
164
206
  {
165
207
  type: 'Folder',
166
208
  id
209
+ },
210
+ {
211
+ type: 'Folder',
212
+ id: 'LIST'
167
213
  }
168
214
  ]
169
215
  }),
@@ -195,7 +241,7 @@ const foldersApi = api.uploadApi.injectEndpoints({
195
241
  })
196
242
  })
197
243
  });
198
- const { useCreateFolderMutation, useGetFoldersQuery, useGetFolderQuery, useGetAllFoldersQuery, useGetFolderStructureQuery, useBulkMoveMutation } = foldersApi;
244
+ const { useCreateFolderMutation, useUpdateFolderMutation, useGetFoldersQuery, useGetFolderQuery, useGetAllFoldersQuery, useGetFolderStructureQuery, useBulkMoveMutation } = foldersApi;
199
245
 
200
246
  exports.useBulkMoveMutation = useBulkMoveMutation;
201
247
  exports.useCreateFolderMutation = useCreateFolderMutation;
@@ -203,4 +249,5 @@ exports.useGetAllFoldersQuery = useGetAllFoldersQuery;
203
249
  exports.useGetFolderQuery = useGetFolderQuery;
204
250
  exports.useGetFolderStructureQuery = useGetFolderStructureQuery;
205
251
  exports.useGetFoldersQuery = useGetFoldersQuery;
252
+ exports.useUpdateFolderMutation = useUpdateFolderMutation;
206
253
  //# sourceMappingURL=folders.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"folders.js","sources":["../../../../admin/src/future/services/folders.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type {\n Folder,\n FolderNode,\n CreateFolders,\n GetFolder,\n GetFolders,\n GetFolderStructure,\n BulkMoveFolders,\n} from '../../../../shared/contracts/folders';\n\nexport type FolderWithCounts = Omit<Folder, 'children' | 'files'> & {\n children?: { count: number };\n files?: { count: number };\n};\n\ninterface GetFoldersParams {\n parentId?: number | null;\n /** Comma-separated rules, e.g. `updatedAt:DESC,name:ASC`. Defaults to alphabetical. */\n sort?: string;\n}\n\ninterface BulkMoveParams {\n fileIds?: number[];\n folderIds?: number[];\n /** `null` moves the items to the root of the Media Library. */\n destinationFolderId: number | null;\n}\n\ntype DataEnvelope<T> = {\n data: T;\n};\n\nconst isDataEnvelope = <T>(response: T | DataEnvelope<T>): response is DataEnvelope<T> =>\n typeof response === 'object' && response !== null && 'data' in response;\n\nconst unwrapData = <T>(response: T | DataEnvelope<T>): T =>\n isDataEnvelope(response) ? response.data : response;\n\nconst foldersApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getFolders: builder.query<Folder[], GetFoldersParams | void>({\n query: (params = {}) => {\n const { parentId, sort } = params as GetFoldersParams;\n\n const queryParams: Record<string, unknown> = {\n // Default matches sidebar FolderTree order (server getStructure uses sortBy('name')).\n sort: sort ?? 'name:ASC',\n };\n\n if (parentId != null) {\n queryParams['filters'] = {\n $and: [{ parent: { id: parentId } }],\n };\n } else {\n queryParams['filters'] = {\n $and: [{ parent: { id: { $null: true } } }],\n };\n }\n\n return {\n url: '/upload/folders',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFolders.Response['data']) =>\n unwrapData<GetFolders.Response['data']>(response),\n providesTags: (results) => {\n if (results) {\n return [\n ...results.map(({ id }) => ({ type: 'Folder' as const, id })),\n { type: 'Folder', id: 'LIST' },\n ];\n }\n return [{ type: 'Folder', id: 'LIST' }];\n },\n }),\n createFolder: builder.mutation<CreateFolders.Response['data'], CreateFolders.Request['body']>({\n query: (body) => ({\n url: '/upload/folders',\n method: 'POST',\n data: body,\n }),\n transformResponse: (response: CreateFolders.Response) => response.data,\n invalidatesTags: [\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n /**\n * Hierarchical folder tree used by the FolderTree sidebar. Returned as a\n * single nested array — the server walks the folders table once and\n * assembles parent/child relationships.\n *\n * TODO: filter results like `admin-folder.find` for folder-scoped roles\n * (v1 returns all folders; relies on global `plugin::upload.read`).\n */\n getFolderStructure: builder.query<FolderNode[], void>({\n query: () => ({\n url: '/upload/folder-structure',\n method: 'GET',\n }),\n // TODO: align GetFolderStructure contract with the real /upload/folder-structure\n // envelope and drop this defensive unwrap (data[] vs { data: [] }).\n transformResponse: (response: GetFolderStructure.Response['data']) =>\n ((response as unknown as { data: FolderNode[] })?.data ??\n (response as unknown as FolderNode[]) ??\n []) as FolderNode[],\n providesTags: [{ type: 'Folder', id: 'STRUCTURE' }],\n }),\n /**\n * Flat list of every folder, used to populate the \"Location\" select in the\n * asset details drawer. No parent filter — we want the entire tree in one\n * query so the select can render any destination folder.\n */\n getAllFolders: builder.query<Folder[], void>({\n query: () => ({\n url: '/upload/folders',\n method: 'GET',\n }),\n transformResponse: (response: GetFolders.Response['data']) =>\n unwrapData<GetFolders.Response['data']>(response ?? []),\n providesTags: (results) => {\n if (results) {\n return [\n ...results.map(({ id }) => ({ type: 'Folder' as const, id })),\n { type: 'Folder' as const, id: 'LIST' },\n ];\n }\n return [{ type: 'Folder' as const, id: 'LIST' }];\n },\n }),\n getFolder: builder.query<FolderWithCounts, { id: number }>({\n query: ({ id }) => ({\n url: `/upload/folders/${id}`,\n method: 'GET',\n config: {\n params: {\n populate: {\n parent: {\n populate: {\n parent: '*',\n },\n },\n children: { count: true },\n files: { count: true },\n },\n },\n },\n }),\n transformResponse: (response: GetFolder.Response) =>\n response.data as unknown as FolderWithCounts,\n providesTags: (_result, _error, { id }) => [{ type: 'Folder', id }],\n }),\n bulkMove: builder.mutation<BulkMoveFolders.Response['data'], BulkMoveParams>({\n query: ({ fileIds = [], folderIds = [], destinationFolderId }) => ({\n url: '/upload/actions/bulk-move',\n method: 'POST',\n data: { fileIds, folderIds, destinationFolderId },\n }),\n transformResponse: (response: BulkMoveFolders.Response) => response.data,\n invalidatesTags: [\n { type: 'Asset', id: 'LIST' },\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n }),\n});\n\nexport const {\n useCreateFolderMutation,\n useGetFoldersQuery,\n useGetFolderQuery,\n useGetAllFoldersQuery,\n useGetFolderStructureQuery,\n useBulkMoveMutation,\n} = foldersApi;\n"],"names":["isDataEnvelope","response","unwrapData","data","foldersApi","uploadApi","injectEndpoints","endpoints","builder","getFolders","query","params","parentId","sort","queryParams","$and","parent","id","$null","url","method","config","transformResponse","providesTags","results","map","type","createFolder","mutation","body","invalidatesTags","getFolderStructure","getAllFolders","getFolder","populate","children","count","files","_result","_error","bulkMove","fileIds","folderIds","destinationFolderId","useCreateFolderMutation","useGetFoldersQuery","useGetFolderQuery","useGetAllFoldersQuery","useGetFolderStructureQuery","useBulkMoveMutation"],"mappings":"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"}
1
+ {"version":3,"file":"folders.js","sources":["../../../../admin/src/future/services/folders.ts"],"sourcesContent":["import { encodeSearchQuery } from '../utils/searchQueryParam';\n\nimport { uploadApi } from './api';\n\nimport type {\n Folder,\n FolderNode,\n CreateFolders,\n GetFolder,\n GetFolders,\n GetFolderStructure,\n BulkMoveFolders,\n UpdateFolder,\n} from '../../../../shared/contracts/folders';\n\nexport type FolderWithCounts = Omit<Folder, 'children' | 'files'> & {\n children?: { count: number };\n files?: { count: number };\n};\n\ninterface GetFoldersParams {\n parentId?: number | null;\n /** Comma-separated rules, e.g. `updatedAt:DESC,name:ASC`. Defaults to alphabetical. */\n sort?: string;\n search?: string;\n /** Extra `filters[$and]` entries (list filters), AND-ed with the parent/search scope. */\n filters?: Record<string, unknown>[];\n}\n\ninterface BulkMoveParams {\n fileIds?: number[];\n folderIds?: number[];\n /** `null` moves the items to the root of the Media Library. */\n destinationFolderId: number | null;\n}\n\ntype DataEnvelope<T> = {\n data: T;\n};\n\nconst isDataEnvelope = <T>(response: T | DataEnvelope<T>): response is DataEnvelope<T> =>\n typeof response === 'object' && response !== null && 'data' in response;\n\nconst unwrapData = <T>(response: T | DataEnvelope<T>): T =>\n isDataEnvelope(response) ? response.data : response;\n\nconst foldersApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getFolders: builder.query<Folder[], GetFoldersParams | void>({\n query: (params = {}) => {\n const { parentId, sort, search, filters = [] } = params as GetFoldersParams;\n\n const queryParams: Record<string, unknown> = {\n // Default matches sidebar FolderTree order (server getStructure uses sortBy('name')).\n sort: sort ?? 'name:ASC',\n populate: { parent: true },\n };\n\n // List filters (dates) apply in BOTH modes — search composes with them,\n // only the parent scope is dropped while searching.\n if (search) {\n // Search is global: the parent filter is dropped so matching folders\n // anywhere in the library surface. The endpoint is unpaginated — it\n // returns every match — so callers can treat the array length as the\n // true total. Bounding it is a separate decision.\n queryParams['_q'] = encodeSearchQuery(search);\n\n if (filters.length > 0) {\n queryParams['filters'] = { $and: [...filters] };\n }\n } else {\n const parentScope =\n parentId != null ? { parent: { id: parentId } } : { parent: { id: { $null: true } } };\n\n queryParams['filters'] = {\n $and: [parentScope, ...filters],\n };\n }\n\n return {\n url: '/upload/folders',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFolders.Response['data']) =>\n unwrapData<GetFolders.Response['data']>(response),\n providesTags: (results) => {\n if (results) {\n return [\n ...results.map(({ id }) => ({ type: 'Folder' as const, id })),\n { type: 'Folder', id: 'LIST' },\n ];\n }\n return [{ type: 'Folder', id: 'LIST' }];\n },\n }),\n createFolder: builder.mutation<CreateFolders.Response['data'], CreateFolders.Request['body']>({\n query: (body) => ({\n url: '/upload/folders',\n method: 'POST',\n data: body,\n }),\n transformResponse: (response: CreateFolders.Response) => response.data,\n invalidatesTags: [\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n updateFolder: builder.mutation<\n UpdateFolder.Response['data'],\n { id: number } & UpdateFolder.Request['body']\n >({\n query: ({ id, ...body }) => ({\n url: `/upload/folders/${id}`,\n method: 'PUT',\n data: body,\n }),\n transformResponse: (response: UpdateFolder.Response) => response.data,\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Folder', id },\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n /**\n * Hierarchical folder tree used by the FolderTree sidebar. Returned as a\n * single nested array — the server walks the folders table once and\n * assembles parent/child relationships.\n *\n * TODO: filter results like `admin-folder.find` for folder-scoped roles\n * (v1 returns all folders; relies on global `plugin::upload.read`).\n */\n getFolderStructure: builder.query<FolderNode[], void>({\n query: () => ({\n url: '/upload/folder-structure',\n method: 'GET',\n }),\n // TODO: align GetFolderStructure contract with the real /upload/folder-structure\n // envelope and drop this defensive unwrap (data[] vs { data: [] }).\n transformResponse: (response: GetFolderStructure.Response['data']) =>\n ((response as unknown as { data: FolderNode[] })?.data ??\n (response as unknown as FolderNode[]) ??\n []) as FolderNode[],\n providesTags: [{ type: 'Folder', id: 'STRUCTURE' }],\n }),\n /**\n * Flat list of every folder, used to populate the \"Location\" select in the\n * asset details drawer. No parent filter — we want the entire tree in one\n * query so the select can render any destination folder.\n */\n getAllFolders: builder.query<Folder[], void>({\n query: () => ({\n url: '/upload/folders',\n method: 'GET',\n }),\n transformResponse: (response: GetFolders.Response['data']) =>\n unwrapData<GetFolders.Response['data']>(response ?? []),\n providesTags: (results) => {\n if (results) {\n return [\n ...results.map(({ id }) => ({ type: 'Folder' as const, id })),\n { type: 'Folder' as const, id: 'LIST' },\n ];\n }\n return [{ type: 'Folder' as const, id: 'LIST' }];\n },\n }),\n getFolder: builder.query<FolderWithCounts, { id: number }>({\n query: ({ id }) => ({\n url: `/upload/folders/${id}`,\n method: 'GET',\n config: {\n params: {\n populate: {\n parent: {\n populate: {\n parent: '*',\n },\n },\n children: { count: true },\n files: { count: true },\n },\n },\n },\n }),\n transformResponse: (response: GetFolder.Response) =>\n response.data as unknown as FolderWithCounts,\n // Also carries the LIST tag so a mutation that changes a folder's file\n // count (upload / delete into it) can refresh the header count by\n // invalidating `{ Folder, LIST }`, without needing the folder id at the\n // mutation site (it's buried in the upload FormData / not passed to the\n // delete).\n providesTags: (_result, _error, { id }) => [\n { type: 'Folder', id },\n { type: 'Folder', id: 'LIST' },\n ],\n }),\n bulkMove: builder.mutation<BulkMoveFolders.Response['data'], BulkMoveParams>({\n query: ({ fileIds = [], folderIds = [], destinationFolderId }) => ({\n url: '/upload/actions/bulk-move',\n method: 'POST',\n data: { fileIds, folderIds, destinationFolderId },\n }),\n transformResponse: (response: BulkMoveFolders.Response) => response.data,\n invalidatesTags: [\n { type: 'Asset', id: 'LIST' },\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n }),\n});\n\nexport const {\n useCreateFolderMutation,\n useUpdateFolderMutation,\n useGetFoldersQuery,\n useGetFolderQuery,\n useGetAllFoldersQuery,\n useGetFolderStructureQuery,\n useBulkMoveMutation,\n} = foldersApi;\n"],"names":["isDataEnvelope","response","unwrapData","data","foldersApi","uploadApi","injectEndpoints","endpoints","builder","getFolders","query","params","parentId","sort","search","filters","queryParams","populate","parent","encodeSearchQuery","length","$and","parentScope","id","$null","url","method","config","transformResponse","providesTags","results","map","type","createFolder","mutation","body","invalidatesTags","updateFolder","_result","_error","getFolderStructure","getAllFolders","getFolder","children","count","files","bulkMove","fileIds","folderIds","destinationFolderId","useCreateFolderMutation","useUpdateFolderMutation","useGetFoldersQuery","useGetFolderQuery","useGetAllFoldersQuery","useGetFolderStructureQuery","useBulkMoveMutation"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { encodeSearchQuery } from '../utils/searchQueryParam.mjs';
1
2
  import { uploadApi } from './api.mjs';
2
3
 
3
4
  const isDataEnvelope = (response)=>typeof response === 'object' && response !== null && 'data' in response;
@@ -6,31 +7,45 @@ const foldersApi = uploadApi.injectEndpoints({
6
7
  endpoints: (builder)=>({
7
8
  getFolders: builder.query({
8
9
  query: (params = {})=>{
9
- const { parentId, sort } = params;
10
+ const { parentId, sort, search, filters = [] } = params;
10
11
  const queryParams = {
11
12
  // Default matches sidebar FolderTree order (server getStructure uses sortBy('name')).
12
- sort: sort ?? 'name:ASC'
13
+ sort: sort ?? 'name:ASC',
14
+ populate: {
15
+ parent: true
16
+ }
13
17
  };
14
- if (parentId != null) {
15
- queryParams['filters'] = {
16
- $and: [
17
- {
18
- parent: {
19
- id: parentId
20
- }
18
+ // List filters (dates) apply in BOTH modes — search composes with them,
19
+ // only the parent scope is dropped while searching.
20
+ if (search) {
21
+ // Search is global: the parent filter is dropped so matching folders
22
+ // anywhere in the library surface. The endpoint is unpaginated — it
23
+ // returns every match — so callers can treat the array length as the
24
+ // true total. Bounding it is a separate decision.
25
+ queryParams['_q'] = encodeSearchQuery(search);
26
+ if (filters.length > 0) {
27
+ queryParams['filters'] = {
28
+ $and: [
29
+ ...filters
30
+ ]
31
+ };
32
+ }
33
+ } else {
34
+ const parentScope = parentId != null ? {
35
+ parent: {
36
+ id: parentId
37
+ }
38
+ } : {
39
+ parent: {
40
+ id: {
41
+ $null: true
21
42
  }
22
- ]
43
+ }
23
44
  };
24
- } else {
25
45
  queryParams['filters'] = {
26
46
  $and: [
27
- {
28
- parent: {
29
- id: {
30
- $null: true
31
- }
32
- }
33
- }
47
+ parentScope,
48
+ ...filters
34
49
  ]
35
50
  };
36
51
  }
@@ -82,6 +97,28 @@ const foldersApi = uploadApi.injectEndpoints({
82
97
  }
83
98
  ]
84
99
  }),
100
+ updateFolder: builder.mutation({
101
+ query: ({ id, ...body })=>({
102
+ url: `/upload/folders/${id}`,
103
+ method: 'PUT',
104
+ data: body
105
+ }),
106
+ transformResponse: (response)=>response.data,
107
+ invalidatesTags: (_result, _error, { id })=>[
108
+ {
109
+ type: 'Folder',
110
+ id
111
+ },
112
+ {
113
+ type: 'Folder',
114
+ id: 'LIST'
115
+ },
116
+ {
117
+ type: 'Folder',
118
+ id: 'STRUCTURE'
119
+ }
120
+ ]
121
+ }),
85
122
  /**
86
123
  * Hierarchical folder tree used by the FolderTree sidebar. Returned as a
87
124
  * single nested array — the server walks the folders table once and
@@ -158,10 +195,19 @@ const foldersApi = uploadApi.injectEndpoints({
158
195
  }
159
196
  }),
160
197
  transformResponse: (response)=>response.data,
198
+ // Also carries the LIST tag so a mutation that changes a folder's file
199
+ // count (upload / delete into it) can refresh the header count by
200
+ // invalidating `{ Folder, LIST }`, without needing the folder id at the
201
+ // mutation site (it's buried in the upload FormData / not passed to the
202
+ // delete).
161
203
  providesTags: (_result, _error, { id })=>[
162
204
  {
163
205
  type: 'Folder',
164
206
  id
207
+ },
208
+ {
209
+ type: 'Folder',
210
+ id: 'LIST'
165
211
  }
166
212
  ]
167
213
  }),
@@ -193,7 +239,7 @@ const foldersApi = uploadApi.injectEndpoints({
193
239
  })
194
240
  })
195
241
  });
196
- const { useCreateFolderMutation, useGetFoldersQuery, useGetFolderQuery, useGetAllFoldersQuery, useGetFolderStructureQuery, useBulkMoveMutation } = foldersApi;
242
+ const { useCreateFolderMutation, useUpdateFolderMutation, useGetFoldersQuery, useGetFolderQuery, useGetAllFoldersQuery, useGetFolderStructureQuery, useBulkMoveMutation } = foldersApi;
197
243
 
198
- export { useBulkMoveMutation, useCreateFolderMutation, useGetAllFoldersQuery, useGetFolderQuery, useGetFolderStructureQuery, useGetFoldersQuery };
244
+ export { useBulkMoveMutation, useCreateFolderMutation, useGetAllFoldersQuery, useGetFolderQuery, useGetFolderStructureQuery, useGetFoldersQuery, useUpdateFolderMutation };
199
245
  //# sourceMappingURL=folders.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"folders.mjs","sources":["../../../../admin/src/future/services/folders.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type {\n Folder,\n FolderNode,\n CreateFolders,\n GetFolder,\n GetFolders,\n GetFolderStructure,\n BulkMoveFolders,\n} from '../../../../shared/contracts/folders';\n\nexport type FolderWithCounts = Omit<Folder, 'children' | 'files'> & {\n children?: { count: number };\n files?: { count: number };\n};\n\ninterface GetFoldersParams {\n parentId?: number | null;\n /** Comma-separated rules, e.g. `updatedAt:DESC,name:ASC`. Defaults to alphabetical. */\n sort?: string;\n}\n\ninterface BulkMoveParams {\n fileIds?: number[];\n folderIds?: number[];\n /** `null` moves the items to the root of the Media Library. */\n destinationFolderId: number | null;\n}\n\ntype DataEnvelope<T> = {\n data: T;\n};\n\nconst isDataEnvelope = <T>(response: T | DataEnvelope<T>): response is DataEnvelope<T> =>\n typeof response === 'object' && response !== null && 'data' in response;\n\nconst unwrapData = <T>(response: T | DataEnvelope<T>): T =>\n isDataEnvelope(response) ? response.data : response;\n\nconst foldersApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getFolders: builder.query<Folder[], GetFoldersParams | void>({\n query: (params = {}) => {\n const { parentId, sort } = params as GetFoldersParams;\n\n const queryParams: Record<string, unknown> = {\n // Default matches sidebar FolderTree order (server getStructure uses sortBy('name')).\n sort: sort ?? 'name:ASC',\n };\n\n if (parentId != null) {\n queryParams['filters'] = {\n $and: [{ parent: { id: parentId } }],\n };\n } else {\n queryParams['filters'] = {\n $and: [{ parent: { id: { $null: true } } }],\n };\n }\n\n return {\n url: '/upload/folders',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFolders.Response['data']) =>\n unwrapData<GetFolders.Response['data']>(response),\n providesTags: (results) => {\n if (results) {\n return [\n ...results.map(({ id }) => ({ type: 'Folder' as const, id })),\n { type: 'Folder', id: 'LIST' },\n ];\n }\n return [{ type: 'Folder', id: 'LIST' }];\n },\n }),\n createFolder: builder.mutation<CreateFolders.Response['data'], CreateFolders.Request['body']>({\n query: (body) => ({\n url: '/upload/folders',\n method: 'POST',\n data: body,\n }),\n transformResponse: (response: CreateFolders.Response) => response.data,\n invalidatesTags: [\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n /**\n * Hierarchical folder tree used by the FolderTree sidebar. Returned as a\n * single nested array — the server walks the folders table once and\n * assembles parent/child relationships.\n *\n * TODO: filter results like `admin-folder.find` for folder-scoped roles\n * (v1 returns all folders; relies on global `plugin::upload.read`).\n */\n getFolderStructure: builder.query<FolderNode[], void>({\n query: () => ({\n url: '/upload/folder-structure',\n method: 'GET',\n }),\n // TODO: align GetFolderStructure contract with the real /upload/folder-structure\n // envelope and drop this defensive unwrap (data[] vs { data: [] }).\n transformResponse: (response: GetFolderStructure.Response['data']) =>\n ((response as unknown as { data: FolderNode[] })?.data ??\n (response as unknown as FolderNode[]) ??\n []) as FolderNode[],\n providesTags: [{ type: 'Folder', id: 'STRUCTURE' }],\n }),\n /**\n * Flat list of every folder, used to populate the \"Location\" select in the\n * asset details drawer. No parent filter — we want the entire tree in one\n * query so the select can render any destination folder.\n */\n getAllFolders: builder.query<Folder[], void>({\n query: () => ({\n url: '/upload/folders',\n method: 'GET',\n }),\n transformResponse: (response: GetFolders.Response['data']) =>\n unwrapData<GetFolders.Response['data']>(response ?? []),\n providesTags: (results) => {\n if (results) {\n return [\n ...results.map(({ id }) => ({ type: 'Folder' as const, id })),\n { type: 'Folder' as const, id: 'LIST' },\n ];\n }\n return [{ type: 'Folder' as const, id: 'LIST' }];\n },\n }),\n getFolder: builder.query<FolderWithCounts, { id: number }>({\n query: ({ id }) => ({\n url: `/upload/folders/${id}`,\n method: 'GET',\n config: {\n params: {\n populate: {\n parent: {\n populate: {\n parent: '*',\n },\n },\n children: { count: true },\n files: { count: true },\n },\n },\n },\n }),\n transformResponse: (response: GetFolder.Response) =>\n response.data as unknown as FolderWithCounts,\n providesTags: (_result, _error, { id }) => [{ type: 'Folder', id }],\n }),\n bulkMove: builder.mutation<BulkMoveFolders.Response['data'], BulkMoveParams>({\n query: ({ fileIds = [], folderIds = [], destinationFolderId }) => ({\n url: '/upload/actions/bulk-move',\n method: 'POST',\n data: { fileIds, folderIds, destinationFolderId },\n }),\n transformResponse: (response: BulkMoveFolders.Response) => response.data,\n invalidatesTags: [\n { type: 'Asset', id: 'LIST' },\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n }),\n});\n\nexport const {\n useCreateFolderMutation,\n useGetFoldersQuery,\n useGetFolderQuery,\n useGetAllFoldersQuery,\n useGetFolderStructureQuery,\n useBulkMoveMutation,\n} = foldersApi;\n"],"names":["isDataEnvelope","response","unwrapData","data","foldersApi","uploadApi","injectEndpoints","endpoints","builder","getFolders","query","params","parentId","sort","queryParams","$and","parent","id","$null","url","method","config","transformResponse","providesTags","results","map","type","createFolder","mutation","body","invalidatesTags","getFolderStructure","getAllFolders","getFolder","populate","children","count","files","_result","_error","bulkMove","fileIds","folderIds","destinationFolderId","useCreateFolderMutation","useGetFoldersQuery","useGetFolderQuery","useGetAllFoldersQuery","useGetFolderStructureQuery","useBulkMoveMutation"],"mappings":"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"}
1
+ {"version":3,"file":"folders.mjs","sources":["../../../../admin/src/future/services/folders.ts"],"sourcesContent":["import { encodeSearchQuery } from '../utils/searchQueryParam';\n\nimport { uploadApi } from './api';\n\nimport type {\n Folder,\n FolderNode,\n CreateFolders,\n GetFolder,\n GetFolders,\n GetFolderStructure,\n BulkMoveFolders,\n UpdateFolder,\n} from '../../../../shared/contracts/folders';\n\nexport type FolderWithCounts = Omit<Folder, 'children' | 'files'> & {\n children?: { count: number };\n files?: { count: number };\n};\n\ninterface GetFoldersParams {\n parentId?: number | null;\n /** Comma-separated rules, e.g. `updatedAt:DESC,name:ASC`. Defaults to alphabetical. */\n sort?: string;\n search?: string;\n /** Extra `filters[$and]` entries (list filters), AND-ed with the parent/search scope. */\n filters?: Record<string, unknown>[];\n}\n\ninterface BulkMoveParams {\n fileIds?: number[];\n folderIds?: number[];\n /** `null` moves the items to the root of the Media Library. */\n destinationFolderId: number | null;\n}\n\ntype DataEnvelope<T> = {\n data: T;\n};\n\nconst isDataEnvelope = <T>(response: T | DataEnvelope<T>): response is DataEnvelope<T> =>\n typeof response === 'object' && response !== null && 'data' in response;\n\nconst unwrapData = <T>(response: T | DataEnvelope<T>): T =>\n isDataEnvelope(response) ? response.data : response;\n\nconst foldersApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getFolders: builder.query<Folder[], GetFoldersParams | void>({\n query: (params = {}) => {\n const { parentId, sort, search, filters = [] } = params as GetFoldersParams;\n\n const queryParams: Record<string, unknown> = {\n // Default matches sidebar FolderTree order (server getStructure uses sortBy('name')).\n sort: sort ?? 'name:ASC',\n populate: { parent: true },\n };\n\n // List filters (dates) apply in BOTH modes — search composes with them,\n // only the parent scope is dropped while searching.\n if (search) {\n // Search is global: the parent filter is dropped so matching folders\n // anywhere in the library surface. The endpoint is unpaginated — it\n // returns every match — so callers can treat the array length as the\n // true total. Bounding it is a separate decision.\n queryParams['_q'] = encodeSearchQuery(search);\n\n if (filters.length > 0) {\n queryParams['filters'] = { $and: [...filters] };\n }\n } else {\n const parentScope =\n parentId != null ? { parent: { id: parentId } } : { parent: { id: { $null: true } } };\n\n queryParams['filters'] = {\n $and: [parentScope, ...filters],\n };\n }\n\n return {\n url: '/upload/folders',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFolders.Response['data']) =>\n unwrapData<GetFolders.Response['data']>(response),\n providesTags: (results) => {\n if (results) {\n return [\n ...results.map(({ id }) => ({ type: 'Folder' as const, id })),\n { type: 'Folder', id: 'LIST' },\n ];\n }\n return [{ type: 'Folder', id: 'LIST' }];\n },\n }),\n createFolder: builder.mutation<CreateFolders.Response['data'], CreateFolders.Request['body']>({\n query: (body) => ({\n url: '/upload/folders',\n method: 'POST',\n data: body,\n }),\n transformResponse: (response: CreateFolders.Response) => response.data,\n invalidatesTags: [\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n updateFolder: builder.mutation<\n UpdateFolder.Response['data'],\n { id: number } & UpdateFolder.Request['body']\n >({\n query: ({ id, ...body }) => ({\n url: `/upload/folders/${id}`,\n method: 'PUT',\n data: body,\n }),\n transformResponse: (response: UpdateFolder.Response) => response.data,\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Folder', id },\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n /**\n * Hierarchical folder tree used by the FolderTree sidebar. Returned as a\n * single nested array — the server walks the folders table once and\n * assembles parent/child relationships.\n *\n * TODO: filter results like `admin-folder.find` for folder-scoped roles\n * (v1 returns all folders; relies on global `plugin::upload.read`).\n */\n getFolderStructure: builder.query<FolderNode[], void>({\n query: () => ({\n url: '/upload/folder-structure',\n method: 'GET',\n }),\n // TODO: align GetFolderStructure contract with the real /upload/folder-structure\n // envelope and drop this defensive unwrap (data[] vs { data: [] }).\n transformResponse: (response: GetFolderStructure.Response['data']) =>\n ((response as unknown as { data: FolderNode[] })?.data ??\n (response as unknown as FolderNode[]) ??\n []) as FolderNode[],\n providesTags: [{ type: 'Folder', id: 'STRUCTURE' }],\n }),\n /**\n * Flat list of every folder, used to populate the \"Location\" select in the\n * asset details drawer. No parent filter — we want the entire tree in one\n * query so the select can render any destination folder.\n */\n getAllFolders: builder.query<Folder[], void>({\n query: () => ({\n url: '/upload/folders',\n method: 'GET',\n }),\n transformResponse: (response: GetFolders.Response['data']) =>\n unwrapData<GetFolders.Response['data']>(response ?? []),\n providesTags: (results) => {\n if (results) {\n return [\n ...results.map(({ id }) => ({ type: 'Folder' as const, id })),\n { type: 'Folder' as const, id: 'LIST' },\n ];\n }\n return [{ type: 'Folder' as const, id: 'LIST' }];\n },\n }),\n getFolder: builder.query<FolderWithCounts, { id: number }>({\n query: ({ id }) => ({\n url: `/upload/folders/${id}`,\n method: 'GET',\n config: {\n params: {\n populate: {\n parent: {\n populate: {\n parent: '*',\n },\n },\n children: { count: true },\n files: { count: true },\n },\n },\n },\n }),\n transformResponse: (response: GetFolder.Response) =>\n response.data as unknown as FolderWithCounts,\n // Also carries the LIST tag so a mutation that changes a folder's file\n // count (upload / delete into it) can refresh the header count by\n // invalidating `{ Folder, LIST }`, without needing the folder id at the\n // mutation site (it's buried in the upload FormData / not passed to the\n // delete).\n providesTags: (_result, _error, { id }) => [\n { type: 'Folder', id },\n { type: 'Folder', id: 'LIST' },\n ],\n }),\n bulkMove: builder.mutation<BulkMoveFolders.Response['data'], BulkMoveParams>({\n query: ({ fileIds = [], folderIds = [], destinationFolderId }) => ({\n url: '/upload/actions/bulk-move',\n method: 'POST',\n data: { fileIds, folderIds, destinationFolderId },\n }),\n transformResponse: (response: BulkMoveFolders.Response) => response.data,\n invalidatesTags: [\n { type: 'Asset', id: 'LIST' },\n { type: 'Folder', id: 'LIST' },\n { type: 'Folder', id: 'STRUCTURE' },\n ],\n }),\n }),\n});\n\nexport const {\n useCreateFolderMutation,\n useUpdateFolderMutation,\n useGetFoldersQuery,\n useGetFolderQuery,\n useGetAllFoldersQuery,\n useGetFolderStructureQuery,\n useBulkMoveMutation,\n} = foldersApi;\n"],"names":["isDataEnvelope","response","unwrapData","data","foldersApi","uploadApi","injectEndpoints","endpoints","builder","getFolders","query","params","parentId","sort","search","filters","queryParams","populate","parent","encodeSearchQuery","length","$and","parentScope","id","$null","url","method","config","transformResponse","providesTags","results","map","type","createFolder","mutation","body","invalidatesTags","updateFolder","_result","_error","getFolderStructure","getAllFolders","getFolder","children","count","files","bulkMove","fileIds","folderIds","destinationFolderId","useCreateFolderMutation","useUpdateFolderMutation","useGetFoldersQuery","useGetFolderQuery","useGetAllFoldersQuery","useGetFolderStructureQuery","useBulkMoveMutation"],"mappings":"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"}
@@ -4,7 +4,14 @@ var api = require('./api.js');
4
4
 
5
5
  const settingsApi = api.uploadApi.injectEndpoints({
6
6
  endpoints: (builder)=>({
7
- getSettings: builder.query({
7
+ // Deliberately NOT named `getSettings`. Every plugin's admin API is built on
8
+ // the same shared `adminApi` instance — one endpoint registry keyed by name,
9
+ // under `reducerPath: 'adminApi'` — and `injectEndpoints` silently keeps the
10
+ // FIRST registration of a duplicate name and discards later ones. The i18n
11
+ // plugin already registers a `getSettings`, so a generic name here would,
12
+ // depending on module load order, resolve this hook to i18n's cache entry
13
+ // (`/i18n/settings`) instead of `/upload/settings` — non-deterministically.
14
+ getUploadSettings: builder.query({
8
15
  query: ()=>({
9
16
  url: '/upload/settings',
10
17
  method: 'GET'
@@ -12,7 +19,7 @@ const settingsApi = api.uploadApi.injectEndpoints({
12
19
  })
13
20
  })
14
21
  });
15
- const { useGetSettingsQuery } = settingsApi;
22
+ const { useGetUploadSettingsQuery } = settingsApi;
16
23
 
17
- exports.useGetSettingsQuery = useGetSettingsQuery;
24
+ exports.useGetUploadSettingsQuery = useGetUploadSettingsQuery;
18
25
  //# sourceMappingURL=settings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings.js","sources":["../../../../admin/src/future/services/settings.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type { GetSettings } from '../../../../shared/contracts/settings';\n\nconst settingsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getSettings: builder.query<GetSettings.Response['data'], void>({\n query: () => ({\n url: '/upload/settings',\n method: 'GET',\n }),\n }),\n }),\n});\n\nconst { useGetSettingsQuery } = settingsApi;\n\nexport { useGetSettingsQuery };\n"],"names":["settingsApi","uploadApi","injectEndpoints","endpoints","builder","getSettings","query","url","method","useGetSettingsQuery"],"mappings":";;;;AAIA,MAAMA,WAAAA,GAAcC,aAAAA,CAAUC,eAAe,CAAC;IAC5CC,SAAAA,EAAW,CAACC,WAAa;YACvBC,WAAAA,EAAaD,OAAAA,CAAQE,KAAK,CAAqC;AAC7DA,gBAAAA,KAAAA,EAAO,KAAO;wBACZC,GAAAA,EAAK,kBAAA;wBACLC,MAAAA,EAAQ;qBACV;AACF,aAAA;SACF;AACF,CAAA,CAAA;AAEA,MAAM,EAAEC,mBAAmB,EAAE,GAAGT;;;;"}
1
+ {"version":3,"file":"settings.js","sources":["../../../../admin/src/future/services/settings.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type { GetSettings } from '../../../../shared/contracts/settings';\n\nconst settingsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n // Deliberately NOT named `getSettings`. Every plugin's admin API is built on\n // the same shared `adminApi` instance — one endpoint registry keyed by name,\n // under `reducerPath: 'adminApi'` — and `injectEndpoints` silently keeps the\n // FIRST registration of a duplicate name and discards later ones. The i18n\n // plugin already registers a `getSettings`, so a generic name here would,\n // depending on module load order, resolve this hook to i18n's cache entry\n // (`/i18n/settings`) instead of `/upload/settings` — non-deterministically.\n getUploadSettings: builder.query<GetSettings.Response['data'], void>({\n query: () => ({\n url: '/upload/settings',\n method: 'GET',\n }),\n }),\n }),\n});\n\nconst { useGetUploadSettingsQuery } = settingsApi;\n\nexport { useGetUploadSettingsQuery };\n"],"names":["settingsApi","uploadApi","injectEndpoints","endpoints","builder","getUploadSettings","query","url","method","useGetUploadSettingsQuery"],"mappings":";;;;AAIA,MAAMA,WAAAA,GAAcC,aAAAA,CAAUC,eAAe,CAAC;IAC5CC,SAAAA,EAAW,CAACC,WAAa;;;;;;;;YAQvBC,iBAAAA,EAAmBD,OAAAA,CAAQE,KAAK,CAAqC;AACnEA,gBAAAA,KAAAA,EAAO,KAAO;wBACZC,GAAAA,EAAK,kBAAA;wBACLC,MAAAA,EAAQ;qBACV;AACF,aAAA;SACF;AACF,CAAA,CAAA;AAEA,MAAM,EAAEC,yBAAyB,EAAE,GAAGT;;;;"}
@@ -2,7 +2,14 @@ import { uploadApi } from './api.mjs';
2
2
 
3
3
  const settingsApi = uploadApi.injectEndpoints({
4
4
  endpoints: (builder)=>({
5
- getSettings: builder.query({
5
+ // Deliberately NOT named `getSettings`. Every plugin's admin API is built on
6
+ // the same shared `adminApi` instance — one endpoint registry keyed by name,
7
+ // under `reducerPath: 'adminApi'` — and `injectEndpoints` silently keeps the
8
+ // FIRST registration of a duplicate name and discards later ones. The i18n
9
+ // plugin already registers a `getSettings`, so a generic name here would,
10
+ // depending on module load order, resolve this hook to i18n's cache entry
11
+ // (`/i18n/settings`) instead of `/upload/settings` — non-deterministically.
12
+ getUploadSettings: builder.query({
6
13
  query: ()=>({
7
14
  url: '/upload/settings',
8
15
  method: 'GET'
@@ -10,7 +17,7 @@ const settingsApi = uploadApi.injectEndpoints({
10
17
  })
11
18
  })
12
19
  });
13
- const { useGetSettingsQuery } = settingsApi;
20
+ const { useGetUploadSettingsQuery } = settingsApi;
14
21
 
15
- export { useGetSettingsQuery };
22
+ export { useGetUploadSettingsQuery };
16
23
  //# sourceMappingURL=settings.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings.mjs","sources":["../../../../admin/src/future/services/settings.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type { GetSettings } from '../../../../shared/contracts/settings';\n\nconst settingsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getSettings: builder.query<GetSettings.Response['data'], void>({\n query: () => ({\n url: '/upload/settings',\n method: 'GET',\n }),\n }),\n }),\n});\n\nconst { useGetSettingsQuery } = settingsApi;\n\nexport { useGetSettingsQuery };\n"],"names":["settingsApi","uploadApi","injectEndpoints","endpoints","builder","getSettings","query","url","method","useGetSettingsQuery"],"mappings":";;AAIA,MAAMA,WAAAA,GAAcC,SAAAA,CAAUC,eAAe,CAAC;IAC5CC,SAAAA,EAAW,CAACC,WAAa;YACvBC,WAAAA,EAAaD,OAAAA,CAAQE,KAAK,CAAqC;AAC7DA,gBAAAA,KAAAA,EAAO,KAAO;wBACZC,GAAAA,EAAK,kBAAA;wBACLC,MAAAA,EAAQ;qBACV;AACF,aAAA;SACF;AACF,CAAA,CAAA;AAEA,MAAM,EAAEC,mBAAmB,EAAE,GAAGT;;;;"}
1
+ {"version":3,"file":"settings.mjs","sources":["../../../../admin/src/future/services/settings.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type { GetSettings } from '../../../../shared/contracts/settings';\n\nconst settingsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n // Deliberately NOT named `getSettings`. Every plugin's admin API is built on\n // the same shared `adminApi` instance — one endpoint registry keyed by name,\n // under `reducerPath: 'adminApi'` — and `injectEndpoints` silently keeps the\n // FIRST registration of a duplicate name and discards later ones. The i18n\n // plugin already registers a `getSettings`, so a generic name here would,\n // depending on module load order, resolve this hook to i18n's cache entry\n // (`/i18n/settings`) instead of `/upload/settings` — non-deterministically.\n getUploadSettings: builder.query<GetSettings.Response['data'], void>({\n query: () => ({\n url: '/upload/settings',\n method: 'GET',\n }),\n }),\n }),\n});\n\nconst { useGetUploadSettingsQuery } = settingsApi;\n\nexport { useGetUploadSettingsQuery };\n"],"names":["settingsApi","uploadApi","injectEndpoints","endpoints","builder","getUploadSettings","query","url","method","useGetUploadSettingsQuery"],"mappings":";;AAIA,MAAMA,WAAAA,GAAcC,SAAAA,CAAUC,eAAe,CAAC;IAC5CC,SAAAA,EAAW,CAACC,WAAa;;;;;;;;YAQvBC,iBAAAA,EAAmBD,OAAAA,CAAQE,KAAK,CAAqC;AACnEA,gBAAAA,KAAAA,EAAO,KAAO;wBACZC,GAAAA,EAAK,kBAAA;wBACLC,MAAAA,EAAQ;qBACV;AACF,aAAA;SACF;AACF,CAAA,CAAA;AAEA,MAAM,EAAEC,yBAAyB,EAAE,GAAGT;;;;"}
@@ -68,6 +68,34 @@ const uploadProgressSlice = toolkit.createSlice({
68
68
  }
69
69
  ];
70
70
  },
71
+ /**
72
+ * Metadata generation started for a row.
73
+ *
74
+ * Metadata requests are fired-and-forgotten, so their callbacks can land after
75
+ * a new batch has replaced `files` and reused the same row indices. Every
76
+ * metadata payload therefore carries the `uploadId` it was fired for, and stale
77
+ * ones are dropped instead of writing onto another batch's row.
78
+ */ setFileMetadataGenerating (state, action) {
79
+ const { index, uploadId } = action.payload;
80
+ if (uploadId !== state.uploadId) {
81
+ return;
82
+ }
83
+ if (state.files[index]) {
84
+ state.files[index].metadataStatus = 'generating';
85
+ }
86
+ },
87
+ /**
88
+ * Terminal metadata outcome for a row. Never touches `status`/`error`/`errors`:
89
+ * the upload itself already succeeded regardless of how generation went.
90
+ */ setFileMetadataResult (state, action) {
91
+ const { index, uploadId, status } = action.payload;
92
+ if (uploadId !== state.uploadId) {
93
+ return;
94
+ }
95
+ if (state.files[index]) {
96
+ state.files[index].metadataStatus = status;
97
+ }
98
+ },
71
99
  addUploadErrors (state, action) {
72
100
  state.errors = [
73
101
  ...state.errors,
@@ -123,7 +151,9 @@ const uploadProgressSlice = toolkit.createSlice({
123
151
  return {
124
152
  ...file,
125
153
  status: 'pending',
126
- uploadedBytes: 0
154
+ uploadedBytes: 0,
155
+ // A retried row goes through the metadata phase again from scratch.
156
+ metadataStatus: undefined
127
157
  };
128
158
  }
129
159
  return file;
@@ -146,7 +176,79 @@ const uploadProgressSlice = toolkit.createSlice({
146
176
  const uploadedBytes = files.reduce((sum, f)=>sum + f.uploadedBytes, 0);
147
177
  return Math.round(uploadedBytes / totalSize * 100);
148
178
  });
149
- const { openUploadProgress, setFileUploading, setFileProgress, setFileComplete, setFileError, addUploadErrors, closeUploadProgress, toggleMinimize, cancelUpload, setUploadFailed, retryCancelledFiles } = uploadProgressSlice.actions;
179
+ /**
180
+ * Count-based metadata progress across the whole batch: `settled / expected`.
181
+ *
182
+ * Count-based rather than byte-weighted because the generation endpoint answers in one
183
+ * go — there is no intermediate progress to weight.
184
+ *
185
+ * The denominator is every row *expected* to enter the phase, not just those that
186
+ * already have. Generation is fired per file as each upload finishes, so counting only
187
+ * started rows made the denominator grow mid-batch and the percentage jump backwards
188
+ * (1/1 = 100%, then a second row starts and it drops to 1/2 = 50%), which flickered the
189
+ * header subtitle on and off once per file.
190
+ *
191
+ * "Expected" is derived from upload outcome rather than from the AI-metadata flag:
192
+ * - a row still uploading may yet enter the phase, so it counts toward the total;
193
+ * - an errored or cancelled row never will, so it must not — otherwise a batch with
194
+ * one failure could never reach 100%.
195
+ *
196
+ * Returns `null` when nothing has entered the phase and nothing is pending — i.e. AI
197
+ * metadata is off, or the batch is done — so the header can hide the subtitle.
198
+ */ const selectMetadataProgress = toolkit.createSelector((state)=>state.uploadProgress.files, (files)=>{
199
+ const started = files.filter((f)=>f.metadataStatus !== undefined);
200
+ // Nothing has entered the phase yet: with no started row there is no evidence AI
201
+ // metadata is even enabled, so stay silent rather than showing a premature 0%.
202
+ if (started.length === 0) return null;
203
+ // Rows whose upload is still in flight are counted in the denominator so the
204
+ // percentage only ever climbs as the batch progresses.
205
+ const pending = files.filter((f)=>f.metadataStatus === undefined && (f.status === 'pending' || f.status === 'uploading')).length;
206
+ const expected = started.length + pending;
207
+ const settled = started.filter((f)=>f.metadataStatus !== 'generating').length;
208
+ return Math.round(settled / expected * 100);
209
+ });
210
+ /**
211
+ * Whether the metadata phase is still ongoing for the batch as a whole.
212
+ *
213
+ * The header uses this — not `selectMetadataProgress === 100` — to decide when to drop
214
+ * the subtitle. Two distinct reasons the phase can be unfinished:
215
+ * - a row is generating right now;
216
+ * - no row is generating this instant, but rows are still uploading and will enter the
217
+ * phase when they finish.
218
+ *
219
+ * The second clause is what keeps the subtitle stable. Generation for file N typically
220
+ * finishes before file N+1 finishes uploading, so a naive "is anything generating?"
221
+ * check goes false between every pair of files and blinks the subtitle out and back in.
222
+ */ const selectIsGeneratingMetadata = toolkit.createSelector((state)=>state.uploadProgress.files, (files)=>{
223
+ if (files.some((f)=>f.metadataStatus === 'generating')) return true;
224
+ // Only bridge the gap if some row has already entered the phase — otherwise a batch
225
+ // with AI metadata disabled would report the phase as ongoing for its whole upload.
226
+ const hasStarted = files.some((f)=>f.metadataStatus !== undefined);
227
+ return hasStarted && files.some((f)=>f.status === 'pending' || f.status === 'uploading');
228
+ });
229
+ /**
230
+ * How many rows reached each terminal metadata outcome, for the header's completion
231
+ * message. `null` while the phase is unfinished — or was never entered at all — so the
232
+ * caller has a single check for "there is no settled outcome to report yet".
233
+ *
234
+ * Counts are reported separately rather than collapsed into one total because they are
235
+ * not interchangeable: only `generated` rows actually had metadata written, so only that
236
+ * count can be claimed as a success. A batch of non-images settles entirely on `skipped`,
237
+ * where "generated on 0 files" would be worse than saying nothing.
238
+ */ const selectMetadataOutcome = toolkit.createSelector((state)=>state.uploadProgress.files, (files)=>{
239
+ const started = files.filter((f)=>f.metadataStatus !== undefined);
240
+ if (started.length === 0) return null;
241
+ // Mirrors `selectIsGeneratingMetadata`: the phase isn't over while a row is
242
+ // generating, nor while a row is still uploading and has yet to enter it.
243
+ const isOngoing = started.some((f)=>f.metadataStatus === 'generating') || files.some((f)=>f.status === 'pending' || f.status === 'uploading');
244
+ if (isOngoing) return null;
245
+ return {
246
+ generated: started.filter((f)=>f.metadataStatus === 'generated').length,
247
+ skipped: started.filter((f)=>f.metadataStatus === 'skipped').length,
248
+ failed: started.filter((f)=>f.metadataStatus === 'failed').length
249
+ };
250
+ });
251
+ const { openUploadProgress, setFileUploading, setFileProgress, setFileComplete, setFileError, setFileMetadataGenerating, setFileMetadataResult, addUploadErrors, closeUploadProgress, toggleMinimize, cancelUpload, setUploadFailed, retryCancelledFiles } = uploadProgressSlice.actions;
150
252
  const uploadProgressReducer = uploadProgressSlice.reducer;
151
253
 
152
254
  exports.addUploadErrors = addUploadErrors;
@@ -155,8 +257,13 @@ exports.closeUploadProgress = closeUploadProgress;
155
257
  exports.openUploadProgress = openUploadProgress;
156
258
  exports.retryCancelledFiles = retryCancelledFiles;
157
259
  exports.selectAggregateProgress = selectAggregateProgress;
260
+ exports.selectIsGeneratingMetadata = selectIsGeneratingMetadata;
261
+ exports.selectMetadataOutcome = selectMetadataOutcome;
262
+ exports.selectMetadataProgress = selectMetadataProgress;
158
263
  exports.setFileComplete = setFileComplete;
159
264
  exports.setFileError = setFileError;
265
+ exports.setFileMetadataGenerating = setFileMetadataGenerating;
266
+ exports.setFileMetadataResult = setFileMetadataResult;
160
267
  exports.setFileProgress = setFileProgress;
161
268
  exports.setFileUploading = setFileUploading;
162
269
  exports.setUploadFailed = setUploadFailed;