From a57b2bbee2f5f5f4d77ee4bfced423afd18abb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?= <4895034+ClementPasteau@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:43:15 +0200 Subject: [PATCH] Fix asset store pagination when navigating folders and correctly installing visible assets (#8466) --- newIDE/app/src/AssetStore/AssetsList.js | 50 +++++++++++++++---- newIDE/app/src/AssetStore/NewObjectDialog.js | 36 +++++++++---- .../HomePage/StoreSection/index.js | 49 +++++++++++++----- 3 files changed, 104 insertions(+), 31 deletions(-) diff --git a/newIDE/app/src/AssetStore/AssetsList.js b/newIDE/app/src/AssetStore/AssetsList.js index 8d083070a7..d7be0e2f29 100644 --- a/newIDE/app/src/AssetStore/AssetsList.js +++ b/newIDE/app/src/AssetStore/AssetsList.js @@ -111,17 +111,28 @@ const getPageBreakAssetLowerIndex = (pageBreakIndex: number) => const getPageBreakAssetUpperIndex = (pageBreakIndex: number) => ASSETS_DISPLAY_LIMIT * (pageBreakIndex + 1); +export const filterAssetShortHeadersByFolders = ( + allAssetShortHeaders: AssetShortHeader[], + selectedFolders: string[] +): AssetShortHeader[] => { + if (!selectedFolders.length) return allAssetShortHeaders; + return allAssetShortHeaders.filter(assetShortHeader => + // Check that the asset has all the selected folders tags. + selectedFolders.every(folderTag => + assetShortHeader.tags.includes(folderTag) + ) + ); +}; + export const getAssetShortHeadersToDisplay = ( allAssetShortHeaders: AssetShortHeader[], selectedFolders: string[], pageBreakIndex: number = 0 ): AssetShortHeader[] => { - let assetShortHeaders = allAssetShortHeaders.filter(assetShortHeader => { - if (!selectedFolders.length) return true; - const allAssetTags = assetShortHeader.tags; - // Check that the asset has all the selected folders tags. - return selectedFolders.every(folderTag => allAssetTags.includes(folderTag)); - }); + let assetShortHeaders = filterAssetShortHeadersByFolders( + allAssetShortHeaders, + selectedFolders + ); // Limit the number of displayed assets to avoid performance issues const pageBreakAssetLowerIndex = getPageBreakAssetLowerIndex(pageBreakIndex); const pageBreakAssetUpperIndex = Math.min( @@ -154,6 +165,8 @@ const styles = { scrollView: { display: 'flex', flexDirection: 'column', + // Prevent horizontal scrollbars. + overflowX: 'hidden', }, previewImageContainer: { display: 'flex', @@ -341,6 +354,25 @@ const AssetsList: React.ComponentType<{ }, [currentPage] ); + React.useEffect( + () => { + setPageBreakIndex(0); + if (currentPage) currentPage.pageBreakIndex = 0; + }, + // selectedFolders is a new array reference each time a folder is selected, + // so this resets pagination whenever the folder selection changes. + // eslint-disable-next-line react-hooks/exhaustive-deps + [selectedFolders] + ); + const folderFilteredAssetShortHeaders = React.useMemo< + Array + >( + () => + assetShortHeaders + ? filterAssetShortHeadersByFolders(assetShortHeaders, selectedFolders) + : [], + [assetShortHeaders, selectedFolders] + ); const { windowSize, isLandscape } = useResponsiveWindowSize(); const scrollView = React.useRef(null); React.useImperativeHandle(ref, () => ({ @@ -973,13 +1005,13 @@ const AssetsList: React.ComponentType<{ !isAssetPackAudioOnly(openedAssetPack)) && noResultComponent} {currentPage && - assetShortHeaders && - assetShortHeaders.length > getPageBreakAssetUpperIndex(0) && ( + folderFilteredAssetShortHeaders.length > + getPageBreakAssetUpperIndex(0) && ( )} diff --git a/newIDE/app/src/AssetStore/NewObjectDialog.js b/newIDE/app/src/AssetStore/NewObjectDialog.js index 45cf86d252..6618973809 100644 --- a/newIDE/app/src/AssetStore/NewObjectDialog.js +++ b/newIDE/app/src/AssetStore/NewObjectDialog.js @@ -308,9 +308,9 @@ function NewObjectDialog({ selectedFolders, } = shopNavigationState.getCurrentPage(); const [ - isAssetPackDialogInstallOpen, - setIsAssetPackDialogInstallOpen, - ] = React.useState(false); + assetShortHeadersToInstall, + setAssetShortHeadersToInstall, + ] = React.useState>(null); // Avoid memoizing the result of enumerateAssetStoreIds, as it does not get updated // when adding assets. const existingAssetStoreIds = enumerateAssetStoreIds( @@ -423,6 +423,22 @@ function NewObjectDialog({ [assetShortHeadersSearchResults, selectedFolders] ); + const openAssetPackInstallDialog = React.useCallback( + () => { + const currentPage = shopNavigationState.getCurrentPage(); + setAssetShortHeadersToInstall( + assetShortHeadersSearchResults + ? getAssetShortHeadersToDisplay( + assetShortHeadersSearchResults, + currentPage.selectedFolders, + currentPage.pageBreakIndex || 0 + ) + : [] + ); + }, + [shopNavigationState, assetShortHeadersSearchResults] + ); + const mainAction = currentTab === 'asset-store' ? ( openedAssetPack ? ( @@ -436,7 +452,7 @@ function NewObjectDialog({ Add these assets to my scene ) } - onClick={() => setIsAssetPackDialogInstallOpen(true)} + onClick={openAssetPackInstallDialog} disabled={ !displayedAssetShortHeaders || displayedAssetShortHeaders.length === 0 @@ -543,7 +559,7 @@ function NewObjectDialog({ onRequestClose={handleClose} onApply={ openedAssetPack - ? () => setIsAssetPackDialogInstallOpen(true) + ? openAssetPackInstallDialog : openedAssetShortHeader ? async () => { await onInstallAsset(openedAssetShortHeader); @@ -603,16 +619,16 @@ function NewObjectDialog({ ))} {isAssetBeingInstalled && } - {isAssetPackDialogInstallOpen && - displayedAssetShortHeaders && + {assetShortHeadersToInstall && + !!assetShortHeadersToInstall.length && openedAssetPack && ( setIsAssetPackDialogInstallOpen(false)} + onClose={() => setAssetShortHeadersToInstall(null)} onAssetsAdded={installAssetOutput => { - setIsAssetPackDialogInstallOpen(false); + setAssetShortHeadersToInstall(null); onObjectsAddedFromAssets(installAssetOutput); }} project={project} diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/StoreSection/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/StoreSection/index.js index 43621fa04d..b9cba26909 100644 --- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/StoreSection/index.js +++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/StoreSection/index.js @@ -10,7 +10,10 @@ import { AssetStoreContext } from '../../../../AssetStore/AssetStoreContext'; import AssetPackInstallDialog from '../../../../AssetStore/AssetPackInstallDialog'; import { enumerateAssetStoreIds } from '../../../../AssetStore/EnumerateAssetStoreIds'; import { type PrivateGameTemplateListingData } from '../../../../Utils/GDevelopServices/Shop'; -import { type Course } from '../../../../Utils/GDevelopServices/Asset'; +import { + type AssetShortHeader, + type Course, +} from '../../../../Utils/GDevelopServices/Asset'; import ErrorBoundary from '../../../../UI/ErrorBoundary'; import { getAssetShortHeadersToDisplay } from '../../../../AssetStore/AssetsList'; import { AssetStoreNavigatorContext } from '../../../../AssetStore/AssetStoreNavigator'; @@ -42,9 +45,9 @@ const StoreSection = ({ getCourseCompletion, }: Props) => { const [ - isAssetPackDialogInstallOpen, - setIsAssetPackDialogInstallOpen, - ] = React.useState(false); + assetShortHeadersToInstall, + setAssetShortHeadersToInstall, + ] = React.useState>(null); const shopNavigationState = React.useContext(AssetStoreNavigatorContext); const { assetShortHeadersSearchResults } = React.useContext( AssetStoreContext @@ -71,7 +74,7 @@ const StoreSection = ({ const existingAssetStoreIds = React.useMemo( () => { - if (!project || !isAssetPackDialogInstallOpen) { + if (!project || !assetShortHeadersToInstall) { return new Set(); } @@ -82,7 +85,29 @@ const StoreSection = ({ // Force recompute existing installed asset ids when the dialog to install them is // opened/closed, so that this list of ids is always up-to-date (but not recomputed at each render, // just when you're likely to need it). - isAssetPackDialogInstallOpen, + assetShortHeadersToInstall, + ] + ); + + const openAssetPackInstallDialog = React.useCallback( + () => { + const currentPage = shopNavigationState.getCurrentPage(); + setAssetShortHeadersToInstall( + openedAssetShortHeader + ? [openedAssetShortHeader] + : assetShortHeadersSearchResults + ? getAssetShortHeadersToDisplay( + assetShortHeadersSearchResults, + currentPage.selectedFolders, + currentPage.pageBreakIndex || 0 + ) + : [] + ); + }, + [ + shopNavigationState, + openedAssetShortHeader, + assetShortHeadersSearchResults, ] ); @@ -107,7 +132,7 @@ const StoreSection = ({ return; // TODO: create a project, await, and then show dialog. } - setIsAssetPackDialogInstallOpen(true); + openAssetPackInstallDialog(); }} disabled={!project || !displayedAssetShortHeaders.length} label={ @@ -131,15 +156,15 @@ const StoreSection = ({ )} {project && - isAssetPackDialogInstallOpen && - !!displayedAssetShortHeaders.length && ( + assetShortHeadersToInstall && + !!assetShortHeadersToInstall.length && ( setIsAssetPackDialogInstallOpen(false)} + onClose={() => setAssetShortHeadersToInstall(null)} onAssetsAdded={() => { - setIsAssetPackDialogInstallOpen(false); + setAssetShortHeadersToInstall(null); }} project={project} objectsContainer={null}