Skip to content

Commit

Permalink
Merge branch 'master' into game-hash-management-filament
Browse files Browse the repository at this point in the history
  • Loading branch information
wescopeland authored Jan 1, 2025
2 parents 1d53178 + e5b24bc commit dac2da2
Show file tree
Hide file tree
Showing 32 changed files with 732 additions and 298 deletions.
6 changes: 5 additions & 1 deletion app/Platform/Controllers/GameController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use App\Platform\Actions\GetRandomGameAction;
use App\Platform\Data\GameListPagePropsData;
use App\Platform\Data\SystemData;
use App\Platform\Enums\GameListSortField;
use App\Platform\Enums\GameListType;
use App\Platform\Requests\GameListRequest;
use App\Platform\Requests\GameRequest;
Expand Down Expand Up @@ -46,7 +47,10 @@ public function index(GameListRequest $request): InertiaResponse
GameListType::AllGames,
user: $user,
filters: $request->getFilters(),
sort: $request->getSort(),
sort: $request->getSort(
defaultSortField: GameListSortField::PlayersTotal,
isDefaultSortAsc: false,
),
perPage: $isMobile ? 100 : $request->getPageSize(),

/**
Expand Down
10 changes: 6 additions & 4 deletions app/Platform/Requests/GameListRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ public function getPageSize(): int
/**
* @return array{field: string, direction: 'asc'|'desc'}
*/
public function getSort(): array
{
public function getSort(
GameListSortField $defaultSortField = GameListSortField::Title,
bool $isDefaultSortAsc = true,
): array {
// URL params take precedence over cookie preferences.
$sortParam = $this->input('sort');

Expand All @@ -102,9 +104,9 @@ public function getSort(): array
}

// If we still don't have a sort param, fall back to sorting by title.
$sortParam ??= GameListSortField::Title->value;
$sortParam ??= $defaultSortField->value;

$sortDirection = 'asc';
$sortDirection = $isDefaultSortAsc ? 'asc' : 'desc';
if (str_starts_with($sortParam, '-')) {
$sortDirection = 'desc';
$sortParam = ltrim($sortParam, '-');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { type Dispatch, type FC, lazy, type SetStateAction, Suspense } from 'rea
import { usePageProps } from '@/common/hooks/usePageProps';
import { useGameListPaginatedQuery } from '@/features/game-list/hooks/useGameListPaginatedQuery';

import { allGamesDefaultFilters } from '../../utils/allGamesDefaultFilters';
import { useAllGamesDefaultColumnState } from '../AllGamesMainRoot/useAllGamesDefaultColumnState';
import { DataTablePagination } from '../DataTablePagination';
import { DataTableToolbar } from '../DataTableToolbar';
import { GameListDataTable } from '../GameListDataTable';
Expand Down Expand Up @@ -44,6 +44,8 @@ export const AllGamesDataTable: FC<AllGamesDataTableProps> = ({
}) => {
const { can, ziggy } = usePageProps<App.Community.Data.UserGameListPageProps>();

const { defaultColumnFilters } = useAllGamesDefaultColumnState();

const gameListQuery = useGameListPaginatedQuery({
columnFilters,
pagination,
Expand Down Expand Up @@ -82,7 +84,7 @@ export const AllGamesDataTable: FC<AllGamesDataTableProps> = ({
<DataTableToolbar
table={table}
unfilteredTotal={gameListQuery.data?.unfilteredTotal ?? null}
defaultColumnFilters={allGamesDefaultFilters}
defaultColumnFilters={defaultColumnFilters}
/>

{ziggy.device === 'mobile' ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ describe('Component: AllGamesMainRoot', () => {
'filter[title]': 'dragon quest',
'page[number]': 1,
'page[size]': 25,
sort: 'title',
sort: '-playersTotal',
},
]);
});
Expand Down Expand Up @@ -409,7 +409,7 @@ describe('Component: AllGamesMainRoot', () => {
'filter[system]': '1',
'page[number]': 1,
'page[size]': 25,
sort: 'title',
sort: '-playersTotal',
},
]);
});
Expand Down Expand Up @@ -507,7 +507,7 @@ describe('Component: AllGamesMainRoot', () => {
'filter[achievementsPublished]': 'none',
'page[number]': 1,
'page[size]': 25,
sort: 'title',
sort: '-playersTotal',
},
]);
});
Expand Down Expand Up @@ -716,7 +716,7 @@ describe('Component: AllGamesMainRoot', () => {
'filter[achievementsPublished]': 'has',
'page[number]': 2,
'page[size]': 50,
sort: 'title',
sort: '-playersTotal',
},
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ import { useGameListState } from '../../hooks/useGameListState';
import { usePreloadedTableDataQueryClient } from '../../hooks/usePreloadedTableDataQueryClient';
import { useTableSync } from '../../hooks/useTableSync';
import { isCurrentlyPersistingViewAtom } from '../../state/game-list.atoms';
import { allGamesDefaultFilters } from '../../utils/allGamesDefaultFilters';
import { AllGamesDataTable } from '../AllGamesDataTable';
import { DataTablePaginationScrollTarget } from '../DataTablePaginationScrollTarget';
import { useAllGamesDefaultColumnState } from './useAllGamesDefaultColumnState';

export const AllGamesMainRoot: FC = memo(() => {
const { auth, defaultDesktopPageSize, paginatedGameListEntries } =
const { defaultDesktopPageSize, paginatedGameListEntries } =
usePageProps<App.Platform.Data.GameListPageProps>();

const { t } = useTranslation();

const { defaultColumnFilters, defaultColumnSort, defaultColumnVisibility } =
useAllGamesDefaultColumnState();

const {
columnFilters,
columnVisibility,
Expand All @@ -29,8 +32,9 @@ export const AllGamesMainRoot: FC = memo(() => {
setSorting,
sorting,
} = useGameListState(paginatedGameListEntries, {
canShowProgressColumn: !!auth?.user,
defaultColumnFilters: allGamesDefaultFilters,
defaultColumnSort,
defaultColumnFilters,
defaultColumnVisibility,
});

const { queryClientWithInitialData } = usePreloadedTableDataQueryClient({
Expand All @@ -45,9 +49,10 @@ export const AllGamesMainRoot: FC = memo(() => {
useTableSync({
columnFilters,
columnVisibility,
defaultColumnFilters,
defaultColumnSort,
pagination,
sorting,
defaultFilters: allGamesDefaultFilters,
defaultPageSize: defaultDesktopPageSize,
isUserPersistenceEnabled: isCurrentlyPersistingView,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ColumnFiltersState, ColumnSort } from '@tanstack/react-table';
import { useMemo } from 'react';

import { usePageProps } from '@/common/hooks/usePageProps';

import type { DefaultColumnState } from '../../models';
import { buildInitialDefaultColumnVisibility } from '../../utils/buildInitialDefaultColumnVisibility';

export function useAllGamesDefaultColumnState(): DefaultColumnState {
const { auth } = usePageProps();

return useMemo(() => {
const defaultColumnFilters: ColumnFiltersState = [
{ id: 'achievementsPublished', value: ['has'] },
];

const defaultColumnSort: ColumnSort = { id: 'playersTotal', desc: true };

const defaultColumnVisibility: Partial<Record<App.Platform.Enums.GameListSortField, boolean>> =
{
...buildInitialDefaultColumnVisibility(!!auth?.user),
};

return { defaultColumnFilters, defaultColumnSort, defaultColumnVisibility };
}, [auth?.user]);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,107 @@ describe('Component: GameListItems', () => {

expect(screen.getByText(/game 4/i)).toBeVisible();
});

it(
'given the user scrolls to the bottom multiple times, only prefetches each page once',
{ timeout: 10_000 },
async () => {
// ARRANGE
mockAllIsIntersecting(false);

const firstPageGames = [
createGameListEntry({ game: createGame({ title: 'Game 1' }) }),
createGameListEntry({ game: createGame({ title: 'Game 2' }) }),
];
const secondPageGames = [
createGameListEntry({ game: createGame({ title: 'Game 3' }) }),
createGameListEntry({ game: createGame({ title: 'Game 4' }) }),
];
const thirdPageGames = [
createGameListEntry({ game: createGame({ title: 'Game 5' }) }),
createGameListEntry({ game: createGame({ title: 'Game 6' }) }),
];

const firstPageData: App.Data.PaginatedData<App.Platform.Data.GameListEntry> = {
currentPage: 1,
lastPage: 3,
perPage: 2,
total: 6,
unfilteredTotal: 6,
items: firstPageGames,
links: { firstPageUrl: '#', lastPageUrl: '#', nextPageUrl: '#', previousPageUrl: '#' },
};

const secondPageData: App.Data.PaginatedData<App.Platform.Data.GameListEntry> = {
currentPage: 2,
lastPage: 3,
perPage: 2,
total: 6,
unfilteredTotal: 6,
items: secondPageGames,
links: { firstPageUrl: '#', lastPageUrl: '#', nextPageUrl: '#', previousPageUrl: '#' },
};

const thirdPageData: App.Data.PaginatedData<App.Platform.Data.GameListEntry> = {
currentPage: 3,
lastPage: 3,
perPage: 2,
total: 6,
unfilteredTotal: 6,
items: thirdPageGames,
links: { firstPageUrl: '#', lastPageUrl: '#', nextPageUrl: '#', previousPageUrl: '#' },
};

const getSpy = vi
.spyOn(axios, 'get')
.mockResolvedValueOnce({ data: firstPageData })
.mockResolvedValueOnce({ data: secondPageData })
.mockResolvedValueOnce({ data: thirdPageData });

render(
<QueryClientProvider client={queryClient}>
<GameListItems
sorting={[{ id: 'title', desc: false }]}
pagination={{ pageIndex: 0, pageSize: 100 }}
columnFilters={[]}
/>
</QueryClientProvider>,
{
pageProps: {
ziggy: createZiggyProps({ device: 'mobile' }),
},
},
);

// ... wait for the initial load ...
await waitFor(() => {
screen.getByText(/game 1/i);
});

// ACT
// ... simulate scrolling to the bottom the first time ...
mockAllIsIntersecting(true);

// ... wait for the prefetch to complete ...
await waitFor(() => {
expect(getSpy).toHaveBeenCalledTimes(2);
});

// ... simulate scrolling back up ...
mockAllIsIntersecting(false);

// ... simulate scrolling to the bottom again ...
mockAllIsIntersecting(true);

// ASSERT
/**
* The spy should still only have been called twice - once for the initial load
* and once for the prefetch. Scrolling to the bottom again shouldn't trigger
* another prefetch of the same page or the Nth+1 page.
*/
await waitFor(() => {
expect(getSpy).toHaveBeenCalledTimes(2);
});
},
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const GameListItems: FC<GameListItemsProps> = ({
});

const [visiblePageNumbers, setVisiblePageNumbers] = useState([1]);

const [prefetchedPageNumbers, setPrefetchedPageNumbers] = useState([1]);
const [isLoadingMore, setIsLoadingMore] = useState(false);

const isEmpty = dataInfiniteQuery.data?.pages?.[0].total === 0;
Expand All @@ -74,9 +74,17 @@ const GameListItems: FC<GameListItemsProps> = ({
const isLastPageResultsVisible =
visiblePageNumbers[visiblePageNumbers.length - 1] === lastPageNumber;

const handleLoadMore = () => {
const handleLoadMore = async () => {
if (dataInfiniteQuery.hasNextPage && !dataInfiniteQuery.isFetchingNextPage) {
dataInfiniteQuery.fetchNextPage();
const nextPageNumber = Math.max(...visiblePageNumbers) + 1;

// Don't prefetch the next page if it has already been prefetched.
if (prefetchedPageNumbers.includes(nextPageNumber)) {
return;
}

await dataInfiniteQuery.fetchNextPage();
setPrefetchedPageNumbers([...prefetchedPageNumbers, nextPageNumber]);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import { type Dispatch, type FC, lazy, type SetStateAction, Suspense } from 'rea
import { usePageProps } from '@/common/hooks/usePageProps';
import { useGameListPaginatedQuery } from '@/features/game-list/hooks/useGameListPaginatedQuery';

import { hubGamesDefaultFilters } from '../../utils/hubGamesDefaultFilters';
import { DataTablePagination } from '../DataTablePagination';
import { DataTableToolbar } from '../DataTableToolbar';
import { GameListDataTable } from '../GameListDataTable';
import { GameListItemsSuspenseFallback } from '../GameListItems/GameListItemsSuspenseFallback';
import { useHubGamesDefaultColumnState } from '../HubMainRoot/useHubGamesDefaultColumnState';
import { useColumnDefinitions } from './useColumnDefinitions';

const GameListItems = lazy(() => import('../GameListItems'));
Expand Down Expand Up @@ -44,6 +44,8 @@ export const HubGamesDataTable: FC<HubGamesDataTableProps> = ({
}) => {
const { can, hub, ziggy } = usePageProps<App.Platform.Data.HubPageProps>();

const { defaultColumnFilters } = useHubGamesDefaultColumnState();

const gameListQuery = useGameListPaginatedQuery({
columnFilters,
pagination,
Expand Down Expand Up @@ -84,7 +86,7 @@ export const HubGamesDataTable: FC<HubGamesDataTableProps> = ({
<DataTableToolbar
table={table}
unfilteredTotal={gameListQuery.data?.unfilteredTotal ?? null}
defaultColumnFilters={hubGamesDefaultFilters}
defaultColumnFilters={defaultColumnFilters}
randomGameApiRouteName="api.hub.game.random"
tableApiRouteName="api.hub.game.index"
tableApiRouteParams={{ gameSet: hub.id }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import { useGameListState } from '../../hooks/useGameListState';
import { usePreloadedTableDataQueryClient } from '../../hooks/usePreloadedTableDataQueryClient';
import { useTableSync } from '../../hooks/useTableSync';
import { isCurrentlyPersistingViewAtom } from '../../state/game-list.atoms';
import { hubGamesDefaultFilters } from '../../utils/hubGamesDefaultFilters';
import { DataTablePaginationScrollTarget } from '../DataTablePaginationScrollTarget';
import { HubGamesDataTable } from '../HubGamesDataTable';
import { HubBreadcrumbs } from './HubBreadcrumbs';
import { HubHeading } from './HubHeading';
import { RelatedHubs } from './RelatedHubs';
import { useHubGamesDefaultColumnState } from './useHubGamesDefaultColumnState';

export const HubMainRoot: FC = memo(() => {
const { auth, breadcrumbs, defaultDesktopPageSize, hub, paginatedGameListEntries } =
const { breadcrumbs, defaultDesktopPageSize, hub, paginatedGameListEntries } =
usePageProps<App.Platform.Data.HubPageProps>();

const { defaultColumnFilters, defaultColumnSort, defaultColumnVisibility } =
useHubGamesDefaultColumnState();

const {
columnFilters,
columnVisibility,
Expand All @@ -30,8 +33,9 @@ export const HubMainRoot: FC = memo(() => {
setSorting,
sorting,
} = useGameListState(paginatedGameListEntries, {
canShowProgressColumn: !!auth?.user,
defaultColumnFilters: hubGamesDefaultFilters,
defaultColumnSort,
defaultColumnFilters,
defaultColumnVisibility,
});

const { queryClientWithInitialData } = usePreloadedTableDataQueryClient({
Expand All @@ -46,9 +50,10 @@ export const HubMainRoot: FC = memo(() => {
useTableSync({
columnFilters,
columnVisibility,
defaultColumnFilters,
defaultColumnSort,
pagination,
sorting,
defaultFilters: hubGamesDefaultFilters,
defaultPageSize: defaultDesktopPageSize,
isUserPersistenceEnabled: isCurrentlyPersistingView,
});
Expand Down
Loading

0 comments on commit dac2da2

Please sign in to comment.