Files
blitzkit/src/app/tools/tankopedia/page.tsx
T

336 lines
11 KiB
TypeScript
Raw Normal View History

2023-12-03 19:45:43 -06:00
'use client';
import { slateDark } from '@radix-ui/colors';
2023-12-06 16:43:36 -06:00
import {
CaretDownIcon,
CaretUpIcon,
MagnifyingGlassIcon,
} from '@radix-ui/react-icons';
2023-12-06 12:40:43 -06:00
import {
Button,
Card,
DropdownMenu,
Flex,
Inset,
Text,
TextField,
} from '@radix-ui/themes';
2023-12-03 19:45:43 -06:00
import { go } from 'fuzzysort';
import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react';
2023-11-30 12:21:20 -06:00
import PageWrapper from '../../../components/PageWrapper';
2023-12-04 10:13:14 -06:00
import { asset } from '../../../core/blitzkrieg/asset';
2023-12-03 19:45:43 -06:00
import {
2023-12-12 13:40:58 -06:00
BlitzkriegTankDefinition,
2023-12-03 19:45:43 -06:00
TANK_ICONS,
TANK_ICONS_COLLECTOR,
TANK_ICONS_PREMIUM,
2023-12-04 18:40:24 -06:00
TIER_ROMAN_NUMERALS,
2023-12-05 17:36:18 -06:00
tanksDefinitionsArray,
} from '../../../core/blitzkrieg/definitions/tanks';
import { tankIcon } from '../../../core/blitzkrieg/tankIcon';
2023-12-03 19:45:43 -06:00
import { theme } from '../../../stitches.config';
2023-12-06 12:40:43 -06:00
import mutateTankopedia, {
TankopediaSortBy,
TankopediaSortDirection,
useTankopedia,
} from '../../../stores/tankopedia';
2023-12-03 19:45:43 -06:00
import { Options } from './components/Options';
2023-12-04 12:39:14 -06:00
import { PageTurner } from './components/PageTurner';
2023-12-03 19:45:43 -06:00
import * as styles from './page.css';
2023-12-06 12:48:19 -06:00
const TANKS_PER_PAGE = 3 * 2 ** 3;
2023-11-30 12:21:20 -06:00
2023-11-30 18:00:48 -06:00
export default function Page() {
2023-12-03 19:45:43 -06:00
const tankopediaState = useTankopedia();
2023-12-05 17:36:18 -06:00
const awaitedTanks = use(tanksDefinitionsArray);
2023-12-03 19:45:43 -06:00
const input = useRef<HTMLInputElement>(null);
2023-12-04 11:47:33 -06:00
const searchableTanks = useMemo(
2023-12-03 19:45:43 -06:00
() =>
2023-12-04 11:47:33 -06:00
awaitedTanks
2023-12-04 12:35:27 -06:00
.filter(
(tank) =>
(tankopediaState.filters.tiers.length === 0
? true
: tankopediaState.filters.tiers.includes(tank.tier)) &&
(tankopediaState.filters.types.length === 0
? true
: tankopediaState.filters.types.includes(tank.type)) &&
(tankopediaState.filters.treeTypes.length === 0
? true
2023-12-04 19:10:26 -06:00
: tankopediaState.filters.treeTypes.includes(tank.tree_type)) &&
(tankopediaState.filters.nations.length === 0
? true
2023-12-06 10:34:43 -06:00
: tankopediaState.filters.nations.includes(tank.nation)) &&
(tankopediaState.filters.test === 'include'
? true
: tankopediaState.filters.test === 'exclude'
? !tank.testing
: tank.testing),
2023-12-04 12:35:27 -06:00
)
2023-12-04 18:40:57 -06:00
.sort((a, b) => {
2023-12-04 11:47:33 -06:00
let diff = 0;
2023-12-04 10:25:54 -06:00
2023-12-04 11:47:33 -06:00
if (tankopediaState.sort.by === 'tier') {
diff = a.tier - b.tier;
}
if (tankopediaState.sort.by === 'name') {
diff = (
a.name_short ??
a.name ??
`Unknown tank ${a.id}`
).localeCompare(b.name_short ?? b.name ?? `Unknown tank ${b.id}`);
}
2023-12-04 10:25:54 -06:00
2023-12-04 11:47:33 -06:00
return tankopediaState.sort.direction === 'ascending' ? diff : -diff;
}),
[tankopediaState.filters, tankopediaState.sort],
2023-12-03 19:45:43 -06:00
);
2023-12-04 11:47:33 -06:00
const [searchedList, setSearchedList] = useState(searchableTanks);
2023-12-03 19:45:43 -06:00
const [page, setPage] = useState(0);
2023-12-06 16:43:36 -06:00
const [showFilters, setShowFilters] = useState(false);
2023-12-03 19:45:43 -06:00
2023-12-04 10:25:54 -06:00
useEffect(() => {
2023-12-04 11:47:33 -06:00
setSearchedList(searchableTanks);
2023-12-04 10:25:54 -06:00
if (input.current) input.current.value = '';
2023-12-04 11:47:33 -06:00
}, [searchableTanks]);
2023-12-04 10:25:54 -06:00
2023-12-03 19:51:55 -06:00
useEffect(() => {
setPage(
Math.min(
Math.max(0, page),
Math.ceil(searchedList.length / TANKS_PER_PAGE) - 1,
),
);
}, [searchedList]);
2023-12-03 19:45:43 -06:00
2023-11-30 12:21:20 -06:00
return (
2023-12-04 10:25:54 -06:00
<PageWrapper size="wide" color="purple">
2023-12-01 12:23:17 -06:00
<Suspense fallback={<Text>Loading...</Text>}>
2023-12-06 12:48:19 -06:00
<Flex direction="column" gap="4">
2023-12-06 12:40:43 -06:00
<Flex gap="2">
<TextField.Root style={{ flex: 1 }}>
<TextField.Slot>
<MagnifyingGlassIcon height="16" width="16" />
</TextField.Slot>
<TextField.Input
ref={input}
placeholder="Search tanks..."
onChange={(event) => {
if (event.target.value.length === 0) {
setSearchedList(searchableTanks);
} else {
setSearchedList(
go(event.target.value, searchableTanks, {
keys: [
'name',
'name_short',
'id',
2023-12-12 13:40:58 -06:00
] satisfies (keyof BlitzkriegTankDefinition)[],
2023-12-06 12:40:43 -06:00
}).map(({ obj }) => obj),
);
}
}}
/>
</TextField.Root>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="soft">
Sort
<CaretDownIcon />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Label>By</DropdownMenu.Label>
<DropdownMenu.RadioGroup
value={tankopediaState.sort.by}
onValueChange={(value) =>
mutateTankopedia((draft) => {
draft.sort.by = value as TankopediaSortBy;
})
}
>
<DropdownMenu.RadioItem value="tier">
Tier
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="name">
Name
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
<DropdownMenu.Separator />
<DropdownMenu.Label>Direction</DropdownMenu.Label>
<DropdownMenu.RadioGroup
value={tankopediaState.sort.direction}
onValueChange={(value) =>
mutateTankopedia((draft) => {
draft.sort.direction = value as TankopediaSortDirection;
})
}
>
<DropdownMenu.RadioItem value="ascending">
Ascending
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="descending">
Descending
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
2023-12-06 16:43:36 -06:00
<Button
variant="soft"
onClick={() => setShowFilters((state) => !state)}
>
Filters {showFilters ? <CaretUpIcon /> : <CaretDownIcon />}
</Button>
2023-12-06 12:40:43 -06:00
</Flex>
2023-12-03 19:45:43 -06:00
2023-12-06 16:43:36 -06:00
{showFilters && (
<Card>
<Suspense fallback={<Text>Loading...</Text>}>
<Options />
</Suspense>
</Card>
)}
2023-12-04 19:10:26 -06:00
</Flex>
2023-12-04 12:39:14 -06:00
2023-12-06 12:48:19 -06:00
<PageTurner
page={page}
setPage={setPage}
tanksPerPage={TANKS_PER_PAGE}
searchedList={searchedList}
/>
2023-12-03 19:45:43 -06:00
<Flex wrap="wrap" gap="3" justify="center">
{searchedList
.map((tank) => (
<a
2023-12-04 10:25:54 -06:00
key={tank.id}
2023-12-03 19:45:43 -06:00
href={`/tools/tankopedia/${tank.id}`}
style={{
flex: 1,
2023-12-04 18:40:24 -06:00
textDecoration: 'none',
color: 'unset',
2023-12-03 19:45:43 -06:00
}}
>
2023-12-03 22:09:08 -06:00
<Card className={styles.listing}>
2023-12-03 19:45:43 -06:00
<Inset
key={tank.id}
style={{
minWidth: 256,
2023-12-03 21:44:58 -06:00
minHeight: 128,
2023-12-03 19:45:43 -06:00
position: 'relative',
display: 'flex',
}}
>
2023-12-06 10:17:42 -06:00
<img
className={styles.flag}
src={asset(`flags/scratched/${tank.nation}.webp`)}
style={{
width: '100%',
height: '100%',
objectFit: 'contain',
position: 'absolute',
}}
/>
2023-12-03 22:09:08 -06:00
2023-12-03 21:44:58 -06:00
<div
2023-12-03 19:45:43 -06:00
style={{
2023-12-03 21:44:58 -06:00
width: 256,
height: 128,
2023-12-03 19:45:43 -06:00
}}
2023-12-03 21:44:58 -06:00
className={styles.listingImage}
>
<img
src={tankIcon(tank.id)}
style={{
objectFit: 'contain',
objectPosition: 'left center',
}}
/>
</div>
2023-12-03 19:45:43 -06:00
<div
className={styles.listingShadow}
style={{
width: '100%',
height: '100%',
position: 'absolute',
transition: `box-shadow ${theme.durations.regular}`,
}}
/>
<Flex
align="center"
2023-12-04 18:40:24 -06:00
justify="between"
2023-12-03 19:45:43 -06:00
className={styles.listingLabel}
style={{
position: 'absolute',
bottom: 8,
2023-12-04 18:40:24 -06:00
padding: '0 12px',
width: '100%',
boxSizing: 'border-box',
2023-12-03 19:45:43 -06:00
}}
>
2023-12-04 18:40:24 -06:00
<Flex align="center" justify="center" gap="1">
<img
src={
(tank.tree_type === 'collector'
? TANK_ICONS_COLLECTOR
: tank.tree_type === 'premium'
? TANK_ICONS_PREMIUM
: TANK_ICONS)[tank.type]
}
style={{ width: '1em', height: '1em' }}
/>
<Text
size="4"
color={
tank.tree_type === 'collector'
? 'blue'
: tank.tree_type === 'premium'
? 'amber'
: undefined
}
weight="medium"
style={{
color:
2023-12-04 18:48:52 -06:00
tank.tree_type === 'researchable'
2023-12-04 18:40:24 -06:00
? slateDark.slate12
: undefined,
2023-12-06 12:40:43 -06:00
whiteSpace: 'nowrap',
maxWidth: 160,
display: 'block',
textOverflow: 'ellipsis',
overflow: 'hidden',
2023-12-04 18:40:24 -06:00
}}
>
{tank.name_short ??
tank.name ??
`Unknown tank ${tank.id}`}
</Text>
</Flex>
<Text size="4" color="gray" highContrast={false}>
{TIER_ROMAN_NUMERALS[tank.tier]}
2023-12-03 19:45:43 -06:00
</Text>
</Flex>
</Inset>
</Card>
</a>
))
.slice(page * TANKS_PER_PAGE, (page + 1) * TANKS_PER_PAGE)}
</Flex>
2023-12-04 12:39:14 -06:00
<PageTurner
page={page}
setPage={setPage}
tanksPerPage={TANKS_PER_PAGE}
searchedList={searchedList}
/>
2023-12-01 12:23:17 -06:00
</Suspense>
2023-11-30 12:21:20 -06:00
</PageWrapper>
);
}