@strapi/upload 5.51.0 → 5.51.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/components/AssetCard/VideoPreview.js +1 -1
- package/dist/admin/components/AssetCard/VideoPreview.js.map +1 -1
- package/dist/admin/components/AssetCard/VideoPreview.mjs +1 -1
- package/dist/admin/components/AssetCard/VideoPreview.mjs.map +1 -1
- package/dist/admin/components/TableList/TableList.js +1 -1
- package/dist/admin/components/TableList/TableList.js.map +1 -1
- package/dist/admin/components/TableList/TableList.mjs +1 -1
- package/dist/admin/components/TableList/TableList.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js +21 -4
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs +21 -4
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs.map +1 -1
- package/dist/admin/future/components/Drawer.js +3 -1
- package/dist/admin/future/components/Drawer.js.map +1 -1
- package/dist/admin/future/components/Drawer.mjs +3 -1
- package/dist/admin/future/components/Drawer.mjs.map +1 -1
- package/dist/admin/future/components/UploadProgressDialog.js +200 -27
- package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
- package/dist/admin/future/components/UploadProgressDialog.mjs +203 -30
- package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
- package/dist/admin/future/hooks/useMediaLibraryPermissions.js +35 -0
- package/dist/admin/future/hooks/useMediaLibraryPermissions.js.map +1 -0
- package/dist/admin/future/hooks/useMediaLibraryPermissions.mjs +33 -0
- package/dist/admin/future/hooks/useMediaLibraryPermissions.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/AssetsPage.js +344 -150
- package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.mjs +343 -149
- package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js +75 -32
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +75 -32
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +46 -19
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +47 -20
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js +161 -19
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +162 -20
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js +100 -0
- package/dist/admin/future/pages/Assets/components/AssetsSearchInput.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs +98 -0
- package/dist/admin/future/pages/Assets/components/AssetsSearchInput.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/AssetsTable.js +218 -37
- package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +219 -38
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js +370 -0
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs +368 -0
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js +209 -0
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs +207 -0
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/DateRangeCalendar.js +193 -0
- package/dist/admin/future/pages/Assets/components/DateRangeCalendar.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/DateRangeCalendar.mjs +191 -0
- package/dist/admin/future/pages/Assets/components/DateRangeCalendar.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js +129 -37
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs +131 -39
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.js +120 -13
- package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.mjs +120 -13
- package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js +71 -0
- package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs +69 -0
- package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/computeValidDropTargets.js +34 -0
- package/dist/admin/future/pages/Assets/components/Dnd/computeValidDropTargets.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/computeValidDropTargets.mjs +32 -0
- package/dist/admin/future/pages/Assets/components/Dnd/computeValidDropTargets.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/dndIds.js +15 -0
- package/dist/admin/future/pages/Assets/components/Dnd/dndIds.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/dndIds.mjs +13 -1
- package/dist/admin/future/pages/Assets/components/Dnd/dndIds.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js +19 -25
- package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs +19 -25
- package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/useFolderTreeDroppable.js +35 -0
- package/dist/admin/future/pages/Assets/components/Dnd/useFolderTreeDroppable.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/useFolderTreeDroppable.mjs +33 -0
- package/dist/admin/future/pages/Assets/components/Dnd/useFolderTreeDroppable.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.js +13 -3
- package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.mjs +13 -3
- package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/EmptyState.js +62 -3
- package/dist/admin/future/pages/Assets/components/EmptyState.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/EmptyState.mjs +62 -4
- package/dist/admin/future/pages/Assets/components/EmptyState.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/FilterBadges.js +408 -0
- package/dist/admin/future/pages/Assets/components/FilterBadges.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/FilterBadges.mjs +406 -0
- package/dist/admin/future/pages/Assets/components/FilterBadges.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/FilterMenu.js +273 -0
- package/dist/admin/future/pages/Assets/components/FilterMenu.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/FilterMenu.mjs +267 -0
- package/dist/admin/future/pages/Assets/components/FilterMenu.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js +195 -40
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs +198 -43
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderTree/filterFolderTree.js +33 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/filterFolderTree.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/filterFolderTree.mjs +31 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/filterFolderTree.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.js +22 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.mjs +19 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.js +154 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.mjs +152 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSearch.js +40 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSearch.js.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSearch.mjs +38 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSearch.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js +75 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs +71 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js +33 -12
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs +33 -12
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js +40 -4
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs +40 -4
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useListFilters.js +168 -0
- package/dist/admin/future/pages/Assets/hooks/useListFilters.js.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useListFilters.mjs +162 -0
- package/dist/admin/future/pages/Assets/hooks/useListFilters.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useListSort.js +124 -0
- package/dist/admin/future/pages/Assets/hooks/useListSort.js.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useListSort.mjs +120 -0
- package/dist/admin/future/pages/Assets/hooks/useListSort.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/utils/buildAssetFilters.js +201 -0
- package/dist/admin/future/pages/Assets/utils/buildAssetFilters.js.map +1 -0
- package/dist/admin/future/pages/Assets/utils/buildAssetFilters.mjs +198 -0
- package/dist/admin/future/pages/Assets/utils/buildAssetFilters.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/utils/listQueryKey.js +29 -0
- package/dist/admin/future/pages/Assets/utils/listQueryKey.js.map +1 -0
- package/dist/admin/future/pages/Assets/utils/listQueryKey.mjs +27 -0
- package/dist/admin/future/pages/Assets/utils/listQueryKey.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/utils/mergeMixedList.js +84 -0
- package/dist/admin/future/pages/Assets/utils/mergeMixedList.js.map +1 -0
- package/dist/admin/future/pages/Assets/utils/mergeMixedList.mjs +81 -0
- package/dist/admin/future/pages/Assets/utils/mergeMixedList.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/utils/selection.js +104 -0
- package/dist/admin/future/pages/Assets/utils/selection.js.map +1 -0
- package/dist/admin/future/pages/Assets/utils/selection.mjs +94 -0
- package/dist/admin/future/pages/Assets/utils/selection.mjs.map +1 -0
- package/dist/admin/future/services/api.js +163 -28
- package/dist/admin/future/services/api.js.map +1 -1
- package/dist/admin/future/services/api.mjs +163 -30
- package/dist/admin/future/services/api.mjs.map +1 -1
- package/dist/admin/future/services/assets.js +62 -18
- package/dist/admin/future/services/assets.js.map +1 -1
- package/dist/admin/future/services/assets.mjs +62 -19
- package/dist/admin/future/services/assets.mjs.map +1 -1
- package/dist/admin/future/services/folders.js +31 -19
- package/dist/admin/future/services/folders.js.map +1 -1
- package/dist/admin/future/services/folders.mjs +31 -19
- package/dist/admin/future/services/folders.mjs.map +1 -1
- package/dist/admin/future/services/settings.js +10 -3
- package/dist/admin/future/services/settings.js.map +1 -1
- package/dist/admin/future/services/settings.mjs +10 -3
- package/dist/admin/future/services/settings.mjs.map +1 -1
- package/dist/admin/future/store/uploadProgress.js +109 -2
- package/dist/admin/future/store/uploadProgress.js.map +1 -1
- package/dist/admin/future/store/uploadProgress.mjs +105 -3
- package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
- package/dist/admin/future/utils/buildDragSetFromSelection.js +33 -0
- package/dist/admin/future/utils/buildDragSetFromSelection.js.map +1 -0
- package/dist/admin/future/utils/buildDragSetFromSelection.mjs +31 -0
- package/dist/admin/future/utils/buildDragSetFromSelection.mjs.map +1 -0
- package/dist/admin/future/utils/canDropItemOnFolder.js +4 -0
- package/dist/admin/future/utils/canDropItemOnFolder.js.map +1 -1
- package/dist/admin/future/utils/canDropItemOnFolder.mjs +4 -0
- package/dist/admin/future/utils/canDropItemOnFolder.mjs.map +1 -1
- package/dist/admin/future/utils/flattenFolderStructure.js +28 -0
- package/dist/admin/future/utils/flattenFolderStructure.js.map +1 -0
- package/dist/admin/future/utils/flattenFolderStructure.mjs +26 -0
- package/dist/admin/future/utils/flattenFolderStructure.mjs.map +1 -0
- package/dist/admin/future/utils/formatMoveSuccessMessage.js +19 -0
- package/dist/admin/future/utils/formatMoveSuccessMessage.js.map +1 -0
- package/dist/admin/future/utils/formatMoveSuccessMessage.mjs +17 -0
- package/dist/admin/future/utils/formatMoveSuccessMessage.mjs.map +1 -0
- package/dist/admin/future/utils/getFolderLabel.js +31 -0
- package/dist/admin/future/utils/getFolderLabel.js.map +1 -0
- package/dist/admin/future/utils/getFolderLabel.mjs +29 -0
- package/dist/admin/future/utils/getFolderLabel.mjs.map +1 -0
- package/dist/admin/future/utils/searchQueryParam.js +11 -0
- package/dist/admin/future/utils/searchQueryParam.js.map +1 -0
- package/dist/admin/future/utils/searchQueryParam.mjs +9 -0
- package/dist/admin/future/utils/searchQueryParam.mjs.map +1 -0
- package/dist/admin/hooks/useSelectionState.js +2 -4
- package/dist/admin/hooks/useSelectionState.js.map +1 -1
- package/dist/admin/hooks/useSelectionState.mjs +2 -4
- package/dist/admin/hooks/useSelectionState.mjs.map +1 -1
- package/dist/admin/shared/constants.js +43 -0
- package/dist/admin/shared/constants.js.map +1 -0
- package/dist/admin/shared/constants.mjs +38 -0
- package/dist/admin/shared/constants.mjs.map +1 -0
- package/dist/admin/src/future/hooks/useMediaLibraryPermissions.d.ts +21 -0
- package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetCropEditor.d.ts +7 -1
- package/dist/admin/src/future/pages/Assets/components/AssetsSearchInput.d.ts +5 -0
- package/dist/admin/src/future/pages/Assets/components/AssetsTable.d.ts +7 -1
- package/dist/admin/src/future/pages/Assets/components/BulkActionsBar.d.ts +14 -0
- package/dist/admin/src/future/pages/Assets/components/BulkMoveDialog.d.ts +13 -0
- package/dist/admin/src/future/pages/Assets/components/DateRangeCalendar.d.ts +8 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/AssetsDndProvider.d.ts +2 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/DragOverlayChip.d.ts +2 -2
- package/dist/admin/src/future/pages/Assets/components/Dnd/buildDragSet.d.ts +17 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/computeValidDropTargets.d.ts +7 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/dndIds.d.ts +3 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/useAssetDnd.d.ts +3 -1
- package/dist/admin/src/future/pages/Assets/components/Dnd/useFolderTreeDroppable.d.ts +19 -0
- package/dist/admin/src/future/pages/Assets/components/DropZone/UploadDropZoneContext.d.ts +8 -1
- package/dist/admin/src/future/pages/Assets/components/EmptyState.d.ts +18 -1
- package/dist/admin/src/future/pages/Assets/components/FilterBadges.d.ts +6 -0
- package/dist/admin/src/future/pages/Assets/components/FilterMenu.d.ts +22 -0
- package/dist/admin/src/future/pages/Assets/components/FolderTree/FolderTree.d.ts +13 -4
- package/dist/admin/src/future/pages/Assets/components/FolderTree/filterFolderTree.d.ts +16 -0
- package/dist/admin/src/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.d.ts +6 -0
- package/dist/admin/src/future/pages/Assets/components/SortMenu.d.ts +23 -0
- package/dist/admin/src/future/pages/Assets/hooks/useAssetSearch.d.ts +12 -0
- package/dist/admin/src/future/pages/Assets/hooks/useAssetSelection.d.ts +46 -0
- package/dist/admin/src/future/pages/Assets/hooks/useInfiniteAssets.d.ts +13 -3
- package/dist/admin/src/future/pages/Assets/hooks/useListFilters.d.ts +69 -0
- package/dist/admin/src/future/pages/Assets/hooks/useListSort.d.ts +47 -0
- package/dist/admin/src/future/pages/Assets/utils/buildAssetFilters.d.ts +46 -0
- package/dist/admin/src/future/pages/Assets/utils/listQueryKey.d.ts +29 -0
- package/dist/admin/src/future/pages/Assets/utils/mergeMixedList.d.ts +40 -0
- package/dist/admin/src/future/pages/Assets/utils/selection.d.ts +50 -0
- package/dist/admin/src/future/services/api.d.ts +53 -5
- package/dist/admin/src/future/services/assets.d.ts +13 -2
- package/dist/admin/src/future/services/folders.d.ts +7 -1
- package/dist/admin/src/future/services/settings.d.ts +2 -2
- package/dist/admin/src/future/store/uploadProgress.d.ts +83 -1
- package/dist/admin/src/future/types/dnd.d.ts +5 -0
- package/dist/admin/src/future/utils/buildDragSetFromSelection.d.ts +11 -0
- package/dist/admin/src/future/utils/canDropItemOnFolder.d.ts +1 -1
- package/dist/admin/src/future/utils/flattenFolderStructure.d.ts +18 -0
- package/dist/admin/src/future/utils/formatMoveSuccessMessage.d.ts +14 -0
- package/dist/admin/src/future/utils/getFolderLabel.d.ts +11 -0
- package/dist/admin/src/future/utils/searchQueryParam.d.ts +7 -0
- package/dist/admin/src/hooks/useAiAvailability.d.ts +1 -1
- package/dist/admin/src/hooks/useEditAsset.d.ts +4 -4
- package/dist/admin/src/hooks/useEditFolder.d.ts +4 -4
- package/dist/admin/src/hooks/useSettings.d.ts +1 -0
- package/dist/admin/src/hooks/useTracking.d.ts +1 -1
- package/dist/admin/src/hooks/useUpload.d.ts +1 -1
- package/dist/admin/translations/en.json.js +85 -2
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +85 -2
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/admin/utils/urlYupSchema.js +33 -9
- package/dist/admin/utils/urlYupSchema.js.map +1 -1
- package/dist/admin/utils/urlYupSchema.mjs +33 -9
- package/dist/admin/utils/urlYupSchema.mjs.map +1 -1
- package/dist/server/config.js +13 -5
- package/dist/server/config.js.map +1 -1
- package/dist/server/config.mjs +13 -5
- package/dist/server/config.mjs.map +1 -1
- package/dist/server/constants.js.map +1 -1
- package/dist/server/constants.mjs.map +1 -1
- package/dist/server/controllers/admin-file.js +30 -0
- package/dist/server/controllers/admin-file.js.map +1 -1
- package/dist/server/controllers/admin-file.mjs +30 -0
- package/dist/server/controllers/admin-file.mjs.map +1 -1
- package/dist/server/controllers/admin-settings.js +20 -2
- package/dist/server/controllers/admin-settings.js.map +1 -1
- package/dist/server/controllers/admin-settings.mjs +21 -3
- package/dist/server/controllers/admin-settings.mjs.map +1 -1
- package/dist/server/controllers/content-api.js +1 -2
- package/dist/server/controllers/content-api.js.map +1 -1
- package/dist/server/controllers/content-api.mjs +2 -2
- package/dist/server/controllers/content-api.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/ai-metadata.js +12 -0
- package/dist/server/controllers/validation/admin/ai-metadata.js.map +1 -0
- package/dist/server/controllers/validation/admin/ai-metadata.mjs +10 -0
- package/dist/server/controllers/validation/admin/ai-metadata.mjs.map +1 -0
- package/dist/server/controllers/validation/admin/settings.js +4 -1
- package/dist/server/controllers/validation/admin/settings.js.map +1 -1
- package/dist/server/controllers/validation/admin/settings.mjs +1 -1
- package/dist/server/controllers/validation/admin/settings.mjs.map +1 -1
- package/dist/server/routes/admin.js +18 -0
- package/dist/server/routes/admin.js.map +1 -1
- package/dist/server/routes/admin.mjs +18 -0
- package/dist/server/routes/admin.mjs.map +1 -1
- package/dist/server/services/ai-metadata.js +108 -11
- package/dist/server/services/ai-metadata.js.map +1 -1
- package/dist/server/services/ai-metadata.mjs +108 -11
- package/dist/server/services/ai-metadata.mjs.map +1 -1
- package/dist/server/services/file.js +86 -13
- package/dist/server/services/file.js.map +1 -1
- package/dist/server/services/file.mjs +87 -14
- package/dist/server/services/file.mjs.map +1 -1
- package/dist/server/services/image-manipulation.js +4 -4
- package/dist/server/services/image-manipulation.js.map +1 -1
- package/dist/server/services/image-manipulation.mjs +4 -4
- package/dist/server/services/image-manipulation.mjs.map +1 -1
- package/dist/server/shared/constants.js +43 -0
- package/dist/server/shared/constants.js.map +1 -0
- package/dist/server/shared/constants.mjs +38 -0
- package/dist/server/shared/constants.mjs.map +1 -0
- package/dist/server/src/config.d.ts +2 -0
- package/dist/server/src/config.d.ts.map +1 -1
- package/dist/server/src/constants.d.ts +2 -1
- package/dist/server/src/constants.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-file.d.ts +10 -0
- package/dist/server/src/controllers/admin-file.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-settings.d.ts.map +1 -1
- package/dist/server/src/controllers/index.d.ts +1 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/ai-metadata.d.ts +2 -0
- package/dist/server/src/controllers/validation/admin/ai-metadata.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/admin/settings.d.ts +1 -1
- package/dist/server/src/controllers/validation/admin/settings.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +7 -1
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/routes/admin.d.ts.map +1 -1
- package/dist/server/src/services/ai-metadata.d.ts +19 -0
- package/dist/server/src/services/ai-metadata.d.ts.map +1 -1
- package/dist/server/src/services/file.d.ts +12 -2
- package/dist/server/src/services/file.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +4 -1
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/types.d.ts +3 -0
- package/dist/server/src/types.d.ts.map +1 -1
- package/dist/shared/constants.d.ts +35 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/contracts/files.d.ts +25 -0
- package/dist/shared/contracts/files.d.ts.map +1 -1
- package/dist/shared/contracts/folders.d.ts +1 -1
- package/dist/shared/contracts/settings.d.ts +7 -0
- package/package.json +8 -8
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const getComparableValue = (record, field)=>{
|
|
2
|
+
switch(field){
|
|
3
|
+
case 'createdAt':
|
|
4
|
+
case 'updatedAt':
|
|
5
|
+
return record[field] ? new Date(record[field]).getTime() : 0;
|
|
6
|
+
case 'size':
|
|
7
|
+
// Folders have no size — they compare as 0 (start of ascending, end of descending).
|
|
8
|
+
return record.size ?? 0;
|
|
9
|
+
case 'name':
|
|
10
|
+
default:
|
|
11
|
+
return (record.name ?? '').toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Builds a comparator from comma-separated sort rules (`updatedAt:DESC,name:ASC`).
|
|
16
|
+
* Rules apply in order; the first non-equal field decides. Client-side
|
|
17
|
+
* approximation of the server ordering — good enough to slot folders between
|
|
18
|
+
* files that were sorted server-side by the same rules.
|
|
19
|
+
*/ const buildComparator = (sort)=>{
|
|
20
|
+
const rules = sort.split(',').map((rule)=>rule.trim()).filter(Boolean).map((rule)=>{
|
|
21
|
+
const [field, direction] = rule.split(':');
|
|
22
|
+
return {
|
|
23
|
+
field,
|
|
24
|
+
desc: direction?.toUpperCase() === 'DESC'
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
return (a, b)=>{
|
|
28
|
+
for (const { field, desc } of rules){
|
|
29
|
+
const va = getComparableValue(a, field);
|
|
30
|
+
const vb = getComparableValue(b, field);
|
|
31
|
+
let cmp;
|
|
32
|
+
if (typeof va === 'string' || typeof vb === 'string') {
|
|
33
|
+
cmp = String(va) < String(vb) ? -1 : String(va) > String(vb) ? 1 : 0;
|
|
34
|
+
} else {
|
|
35
|
+
cmp = va - vb;
|
|
36
|
+
}
|
|
37
|
+
if (cmp !== 0) {
|
|
38
|
+
return desc ? -cmp : cmp;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Interleaves the complete folder list into the (possibly partially loaded)
|
|
46
|
+
* asset stream according to the active sort. Ties order folders first.
|
|
47
|
+
*/ const mergeMixedList = ({ folders, assets, sort, hasNextPage })=>{
|
|
48
|
+
const compare = buildComparator(sort);
|
|
49
|
+
const sortedFolders = [
|
|
50
|
+
...folders
|
|
51
|
+
].sort(compare);
|
|
52
|
+
const lastAsset = assets[assets.length - 1];
|
|
53
|
+
const placeableFolders = !hasNextPage || !lastAsset ? hasNextPage ? [] // nothing loaded yet — wait for the first page
|
|
54
|
+
: sortedFolders : sortedFolders.filter((folder)=>compare(folder, lastAsset) <= 0);
|
|
55
|
+
const items = [];
|
|
56
|
+
let folderIndex = 0;
|
|
57
|
+
for (const asset of assets){
|
|
58
|
+
while(folderIndex < placeableFolders.length && compare(placeableFolders[folderIndex], asset) <= 0){
|
|
59
|
+
items.push({
|
|
60
|
+
kind: 'folder',
|
|
61
|
+
folder: placeableFolders[folderIndex]
|
|
62
|
+
});
|
|
63
|
+
folderIndex += 1;
|
|
64
|
+
}
|
|
65
|
+
items.push({
|
|
66
|
+
kind: 'asset',
|
|
67
|
+
asset
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
while(folderIndex < placeableFolders.length){
|
|
71
|
+
items.push({
|
|
72
|
+
kind: 'folder',
|
|
73
|
+
folder: placeableFolders[folderIndex]
|
|
74
|
+
});
|
|
75
|
+
folderIndex += 1;
|
|
76
|
+
}
|
|
77
|
+
return items;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export { buildComparator, mergeMixedList };
|
|
81
|
+
//# sourceMappingURL=mergeMixedList.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergeMixedList.mjs","sources":["../../../../../../admin/src/future/pages/Assets/utils/mergeMixedList.ts"],"sourcesContent":["import type { File } from '../../../../../../shared/contracts/files';\nimport type { Folder } from '../../../../../../shared/contracts/folders';\n\nexport type MixedItem = { kind: 'folder'; folder: Folder } | { kind: 'asset'; asset: File };\n\ntype SortableRecord = {\n name?: string | null;\n createdAt?: string | null;\n updatedAt?: string | null;\n size?: number | null;\n};\n\nconst getComparableValue = (record: SortableRecord, field: string): number | string => {\n switch (field) {\n case 'createdAt':\n case 'updatedAt':\n return record[field] ? new Date(record[field]!).getTime() : 0;\n case 'size':\n // Folders have no size — they compare as 0 (start of ascending, end of descending).\n return record.size ?? 0;\n case 'name':\n default:\n return (record.name ?? '').toLowerCase();\n }\n};\n\n/**\n * Builds a comparator from comma-separated sort rules (`updatedAt:DESC,name:ASC`).\n * Rules apply in order; the first non-equal field decides. Client-side\n * approximation of the server ordering — good enough to slot folders between\n * files that were sorted server-side by the same rules.\n */\nexport const buildComparator = (sort: string) => {\n const rules = sort\n .split(',')\n .map((rule) => rule.trim())\n .filter(Boolean)\n .map((rule) => {\n const [field, direction] = rule.split(':');\n return { field, desc: direction?.toUpperCase() === 'DESC' };\n });\n\n return (a: SortableRecord, b: SortableRecord): number => {\n for (const { field, desc } of rules) {\n const va = getComparableValue(a, field);\n const vb = getComparableValue(b, field);\n\n let cmp: number;\n if (typeof va === 'string' || typeof vb === 'string') {\n cmp = String(va) < String(vb) ? -1 : String(va) > String(vb) ? 1 : 0;\n } else {\n cmp = va - vb;\n }\n\n if (cmp !== 0) {\n return desc ? -cmp : cmp;\n }\n }\n\n return 0;\n };\n};\n\ninterface MergeMixedListParams {\n folders: Folder[];\n assets: File[];\n /** Combined sort rules used for the assets query. */\n sort: string;\n /**\n * More asset pages exist. A folder may only be placed once we know no\n * not-yet-loaded asset sorts before it — i.e. it compares ≤ the last loaded\n * asset. Later pages release the remaining folders progressively.\n */\n hasNextPage: boolean;\n}\n\n/**\n * Interleaves the complete folder list into the (possibly partially loaded)\n * asset stream according to the active sort. Ties order folders first.\n */\nexport const mergeMixedList = ({\n folders,\n assets,\n sort,\n hasNextPage,\n}: MergeMixedListParams): MixedItem[] => {\n const compare = buildComparator(sort);\n\n const sortedFolders = [...folders].sort(compare);\n\n const lastAsset = assets[assets.length - 1];\n const placeableFolders =\n !hasNextPage || !lastAsset\n ? hasNextPage\n ? [] // nothing loaded yet — wait for the first page\n : sortedFolders\n : sortedFolders.filter((folder) => compare(folder, lastAsset) <= 0);\n\n const items: MixedItem[] = [];\n let folderIndex = 0;\n\n for (const asset of assets) {\n while (\n folderIndex < placeableFolders.length &&\n compare(placeableFolders[folderIndex], asset) <= 0\n ) {\n items.push({ kind: 'folder', folder: placeableFolders[folderIndex] });\n folderIndex += 1;\n }\n items.push({ kind: 'asset', asset });\n }\n\n while (folderIndex < placeableFolders.length) {\n items.push({ kind: 'folder', folder: placeableFolders[folderIndex] });\n folderIndex += 1;\n }\n\n return items;\n};\n"],"names":["getComparableValue","record","field","Date","getTime","size","name","toLowerCase","buildComparator","sort","rules","split","map","rule","trim","filter","Boolean","direction","desc","toUpperCase","a","b","va","vb","cmp","String","mergeMixedList","folders","assets","hasNextPage","compare","sortedFolders","lastAsset","length","placeableFolders","folder","items","folderIndex","asset","push","kind"],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure, view-agnostic selection logic for the future Media Library multi-select.
|
|
5
|
+
*
|
|
6
|
+
* Selection covers both assets and folders. Since their numeric ids live in
|
|
7
|
+
* different tables (and can collide), items are tracked as namespaced keys
|
|
8
|
+
* (`asset:1` / `folder:1`). Views build their rendered order as a key array so
|
|
9
|
+
* range / select-all work across the folder and asset sections seamlessly.
|
|
10
|
+
*/ const assetKey = (id)=>`asset:${id}`;
|
|
11
|
+
const folderKey = (id)=>`folder:${id}`;
|
|
12
|
+
/** Extracts the numeric ids of one kind from a set of item keys. */ const getIdsOfKind = (keys, kind)=>{
|
|
13
|
+
const ids = new Set();
|
|
14
|
+
keys.forEach((key)=>{
|
|
15
|
+
const [keyKind, id] = key.split(':');
|
|
16
|
+
if (keyKind === kind) {
|
|
17
|
+
ids.add(Number(id));
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return ids;
|
|
21
|
+
};
|
|
22
|
+
const createEmptySelection = ()=>({
|
|
23
|
+
selectedKeys: new Set(),
|
|
24
|
+
anchorKey: null
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Additive toggle — adds the key if absent, removes it otherwise, without
|
|
28
|
+
* touching the rest of the selection. Used by the item checkbox and Cmd/Ctrl+click.
|
|
29
|
+
* The toggled key becomes the new anchor.
|
|
30
|
+
*/ const toggleSelection = (state, key)=>{
|
|
31
|
+
const selectedKeys = new Set(state.selectedKeys);
|
|
32
|
+
if (selectedKeys.has(key)) {
|
|
33
|
+
selectedKeys.delete(key);
|
|
34
|
+
} else {
|
|
35
|
+
selectedKeys.add(key);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
selectedKeys,
|
|
39
|
+
anchorKey: key
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Shift+click — selects the contiguous range between the current anchor and the
|
|
44
|
+
* target in render order, replacing the current selection. The anchor stays put.
|
|
45
|
+
*
|
|
46
|
+
* Falls back to a single selection when there is no usable anchor (e.g. first
|
|
47
|
+
* click was a Shift+click, or the anchor is no longer in the list).
|
|
48
|
+
*/ const selectRange = (state, orderedKeys, targetKey)=>{
|
|
49
|
+
const targetIndex = orderedKeys.indexOf(targetKey);
|
|
50
|
+
if (targetIndex === -1) {
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
const anchorIndex = state.anchorKey === null ? -1 : orderedKeys.indexOf(state.anchorKey);
|
|
54
|
+
if (anchorIndex === -1) {
|
|
55
|
+
return {
|
|
56
|
+
selectedKeys: new Set([
|
|
57
|
+
targetKey
|
|
58
|
+
]),
|
|
59
|
+
anchorKey: targetKey
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const start = Math.min(anchorIndex, targetIndex);
|
|
63
|
+
const end = Math.max(anchorIndex, targetIndex);
|
|
64
|
+
return {
|
|
65
|
+
selectedKeys: new Set(orderedKeys.slice(start, end + 1)),
|
|
66
|
+
anchorKey: state.anchorKey
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Select-all — selects every key in the provided ordered array. Used by the header
|
|
71
|
+
* checkbox when not everything is already selected.
|
|
72
|
+
*/ const selectAll = (orderedKeys)=>({
|
|
73
|
+
selectedKeys: new Set(orderedKeys),
|
|
74
|
+
anchorKey: orderedKeys.length > 0 ? orderedKeys[orderedKeys.length - 1] : null
|
|
75
|
+
});
|
|
76
|
+
const clearSelection = ()=>createEmptySelection();
|
|
77
|
+
/**
|
|
78
|
+
* Derives the header checkbox state: everything selected, some (indeterminate),
|
|
79
|
+
* or none. Only considers the currently-rendered items (orderedKeys).
|
|
80
|
+
*/ const getSelectAllState = (selectedKeys, orderedKeys)=>{
|
|
81
|
+
if (orderedKeys.length === 0) {
|
|
82
|
+
return {
|
|
83
|
+
allSelected: false,
|
|
84
|
+
isIndeterminate: false
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const selectedCount = orderedKeys.reduce((count, key)=>selectedKeys.has(key) ? count + 1 : count, 0);
|
|
88
|
+
const allSelected = selectedCount === orderedKeys.length;
|
|
89
|
+
return {
|
|
90
|
+
allSelected,
|
|
91
|
+
isIndeterminate: selectedCount > 0 && !allSelected
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
exports.assetKey = assetKey;
|
|
96
|
+
exports.clearSelection = clearSelection;
|
|
97
|
+
exports.createEmptySelection = createEmptySelection;
|
|
98
|
+
exports.folderKey = folderKey;
|
|
99
|
+
exports.getIdsOfKind = getIdsOfKind;
|
|
100
|
+
exports.getSelectAllState = getSelectAllState;
|
|
101
|
+
exports.selectAll = selectAll;
|
|
102
|
+
exports.selectRange = selectRange;
|
|
103
|
+
exports.toggleSelection = toggleSelection;
|
|
104
|
+
//# sourceMappingURL=selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.js","sources":["../../../../../../admin/src/future/pages/Assets/utils/selection.ts"],"sourcesContent":["/**\n * Pure, view-agnostic selection logic for the future Media Library multi-select.\n *\n * Selection covers both assets and folders. Since their numeric ids live in\n * different tables (and can collide), items are tracked as namespaced keys\n * (`asset:1` / `folder:1`). Views build their rendered order as a key array so\n * range / select-all work across the folder and asset sections seamlessly.\n */\n\nexport type SelectableKind = 'asset' | 'folder';\n\nexport type ItemKey = `${SelectableKind}:${number}`;\n\nexport const assetKey = (id: number): ItemKey => `asset:${id}`;\n\nexport const folderKey = (id: number): ItemKey => `folder:${id}`;\n\n/** Extracts the numeric ids of one kind from a set of item keys. */\nexport const getIdsOfKind = (keys: Set<ItemKey>, kind: SelectableKind): Set<number> => {\n const ids = new Set<number>();\n\n keys.forEach((key) => {\n const [keyKind, id] = key.split(':');\n if (keyKind === kind) {\n ids.add(Number(id));\n }\n });\n\n return ids;\n};\n\nexport interface SelectionState {\n /** Currently selected item keys (assets and folders). */\n selectedKeys: Set<ItemKey>;\n /** Anchor used as the start of a Shift+click range. */\n anchorKey: ItemKey | null;\n}\n\nexport const createEmptySelection = (): SelectionState => ({\n selectedKeys: new Set<ItemKey>(),\n anchorKey: null,\n});\n\n/**\n * Additive toggle — adds the key if absent, removes it otherwise, without\n * touching the rest of the selection. Used by the item checkbox and Cmd/Ctrl+click.\n * The toggled key becomes the new anchor.\n */\nexport const toggleSelection = (state: SelectionState, key: ItemKey): SelectionState => {\n const selectedKeys = new Set(state.selectedKeys);\n\n if (selectedKeys.has(key)) {\n selectedKeys.delete(key);\n } else {\n selectedKeys.add(key);\n }\n\n return { selectedKeys, anchorKey: key };\n};\n\n/**\n * Shift+click — selects the contiguous range between the current anchor and the\n * target in render order, replacing the current selection. The anchor stays put.\n *\n * Falls back to a single selection when there is no usable anchor (e.g. first\n * click was a Shift+click, or the anchor is no longer in the list).\n */\nexport const selectRange = (\n state: SelectionState,\n orderedKeys: ItemKey[],\n targetKey: ItemKey\n): SelectionState => {\n const targetIndex = orderedKeys.indexOf(targetKey);\n\n if (targetIndex === -1) {\n return state;\n }\n\n const anchorIndex = state.anchorKey === null ? -1 : orderedKeys.indexOf(state.anchorKey);\n\n if (anchorIndex === -1) {\n return { selectedKeys: new Set<ItemKey>([targetKey]), anchorKey: targetKey };\n }\n\n const start = Math.min(anchorIndex, targetIndex);\n const end = Math.max(anchorIndex, targetIndex);\n\n return {\n selectedKeys: new Set(orderedKeys.slice(start, end + 1)),\n anchorKey: state.anchorKey,\n };\n};\n\n/**\n * Select-all — selects every key in the provided ordered array. Used by the header\n * checkbox when not everything is already selected.\n */\nexport const selectAll = (orderedKeys: ItemKey[]): SelectionState => ({\n selectedKeys: new Set(orderedKeys),\n anchorKey: orderedKeys.length > 0 ? orderedKeys[orderedKeys.length - 1] : null,\n});\n\nexport const clearSelection = (): SelectionState => createEmptySelection();\n\nexport interface SelectAllState {\n allSelected: boolean;\n isIndeterminate: boolean;\n}\n\n/**\n * Derives the header checkbox state: everything selected, some (indeterminate),\n * or none. Only considers the currently-rendered items (orderedKeys).\n */\nexport const getSelectAllState = (\n selectedKeys: Set<ItemKey>,\n orderedKeys: ItemKey[]\n): SelectAllState => {\n if (orderedKeys.length === 0) {\n return { allSelected: false, isIndeterminate: false };\n }\n\n const selectedCount = orderedKeys.reduce(\n (count, key) => (selectedKeys.has(key) ? count + 1 : count),\n 0\n );\n const allSelected = selectedCount === orderedKeys.length;\n\n return {\n allSelected,\n isIndeterminate: selectedCount > 0 && !allSelected,\n };\n};\n"],"names":["assetKey","id","folderKey","getIdsOfKind","keys","kind","ids","Set","forEach","key","keyKind","split","add","Number","createEmptySelection","selectedKeys","anchorKey","toggleSelection","state","has","delete","selectRange","orderedKeys","targetKey","targetIndex","indexOf","anchorIndex","start","Math","min","end","max","slice","selectAll","length","clearSelection","getSelectAllState","allSelected","isIndeterminate","selectedCount","reduce","count"],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, view-agnostic selection logic for the future Media Library multi-select.
|
|
3
|
+
*
|
|
4
|
+
* Selection covers both assets and folders. Since their numeric ids live in
|
|
5
|
+
* different tables (and can collide), items are tracked as namespaced keys
|
|
6
|
+
* (`asset:1` / `folder:1`). Views build their rendered order as a key array so
|
|
7
|
+
* range / select-all work across the folder and asset sections seamlessly.
|
|
8
|
+
*/ const assetKey = (id)=>`asset:${id}`;
|
|
9
|
+
const folderKey = (id)=>`folder:${id}`;
|
|
10
|
+
/** Extracts the numeric ids of one kind from a set of item keys. */ const getIdsOfKind = (keys, kind)=>{
|
|
11
|
+
const ids = new Set();
|
|
12
|
+
keys.forEach((key)=>{
|
|
13
|
+
const [keyKind, id] = key.split(':');
|
|
14
|
+
if (keyKind === kind) {
|
|
15
|
+
ids.add(Number(id));
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
return ids;
|
|
19
|
+
};
|
|
20
|
+
const createEmptySelection = ()=>({
|
|
21
|
+
selectedKeys: new Set(),
|
|
22
|
+
anchorKey: null
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Additive toggle — adds the key if absent, removes it otherwise, without
|
|
26
|
+
* touching the rest of the selection. Used by the item checkbox and Cmd/Ctrl+click.
|
|
27
|
+
* The toggled key becomes the new anchor.
|
|
28
|
+
*/ const toggleSelection = (state, key)=>{
|
|
29
|
+
const selectedKeys = new Set(state.selectedKeys);
|
|
30
|
+
if (selectedKeys.has(key)) {
|
|
31
|
+
selectedKeys.delete(key);
|
|
32
|
+
} else {
|
|
33
|
+
selectedKeys.add(key);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
selectedKeys,
|
|
37
|
+
anchorKey: key
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Shift+click — selects the contiguous range between the current anchor and the
|
|
42
|
+
* target in render order, replacing the current selection. The anchor stays put.
|
|
43
|
+
*
|
|
44
|
+
* Falls back to a single selection when there is no usable anchor (e.g. first
|
|
45
|
+
* click was a Shift+click, or the anchor is no longer in the list).
|
|
46
|
+
*/ const selectRange = (state, orderedKeys, targetKey)=>{
|
|
47
|
+
const targetIndex = orderedKeys.indexOf(targetKey);
|
|
48
|
+
if (targetIndex === -1) {
|
|
49
|
+
return state;
|
|
50
|
+
}
|
|
51
|
+
const anchorIndex = state.anchorKey === null ? -1 : orderedKeys.indexOf(state.anchorKey);
|
|
52
|
+
if (anchorIndex === -1) {
|
|
53
|
+
return {
|
|
54
|
+
selectedKeys: new Set([
|
|
55
|
+
targetKey
|
|
56
|
+
]),
|
|
57
|
+
anchorKey: targetKey
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const start = Math.min(anchorIndex, targetIndex);
|
|
61
|
+
const end = Math.max(anchorIndex, targetIndex);
|
|
62
|
+
return {
|
|
63
|
+
selectedKeys: new Set(orderedKeys.slice(start, end + 1)),
|
|
64
|
+
anchorKey: state.anchorKey
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Select-all — selects every key in the provided ordered array. Used by the header
|
|
69
|
+
* checkbox when not everything is already selected.
|
|
70
|
+
*/ const selectAll = (orderedKeys)=>({
|
|
71
|
+
selectedKeys: new Set(orderedKeys),
|
|
72
|
+
anchorKey: orderedKeys.length > 0 ? orderedKeys[orderedKeys.length - 1] : null
|
|
73
|
+
});
|
|
74
|
+
const clearSelection = ()=>createEmptySelection();
|
|
75
|
+
/**
|
|
76
|
+
* Derives the header checkbox state: everything selected, some (indeterminate),
|
|
77
|
+
* or none. Only considers the currently-rendered items (orderedKeys).
|
|
78
|
+
*/ const getSelectAllState = (selectedKeys, orderedKeys)=>{
|
|
79
|
+
if (orderedKeys.length === 0) {
|
|
80
|
+
return {
|
|
81
|
+
allSelected: false,
|
|
82
|
+
isIndeterminate: false
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const selectedCount = orderedKeys.reduce((count, key)=>selectedKeys.has(key) ? count + 1 : count, 0);
|
|
86
|
+
const allSelected = selectedCount === orderedKeys.length;
|
|
87
|
+
return {
|
|
88
|
+
allSelected,
|
|
89
|
+
isIndeterminate: selectedCount > 0 && !allSelected
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { assetKey, clearSelection, createEmptySelection, folderKey, getIdsOfKind, getSelectAllState, selectAll, selectRange, toggleSelection };
|
|
94
|
+
//# sourceMappingURL=selection.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.mjs","sources":["../../../../../../admin/src/future/pages/Assets/utils/selection.ts"],"sourcesContent":["/**\n * Pure, view-agnostic selection logic for the future Media Library multi-select.\n *\n * Selection covers both assets and folders. Since their numeric ids live in\n * different tables (and can collide), items are tracked as namespaced keys\n * (`asset:1` / `folder:1`). Views build their rendered order as a key array so\n * range / select-all work across the folder and asset sections seamlessly.\n */\n\nexport type SelectableKind = 'asset' | 'folder';\n\nexport type ItemKey = `${SelectableKind}:${number}`;\n\nexport const assetKey = (id: number): ItemKey => `asset:${id}`;\n\nexport const folderKey = (id: number): ItemKey => `folder:${id}`;\n\n/** Extracts the numeric ids of one kind from a set of item keys. */\nexport const getIdsOfKind = (keys: Set<ItemKey>, kind: SelectableKind): Set<number> => {\n const ids = new Set<number>();\n\n keys.forEach((key) => {\n const [keyKind, id] = key.split(':');\n if (keyKind === kind) {\n ids.add(Number(id));\n }\n });\n\n return ids;\n};\n\nexport interface SelectionState {\n /** Currently selected item keys (assets and folders). */\n selectedKeys: Set<ItemKey>;\n /** Anchor used as the start of a Shift+click range. */\n anchorKey: ItemKey | null;\n}\n\nexport const createEmptySelection = (): SelectionState => ({\n selectedKeys: new Set<ItemKey>(),\n anchorKey: null,\n});\n\n/**\n * Additive toggle — adds the key if absent, removes it otherwise, without\n * touching the rest of the selection. Used by the item checkbox and Cmd/Ctrl+click.\n * The toggled key becomes the new anchor.\n */\nexport const toggleSelection = (state: SelectionState, key: ItemKey): SelectionState => {\n const selectedKeys = new Set(state.selectedKeys);\n\n if (selectedKeys.has(key)) {\n selectedKeys.delete(key);\n } else {\n selectedKeys.add(key);\n }\n\n return { selectedKeys, anchorKey: key };\n};\n\n/**\n * Shift+click — selects the contiguous range between the current anchor and the\n * target in render order, replacing the current selection. The anchor stays put.\n *\n * Falls back to a single selection when there is no usable anchor (e.g. first\n * click was a Shift+click, or the anchor is no longer in the list).\n */\nexport const selectRange = (\n state: SelectionState,\n orderedKeys: ItemKey[],\n targetKey: ItemKey\n): SelectionState => {\n const targetIndex = orderedKeys.indexOf(targetKey);\n\n if (targetIndex === -1) {\n return state;\n }\n\n const anchorIndex = state.anchorKey === null ? -1 : orderedKeys.indexOf(state.anchorKey);\n\n if (anchorIndex === -1) {\n return { selectedKeys: new Set<ItemKey>([targetKey]), anchorKey: targetKey };\n }\n\n const start = Math.min(anchorIndex, targetIndex);\n const end = Math.max(anchorIndex, targetIndex);\n\n return {\n selectedKeys: new Set(orderedKeys.slice(start, end + 1)),\n anchorKey: state.anchorKey,\n };\n};\n\n/**\n * Select-all — selects every key in the provided ordered array. Used by the header\n * checkbox when not everything is already selected.\n */\nexport const selectAll = (orderedKeys: ItemKey[]): SelectionState => ({\n selectedKeys: new Set(orderedKeys),\n anchorKey: orderedKeys.length > 0 ? orderedKeys[orderedKeys.length - 1] : null,\n});\n\nexport const clearSelection = (): SelectionState => createEmptySelection();\n\nexport interface SelectAllState {\n allSelected: boolean;\n isIndeterminate: boolean;\n}\n\n/**\n * Derives the header checkbox state: everything selected, some (indeterminate),\n * or none. Only considers the currently-rendered items (orderedKeys).\n */\nexport const getSelectAllState = (\n selectedKeys: Set<ItemKey>,\n orderedKeys: ItemKey[]\n): SelectAllState => {\n if (orderedKeys.length === 0) {\n return { allSelected: false, isIndeterminate: false };\n }\n\n const selectedCount = orderedKeys.reduce(\n (count, key) => (selectedKeys.has(key) ? count + 1 : count),\n 0\n );\n const allSelected = selectedCount === orderedKeys.length;\n\n return {\n allSelected,\n isIndeterminate: selectedCount > 0 && !allSelected,\n };\n};\n"],"names":["assetKey","id","folderKey","getIdsOfKind","keys","kind","ids","Set","forEach","key","keyKind","split","add","Number","createEmptySelection","selectedKeys","anchorKey","toggleSelection","state","has","delete","selectRange","orderedKeys","targetKey","targetIndex","indexOf","anchorIndex","start","Math","min","end","max","slice","selectAll","length","clearSelection","getSelectAllState","allSelected","isIndeterminate","selectedCount","reduce","count"],"mappings":"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"}
|
|
@@ -7,10 +7,14 @@ var files = require('../utils/files.js');
|
|
|
7
7
|
var uploadFileViaXHR = require('./uploadFileViaXHR.js');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Stores the original upload
|
|
10
|
+
* Stores the original upload batch (keyed by uploadId) to enable retry.
|
|
11
11
|
*/ const uploadRegistry = new Map();
|
|
12
|
-
const registerUploadEntries = (uploadId, entries)=>{
|
|
13
|
-
uploadRegistry.set(uploadId,
|
|
12
|
+
const registerUploadEntries = (uploadId, entries, generateAiMetadata, concurrency)=>{
|
|
13
|
+
uploadRegistry.set(uploadId, {
|
|
14
|
+
entries,
|
|
15
|
+
generateAiMetadata,
|
|
16
|
+
concurrency
|
|
17
|
+
});
|
|
14
18
|
};
|
|
15
19
|
const getUploadEntries = (uploadId)=>{
|
|
16
20
|
return uploadRegistry.get(uploadId);
|
|
@@ -39,6 +43,54 @@ const unregisterAbortController = (uploadId)=>{
|
|
|
39
43
|
unregisterAbortController(uploadId);
|
|
40
44
|
}
|
|
41
45
|
};
|
|
46
|
+
/** Maps a server per-file outcome onto the row's terminal metadata status. */ const METADATA_STATUS_BY_RESULT = {
|
|
47
|
+
success: 'generated',
|
|
48
|
+
skipped: 'skipped',
|
|
49
|
+
error: 'failed'
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Kicks off AI metadata generation for a freshly uploaded file.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately fire-and-forget — the caller must NOT await it:
|
|
55
|
+
* - generation for file N overlaps the upload of file N+1;
|
|
56
|
+
* - a generation failure can never affect the upload result (the upload already
|
|
57
|
+
* succeeded), which is why the promise is fully swallowed here.
|
|
58
|
+
*
|
|
59
|
+
* Every uploaded file is sent, including non-images: the server already classifies
|
|
60
|
+
* them (`ai-metadata.generateForFiles` returns `skipped` for anything whose mime
|
|
61
|
+
* isn't `image/*`) and duplicating that rule here would let the two drift apart.
|
|
62
|
+
* The upshot is that a non-image now reports "Metadata generation skipped" rather
|
|
63
|
+
* than silently showing no metadata state at all.
|
|
64
|
+
*/ const maybeGenerateMetadata = ({ file, index, uploadId, enabled, dispatch })=>{
|
|
65
|
+
if (!enabled || typeof file.id !== 'number') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
dispatch(uploadProgress.setFileMetadataGenerating({
|
|
69
|
+
index,
|
|
70
|
+
uploadId
|
|
71
|
+
}));
|
|
72
|
+
// `uploadApi` is declared below but only read at call time, long after the module
|
|
73
|
+
// has finished evaluating.
|
|
74
|
+
const promise = dispatch(uploadApi.endpoints.generateAiMetadata.initiate({
|
|
75
|
+
fileIds: [
|
|
76
|
+
file.id
|
|
77
|
+
]
|
|
78
|
+
}));
|
|
79
|
+
promise.unwrap().then((results)=>{
|
|
80
|
+
const result = results?.find((r)=>r.id === file.id) ?? results?.[0];
|
|
81
|
+
dispatch(uploadProgress.setFileMetadataResult({
|
|
82
|
+
index,
|
|
83
|
+
uploadId,
|
|
84
|
+
status: result ? METADATA_STATUS_BY_RESULT[result.status] : 'failed'
|
|
85
|
+
}));
|
|
86
|
+
}).catch(()=>{
|
|
87
|
+
dispatch(uploadProgress.setFileMetadataResult({
|
|
88
|
+
index,
|
|
89
|
+
uploadId,
|
|
90
|
+
status: 'failed'
|
|
91
|
+
}));
|
|
92
|
+
});
|
|
93
|
+
};
|
|
42
94
|
/**
|
|
43
95
|
* Uploads the given entry indices one at a time through the single-file endpoint.
|
|
44
96
|
*
|
|
@@ -46,16 +98,19 @@ const unregisterAbortController = (uploadId)=>{
|
|
|
46
98
|
* per-frame batcher to `setFileProgress`, awaits the XHR, then dispatches
|
|
47
99
|
* "complete" or "error". Each file is wrapped in its own try/catch so one failure
|
|
48
100
|
* does not stop the batch. An abort stops the loop without starting further files.
|
|
49
|
-
*/ const
|
|
101
|
+
*/ const runUploadPool = async ({ entries, indices, token, uploadId, abortController, dispatch, concurrency = 1, generateAiMetadata })=>{
|
|
50
102
|
const url = `${window.strapi.backendURL}/upload/unstable/upload-file`;
|
|
51
103
|
const uploaded = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
104
|
+
// Shared queue: each worker shifts the next index when it frees up, so N
|
|
105
|
+
// files are in flight at any moment (not N-sized waves). With concurrency 1
|
|
106
|
+
// this degenerates to the original strictly sequential loop.
|
|
107
|
+
const queue = [
|
|
108
|
+
...indices
|
|
109
|
+
];
|
|
110
|
+
const uploadOne = async (index)=>{
|
|
56
111
|
const entry = entries[index];
|
|
57
112
|
if (!entry) {
|
|
58
|
-
|
|
113
|
+
return;
|
|
59
114
|
}
|
|
60
115
|
const fileName = entry.fileInfo?.name ?? entry.file.name;
|
|
61
116
|
dispatch(uploadProgress.setFileUploading({
|
|
@@ -81,10 +136,20 @@ const unregisterAbortController = (uploadId)=>{
|
|
|
81
136
|
index,
|
|
82
137
|
file
|
|
83
138
|
}));
|
|
139
|
+
// Not awaited: overlaps with the next file's upload and can't fail the batch.
|
|
140
|
+
maybeGenerateMetadata({
|
|
141
|
+
file,
|
|
142
|
+
index,
|
|
143
|
+
uploadId,
|
|
144
|
+
enabled: generateAiMetadata,
|
|
145
|
+
dispatch
|
|
146
|
+
});
|
|
84
147
|
} catch (err) {
|
|
85
148
|
batcher.cancel();
|
|
86
149
|
if (err instanceof uploadFileViaXHR.UploadAbortedError) {
|
|
87
|
-
|
|
150
|
+
// Batch was cancelled — the worker loop checks the signal and stops.
|
|
151
|
+
// cancelUpload (dispatched from the dialog) marks the remaining rows.
|
|
152
|
+
return;
|
|
88
153
|
}
|
|
89
154
|
const message = err instanceof Error ? err.message : 'Upload failed';
|
|
90
155
|
dispatch(uploadProgress.setFileError({
|
|
@@ -93,7 +158,28 @@ const unregisterAbortController = (uploadId)=>{
|
|
|
93
158
|
message
|
|
94
159
|
}));
|
|
95
160
|
}
|
|
96
|
-
}
|
|
161
|
+
};
|
|
162
|
+
// Guard non-finite/invalid values (NaN, Infinity, <1): `Math.floor(NaN)`
|
|
163
|
+
// would propagate through Math.max to `Array.from({ length: NaN })` = zero
|
|
164
|
+
// workers, silently uploading nothing. Anything invalid falls back to 1.
|
|
165
|
+
const requested = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;
|
|
166
|
+
const workerCount = Math.min(Math.max(1, requested), queue.length);
|
|
167
|
+
const worker = async ()=>{
|
|
168
|
+
while(queue.length > 0){
|
|
169
|
+
if (abortController.signal.aborted) {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
const index = queue.shift();
|
|
173
|
+
if (index === undefined) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
// eslint-disable-next-line no-await-in-loop
|
|
177
|
+
await uploadOne(index);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
await Promise.all(Array.from({
|
|
181
|
+
length: workerCount
|
|
182
|
+
}, ()=>worker()));
|
|
97
183
|
unregisterAbortController(uploadId);
|
|
98
184
|
return {
|
|
99
185
|
data: uploaded
|
|
@@ -154,7 +240,7 @@ const unregisterAbortController = (uploadId)=>{
|
|
|
154
240
|
/**
|
|
155
241
|
* Processes an SSE stream from the URL upload endpoint.
|
|
156
242
|
* Dispatches Redux actions for each file event and returns the final result.
|
|
157
|
-
*/ const processSSEStream = async ({ response, dispatch })=>{
|
|
243
|
+
*/ const processSSEStream = async ({ response, dispatch, uploadId, generateAiMetadata })=>{
|
|
158
244
|
const reader = response.body.getReader();
|
|
159
245
|
const decoder = new TextDecoder();
|
|
160
246
|
let streamResult = null;
|
|
@@ -206,6 +292,14 @@ const unregisterAbortController = (uploadId)=>{
|
|
|
206
292
|
index,
|
|
207
293
|
file: payload.file
|
|
208
294
|
}));
|
|
295
|
+
// Same fire-and-forget semantics as the file flow.
|
|
296
|
+
maybeGenerateMetadata({
|
|
297
|
+
file: payload.file,
|
|
298
|
+
index,
|
|
299
|
+
uploadId,
|
|
300
|
+
enabled: generateAiMetadata,
|
|
301
|
+
dispatch
|
|
302
|
+
});
|
|
209
303
|
break;
|
|
210
304
|
}
|
|
211
305
|
case 'file:error':
|
|
@@ -242,10 +336,12 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
|
|
|
242
336
|
}).injectEndpoints({
|
|
243
337
|
endpoints: (builder)=>({
|
|
244
338
|
/**
|
|
245
|
-
* Upload files
|
|
246
|
-
*
|
|
339
|
+
* Upload files to `/upload/unstable/upload-file`, one request per file,
|
|
340
|
+
* through a worker pool of `concurrency` parallel requests (default 1 =
|
|
341
|
+
* sequential). Real per-file byte progress comes from
|
|
342
|
+
* `XHR.upload.onprogress`.
|
|
247
343
|
*/ uploadFiles: builder.mutation({
|
|
248
|
-
queryFn: async ({ formData, totalFiles }, { dispatch, getState })=>{
|
|
344
|
+
queryFn: async ({ formData, totalFiles, concurrency = 1, generateAiMetadata }, { dispatch, getState })=>{
|
|
249
345
|
const token = getState().admin_app?.token;
|
|
250
346
|
// Extract the original files and their per-file fileInfo from the combined FormData.
|
|
251
347
|
const files = formData.getAll('files');
|
|
@@ -271,17 +367,19 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
|
|
|
271
367
|
// Get the uploadId from state after dispatching
|
|
272
368
|
const uploadId = getState().uploadProgress.uploadId;
|
|
273
369
|
// Store original entries for retry functionality
|
|
274
|
-
registerUploadEntries(uploadId, entries);
|
|
370
|
+
registerUploadEntries(uploadId, entries, generateAiMetadata, concurrency);
|
|
275
371
|
// One AbortController per batch
|
|
276
372
|
const abortController = new AbortController();
|
|
277
373
|
registerAbortController(uploadId, abortController);
|
|
278
|
-
return
|
|
374
|
+
return runUploadPool({
|
|
279
375
|
entries,
|
|
280
376
|
indices: entries.map((_, index)=>index),
|
|
281
377
|
token,
|
|
282
378
|
uploadId,
|
|
283
379
|
abortController,
|
|
284
|
-
dispatch
|
|
380
|
+
dispatch,
|
|
381
|
+
concurrency,
|
|
382
|
+
generateAiMetadata
|
|
285
383
|
});
|
|
286
384
|
},
|
|
287
385
|
invalidatesTags: [
|
|
@@ -329,9 +427,9 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
|
|
|
329
427
|
/**
|
|
330
428
|
* Retry uploading cancelled files.
|
|
331
429
|
* Maps cancelled rows back to their original entries and re-runs only those
|
|
332
|
-
* through the same
|
|
430
|
+
* through the same upload pool with a fresh AbortController.
|
|
333
431
|
*/ retryCancelledFiles: builder.mutation({
|
|
334
|
-
queryFn: async (
|
|
432
|
+
queryFn: async (_arg, { dispatch, getState })=>{
|
|
335
433
|
const { uploadId, files: stateFiles } = getState().uploadProgress;
|
|
336
434
|
const token = getState().admin_app?.token;
|
|
337
435
|
const cancelledIndices = stateFiles.filter((f)=>f.status === 'cancelled').map((f)=>f.index);
|
|
@@ -343,8 +441,8 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
|
|
|
343
441
|
}
|
|
344
442
|
};
|
|
345
443
|
}
|
|
346
|
-
const
|
|
347
|
-
if (!
|
|
444
|
+
const batch = getUploadEntries(uploadId);
|
|
445
|
+
if (!batch) {
|
|
348
446
|
return {
|
|
349
447
|
error: {
|
|
350
448
|
name: 'UnknownError',
|
|
@@ -357,13 +455,16 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
|
|
|
357
455
|
// Fresh AbortController for the retry run
|
|
358
456
|
const abortController = new AbortController();
|
|
359
457
|
registerAbortController(uploadId, abortController);
|
|
360
|
-
return
|
|
361
|
-
entries,
|
|
458
|
+
return runUploadPool({
|
|
459
|
+
entries: batch.entries,
|
|
362
460
|
indices: cancelledIndices,
|
|
363
461
|
token,
|
|
364
462
|
uploadId,
|
|
365
463
|
abortController,
|
|
366
|
-
dispatch
|
|
464
|
+
dispatch,
|
|
465
|
+
// Replay the retried rows with the flags the batch was started with.
|
|
466
|
+
concurrency: batch.concurrency,
|
|
467
|
+
generateAiMetadata: batch.generateAiMetadata
|
|
367
468
|
});
|
|
368
469
|
},
|
|
369
470
|
invalidatesTags: [
|
|
@@ -377,7 +478,7 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
|
|
|
377
478
|
* Upload files from URLs.
|
|
378
479
|
* Sends URLs to the server which fetches and uploads them (SSE flow, unchanged).
|
|
379
480
|
*/ uploadFromUrls: builder.mutation({
|
|
380
|
-
queryFn: async ({ urls, folderId }, { dispatch, getState })=>{
|
|
481
|
+
queryFn: async ({ urls, folderId, generateAiMetadata }, { dispatch, getState })=>{
|
|
381
482
|
const token = getState().admin_app?.token;
|
|
382
483
|
// Extract filenames from URLs for the progress dialog
|
|
383
484
|
const fileNames = urls.map((url)=>files.getFilenameFromUrl(url));
|
|
@@ -426,7 +527,9 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
|
|
|
426
527
|
// Process SSE stream from server
|
|
427
528
|
const streamResult = await processSSEStream({
|
|
428
529
|
response,
|
|
429
|
-
dispatch
|
|
530
|
+
dispatch,
|
|
531
|
+
uploadId,
|
|
532
|
+
generateAiMetadata
|
|
430
533
|
});
|
|
431
534
|
unregisterAbortController(uploadId);
|
|
432
535
|
if (streamResult && streamResult.data.length > 0) {
|
|
@@ -468,13 +571,45 @@ const uploadApi = strapiAdmin.adminApi.enhanceEndpoints({
|
|
|
468
571
|
id: 'LIST'
|
|
469
572
|
}
|
|
470
573
|
]
|
|
574
|
+
}),
|
|
575
|
+
/**
|
|
576
|
+
* Generate AI metadata (alt text + caption) for the given assets.
|
|
577
|
+
* Synchronous: resolves once every file has been processed and reports the
|
|
578
|
+
* outcome per file, so non-images and individual failures don't fail the
|
|
579
|
+
* whole batch. Existing alt text and captions are never overwritten.
|
|
580
|
+
*
|
|
581
|
+
* Lives here rather than in `assets.ts` because the upload flows dispatch it
|
|
582
|
+
* directly via `initiate()` after each file completes, and `assets.ts` already
|
|
583
|
+
* imports from this module — defining it there would be a circular import.
|
|
584
|
+
* Re-exported from `assets.ts` for the components that consume the hook.
|
|
585
|
+
*/ generateAiMetadata: builder.mutation({
|
|
586
|
+
query: ({ fileIds })=>({
|
|
587
|
+
url: '/upload/unstable/generate-ai-metadata',
|
|
588
|
+
method: 'POST',
|
|
589
|
+
data: {
|
|
590
|
+
fileIds
|
|
591
|
+
}
|
|
592
|
+
}),
|
|
593
|
+
transformResponse: (response)=>response.data,
|
|
594
|
+
invalidatesTags: (_result, _error, { fileIds })=>[
|
|
595
|
+
...fileIds.map((id)=>({
|
|
596
|
+
type: 'Asset',
|
|
597
|
+
id
|
|
598
|
+
})),
|
|
599
|
+
{
|
|
600
|
+
type: 'Asset',
|
|
601
|
+
id: 'LIST'
|
|
602
|
+
}
|
|
603
|
+
]
|
|
471
604
|
})
|
|
472
605
|
})
|
|
473
606
|
});
|
|
474
|
-
const { useUploadFilesMutation, useUploadFileSilentlyMutation, useRetryCancelledFilesMutation, useUploadFromUrlsMutation } = uploadApi;
|
|
607
|
+
const { useUploadFilesMutation, useUploadFileSilentlyMutation, useRetryCancelledFilesMutation, useUploadFromUrlsMutation, useGenerateAiMetadataMutation } = uploadApi;
|
|
475
608
|
|
|
476
609
|
exports.abortUpload = abortUpload;
|
|
610
|
+
exports.maybeGenerateMetadata = maybeGenerateMetadata;
|
|
477
611
|
exports.uploadApi = uploadApi;
|
|
612
|
+
exports.useGenerateAiMetadataMutation = useGenerateAiMetadataMutation;
|
|
478
613
|
exports.useRetryCancelledFilesMutation = useRetryCancelledFilesMutation;
|
|
479
614
|
exports.useUploadFileSilentlyMutation = useUploadFileSilentlyMutation;
|
|
480
615
|
exports.useUploadFilesMutation = useUploadFilesMutation;
|