diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 4af90c3f4..980ebccb0 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -756,6 +756,7 @@ }, "tank_search": { + "clear_filters": "Clear", "search_bar_hint": "Search tanks...", "recent": "Recently viewed", "select_all": "Select all", diff --git a/packages/website/src/components/MissingShellIcon.tsx b/packages/website/src/components/MissingShellIcon.tsx new file mode 100644 index 000000000..686c4b492 --- /dev/null +++ b/packages/website/src/components/MissingShellIcon.tsx @@ -0,0 +1,22 @@ +import type { ComponentProps } from 'react'; + +export function MissingShellIcon(props: ComponentProps<'svg'>) { + return ( + + + + ); +} diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index aca24fec1..cbcd4f84f 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -1,15 +1,21 @@ import { asset, + ShellType, TANK_CLASSES, TankType, TIER_ROMAN_NUMERALS, } from '@blitzkit/core'; import { useStore } from '@nanostores/react'; import { ReloadIcon } from '@radix-ui/react-icons'; -import { Box, Flex, IconButton, Text } from '@radix-ui/themes'; +import { Box, Button, Flex, IconButton, Popover, Text } from '@radix-ui/themes'; import { times } from 'lodash-es'; import { awaitableGameDefinitions } from '../../../core/awaitables/gameDefinitions'; -import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters'; +import { useLocale } from '../../../hooks/useLocale'; +import { + $tankFilters, + initialTankFilters, + type TankFilters, +} from '../../../stores/tankFilters'; import { $tankopediaSort, initialTankopediaSort, @@ -19,6 +25,7 @@ import { ExperimentIcon } from '../../ExperimentIcon'; import { GunAutoloaderIcon } from '../../GunAutoloaderIcon'; import { GunAutoreloaderIcon } from '../../GunAutoreloaderIcon'; import { GunRegularIcon } from '../../GunRegularIcon'; +import { MissingShellIcon } from '../../MissingShellIcon'; import { ResearchedIcon } from '../../ResearchedIcon'; import { ScienceIcon } from '../../ScienceIcon'; import { ScienceOffIcon } from '../../ScienceOffIcon'; @@ -29,14 +36,97 @@ interface FilterControlProps { const gameDefinitions = await awaitableGameDefinitions; -export function FilterControl({ compact }: FilterControlProps) { +const shellTypeIcons: Record = { + [ShellType.AP]: 'ap', + [ShellType.APCR]: 'ap_cr', + [ShellType.HE]: 'he', + [ShellType.HEAT]: 'hc', +}; + +function ShellFilter({ index, premium }: { index: number; premium?: boolean }) { const tankFilters = useStore($tankFilters); return ( - + + + + {tankFilters.shells[index] === null && ( + + + + )} + {tankFilters.shells[index] !== null && ( + + )} + + + + + + {Object.values(ShellType).map((shellType) => { + if (typeof shellType === 'string') return null; + + const selected = tankFilters.shells[index] === shellType; + + return ( + { + const mutated = [ + tankFilters.shells[0], + tankFilters.shells[1], + tankFilters.shells[2], + ] as TankFilters['shells']; + + mutated[index] = selected ? null : shellType; + + $tankFilters.setKey('shells', mutated); + }} + highContrast={selected} + color={selected ? undefined : 'gray'} + > + + + ); + })} + + + + ); +} + +export function FilterControl({ compact }: FilterControlProps) { + const tankFilters = useStore($tankFilters); + const { strings } = useLocale(); + + return ( + @@ -101,6 +191,7 @@ export function FilterControl({ compact }: FilterControlProps) { direction={compact ? 'row' : { initial: 'row', md: 'column' }} overflow="hidden" style={{ borderRadius: 'var(--radius-5)' }} + gap={{ initial: '0', md: '1' }} > {gameDefinitions.nations @@ -176,270 +267,315 @@ export function FilterControl({ compact }: FilterControlProps) { - {TANK_CLASSES.map((tankClass) => { - const Icon = classIcons[tankClass]; - const selected = tankFilters.classes?.includes(tankClass); + + + {TANK_CLASSES.map((tankClass) => { + const Icon = classIcons[tankClass]; + const selected = tankFilters.classes?.includes(tankClass); - return ( + return ( + { + if (tankFilters.classes.includes(tankClass)) { + $tankFilters.setKey( + 'classes', + tankFilters.classes.filter((c) => c !== tankClass), + ); + } else { + $tankFilters.setKey('classes', [ + ...tankFilters.classes, + tankClass, + ]); + } + }} + > + + + ); + })} + + + { - if (tankFilters.classes.includes(tankClass)) { + if (tankFilters.gunType.includes('regular')) { $tankFilters.setKey( - 'classes', - tankFilters.classes.filter((c) => c !== tankClass), + 'gunType', + tankFilters.gunType.filter((t) => t !== 'regular'), ); } else { - $tankFilters.setKey('classes', [ - ...tankFilters.classes, - tankClass, + $tankFilters.setKey('gunType', [ + ...tankFilters.gunType, + 'regular', ]); } }} > - + - ); - })} - + { + if (tankFilters.gunType.includes('auto_loader')) { + $tankFilters.setKey( + 'gunType', + tankFilters.gunType.filter((t) => t !== 'auto_loader'), + ); + } else { + $tankFilters.setKey('gunType', [ + ...tankFilters.gunType, + 'auto_loader', + ]); + } + }} + > + + + { + if (tankFilters.gunType.includes('auto_reloader')) { + $tankFilters.setKey( + 'gunType', + tankFilters.gunType.filter((t) => t !== 'auto_reloader'), + ); + } else { + $tankFilters.setKey('gunType', [ + ...tankFilters.gunType, + 'auto_reloader', + ]); + } + }} + > + + + - - { - if (tankFilters.gunType.includes('regular')) { - $tankFilters.setKey( - 'gunType', - tankFilters.gunType.filter((t) => t !== 'regular'), - ); - } else { - $tankFilters.setKey('gunType', [ - ...tankFilters.gunType, - 'regular', - ]); - } - }} - > - - - { - if (tankFilters.gunType.includes('auto_loader')) { - $tankFilters.setKey( - 'gunType', - tankFilters.gunType.filter((t) => t !== 'auto_loader'), - ); - } else { - $tankFilters.setKey('gunType', [ - ...tankFilters.gunType, - 'auto_loader', - ]); - } - }} - > - - - { - if (tankFilters.gunType.includes('auto_reloader')) { - $tankFilters.setKey( - 'gunType', - tankFilters.gunType.filter((t) => t !== 'auto_reloader'), - ); - } else { - $tankFilters.setKey('gunType', [ - ...tankFilters.gunType, - 'auto_reloader', - ]); - } - }} - > - - - + + + + - - { - if (tankFilters.types.includes(TankType.RESEARCHABLE)) { - $tankFilters.setKey( - 'types', - tankFilters.types.filter((t) => t !== TankType.RESEARCHABLE), - ); - } else { - $tankFilters.setKey('types', [ - ...tankFilters.types, - TankType.RESEARCHABLE, - ]); - } - }} - > - - - { - if (tankFilters.types.includes(TankType.PREMIUM)) { - $tankFilters.setKey( - 'types', - tankFilters.types.filter((t) => t !== TankType.PREMIUM), - ); - } else { - $tankFilters.setKey('types', [ - ...tankFilters.types, - TankType.PREMIUM, - ]); - } - }} - > - + - - - - { - if (tankFilters.types.includes(TankType.COLLECTOR)) { - $tankFilters.setKey( - 'types', - tankFilters.types.filter((t) => t !== TankType.COLLECTOR), - ); - } else { - $tankFilters.setKey('types', [ - ...tankFilters.types, - TankType.COLLECTOR, - ]); - } - }} - > - { + if (tankFilters.types.includes(TankType.RESEARCHABLE)) { + $tankFilters.setKey( + 'types', + tankFilters.types.filter( + (t) => t !== TankType.RESEARCHABLE, + ), + ); + } else { + $tankFilters.setKey('types', [ + ...tankFilters.types, + TankType.RESEARCHABLE, + ]); + } + }} + > + + + { + if (tankFilters.types.includes(TankType.PREMIUM)) { + $tankFilters.setKey( + 'types', + tankFilters.types.filter((t) => t !== TankType.PREMIUM), + ); + } else { + $tankFilters.setKey('types', [ + ...tankFilters.types, + TankType.PREMIUM, + ]); + } + }} + > + + + + + { + if (tankFilters.types.includes(TankType.COLLECTOR)) { + $tankFilters.setKey( + 'types', + tankFilters.types.filter((t) => t !== TankType.COLLECTOR), + ); + } else { + $tankFilters.setKey('types', [ + ...tankFilters.types, + TankType.COLLECTOR, + ]); + } + }} + > + + + + + + + - - - + { + $tankFilters.setKey('testing', 'include'); + }} + > + + + { + $tankFilters.setKey('testing', 'exclude'); + }} + > + + + { + $tankFilters.setKey('testing', 'only'); + }} + > + + + + + + + + + + - - { - $tankFilters.setKey('testing', 'include'); - }} - > - - - { - $tankFilters.setKey('testing', 'exclude'); - }} - > - - - { - $tankFilters.setKey('testing', 'only'); - }} - > - - - - - + { diff --git a/packages/website/src/core/blitzkit/filterTank.ts b/packages/website/src/core/blitzkit/filterTank.ts index abba5fa6f..a8c66c67d 100644 --- a/packages/website/src/core/blitzkit/filterTank.ts +++ b/packages/website/src/core/blitzkit/filterTank.ts @@ -22,6 +22,27 @@ export function filterTank(filters: TankFilters, tank: TankDefinition) { (filters.gunType.includes('auto_reloader') && tank.turrets.some((turret) => turret.guns.some((gun) => gun.gun_type!.$case === 'auto_reloader'), - ))) + ))) && + (filters.shells[0] === null || + tank.turrets.some((turret) => + turret.guns.some( + (gun) => + gun.gun_type!.value.base.shells[0]?.type === filters.shells[0], + ), + )) && + (filters.shells[1] === null || + tank.turrets.some((turret) => + turret.guns.some( + (gun) => + gun.gun_type!.value.base.shells[1]?.type === filters.shells[1], + ), + )) && + (filters.shells[2] === null || + tank.turrets.some((turret) => + turret.guns.some( + (gun) => + gun.gun_type!.value.base.shells[2]?.type === filters.shells[2], + ), + )) ); } diff --git a/packages/website/src/stores/tankFilters.ts b/packages/website/src/stores/tankFilters.ts index bdb4b7dc2..a2ba14df9 100644 --- a/packages/website/src/stores/tankFilters.ts +++ b/packages/website/src/stores/tankFilters.ts @@ -1,4 +1,9 @@ -import type { GunDefinition, TankClass, TankType } from '@blitzkit/core'; +import type { + GunDefinition, + ShellType, + TankClass, + TankType, +} from '@blitzkit/core'; import { map } from 'nanostores'; export type CaseType = T extends { @@ -16,6 +21,7 @@ export interface TankFilters { search?: string; searching: boolean; gunType: CaseType[]; + shells: [ShellType | null, ShellType | null, ShellType | null]; } export const initialTankFilters: TankFilters = { @@ -27,6 +33,7 @@ export const initialTankFilters: TankFilters = { search: undefined, searching: false, gunType: [], + shells: [null, null, null], }; export const $tankFilters = map(initialTankFilters);