diff --git a/packages/website/src/components/Performance/Tanks.tsx b/packages/website/src/components/Performance/Tanks.tsx index e49a40487..e1dfeba0d 100644 --- a/packages/website/src/components/Performance/Tanks.tsx +++ b/packages/website/src/components/Performance/Tanks.tsx @@ -1,17 +1,16 @@ -import { useStore } from '@nanostores/react'; -import { Table } from '@radix-ui/themes'; -import { times } from 'lodash-es'; -import { Fragment, Suspense, useEffect, useMemo, useState } from 'react'; -import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions'; -import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions'; -import { filterTank } from '../../core/blitzkit/filterTank'; -import { Performance } from '../../stores/performance'; -import { $tankFilters } from '../../stores/tankFilters'; -import { TankPerformanceSort } from '../../stores/tankPerformanceSort'; -import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps'; -import { RowLoader } from './RowLoader'; -import { TankRow } from './TankRow'; -import { Total } from './Total'; +import { Table } from "@radix-ui/themes"; +import { times } from "lodash-es"; +import { Fragment, Suspense, useEffect, useMemo, useState } from "react"; +import { awaitableAverageDefinitions } from "../../core/awaitables/averageDefinitions"; +import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; +import { filterTank } from "../../core/blitzkit/filterTank"; +import { Performance } from "../../stores/performance"; +import { TankFilters } from "../../stores/tankFilters"; +import { TankPerformanceSort } from "../../stores/tankPerformanceSort"; +import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; +import { RowLoader } from "./RowLoader"; +import { TankRow } from "./TankRow"; +import { Total } from "./Total"; const PREVIEW_COUNT = 10; const DEFAULT_LOADED_ROWS = 25; @@ -25,7 +24,7 @@ const averageDefinitionsArray = Object.entries(averageDefinitions.averages).map( ([id, average]) => ({ id: Number(id), ...average, - }), + }) ); export function Tanks({ skeleton }: MaybeSkeletonComponentProps) { @@ -40,97 +39,97 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) { } const sort = TankPerformanceSort.use(); - const filters = useStore($tankFilters); const tanksSorted = useMemo(() => { const { playerCountPeriod } = Performance.state; switch (sort.type) { - case 'accuracy': + case "accuracy": return averageDefinitionsArray.sort( (a, b) => - sort.direction * (a.mu.hits / a.mu.shots - b.mu.hits / b.mu.shots), + sort.direction * (a.mu.hits / a.mu.shots - b.mu.hits / b.mu.shots) ); - case 'capture_points': + case "capture_points": return averageDefinitionsArray.sort( (a, b) => sort.direction * (a.mu.capture_points / a.mu.battles - - b.mu.capture_points / b.mu.battles), + b.mu.capture_points / b.mu.battles) ); - case 'damage': + case "damage": return averageDefinitionsArray.sort( (a, b) => sort.direction * (a.mu.damage_dealt / a.mu.battles - - b.mu.damage_dealt / b.mu.battles), + b.mu.damage_dealt / b.mu.battles) ); - case 'damage_ratio': + case "damage_ratio": return averageDefinitionsArray.sort( (a, b) => sort.direction * (a.mu.damage_dealt / a.mu.damage_received - - b.mu.damage_dealt / b.mu.damage_received), + b.mu.damage_dealt / b.mu.damage_received) ); - case 'damage_taken': + case "damage_taken": return averageDefinitionsArray.sort( (a, b) => sort.direction * (a.mu.damage_received / a.mu.battles - - b.mu.damage_received / b.mu.battles), + b.mu.damage_received / b.mu.battles) ); - case 'hits': + case "hits": return averageDefinitionsArray.sort( (a, b) => sort.direction * - (a.mu.hits / a.mu.battles - b.mu.hits / b.mu.battles), + (a.mu.hits / a.mu.battles - b.mu.hits / b.mu.battles) ); - case 'kills': + case "kills": return averageDefinitionsArray.sort( (a, b) => sort.direction * - (a.mu.frags / a.mu.battles - b.mu.frags / b.mu.battles), + (a.mu.frags / a.mu.battles - b.mu.frags / b.mu.battles) ); - case 'shots': + case "shots": return averageDefinitionsArray.sort( (a, b) => sort.direction * - (a.mu.shots / a.mu.battles - b.mu.shots / b.mu.battles), + (a.mu.shots / a.mu.battles - b.mu.shots / b.mu.battles) ); - case 'players': + case "players": return averageDefinitionsArray.sort( (a, b) => sort.direction * - (a.samples[playerCountPeriod] - b.samples[playerCountPeriod]), + (a.samples[playerCountPeriod] - b.samples[playerCountPeriod]) ); - case 'spots': + case "spots": return averageDefinitionsArray.sort( (a, b) => sort.direction * - (a.mu.spotted / a.mu.battles - b.mu.spotted / b.mu.battles), + (a.mu.spotted / a.mu.battles - b.mu.spotted / b.mu.battles) ); - case 'survival': + case "survival": return averageDefinitionsArray.sort( (a, b) => sort.direction * (a.mu.survived_battles / a.mu.battles - - b.mu.survived_battles / b.mu.battles), + b.mu.survived_battles / b.mu.battles) ); - case 'winrate': + case "winrate": return averageDefinitionsArray.sort( (a, b) => sort.direction * - (a.mu.wins / a.mu.battles - b.mu.wins / b.mu.battles), + (a.mu.wins / a.mu.battles - b.mu.wins / b.mu.battles) ); - case 'xp': + case "xp": return averageDefinitionsArray.sort( (a, b) => - sort.direction * (a.mu.xp / a.mu.battles - b.mu.xp / b.mu.battles), + sort.direction * (a.mu.xp / a.mu.battles - b.mu.xp / b.mu.battles) ); } }, [sort]); + const filters = TankFilters.use(); const tanks = useMemo(() => { return tanksSorted.filter((tank) => - filterTank(filters, tankDefinitions.tanks[tank.id]), + filterTank(filters, tankDefinitions.tanks[tank.id]) ); }, [filters, sort]); const [loadedRows, setLoadedRows] = useState(DEFAULT_LOADED_ROWS); diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index 6d56b024e..805172f92 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -4,8 +4,7 @@ import { TANK_CLASSES, TankType, TIER_ROMAN_NUMERALS, -} from '@blitzkit/core'; -import { useStore } from '@nanostores/react'; +} from "@blitzkit/core"; import { Box, Flex, @@ -14,26 +13,22 @@ import { Link, Popover, Text, -} from '@radix-ui/themes'; -import { isEqual, times } from 'lodash-es'; -import { awaitableGameDefinitions } from '../../../core/awaitables/gameDefinitions'; -import { Var } from '../../../core/radix/var'; -import { useLocale } from '../../../hooks/useLocale'; -import { - $tankFilters, - initialTankFilters, - type TankFilters, -} from '../../../stores/tankFilters'; -import { TankSort } from '../../../stores/tankopediaSort'; -import { classIcons } from '../../ClassIcon'; -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'; +} from "@radix-ui/themes"; +import { isEqual, times } from "lodash-es"; +import { awaitableGameDefinitions } from "../../../core/awaitables/gameDefinitions"; +import { Var } from "../../../core/radix/var"; +import { useLocale } from "../../../hooks/useLocale"; +import { TankFilters } from "../../../stores/tankFilters"; +import { TankSort } from "../../../stores/tankopediaSort"; +import { classIcons } from "../../ClassIcon"; +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"; interface FilterControlProps { compact?: boolean; @@ -42,29 +37,29 @@ interface FilterControlProps { const gameDefinitions = await awaitableGameDefinitions; const shellTypeIcons: Record = { - [ShellType.AP]: 'ap', - [ShellType.APCR]: 'ap_cr', - [ShellType.HE]: 'he', - [ShellType.HEAT]: 'hc', + [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 shells = TankFilters.use((state) => state.shells); return ( - {tankFilters.shells[index] === null && ( - + {shells[index] === null && ( + )} - {tankFilters.shells[index] !== null && ( + {shells[index] !== null && ( )} @@ -75,35 +70,33 @@ function ShellFilter({ index, premium }: { index: number; premium?: boolean }) { {Object.values(ShellType).map((shellType) => { - if (typeof shellType === 'string') return null; + if (typeof shellType === "string") return null; - const selected = tankFilters.shells[index] === shellType; + const selected = shells[index] === shellType; return ( { - const mutated = [ - tankFilters.shells[0], - tankFilters.shells[1], - tankFilters.shells[2], - ] as TankFilters['shells']; + const mutated = [...shells] as TankFilters["shells"]; mutated[index] = selected ? null : shellType; - $tankFilters.setKey('shells', mutated); + TankFilters.mutate((draft) => { + draft.shells = mutated; + }); }} highContrast={selected} - color={selected ? undefined : 'gray'} + color={selected ? undefined : "gray"} > ); @@ -116,26 +109,26 @@ function ShellFilter({ index, premium }: { index: number; premium?: boolean }) { } export function FilterControl({ compact }: FilterControlProps) { - const tankFilters = useStore($tankFilters); + const tankFilters = TankFilters.use(); const { strings } = useLocale(); - const clearable = !isEqual(tankFilters, initialTankFilters); + const clearable = !isEqual(tankFilters, TankFilters.initial); return ( {times(5, (index) => { const tier = index + 1; @@ -144,21 +137,19 @@ export function FilterControl({ compact }: FilterControlProps) { return ( { if (tankFilters.tiers.includes(tier)) { - $tankFilters.setKey( - 'tiers', - tankFilters.tiers.filter((t) => t !== tier), - ); + TankFilters.mutate((draft) => { + draft.tiers = draft.tiers.filter((t) => t !== tier); + }); } else { - $tankFilters.setKey('tiers', [ - ...tankFilters.tiers, - tier, - ]); + TankFilters.mutate((draft) => { + draft.tiers = [...draft.tiers, tier]; + }); } }} > @@ -169,15 +160,15 @@ export function FilterControl({ compact }: FilterControlProps) { {times(5, (index) => { const tier = index + 6; @@ -186,21 +177,19 @@ export function FilterControl({ compact }: FilterControlProps) { return ( { if (tankFilters.tiers.includes(tier)) { - $tankFilters.setKey( - 'tiers', - tankFilters.tiers.filter((t) => t !== tier), - ); + TankFilters.mutate((draft) => { + draft.tiers = draft.tiers.filter((t) => t !== tier); + }); } else { - $tankFilters.setKey('tiers', [ - ...tankFilters.tiers, - tier, - ]); + TankFilters.mutate((draft) => { + draft.tiers = [...draft.tiers, tier]; + }); } }} > @@ -213,12 +202,12 @@ export function FilterControl({ compact }: FilterControlProps) { {gameDefinitions.nations .slice(0, Math.ceil(gameDefinitions.nations.length / 2)) @@ -228,26 +217,24 @@ export function FilterControl({ compact }: FilterControlProps) { return ( { - if (tankFilters.nations.includes(nation)) { - $tankFilters.setKey( - 'nations', - tankFilters.nations.filter((n) => n !== nation), - ); - } else { - $tankFilters.setKey('nations', [ - ...tankFilters.nations, - nation, - ]); - } + TankFilters.mutate((draft) => { + if (tankFilters.nations.includes(nation)) { + draft.nations = draft.nations.filter( + (n) => n !== nation + ); + } else { + draft.nations = [...draft.nations, nation]; + } + }); }} > @@ -256,15 +243,15 @@ export function FilterControl({ compact }: FilterControlProps) { {gameDefinitions.nations .slice(Math.ceil(gameDefinitions.nations.length / 2)) @@ -275,26 +262,24 @@ export function FilterControl({ compact }: FilterControlProps) { { - if (tankFilters.nations.includes(nation)) { - $tankFilters.setKey( - 'nations', - tankFilters.nations.filter((n) => n !== nation), - ); - } else { - $tankFilters.setKey('nations', [ - ...tankFilters.nations, - nation, - ]); - } + TankFilters.mutate((draft) => { + if (tankFilters.nations.includes(nation)) { + draft.nations = draft.nations.filter( + (n) => n !== nation + ); + } else { + draft.nations = [...draft.nations, nation]; + } + }); }} > @@ -304,15 +289,15 @@ export function FilterControl({ compact }: FilterControlProps) { {TANK_CLASSES.map((tankClass) => { const Icon = classIcons[tankClass]; @@ -321,25 +306,23 @@ export function FilterControl({ compact }: FilterControlProps) { return ( { - if (tankFilters.classes.includes(tankClass)) { - $tankFilters.setKey( - 'classes', - tankFilters.classes.filter((c) => c !== tankClass), - ); - } else { - $tankFilters.setKey('classes', [ - ...tankFilters.classes, - tankClass, - ]); - } + TankFilters.mutate((draft) => { + if (tankFilters.classes.includes(tankClass)) { + draft.classes = draft.classes.filter( + (c) => c !== tankClass + ); + } else { + draft.classes = [...draft.classes, tankClass]; + } + }); }} > - + ); })} @@ -347,96 +330,90 @@ export function FilterControl({ compact }: FilterControlProps) { { - if (tankFilters.gunType.includes('regular')) { - $tankFilters.setKey( - 'gunType', - tankFilters.gunType.filter((t) => t !== 'regular'), - ); - } else { - $tankFilters.setKey('gunType', [ - ...tankFilters.gunType, - 'regular', - ]); - } + TankFilters.mutate((draft) => { + if (tankFilters.gunType.includes("regular")) { + draft.gunType = draft.gunType.filter( + (t) => t !== "regular" + ); + } else { + draft.gunType = [...draft.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', - ]); - } + TankFilters.mutate((draft) => { + if (tankFilters.gunType.includes("auto_loader")) { + draft.gunType = draft.gunType.filter( + (t) => t !== "auto_loader" + ); + } else { + draft.gunType = [...draft.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', - ]); - } + TankFilters.mutate((draft) => { + if (tankFilters.gunType.includes("auto_reloader")) { + draft.gunType = draft.gunType.filter( + (t) => t !== "auto_reloader" + ); + } else { + draft.gunType = [...draft.gunType, "auto_reloader"]; + } + }); }} > - + @@ -447,160 +424,158 @@ export function FilterControl({ compact }: FilterControlProps) { { - if (tankFilters.types.includes(TankType.RESEARCHABLE)) { - $tankFilters.setKey( - 'types', - tankFilters.types.filter( - (t) => t !== TankType.RESEARCHABLE, - ), - ); - } else { - $tankFilters.setKey('types', [ - ...tankFilters.types, - TankType.RESEARCHABLE, - ]); - } + TankFilters.mutate((draft) => { + if (tankFilters.types.includes(TankType.RESEARCHABLE)) { + draft.types = draft.types.filter( + (t) => t !== TankType.RESEARCHABLE + ); + } else { + draft.types = [...draft.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, - ]); - } + TankFilters.mutate((draft) => { + if (tankFilters.types.includes(TankType.PREMIUM)) { + draft.types = draft.types.filter( + (t) => t !== TankType.PREMIUM + ); + } else { + draft.types = [...draft.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.mutate((draft) => { + if (tankFilters.types.includes(TankType.COLLECTOR)) { + draft.types = draft.types.filter( + (t) => t !== TankType.COLLECTOR + ); + } else { + draft.types = [...draft.types, TankType.COLLECTOR]; + } + }); }} > - + { - $tankFilters.setKey('testing', 'include'); + TankFilters.mutate((draft) => { + draft.testing = "include"; + }); }} > { - $tankFilters.setKey('testing', 'exclude'); + TankFilters.mutate((draft) => { + draft.testing = "exclude"; + }); }} > { - $tankFilters.setKey('testing', 'only'); + TankFilters.mutate((draft) => { + draft.testing = "only"; + }); }} > @@ -616,7 +591,7 @@ export function FilterControl({ compact }: FilterControlProps) { href="#" onClick={(event) => { event.preventDefault(); - $tankFilters.set(initialTankFilters); + TankFilters.set(TankFilters.initial); TankSort.set(TankSort.initial); }} > diff --git a/packages/website/src/components/TankSearch/components/NoResults.tsx b/packages/website/src/components/TankSearch/components/NoResults.tsx index 5c8e8d8de..2d5cc6d91 100644 --- a/packages/website/src/components/TankSearch/components/NoResults.tsx +++ b/packages/website/src/components/TankSearch/components/NoResults.tsx @@ -1,27 +1,27 @@ -import { Flex, Link, Text } from '@radix-ui/themes'; -import { useLocale } from '../../../hooks/useLocale'; -import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters'; +import { Flex, Link, Text } from "@radix-ui/themes"; +import { useLocale } from "../../../hooks/useLocale"; +import { TankFilters } from "../../../stores/tankFilters"; interface NoResultsProps { - type?: 'filters' | 'search'; + type?: "filters" | "search"; } -export function NoResults({ type = 'filters' }: NoResultsProps) { +export function NoResults({ type = "filters" }: NoResultsProps) { const { strings } = useLocale(); return ( - {strings.website.common.tank_search.no_tanks_found.body}{' '} + {strings.website.common.tank_search.no_tanks_found.body}{" "} { - $tankFilters.set(initialTankFilters); + TankFilters.set(TankFilters.initial); }} > - {type === 'filters' + {type === "filters" ? strings.website.common.tank_search.no_tanks_found.clear_filters : strings.website.common.tank_search.no_tanks_found.search_again} diff --git a/packages/website/src/components/TankSearch/components/QuickLink.tsx b/packages/website/src/components/TankSearch/components/QuickLink.tsx index 04bf60b43..cb4409142 100644 --- a/packages/website/src/components/TankSearch/components/QuickLink.tsx +++ b/packages/website/src/components/TankSearch/components/QuickLink.tsx @@ -1,20 +1,20 @@ -import { type TankDefinition } from '@blitzkit/core'; -import { useStore } from '@nanostores/react'; -import { CaretRightIcon } from '@radix-ui/react-icons'; -import { Button, TextField } from '@radix-ui/themes'; -import { useLocale } from '../../../hooks/useLocale'; -import { $tankFilters } from '../../../stores/tankFilters'; -import { LinkI18n } from '../../LinkI18n'; +import { type TankDefinition } from "@blitzkit/core"; +import { CaretRightIcon } from "@radix-ui/react-icons"; +import { Button, TextField } from "@radix-ui/themes"; +import { useLocale } from "../../../hooks/useLocale"; +import { TankFilters } from "../../../stores/tankFilters"; +import { LinkI18n } from "../../LinkI18n"; interface QuickLinkProps { topResult?: TankDefinition; } export function QuickLink({ topResult }: QuickLinkProps) { - const tankFilters = useStore($tankFilters); const { locale, unwrap } = useLocale(); + const search = TankFilters.use((state) => state.search); + const searching = TankFilters.use((state) => state.searching); - if (!tankFilters.search || !topResult || tankFilters.searching) return null; + if (!search || !topResult || searching) return null; return ( diff --git a/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx b/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx index 94484fec1..fc2338964 100644 --- a/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx +++ b/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx @@ -1,20 +1,19 @@ -import { useStore } from '@nanostores/react'; -import { Flex, Separator, Text } from '@radix-ui/themes'; -import { isEqual } from 'lodash-es'; -import { useMemo } from 'react'; -import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions'; -import { useLocale } from '../../../hooks/useLocale'; -import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters'; -import { TankopediaPersistent } from '../../../stores/tankopediaPersistent'; -import { TankSort } from '../../../stores/tankopediaSort'; -import { TankCard } from '../../TankCard'; -import { TankCardWrapper } from './TankCardWrapper'; +import { Flex, Separator, Text } from "@radix-ui/themes"; +import { isEqual } from "lodash-es"; +import { useMemo } from "react"; +import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; +import { useLocale } from "../../../hooks/useLocale"; +import { TankFilters } from "../../../stores/tankFilters"; +import { TankopediaPersistent } from "../../../stores/tankopediaPersistent"; +import { TankSort } from "../../../stores/tankopediaSort"; +import { TankCard } from "../../TankCard"; +import { TankCardWrapper } from "./TankCardWrapper"; const tankDefinitions = await awaitableTankDefinitions; export function RecentlyViewed() { const tankopediaPersistentStore = TankopediaPersistent.useStore(); - const filters = useStore($tankFilters); + const filters = TankFilters.use(); // non-reactive because it is a little weird that it updates instantly even before the page loads const recentlyViewed = tankopediaPersistentStore .getState() @@ -24,15 +23,15 @@ export function RecentlyViewed() { Object.entries(filters).some(([key, value]) => { return !isEqual( value, - initialTankFilters[key as keyof typeof initialTankFilters], + TankFilters.initial[key as keyof typeof TankFilters.initial] ); }), - [filters], + [filters] ); const { strings } = useLocale(); const by = TankSort.use((state) => state.by); - if (recentlyViewed.length === 0 || by !== 'meta.none' || hasFilters) { + if (recentlyViewed.length === 0 || by !== "meta.none" || hasFilters) { return null; } diff --git a/packages/website/src/components/TankSearch/components/SearchBar.tsx b/packages/website/src/components/TankSearch/components/SearchBar.tsx index 5d65bcbc9..32eba599f 100644 --- a/packages/website/src/components/TankSearch/components/SearchBar.tsx +++ b/packages/website/src/components/TankSearch/components/SearchBar.tsx @@ -1,14 +1,13 @@ -import type { TankDefinition } from '@blitzkit/core'; -import { useStore } from '@nanostores/react'; -import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; -import { Flex, Spinner, TextField } from '@radix-ui/themes'; -import { debounce } from 'lodash-es'; -import { type KeyboardEventHandler, useCallback, useRef } from 'react'; -import { useLocale } from '../../../hooks/useLocale'; -import { $tankFilters } from '../../../stores/tankFilters'; -import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps'; -import { QuickLink } from './QuickLink'; -import { Sort } from './Sort'; +import type { TankDefinition } from "@blitzkit/core"; +import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; +import { Flex, Spinner, TextField } from "@radix-ui/themes"; +import { debounce } from "lodash-es"; +import { type KeyboardEventHandler, useCallback, useRef } from "react"; +import { useLocale } from "../../../hooks/useLocale"; +import { TankFilters } from "../../../stores/tankFilters"; +import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; +import { QuickLink } from "./QuickLink"; +import { Sort } from "./Sort"; type SearchBarProps = MaybeSkeletonComponentProps & { topResult?: TankDefinition; @@ -17,30 +16,37 @@ type SearchBarProps = MaybeSkeletonComponentProps & { export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) { const { strings } = useLocale(); - const tankFilters = useStore($tankFilters); + const search = TankFilters.use((state) => state.search); + const searching = TankFilters.use((state) => state.searching); const input = useRef(null); const performSearch = useCallback( debounce(() => { - $tankFilters.setKey('searching', false); + TankFilters.mutate((draft) => { + draft.searching = false; + }); if (!input.current) return; const sanitized = input.current.value.trim(); - $tankFilters.setKey('search', sanitized.length === 0 ? null : sanitized); + TankFilters.mutate((draft) => { + draft.search = sanitized.length === 0 ? null : sanitized; + }); }, 500), - [], + [] ); const handleChange = useCallback(() => { - if (!tankFilters.searching) { - $tankFilters.setKey('searching', true); + if (!searching) { + TankFilters.mutate((draft) => { + draft.searching = true; + }); } performSearch(); - }, [tankFilters]); + }, [searching]); const handleKeyDown = useCallback( (event) => { - if (event.key !== 'Enter' || !topResult || tankFilters.searching) return; + if (event.key !== "Enter" || !topResult || searching) return; event.preventDefault(); @@ -50,7 +56,7 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) { window.location.href = `/tanks/${topResult.slug}`; } }, - [topResult], + [topResult] ); return ( @@ -59,7 +65,7 @@ export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) { - {tankFilters.searching ? : } + {searching ? : } diff --git a/packages/website/src/components/TankSearch/components/TierFilter.tsx b/packages/website/src/components/TankSearch/components/TierFilter.tsx index c4e7ccdbc..735399ea9 100644 --- a/packages/website/src/components/TankSearch/components/TierFilter.tsx +++ b/packages/website/src/components/TankSearch/components/TierFilter.tsx @@ -1,44 +1,43 @@ -import { TIER_ROMAN_NUMERALS } from '@blitzkit/core'; -import { useStore } from '@nanostores/react'; -import { Flex, IconButton, Text } from '@radix-ui/themes'; -import { times } from 'lodash-es'; -import { memo } from 'react'; -import { $tankFilters } from '../../../stores/tankFilters'; -import { TankSort } from '../../../stores/tankopediaSort'; +import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { Flex, IconButton, Text } from "@radix-ui/themes"; +import { times } from "lodash-es"; +import { memo } from "react"; +import { TankFilters } from "../../../stores/tankFilters"; +import { TankSort } from "../../../stores/tankopediaSort"; export const TierFilter = memo(() => { - const tankFilters = useStore($tankFilters); const by = TankSort.use((state) => state.by); + const search = TankFilters.use((state) => state.search); + const tiers = TankFilters.use((state) => state.tiers); - if (by !== 'meta.none' || tankFilters.search) return null; + if (by !== "meta.none" || search) return null; return ( {times(10, (index) => { const tier = 10 - index; - const selected = tankFilters.tiers.includes(tier); + const selected = tiers.includes(tier); return ( { - if (tankFilters.tiers.includes(tier)) { - $tankFilters.setKey( - 'tiers', - tankFilters.tiers.filter((t) => t !== tier), - ); - } else { - $tankFilters.setKey('tiers', [...tankFilters.tiers, tier]); - } + TankFilters.mutate((draft) => { + if (tiers.includes(tier)) { + draft.tiers = tiers.filter((t) => t !== tier); + } else { + draft.tiers = [...tiers, tier]; + } + }); }} > {TIER_ROMAN_NUMERALS[tier]} diff --git a/packages/website/src/components/TankSearch/index.tsx b/packages/website/src/components/TankSearch/index.tsx index c5fa3fc7f..a2f9f7d33 100644 --- a/packages/website/src/components/TankSearch/index.tsx +++ b/packages/website/src/components/TankSearch/index.tsx @@ -5,37 +5,36 @@ import { SEARCH_KEYS, unionBoundingBox, type TankDefinition, -} from '@blitzkit/core'; -import { literals } from '@blitzkit/i18n/src/literals'; -import { useStore } from '@nanostores/react'; -import { Callout, Flex, Link, Text, type FlexProps } from '@radix-ui/themes'; -import fuzzysort from 'fuzzysort'; -import { times, uniq } from 'lodash-es'; -import { memo, useEffect, useMemo, useState } from 'react'; -import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions'; -import { awaitableModelDefinitions } from '../../core/awaitables/modelDefinitions'; -import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions'; -import { awaitableTankNames } from '../../core/awaitables/tankNames'; -import { filterTank } from '../../core/blitzkit/filterTank'; -import { resolveReload } from '../../core/blitzkit/resolveReload'; -import { useLocale } from '../../hooks/useLocale'; -import { $tankFilters } from '../../stores/tankFilters'; -import { TankopediaPersistent } from '../../stores/tankopediaPersistent'; -import { SORT_UNITS } from '../../stores/tankopediaPersistent/constants'; -import { TankSort } from '../../stores/tankopediaSort'; -import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps'; -import { ExperimentIcon } from '../ExperimentIcon'; -import { TankSearchCard } from './components/Card'; -import { FilterControl } from './components/FilterControl'; -import { NoResults } from './components/NoResults'; -import { RecentlyViewed } from './components/RecentlyViewed'; -import { SearchBar } from './components/SearchBar'; -import { SkeletonTankCard } from './components/SkeletonTankCard'; -import { TankCardWrapper } from './components/TankCardWrapper'; -import { MAX_RECENTLY_VIEWED } from './constants'; +} from "@blitzkit/core"; +import { literals } from "@blitzkit/i18n/src/literals"; +import { Callout, Flex, Link, Text, type FlexProps } from "@radix-ui/themes"; +import fuzzysort from "fuzzysort"; +import { times, uniq } from "lodash-es"; +import { memo, useEffect, useMemo, useState } from "react"; +import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions"; +import { awaitableModelDefinitions } from "../../core/awaitables/modelDefinitions"; +import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; +import { awaitableTankNames } from "../../core/awaitables/tankNames"; +import { filterTank } from "../../core/blitzkit/filterTank"; +import { resolveReload } from "../../core/blitzkit/resolveReload"; +import { useLocale } from "../../hooks/useLocale"; +import { TankFilters } from "../../stores/tankFilters"; +import { TankopediaPersistent } from "../../stores/tankopediaPersistent"; +import { SORT_UNITS } from "../../stores/tankopediaPersistent/constants"; +import { TankSort } from "../../stores/tankopediaSort"; +import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; +import { ExperimentIcon } from "../ExperimentIcon"; +import { TankSearchCard } from "./components/Card"; +import { FilterControl } from "./components/FilterControl"; +import { NoResults } from "./components/NoResults"; +import { RecentlyViewed } from "./components/RecentlyViewed"; +import { SearchBar } from "./components/SearchBar"; +import { SkeletonTankCard } from "./components/SkeletonTankCard"; +import { TankCardWrapper } from "./components/TankCardWrapper"; +import { MAX_RECENTLY_VIEWED } from "./constants"; type TankSearchProps = MaybeSkeletonComponentProps & - Omit & { + Omit & { compact?: boolean; onSelect?: (tank: TankDefinition) => void; onSelectAll?: (tanks: TankDefinition[]) => void; @@ -57,21 +56,21 @@ export const TankSearch = memo( const { strings, locale } = useLocale(); const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks); - const tankFilters = useStore($tankFilters); const tankopediaSort = TankSort.use(); + const tankFilters = TankFilters.use(); const tanksFiltered = useMemo(() => { if (tankFilters.search === null) { const filtered = awaitedTanksDefinitionsArray.filter((tank) => - filterTank(tankFilters, tank), + filterTank(tankFilters, tank) ); let sorted: TankDefinition[]; switch (tankopediaSort.by) { - case 'meta.none': + case "meta.none": sorted = metaSortTank(filtered, gameDefinitions); break; - case 'survivability.health': { + case "survivability.health": { sorted = filtered.sort((a, b) => { const aHealth = a.health + a.turrets.at(-1)!.health; const bHealth = b.health + b.turrets.at(-1)!.health; @@ -81,36 +80,36 @@ export const TankSearch = memo( break; } - case 'survivability.viewRange': + case "survivability.viewRange": sorted = filtered.sort( (a, b) => - a.turrets.at(-1)!.view_range - b.turrets.at(-1)!.view_range, + a.turrets.at(-1)!.view_range - b.turrets.at(-1)!.view_range ); break; - case 'survivability.camouflageStill': + case "survivability.camouflageStill": sorted = filtered.sort( - (a, b) => a.camouflage_still - b.camouflage_still, + (a, b) => a.camouflage_still - b.camouflage_still ); break; - case 'survivability.camouflageMoving': + case "survivability.camouflageMoving": sorted = filtered.sort( - (a, b) => a.camouflage_moving - b.camouflage_moving, + (a, b) => a.camouflage_moving - b.camouflage_moving ); break; - case 'survivability.camouflageShooting': + case "survivability.camouflageShooting": sorted = filtered.sort( (a, b) => a.camouflage_still * a.turrets.at(-1)!.guns.at(-1)!.camouflage_loss - b.camouflage_still * - b.turrets.at(-1)!.guns.at(-1)!.camouflage_loss, + b.turrets.at(-1)!.guns.at(-1)!.camouflage_loss ); break; - case 'survivability.volume': + case "survivability.volume": sorted = filtered.sort((a, b) => { const aTankModelDefinition = modelDefinitions.models[a.id]; const bTankModelDefinition = modelDefinitions.models[b.id]; @@ -121,14 +120,14 @@ export const TankSearch = memo( const aSize = normalizeBoundingBox( unionBoundingBox( aTankModelDefinition.bounding_box, - aTurretModelDefinition.bounding_box, - ), + aTurretModelDefinition.bounding_box + ) ); const bSize = normalizeBoundingBox( unionBoundingBox( bTankModelDefinition.bounding_box, - bTurretModelDefinition.bounding_box, - ), + bTurretModelDefinition.bounding_box + ) ); const aVolume = aSize.x * aSize.y * aSize.z; const bVolume = bSize.x * bSize.y * bSize.z; @@ -137,7 +136,7 @@ export const TankSearch = memo( }); break; - case 'survivability.length': + case "survivability.length": sorted = filtered.sort((a, b) => { const aTankModelDefinition = modelDefinitions.models[a.id]; const bTankModelDefinition = modelDefinitions.models[b.id]; @@ -148,14 +147,14 @@ export const TankSearch = memo( const aSize = normalizeBoundingBox( unionBoundingBox( aTankModelDefinition.bounding_box, - aTurretModelDefinition.bounding_box, - ), + aTurretModelDefinition.bounding_box + ) ); const bSize = normalizeBoundingBox( unionBoundingBox( bTankModelDefinition.bounding_box, - bTurretModelDefinition.bounding_box, - ), + bTurretModelDefinition.bounding_box + ) ); const aLength = Math.max(aSize.x, aSize.y, aSize.z); const bLength = Math.max(bSize.x, bSize.y, bSize.z); @@ -164,147 +163,147 @@ export const TankSearch = memo( }); break; - case 'fire.dpm': + case "fire.dpm": sorted = filtered.sort( (a, b) => resolveDpm( a.turrets.at(-1)!.guns.at(-1)!, - a.turrets.at(-1)!.guns.at(-1)!.shells[0], + a.turrets.at(-1)!.guns.at(-1)!.shells[0] ) - resolveDpm( b.turrets.at(-1)!.guns.at(-1)!, - b.turrets.at(-1)!.guns.at(-1)!.shells[0], - ), + b.turrets.at(-1)!.guns.at(-1)!.shells[0] + ) ); break; - case 'fire.dpmPremium': + case "fire.dpmPremium": sorted = filtered.sort( (a, b) => (a.turrets.at(-1)!.guns.at(-1)!.shells[1] ? resolveDpm( a.turrets.at(-1)!.guns.at(-1)!, - a.turrets.at(-1)!.guns.at(-1)!.shells[1], + a.turrets.at(-1)!.guns.at(-1)!.shells[1] ) : 0) - (b.turrets.at(-1)!.guns.at(-1)!.shells[1] ? resolveDpm( b.turrets.at(-1)!.guns.at(-1)!, - b.turrets.at(-1)!.guns.at(-1)!.shells[1], + b.turrets.at(-1)!.guns.at(-1)!.shells[1] ) - : 0), + : 0) ); break; - case 'fire.reload': + case "fire.reload": sorted = filtered.sort( (a, b) => resolveReload(a.turrets.at(-1)!.guns.at(-1)!) - - resolveReload(b.turrets.at(-1)!.guns.at(-1)!), + resolveReload(b.turrets.at(-1)!.guns.at(-1)!) ); break; - case 'fire.caliber': + case "fire.caliber": sorted = filtered.sort( (a, b) => a.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber, + b.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber ); break; - case 'fire.standardPenetration': + case "fire.standardPenetration": sorted = filtered.sort( (a, b) => a.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near, + b.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near ); break; - case 'fire.premiumPenetration': + case "fire.premiumPenetration": sorted = filtered.sort( (a, b) => (a.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration.near ?? 0) - (b.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration.near ?? - 0), + 0) ); break; - case 'fire.tertiaryPenetration': + case "fire.tertiaryPenetration": sorted = filtered.sort( (a, b) => (a.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration.near ?? 0) - (b.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration.near ?? - 0), + 0) ); break; - case 'fire.damage': + case "fire.damage": sorted = filtered.sort( (a, b) => a.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage, + b.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage ); break; - case 'fire.moduleDamage': + case "fire.moduleDamage": sorted = filtered.sort( (a, b) => a.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage, + b.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage ); break; - case 'fire.shellVelocity': + case "fire.shellVelocity": sorted = filtered.sort( (a, b) => a.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity, + b.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity ); break; - case 'fire.shellRange': + case "fire.shellRange": sorted = filtered.sort( (a, b) => a.turrets.at(-1)!.guns.at(-1)!.shells[0].range - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].range, + b.turrets.at(-1)!.guns.at(-1)!.shells[0].range ); break; - case 'fire.shellCapacity': + case "fire.shellCapacity": sorted = filtered.sort( (a, b) => a.turrets.at(-1)!.guns.at(-1)!.shell_capacity - - b.turrets.at(-1)!.guns.at(-1)!.shell_capacity, + b.turrets.at(-1)!.guns.at(-1)!.shell_capacity ); break; - case 'fire.aimTime': + case "fire.aimTime": sorted = filtered.sort( (a, b) => a.turrets.at(-1)!.guns.at(-1)!.aim_time - - b.turrets.at(-1)!.guns.at(-1)!.aim_time, + b.turrets.at(-1)!.guns.at(-1)!.aim_time ); break; - case 'fire.dispersionStill': + case "fire.dispersionStill": sorted = filtered.sort( (a, b) => a.turrets.at(-1)!.guns.at(-1)!.dispersion_base - - b.turrets.at(-1)!.guns.at(-1)!.dispersion_base, + b.turrets.at(-1)!.guns.at(-1)!.dispersion_base ); break; - case 'fire.dispersionMoving': + case "fire.dispersionMoving": sorted = filtered.sort( (a, b) => a.tracks.at(-1)!.dispersion_move - - b.tracks.at(-1)!.dispersion_move, + b.tracks.at(-1)!.dispersion_move ); break; - case 'fire.gunDepression': + case "fire.gunDepression": sorted = filtered.sort( (a, b) => modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id] @@ -314,11 +313,11 @@ export const TankSearch = memo( modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id] .guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch.max - (modelDefinitions.models[b.id].initial_turret_rotation?.pitch ?? - 0), + 0) ); break; - case 'fire.gunElevation': + case "fire.gunElevation": sorted = filtered.sort( (a, b) => modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id] @@ -328,29 +327,29 @@ export const TankSearch = memo( modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id] .guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch.min - (modelDefinitions.models[a.id].initial_turret_rotation?.pitch ?? - 0), + 0) ); break; - case 'maneuverability.forwardsSpeed': + case "maneuverability.forwardsSpeed": sorted = filtered.sort( - (a, b) => a.speed_forwards - b.speed_forwards, + (a, b) => a.speed_forwards - b.speed_forwards ); break; - case 'maneuverability.backwardsSpeed': + case "maneuverability.backwardsSpeed": sorted = filtered.sort( - (a, b) => a.speed_backwards - b.speed_backwards, + (a, b) => a.speed_backwards - b.speed_backwards ); break; - case 'maneuverability.power': + case "maneuverability.power": sorted = filtered.sort( - (a, b) => a.engines.at(-1)!.power - b.engines.at(-1)!.power, + (a, b) => a.engines.at(-1)!.power - b.engines.at(-1)!.power ); break; - case 'maneuverability.powerToWeight': + case "maneuverability.powerToWeight": sorted = filtered.sort( (a, b) => a.engines.at(-1)!.power / @@ -364,11 +363,11 @@ export const TankSearch = memo( b.engines.at(-1)!.weight + b.tracks.at(-1)!.weight + b.turrets.at(-1)!.weight + - b.turrets.at(-1)!.guns.at(-1)!.weight), + b.turrets.at(-1)!.guns.at(-1)!.weight) ); break; - case 'maneuverability.weight': + case "maneuverability.weight": sorted = filtered.sort( (a, b) => a.weight + @@ -380,20 +379,20 @@ export const TankSearch = memo( b.engines.at(-1)!.weight + b.tracks.at(-1)!.weight + b.turrets.at(-1)!.weight + - b.turrets.at(-1)!.guns.at(-1)!.weight), + b.turrets.at(-1)!.guns.at(-1)!.weight) ); break; - case 'maneuverability.traverseSpeed': + case "maneuverability.traverseSpeed": sorted = filtered.sort( (a, b) => a.tracks.at(-1)!.traverse_speed - - b.tracks.at(-1)!.traverse_speed, + b.tracks.at(-1)!.traverse_speed ); break; } - return tankopediaSort.direction === 'ascending' + return tankopediaSort.direction === "ascending" ? sorted : sorted.reverse(); } else { @@ -401,7 +400,7 @@ export const TankSearch = memo( keys: SEARCH_KEYS, }); const searchedTanks = searchedRaw.map( - (result) => tankDefinitions.tanks[result.obj.id], + (result) => tankDefinitions.tanks[result.obj.id] ); return searchedTanks; } @@ -459,24 +458,24 @@ export const TankSearch = memo( )} - {tankopediaSort.by !== 'meta.none' && ( + {tankopediaSort.by !== "meta.none" && ( {literals(strings.website.common.tank_search.sorting_by, [ strings.website.common.tank_search.sort[tankopediaSort.by], ])} {SORT_UNITS[tankopediaSort.by] === undefined - ? '' + ? "" : ` (${SORT_UNITS[tankopediaSort.by]})`} , {tankopediaSort.direction} )} - {tankFilters.testing === 'only' && ( + {tankFilters.testing === "only" && ( - + {strings.website.common.warnings.test} @@ -505,12 +504,12 @@ export const TankSearch = memo( key={index} onIntersection={() => { setLoadedCards((state) => - Math.min(state + 2, tanksFiltered.length), + Math.min(state + 2, tanksFiltered.length) ); }} /> ); - }, + } )} )} @@ -523,15 +522,15 @@ export const TankSearch = memo( {times( Math.round( - skeleton ? DEFAULT_LOADED_CARDS : 10 + 10 * Math.random(), + skeleton ? DEFAULT_LOADED_CARDS : 10 + 10 * Math.random() ), (index) => ( - ), + ) )} )} ); - }, + } ); diff --git a/packages/website/src/components/TierList/Tiles.tsx b/packages/website/src/components/TierList/Tiles.tsx index 6686f3081..664ca90b8 100644 --- a/packages/website/src/components/TierList/Tiles.tsx +++ b/packages/website/src/components/TierList/Tiles.tsx @@ -1,15 +1,14 @@ -import { metaSortTank } from '@blitzkit/core'; -import { useStore } from '@nanostores/react'; -import { times } from 'lodash-es'; -import { useMemo, useState } from 'react'; -import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions'; -import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions'; -import { filterTank } from '../../core/blitzkit/filterTank'; -import { $tankFilters } from '../../stores/tankFilters'; -import { TierList } from '../../stores/tierList'; -import { SkeletonTankCard } from '../TankSearch/components/SkeletonTankCard'; -import { TankCardWrapper } from '../TankSearch/components/TankCardWrapper'; -import { TierListTile } from './Tile'; +import { metaSortTank } from "@blitzkit/core"; +import { times } from "lodash-es"; +import { useMemo, useState } from "react"; +import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions"; +import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; +import { filterTank } from "../../core/blitzkit/filterTank"; +import { TankFilters } from "../../stores/tankFilters"; +import { TierList } from "../../stores/tierList"; +import { SkeletonTankCard } from "../TankSearch/components/SkeletonTankCard"; +import { TankCardWrapper } from "../TankSearch/components/TankCardWrapper"; +import { TierListTile } from "./Tile"; const [tankDefinitions, gameDefinitions] = await Promise.all([ awaitableTankDefinitions, @@ -22,17 +21,17 @@ const PREVIEW_COUNT = 32; const DEFAULT_LOADED_CARDS = 75; export function TierListTiles() { - const filters = useStore($tankFilters); + const filters = TankFilters.use(); const placedTanks = TierList.use((state) => state.placedTanks); const sorted = useMemo( () => metaSortTank( tanks.filter( - (tank) => filterTank(filters, tank) && !placedTanks.has(tank.id), + (tank) => filterTank(filters, tank) && !placedTanks.has(tank.id) ), - gameDefinitions, + gameDefinitions ).reverse(), - [filters, placedTanks], + [filters, placedTanks] ); const [loadedTiles, setLoadedTiles] = useState(DEFAULT_LOADED_CARDS); diff --git a/packages/website/src/stores/tankFilters.ts b/packages/website/src/stores/tankFilters.ts index 14eaac227..5968f6395 100644 --- a/packages/website/src/stores/tankFilters.ts +++ b/packages/website/src/stores/tankFilters.ts @@ -3,8 +3,8 @@ import type { ShellType, TankClass, TankType, -} from '@blitzkit/core'; -import { map } from 'nanostores'; +} from "@blitzkit/core"; +import { Varuna } from "varuna"; export type CaseType = T extends { gun_type?: { $case: infer U; value: any }; @@ -17,23 +17,21 @@ export interface TankFilters { nations: string[]; classes: TankClass[]; types: TankType[]; - testing: 'include' | 'exclude' | 'only'; + testing: "include" | "exclude" | "only"; search: string | null; searching: boolean; gunType: CaseType[]; shells: [ShellType | null, ShellType | null, ShellType | null]; } -export const initialTankFilters: TankFilters = { +export const TankFilters = new Varuna({ tiers: [], nations: [], classes: [], types: [], - testing: 'include', + testing: "include", search: null, searching: false, gunType: [], shells: [null, null, null], -}; - -export const $tankFilters = map(initialTankFilters); +});