mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: shell filters
This commit is contained in:
1 parent
92399de5e2
commit
df0dc4f118
5 files changed
+437
-250
No files matched your search
@@ -756,6 +756,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"tank_search": {
|
"tank_search": {
|
||||||
|
"clear_filters": "Clear",
|
||||||
"search_bar_hint": "Search tanks...",
|
"search_bar_hint": "Search tanks...",
|
||||||
"recent": "Recently viewed",
|
"recent": "Recently viewed",
|
||||||
"select_all": "Select all",
|
"select_all": "Select all",
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
|
||||||
|
export function MissingShellIcon(props: ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M40.8038 14L50 23.1962M13.5 38.5L25.5 50.5M60 6.77686L57.2231 4C32.2774 18.0987 20.4352 28.1445 4 50.7438C4.15427 51.3609 4.7405 53.3355 7.70248 56.2975C10.6645 59.2595 12.6391 59.8457 13.2562 60C35.8555 43.5648 45.9013 31.7226 60 6.77686Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="4"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,15 +1,21 @@
|
|||||||
import {
|
import {
|
||||||
asset,
|
asset,
|
||||||
|
ShellType,
|
||||||
TANK_CLASSES,
|
TANK_CLASSES,
|
||||||
TankType,
|
TankType,
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { ReloadIcon } from '@radix-ui/react-icons';
|
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 { times } from 'lodash-es';
|
||||||
import { awaitableGameDefinitions } from '../../../core/awaitables/gameDefinitions';
|
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 {
|
import {
|
||||||
$tankopediaSort,
|
$tankopediaSort,
|
||||||
initialTankopediaSort,
|
initialTankopediaSort,
|
||||||
@@ -19,6 +25,7 @@ import { ExperimentIcon } from '../../ExperimentIcon';
|
|||||||
import { GunAutoloaderIcon } from '../../GunAutoloaderIcon';
|
import { GunAutoloaderIcon } from '../../GunAutoloaderIcon';
|
||||||
import { GunAutoreloaderIcon } from '../../GunAutoreloaderIcon';
|
import { GunAutoreloaderIcon } from '../../GunAutoreloaderIcon';
|
||||||
import { GunRegularIcon } from '../../GunRegularIcon';
|
import { GunRegularIcon } from '../../GunRegularIcon';
|
||||||
|
import { MissingShellIcon } from '../../MissingShellIcon';
|
||||||
import { ResearchedIcon } from '../../ResearchedIcon';
|
import { ResearchedIcon } from '../../ResearchedIcon';
|
||||||
import { ScienceIcon } from '../../ScienceIcon';
|
import { ScienceIcon } from '../../ScienceIcon';
|
||||||
import { ScienceOffIcon } from '../../ScienceOffIcon';
|
import { ScienceOffIcon } from '../../ScienceOffIcon';
|
||||||
@@ -29,14 +36,97 @@ interface FilterControlProps {
|
|||||||
|
|
||||||
const gameDefinitions = await awaitableGameDefinitions;
|
const gameDefinitions = await awaitableGameDefinitions;
|
||||||
|
|
||||||
export function FilterControl({ compact }: FilterControlProps) {
|
const shellTypeIcons: Record<ShellType, string> = {
|
||||||
|
[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);
|
const tankFilters = useStore($tankFilters);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex height="fit-content" gap="2" align="start" justify="center">
|
<Popover.Root>
|
||||||
|
<Popover.Trigger>
|
||||||
|
<IconButton variant="soft" radius="none" color="gray" highContrast>
|
||||||
|
{tankFilters.shells[index] === null && (
|
||||||
|
<Text color="gray" style={{ display: 'contents' }}>
|
||||||
|
<MissingShellIcon width="1em" height="1em" />
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{tankFilters.shells[index] !== null && (
|
||||||
|
<img
|
||||||
|
style={{ width: '1em', height: '1em' }}
|
||||||
|
src={asset(
|
||||||
|
`icons/shells/${shellTypeIcons[tankFilters.shells[index]]}${premium ? '_premium' : ''}.webp`,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Popover.Trigger>
|
||||||
|
|
||||||
|
<Popover.Content>
|
||||||
|
<Flex
|
||||||
|
style={{
|
||||||
|
borderRadius: 'var(--radius-full)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.values(ShellType).map((shellType) => {
|
||||||
|
if (typeof shellType === 'string') return null;
|
||||||
|
|
||||||
|
const selected = tankFilters.shells[index] === shellType;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
value={`${shellType}`}
|
||||||
|
radius="none"
|
||||||
|
variant={selected ? 'solid' : 'soft'}
|
||||||
|
onClick={() => {
|
||||||
|
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'}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={asset(
|
||||||
|
`icons/shells/${shellTypeIcons[shellType]}${premium ? '_premium' : ''}.webp`,
|
||||||
|
)}
|
||||||
|
style={{ width: '1em', height: '1em' }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FilterControl({ compact }: FilterControlProps) {
|
||||||
|
const tankFilters = useStore($tankFilters);
|
||||||
|
const { strings } = useLocale();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
height="fit-content"
|
||||||
|
gap="2"
|
||||||
|
align={{ initial: 'start', md: 'center' }}
|
||||||
|
justify="center"
|
||||||
|
>
|
||||||
<Flex
|
<Flex
|
||||||
direction={compact ? 'row' : { initial: 'row', md: 'column' }}
|
direction={compact ? 'row' : { initial: 'row', md: 'column' }}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
|
gap={{ initial: '0', md: '1' }}
|
||||||
style={{ borderRadius: 'var(--radius-5)' }}
|
style={{ borderRadius: 'var(--radius-5)' }}
|
||||||
>
|
>
|
||||||
<Flex direction={compact ? 'column' : { md: 'row', initial: 'column' }}>
|
<Flex direction={compact ? 'column' : { md: 'row', initial: 'column' }}>
|
||||||
@@ -101,6 +191,7 @@ export function FilterControl({ compact }: FilterControlProps) {
|
|||||||
direction={compact ? 'row' : { initial: 'row', md: 'column' }}
|
direction={compact ? 'row' : { initial: 'row', md: 'column' }}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
style={{ borderRadius: 'var(--radius-5)' }}
|
style={{ borderRadius: 'var(--radius-5)' }}
|
||||||
|
gap={{ initial: '0', md: '1' }}
|
||||||
>
|
>
|
||||||
<Flex direction={compact ? 'column' : { md: 'row', initial: 'column' }}>
|
<Flex direction={compact ? 'column' : { md: 'row', initial: 'column' }}>
|
||||||
{gameDefinitions.nations
|
{gameDefinitions.nations
|
||||||
@@ -176,270 +267,315 @@ export function FilterControl({ compact }: FilterControlProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
overflow="hidden"
|
direction={{ initial: 'row', md: 'column' }}
|
||||||
style={{ borderRadius: 'var(--radius-full)' }}
|
align="start"
|
||||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
gap={{ initial: '2', md: '1' }}
|
||||||
>
|
>
|
||||||
{TANK_CLASSES.map((tankClass) => {
|
<Flex gap="2" align="start">
|
||||||
const Icon = classIcons[tankClass];
|
<Flex
|
||||||
const selected = tankFilters.classes?.includes(tankClass);
|
overflow="hidden"
|
||||||
|
style={{ borderRadius: 'var(--radius-full)' }}
|
||||||
|
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||||
|
>
|
||||||
|
{TANK_CLASSES.map((tankClass) => {
|
||||||
|
const Icon = classIcons[tankClass];
|
||||||
|
const selected = tankFilters.classes?.includes(tankClass);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<IconButton
|
||||||
|
key={tankClass}
|
||||||
|
variant={selected ? 'solid' : 'soft'}
|
||||||
|
radius="none"
|
||||||
|
color={selected ? undefined : 'gray'}
|
||||||
|
highContrast
|
||||||
|
onClick={() => {
|
||||||
|
if (tankFilters.classes.includes(tankClass)) {
|
||||||
|
$tankFilters.setKey(
|
||||||
|
'classes',
|
||||||
|
tankFilters.classes.filter((c) => c !== tankClass),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$tankFilters.setKey('classes', [
|
||||||
|
...tankFilters.classes,
|
||||||
|
tankClass,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon style={{ width: '1em', height: '1em' }} />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
overflow="hidden"
|
||||||
|
style={{ borderRadius: 'var(--radius-full)' }}
|
||||||
|
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
key={tankClass}
|
variant={
|
||||||
variant={selected ? 'solid' : 'soft'}
|
tankFilters.gunType.includes('regular') ? 'solid' : 'soft'
|
||||||
|
}
|
||||||
radius="none"
|
radius="none"
|
||||||
color={selected ? undefined : 'gray'}
|
color={
|
||||||
|
tankFilters.gunType.includes('regular') ? undefined : 'gray'
|
||||||
|
}
|
||||||
highContrast
|
highContrast
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (tankFilters.classes.includes(tankClass)) {
|
if (tankFilters.gunType.includes('regular')) {
|
||||||
$tankFilters.setKey(
|
$tankFilters.setKey(
|
||||||
'classes',
|
'gunType',
|
||||||
tankFilters.classes.filter((c) => c !== tankClass),
|
tankFilters.gunType.filter((t) => t !== 'regular'),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$tankFilters.setKey('classes', [
|
$tankFilters.setKey('gunType', [
|
||||||
...tankFilters.classes,
|
...tankFilters.gunType,
|
||||||
tankClass,
|
'regular',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon style={{ width: '1em', height: '1em' }} />
|
<GunRegularIcon style={{ width: '1em', height: '1em' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
);
|
<IconButton
|
||||||
})}
|
variant={
|
||||||
</Flex>
|
tankFilters.gunType.includes('auto_loader') ? 'solid' : 'soft'
|
||||||
|
}
|
||||||
|
radius="none"
|
||||||
|
color={
|
||||||
|
tankFilters.gunType.includes('auto_loader') ? undefined : 'gray'
|
||||||
|
}
|
||||||
|
highContrast
|
||||||
|
onClick={() => {
|
||||||
|
if (tankFilters.gunType.includes('auto_loader')) {
|
||||||
|
$tankFilters.setKey(
|
||||||
|
'gunType',
|
||||||
|
tankFilters.gunType.filter((t) => t !== 'auto_loader'),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$tankFilters.setKey('gunType', [
|
||||||
|
...tankFilters.gunType,
|
||||||
|
'auto_loader',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GunAutoloaderIcon style={{ width: '1em', height: '1em' }} />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
variant={
|
||||||
|
tankFilters.gunType.includes('auto_reloader') ? 'solid' : 'soft'
|
||||||
|
}
|
||||||
|
radius="none"
|
||||||
|
color={
|
||||||
|
tankFilters.gunType.includes('auto_reloader')
|
||||||
|
? undefined
|
||||||
|
: 'gray'
|
||||||
|
}
|
||||||
|
highContrast
|
||||||
|
onClick={() => {
|
||||||
|
if (tankFilters.gunType.includes('auto_reloader')) {
|
||||||
|
$tankFilters.setKey(
|
||||||
|
'gunType',
|
||||||
|
tankFilters.gunType.filter((t) => t !== 'auto_reloader'),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$tankFilters.setKey('gunType', [
|
||||||
|
...tankFilters.gunType,
|
||||||
|
'auto_reloader',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GunAutoreloaderIcon style={{ width: '1em', height: '1em' }} />
|
||||||
|
</IconButton>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Flex
|
<Box display={{ initial: 'none', md: 'block' }}>
|
||||||
overflow="hidden"
|
<Button
|
||||||
style={{ borderRadius: 'var(--radius-full)' }}
|
color="red"
|
||||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
onClick={() => {
|
||||||
>
|
$tankFilters.set(initialTankFilters);
|
||||||
<IconButton
|
$tankopediaSort.set(initialTankopediaSort);
|
||||||
variant={tankFilters.gunType.includes('regular') ? 'solid' : 'soft'}
|
}}
|
||||||
radius="none"
|
>
|
||||||
color={tankFilters.gunType.includes('regular') ? undefined : 'gray'}
|
<ReloadIcon />
|
||||||
highContrast
|
{strings.website.common.tank_search.clear_filters}
|
||||||
onClick={() => {
|
</Button>
|
||||||
if (tankFilters.gunType.includes('regular')) {
|
</Box>
|
||||||
$tankFilters.setKey(
|
</Flex>
|
||||||
'gunType',
|
|
||||||
tankFilters.gunType.filter((t) => t !== 'regular'),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$tankFilters.setKey('gunType', [
|
|
||||||
...tankFilters.gunType,
|
|
||||||
'regular',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GunRegularIcon style={{ width: '1em', height: '1em' }} />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
variant={
|
|
||||||
tankFilters.gunType.includes('auto_loader') ? 'solid' : 'soft'
|
|
||||||
}
|
|
||||||
radius="none"
|
|
||||||
color={
|
|
||||||
tankFilters.gunType.includes('auto_loader') ? undefined : 'gray'
|
|
||||||
}
|
|
||||||
highContrast
|
|
||||||
onClick={() => {
|
|
||||||
if (tankFilters.gunType.includes('auto_loader')) {
|
|
||||||
$tankFilters.setKey(
|
|
||||||
'gunType',
|
|
||||||
tankFilters.gunType.filter((t) => t !== 'auto_loader'),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$tankFilters.setKey('gunType', [
|
|
||||||
...tankFilters.gunType,
|
|
||||||
'auto_loader',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GunAutoloaderIcon style={{ width: '1em', height: '1em' }} />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
variant={
|
|
||||||
tankFilters.gunType.includes('auto_reloader') ? 'solid' : 'soft'
|
|
||||||
}
|
|
||||||
radius="none"
|
|
||||||
color={
|
|
||||||
tankFilters.gunType.includes('auto_reloader') ? undefined : 'gray'
|
|
||||||
}
|
|
||||||
highContrast
|
|
||||||
onClick={() => {
|
|
||||||
if (tankFilters.gunType.includes('auto_reloader')) {
|
|
||||||
$tankFilters.setKey(
|
|
||||||
'gunType',
|
|
||||||
tankFilters.gunType.filter((t) => t !== 'auto_reloader'),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$tankFilters.setKey('gunType', [
|
|
||||||
...tankFilters.gunType,
|
|
||||||
'auto_reloader',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GunAutoreloaderIcon style={{ width: '1em', height: '1em' }} />
|
|
||||||
</IconButton>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex
|
<Flex gap="2" align="start">
|
||||||
overflow="hidden"
|
<Flex
|
||||||
style={{ borderRadius: 'var(--radius-full)' }}
|
overflow="hidden"
|
||||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
style={{ borderRadius: 'var(--radius-full)' }}
|
||||||
>
|
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||||
<IconButton
|
|
||||||
variant={
|
|
||||||
tankFilters.types?.includes(TankType.RESEARCHABLE)
|
|
||||||
? 'solid'
|
|
||||||
: 'soft'
|
|
||||||
}
|
|
||||||
radius="none"
|
|
||||||
color={
|
|
||||||
tankFilters.types?.includes(TankType.RESEARCHABLE)
|
|
||||||
? undefined
|
|
||||||
: 'gray'
|
|
||||||
}
|
|
||||||
highContrast
|
|
||||||
onClick={() => {
|
|
||||||
if (tankFilters.types.includes(TankType.RESEARCHABLE)) {
|
|
||||||
$tankFilters.setKey(
|
|
||||||
'types',
|
|
||||||
tankFilters.types.filter((t) => t !== TankType.RESEARCHABLE),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$tankFilters.setKey('types', [
|
|
||||||
...tankFilters.types,
|
|
||||||
TankType.RESEARCHABLE,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
variant={
|
|
||||||
tankFilters.types?.includes(TankType.PREMIUM) ? 'solid' : 'soft'
|
|
||||||
}
|
|
||||||
radius="none"
|
|
||||||
color={
|
|
||||||
tankFilters.types?.includes(TankType.PREMIUM) ? 'amber' : 'gray'
|
|
||||||
}
|
|
||||||
highContrast
|
|
||||||
onClick={() => {
|
|
||||||
if (tankFilters.types.includes(TankType.PREMIUM)) {
|
|
||||||
$tankFilters.setKey(
|
|
||||||
'types',
|
|
||||||
tankFilters.types.filter((t) => t !== TankType.PREMIUM),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$tankFilters.setKey('types', [
|
|
||||||
...tankFilters.types,
|
|
||||||
TankType.PREMIUM,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
color={
|
|
||||||
tankFilters.types?.includes(TankType.PREMIUM)
|
|
||||||
? undefined
|
|
||||||
: 'amber'
|
|
||||||
}
|
|
||||||
style={{ display: 'flex', justifyContent: 'center' }}
|
|
||||||
>
|
>
|
||||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
<IconButton
|
||||||
</Text>
|
variant={
|
||||||
</IconButton>
|
tankFilters.types?.includes(TankType.RESEARCHABLE)
|
||||||
<IconButton
|
? 'solid'
|
||||||
variant={
|
: 'soft'
|
||||||
tankFilters.types?.includes(TankType.COLLECTOR) ? 'solid' : 'soft'
|
}
|
||||||
}
|
radius="none"
|
||||||
radius="none"
|
color={
|
||||||
color={
|
tankFilters.types?.includes(TankType.RESEARCHABLE)
|
||||||
tankFilters.types?.includes(TankType.COLLECTOR) ? 'blue' : 'gray'
|
? undefined
|
||||||
}
|
: 'gray'
|
||||||
highContrast
|
}
|
||||||
onClick={() => {
|
highContrast
|
||||||
if (tankFilters.types.includes(TankType.COLLECTOR)) {
|
onClick={() => {
|
||||||
$tankFilters.setKey(
|
if (tankFilters.types.includes(TankType.RESEARCHABLE)) {
|
||||||
'types',
|
$tankFilters.setKey(
|
||||||
tankFilters.types.filter((t) => t !== TankType.COLLECTOR),
|
'types',
|
||||||
);
|
tankFilters.types.filter(
|
||||||
} else {
|
(t) => t !== TankType.RESEARCHABLE,
|
||||||
$tankFilters.setKey('types', [
|
),
|
||||||
...tankFilters.types,
|
);
|
||||||
TankType.COLLECTOR,
|
} else {
|
||||||
]);
|
$tankFilters.setKey('types', [
|
||||||
}
|
...tankFilters.types,
|
||||||
}}
|
TankType.RESEARCHABLE,
|
||||||
>
|
]);
|
||||||
<Text
|
}
|
||||||
color={
|
}}
|
||||||
tankFilters.types?.includes(TankType.COLLECTOR)
|
>
|
||||||
? undefined
|
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||||
: 'blue'
|
</IconButton>
|
||||||
}
|
<IconButton
|
||||||
style={{ display: 'flex', justifyContent: 'center' }}
|
variant={
|
||||||
|
tankFilters.types?.includes(TankType.PREMIUM) ? 'solid' : 'soft'
|
||||||
|
}
|
||||||
|
radius="none"
|
||||||
|
color={
|
||||||
|
tankFilters.types?.includes(TankType.PREMIUM) ? 'amber' : 'gray'
|
||||||
|
}
|
||||||
|
highContrast
|
||||||
|
onClick={() => {
|
||||||
|
if (tankFilters.types.includes(TankType.PREMIUM)) {
|
||||||
|
$tankFilters.setKey(
|
||||||
|
'types',
|
||||||
|
tankFilters.types.filter((t) => t !== TankType.PREMIUM),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$tankFilters.setKey('types', [
|
||||||
|
...tankFilters.types,
|
||||||
|
TankType.PREMIUM,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
color={
|
||||||
|
tankFilters.types?.includes(TankType.PREMIUM)
|
||||||
|
? undefined
|
||||||
|
: 'amber'
|
||||||
|
}
|
||||||
|
style={{ display: 'flex', justifyContent: 'center' }}
|
||||||
|
>
|
||||||
|
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||||
|
</Text>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
variant={
|
||||||
|
tankFilters.types?.includes(TankType.COLLECTOR)
|
||||||
|
? 'solid'
|
||||||
|
: 'soft'
|
||||||
|
}
|
||||||
|
radius="none"
|
||||||
|
color={
|
||||||
|
tankFilters.types?.includes(TankType.COLLECTOR)
|
||||||
|
? 'blue'
|
||||||
|
: 'gray'
|
||||||
|
}
|
||||||
|
highContrast
|
||||||
|
onClick={() => {
|
||||||
|
if (tankFilters.types.includes(TankType.COLLECTOR)) {
|
||||||
|
$tankFilters.setKey(
|
||||||
|
'types',
|
||||||
|
tankFilters.types.filter((t) => t !== TankType.COLLECTOR),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$tankFilters.setKey('types', [
|
||||||
|
...tankFilters.types,
|
||||||
|
TankType.COLLECTOR,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
color={
|
||||||
|
tankFilters.types?.includes(TankType.COLLECTOR)
|
||||||
|
? undefined
|
||||||
|
: 'blue'
|
||||||
|
}
|
||||||
|
style={{ display: 'flex', justifyContent: 'center' }}
|
||||||
|
>
|
||||||
|
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
||||||
|
</Text>
|
||||||
|
</IconButton>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
overflow="hidden"
|
||||||
|
style={{ borderRadius: 'var(--radius-full)' }}
|
||||||
|
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||||
>
|
>
|
||||||
<ResearchedIcon style={{ width: '1em', height: '1em' }} />
|
<IconButton
|
||||||
</Text>
|
variant={tankFilters.testing === 'include' ? 'solid' : 'soft'}
|
||||||
</IconButton>
|
radius="none"
|
||||||
|
color={tankFilters.testing === 'include' ? undefined : 'gray'}
|
||||||
|
highContrast
|
||||||
|
onClick={() => {
|
||||||
|
$tankFilters.setKey('testing', 'include');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ExperimentIcon
|
||||||
|
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
variant={tankFilters.testing === 'exclude' ? 'solid' : 'soft'}
|
||||||
|
radius="none"
|
||||||
|
color={tankFilters.testing === 'exclude' ? undefined : 'gray'}
|
||||||
|
highContrast
|
||||||
|
onClick={() => {
|
||||||
|
$tankFilters.setKey('testing', 'exclude');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ScienceOffIcon
|
||||||
|
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
variant={tankFilters.testing === 'only' ? 'solid' : 'soft'}
|
||||||
|
radius="none"
|
||||||
|
color={tankFilters.testing === 'only' ? undefined : 'gray'}
|
||||||
|
highContrast
|
||||||
|
onClick={() => {
|
||||||
|
$tankFilters.setKey('testing', 'only');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ScienceIcon
|
||||||
|
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
overflow="hidden"
|
||||||
|
style={{ borderRadius: 'var(--radius-full)' }}
|
||||||
|
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
||||||
|
>
|
||||||
|
<ShellFilter index={0} />
|
||||||
|
<ShellFilter index={1} premium />
|
||||||
|
<ShellFilter index={2} />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex
|
<Box display={{ initial: 'block', md: 'none' }} ml="4">
|
||||||
overflow="hidden"
|
|
||||||
style={{ borderRadius: 'var(--radius-full)' }}
|
|
||||||
direction={compact ? 'column' : { md: 'row', initial: 'column' }}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
variant={tankFilters.testing === 'include' ? 'solid' : 'soft'}
|
|
||||||
radius="none"
|
|
||||||
color={tankFilters.testing === 'include' ? undefined : 'gray'}
|
|
||||||
highContrast
|
|
||||||
onClick={() => {
|
|
||||||
$tankFilters.setKey('testing', 'include');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ExperimentIcon
|
|
||||||
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
variant={tankFilters.testing === 'exclude' ? 'solid' : 'soft'}
|
|
||||||
radius="none"
|
|
||||||
color={tankFilters.testing === 'exclude' ? undefined : 'gray'}
|
|
||||||
highContrast
|
|
||||||
onClick={() => {
|
|
||||||
$tankFilters.setKey('testing', 'exclude');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ScienceOffIcon
|
|
||||||
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
variant={tankFilters.testing === 'only' ? 'solid' : 'soft'}
|
|
||||||
radius="none"
|
|
||||||
color={tankFilters.testing === 'only' ? undefined : 'gray'}
|
|
||||||
highContrast
|
|
||||||
onClick={() => {
|
|
||||||
$tankFilters.setKey('testing', 'only');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ScienceIcon
|
|
||||||
style={{ width: '1em', height: '1em', color: 'currentColor' }}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -22,6 +22,27 @@ export function filterTank(filters: TankFilters, tank: TankDefinition) {
|
|||||||
(filters.gunType.includes('auto_reloader') &&
|
(filters.gunType.includes('auto_reloader') &&
|
||||||
tank.turrets.some((turret) =>
|
tank.turrets.some((turret) =>
|
||||||
turret.guns.some((gun) => gun.gun_type!.$case === 'auto_reloader'),
|
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],
|
||||||
|
),
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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';
|
import { map } from 'nanostores';
|
||||||
|
|
||||||
export type CaseType<T> = T extends {
|
export type CaseType<T> = T extends {
|
||||||
@@ -16,6 +21,7 @@ export interface TankFilters {
|
|||||||
search?: string;
|
search?: string;
|
||||||
searching: boolean;
|
searching: boolean;
|
||||||
gunType: CaseType<GunDefinition>[];
|
gunType: CaseType<GunDefinition>[];
|
||||||
|
shells: [ShellType | null, ShellType | null, ShellType | null];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialTankFilters: TankFilters = {
|
export const initialTankFilters: TankFilters = {
|
||||||
@@ -27,6 +33,7 @@ export const initialTankFilters: TankFilters = {
|
|||||||
search: undefined,
|
search: undefined,
|
||||||
searching: false,
|
searching: false,
|
||||||
gunType: [],
|
gunType: [],
|
||||||
|
shells: [null, null, null],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const $tankFilters = map<TankFilters>(initialTankFilters);
|
export const $tankFilters = map<TankFilters>(initialTankFilters);
|
||||||
Reference in new issue
Block a user