From c5e1a7d6ab57cb4c54608fa47d57b764593689e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Dec 2023 19:45:43 -0600 Subject: [PATCH] feat: page turner --- src/app/layout.tsx | 8 +- .../ratings/components/PageTurner/index.tsx | 2 + .../tankopedia/components/List/index.tsx | 135 ----------- .../tools/tankopedia/components/Options.tsx | 60 +++++ .../tankopedia/components/PageTurner.tsx | 0 .../List/index.css.tsx => page.css.tsx} | 8 +- src/app/tools/tankopedia/page.tsx | 226 +++++++++++++++++- src/stores/tankopedia.ts | 27 +++ 8 files changed, 322 insertions(+), 144 deletions(-) delete mode 100644 src/app/tools/tankopedia/components/List/index.tsx create mode 100644 src/app/tools/tankopedia/components/Options.tsx create mode 100644 src/app/tools/tankopedia/components/PageTurner.tsx rename src/app/tools/tankopedia/{components/List/index.css.tsx => page.css.tsx} (75%) create mode 100644 src/stores/tankopedia.ts diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 69c55312d..969502694 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -41,7 +41,13 @@ export default function RootLayout({ children }: RootLayoutProps) { : 'url(https://i.imgur.com/PhS06NJ.png)', }} > - + {!isEmbed && } {children} diff --git a/src/app/tools/ratings/components/PageTurner/index.tsx b/src/app/tools/ratings/components/PageTurner/index.tsx index ba009d392..ecda55b86 100644 --- a/src/app/tools/ratings/components/PageTurner/index.tsx +++ b/src/app/tools/ratings/components/PageTurner/index.tsx @@ -59,6 +59,8 @@ export function PageTurner({ type="number" ref={pageInput} style={{ width: 64, textAlign: 'center' }} + min={1} + max={pages} onBlur={(event) => { onPageChange( Math.max( diff --git a/src/app/tools/tankopedia/components/List/index.tsx b/src/app/tools/tankopedia/components/List/index.tsx deleted file mode 100644 index 4b72a37f0..000000000 --- a/src/app/tools/tankopedia/components/List/index.tsx +++ /dev/null @@ -1,135 +0,0 @@ -'use client'; - -import { slateDark } from '@radix-ui/colors'; -import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; -import { Card, Flex, Inset, Text, TextField } from '@radix-ui/themes'; -import { use, useRef } from 'react'; -import { Search } from 'react-fuzzysort'; -import { tankIcon } from '../../../../../core/blitzkrieg/tankIcon'; -import { - TANK_ICONS, - TANK_ICONS_COLLECTOR, - TANK_ICONS_PREMIUM, - tanks, -} from '../../../../../core/blitzkrieg/tankopedia'; -import { theme } from '../../../../../stitches.config'; -import * as styles from './index.css'; - -export function List() { - const awaitedTanks = use( - tanks.then((tanks) => tanks.sort((a, b) => b.tier - a.tier)), - ); - const input = useRef(null); - - return ( - <> - - - - - - - - {awaitedTanks.length > 0 && ( - - ({ - node: ( - - - - - -
- - - - - {tank.name_short ?? - tank.name ?? - `Unknown tank ${tank.id}`} - - - - - - ), - query: `${ - tank.name_short ?? tank.name ?? `Unknown tank ${tank.id}` - } ${tank.tree_type} ${ - tank.type === 'AT-SPG' ? 'tank destroyer' : tank.type - } ${tank.id}`, - }))} - /> - - )} - - ); -} diff --git a/src/app/tools/tankopedia/components/Options.tsx b/src/app/tools/tankopedia/components/Options.tsx new file mode 100644 index 000000000..43fce3f1c --- /dev/null +++ b/src/app/tools/tankopedia/components/Options.tsx @@ -0,0 +1,60 @@ +import { CaretDownIcon } from '@radix-ui/react-icons'; +import { Button, DropdownMenu, Flex } from '@radix-ui/themes'; +import { + TankopediaSortBy, + TankopediaSortDirection, + useTankopedia, +} from '../../../../stores/tankopedia'; + +export function Options() { + const tankopediaState = useTankopedia(); + + return ( + + + + + + + + useTankopedia.setState({ sortBy: value as TankopediaSortBy }) + } + > + Tier + + + + + + + + + + + useTankopedia.setState({ + sortDirection: value as TankopediaSortDirection, + }) + } + > + + Ascending + + + Descending + + + + + + ); +} diff --git a/src/app/tools/tankopedia/components/PageTurner.tsx b/src/app/tools/tankopedia/components/PageTurner.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/tools/tankopedia/components/List/index.css.tsx b/src/app/tools/tankopedia/page.css.tsx similarity index 75% rename from src/app/tools/tankopedia/components/List/index.css.tsx rename to src/app/tools/tankopedia/page.css.tsx index fdf0efa93..2e23bdc30 100644 --- a/src/app/tools/tankopedia/components/List/index.css.tsx +++ b/src/app/tools/tankopedia/page.css.tsx @@ -1,12 +1,12 @@ import { style } from '@vanilla-extract/css'; -import { theme } from '../../../../../stitches.config'; +import { theme } from '../../../stitches.config'; export const listing = style({}); export const listingShadow = style({ boxShadow: `inset 0 -128px 128px -128px ${theme.colors.appBackground1}`, - [`.${listing}:hover &`]: { + [`.${listing}:hover &, .${listing}:active &`]: { boxShadow: `inset 0 0 0 -128px ${theme.colors.appBackground1}`, }, }); @@ -15,7 +15,7 @@ export const listingLabel = style({ transition: `transform ${theme.durations.regular}`, transform: 'translateY(0)', - [`.${listing}:hover &`]: { + [`.${listing}:hover &, .${listing}:active &`]: { transform: 'translateY(200%)', }, }); @@ -24,7 +24,7 @@ export const listingImage = style({ transition: `transform ${theme.durations.regular}`, transform: 'translateX(25%) translateY(0)', - [`.${listing}:hover &`]: { + [`.${listing}:hover &, .${listing}:active &`]: { transform: 'translateX(25%) translateY(10%)', }, }); diff --git a/src/app/tools/tankopedia/page.tsx b/src/app/tools/tankopedia/page.tsx index ca38f7641..6bebd5f78 100644 --- a/src/app/tools/tankopedia/page.tsx +++ b/src/app/tools/tankopedia/page.tsx @@ -1,13 +1,231 @@ -import { Text } from '@radix-ui/themes'; -import { Suspense } from 'react'; +'use client'; + +import { slateDark } from '@radix-ui/colors'; +import { + CaretLeftIcon, + CaretRightIcon, + MagnifyingGlassIcon, +} from '@radix-ui/react-icons'; +import { Button, Card, Flex, Inset, Text, TextField } from '@radix-ui/themes'; +import { go } from 'fuzzysort'; +import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react'; import PageWrapper from '../../../components/PageWrapper'; -import { List } from './components/List'; +import { tankIcon } from '../../../core/blitzkrieg/tankIcon'; +import { + BlitzkriegTankopediaEntry, + TANK_ICONS, + TANK_ICONS_COLLECTOR, + TANK_ICONS_PREMIUM, + tanks, +} from '../../../core/blitzkrieg/tankopedia'; +import { theme } from '../../../stitches.config'; +import { useTankopedia } from '../../../stores/tankopedia'; +import { Options } from './components/Options'; +import * as styles from './page.css'; + +const TANKS_PER_PAGE = 30; export default function Page() { + const tankopediaState = useTankopedia(); + const awaitedTanks = use(tanks); + const input = useRef(null); + const sortedTanks = useMemo( + () => + awaitedTanks.sort((a, b) => { + let diff = 0; + if (tankopediaState.sortBy === 'tier') diff = a.tier - b.tier; + return tankopediaState.sortDirection === 'ascending' ? diff : -diff; + }), + [tankopediaState.sortBy, tankopediaState.sortDirection], + ); + const [searchedList, setSearchedList] = useState(sortedTanks); + const [page, setPage] = useState(0); + const pageInput = useRef(null); + + useEffect(() => { + if (pageInput.current) pageInput.current.value = `${page + 1}`; + }, [page]); + return ( Loading...}> - + + + + + + { + if (event.target.value.length === 0) { + setSearchedList(sortedTanks); + } else { + setSearchedList( + go(event.target.value, sortedTanks, { + keys: [ + 'name', + 'name_short', + 'id', + 'nation', + 'tree_type', + 'type', + ] satisfies (keyof BlitzkriegTankopediaEntry)[], + }).map(({ obj }) => obj), + ); + } + }} + /> + + + + + + + + Page + { + setPage( + Math.max( + 0, + Math.min( + Math.floor(searchedList.length / TANKS_PER_PAGE), + event.target.valueAsNumber - 1, + ), + ), + ); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + (event.target as HTMLInputElement).blur(); + } + }} + /> + + out of {Math.floor(searchedList.length / TANKS_PER_PAGE) + 1} + + + + + + + + {searchedList + .map((tank) => ( + + + + + +
+ + + + + {tank.name_short ?? + tank.name ?? + `Unknown tank ${tank.id}`} + + + + + + )) + .slice(page * TANKS_PER_PAGE, (page + 1) * TANKS_PER_PAGE)} + ); diff --git a/src/stores/tankopedia.ts b/src/stores/tankopedia.ts new file mode 100644 index 000000000..097b9441b --- /dev/null +++ b/src/stores/tankopedia.ts @@ -0,0 +1,27 @@ +import { produce } from 'immer'; +import { create } from 'zustand'; +import { devtools, persist } from 'zustand/middleware'; + +export type TankopediaSortBy = 'tier'; +export type TankopediaSortDirection = 'ascending' | 'descending'; + +type Tankopedia = { + sortBy: TankopediaSortBy; + sortDirection: TankopediaSortDirection; +}; + +export const useTankopedia = create()( + devtools( + persist( + (set) => ({ + sortBy: 'tier', + sortDirection: 'descending', + }), + { name: 'tankopedia' }, + ), + ), +); + +export default function mutateTankopedia(recipe: (draft: Tankopedia) => void) { + useTankopedia.setState(produce(recipe)); +}