From d2b579d12704e87c5d274002927fcae23064f344 Mon Sep 17 00:00:00 2001 From: Misael dos Santos Date: Sat, 26 Nov 2022 19:35:35 -0300 Subject: [PATCH] feat: add virtual pagination in game list (library) --- package.json | 1 + src/frontend/hooks/usePaginatedList.ts | 53 ++++++++++++ .../Library/components/GamesList/index.tsx | 83 ++++++++++++------- 3 files changed, 107 insertions(+), 30 deletions(-) create mode 100644 src/frontend/hooks/usePaginatedList.ts diff --git a/package.json b/package.json index f2ce2b2322..e2884b7f39 100644 --- a/package.json +++ b/package.json @@ -154,6 +154,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^11.16.7", + "react-infinite-scroll-hook": "^4.0.4", "react-router-dom": "^6.3.0", "recharts": "^2.1.14", "shlex": "^2.1.2", diff --git a/src/frontend/hooks/usePaginatedList.ts b/src/frontend/hooks/usePaginatedList.ts new file mode 100644 index 0000000000..108b47489f --- /dev/null +++ b/src/frontend/hooks/usePaginatedList.ts @@ -0,0 +1,53 @@ +import useInfiniteScroll from 'react-infinite-scroll-hook' +import { useState, useEffect, useCallback } from 'react' + +// TODO: improvement suggestion: paginate in backend +export default function usePaginatedList( + list: T[], + { rpp, infinite }: { rpp: number; infinite?: boolean } +) { + const loadPage = useCallback( + (page: number) => { + const offset = rpp * (page - 1) + return list.slice(offset, offset + rpp) + }, + [list] + ) + + const [paginatedList, setPaginatedList] = useState(() => loadPage(1)) + const [page, setPage] = useState(1) + + useEffect(() => { + setPaginatedList(loadPage(1)) + }, [loadPage, list]) + + const hasMore = paginatedList.length !== list.length + + const loadMore = useCallback(() => { + if (!hasMore) { + return + } + + setPage(page + 1) + const newListPage = loadPage(page + 1) + if (infinite) { + setPaginatedList([...paginatedList, ...newListPage]) + } else { + setPaginatedList(newListPage) + } + }, [hasMore, page, paginatedList, loadPage]) + + const [sentryRef] = useInfiniteScroll({ + loading: false, + hasNextPage: hasMore, + onLoadMore: loadMore + }) + + return { + loadMore: loadMore, + page, + paginatedList, + hasMore, + infiniteScrollSentryRef: sentryRef + } +} diff --git a/src/frontend/screens/Library/components/GamesList/index.tsx b/src/frontend/screens/Library/components/GamesList/index.tsx index aecc84d65c..0da61d5eb0 100644 --- a/src/frontend/screens/Library/components/GamesList/index.tsx +++ b/src/frontend/screens/Library/components/GamesList/index.tsx @@ -4,6 +4,7 @@ import cx from 'classnames' import GameCard from '../GameCard' import ContextProvider from 'frontend/state/ContextProvider' import { useTranslation } from 'react-i18next' +import usePaginatedList from 'frontend/hooks/usePaginatedList' interface Props { library: GameInfo[] @@ -29,9 +30,51 @@ const GamesList = ({ const { gameUpdates } = useContext(ContextProvider) const { t } = useTranslation() + const { infiniteScrollSentryRef, paginatedList, hasMore } = usePaginatedList( + library, + { + rpp: 10, + infinite: true + } + ) + + const renderGameInfo = (gameInfo: GameInfo) => { + const { + app_name, + is_installed, + runner, + install: { is_dlc } + } = gameInfo + + if (is_dlc) { + return null + } + if (!is_installed && onlyInstalled) { + return null + } + + const hasUpdate = is_installed && gameUpdates?.includes(app_name) + return ( + handleGameCardClick(app_name, runner, gameInfo)} + forceCard={layout === 'grid'} + isRecent={isRecent} + gameInfo={gameInfo} + /> + ) + } + return (
{t('wine.actions', 'Action')}
)} - {!!library.length && - library.map((gameInfo) => { - const { - app_name, - is_installed, - runner, - install: { is_dlc } - } = gameInfo - if (is_dlc) { - return null - } - if (!is_installed && onlyInstalled) { - return null - } - - const hasUpdate = is_installed && gameUpdates?.includes(app_name) - return ( - - handleGameCardClick(app_name, runner, gameInfo) - } - forceCard={layout === 'grid'} - isRecent={isRecent} - gameInfo={gameInfo} - /> - ) - })} + {paginatedList.map((item) => { + return renderGameInfo(item) + })} + {hasMore && ( +
+ )}
) }