@strapi/upload 5.52.2 → 5.53.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.
- package/dist/admin/components/AssetCard/AssetCard.js +1 -0
- package/dist/admin/components/AssetCard/AssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/AssetCard.mjs +1 -0
- package/dist/admin/components/AssetCard/AssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetCard/AssetCardBase.js +1 -0
- package/dist/admin/components/AssetCard/AssetCardBase.js.map +1 -1
- package/dist/admin/components/AssetCard/AssetCardBase.mjs +1 -0
- package/dist/admin/components/AssetCard/AssetCardBase.mjs.map +1 -1
- package/dist/admin/components/AssetCard/ImageAssetCard.js +1 -0
- package/dist/admin/components/AssetCard/ImageAssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/ImageAssetCard.mjs +1 -0
- package/dist/admin/components/AssetCard/ImageAssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetCard/UploadingAssetCard.js +1 -0
- package/dist/admin/components/AssetCard/UploadingAssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/UploadingAssetCard.mjs +1 -0
- package/dist/admin/components/AssetCard/UploadingAssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetCard/VideoAssetCard.js +1 -0
- package/dist/admin/components/AssetCard/VideoAssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/VideoAssetCard.mjs +1 -0
- package/dist/admin/components/AssetCard/VideoAssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.js +1 -0
- package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.mjs +1 -0
- package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/Filters.js +1 -0
- package/dist/admin/components/AssetDialog/BrowseStep/Filters.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/Filters.mjs +1 -0
- package/dist/admin/components/AssetDialog/BrowseStep/Filters.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/SearchAsset/SearchAsset.js +1 -0
- package/dist/admin/components/AssetDialog/BrowseStep/SearchAsset/SearchAsset.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/SearchAsset/SearchAsset.mjs +1 -0
- package/dist/admin/components/AssetDialog/BrowseStep/SearchAsset/SearchAsset.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/SelectedStep/SelectedStep.js +1 -0
- package/dist/admin/components/AssetDialog/SelectedStep/SelectedStep.js.map +1 -1
- package/dist/admin/components/AssetDialog/SelectedStep/SelectedStep.mjs +1 -0
- package/dist/admin/components/AssetDialog/SelectedStep/SelectedStep.mjs.map +1 -1
- package/dist/admin/components/BulkMoveDialog/BulkMoveDialog.js +1 -0
- package/dist/admin/components/BulkMoveDialog/BulkMoveDialog.js.map +1 -1
- package/dist/admin/components/BulkMoveDialog/BulkMoveDialog.mjs +1 -0
- package/dist/admin/components/BulkMoveDialog/BulkMoveDialog.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/EditAssetContent.js +1 -0
- package/dist/admin/components/EditAssetDialog/EditAssetContent.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs +1 -0
- package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.js +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.mjs +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/CroppingActions.js +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/CroppingActions.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/CroppingActions.mjs +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/CroppingActions.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/FocalPointActions.js +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/FocalPointActions.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/FocalPointActions.mjs +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/FocalPointActions.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.js +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.mjs +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/ReplaceMediaButton.js +1 -0
- package/dist/admin/components/EditAssetDialog/ReplaceMediaButton.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/ReplaceMediaButton.mjs +1 -0
- package/dist/admin/components/EditAssetDialog/ReplaceMediaButton.mjs.map +1 -1
- package/dist/admin/components/EditFolderDialog/ModalHeader/ModalHeader.js +1 -0
- package/dist/admin/components/EditFolderDialog/ModalHeader/ModalHeader.js.map +1 -1
- package/dist/admin/components/EditFolderDialog/ModalHeader/ModalHeader.mjs +1 -0
- package/dist/admin/components/EditFolderDialog/ModalHeader/ModalHeader.mjs.map +1 -1
- package/dist/admin/components/FilterList/FilterList.js +2 -2
- package/dist/admin/components/FilterList/FilterList.js.map +1 -1
- package/dist/admin/components/FilterList/FilterList.mjs +2 -2
- package/dist/admin/components/FilterList/FilterList.mjs.map +1 -1
- package/dist/admin/components/FilterPopover/FilterPopover.js +4 -4
- package/dist/admin/components/FilterPopover/FilterPopover.js.map +1 -1
- package/dist/admin/components/FilterPopover/FilterPopover.mjs +4 -4
- package/dist/admin/components/FilterPopover/FilterPopover.mjs.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAsset.js +1 -0
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAsset.js.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAsset.mjs +1 -0
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAsset.mjs.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssetActions.js +1 -0
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssetActions.js.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssetActions.mjs +1 -0
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssetActions.mjs.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/EmptyStateAsset.js +4 -10
- package/dist/admin/components/MediaLibraryInput/Carousel/EmptyStateAsset.js.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/EmptyStateAsset.mjs +4 -10
- package/dist/admin/components/MediaLibraryInput/Carousel/EmptyStateAsset.mjs.map +1 -1
- package/dist/admin/components/SortPicker/SortPicker.js +1 -0
- package/dist/admin/components/SortPicker/SortPicker.js.map +1 -1
- package/dist/admin/components/SortPicker/SortPicker.mjs +1 -0
- package/dist/admin/components/SortPicker/SortPicker.mjs.map +1 -1
- package/dist/admin/components/TableList/CellContent.js +1 -0
- package/dist/admin/components/TableList/CellContent.js.map +1 -1
- package/dist/admin/components/TableList/CellContent.mjs +1 -0
- package/dist/admin/components/TableList/CellContent.mjs.map +1 -1
- package/dist/admin/components/TableList/PreviewCell.js +1 -0
- package/dist/admin/components/TableList/PreviewCell.js.map +1 -1
- package/dist/admin/components/TableList/PreviewCell.mjs +1 -0
- package/dist/admin/components/TableList/PreviewCell.mjs.map +1 -1
- package/dist/admin/components/TableList/TableList.js +1 -0
- package/dist/admin/components/TableList/TableList.js.map +1 -1
- package/dist/admin/components/TableList/TableList.mjs +1 -0
- package/dist/admin/components/TableList/TableList.mjs.map +1 -1
- package/dist/admin/components/TableList/TableRows.js +1 -0
- package/dist/admin/components/TableList/TableRows.js.map +1 -1
- package/dist/admin/components/TableList/TableRows.mjs +1 -0
- package/dist/admin/components/TableList/TableRows.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/AddAssetStep.js +1 -0
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/AddAssetStep.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/AddAssetStep.mjs +1 -0
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/AddAssetStep.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromComputerForm.js +9 -21
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromComputerForm.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromComputerForm.mjs +9 -21
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromComputerForm.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js +1 -0
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs +1 -0
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.js +1 -0
- package/dist/admin/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.mjs +1 -0
- package/dist/admin/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.mjs.map +1 -1
- package/dist/admin/constants.js +1 -0
- package/dist/admin/constants.js.map +1 -1
- package/dist/admin/constants.mjs +1 -0
- package/dist/admin/constants.mjs.map +1 -1
- package/dist/admin/future/components/Drawer.js +17 -3
- package/dist/admin/future/components/Drawer.js.map +1 -1
- package/dist/admin/future/components/Drawer.mjs +17 -3
- package/dist/admin/future/components/Drawer.mjs.map +1 -1
- package/dist/admin/future/components/UploadProgressDialog.js +46 -12
- package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
- package/dist/admin/future/components/UploadProgressDialog.mjs +47 -13
- package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.js +27 -2
- package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.mjs +27 -2
- package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js +9 -1
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs +9 -1
- package/dist/admin/future/pages/Assets/components/AssetActionsMenu.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js +19 -4
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +19 -4
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +64 -10
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +66 -12
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js +17 -5
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +17 -5
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.js +56 -28
- package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +58 -30
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js +203 -83
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs +207 -87
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js +21 -3
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs +21 -3
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/MainAreaContextMenu.js +181 -0
- package/dist/admin/future/pages/Assets/components/MainAreaContextMenu.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/MainAreaContextMenu.mjs +179 -0
- package/dist/admin/future/pages/Assets/components/MainAreaContextMenu.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/constants.js +22 -0
- package/dist/admin/future/pages/Assets/constants.js.map +1 -1
- package/dist/admin/future/pages/Assets/constants.mjs +19 -1
- package/dist/admin/future/pages/Assets/constants.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js +4 -3
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs +5 -4
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js +105 -12
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs +106 -13
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useListFilters.js +5 -5
- package/dist/admin/future/pages/Assets/hooks/useListFilters.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useListFilters.mjs +6 -6
- package/dist/admin/future/pages/Assets/hooks/useListFilters.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useListScrollRestoration.js +111 -0
- package/dist/admin/future/pages/Assets/hooks/useListScrollRestoration.js.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useListScrollRestoration.mjs +109 -0
- package/dist/admin/future/pages/Assets/hooks/useListScrollRestoration.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useListSort.js +10 -10
- package/dist/admin/future/pages/Assets/hooks/useListSort.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useListSort.mjs +11 -11
- package/dist/admin/future/pages/Assets/hooks/useListSort.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/utils/mergeUploadedAssets.js +86 -0
- package/dist/admin/future/pages/Assets/utils/mergeUploadedAssets.js.map +1 -0
- package/dist/admin/future/pages/Assets/utils/mergeUploadedAssets.mjs +83 -0
- package/dist/admin/future/pages/Assets/utils/mergeUploadedAssets.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/utils/renderedKeys.js +19 -0
- package/dist/admin/future/pages/Assets/utils/renderedKeys.js.map +1 -0
- package/dist/admin/future/pages/Assets/utils/renderedKeys.mjs +17 -0
- package/dist/admin/future/pages/Assets/utils/renderedKeys.mjs.map +1 -0
- package/dist/admin/future/services/api.js +19 -6
- package/dist/admin/future/services/api.js.map +1 -1
- package/dist/admin/future/services/api.mjs +19 -6
- package/dist/admin/future/services/api.mjs.map +1 -1
- package/dist/admin/future/store/uploadProgress.js +55 -2
- package/dist/admin/future/store/uploadProgress.js.map +1 -1
- package/dist/admin/future/store/uploadProgress.mjs +53 -3
- package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
- package/dist/admin/index.js +1 -0
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +1 -0
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/components/Settings.js +1 -0
- package/dist/admin/pages/App/ConfigureTheView/components/Settings.js.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/components/Settings.mjs +1 -0
- package/dist/admin/pages/App/ConfigureTheView/components/Settings.mjs.map +1 -1
- package/dist/admin/pages/App/MediaLibrary.js +7 -9
- package/dist/admin/pages/App/MediaLibrary.js.map +1 -1
- package/dist/admin/pages/App/MediaLibrary.mjs +8 -10
- package/dist/admin/pages/App/MediaLibrary.mjs.map +1 -1
- package/dist/admin/pages/App/components/BulkActions.js +1 -0
- package/dist/admin/pages/App/components/BulkActions.js.map +1 -1
- package/dist/admin/pages/App/components/BulkActions.mjs +1 -0
- package/dist/admin/pages/App/components/BulkActions.mjs.map +1 -1
- package/dist/admin/pages/App/components/EmptyOrNoPermissions.js +1 -0
- package/dist/admin/pages/App/components/EmptyOrNoPermissions.js.map +1 -1
- package/dist/admin/pages/App/components/EmptyOrNoPermissions.mjs +1 -0
- package/dist/admin/pages/App/components/EmptyOrNoPermissions.mjs.map +1 -1
- package/dist/admin/pages/App/components/Filters.js +8 -8
- package/dist/admin/pages/App/components/Filters.js.map +1 -1
- package/dist/admin/pages/App/components/Filters.mjs +9 -9
- package/dist/admin/pages/App/components/Filters.mjs.map +1 -1
- package/dist/admin/pages/App/components/Header.js +1 -1
- package/dist/admin/pages/App/components/Header.js.map +1 -1
- package/dist/admin/pages/App/components/Header.mjs +2 -2
- package/dist/admin/pages/App/components/Header.mjs.map +1 -1
- package/dist/admin/src/future/components/Drawer.d.ts +10 -0
- package/dist/admin/src/future/pages/Assets/components/AssetsGrid.d.ts +4 -1
- package/dist/admin/src/future/pages/Assets/components/AssetsTable.d.ts +4 -1
- package/dist/admin/src/future/pages/Assets/components/BulkActionsBar.d.ts +8 -1
- package/dist/admin/src/future/pages/Assets/components/MainAreaContextMenu.d.ts +28 -0
- package/dist/admin/src/future/pages/Assets/constants.d.ts +20 -0
- package/dist/admin/src/future/pages/Assets/hooks/useInfiniteAssets.d.ts +4 -0
- package/dist/admin/src/future/pages/Assets/hooks/useListScrollRestoration.d.ts +15 -0
- package/dist/admin/src/future/pages/Assets/utils/mergeUploadedAssets.d.ts +35 -0
- package/dist/admin/src/future/pages/Assets/utils/renderedKeys.d.ts +19 -0
- package/dist/admin/src/future/store/uploadProgress.d.ts +51 -0
- package/dist/admin/src/utils/index.d.ts +1 -0
- package/dist/admin/src/utils/rawFileToAsset.d.ts +2 -2
- package/dist/admin/src/utils/resolveFileMime.d.ts +13 -0
- package/dist/admin/translations/en.json.js +5 -1
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +5 -1
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/admin/utils/getFolderURL.js +2 -1
- package/dist/admin/utils/getFolderURL.js.map +1 -1
- package/dist/admin/utils/getFolderURL.mjs +2 -1
- package/dist/admin/utils/getFolderURL.mjs.map +1 -1
- package/dist/admin/utils/rawFileToAsset.js +5 -3
- package/dist/admin/utils/rawFileToAsset.js.map +1 -1
- package/dist/admin/utils/rawFileToAsset.mjs +5 -3
- package/dist/admin/utils/rawFileToAsset.mjs.map +1 -1
- package/dist/admin/utils/resolveFileMime.js +69 -0
- package/dist/admin/utils/resolveFileMime.js.map +1 -0
- package/dist/admin/utils/resolveFileMime.mjs +64 -0
- package/dist/admin/utils/resolveFileMime.mjs.map +1 -0
- package/dist/admin/utils/urlsToAssets.js +8 -4
- package/dist/admin/utils/urlsToAssets.js.map +1 -1
- package/dist/admin/utils/urlsToAssets.mjs +8 -4
- package/dist/admin/utils/urlsToAssets.mjs.map +1 -1
- package/dist/server/services/upload.js +20 -8
- package/dist/server/services/upload.js.map +1 -1
- package/dist/server/services/upload.mjs +20 -8
- package/dist/server/services/upload.mjs.map +1 -1
- package/dist/server/src/index.d.ts +2 -0
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +2 -0
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/upload.d.ts +2 -0
- package/dist/server/src/services/upload.d.ts.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInfiniteAssets.js","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useInfiniteAssets.ts"],"sourcesContent":["import { createElement, Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useDispatch, useStore } from 'react-redux';\n\nimport { uploadApi } from '../../../services/api';\nimport { useGetAssetsQuery } from '../../../services/assets';\n\nimport type { File, Pagination } from '../../../../../../shared/contracts/files';\n\nconst PAGE_SIZE = 20;\n\ninterface UseInfiniteAssetsOptions {\n folder?: number | null;\n sort?: string;\n search?: string;\n /** Extra `filters[$and]` entries for the files query. */\n filters?: Record<string, unknown>[];\n /** Structural switch: an `is [folder]`-only type badge means no file can match. */\n enabled?: boolean;\n}\n\ninterface QueryArgs {\n folder: number | null;\n sort?: string;\n search?: string;\n filters?: Record<string, unknown>[];\n}\n\n/**\n * Everything loaded for one query identity.\n *\n * `queryKey` lags the live key while a new query is in flight — that lag is\n * what keeps the previous results on screen across a search change.\n */\ninterface Accumulated {\n queryKey: string;\n listKey: string;\n pages: Record<number, File[]>;\n pagination?: Pagination;\n}\n\ninterface PageState {\n queryKey: string;\n page: number;\n}\n\ntype PageRefreshedHandler = (page: number, results: File[]) => void;\n\n/**\n * Concatenates the pages in ascending order, one entry per asset id.\n *\n * `Map.set` keeps the first insertion position but takes the latest value, so\n * an asset that moved between pages after a mutation holds its place and shows\n * fresh data instead of rendering twice under a duplicate key.\n */\nconst flattenPages = (pages: Record<number, File[]>): File[] => {\n const byId = new Map<number, File>();\n\n for (const page of Object.keys(pages)\n .map(Number)\n .sort((a, b) => a - b)) {\n for (const asset of pages[page]) {\n byId.set(asset.id, asset);\n }\n }\n\n return [...byId.values()];\n};\n\n/**\n * Keeps one earlier page's `getAssets` query subscribed and reports its results\n * back up whenever they change.\n *\n * The hook itself only subscribes the current (latest) page. Without these, a\n * mutation invalidating `{Asset, LIST}` refetches that page alone and earlier\n * pages stay frozen — a rename or delete on an earlier page never reaches the\n * list until a folder/sort/search change. One subscriber per earlier\n * page keeps them all subscribed so the invalidation refetches the whole list.\n * Renders nothing; it exists only for the subscription + `onRefreshed` report.\n */\nconst PageSubscriber = ({\n queryArgs,\n page,\n onRefreshed,\n}: {\n queryArgs: QueryArgs;\n page: number;\n onRefreshed: PageRefreshedHandler;\n}) => {\n const { currentData } = useGetAssetsQuery({ ...queryArgs, page, pageSize: PAGE_SIZE });\n const results = currentData?.results;\n\n useEffect(() => {\n if (results) {\n onRefreshed(page, results);\n }\n // `results` is a stable reference per cache entry, so this only re-reports\n // when a refetch actually replaces the page's data. Reporting the same\n // reference the accumulator already holds is a no-op (see `onRefreshed`).\n }, [results, page, onRefreshed]);\n\n return null;\n};\n\n/**\n * Infinite scroll over `/upload/files`, keeping one accumulated slot per page.\n *\n * The caller MUST render the returned `subscribers` node — that is what keeps\n * every loaded page subscribed, so `{Asset, LIST}` invalidations keep the whole\n * list consistent rather than only the current page.\n */\nconst useInfiniteAssets = ({\n folder = null,\n sort,\n search,\n filters,\n enabled = true,\n}: UseInfiniteAssetsOptions = {}) => {\n // Derived from the request args themselves, so a new filter can't reach the\n // API without also invalidating what was accumulated under the old one.\n const queryArgs: QueryArgs = { folder, sort, search, filters };\n const queryKey = JSON.stringify(queryArgs);\n // Identifies the list being viewed, ignoring the search term. Folder, sort\n // and filter changes are single committed actions, so one clean reset per\n // change is honest. Search is committed per keystroke on a 300ms debounce,\n // and blanking the list on each one would strobe as the user types.\n // The cost: through the search window the previous rows stay rendered and\n // `hasNextPage` is false, so the list reads as complete until page 1 lands.\n const listKey = JSON.stringify({ folder, sort, filters });\n\n const [pageState, setPageState] = useState<PageState>({ queryKey, page: 1 });\n const [accumulated, setAccumulated] = useState<Accumulated>({ queryKey, listKey, pages: {} });\n\n // Derived rather than read straight from state: the render that discovers\n // the key change still reaches the query below, and a list with no page 1\n // must never be requested at page 2.\n const page = pageState.queryKey === queryKey ? pageState.page : 1;\n\n if (pageState.queryKey !== queryKey) {\n setPageState({ queryKey, page: 1 });\n }\n\n const { currentData, isLoading, isFetching, error } = useGetAssetsQuery(\n {\n ...queryArgs,\n page,\n pageSize: PAGE_SIZE,\n },\n { skip: !enabled }\n );\n\n const isSameQuery = accumulated.queryKey === queryKey;\n\n // `currentData` is only ever the payload for the args just passed, so it\n // belongs to this key at this page — nothing to infer. Its reference is\n // stable per cache entry, so this settles after one extra render.\n if (currentData && (!isSameQuery || accumulated.pages[page] !== currentData.results)) {\n setAccumulated(\n isSameQuery\n ? {\n ...accumulated,\n pages: { ...accumulated.pages, [page]: currentData.results },\n pagination: currentData.pagination,\n }\n : {\n queryKey,\n listKey,\n pages: { [page]: currentData.results },\n pagination: currentData.pagination,\n }\n );\n }\n\n // Merges a refetched earlier page back into the accumulator. Ignores reports\n // for a superseded query, and no-ops when the reference is unchanged so a\n // subscriber re-reporting its already-stored page can't loop.\n const handlePageRefreshed = useCallback<PageRefreshedHandler>(\n (refreshedPage, results) => {\n setAccumulated((prev) => {\n if (prev.queryKey !== queryKey || prev.pages[refreshedPage] === results) {\n return prev;\n }\n\n return { ...prev, pages: { ...prev.pages, [refreshedPage]: results } };\n });\n },\n [queryKey]\n );\n\n // One subscriber per earlier page (the current page is already subscribed by\n // the query above). Keyed by queryKey so a folder/sort/search change remounts\n // them against the new args instead of reusing a stale cache entry.\n // `createElement` rather than JSX so this stays a plain `.ts` hook file.\n const subscribers = createElement(\n Fragment,\n null,\n Array.from({ length: Math.max(0, page - 1) }, (_, index) => index + 1).map((earlierPage) =>\n createElement(PageSubscriber, {\n key: `${queryKey}:${earlierPage}`,\n queryArgs,\n page: earlierPage,\n onRefreshed: handlePageRefreshed,\n })\n )\n );\n\n // Drop the left folder's cached `getAssets` pages when the folder changes.\n //\n // RTK Query 1.9.7 leaves a query's store subscription in place when its last\n // subscriber unmounts while a refetch is still in flight — e.g. a delete\n // invalidates `{Asset, LIST}`, every loaded page starts refetching, and the\n // user navigates away before those settle. Those stale subscriptions make the\n // next `{Asset, LIST}` invalidation (an upload in the new folder) refetch the\n // *previous* folder's pages: one bogus `/upload/files` request per loaded\n // page. There is no public per-entry unsubscribe, so evict the left folder's\n // entries outright — returning reloads from page 1, which the accumulator\n // already does on a list change.\n const dispatch = useDispatch();\n const store = useStore();\n const prevFolderRef = useRef(folder);\n useEffect(() => {\n const prevFolder = prevFolderRef.current;\n prevFolderRef.current = folder;\n if (prevFolder === folder) {\n return;\n }\n\n const apiState = (store.getState() as Record<string, { queries?: Record<string, unknown> }>)[\n uploadApi.reducerPath\n ];\n const removeQueryResult = (\n uploadApi as unknown as {\n internalActions: {\n removeQueryResult: (payload: { queryCacheKey: string }) => { type: string };\n };\n }\n ).internalActions.removeQueryResult;\n\n Object.keys(apiState?.queries ?? {}).forEach((cacheKey) => {\n if (!cacheKey.startsWith('getAssets(')) {\n return;\n }\n let args: { folder?: number | null };\n try {\n args = JSON.parse(cacheKey.slice('getAssets('.length, -1));\n } catch {\n return;\n }\n if (args.folder === prevFolder) {\n dispatch(removeQueryResult({ queryCacheKey: cacheKey }));\n }\n });\n }, [folder, dispatch, store]);\n\n // Until the new list's first page lands the accumulator still holds the\n // previous folder. Reported as loading so the page shows a spinner instead\n // of the outgoing folder's assets under the incoming folder's header.\n const isChangingList = accumulated.listKey !== listKey;\n\n const assets = useMemo(\n () => (isChangingList ? [] : flattenPages(accumulated.pages)),\n [isChangingList, accumulated.pages]\n );\n\n // Deliberately the live value, not the stale-tolerant one below: paging must\n // never be driven by a total that belongs to the previous query.\n const hasNextPage = currentData ? page < currentData.pagination.pageCount : false;\n const isFetchingMore = isFetching && page > 1;\n\n const fetchNextPage = useCallback(() => {\n setPageState((prev) => ({\n queryKey,\n page: (prev.queryKey === queryKey ? prev.page : 1) + 1,\n }));\n }, [queryKey]);\n\n if (!enabled) {\n return {\n assets: [] as File[],\n // No list to keep fresh while disabled, so nothing to subscribe.\n subscribers: null,\n pagination: undefined,\n isLoading: false,\n isFetchingMore: false,\n hasNextPage: false,\n fetchNextPage,\n error: undefined,\n };\n }\n\n return {\n assets,\n subscribers,\n // Falls back to the accumulated total so a consumer showing the count\n // doesn't flash zero mid-transition, matching the possibly stale `assets`.\n pagination: currentData?.pagination ?? accumulated.pagination,\n isLoading: isLoading || isChangingList,\n isFetchingMore,\n hasNextPage,\n fetchNextPage,\n error,\n };\n};\n\nexport { useInfiniteAssets };\nexport { PAGE_SIZE };\n"],"names":["PAGE_SIZE","flattenPages","pages","byId","Map","page","Object","keys","map","Number","sort","a","b","asset","set","id","values","PageSubscriber","queryArgs","onRefreshed","currentData","useGetAssetsQuery","pageSize","results","useEffect","useInfiniteAssets","folder","search","filters","enabled","queryKey","JSON","stringify","listKey","pageState","setPageState","useState","accumulated","setAccumulated","isLoading","isFetching","error","skip","isSameQuery","pagination","handlePageRefreshed","useCallback","refreshedPage","prev","subscribers","createElement","Fragment","Array","from","length","Math","max","_","index","earlierPage","key","dispatch","useDispatch","store","useStore","prevFolderRef","useRef","prevFolder","current","apiState","getState","uploadApi","reducerPath","removeQueryResult","internalActions","queries","forEach","cacheKey","startsWith","args","parse","slice","queryCacheKey","isChangingList","assets","useMemo","hasNextPage","pageCount","isFetchingMore","fetchNextPage","undefined"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useInfiniteAssets.js","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useInfiniteAssets.ts"],"sourcesContent":["import { createElement, Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useDispatch, useStore } from 'react-redux';\n\nimport { uploadApi } from '../../../services/api';\nimport { useGetAssetsQuery } from '../../../services/assets';\nimport { useTypedSelector } from '../../../store/hooks';\nimport { selectCompletedUploads } from '../../../store/uploadProgress';\nimport { getRelationId } from '../../../utils/getRelationId';\nimport { mergeUploadedAssets } from '../utils/mergeUploadedAssets';\n\nimport type { File, Pagination } from '../../../../../../shared/contracts/files';\n\nconst PAGE_SIZE = 20;\n\n/**\n * How many list views keep their loaded pages in memory.\n */\nconst MAX_REMEMBERED_LISTS = 10;\n\ninterface UseInfiniteAssetsOptions {\n folder?: number | null;\n sort?: string;\n search?: string;\n /** Extra `filters[$and]` entries for the files query. */\n filters?: Record<string, unknown>[];\n /** Structural switch: an `is [folder]`-only type badge means no file can match. */\n enabled?: boolean;\n}\n\ninterface QueryArgs {\n folder: number | null;\n sort?: string;\n search?: string;\n filters?: Record<string, unknown>[];\n}\n\n/**\n * Everything loaded for one query identity.\n *\n * `queryKey` lags the live key while a new query is in flight — that lag is\n * what keeps the previous results on screen across a search change.\n */\ninterface Accumulated {\n queryKey: string;\n listKey: string;\n pages: Record<number, File[]>;\n pagination?: Pagination;\n}\n\ninterface PageState {\n queryKey: string;\n page: number;\n}\n\ntype PageRefreshedHandler = (page: number, results: File[]) => void;\n\n/**\n * Concatenates the pages in ascending order, one entry per asset id.\n *\n * `Map.set` keeps the first insertion position but takes the latest value, so\n * an asset that moved between pages after a mutation holds its place and shows\n * fresh data instead of rendering twice under a duplicate key.\n */\nconst flattenPages = (pages: Record<number, File[]>): File[] => {\n const byId = new Map<number, File>();\n\n for (const page of Object.keys(pages)\n .map(Number)\n .sort((a, b) => a - b)) {\n for (const asset of pages[page]) {\n byId.set(asset.id, asset);\n }\n }\n\n return [...byId.values()];\n};\n\n/**\n * The highest page an accumulator holds — where the list was last read to, and\n * so where to resume from on the way back.\n */\nconst deepestPage = (pages: Record<number, File[]>): number =>\n Object.keys(pages).reduce((deepest, key) => Math.max(deepest, Number(key)), 1);\n\n/**\n * Keeps one earlier page's `getAssets` query subscribed and reports its results\n * back up whenever they change.\n *\n * The hook itself only subscribes the current (latest) page. Without these, a\n * mutation invalidating `{Asset, LIST}` refetches that page alone and earlier\n * pages stay frozen — a rename or delete on an earlier page never reaches the\n * list until a folder/sort/search change. One subscriber per earlier\n * page keeps them all subscribed so the invalidation refetches the whole list.\n * Renders nothing; it exists only for the subscription + `onRefreshed` report.\n */\nconst PageSubscriber = ({\n queryArgs,\n page,\n onRefreshed,\n}: {\n queryArgs: QueryArgs;\n page: number;\n onRefreshed: PageRefreshedHandler;\n}) => {\n const { currentData } = useGetAssetsQuery({ ...queryArgs, page, pageSize: PAGE_SIZE });\n const results = currentData?.results;\n\n useEffect(() => {\n if (results) {\n onRefreshed(page, results);\n }\n // `results` is a stable reference per cache entry, so this only re-reports\n // when a refetch actually replaces the page's data. Reporting the same\n // reference the accumulator already holds is a no-op (see `onRefreshed`).\n }, [results, page, onRefreshed]);\n\n return null;\n};\n\n/**\n * Infinite scroll over `/upload/files`, keeping one accumulated slot per page.\n *\n * The caller MUST render the returned `subscribers` node — that is what keeps\n * every loaded page subscribed, so `{Asset, LIST}` invalidations keep the whole\n * list consistent rather than only the current page.\n *\n * Loaded pages survive navigating away and back: each list's accumulator is\n * remembered (LRU, `MAX_REMEMBERED_LISTS`) and restored when its key returns, so\n * stepping into a subfolder and back out keeps the rows the user scrolled for.\n */\nconst useInfiniteAssets = ({\n folder = null,\n sort,\n search,\n filters,\n enabled = true,\n}: UseInfiniteAssetsOptions = {}) => {\n // Derived from the request args themselves, so a new filter can't reach the\n // API without also invalidating what was accumulated under the old one.\n const queryArgs: QueryArgs = { folder, sort, search, filters };\n const queryKey = JSON.stringify(queryArgs);\n // Identifies the list being viewed, ignoring the search term. Folder, sort\n // and filter changes are single committed actions, so one clean reset per\n // change is honest. Search is committed per keystroke on a 300ms debounce,\n // and blanking the list on each one would strobe as the user types.\n // The cost: through the search window the previous rows stay rendered and\n // `hasNextPage` is false, so the list reads as complete until page 1 lands.\n const listKey = JSON.stringify({ folder, sort, filters });\n\n const [pageState, setPageState] = useState<PageState>({ queryKey, page: 1 });\n const [accumulated, setAccumulated] = useState<Accumulated>({ queryKey, listKey, pages: {} });\n\n // Each list's loaded pages, kept across navigation away from it.\n const memoryRef = useRef(new Map<string, Accumulated>());\n\n const isNewList = pageState.queryKey !== queryKey;\n\n const remembered = isNewList ? memoryRef.current.get(queryKey) : undefined;\n\n // Derived rather than read from state: the render that discovers the key\n // change still reaches the query below, and a list with no page 1 must never\n // be requested at page 2. A remembered list always holds page 1, so resuming\n // at its deepest page can't skip one.\n let page: number;\n if (!isNewList) {\n page = pageState.page;\n } else if (remembered) {\n page = deepestPage(remembered.pages);\n } else {\n page = 1;\n }\n\n if (isNewList) {\n setPageState({ queryKey, page });\n\n if (remembered) {\n setAccumulated(remembered);\n }\n }\n\n const { currentData, isLoading, isFetching, error, startedTimeStamp, fulfilledTimeStamp } =\n useGetAssetsQuery(\n {\n ...queryArgs,\n page,\n pageSize: PAGE_SIZE,\n },\n { skip: !enabled }\n );\n\n const isSameQuery = accumulated.queryKey === queryKey;\n\n // `currentData` is only ever the payload for the args just passed, so it\n // belongs to this key at this page — nothing to infer. Its reference is\n // stable per cache entry, so this settles after one extra render.\n if (\n // A restore already set the accumulator from memory; don't overwrite it.\n !remembered &&\n currentData &&\n (!isSameQuery || accumulated.pages[page] !== currentData.results)\n ) {\n setAccumulated(\n isSameQuery\n ? {\n ...accumulated,\n pages: { ...accumulated.pages, [page]: currentData.results },\n pagination: currentData.pagination,\n }\n : {\n queryKey,\n listKey,\n pages: { [page]: currentData.results },\n pagination: currentData.pagination,\n }\n );\n }\n\n // Merges a refetched earlier page back into the accumulator. Ignores reports\n // for a superseded query, and no-ops when the reference is unchanged so a\n // subscriber re-reporting its already-stored page can't loop.\n const handlePageRefreshed = useCallback<PageRefreshedHandler>(\n (refreshedPage, results) => {\n setAccumulated((prev) => {\n if (prev.queryKey !== queryKey || prev.pages[refreshedPage] === results) {\n return prev;\n }\n\n return { ...prev, pages: { ...prev.pages, [refreshedPage]: results } };\n });\n },\n [queryKey]\n );\n\n // One subscriber per earlier page (the current page is already subscribed by\n // the query above). Keyed by queryKey so a folder/sort/search change remounts\n // them against the new args instead of reusing a stale cache entry.\n // `createElement` rather than JSX so this stays a plain `.ts` hook file.\n const subscribers = createElement(\n Fragment,\n null,\n Array.from({ length: Math.max(0, page - 1) }, (_, index) => index + 1).map((earlierPage) =>\n createElement(PageSubscriber, {\n key: `${queryKey}:${earlierPage}`,\n queryArgs,\n page: earlierPage,\n onRefreshed: handlePageRefreshed,\n })\n )\n );\n\n // Remember what this list has read, so leaving it isn't forgetting it. Filed\n // under `accumulated.queryKey`, not the live `queryKey`: the accumulator names\n // the list it belongs to, so an entry still describing the list being left\n // can't be misfiled under the one being entered.\n useEffect(() => {\n if (Object.keys(accumulated.pages).length === 0) {\n return;\n }\n\n const memory = memoryRef.current;\n // Delete before set so the entry moves to the end. Map iterates in\n // insertion order, so the first key is the least recently used.\n memory.delete(accumulated.queryKey);\n memory.set(accumulated.queryKey, accumulated);\n\n while (memory.size > MAX_REMEMBERED_LISTS) {\n const oldest = memory.keys().next();\n\n if (oldest.done) {\n break;\n }\n\n memory.delete(oldest.value);\n }\n }, [accumulated]);\n\n // Drop the left folder's cached `getAssets` pages when the folder changes.\n //\n // RTK Query 1.9.7 leaves a query's store subscription in place when its last\n // subscriber unmounts while a refetch is still in flight — e.g. a delete\n // invalidates `{Asset, LIST}`, every loaded page starts refetching, and the\n // user navigates away before those settle. Those stale subscriptions make the\n // next `{Asset, LIST}` invalidation (an upload in the new folder) refetch the\n // *previous* folder's pages: one bogus `/upload/files` request per loaded\n // page. There is no public per-entry unsubscribe, so evict the left folder's\n // entries outright. Returning then re-reads every page it had loaded, but the\n // rows come straight back from the accumulator memory above — so those\n // requests refresh a list already on screen rather than being waited on.\n const dispatch = useDispatch();\n const store = useStore();\n const prevFolderRef = useRef(folder);\n useEffect(() => {\n const prevFolder = prevFolderRef.current;\n prevFolderRef.current = folder;\n if (prevFolder === folder) {\n return;\n }\n\n const apiState = (store.getState() as Record<string, { queries?: Record<string, unknown> }>)[\n uploadApi.reducerPath\n ];\n const removeQueryResult = (\n uploadApi as unknown as {\n internalActions: {\n removeQueryResult: (payload: { queryCacheKey: string }) => { type: string };\n };\n }\n ).internalActions.removeQueryResult;\n\n Object.keys(apiState?.queries ?? {}).forEach((cacheKey) => {\n if (!cacheKey.startsWith('getAssets(')) {\n return;\n }\n let args: { folder?: number | null };\n try {\n args = JSON.parse(cacheKey.slice('getAssets('.length, -1));\n } catch {\n return;\n }\n if (args.folder === prevFolder) {\n dispatch(removeQueryResult({ queryCacheKey: cacheKey }));\n }\n });\n }, [folder, dispatch, store]);\n\n // Until the new list's first page lands the accumulator still holds the\n // previous folder. Reported as loading so the page shows a spinner instead\n // of the outgoing folder's assets under the incoming folder's header.\n const isChangingList = accumulated.listKey !== listKey;\n\n const loadedAssets = useMemo(\n () => (isChangingList ? [] : flattenPages(accumulated.pages)),\n [isChangingList, accumulated.pages]\n );\n\n // Deliberately the live value, not the stale-tolerant one below: paging must\n // never be driven by a total that belongs to the previous query.\n const hasNextPage = currentData ? page < currentData.pagination.pageCount : false;\n\n const completedUploads = useTypedSelector(selectCompletedUploads);\n\n // Only an unfiltered, unsearched list can place an upload locally: deciding\n // whether an asset satisfies a filter means reimplementing the server's\n // filter semantics client-side. Filtered views still refresh on the\n // end-of-batch invalidation, as they did before.\n const canPlaceUploads = !search && (filters?.length ?? 0) === 0;\n\n // When the loaded data came back from a request that started after a given\n // upload — i.e. the server had already stored it when asked. Past that point\n // the list is the authority on whether the asset is there at all, so bridging\n // must stop: otherwise a later delete or move, which refetches without it,\n // would have it re-inserted.\n const listAnsweredAfter =\n fulfilledTimeStamp !== undefined &&\n startedTimeStamp !== undefined &&\n // A refetch in flight leaves `fulfilledTimeStamp` on the previous response,\n // which predates this request — so only settled data counts.\n fulfilledTimeStamp > startedTimeStamp\n ? startedTimeStamp\n : undefined;\n\n const assets = useMemo(() => {\n if (!canPlaceUploads || completedUploads.length === 0) {\n return loadedAssets;\n }\n\n const bridging = completedUploads.filter(\n ({ asset, completedAt }) =>\n getRelationId(asset.folder) === folder &&\n (listAnsweredAfter === undefined || completedAt > listAnsweredAfter)\n );\n\n return mergeUploadedAssets({\n assets: loadedAssets,\n uploaded: bridging.map(({ asset }) => asset),\n sort,\n hasNextPage,\n });\n }, [\n canPlaceUploads,\n completedUploads,\n loadedAssets,\n folder,\n sort,\n hasNextPage,\n listAnsweredAfter,\n ]);\n const isFetchingMore = isFetching && page > 1;\n\n // A restored list already has rows on screen, so re-reading its current page\n // is a background refresh — reporting it as loading would swap those rows for\n // a spinner. Only an empty list has nothing better to show.\n const isLoadingList = isChangingList || (isLoading && assets.length === 0);\n\n const fetchNextPage = useCallback(() => {\n setPageState((prev) => ({\n queryKey,\n page: (prev.queryKey === queryKey ? prev.page : 1) + 1,\n }));\n }, [queryKey]);\n\n if (!enabled) {\n return {\n assets: [] as File[],\n // No list to keep fresh while disabled, so nothing to subscribe.\n subscribers: null,\n pagination: undefined,\n isLoading: false,\n isFetchingMore: false,\n hasNextPage: false,\n fetchNextPage,\n error: undefined,\n };\n }\n\n return {\n assets,\n subscribers,\n // Falls back to the accumulated total so a consumer showing the count\n // doesn't flash zero mid-transition, matching the possibly stale `assets`.\n pagination: currentData?.pagination ?? accumulated.pagination,\n isLoading: isLoadingList,\n isFetchingMore,\n hasNextPage,\n fetchNextPage,\n error,\n };\n};\n\nexport { useInfiniteAssets };\nexport { PAGE_SIZE };\n"],"names":["PAGE_SIZE","MAX_REMEMBERED_LISTS","flattenPages","pages","byId","Map","page","Object","keys","map","Number","sort","a","b","asset","set","id","values","deepestPage","reduce","deepest","key","Math","max","PageSubscriber","queryArgs","onRefreshed","currentData","useGetAssetsQuery","pageSize","results","useEffect","useInfiniteAssets","folder","search","filters","enabled","queryKey","JSON","stringify","listKey","pageState","setPageState","useState","accumulated","setAccumulated","memoryRef","useRef","isNewList","remembered","current","get","undefined","isLoading","isFetching","error","startedTimeStamp","fulfilledTimeStamp","skip","isSameQuery","pagination","handlePageRefreshed","useCallback","refreshedPage","prev","subscribers","createElement","Fragment","Array","from","length","_","index","earlierPage","memory","delete","size","oldest","next","done","value","dispatch","useDispatch","store","useStore","prevFolderRef","prevFolder","apiState","getState","uploadApi","reducerPath","removeQueryResult","internalActions","queries","forEach","cacheKey","startsWith","args","parse","slice","queryCacheKey","isChangingList","loadedAssets","useMemo","hasNextPage","pageCount","completedUploads","useTypedSelector","selectCompletedUploads","canPlaceUploads","listAnsweredAfter","assets","bridging","filter","completedAt","getRelationId","mergeUploadedAssets","uploaded","isFetchingMore","isLoadingList","fetchNextPage"],"mappings":"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"}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { useState, useCallback, createElement, Fragment,
|
|
1
|
+
import { useState, useRef, useCallback, createElement, Fragment, useEffect, useMemo } from 'react';
|
|
2
2
|
import { useDispatch, useStore } from 'react-redux';
|
|
3
3
|
import { uploadApi } from '../../../services/api.mjs';
|
|
4
4
|
import { useGetAssetsQuery } from '../../../services/assets.mjs';
|
|
5
|
+
import { useTypedSelector } from '../../../store/hooks.mjs';
|
|
6
|
+
import { selectCompletedUploads } from '../../../store/uploadProgress.mjs';
|
|
7
|
+
import { getRelationId } from '../../../utils/getRelationId.mjs';
|
|
8
|
+
import { mergeUploadedAssets } from '../utils/mergeUploadedAssets.mjs';
|
|
5
9
|
|
|
6
10
|
const PAGE_SIZE = 20;
|
|
11
|
+
/**
|
|
12
|
+
* How many list views keep their loaded pages in memory.
|
|
13
|
+
*/ const MAX_REMEMBERED_LISTS = 10;
|
|
7
14
|
/**
|
|
8
15
|
* Concatenates the pages in ascending order, one entry per asset id.
|
|
9
16
|
*
|
|
@@ -21,6 +28,10 @@ const PAGE_SIZE = 20;
|
|
|
21
28
|
...byId.values()
|
|
22
29
|
];
|
|
23
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* The highest page an accumulator holds — where the list was last read to, and
|
|
33
|
+
* so where to resume from on the way back.
|
|
34
|
+
*/ const deepestPage = (pages)=>Object.keys(pages).reduce((deepest, key)=>Math.max(deepest, Number(key)), 1);
|
|
24
35
|
/**
|
|
25
36
|
* Keeps one earlier page's `getAssets` query subscribed and reports its results
|
|
26
37
|
* back up whenever they change.
|
|
@@ -58,6 +69,10 @@ const PAGE_SIZE = 20;
|
|
|
58
69
|
* The caller MUST render the returned `subscribers` node — that is what keeps
|
|
59
70
|
* every loaded page subscribed, so `{Asset, LIST}` invalidations keep the whole
|
|
60
71
|
* list consistent rather than only the current page.
|
|
72
|
+
*
|
|
73
|
+
* Loaded pages survive navigating away and back: each list's accumulator is
|
|
74
|
+
* remembered (LRU, `MAX_REMEMBERED_LISTS`) and restored when its key returns, so
|
|
75
|
+
* stepping into a subfolder and back out keeps the rows the user scrolled for.
|
|
61
76
|
*/ const useInfiniteAssets = ({ folder = null, sort, search, filters, enabled = true } = {})=>{
|
|
62
77
|
// Derived from the request args themselves, so a new filter can't reach the
|
|
63
78
|
// API without also invalidating what was accumulated under the old one.
|
|
@@ -88,17 +103,32 @@ const PAGE_SIZE = 20;
|
|
|
88
103
|
listKey,
|
|
89
104
|
pages: {}
|
|
90
105
|
});
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
106
|
+
// Each list's loaded pages, kept across navigation away from it.
|
|
107
|
+
const memoryRef = useRef(new Map());
|
|
108
|
+
const isNewList = pageState.queryKey !== queryKey;
|
|
109
|
+
const remembered = isNewList ? memoryRef.current.get(queryKey) : undefined;
|
|
110
|
+
// Derived rather than read from state: the render that discovers the key
|
|
111
|
+
// change still reaches the query below, and a list with no page 1 must never
|
|
112
|
+
// be requested at page 2. A remembered list always holds page 1, so resuming
|
|
113
|
+
// at its deepest page can't skip one.
|
|
114
|
+
let page;
|
|
115
|
+
if (!isNewList) {
|
|
116
|
+
page = pageState.page;
|
|
117
|
+
} else if (remembered) {
|
|
118
|
+
page = deepestPage(remembered.pages);
|
|
119
|
+
} else {
|
|
120
|
+
page = 1;
|
|
121
|
+
}
|
|
122
|
+
if (isNewList) {
|
|
96
123
|
setPageState({
|
|
97
124
|
queryKey,
|
|
98
|
-
page
|
|
125
|
+
page
|
|
99
126
|
});
|
|
127
|
+
if (remembered) {
|
|
128
|
+
setAccumulated(remembered);
|
|
129
|
+
}
|
|
100
130
|
}
|
|
101
|
-
const { currentData, isLoading, isFetching, error } = useGetAssetsQuery({
|
|
131
|
+
const { currentData, isLoading, isFetching, error, startedTimeStamp, fulfilledTimeStamp } = useGetAssetsQuery({
|
|
102
132
|
...queryArgs,
|
|
103
133
|
page,
|
|
104
134
|
pageSize: PAGE_SIZE
|
|
@@ -109,7 +139,8 @@ const PAGE_SIZE = 20;
|
|
|
109
139
|
// `currentData` is only ever the payload for the args just passed, so it
|
|
110
140
|
// belongs to this key at this page — nothing to infer. Its reference is
|
|
111
141
|
// stable per cache entry, so this settles after one extra render.
|
|
112
|
-
if (
|
|
142
|
+
if (// A restore already set the accumulator from memory; don't overwrite it.
|
|
143
|
+
!remembered && currentData && (!isSameQuery || accumulated.pages[page] !== currentData.results)) {
|
|
113
144
|
setAccumulated(isSameQuery ? {
|
|
114
145
|
...accumulated,
|
|
115
146
|
pages: {
|
|
@@ -157,6 +188,29 @@ const PAGE_SIZE = 20;
|
|
|
157
188
|
page: earlierPage,
|
|
158
189
|
onRefreshed: handlePageRefreshed
|
|
159
190
|
})));
|
|
191
|
+
// Remember what this list has read, so leaving it isn't forgetting it. Filed
|
|
192
|
+
// under `accumulated.queryKey`, not the live `queryKey`: the accumulator names
|
|
193
|
+
// the list it belongs to, so an entry still describing the list being left
|
|
194
|
+
// can't be misfiled under the one being entered.
|
|
195
|
+
useEffect(()=>{
|
|
196
|
+
if (Object.keys(accumulated.pages).length === 0) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const memory = memoryRef.current;
|
|
200
|
+
// Delete before set so the entry moves to the end. Map iterates in
|
|
201
|
+
// insertion order, so the first key is the least recently used.
|
|
202
|
+
memory.delete(accumulated.queryKey);
|
|
203
|
+
memory.set(accumulated.queryKey, accumulated);
|
|
204
|
+
while(memory.size > MAX_REMEMBERED_LISTS){
|
|
205
|
+
const oldest = memory.keys().next();
|
|
206
|
+
if (oldest.done) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
memory.delete(oldest.value);
|
|
210
|
+
}
|
|
211
|
+
}, [
|
|
212
|
+
accumulated
|
|
213
|
+
]);
|
|
160
214
|
// Drop the left folder's cached `getAssets` pages when the folder changes.
|
|
161
215
|
//
|
|
162
216
|
// RTK Query 1.9.7 leaves a query's store subscription in place when its last
|
|
@@ -166,8 +220,9 @@ const PAGE_SIZE = 20;
|
|
|
166
220
|
// next `{Asset, LIST}` invalidation (an upload in the new folder) refetch the
|
|
167
221
|
// *previous* folder's pages: one bogus `/upload/files` request per loaded
|
|
168
222
|
// page. There is no public per-entry unsubscribe, so evict the left folder's
|
|
169
|
-
// entries outright
|
|
170
|
-
//
|
|
223
|
+
// entries outright. Returning then re-reads every page it had loaded, but the
|
|
224
|
+
// rows come straight back from the accumulator memory above — so those
|
|
225
|
+
// requests refresh a list already on screen rather than being waited on.
|
|
171
226
|
const dispatch = useDispatch();
|
|
172
227
|
const store = useStore();
|
|
173
228
|
const prevFolderRef = useRef(folder);
|
|
@@ -204,14 +259,52 @@ const PAGE_SIZE = 20;
|
|
|
204
259
|
// previous folder. Reported as loading so the page shows a spinner instead
|
|
205
260
|
// of the outgoing folder's assets under the incoming folder's header.
|
|
206
261
|
const isChangingList = accumulated.listKey !== listKey;
|
|
207
|
-
const
|
|
262
|
+
const loadedAssets = useMemo(()=>isChangingList ? [] : flattenPages(accumulated.pages), [
|
|
208
263
|
isChangingList,
|
|
209
264
|
accumulated.pages
|
|
210
265
|
]);
|
|
211
266
|
// Deliberately the live value, not the stale-tolerant one below: paging must
|
|
212
267
|
// never be driven by a total that belongs to the previous query.
|
|
213
268
|
const hasNextPage = currentData ? page < currentData.pagination.pageCount : false;
|
|
269
|
+
const completedUploads = useTypedSelector(selectCompletedUploads);
|
|
270
|
+
// Only an unfiltered, unsearched list can place an upload locally: deciding
|
|
271
|
+
// whether an asset satisfies a filter means reimplementing the server's
|
|
272
|
+
// filter semantics client-side. Filtered views still refresh on the
|
|
273
|
+
// end-of-batch invalidation, as they did before.
|
|
274
|
+
const canPlaceUploads = !search && (filters?.length ?? 0) === 0;
|
|
275
|
+
// When the loaded data came back from a request that started after a given
|
|
276
|
+
// upload — i.e. the server had already stored it when asked. Past that point
|
|
277
|
+
// the list is the authority on whether the asset is there at all, so bridging
|
|
278
|
+
// must stop: otherwise a later delete or move, which refetches without it,
|
|
279
|
+
// would have it re-inserted.
|
|
280
|
+
const listAnsweredAfter = fulfilledTimeStamp !== undefined && startedTimeStamp !== undefined && // A refetch in flight leaves `fulfilledTimeStamp` on the previous response,
|
|
281
|
+
// which predates this request — so only settled data counts.
|
|
282
|
+
fulfilledTimeStamp > startedTimeStamp ? startedTimeStamp : undefined;
|
|
283
|
+
const assets = useMemo(()=>{
|
|
284
|
+
if (!canPlaceUploads || completedUploads.length === 0) {
|
|
285
|
+
return loadedAssets;
|
|
286
|
+
}
|
|
287
|
+
const bridging = completedUploads.filter(({ asset, completedAt })=>getRelationId(asset.folder) === folder && (listAnsweredAfter === undefined || completedAt > listAnsweredAfter));
|
|
288
|
+
return mergeUploadedAssets({
|
|
289
|
+
assets: loadedAssets,
|
|
290
|
+
uploaded: bridging.map(({ asset })=>asset),
|
|
291
|
+
sort,
|
|
292
|
+
hasNextPage
|
|
293
|
+
});
|
|
294
|
+
}, [
|
|
295
|
+
canPlaceUploads,
|
|
296
|
+
completedUploads,
|
|
297
|
+
loadedAssets,
|
|
298
|
+
folder,
|
|
299
|
+
sort,
|
|
300
|
+
hasNextPage,
|
|
301
|
+
listAnsweredAfter
|
|
302
|
+
]);
|
|
214
303
|
const isFetchingMore = isFetching && page > 1;
|
|
304
|
+
// A restored list already has rows on screen, so re-reading its current page
|
|
305
|
+
// is a background refresh — reporting it as loading would swap those rows for
|
|
306
|
+
// a spinner. Only an empty list has nothing better to show.
|
|
307
|
+
const isLoadingList = isChangingList || isLoading && assets.length === 0;
|
|
215
308
|
const fetchNextPage = useCallback(()=>{
|
|
216
309
|
setPageState((prev)=>({
|
|
217
310
|
queryKey,
|
|
@@ -239,7 +332,7 @@ const PAGE_SIZE = 20;
|
|
|
239
332
|
// Falls back to the accumulated total so a consumer showing the count
|
|
240
333
|
// doesn't flash zero mid-transition, matching the possibly stale `assets`.
|
|
241
334
|
pagination: currentData?.pagination ?? accumulated.pagination,
|
|
242
|
-
isLoading:
|
|
335
|
+
isLoading: isLoadingList,
|
|
243
336
|
isFetchingMore,
|
|
244
337
|
hasNextPage,
|
|
245
338
|
fetchNextPage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInfiniteAssets.mjs","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useInfiniteAssets.ts"],"sourcesContent":["import { createElement, Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useDispatch, useStore } from 'react-redux';\n\nimport { uploadApi } from '../../../services/api';\nimport { useGetAssetsQuery } from '../../../services/assets';\n\nimport type { File, Pagination } from '../../../../../../shared/contracts/files';\n\nconst PAGE_SIZE = 20;\n\ninterface UseInfiniteAssetsOptions {\n folder?: number | null;\n sort?: string;\n search?: string;\n /** Extra `filters[$and]` entries for the files query. */\n filters?: Record<string, unknown>[];\n /** Structural switch: an `is [folder]`-only type badge means no file can match. */\n enabled?: boolean;\n}\n\ninterface QueryArgs {\n folder: number | null;\n sort?: string;\n search?: string;\n filters?: Record<string, unknown>[];\n}\n\n/**\n * Everything loaded for one query identity.\n *\n * `queryKey` lags the live key while a new query is in flight — that lag is\n * what keeps the previous results on screen across a search change.\n */\ninterface Accumulated {\n queryKey: string;\n listKey: string;\n pages: Record<number, File[]>;\n pagination?: Pagination;\n}\n\ninterface PageState {\n queryKey: string;\n page: number;\n}\n\ntype PageRefreshedHandler = (page: number, results: File[]) => void;\n\n/**\n * Concatenates the pages in ascending order, one entry per asset id.\n *\n * `Map.set` keeps the first insertion position but takes the latest value, so\n * an asset that moved between pages after a mutation holds its place and shows\n * fresh data instead of rendering twice under a duplicate key.\n */\nconst flattenPages = (pages: Record<number, File[]>): File[] => {\n const byId = new Map<number, File>();\n\n for (const page of Object.keys(pages)\n .map(Number)\n .sort((a, b) => a - b)) {\n for (const asset of pages[page]) {\n byId.set(asset.id, asset);\n }\n }\n\n return [...byId.values()];\n};\n\n/**\n * Keeps one earlier page's `getAssets` query subscribed and reports its results\n * back up whenever they change.\n *\n * The hook itself only subscribes the current (latest) page. Without these, a\n * mutation invalidating `{Asset, LIST}` refetches that page alone and earlier\n * pages stay frozen — a rename or delete on an earlier page never reaches the\n * list until a folder/sort/search change. One subscriber per earlier\n * page keeps them all subscribed so the invalidation refetches the whole list.\n * Renders nothing; it exists only for the subscription + `onRefreshed` report.\n */\nconst PageSubscriber = ({\n queryArgs,\n page,\n onRefreshed,\n}: {\n queryArgs: QueryArgs;\n page: number;\n onRefreshed: PageRefreshedHandler;\n}) => {\n const { currentData } = useGetAssetsQuery({ ...queryArgs, page, pageSize: PAGE_SIZE });\n const results = currentData?.results;\n\n useEffect(() => {\n if (results) {\n onRefreshed(page, results);\n }\n // `results` is a stable reference per cache entry, so this only re-reports\n // when a refetch actually replaces the page's data. Reporting the same\n // reference the accumulator already holds is a no-op (see `onRefreshed`).\n }, [results, page, onRefreshed]);\n\n return null;\n};\n\n/**\n * Infinite scroll over `/upload/files`, keeping one accumulated slot per page.\n *\n * The caller MUST render the returned `subscribers` node — that is what keeps\n * every loaded page subscribed, so `{Asset, LIST}` invalidations keep the whole\n * list consistent rather than only the current page.\n */\nconst useInfiniteAssets = ({\n folder = null,\n sort,\n search,\n filters,\n enabled = true,\n}: UseInfiniteAssetsOptions = {}) => {\n // Derived from the request args themselves, so a new filter can't reach the\n // API without also invalidating what was accumulated under the old one.\n const queryArgs: QueryArgs = { folder, sort, search, filters };\n const queryKey = JSON.stringify(queryArgs);\n // Identifies the list being viewed, ignoring the search term. Folder, sort\n // and filter changes are single committed actions, so one clean reset per\n // change is honest. Search is committed per keystroke on a 300ms debounce,\n // and blanking the list on each one would strobe as the user types.\n // The cost: through the search window the previous rows stay rendered and\n // `hasNextPage` is false, so the list reads as complete until page 1 lands.\n const listKey = JSON.stringify({ folder, sort, filters });\n\n const [pageState, setPageState] = useState<PageState>({ queryKey, page: 1 });\n const [accumulated, setAccumulated] = useState<Accumulated>({ queryKey, listKey, pages: {} });\n\n // Derived rather than read straight from state: the render that discovers\n // the key change still reaches the query below, and a list with no page 1\n // must never be requested at page 2.\n const page = pageState.queryKey === queryKey ? pageState.page : 1;\n\n if (pageState.queryKey !== queryKey) {\n setPageState({ queryKey, page: 1 });\n }\n\n const { currentData, isLoading, isFetching, error } = useGetAssetsQuery(\n {\n ...queryArgs,\n page,\n pageSize: PAGE_SIZE,\n },\n { skip: !enabled }\n );\n\n const isSameQuery = accumulated.queryKey === queryKey;\n\n // `currentData` is only ever the payload for the args just passed, so it\n // belongs to this key at this page — nothing to infer. Its reference is\n // stable per cache entry, so this settles after one extra render.\n if (currentData && (!isSameQuery || accumulated.pages[page] !== currentData.results)) {\n setAccumulated(\n isSameQuery\n ? {\n ...accumulated,\n pages: { ...accumulated.pages, [page]: currentData.results },\n pagination: currentData.pagination,\n }\n : {\n queryKey,\n listKey,\n pages: { [page]: currentData.results },\n pagination: currentData.pagination,\n }\n );\n }\n\n // Merges a refetched earlier page back into the accumulator. Ignores reports\n // for a superseded query, and no-ops when the reference is unchanged so a\n // subscriber re-reporting its already-stored page can't loop.\n const handlePageRefreshed = useCallback<PageRefreshedHandler>(\n (refreshedPage, results) => {\n setAccumulated((prev) => {\n if (prev.queryKey !== queryKey || prev.pages[refreshedPage] === results) {\n return prev;\n }\n\n return { ...prev, pages: { ...prev.pages, [refreshedPage]: results } };\n });\n },\n [queryKey]\n );\n\n // One subscriber per earlier page (the current page is already subscribed by\n // the query above). Keyed by queryKey so a folder/sort/search change remounts\n // them against the new args instead of reusing a stale cache entry.\n // `createElement` rather than JSX so this stays a plain `.ts` hook file.\n const subscribers = createElement(\n Fragment,\n null,\n Array.from({ length: Math.max(0, page - 1) }, (_, index) => index + 1).map((earlierPage) =>\n createElement(PageSubscriber, {\n key: `${queryKey}:${earlierPage}`,\n queryArgs,\n page: earlierPage,\n onRefreshed: handlePageRefreshed,\n })\n )\n );\n\n // Drop the left folder's cached `getAssets` pages when the folder changes.\n //\n // RTK Query 1.9.7 leaves a query's store subscription in place when its last\n // subscriber unmounts while a refetch is still in flight — e.g. a delete\n // invalidates `{Asset, LIST}`, every loaded page starts refetching, and the\n // user navigates away before those settle. Those stale subscriptions make the\n // next `{Asset, LIST}` invalidation (an upload in the new folder) refetch the\n // *previous* folder's pages: one bogus `/upload/files` request per loaded\n // page. There is no public per-entry unsubscribe, so evict the left folder's\n // entries outright — returning reloads from page 1, which the accumulator\n // already does on a list change.\n const dispatch = useDispatch();\n const store = useStore();\n const prevFolderRef = useRef(folder);\n useEffect(() => {\n const prevFolder = prevFolderRef.current;\n prevFolderRef.current = folder;\n if (prevFolder === folder) {\n return;\n }\n\n const apiState = (store.getState() as Record<string, { queries?: Record<string, unknown> }>)[\n uploadApi.reducerPath\n ];\n const removeQueryResult = (\n uploadApi as unknown as {\n internalActions: {\n removeQueryResult: (payload: { queryCacheKey: string }) => { type: string };\n };\n }\n ).internalActions.removeQueryResult;\n\n Object.keys(apiState?.queries ?? {}).forEach((cacheKey) => {\n if (!cacheKey.startsWith('getAssets(')) {\n return;\n }\n let args: { folder?: number | null };\n try {\n args = JSON.parse(cacheKey.slice('getAssets('.length, -1));\n } catch {\n return;\n }\n if (args.folder === prevFolder) {\n dispatch(removeQueryResult({ queryCacheKey: cacheKey }));\n }\n });\n }, [folder, dispatch, store]);\n\n // Until the new list's first page lands the accumulator still holds the\n // previous folder. Reported as loading so the page shows a spinner instead\n // of the outgoing folder's assets under the incoming folder's header.\n const isChangingList = accumulated.listKey !== listKey;\n\n const assets = useMemo(\n () => (isChangingList ? [] : flattenPages(accumulated.pages)),\n [isChangingList, accumulated.pages]\n );\n\n // Deliberately the live value, not the stale-tolerant one below: paging must\n // never be driven by a total that belongs to the previous query.\n const hasNextPage = currentData ? page < currentData.pagination.pageCount : false;\n const isFetchingMore = isFetching && page > 1;\n\n const fetchNextPage = useCallback(() => {\n setPageState((prev) => ({\n queryKey,\n page: (prev.queryKey === queryKey ? prev.page : 1) + 1,\n }));\n }, [queryKey]);\n\n if (!enabled) {\n return {\n assets: [] as File[],\n // No list to keep fresh while disabled, so nothing to subscribe.\n subscribers: null,\n pagination: undefined,\n isLoading: false,\n isFetchingMore: false,\n hasNextPage: false,\n fetchNextPage,\n error: undefined,\n };\n }\n\n return {\n assets,\n subscribers,\n // Falls back to the accumulated total so a consumer showing the count\n // doesn't flash zero mid-transition, matching the possibly stale `assets`.\n pagination: currentData?.pagination ?? accumulated.pagination,\n isLoading: isLoading || isChangingList,\n isFetchingMore,\n hasNextPage,\n fetchNextPage,\n error,\n };\n};\n\nexport { useInfiniteAssets };\nexport { PAGE_SIZE };\n"],"names":["PAGE_SIZE","flattenPages","pages","byId","Map","page","Object","keys","map","Number","sort","a","b","asset","set","id","values","PageSubscriber","queryArgs","onRefreshed","currentData","useGetAssetsQuery","pageSize","results","useEffect","useInfiniteAssets","folder","search","filters","enabled","queryKey","JSON","stringify","listKey","pageState","setPageState","useState","accumulated","setAccumulated","isLoading","isFetching","error","skip","isSameQuery","pagination","handlePageRefreshed","useCallback","refreshedPage","prev","subscribers","createElement","Fragment","Array","from","length","Math","max","_","index","earlierPage","key","dispatch","useDispatch","store","useStore","prevFolderRef","useRef","prevFolder","current","apiState","getState","uploadApi","reducerPath","removeQueryResult","internalActions","queries","forEach","cacheKey","startsWith","args","parse","slice","queryCacheKey","isChangingList","assets","useMemo","hasNextPage","pageCount","isFetchingMore","fetchNextPage","undefined"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useInfiniteAssets.mjs","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useInfiniteAssets.ts"],"sourcesContent":["import { createElement, Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useDispatch, useStore } from 'react-redux';\n\nimport { uploadApi } from '../../../services/api';\nimport { useGetAssetsQuery } from '../../../services/assets';\nimport { useTypedSelector } from '../../../store/hooks';\nimport { selectCompletedUploads } from '../../../store/uploadProgress';\nimport { getRelationId } from '../../../utils/getRelationId';\nimport { mergeUploadedAssets } from '../utils/mergeUploadedAssets';\n\nimport type { File, Pagination } from '../../../../../../shared/contracts/files';\n\nconst PAGE_SIZE = 20;\n\n/**\n * How many list views keep their loaded pages in memory.\n */\nconst MAX_REMEMBERED_LISTS = 10;\n\ninterface UseInfiniteAssetsOptions {\n folder?: number | null;\n sort?: string;\n search?: string;\n /** Extra `filters[$and]` entries for the files query. */\n filters?: Record<string, unknown>[];\n /** Structural switch: an `is [folder]`-only type badge means no file can match. */\n enabled?: boolean;\n}\n\ninterface QueryArgs {\n folder: number | null;\n sort?: string;\n search?: string;\n filters?: Record<string, unknown>[];\n}\n\n/**\n * Everything loaded for one query identity.\n *\n * `queryKey` lags the live key while a new query is in flight — that lag is\n * what keeps the previous results on screen across a search change.\n */\ninterface Accumulated {\n queryKey: string;\n listKey: string;\n pages: Record<number, File[]>;\n pagination?: Pagination;\n}\n\ninterface PageState {\n queryKey: string;\n page: number;\n}\n\ntype PageRefreshedHandler = (page: number, results: File[]) => void;\n\n/**\n * Concatenates the pages in ascending order, one entry per asset id.\n *\n * `Map.set` keeps the first insertion position but takes the latest value, so\n * an asset that moved between pages after a mutation holds its place and shows\n * fresh data instead of rendering twice under a duplicate key.\n */\nconst flattenPages = (pages: Record<number, File[]>): File[] => {\n const byId = new Map<number, File>();\n\n for (const page of Object.keys(pages)\n .map(Number)\n .sort((a, b) => a - b)) {\n for (const asset of pages[page]) {\n byId.set(asset.id, asset);\n }\n }\n\n return [...byId.values()];\n};\n\n/**\n * The highest page an accumulator holds — where the list was last read to, and\n * so where to resume from on the way back.\n */\nconst deepestPage = (pages: Record<number, File[]>): number =>\n Object.keys(pages).reduce((deepest, key) => Math.max(deepest, Number(key)), 1);\n\n/**\n * Keeps one earlier page's `getAssets` query subscribed and reports its results\n * back up whenever they change.\n *\n * The hook itself only subscribes the current (latest) page. Without these, a\n * mutation invalidating `{Asset, LIST}` refetches that page alone and earlier\n * pages stay frozen — a rename or delete on an earlier page never reaches the\n * list until a folder/sort/search change. One subscriber per earlier\n * page keeps them all subscribed so the invalidation refetches the whole list.\n * Renders nothing; it exists only for the subscription + `onRefreshed` report.\n */\nconst PageSubscriber = ({\n queryArgs,\n page,\n onRefreshed,\n}: {\n queryArgs: QueryArgs;\n page: number;\n onRefreshed: PageRefreshedHandler;\n}) => {\n const { currentData } = useGetAssetsQuery({ ...queryArgs, page, pageSize: PAGE_SIZE });\n const results = currentData?.results;\n\n useEffect(() => {\n if (results) {\n onRefreshed(page, results);\n }\n // `results` is a stable reference per cache entry, so this only re-reports\n // when a refetch actually replaces the page's data. Reporting the same\n // reference the accumulator already holds is a no-op (see `onRefreshed`).\n }, [results, page, onRefreshed]);\n\n return null;\n};\n\n/**\n * Infinite scroll over `/upload/files`, keeping one accumulated slot per page.\n *\n * The caller MUST render the returned `subscribers` node — that is what keeps\n * every loaded page subscribed, so `{Asset, LIST}` invalidations keep the whole\n * list consistent rather than only the current page.\n *\n * Loaded pages survive navigating away and back: each list's accumulator is\n * remembered (LRU, `MAX_REMEMBERED_LISTS`) and restored when its key returns, so\n * stepping into a subfolder and back out keeps the rows the user scrolled for.\n */\nconst useInfiniteAssets = ({\n folder = null,\n sort,\n search,\n filters,\n enabled = true,\n}: UseInfiniteAssetsOptions = {}) => {\n // Derived from the request args themselves, so a new filter can't reach the\n // API without also invalidating what was accumulated under the old one.\n const queryArgs: QueryArgs = { folder, sort, search, filters };\n const queryKey = JSON.stringify(queryArgs);\n // Identifies the list being viewed, ignoring the search term. Folder, sort\n // and filter changes are single committed actions, so one clean reset per\n // change is honest. Search is committed per keystroke on a 300ms debounce,\n // and blanking the list on each one would strobe as the user types.\n // The cost: through the search window the previous rows stay rendered and\n // `hasNextPage` is false, so the list reads as complete until page 1 lands.\n const listKey = JSON.stringify({ folder, sort, filters });\n\n const [pageState, setPageState] = useState<PageState>({ queryKey, page: 1 });\n const [accumulated, setAccumulated] = useState<Accumulated>({ queryKey, listKey, pages: {} });\n\n // Each list's loaded pages, kept across navigation away from it.\n const memoryRef = useRef(new Map<string, Accumulated>());\n\n const isNewList = pageState.queryKey !== queryKey;\n\n const remembered = isNewList ? memoryRef.current.get(queryKey) : undefined;\n\n // Derived rather than read from state: the render that discovers the key\n // change still reaches the query below, and a list with no page 1 must never\n // be requested at page 2. A remembered list always holds page 1, so resuming\n // at its deepest page can't skip one.\n let page: number;\n if (!isNewList) {\n page = pageState.page;\n } else if (remembered) {\n page = deepestPage(remembered.pages);\n } else {\n page = 1;\n }\n\n if (isNewList) {\n setPageState({ queryKey, page });\n\n if (remembered) {\n setAccumulated(remembered);\n }\n }\n\n const { currentData, isLoading, isFetching, error, startedTimeStamp, fulfilledTimeStamp } =\n useGetAssetsQuery(\n {\n ...queryArgs,\n page,\n pageSize: PAGE_SIZE,\n },\n { skip: !enabled }\n );\n\n const isSameQuery = accumulated.queryKey === queryKey;\n\n // `currentData` is only ever the payload for the args just passed, so it\n // belongs to this key at this page — nothing to infer. Its reference is\n // stable per cache entry, so this settles after one extra render.\n if (\n // A restore already set the accumulator from memory; don't overwrite it.\n !remembered &&\n currentData &&\n (!isSameQuery || accumulated.pages[page] !== currentData.results)\n ) {\n setAccumulated(\n isSameQuery\n ? {\n ...accumulated,\n pages: { ...accumulated.pages, [page]: currentData.results },\n pagination: currentData.pagination,\n }\n : {\n queryKey,\n listKey,\n pages: { [page]: currentData.results },\n pagination: currentData.pagination,\n }\n );\n }\n\n // Merges a refetched earlier page back into the accumulator. Ignores reports\n // for a superseded query, and no-ops when the reference is unchanged so a\n // subscriber re-reporting its already-stored page can't loop.\n const handlePageRefreshed = useCallback<PageRefreshedHandler>(\n (refreshedPage, results) => {\n setAccumulated((prev) => {\n if (prev.queryKey !== queryKey || prev.pages[refreshedPage] === results) {\n return prev;\n }\n\n return { ...prev, pages: { ...prev.pages, [refreshedPage]: results } };\n });\n },\n [queryKey]\n );\n\n // One subscriber per earlier page (the current page is already subscribed by\n // the query above). Keyed by queryKey so a folder/sort/search change remounts\n // them against the new args instead of reusing a stale cache entry.\n // `createElement` rather than JSX so this stays a plain `.ts` hook file.\n const subscribers = createElement(\n Fragment,\n null,\n Array.from({ length: Math.max(0, page - 1) }, (_, index) => index + 1).map((earlierPage) =>\n createElement(PageSubscriber, {\n key: `${queryKey}:${earlierPage}`,\n queryArgs,\n page: earlierPage,\n onRefreshed: handlePageRefreshed,\n })\n )\n );\n\n // Remember what this list has read, so leaving it isn't forgetting it. Filed\n // under `accumulated.queryKey`, not the live `queryKey`: the accumulator names\n // the list it belongs to, so an entry still describing the list being left\n // can't be misfiled under the one being entered.\n useEffect(() => {\n if (Object.keys(accumulated.pages).length === 0) {\n return;\n }\n\n const memory = memoryRef.current;\n // Delete before set so the entry moves to the end. Map iterates in\n // insertion order, so the first key is the least recently used.\n memory.delete(accumulated.queryKey);\n memory.set(accumulated.queryKey, accumulated);\n\n while (memory.size > MAX_REMEMBERED_LISTS) {\n const oldest = memory.keys().next();\n\n if (oldest.done) {\n break;\n }\n\n memory.delete(oldest.value);\n }\n }, [accumulated]);\n\n // Drop the left folder's cached `getAssets` pages when the folder changes.\n //\n // RTK Query 1.9.7 leaves a query's store subscription in place when its last\n // subscriber unmounts while a refetch is still in flight — e.g. a delete\n // invalidates `{Asset, LIST}`, every loaded page starts refetching, and the\n // user navigates away before those settle. Those stale subscriptions make the\n // next `{Asset, LIST}` invalidation (an upload in the new folder) refetch the\n // *previous* folder's pages: one bogus `/upload/files` request per loaded\n // page. There is no public per-entry unsubscribe, so evict the left folder's\n // entries outright. Returning then re-reads every page it had loaded, but the\n // rows come straight back from the accumulator memory above — so those\n // requests refresh a list already on screen rather than being waited on.\n const dispatch = useDispatch();\n const store = useStore();\n const prevFolderRef = useRef(folder);\n useEffect(() => {\n const prevFolder = prevFolderRef.current;\n prevFolderRef.current = folder;\n if (prevFolder === folder) {\n return;\n }\n\n const apiState = (store.getState() as Record<string, { queries?: Record<string, unknown> }>)[\n uploadApi.reducerPath\n ];\n const removeQueryResult = (\n uploadApi as unknown as {\n internalActions: {\n removeQueryResult: (payload: { queryCacheKey: string }) => { type: string };\n };\n }\n ).internalActions.removeQueryResult;\n\n Object.keys(apiState?.queries ?? {}).forEach((cacheKey) => {\n if (!cacheKey.startsWith('getAssets(')) {\n return;\n }\n let args: { folder?: number | null };\n try {\n args = JSON.parse(cacheKey.slice('getAssets('.length, -1));\n } catch {\n return;\n }\n if (args.folder === prevFolder) {\n dispatch(removeQueryResult({ queryCacheKey: cacheKey }));\n }\n });\n }, [folder, dispatch, store]);\n\n // Until the new list's first page lands the accumulator still holds the\n // previous folder. Reported as loading so the page shows a spinner instead\n // of the outgoing folder's assets under the incoming folder's header.\n const isChangingList = accumulated.listKey !== listKey;\n\n const loadedAssets = useMemo(\n () => (isChangingList ? [] : flattenPages(accumulated.pages)),\n [isChangingList, accumulated.pages]\n );\n\n // Deliberately the live value, not the stale-tolerant one below: paging must\n // never be driven by a total that belongs to the previous query.\n const hasNextPage = currentData ? page < currentData.pagination.pageCount : false;\n\n const completedUploads = useTypedSelector(selectCompletedUploads);\n\n // Only an unfiltered, unsearched list can place an upload locally: deciding\n // whether an asset satisfies a filter means reimplementing the server's\n // filter semantics client-side. Filtered views still refresh on the\n // end-of-batch invalidation, as they did before.\n const canPlaceUploads = !search && (filters?.length ?? 0) === 0;\n\n // When the loaded data came back from a request that started after a given\n // upload — i.e. the server had already stored it when asked. Past that point\n // the list is the authority on whether the asset is there at all, so bridging\n // must stop: otherwise a later delete or move, which refetches without it,\n // would have it re-inserted.\n const listAnsweredAfter =\n fulfilledTimeStamp !== undefined &&\n startedTimeStamp !== undefined &&\n // A refetch in flight leaves `fulfilledTimeStamp` on the previous response,\n // which predates this request — so only settled data counts.\n fulfilledTimeStamp > startedTimeStamp\n ? startedTimeStamp\n : undefined;\n\n const assets = useMemo(() => {\n if (!canPlaceUploads || completedUploads.length === 0) {\n return loadedAssets;\n }\n\n const bridging = completedUploads.filter(\n ({ asset, completedAt }) =>\n getRelationId(asset.folder) === folder &&\n (listAnsweredAfter === undefined || completedAt > listAnsweredAfter)\n );\n\n return mergeUploadedAssets({\n assets: loadedAssets,\n uploaded: bridging.map(({ asset }) => asset),\n sort,\n hasNextPage,\n });\n }, [\n canPlaceUploads,\n completedUploads,\n loadedAssets,\n folder,\n sort,\n hasNextPage,\n listAnsweredAfter,\n ]);\n const isFetchingMore = isFetching && page > 1;\n\n // A restored list already has rows on screen, so re-reading its current page\n // is a background refresh — reporting it as loading would swap those rows for\n // a spinner. Only an empty list has nothing better to show.\n const isLoadingList = isChangingList || (isLoading && assets.length === 0);\n\n const fetchNextPage = useCallback(() => {\n setPageState((prev) => ({\n queryKey,\n page: (prev.queryKey === queryKey ? prev.page : 1) + 1,\n }));\n }, [queryKey]);\n\n if (!enabled) {\n return {\n assets: [] as File[],\n // No list to keep fresh while disabled, so nothing to subscribe.\n subscribers: null,\n pagination: undefined,\n isLoading: false,\n isFetchingMore: false,\n hasNextPage: false,\n fetchNextPage,\n error: undefined,\n };\n }\n\n return {\n assets,\n subscribers,\n // Falls back to the accumulated total so a consumer showing the count\n // doesn't flash zero mid-transition, matching the possibly stale `assets`.\n pagination: currentData?.pagination ?? accumulated.pagination,\n isLoading: isLoadingList,\n isFetchingMore,\n hasNextPage,\n fetchNextPage,\n error,\n };\n};\n\nexport { useInfiniteAssets };\nexport { PAGE_SIZE };\n"],"names":["PAGE_SIZE","MAX_REMEMBERED_LISTS","flattenPages","pages","byId","Map","page","Object","keys","map","Number","sort","a","b","asset","set","id","values","deepestPage","reduce","deepest","key","Math","max","PageSubscriber","queryArgs","onRefreshed","currentData","useGetAssetsQuery","pageSize","results","useEffect","useInfiniteAssets","folder","search","filters","enabled","queryKey","JSON","stringify","listKey","pageState","setPageState","useState","accumulated","setAccumulated","memoryRef","useRef","isNewList","remembered","current","get","undefined","isLoading","isFetching","error","startedTimeStamp","fulfilledTimeStamp","skip","isSameQuery","pagination","handlePageRefreshed","useCallback","refreshedPage","prev","subscribers","createElement","Fragment","Array","from","length","_","index","earlierPage","memory","delete","size","oldest","next","done","value","dispatch","useDispatch","store","useStore","prevFolderRef","prevFolder","apiState","getState","uploadApi","reducerPath","removeQueryResult","internalActions","queries","forEach","cacheKey","startsWith","args","parse","slice","queryCacheKey","isChangingList","loadedAssets","useMemo","hasNextPage","pageCount","completedUploads","useTypedSelector","selectCompletedUploads","canPlaceUploads","listAnsweredAfter","assets","bridging","filter","completedAt","getRelationId","mergeUploadedAssets","uploaded","isFetchingMore","isLoadingList","fetchNextPage"],"mappings":"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"}
|
|
@@ -138,13 +138,13 @@ const useListFilters = ()=>{
|
|
|
138
138
|
// URL stays shareable and refresh-safe either way.
|
|
139
139
|
const writeFilters = (next)=>{
|
|
140
140
|
if (next.length === 0) {
|
|
141
|
-
setQuery({
|
|
142
|
-
filters:
|
|
143
|
-
}, '
|
|
141
|
+
setQuery(strapiAdmin.withEncodedUserParams(query, {
|
|
142
|
+
filters: undefined
|
|
143
|
+
}), 'push', true);
|
|
144
144
|
} else {
|
|
145
|
-
setQuery({
|
|
145
|
+
setQuery(strapiAdmin.withEncodedUserParams(query, {
|
|
146
146
|
filters: serializeFilters(next)
|
|
147
|
-
}, 'push', true);
|
|
147
|
+
}), 'push', true);
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
150
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useListFilters.js","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useListFilters.ts"],"sourcesContent":["import { useQueryParams } from '@strapi/admin/strapi-admin';\n\n/**\n * Filter state for the assets list, backed by a single `?filters=` query param\n * so the applied filters are shareable and survive Back/refresh.\n *\n * Serialized grammar — badges separated by `;`, one badge is\n * `<field>:<condition>:<value>`:\n *\n * - `type:is:picture,audio` / `type:not:folder`\n * - `created:within:1week` / `updated:notwithin:3months` / `created:exact:1day`\n * - `created:rangeis:2024-01-01..2024-04-07` / `created:rangenot:...`\n *\n * Relative presets stay relative in the URL on purpose: the badge label\n * (\"1 week ago\") remains truthful forever and a shared link means \"within the\n * last week from whenever it is opened\". Resolution to absolute timestamps\n * happens at query-build time (see utils/buildAssetFilters.ts).\n *\n * Several badges for the same field are allowed, even contradictory ones —\n * everything is combined with AND; contradictions simply produce the filtered\n * empty state.\n */\n\nexport const TYPE_VALUES = ['folder', 'picture', 'audio', 'video', 'document'] as const;\nexport type TypeValue = (typeof TYPE_VALUES)[number];\n\nexport const DATE_PRESETS = [\n '1day',\n '3days',\n '1week',\n '1month',\n '3months',\n '6months',\n '1year',\n] as const;\nexport type DatePreset = (typeof DATE_PRESETS)[number];\n\nexport type TypeCondition = 'is' | 'isNot';\nexport type PresetCondition = 'isExactly' | 'withinLast' | 'notWithinLast';\nexport type RangeCondition = 'is' | 'isNot';\n\nexport type DateField = 'createdAt' | 'updatedAt';\n\nexport interface TypeFilter {\n kind: 'type';\n condition: TypeCondition;\n values: TypeValue[];\n}\n\nexport interface DatePresetFilter {\n kind: 'date';\n field: DateField;\n mode: 'preset';\n condition: PresetCondition;\n preset: DatePreset;\n}\n\nexport interface DateRangeFilter {\n kind: 'date';\n field: DateField;\n mode: 'range';\n condition: RangeCondition;\n /** Calendar dates, `YYYY-MM-DD`, inclusive on both ends. */\n from: string;\n to: string;\n}\n\nexport type DateFilter = DatePresetFilter | DateRangeFilter;\nexport type ListFilter = TypeFilter | DateFilter;\n\nconst FIELD_TOKENS: Record<string, DateField> = {\n created: 'createdAt',\n updated: 'updatedAt',\n};\nconst FIELD_TOKEN_BY_FIELD: Record<DateField, string> = {\n createdAt: 'created',\n updatedAt: 'updated',\n};\n\nconst PRESET_CONDITION_TOKENS: Record<string, PresetCondition> = {\n exact: 'isExactly',\n within: 'withinLast',\n notwithin: 'notWithinLast',\n};\nconst PRESET_TOKEN_BY_CONDITION: Record<PresetCondition, string> = {\n isExactly: 'exact',\n withinLast: 'within',\n notWithinLast: 'notwithin',\n};\n\nconst RANGE_CONDITION_TOKENS: Record<string, RangeCondition> = {\n rangeis: 'is',\n rangenot: 'isNot',\n};\nconst RANGE_TOKEN_BY_CONDITION: Record<RangeCondition, string> = {\n is: 'rangeis',\n isNot: 'rangenot',\n};\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nconst isTypeValue = (value: string): value is TypeValue =>\n (TYPE_VALUES as readonly string[]).includes(value);\n\nconst isDatePreset = (value: string): value is DatePreset =>\n (DATE_PRESETS as readonly string[]).includes(value);\n\nconst parseBadge = (raw: string): ListFilter | null => {\n const [field, condition, value] = raw.split(':');\n\n if (!field || !condition || !value) {\n return null;\n }\n\n if (field === 'type') {\n if (condition !== 'is' && condition !== 'not') {\n return null;\n }\n const values = value.split(',').filter(isTypeValue);\n\n return values.length > 0\n ? { kind: 'type', condition: condition === 'is' ? 'is' : 'isNot', values }\n : null;\n }\n\n const dateField = FIELD_TOKENS[field];\n if (!dateField) {\n return null;\n }\n\n if (condition in PRESET_CONDITION_TOKENS) {\n return isDatePreset(value)\n ? {\n kind: 'date',\n field: dateField,\n mode: 'preset',\n condition: PRESET_CONDITION_TOKENS[condition],\n preset: value,\n }\n : null;\n }\n\n if (condition in RANGE_CONDITION_TOKENS) {\n const [from, to] = value.split('..');\n\n return DATE_RE.test(from ?? '') && DATE_RE.test(to ?? '')\n ? {\n kind: 'date',\n field: dateField,\n mode: 'range',\n condition: RANGE_CONDITION_TOKENS[condition],\n from,\n to,\n }\n : null;\n }\n\n return null;\n};\n\n/**\n * `unknown` on purpose: the query string is user-editable, and qs parses\n * shapes like `?filters[]=a&filters[]=b` into arrays — the guard is what\n * makes the caller's `?filters=` type an assertion rather than a crash.\n */\nexport const parseFiltersParam = (raw: unknown): ListFilter[] => {\n if (typeof raw !== 'string' || raw === '') {\n return [];\n }\n\n return raw\n .split(';')\n .map(parseBadge)\n .filter((filter): filter is ListFilter => filter !== null);\n};\n\nconst serializeBadge = (filter: ListFilter): string => {\n if (filter.kind === 'type') {\n return `type:${filter.condition === 'is' ? 'is' : 'not'}:${filter.values.join(',')}`;\n }\n\n const field = FIELD_TOKEN_BY_FIELD[filter.field];\n\n if (filter.mode === 'preset') {\n return `${field}:${PRESET_TOKEN_BY_CONDITION[filter.condition]}:${filter.preset}`;\n }\n\n return `${field}:${RANGE_TOKEN_BY_CONDITION[filter.condition]}:${filter.from}..${filter.to}`;\n};\n\nexport const serializeFilters = (filters: ListFilter[]): string =>\n filters.map(serializeBadge).join(';');\n\nexport interface ListFilters {\n filters: ListFilter[];\n /** Serialized form — stable fingerprint for list-identity (selection reset). */\n serialized: string;\n addFilter: (filter: ListFilter) => void;\n /** Replaces the badge at `index` (condition or value edited in place). */\n updateFilter: (index: number, filter: ListFilter) => void;\n removeFilter: (index: number) => void;\n clearFilters: () => void;\n}\n\nexport const useListFilters = (): ListFilters => {\n const [{ query }, setQuery] = useQueryParams<{ filters?: string }>();\n\n const filters = parseFiltersParam(query?.filters);\n\n // `replace: true` on every write: filters are edited in rapid micro-steps\n // (each checkbox of the keep-open Type submenu, each badge segment edit), so\n // pushing would turn one logical change into a stack of Back entries. The\n // URL stays shareable and refresh-safe either way.\n const writeFilters = (next: ListFilter[]) => {\n if (next.length === 0) {\n setQuery({ filters: '' }, 'remove', true);\n } else {\n setQuery({ filters: serializeFilters(next) }, 'push', true);\n }\n };\n\n return {\n filters,\n serialized: serializeFilters(filters),\n addFilter: (filter) => writeFilters([...filters, filter]),\n updateFilter: (index, filter) =>\n writeFilters(filters.map((f, i) => (i === index ? filter : f))),\n removeFilter: (index) => writeFilters(filters.filter((_, i) => i !== index)),\n clearFilters: () => writeFilters([]),\n };\n};\n"],"names":["TYPE_VALUES","DATE_PRESETS","FIELD_TOKENS","created","updated","FIELD_TOKEN_BY_FIELD","createdAt","updatedAt","PRESET_CONDITION_TOKENS","exact","within","notwithin","PRESET_TOKEN_BY_CONDITION","isExactly","withinLast","notWithinLast","RANGE_CONDITION_TOKENS","rangeis","rangenot","RANGE_TOKEN_BY_CONDITION","is","isNot","DATE_RE","isTypeValue","value","includes","isDatePreset","parseBadge","raw","field","condition","split","values","filter","length","kind","dateField","mode","preset","from","to","test","parseFiltersParam","map","serializeBadge","join","serializeFilters","filters","useListFilters","query","setQuery","useQueryParams","writeFilters","next","serialized","addFilter","updateFilter","index","f","i","removeFilter","_","clearFilters"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useListFilters.js","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useListFilters.ts"],"sourcesContent":["import { useQueryParams, withEncodedUserParams } from '@strapi/admin/strapi-admin';\n\n/**\n * Filter state for the assets list, backed by a single `?filters=` query param\n * so the applied filters are shareable and survive Back/refresh.\n *\n * Serialized grammar — badges separated by `;`, one badge is\n * `<field>:<condition>:<value>`:\n *\n * - `type:is:picture,audio` / `type:not:folder`\n * - `created:within:1week` / `updated:notwithin:3months` / `created:exact:1day`\n * - `created:rangeis:2024-01-01..2024-04-07` / `created:rangenot:...`\n *\n * Relative presets stay relative in the URL on purpose: the badge label\n * (\"1 week ago\") remains truthful forever and a shared link means \"within the\n * last week from whenever it is opened\". Resolution to absolute timestamps\n * happens at query-build time (see utils/buildAssetFilters.ts).\n *\n * Several badges for the same field are allowed, even contradictory ones —\n * everything is combined with AND; contradictions simply produce the filtered\n * empty state.\n */\n\nexport const TYPE_VALUES = ['folder', 'picture', 'audio', 'video', 'document'] as const;\nexport type TypeValue = (typeof TYPE_VALUES)[number];\n\nexport const DATE_PRESETS = [\n '1day',\n '3days',\n '1week',\n '1month',\n '3months',\n '6months',\n '1year',\n] as const;\nexport type DatePreset = (typeof DATE_PRESETS)[number];\n\nexport type TypeCondition = 'is' | 'isNot';\nexport type PresetCondition = 'isExactly' | 'withinLast' | 'notWithinLast';\nexport type RangeCondition = 'is' | 'isNot';\n\nexport type DateField = 'createdAt' | 'updatedAt';\n\nexport interface TypeFilter {\n kind: 'type';\n condition: TypeCondition;\n values: TypeValue[];\n}\n\nexport interface DatePresetFilter {\n kind: 'date';\n field: DateField;\n mode: 'preset';\n condition: PresetCondition;\n preset: DatePreset;\n}\n\nexport interface DateRangeFilter {\n kind: 'date';\n field: DateField;\n mode: 'range';\n condition: RangeCondition;\n /** Calendar dates, `YYYY-MM-DD`, inclusive on both ends. */\n from: string;\n to: string;\n}\n\nexport type DateFilter = DatePresetFilter | DateRangeFilter;\nexport type ListFilter = TypeFilter | DateFilter;\n\nconst FIELD_TOKENS: Record<string, DateField> = {\n created: 'createdAt',\n updated: 'updatedAt',\n};\nconst FIELD_TOKEN_BY_FIELD: Record<DateField, string> = {\n createdAt: 'created',\n updatedAt: 'updated',\n};\n\nconst PRESET_CONDITION_TOKENS: Record<string, PresetCondition> = {\n exact: 'isExactly',\n within: 'withinLast',\n notwithin: 'notWithinLast',\n};\nconst PRESET_TOKEN_BY_CONDITION: Record<PresetCondition, string> = {\n isExactly: 'exact',\n withinLast: 'within',\n notWithinLast: 'notwithin',\n};\n\nconst RANGE_CONDITION_TOKENS: Record<string, RangeCondition> = {\n rangeis: 'is',\n rangenot: 'isNot',\n};\nconst RANGE_TOKEN_BY_CONDITION: Record<RangeCondition, string> = {\n is: 'rangeis',\n isNot: 'rangenot',\n};\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nconst isTypeValue = (value: string): value is TypeValue =>\n (TYPE_VALUES as readonly string[]).includes(value);\n\nconst isDatePreset = (value: string): value is DatePreset =>\n (DATE_PRESETS as readonly string[]).includes(value);\n\nconst parseBadge = (raw: string): ListFilter | null => {\n const [field, condition, value] = raw.split(':');\n\n if (!field || !condition || !value) {\n return null;\n }\n\n if (field === 'type') {\n if (condition !== 'is' && condition !== 'not') {\n return null;\n }\n const values = value.split(',').filter(isTypeValue);\n\n return values.length > 0\n ? { kind: 'type', condition: condition === 'is' ? 'is' : 'isNot', values }\n : null;\n }\n\n const dateField = FIELD_TOKENS[field];\n if (!dateField) {\n return null;\n }\n\n if (condition in PRESET_CONDITION_TOKENS) {\n return isDatePreset(value)\n ? {\n kind: 'date',\n field: dateField,\n mode: 'preset',\n condition: PRESET_CONDITION_TOKENS[condition],\n preset: value,\n }\n : null;\n }\n\n if (condition in RANGE_CONDITION_TOKENS) {\n const [from, to] = value.split('..');\n\n return DATE_RE.test(from ?? '') && DATE_RE.test(to ?? '')\n ? {\n kind: 'date',\n field: dateField,\n mode: 'range',\n condition: RANGE_CONDITION_TOKENS[condition],\n from,\n to,\n }\n : null;\n }\n\n return null;\n};\n\n/**\n * `unknown` on purpose: the query string is user-editable, and qs parses\n * shapes like `?filters[]=a&filters[]=b` into arrays — the guard is what\n * makes the caller's `?filters=` type an assertion rather than a crash.\n */\nexport const parseFiltersParam = (raw: unknown): ListFilter[] => {\n if (typeof raw !== 'string' || raw === '') {\n return [];\n }\n\n return raw\n .split(';')\n .map(parseBadge)\n .filter((filter): filter is ListFilter => filter !== null);\n};\n\nconst serializeBadge = (filter: ListFilter): string => {\n if (filter.kind === 'type') {\n return `type:${filter.condition === 'is' ? 'is' : 'not'}:${filter.values.join(',')}`;\n }\n\n const field = FIELD_TOKEN_BY_FIELD[filter.field];\n\n if (filter.mode === 'preset') {\n return `${field}:${PRESET_TOKEN_BY_CONDITION[filter.condition]}:${filter.preset}`;\n }\n\n return `${field}:${RANGE_TOKEN_BY_CONDITION[filter.condition]}:${filter.from}..${filter.to}`;\n};\n\nexport const serializeFilters = (filters: ListFilter[]): string =>\n filters.map(serializeBadge).join(';');\n\nexport interface ListFilters {\n filters: ListFilter[];\n /** Serialized form — stable fingerprint for list-identity (selection reset). */\n serialized: string;\n addFilter: (filter: ListFilter) => void;\n /** Replaces the badge at `index` (condition or value edited in place). */\n updateFilter: (index: number, filter: ListFilter) => void;\n removeFilter: (index: number) => void;\n clearFilters: () => void;\n}\n\nexport const useListFilters = (): ListFilters => {\n const [{ query }, setQuery] = useQueryParams<{ filters?: string; _q?: string }>();\n\n const filters = parseFiltersParam(query?.filters);\n\n // `replace: true` on every write: filters are edited in rapid micro-steps\n // (each checkbox of the keep-open Type submenu, each badge segment edit), so\n // pushing would turn one logical change into a stack of Back entries. The\n // URL stays shareable and refresh-safe either way.\n const writeFilters = (next: ListFilter[]) => {\n if (next.length === 0) {\n setQuery(withEncodedUserParams(query, { filters: undefined }), 'push', true);\n } else {\n setQuery(withEncodedUserParams(query, { filters: serializeFilters(next) }), 'push', true);\n }\n };\n\n return {\n filters,\n serialized: serializeFilters(filters),\n addFilter: (filter) => writeFilters([...filters, filter]),\n updateFilter: (index, filter) =>\n writeFilters(filters.map((f, i) => (i === index ? filter : f))),\n removeFilter: (index) => writeFilters(filters.filter((_, i) => i !== index)),\n clearFilters: () => writeFilters([]),\n };\n};\n"],"names":["TYPE_VALUES","DATE_PRESETS","FIELD_TOKENS","created","updated","FIELD_TOKEN_BY_FIELD","createdAt","updatedAt","PRESET_CONDITION_TOKENS","exact","within","notwithin","PRESET_TOKEN_BY_CONDITION","isExactly","withinLast","notWithinLast","RANGE_CONDITION_TOKENS","rangeis","rangenot","RANGE_TOKEN_BY_CONDITION","is","isNot","DATE_RE","isTypeValue","value","includes","isDatePreset","parseBadge","raw","field","condition","split","values","filter","length","kind","dateField","mode","preset","from","to","test","parseFiltersParam","map","serializeBadge","join","serializeFilters","filters","useListFilters","query","setQuery","useQueryParams","writeFilters","next","withEncodedUserParams","undefined","serialized","addFilter","updateFilter","index","f","i","removeFilter","_","clearFilters"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useQueryParams } from '@strapi/admin/strapi-admin';
|
|
1
|
+
import { useQueryParams, withEncodedUserParams } from '@strapi/admin/strapi-admin';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Filter state for the assets list, backed by a single `?filters=` query param
|
|
@@ -136,13 +136,13 @@ const useListFilters = ()=>{
|
|
|
136
136
|
// URL stays shareable and refresh-safe either way.
|
|
137
137
|
const writeFilters = (next)=>{
|
|
138
138
|
if (next.length === 0) {
|
|
139
|
-
setQuery({
|
|
140
|
-
filters:
|
|
141
|
-
}, '
|
|
139
|
+
setQuery(withEncodedUserParams(query, {
|
|
140
|
+
filters: undefined
|
|
141
|
+
}), 'push', true);
|
|
142
142
|
} else {
|
|
143
|
-
setQuery({
|
|
143
|
+
setQuery(withEncodedUserParams(query, {
|
|
144
144
|
filters: serializeFilters(next)
|
|
145
|
-
}, 'push', true);
|
|
145
|
+
}), 'push', true);
|
|
146
146
|
}
|
|
147
147
|
};
|
|
148
148
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useListFilters.mjs","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useListFilters.ts"],"sourcesContent":["import { useQueryParams } from '@strapi/admin/strapi-admin';\n\n/**\n * Filter state for the assets list, backed by a single `?filters=` query param\n * so the applied filters are shareable and survive Back/refresh.\n *\n * Serialized grammar — badges separated by `;`, one badge is\n * `<field>:<condition>:<value>`:\n *\n * - `type:is:picture,audio` / `type:not:folder`\n * - `created:within:1week` / `updated:notwithin:3months` / `created:exact:1day`\n * - `created:rangeis:2024-01-01..2024-04-07` / `created:rangenot:...`\n *\n * Relative presets stay relative in the URL on purpose: the badge label\n * (\"1 week ago\") remains truthful forever and a shared link means \"within the\n * last week from whenever it is opened\". Resolution to absolute timestamps\n * happens at query-build time (see utils/buildAssetFilters.ts).\n *\n * Several badges for the same field are allowed, even contradictory ones —\n * everything is combined with AND; contradictions simply produce the filtered\n * empty state.\n */\n\nexport const TYPE_VALUES = ['folder', 'picture', 'audio', 'video', 'document'] as const;\nexport type TypeValue = (typeof TYPE_VALUES)[number];\n\nexport const DATE_PRESETS = [\n '1day',\n '3days',\n '1week',\n '1month',\n '3months',\n '6months',\n '1year',\n] as const;\nexport type DatePreset = (typeof DATE_PRESETS)[number];\n\nexport type TypeCondition = 'is' | 'isNot';\nexport type PresetCondition = 'isExactly' | 'withinLast' | 'notWithinLast';\nexport type RangeCondition = 'is' | 'isNot';\n\nexport type DateField = 'createdAt' | 'updatedAt';\n\nexport interface TypeFilter {\n kind: 'type';\n condition: TypeCondition;\n values: TypeValue[];\n}\n\nexport interface DatePresetFilter {\n kind: 'date';\n field: DateField;\n mode: 'preset';\n condition: PresetCondition;\n preset: DatePreset;\n}\n\nexport interface DateRangeFilter {\n kind: 'date';\n field: DateField;\n mode: 'range';\n condition: RangeCondition;\n /** Calendar dates, `YYYY-MM-DD`, inclusive on both ends. */\n from: string;\n to: string;\n}\n\nexport type DateFilter = DatePresetFilter | DateRangeFilter;\nexport type ListFilter = TypeFilter | DateFilter;\n\nconst FIELD_TOKENS: Record<string, DateField> = {\n created: 'createdAt',\n updated: 'updatedAt',\n};\nconst FIELD_TOKEN_BY_FIELD: Record<DateField, string> = {\n createdAt: 'created',\n updatedAt: 'updated',\n};\n\nconst PRESET_CONDITION_TOKENS: Record<string, PresetCondition> = {\n exact: 'isExactly',\n within: 'withinLast',\n notwithin: 'notWithinLast',\n};\nconst PRESET_TOKEN_BY_CONDITION: Record<PresetCondition, string> = {\n isExactly: 'exact',\n withinLast: 'within',\n notWithinLast: 'notwithin',\n};\n\nconst RANGE_CONDITION_TOKENS: Record<string, RangeCondition> = {\n rangeis: 'is',\n rangenot: 'isNot',\n};\nconst RANGE_TOKEN_BY_CONDITION: Record<RangeCondition, string> = {\n is: 'rangeis',\n isNot: 'rangenot',\n};\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nconst isTypeValue = (value: string): value is TypeValue =>\n (TYPE_VALUES as readonly string[]).includes(value);\n\nconst isDatePreset = (value: string): value is DatePreset =>\n (DATE_PRESETS as readonly string[]).includes(value);\n\nconst parseBadge = (raw: string): ListFilter | null => {\n const [field, condition, value] = raw.split(':');\n\n if (!field || !condition || !value) {\n return null;\n }\n\n if (field === 'type') {\n if (condition !== 'is' && condition !== 'not') {\n return null;\n }\n const values = value.split(',').filter(isTypeValue);\n\n return values.length > 0\n ? { kind: 'type', condition: condition === 'is' ? 'is' : 'isNot', values }\n : null;\n }\n\n const dateField = FIELD_TOKENS[field];\n if (!dateField) {\n return null;\n }\n\n if (condition in PRESET_CONDITION_TOKENS) {\n return isDatePreset(value)\n ? {\n kind: 'date',\n field: dateField,\n mode: 'preset',\n condition: PRESET_CONDITION_TOKENS[condition],\n preset: value,\n }\n : null;\n }\n\n if (condition in RANGE_CONDITION_TOKENS) {\n const [from, to] = value.split('..');\n\n return DATE_RE.test(from ?? '') && DATE_RE.test(to ?? '')\n ? {\n kind: 'date',\n field: dateField,\n mode: 'range',\n condition: RANGE_CONDITION_TOKENS[condition],\n from,\n to,\n }\n : null;\n }\n\n return null;\n};\n\n/**\n * `unknown` on purpose: the query string is user-editable, and qs parses\n * shapes like `?filters[]=a&filters[]=b` into arrays — the guard is what\n * makes the caller's `?filters=` type an assertion rather than a crash.\n */\nexport const parseFiltersParam = (raw: unknown): ListFilter[] => {\n if (typeof raw !== 'string' || raw === '') {\n return [];\n }\n\n return raw\n .split(';')\n .map(parseBadge)\n .filter((filter): filter is ListFilter => filter !== null);\n};\n\nconst serializeBadge = (filter: ListFilter): string => {\n if (filter.kind === 'type') {\n return `type:${filter.condition === 'is' ? 'is' : 'not'}:${filter.values.join(',')}`;\n }\n\n const field = FIELD_TOKEN_BY_FIELD[filter.field];\n\n if (filter.mode === 'preset') {\n return `${field}:${PRESET_TOKEN_BY_CONDITION[filter.condition]}:${filter.preset}`;\n }\n\n return `${field}:${RANGE_TOKEN_BY_CONDITION[filter.condition]}:${filter.from}..${filter.to}`;\n};\n\nexport const serializeFilters = (filters: ListFilter[]): string =>\n filters.map(serializeBadge).join(';');\n\nexport interface ListFilters {\n filters: ListFilter[];\n /** Serialized form — stable fingerprint for list-identity (selection reset). */\n serialized: string;\n addFilter: (filter: ListFilter) => void;\n /** Replaces the badge at `index` (condition or value edited in place). */\n updateFilter: (index: number, filter: ListFilter) => void;\n removeFilter: (index: number) => void;\n clearFilters: () => void;\n}\n\nexport const useListFilters = (): ListFilters => {\n const [{ query }, setQuery] = useQueryParams<{ filters?: string }>();\n\n const filters = parseFiltersParam(query?.filters);\n\n // `replace: true` on every write: filters are edited in rapid micro-steps\n // (each checkbox of the keep-open Type submenu, each badge segment edit), so\n // pushing would turn one logical change into a stack of Back entries. The\n // URL stays shareable and refresh-safe either way.\n const writeFilters = (next: ListFilter[]) => {\n if (next.length === 0) {\n setQuery({ filters: '' }, 'remove', true);\n } else {\n setQuery({ filters: serializeFilters(next) }, 'push', true);\n }\n };\n\n return {\n filters,\n serialized: serializeFilters(filters),\n addFilter: (filter) => writeFilters([...filters, filter]),\n updateFilter: (index, filter) =>\n writeFilters(filters.map((f, i) => (i === index ? filter : f))),\n removeFilter: (index) => writeFilters(filters.filter((_, i) => i !== index)),\n clearFilters: () => writeFilters([]),\n };\n};\n"],"names":["TYPE_VALUES","DATE_PRESETS","FIELD_TOKENS","created","updated","FIELD_TOKEN_BY_FIELD","createdAt","updatedAt","PRESET_CONDITION_TOKENS","exact","within","notwithin","PRESET_TOKEN_BY_CONDITION","isExactly","withinLast","notWithinLast","RANGE_CONDITION_TOKENS","rangeis","rangenot","RANGE_TOKEN_BY_CONDITION","is","isNot","DATE_RE","isTypeValue","value","includes","isDatePreset","parseBadge","raw","field","condition","split","values","filter","length","kind","dateField","mode","preset","from","to","test","parseFiltersParam","map","serializeBadge","join","serializeFilters","filters","useListFilters","query","setQuery","useQueryParams","writeFilters","next","serialized","addFilter","updateFilter","index","f","i","removeFilter","_","clearFilters"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useListFilters.mjs","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useListFilters.ts"],"sourcesContent":["import { useQueryParams, withEncodedUserParams } from '@strapi/admin/strapi-admin';\n\n/**\n * Filter state for the assets list, backed by a single `?filters=` query param\n * so the applied filters are shareable and survive Back/refresh.\n *\n * Serialized grammar — badges separated by `;`, one badge is\n * `<field>:<condition>:<value>`:\n *\n * - `type:is:picture,audio` / `type:not:folder`\n * - `created:within:1week` / `updated:notwithin:3months` / `created:exact:1day`\n * - `created:rangeis:2024-01-01..2024-04-07` / `created:rangenot:...`\n *\n * Relative presets stay relative in the URL on purpose: the badge label\n * (\"1 week ago\") remains truthful forever and a shared link means \"within the\n * last week from whenever it is opened\". Resolution to absolute timestamps\n * happens at query-build time (see utils/buildAssetFilters.ts).\n *\n * Several badges for the same field are allowed, even contradictory ones —\n * everything is combined with AND; contradictions simply produce the filtered\n * empty state.\n */\n\nexport const TYPE_VALUES = ['folder', 'picture', 'audio', 'video', 'document'] as const;\nexport type TypeValue = (typeof TYPE_VALUES)[number];\n\nexport const DATE_PRESETS = [\n '1day',\n '3days',\n '1week',\n '1month',\n '3months',\n '6months',\n '1year',\n] as const;\nexport type DatePreset = (typeof DATE_PRESETS)[number];\n\nexport type TypeCondition = 'is' | 'isNot';\nexport type PresetCondition = 'isExactly' | 'withinLast' | 'notWithinLast';\nexport type RangeCondition = 'is' | 'isNot';\n\nexport type DateField = 'createdAt' | 'updatedAt';\n\nexport interface TypeFilter {\n kind: 'type';\n condition: TypeCondition;\n values: TypeValue[];\n}\n\nexport interface DatePresetFilter {\n kind: 'date';\n field: DateField;\n mode: 'preset';\n condition: PresetCondition;\n preset: DatePreset;\n}\n\nexport interface DateRangeFilter {\n kind: 'date';\n field: DateField;\n mode: 'range';\n condition: RangeCondition;\n /** Calendar dates, `YYYY-MM-DD`, inclusive on both ends. */\n from: string;\n to: string;\n}\n\nexport type DateFilter = DatePresetFilter | DateRangeFilter;\nexport type ListFilter = TypeFilter | DateFilter;\n\nconst FIELD_TOKENS: Record<string, DateField> = {\n created: 'createdAt',\n updated: 'updatedAt',\n};\nconst FIELD_TOKEN_BY_FIELD: Record<DateField, string> = {\n createdAt: 'created',\n updatedAt: 'updated',\n};\n\nconst PRESET_CONDITION_TOKENS: Record<string, PresetCondition> = {\n exact: 'isExactly',\n within: 'withinLast',\n notwithin: 'notWithinLast',\n};\nconst PRESET_TOKEN_BY_CONDITION: Record<PresetCondition, string> = {\n isExactly: 'exact',\n withinLast: 'within',\n notWithinLast: 'notwithin',\n};\n\nconst RANGE_CONDITION_TOKENS: Record<string, RangeCondition> = {\n rangeis: 'is',\n rangenot: 'isNot',\n};\nconst RANGE_TOKEN_BY_CONDITION: Record<RangeCondition, string> = {\n is: 'rangeis',\n isNot: 'rangenot',\n};\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nconst isTypeValue = (value: string): value is TypeValue =>\n (TYPE_VALUES as readonly string[]).includes(value);\n\nconst isDatePreset = (value: string): value is DatePreset =>\n (DATE_PRESETS as readonly string[]).includes(value);\n\nconst parseBadge = (raw: string): ListFilter | null => {\n const [field, condition, value] = raw.split(':');\n\n if (!field || !condition || !value) {\n return null;\n }\n\n if (field === 'type') {\n if (condition !== 'is' && condition !== 'not') {\n return null;\n }\n const values = value.split(',').filter(isTypeValue);\n\n return values.length > 0\n ? { kind: 'type', condition: condition === 'is' ? 'is' : 'isNot', values }\n : null;\n }\n\n const dateField = FIELD_TOKENS[field];\n if (!dateField) {\n return null;\n }\n\n if (condition in PRESET_CONDITION_TOKENS) {\n return isDatePreset(value)\n ? {\n kind: 'date',\n field: dateField,\n mode: 'preset',\n condition: PRESET_CONDITION_TOKENS[condition],\n preset: value,\n }\n : null;\n }\n\n if (condition in RANGE_CONDITION_TOKENS) {\n const [from, to] = value.split('..');\n\n return DATE_RE.test(from ?? '') && DATE_RE.test(to ?? '')\n ? {\n kind: 'date',\n field: dateField,\n mode: 'range',\n condition: RANGE_CONDITION_TOKENS[condition],\n from,\n to,\n }\n : null;\n }\n\n return null;\n};\n\n/**\n * `unknown` on purpose: the query string is user-editable, and qs parses\n * shapes like `?filters[]=a&filters[]=b` into arrays — the guard is what\n * makes the caller's `?filters=` type an assertion rather than a crash.\n */\nexport const parseFiltersParam = (raw: unknown): ListFilter[] => {\n if (typeof raw !== 'string' || raw === '') {\n return [];\n }\n\n return raw\n .split(';')\n .map(parseBadge)\n .filter((filter): filter is ListFilter => filter !== null);\n};\n\nconst serializeBadge = (filter: ListFilter): string => {\n if (filter.kind === 'type') {\n return `type:${filter.condition === 'is' ? 'is' : 'not'}:${filter.values.join(',')}`;\n }\n\n const field = FIELD_TOKEN_BY_FIELD[filter.field];\n\n if (filter.mode === 'preset') {\n return `${field}:${PRESET_TOKEN_BY_CONDITION[filter.condition]}:${filter.preset}`;\n }\n\n return `${field}:${RANGE_TOKEN_BY_CONDITION[filter.condition]}:${filter.from}..${filter.to}`;\n};\n\nexport const serializeFilters = (filters: ListFilter[]): string =>\n filters.map(serializeBadge).join(';');\n\nexport interface ListFilters {\n filters: ListFilter[];\n /** Serialized form — stable fingerprint for list-identity (selection reset). */\n serialized: string;\n addFilter: (filter: ListFilter) => void;\n /** Replaces the badge at `index` (condition or value edited in place). */\n updateFilter: (index: number, filter: ListFilter) => void;\n removeFilter: (index: number) => void;\n clearFilters: () => void;\n}\n\nexport const useListFilters = (): ListFilters => {\n const [{ query }, setQuery] = useQueryParams<{ filters?: string; _q?: string }>();\n\n const filters = parseFiltersParam(query?.filters);\n\n // `replace: true` on every write: filters are edited in rapid micro-steps\n // (each checkbox of the keep-open Type submenu, each badge segment edit), so\n // pushing would turn one logical change into a stack of Back entries. The\n // URL stays shareable and refresh-safe either way.\n const writeFilters = (next: ListFilter[]) => {\n if (next.length === 0) {\n setQuery(withEncodedUserParams(query, { filters: undefined }), 'push', true);\n } else {\n setQuery(withEncodedUserParams(query, { filters: serializeFilters(next) }), 'push', true);\n }\n };\n\n return {\n filters,\n serialized: serializeFilters(filters),\n addFilter: (filter) => writeFilters([...filters, filter]),\n updateFilter: (index, filter) =>\n writeFilters(filters.map((f, i) => (i === index ? filter : f))),\n removeFilter: (index) => writeFilters(filters.filter((_, i) => i !== index)),\n clearFilters: () => writeFilters([]),\n };\n};\n"],"names":["TYPE_VALUES","DATE_PRESETS","FIELD_TOKENS","created","updated","FIELD_TOKEN_BY_FIELD","createdAt","updatedAt","PRESET_CONDITION_TOKENS","exact","within","notwithin","PRESET_TOKEN_BY_CONDITION","isExactly","withinLast","notWithinLast","RANGE_CONDITION_TOKENS","rangeis","rangenot","RANGE_TOKEN_BY_CONDITION","is","isNot","DATE_RE","isTypeValue","value","includes","isDatePreset","parseBadge","raw","field","condition","split","values","filter","length","kind","dateField","mode","preset","from","to","test","parseFiltersParam","map","serializeBadge","join","serializeFilters","filters","useListFilters","query","setQuery","useQueryParams","writeFilters","next","withEncodedUserParams","undefined","serialized","addFilter","updateFilter","index","f","i","removeFilter","_","clearFilters"],"mappings":"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"}
|