Skip to content

Commit

Permalink
feat(GameListDataTable): add 'Remember my view' checkbox (#2936)
Browse files Browse the repository at this point in the history
  • Loading branch information
wescopeland authored Dec 22, 2024
1 parent 902d51c commit 6fdb920
Show file tree
Hide file tree
Showing 33 changed files with 805 additions and 83 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ rules:
- 'sonner'
- 'vaul'

# don't enforce explicit-any rule in test files
# don't enforce explicit-any rule in test files or autogenerated files
overrides:
- files:
- "generated.d.ts"
- "*.test.ts"
- "*.test.tsx"
extends:
Expand Down
5 changes: 5 additions & 0 deletions app/Community/Controllers/UserGameListController.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public function index(UserGameListRequest $request): InertiaResponse

$isMobile = (new GetUserDeviceKindAction())->execute() === 'mobile';

$persistenceCookieName = 'datatable_view_preference_playlist';
$request->setPersistenceCookieName($persistenceCookieName);

$paginatedData = (new BuildGameListAction())->execute(
GameListType::UserPlay,
user: $user,
Expand Down Expand Up @@ -61,6 +64,8 @@ public function index(UserGameListRequest $request): InertiaResponse
paginatedGameListEntries: $paginatedData,
filterableSystemOptions: $filterableSystemOptions,
can: $can,
persistenceCookieName: $persistenceCookieName,
persistedViewPreferences: $request->getCookiePreferences(),
);

return Inertia::render('game-list/play', $props);
Expand Down
4 changes: 4 additions & 0 deletions app/Community/Data/UserGameListPagePropsData.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Data\UserPermissionsData;
use App\Platform\Data\SystemData;
use Spatie\LaravelData\Data;
use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript('UserGameListPageProps<TItems = App.Platform.Data.GameListEntry>')]
Expand All @@ -20,6 +21,9 @@ public function __construct(
public PaginatedData $paginatedGameListEntries,
public array $filterableSystemOptions,
public UserPermissionsData $can,
public string $persistenceCookieName,
#[LiteralTypeScriptType('Record<string, any> | null')]
public ?array $persistedViewPreferences = null,
public int $defaultDesktopPageSize = 25,
) {
}
Expand Down
5 changes: 5 additions & 0 deletions app/Http/Middleware/EncryptCookies.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class EncryptCookies extends Middleware
'theme', // color scheme
'logo',
'active_players_search',
'datatable_view_preference_all_games',
'datatable_view_preference_generic_games',
'datatable_view_preference_hub_games',
'datatable_view_preference_playlist',
'datatable_view_preference_system_games',
'prefers_hidden_user_completed_sets',
'prefers_hidden_user_profile_stats',
'prefers_seeing_saved_hidden_rows_when_reordering',
Expand Down
7 changes: 6 additions & 1 deletion app/Platform/Controllers/GameController.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public function index(GameListRequest $request): InertiaResponse

$this->authorize('viewAny', [Game::class, $user]);

$persistenceCookieName = 'datatable_view_preference_all_games';
$request->setPersistenceCookieName($persistenceCookieName);

$isMobile = (new GetUserDeviceKindAction())->execute() === 'mobile';

$paginatedData = (new BuildGameListAction())->execute(
Expand Down Expand Up @@ -67,9 +70,11 @@ public function index(GameListRequest $request): InertiaResponse
paginatedGameListEntries: $paginatedData,
filterableSystemOptions: $filterableSystemOptions,
can: $can,
persistenceCookieName: $persistenceCookieName,
persistedViewPreferences: $request->getCookiePreferences(),
);

return Inertia::render('game-list/index', $props);
return Inertia::render('games', $props);
}

public function popular(): void
Expand Down
5 changes: 5 additions & 0 deletions app/Platform/Controllers/HubController.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public function show(GameListRequest $request, ?GameSet $gameSet): InertiaRespon
/** @var ?User $user */
$user = $request->user();

$persistenceCookieName = 'datatable_view_preference_hub_games';
$request->setPersistenceCookieName($persistenceCookieName);

$isMobile = (new GetUserDeviceKindAction())->execute() === 'mobile';

$paginatedData = (new BuildGameListAction())->execute(
Expand Down Expand Up @@ -99,6 +102,8 @@ public function show(GameListRequest $request, ?GameSet $gameSet): InertiaRespon
paginatedGameListEntries: $paginatedData,
filterableSystemOptions: $filterableSystemOptions,
can: $can,
persistenceCookieName: $persistenceCookieName,
persistedViewPreferences: $request->getCookiePreferences(),
);

return Inertia::render('hub/[gameSet]', $props);
Expand Down
5 changes: 5 additions & 0 deletions app/Platform/Controllers/SystemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public function games(GameListRequest $request, System $system): InertiaResponse
/** @var ?User $user */
$user = $request->user();

$persistenceCookieName = 'datatable_view_preference_system_games';
$request->setPersistenceCookieName($persistenceCookieName);

$isMobile = (new GetUserDeviceKindAction())->execute() === 'mobile';

$paginatedData = (new BuildGameListAction())->execute(
Expand All @@ -75,6 +78,8 @@ public function games(GameListRequest $request, System $system): InertiaResponse
system: SystemData::from($system)->include('iconUrl'),
paginatedGameListEntries: $paginatedData,
can: $can,
persistenceCookieName: $persistenceCookieName,
persistedViewPreferences: $request->getCookiePreferences(),
);

return Inertia::render('system/games', $props);
Expand Down
4 changes: 4 additions & 0 deletions app/Platform/Data/GameListPagePropsData.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Data\PaginatedData;
use App\Data\UserPermissionsData;
use Spatie\LaravelData\Data;
use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript('GameListPageProps<TItems = App.Platform.Data.GameListEntry>')]
Expand All @@ -19,6 +20,9 @@ public function __construct(
public PaginatedData $paginatedGameListEntries,
public array $filterableSystemOptions,
public UserPermissionsData $can,
public string $persistenceCookieName,
#[LiteralTypeScriptType('Record<string, any> | null')]
public ?array $persistedViewPreferences = null,
public int $defaultDesktopPageSize = 25,
) {
}
Expand Down
4 changes: 4 additions & 0 deletions app/Platform/Data/HubPagePropsData.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Data\PaginatedData;
use App\Data\UserPermissionsData;
use Spatie\LaravelData\Data;
use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript('HubPageProps<TItems = App.Platform.Data.GameListEntry>')]
Expand All @@ -24,6 +25,9 @@ public function __construct(
public UserPermissionsData $can,
public array $breadcrumbs,
public array $relatedHubs,
public string $persistenceCookieName,
#[LiteralTypeScriptType('Record<string, any> | null')]
public ?array $persistedViewPreferences = null,
public int $defaultDesktopPageSize = 25,
) {
}
Expand Down
4 changes: 4 additions & 0 deletions app/Platform/Data/SystemGameListPagePropsData.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Data\PaginatedData;
use App\Data\UserPermissionsData;
use Spatie\LaravelData\Data;
use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript('SystemGameListPageProps<TItems = App.Platform.Data.GameListEntry>')]
Expand All @@ -16,6 +17,9 @@ public function __construct(
public SystemData $system,
public PaginatedData $paginatedGameListEntries,
public UserPermissionsData $can,
public string $persistenceCookieName,
#[LiteralTypeScriptType('Record<string, any> | null')]
public ?array $persistedViewPreferences = null,
public int $defaultDesktopPageSize = 100,
) {
}
Expand Down
68 changes: 65 additions & 3 deletions app/Platform/Requests/GameListRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ class GameListRequest extends FormRequest
private const DEFAULT_PAGE_SIZE = 25;
private const VALID_PAGE_SIZES = [10, 25, 50, 100, 200];

private string $persistenceCookieName = 'datatable_view_preference_generic_games';
private ?array $cookiePreferences = null;

public function setPersistenceCookieName(string $name): self
{
$this->persistenceCookieName = $name;

return $this;
}

public function rules(): array
{
// Get all valid sort values with and without the "-" prefix.
Expand All @@ -39,24 +49,62 @@ public function rules(): array
];
}

public function getCookiePreferences(): ?array
{
if (!isset($this->cookiePreferences)) {
$cookie = $this->cookie($this->persistenceCookieName);
$this->cookiePreferences = $cookie ? json_decode($cookie, true) : null;
}

return $this->cookiePreferences;
}

public function getPage(): int
{
return (int) $this->input('page.number', 1);
}

public function getPageSize(): int
{
return (int) $this->input('page.size', self::DEFAULT_PAGE_SIZE);
// URL params take precedence over cookie preferences.
if ($this->has('page.size')) {
return (int) $this->input('page.size', self::DEFAULT_PAGE_SIZE);
}

// If no URL param, check the cookie next.
$preferences = $this->getCookiePreferences();
if ($preferences && isset($preferences['pagination']['pageSize'])) {
$cookieSize = (int) $preferences['pagination']['pageSize'];

return in_array($cookieSize, self::VALID_PAGE_SIZES) ? $cookieSize : self::DEFAULT_PAGE_SIZE;
}

return self::DEFAULT_PAGE_SIZE;
}

/**
* @return array{field: string, direction: 'asc'|'desc'}
*/
public function getSort(): array
{
$sortParam = $this->input('sort', GameListSortField::Title->value);
$sortDirection = 'asc';
// URL params take precedence over cookie preferences.
$sortParam = $this->input('sort');

// If no URL param, check the cookie next.
if ($sortParam === null) {
$preferences = $this->getCookiePreferences();
if ($preferences && !empty($preferences['sorting'])) {
$sorting = $preferences['sorting'][0] ?? null;
if ($sorting) {
$sortParam = $sorting['desc'] ? "-{$sorting['id']}" : $sorting['id'];
}
}
}

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

$sortDirection = 'asc';
if (str_starts_with($sortParam, '-')) {
$sortDirection = 'desc';
$sortParam = ltrim($sortParam, '-');
Expand All @@ -78,10 +126,24 @@ public function getSort(): array
public function getFilters(string $defaultAchievementsPublishedFilter = 'has', ?int $targetSystemId = null): array
{
$filters = [];

// URL params take precedence over cookie preferences.
foreach ($this->query('filter', []) as $key => $value) {
$filters[$key] = explode(',', $value);
}

// If no URL params, check the cookie next.
if (empty($filters)) {
$preferences = $this->getCookiePreferences();
if ($preferences && !empty($preferences['columnFilters'])) {
foreach ($preferences['columnFilters'] as $filter) {
$value = $filter['value'];
$filters[$filter['id']] = is_array($value) ? $value : [$value];
}
}
}

// Apply defaults after checking both the URL and the persistence cookie.
if (!isset($filters['achievementsPublished'])) {
$filters['achievementsPublished'] = [$defaultAchievementsPublishedFilter];
}
Expand Down
2 changes: 1 addition & 1 deletion lang/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -502,14 +502,14 @@
"Go to previous news page": "Go to previous news page",
"Go to next news page": "Go to next news page",
"Swipe to view more": "Swipe to view more",
"All {{systemName}} Games": "All {{systemName}} Games",
"rowGroupAriaLabel_one": "{{systemName}} group with {{val, number}} game",
"rowGroupAriaLabel_other": "{{systemName}} group with {{val, number}} games",
"Games are now grouped by system": "Games are now grouped by system",
"Games are no longer grouped": "Games are no longer grouped",
"We provide <1>the emulators</1>, you just need <2>the games</2>. From Atari 2600 to PlayStation 2, and everything in between.": "We provide <1>the emulators</1>, you just need <2>the games</2>. From Atari 2600 to PlayStation 2, and everything in between.",
"We're excited to have you here! We know you might have some questions about hardcore mode, RetroPoints (white points), subsets, or which emulators to use. Don't worry, we've got you covered! Check out our <1>comprehensive FAQ</1> to get started. Happy gaming!": "We're excited to have you here! We know you might have some questions about hardcore mode, RetroPoints (white points), subsets, or which emulators to use. Don't worry, we've got you covered! Check out our <1>comprehensive FAQ</1> to get started. Happy gaming!",
"Getting Started": "Getting Started",
"Remember my view": "Remember my view",
"Moderation Comments - {{user}}": "Moderation Comments - {{user}}",
"Moderation Comments": "Moderation Comments",
"Columns": "Columns"
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"dayjs": "^1.11.11",
"i18next": "^23.16.4",
"i18next-resources-to-backend": "^1.2.1",
"jotai": "^2.10.3",
"js-cookie": "^3.0.5",
"linkify-html": "^4.1.3",
"motion": "^11.11.17",
Expand Down
20 changes: 20 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 15 additions & 12 deletions resources/js/common/components/AppProviders/AppProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { Provider as JotaiProvider } from 'jotai';
import { domAnimation, LazyMotion } from 'motion/react';
import { type FC, type ReactNode } from 'react';
import { I18nextProvider, type I18nextProviderProps } from 'react-i18next';
Expand All @@ -19,20 +20,22 @@ export const AppProviders: FC<AppProvidersProps> = ({ children, i18n }) => {
return (
<QueryClientProvider client={appQueryClient}>
<I18nextProvider i18n={i18n}>
<LazyMotion features={domAnimation}>
<BaseTooltipProvider delayDuration={300}>
{children}
<JotaiProvider>
<LazyMotion features={domAnimation}>
<BaseTooltipProvider delayDuration={300}>
{children}

<BaseToaster richColors={true} duration={4000} />
<BaseToaster richColors={true} duration={4000} />

{/* Everything below this line is excluded from prod builds. */}
{import.meta.env.VITE_REACT_QUERY_DEVTOOLS_ENABLED === 'true' ? (
<div className="hidden text-lg sm:block" data-testid="query-devtools">
<ReactQueryDevtools />
</div>
) : null}
</BaseTooltipProvider>
</LazyMotion>
{/* Everything below this line is excluded from prod builds. */}
{import.meta.env.VITE_REACT_QUERY_DEVTOOLS_ENABLED === 'true' ? (
<div className="hidden text-lg sm:block" data-testid="query-devtools">
<ReactQueryDevtools />
</div>
) : null}
</BaseTooltipProvider>
</LazyMotion>
</JotaiProvider>
</I18nextProvider>
</QueryClientProvider>
);
Expand Down
Loading

0 comments on commit 6fdb920

Please sign in to comment.