From e241870ece98a76103b1141e973d2cf88bed8a6f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Tue, 17 Sep 2024 22:13:35 -0500
Subject: [PATCH] feat: migrated tank search
---
packages/core/src/blitzkit/index.ts | 14 +-
packages/core/src/blitzkit/tankNames.ts | 3 +-
.../tools/performance/components/Controls.tsx | 2 +-
.../tankopedia/[id]/components/Lighting.tsx | 8 -
.../tankopedia/components/FilterControl.tsx | 372 -------------
.../tankopedia/components/TierFilter.tsx | 51 --
packages/website-legacy/tsconfig.json | 6 +-
packages/website/src/components/Ad.tsx | 4 +-
.../src/components/AdMidSectionNarrowOnly.tsx | 2 +-
.../src/components/AdMidSectionResponsive.tsx | 2 +-
.../src/components/AdMidSectionWideOnly.tsx | 2 +-
.../src/components/ConsumablesManager.tsx | 2 +-
packages/website/src/components/Navbar.astro | 2 -
.../website/src/components/PageWrapper.tsx | 4 +-
.../website/src/components/Speedometer.tsx | 4 +-
.../TankSearch/components/FilterControl.tsx | 496 ++++++++++++++++++
.../TankSearch}/components/NoResults.tsx | 10 +-
.../TankSearch}/components/QuickLink.tsx | 8 +-
.../TankSearch}/components/SearchBar.tsx | 39 +-
.../components/SkeletonTankCard.tsx | 2 +-
.../TankSearch}/components/Sort.tsx | 207 ++++----
.../TankSearch}/components/TankCard.css.ts | 0
.../TankSearch}/components/TankCard.tsx | 26 +-
.../components/TankCardWrapper.tsx | 2 +-
.../TankSearch/components/TierFilter.tsx | 60 +++
.../src}/components/TankSearch/constants.tsx | 2 +-
.../src}/components/TankSearch/index.tsx | 88 ++--
.../src/constants/lightingEnvironments.ts | 7 +
packages/website/src/hooks/useIntersection.ts | 2 +-
packages/website/src/layouts/ToolLayout.astro | 66 +++
packages/website/src/pages/TestComponent.tsx | 21 -
packages/website/src/pages/test.astro | 16 -
.../src/pages/tools/tankopedia/index.astro | 8 +
packages/website/src/stores/tankFilters.ts | 28 +-
.../src/stores/tankopediaPersistent/index.ts | 2 +-
packages/website/src/stores/tankopediaSort.ts | 18 +-
36 files changed, 868 insertions(+), 718 deletions(-)
delete mode 100644 packages/website-legacy/src/app/tools/tankopedia/components/FilterControl.tsx
delete mode 100644 packages/website-legacy/src/app/tools/tankopedia/components/TierFilter.tsx
create mode 100644 packages/website/src/components/TankSearch/components/FilterControl.tsx
rename packages/{website-legacy/src/app/tools/tankopedia => website/src/components/TankSearch}/components/NoResults.tsx (69%)
rename packages/{website-legacy/src/app/tools/tankopedia => website/src/components/TankSearch}/components/QuickLink.tsx (68%)
rename packages/{website-legacy/src/app/tools/tankopedia => website/src/components/TankSearch}/components/SearchBar.tsx (59%)
rename packages/{website-legacy/src/app/tools/tankopedia => website/src/components/TankSearch}/components/SkeletonTankCard.tsx (89%)
rename packages/{website-legacy/src/app/tools/tankopedia => website/src/components/TankSearch}/components/Sort.tsx (52%)
rename packages/{website-legacy/src/app/tools/tankopedia => website/src/components/TankSearch}/components/TankCard.css.ts (100%)
rename packages/{website-legacy/src/app/tools/tankopedia => website/src/components/TankSearch}/components/TankCard.tsx (90%)
rename packages/{website-legacy/src/app/tools/tankopedia => website/src/components/TankSearch}/components/TankCardWrapper.tsx (79%)
create mode 100644 packages/website/src/components/TankSearch/components/TierFilter.tsx
rename packages/{website-legacy/src/app/tools/tankopedia => website/src}/components/TankSearch/constants.tsx (65%)
rename packages/{website-legacy/src/app/tools/tankopedia => website/src}/components/TankSearch/index.tsx (86%)
create mode 100644 packages/website/src/constants/lightingEnvironments.ts
create mode 100644 packages/website/src/layouts/ToolLayout.astro
delete mode 100644 packages/website/src/pages/TestComponent.tsx
delete mode 100644 packages/website/src/pages/test.astro
create mode 100644 packages/website/src/pages/tools/tankopedia/index.astro
diff --git a/packages/core/src/blitzkit/index.ts b/packages/core/src/blitzkit/index.ts
index 21a33644a..e2e4d995e 100644
--- a/packages/core/src/blitzkit/index.ts
+++ b/packages/core/src/blitzkit/index.ts
@@ -1,5 +1,5 @@
export * from './assertSecret';
-// export * from './asset';
+export * from './asset';
// export * from './availableProvisions';
// export * from './averageDefinitions';
// export * from './camouflageDefinitions';
@@ -24,14 +24,14 @@ export * from './gameDefinitions';
// export * from './isExplosive';
// export * from './linkBlitzAndDiscord';
// export * from './mapDefinitions';
-// export * from './modelDefinitions';
-// export * from './normalizeBoundingBox';
+export * from './modelDefinitions';
+export * from './normalizeBoundingBox';
// export * from './parseBkni';
// export * from './patientFetch';
// export * from './patientFetchJSON';
// export * from './progressiveStat';
// export * from './provisionDefinitions';
-// export * from './resolveDpm';
+export * from './resolveDpm';
// export * from './resolvePenetrationCoefficient';
// export * from './reviews';
// export * from './skillDefinitions';
@@ -39,9 +39,9 @@ export * from './gameDefinitions';
export * from './superCompress';
export * from './superDecompress';
export * from './tankDefinitions';
-// export * from './tankIcon';
-// export * from './tankNames';
-// export * from './unionBoundingBox';
+export * from './tankIcon';
+export * from './tankNames';
+export * from './unionBoundingBox';
// export * from './uniqueGuns';
// export * from './unwrapBlitzkitResponse';
// export * from './votes';
diff --git a/packages/core/src/blitzkit/tankNames.ts b/packages/core/src/blitzkit/tankNames.ts
index 9cfb469c8..65a5b3fdb 100644
--- a/packages/core/src/blitzkit/tankNames.ts
+++ b/packages/core/src/blitzkit/tankNames.ts
@@ -1,5 +1,6 @@
import { deburr } from 'lodash-es';
-import { camouflageDefinitions, tankDefinitionsArray } from '.';
+import { tankDefinitionsArray } from '.';
+import { camouflageDefinitions } from './camouflageDefinitions';
export const tankNames = Promise.all([
tankDefinitionsArray,
diff --git a/packages/website-legacy/src/app/tools/performance/components/Controls.tsx b/packages/website-legacy/src/app/tools/performance/components/Controls.tsx
index 86a34b01d..d77e1d587 100644
--- a/packages/website-legacy/src/app/tools/performance/components/Controls.tsx
+++ b/packages/website-legacy/src/app/tools/performance/components/Controls.tsx
@@ -1,4 +1,4 @@
-import { FilterControl } from '../../tankopedia/components/FilterControl';
+import { FilterControl } from '../../../../../../website/src/components/TankSearch/components/FilterControl';
import { PlayerCountControl } from './PlayerCountControl';
export function Controls() {
diff --git a/packages/website-legacy/src/app/tools/tankopedia/[id]/components/Lighting.tsx b/packages/website-legacy/src/app/tools/tankopedia/[id]/components/Lighting.tsx
index dc2b21685..077a7e628 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/[id]/components/Lighting.tsx
+++ b/packages/website-legacy/src/app/tools/tankopedia/[id]/components/Lighting.tsx
@@ -3,14 +3,6 @@ import { useModel } from '../../../../../hooks/useModel';
import * as Duel from '../../../../../stores/duel';
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
-export const ENVIRONMENTS = [
- 'lobby',
- 'apartment',
- 'warehouse',
- 'dawn',
- 'sunset',
-] as const;
-
export function Lighting() {
const protagonist = Duel.use((state) => state.protagonist);
const { hasPbr } = useModel(protagonist.tank.id);
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/FilterControl.tsx b/packages/website-legacy/src/app/tools/tankopedia/components/FilterControl.tsx
deleted file mode 100644
index 7aeee58fa..000000000
--- a/packages/website-legacy/src/app/tools/tankopedia/components/FilterControl.tsx
+++ /dev/null
@@ -1,372 +0,0 @@
-import {
- asset,
- gameDefinitions,
- TANK_CLASSES,
- Tier,
- TIER_ROMAN_NUMERALS,
-} from '@blitzkit/core';
-import strings from '@blitzkit/core/lang/en-US.json';
-import { ReloadIcon } from '@radix-ui/react-icons';
-import { Box, Flex, IconButton, Text, Tooltip } from '@radix-ui/themes';
-import { times } from 'lodash-es';
-import { use } from 'react';
-import { classIcons } from '../../../../components/ClassIcon';
-import { ExperimentIcon } from '../../../../components/ExperimentIcon';
-import { ResearchedIcon } from '../../../../components/ResearchedIcon';
-import { ScienceIcon } from '../../../../components/ScienceIcon';
-import { ScienceOffIcon } from '../../../../components/ScienceOffIcon';
-import * as TankFilters from '../../../../stores/tankFilters';
-
-interface FilterControlProps {
- compact?: boolean;
-}
-
-export function FilterControl({ compact }: FilterControlProps) {
- const awaitedGameDefinitions = use(gameDefinitions);
- const filters = TankFilters.use();
- const mutateTankFilters = TankFilters.useMutation();
- const tankFiltersStore = TankFilters.useStore();
-
- return (
-
-
-
- {times(5, (index) => {
- const tier = (index + 1) as Tier;
- const selected = filters.tiers?.includes(tier);
-
- return (
-
-
- mutateTankFilters((draft) => {
- if (draft.tiers?.includes(tier)) {
- draft.tiers = draft.tiers?.filter((t) => t !== tier);
- } else {
- draft.tiers = [...(draft.tiers ?? []), tier];
- }
- })
- }
- >
- {TIER_ROMAN_NUMERALS[tier]}
-
-
- );
- })}
-
-
- {times(5, (index) => {
- const tier = (index + 6) as Tier;
- const selected = filters.tiers?.includes(tier);
-
- return (
-
-
- mutateTankFilters((draft) => {
- if (draft.tiers?.includes(tier)) {
- draft.tiers = draft.tiers?.filter((t) => t !== tier);
- } else {
- draft.tiers = [...(draft.tiers ?? []), tier];
- }
- })
- }
- >
- {TIER_ROMAN_NUMERALS[tier]}
-
-
- );
- })}
-
-
-
-
-
- {awaitedGameDefinitions.nations
- .slice(0, Math.ceil(awaitedGameDefinitions.nations.length / 2))
- .map((nation) => {
- const selected = filters.nations?.includes(nation);
-
- return (
-
-
- mutateTankFilters((draft) => {
- if (draft.nations?.includes(nation)) {
- draft.nations = draft.nations?.filter(
- (n) => n !== nation,
- );
- } else {
- draft.nations = [...(draft.nations ?? []), nation];
- }
- })
- }
- >
-
-
-
- );
- })}
-
-
- {awaitedGameDefinitions.nations
- .slice(Math.ceil(awaitedGameDefinitions.nations.length / 2))
- .map((nation) => {
- const selected = filters.nations?.includes(nation);
-
- return (
-
-
- mutateTankFilters((draft) => {
- if (draft.nations?.includes(nation)) {
- draft.nations = draft.nations?.filter(
- (n) => n !== nation,
- );
- } else {
- draft.nations = [...(draft.nations ?? []), nation];
- }
- })
- }
- >
-
-
-
- );
- })}
-
-
-
-
- {TANK_CLASSES.map((tankClass) => {
- const Icon = classIcons[tankClass];
- const selected = filters.classes?.includes(tankClass);
-
- return (
-
-
- mutateTankFilters((draft) => {
- if (draft.classes?.includes(tankClass)) {
- draft.classes = draft.classes?.filter(
- (c) => c !== tankClass,
- );
- } else {
- draft.classes = [...(draft.classes ?? []), tankClass];
- }
- })
- }
- >
-
-
-
- );
- })}
-
-
-
-
-
- mutateTankFilters((draft) => {
- if (draft.types?.includes('researchable')) {
- draft.types = draft.types?.filter(
- (t) => t !== 'researchable',
- );
- } else {
- draft.types = [...(draft.types ?? []), 'researchable'];
- }
- })
- }
- >
-
-
-
-
-
- mutateTankFilters((draft) => {
- if (draft.types?.includes('premium')) {
- draft.types = draft.types?.filter((t) => t !== 'premium');
- } else {
- draft.types = [...(draft.types ?? []), 'premium'];
- }
- })
- }
- >
-
-
-
-
-
-
-
- mutateTankFilters((draft) => {
- if (draft.types?.includes('collector')) {
- draft.types = draft.types?.filter((t) => t !== 'collector');
- } else {
- draft.types = [...(draft.types ?? []), 'collector'];
- }
- })
- }
- >
-
-
-
-
-
-
-
-
-
-
- mutateTankFilters((draft) => {
- draft.testing = 'include';
- })
- }
- >
-
-
-
-
-
- mutateTankFilters((draft) => {
- draft.testing = 'exclude';
- })
- }
- >
-
-
-
-
-
- mutateTankFilters((draft) => {
- draft.testing = 'only';
- })
- }
- >
-
-
-
-
-
-
- {
- tankFiltersStore.setState(tankFiltersStore.getInitialState(), true);
- }}
- >
-
-
-
-
- );
-}
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/TierFilter.tsx b/packages/website-legacy/src/app/tools/tankopedia/components/TierFilter.tsx
deleted file mode 100644
index 82f25238b..000000000
--- a/packages/website-legacy/src/app/tools/tankopedia/components/TierFilter.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Tier, TIER_ROMAN_NUMERALS } from '@blitzkit/core';
-import { Flex, IconButton, Text } from '@radix-ui/themes';
-import { times } from 'lodash-es';
-import { memo } from 'react';
-import * as TankFilters from '../../../../stores/tankFilters';
-import * as TankopediaSort from '../../../../stores/tankopediaSort';
-
-export const TierFilter = memo(() => {
- const tierFilter = TankFilters.use((state) => state.tiers);
- const sort = TankopediaSort.use();
- const search = TankFilters.use((state) => state.search);
- const mutateTankFilters = TankFilters.useMutation();
-
- if (sort.by !== 'meta.none' || search) return null;
-
- return (
-
-
- {times(10, (index) => {
- const tier = (10 - index) as Tier;
- const selected = tierFilter.includes(tier);
-
- return (
-
- mutateTankFilters((draft) => {
- if (draft.tiers?.includes(tier)) {
- draft.tiers = draft.tiers?.filter((t) => t !== tier);
- } else {
- draft.tiers = [...(draft.tiers ?? []), tier];
- }
- })
- }
- >
- {TIER_ROMAN_NUMERALS[tier]}
-
- );
- })}
-
-
- );
-});
diff --git a/packages/website-legacy/tsconfig.json b/packages/website-legacy/tsconfig.json
index 6e8705550..b22de154d 100644
--- a/packages/website-legacy/tsconfig.json
+++ b/packages/website-legacy/tsconfig.json
@@ -7,5 +7,9 @@
}
]
},
- "include": ["src/**/*", ".next/types/**/*.ts"]
+ "include": [
+ "src/**/*",
+ ".next/types/**/*.ts",
+ "../website/src/components/TankSearch/components/FilterControl.tsx"
+ ]
}
diff --git a/packages/website/src/components/Ad.tsx b/packages/website/src/components/Ad.tsx
index 57921965e..ae5d455a5 100644
--- a/packages/website/src/components/Ad.tsx
+++ b/packages/website/src/components/Ad.tsx
@@ -1,8 +1,8 @@
import { imgur } from '@blitzkit/core';
-import { Box, BoxProps, Text } from '@radix-ui/themes';
+import { Box, type BoxProps, Text } from '@radix-ui/themes';
import { uniqueId } from 'lodash-es';
import { useEffect, useRef } from 'react';
-import { Vector2Tuple } from 'three';
+import type { Vector2Tuple } from 'three';
export enum AdType {
MediumRectangleHorizontalPurple = 738182777,
diff --git a/packages/website/src/components/AdMidSectionNarrowOnly.tsx b/packages/website/src/components/AdMidSectionNarrowOnly.tsx
index 8d5361fe3..ee53813fb 100644
--- a/packages/website/src/components/AdMidSectionNarrowOnly.tsx
+++ b/packages/website/src/components/AdMidSectionNarrowOnly.tsx
@@ -1,4 +1,4 @@
-import { Flex, FlexProps } from '@radix-ui/themes';
+import { Flex, type FlexProps } from '@radix-ui/themes';
import { Ad, AdType } from './Ad';
export function AdMidSectionNarrowOnly(props: FlexProps) {
diff --git a/packages/website/src/components/AdMidSectionResponsive.tsx b/packages/website/src/components/AdMidSectionResponsive.tsx
index 5bd1a39b0..2ec38d2e1 100644
--- a/packages/website/src/components/AdMidSectionResponsive.tsx
+++ b/packages/website/src/components/AdMidSectionResponsive.tsx
@@ -1,4 +1,4 @@
-import { Flex, FlexProps } from '@radix-ui/themes';
+import { Flex, type FlexProps } from '@radix-ui/themes';
import { Ad, AdType } from './Ad';
export function AdMidSectionResponsive(props: FlexProps) {
diff --git a/packages/website/src/components/AdMidSectionWideOnly.tsx b/packages/website/src/components/AdMidSectionWideOnly.tsx
index 960534842..cc828043c 100644
--- a/packages/website/src/components/AdMidSectionWideOnly.tsx
+++ b/packages/website/src/components/AdMidSectionWideOnly.tsx
@@ -1,4 +1,4 @@
-import { Flex, FlexProps } from '@radix-ui/themes';
+import { Flex, type FlexProps } from '@radix-ui/themes';
import { Ad, AdType } from './Ad';
export function AdMidSectionWideOnly(props: FlexProps) {
diff --git a/packages/website/src/components/ConsumablesManager.tsx b/packages/website/src/components/ConsumablesManager.tsx
index 1ed88ae70..e10b8ba20 100644
--- a/packages/website/src/components/ConsumablesManager.tsx
+++ b/packages/website/src/components/ConsumablesManager.tsx
@@ -1,5 +1,5 @@
import { ConsumableEntry } from '@blitzkit/core';
-import { Flex, FlexProps } from '@radix-ui/themes';
+import { Flex, type FlexProps } from '@radix-ui/themes';
import { ConsumableButton } from './ModuleButtons/ConsumableButton';
type ConsumablesManagerProps = FlexProps & {
diff --git a/packages/website/src/components/Navbar.astro b/packages/website/src/components/Navbar.astro
index d14ccb2dc..f795dc023 100644
--- a/packages/website/src/components/Navbar.astro
+++ b/packages/website/src/components/Navbar.astro
@@ -18,11 +18,9 @@ import {
Text,
} from '@radix-ui/themes';
import { BRANCH_NAMES } from '../constants/branches';
-import { NAVBAR_HEIGHT } from '../constants/navbar';
import { homeTool, TOOLS } from '../constants/tools';
import { PatreonIcon } from './PatreonIcon';
import { NavbarAccount } from './NavbarAccount';
-import { TestComponent } from '../pages/TestComponent';
const toolsFiltered = TOOLS.filter((tool) => !('href' in tool));
const tools = [homeTool, ...toolsFiltered];
diff --git a/packages/website/src/components/PageWrapper.tsx b/packages/website/src/components/PageWrapper.tsx
index a1f451f31..21463f0c2 100644
--- a/packages/website/src/components/PageWrapper.tsx
+++ b/packages/website/src/components/PageWrapper.tsx
@@ -1,6 +1,6 @@
-import { Flex, FlexProps, Theme } from '@radix-ui/themes';
+import { Flex, type FlexProps, Theme } from '@radix-ui/themes';
import type { ComponentProps } from 'react';
-import { RadixSize } from '../../../website-legacy/src/stores/embedState';
+import type { RadixSize } from '../stores/embedState';
type PageWrapperProps = FlexProps & {
color?: ComponentProps['accentColor'];
diff --git a/packages/website/src/components/Speedometer.tsx b/packages/website/src/components/Speedometer.tsx
index 8146622a6..02680ca7d 100644
--- a/packages/website/src/components/Speedometer.tsx
+++ b/packages/website/src/components/Speedometer.tsx
@@ -1,5 +1,5 @@
-import { Flex, FlexProps, Text } from '@radix-ui/themes';
-import { ReactNode } from 'react';
+import { Flex, type FlexProps, Text } from '@radix-ui/themes';
+import type { ReactNode } from 'react';
type SpeedometerProps = FlexProps & {
value: ReactNode;
diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx
new file mode 100644
index 000000000..ea5b7334a
--- /dev/null
+++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx
@@ -0,0 +1,496 @@
+import {
+ asset,
+ gameDefinitions,
+ TANK_CLASSES,
+ TIER_ROMAN_NUMERALS,
+ type Tier,
+} from '@blitzkit/core';
+import strings from '@blitzkit/core/lang/en-US.json';
+import { useStore } from '@nanostores/react';
+import { ReloadIcon } from '@radix-ui/react-icons';
+import { Box, Flex, IconButton, Text, Tooltip } from '@radix-ui/themes';
+import { times } from 'lodash-es';
+import { useAwait } from '../../../hooks/useAwait';
+import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
+import { classIcons } from '../../ClassIcon';
+import { ExperimentIcon } from '../../ExperimentIcon';
+import { ResearchedIcon } from '../../ResearchedIcon';
+import { ScienceIcon } from '../../ScienceIcon';
+import { ScienceOffIcon } from '../../ScienceOffIcon';
+
+interface FilterControlProps {
+ compact?: boolean;
+}
+
+export function FilterControl({ compact }: FilterControlProps) {
+ const awaitedGameDefinitions = useAwait(gameDefinitions);
+ const tankFilters = useStore($tankFilters);
+
+ return (
+
+
+
+ {times(5, (index) => {
+ const tier = (index + 1) as Tier;
+ const selected = tankFilters.tiers?.includes(tier);
+
+ return (
+
+
+ // mutateTankFilters((draft) => {
+ // if (draft.tiers?.includes(tier)) {
+ // draft.tiers = draft.tiers?.filter((t) => t !== tier);
+ // } else {
+ // draft.tiers = [...(draft.tiers ?? []), tier];
+ // }
+ // })
+
+ {
+ if (tankFilters.tiers.includes(tier)) {
+ $tankFilters.setKey(
+ 'tiers',
+ tankFilters.tiers.filter((t) => t !== tier),
+ );
+ } else {
+ $tankFilters.setKey('tiers', [
+ ...tankFilters.tiers,
+ tier,
+ ]);
+ }
+ }
+ }
+ >
+ {TIER_ROMAN_NUMERALS[tier]}
+
+
+ );
+ })}
+
+
+ {times(5, (index) => {
+ const tier = (index + 6) as Tier;
+ const selected = tankFilters.tiers?.includes(tier);
+
+ return (
+
+
+ // mutateTankFilters((draft) => {
+ // if (draft.tiers?.includes(tier)) {
+ // draft.tiers = draft.tiers?.filter((t) => t !== tier);
+ // } else {
+ // draft.tiers = [...(draft.tiers ?? []), tier];
+ // }
+ // })
+ {
+ if (tankFilters.tiers.includes(tier)) {
+ $tankFilters.setKey(
+ 'tiers',
+ tankFilters.tiers.filter((t) => t !== tier),
+ );
+ } else {
+ $tankFilters.setKey('tiers', [
+ ...tankFilters.tiers,
+ tier,
+ ]);
+ }
+ }
+ }
+ >
+ {TIER_ROMAN_NUMERALS[tier]}
+
+
+ );
+ })}
+
+
+
+
+
+ {awaitedGameDefinitions.nations
+ .slice(0, Math.ceil(awaitedGameDefinitions.nations.length / 2))
+ .map((nation) => {
+ const selected = tankFilters.nations?.includes(nation);
+
+ return (
+
+
+ // mutateTankFilters((draft) => {
+ // if (draft.nations?.includes(nation)) {
+ // draft.nations = draft.nations?.filter(
+ // (n) => n !== nation,
+ // );
+ // } else {
+ // draft.nations = [...(draft.nations ?? []), nation];
+ // }
+ // })
+ {
+ if (tankFilters.nations.includes(nation)) {
+ $tankFilters.setKey(
+ 'nations',
+ tankFilters.nations.filter((n) => n !== nation),
+ );
+ } else {
+ $tankFilters.setKey('nations', [
+ ...tankFilters.nations,
+ nation,
+ ]);
+ }
+ }
+ }
+ >
+
+
+
+ );
+ })}
+
+
+ {awaitedGameDefinitions.nations
+ .slice(Math.ceil(awaitedGameDefinitions.nations.length / 2))
+ .map((nation) => {
+ const selected = tankFilters.nations?.includes(nation);
+
+ return (
+
+
+ // mutateTankFilters((draft) => {
+ // if (draft.nations?.includes(nation)) {
+ // draft.nations = draft.nations?.filter(
+ // (n) => n !== nation,
+ // );
+ // } else {
+ // draft.nations = [...(draft.nations ?? []), nation];
+ // }
+ // })
+ {
+ if (tankFilters.nations.includes(nation)) {
+ $tankFilters.setKey(
+ 'nations',
+ tankFilters.nations.filter((n) => n !== nation),
+ );
+ } else {
+ $tankFilters.setKey('nations', [
+ ...tankFilters.nations,
+ nation,
+ ]);
+ }
+ }
+ }
+ >
+
+
+
+ );
+ })}
+
+
+
+
+ {TANK_CLASSES.map((tankClass) => {
+ const Icon = classIcons[tankClass];
+ const selected = tankFilters.classes?.includes(tankClass);
+
+ return (
+
+
+ // mutateTankFilters((draft) => {
+ // if (draft.classes?.includes(tankClass)) {
+ // draft.classes = draft.classes?.filter(
+ // (c) => c !== tankClass,
+ // );
+ // } else {
+ // draft.classes = [...(draft.classes ?? []), tankClass];
+ // }
+ // })
+ {
+ if (tankFilters.classes.includes(tankClass)) {
+ $tankFilters.setKey(
+ 'classes',
+ tankFilters.classes.filter((c) => c !== tankClass),
+ );
+ } else {
+ $tankFilters.setKey('classes', [
+ ...tankFilters.classes,
+ tankClass,
+ ]);
+ }
+ }
+ }
+ >
+
+
+
+ );
+ })}
+
+
+
+
+
+ // mutateTankFilters((draft) => {
+ // if (draft.types?.includes('researchable')) {
+ // draft.types = draft.types?.filter(
+ // (t) => t !== 'researchable',
+ // );
+ // } else {
+ // draft.types = [...(draft.types ?? []), 'researchable'];
+ // }
+ // })
+ {
+ if (tankFilters.types.includes('researchable')) {
+ $tankFilters.setKey(
+ 'types',
+ tankFilters.types.filter((t) => t !== 'researchable'),
+ );
+ } else {
+ $tankFilters.setKey('types', [
+ ...tankFilters.types,
+ 'researchable',
+ ]);
+ }
+ }
+ }
+ >
+
+
+
+
+
+ // mutateTankFilters((draft) => {
+ // if (draft.types?.includes('premium')) {
+ // draft.types = draft.types?.filter((t) => t !== 'premium');
+ // } else {
+ // draft.types = [...(draft.types ?? []), 'premium'];
+ // }
+ // })
+ {
+ if (tankFilters.types.includes('premium')) {
+ $tankFilters.setKey(
+ 'types',
+ tankFilters.types.filter((t) => t !== 'premium'),
+ );
+ } else {
+ $tankFilters.setKey('types', [
+ ...tankFilters.types,
+ 'premium',
+ ]);
+ }
+ }
+ }
+ >
+
+
+
+
+
+
+
+ // mutateTankFilters((draft) => {
+ // if (draft.types?.includes('collector')) {
+ // draft.types = draft.types?.filter((t) => t !== 'collector');
+ // } else {
+ // draft.types = [...(draft.types ?? []), 'collector'];
+ // }
+ // })
+ {
+ if (tankFilters.types.includes('collector')) {
+ $tankFilters.setKey(
+ 'types',
+ tankFilters.types.filter((t) => t !== 'collector'),
+ );
+ } else {
+ $tankFilters.setKey('types', [
+ ...tankFilters.types,
+ 'collector',
+ ]);
+ }
+ }
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+ // mutateTankFilters((draft) => {
+ // draft.testing = 'include';
+ // })
+ {
+ $tankFilters.setKey('testing', 'include');
+ }
+ }
+ >
+
+
+
+
+
+ // mutateTankFilters((draft) => {
+ // draft.testing = 'exclude';
+ // })
+ {
+ $tankFilters.setKey('testing', 'exclude');
+ }
+ }
+ >
+
+
+
+
+
+ // mutateTankFilters((draft) => {
+ // draft.testing = 'only';
+ // })
+ {
+ $tankFilters.setKey('testing', 'only');
+ }
+ }
+ >
+
+
+
+
+
+
+ {
+ // tankFiltersStore.setState(tankFiltersStore.getInitialState(), true);
+ $tankFilters.set(initialTankFilters);
+ }}
+ >
+
+
+
+
+ );
+}
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/NoResults.tsx b/packages/website/src/components/TankSearch/components/NoResults.tsx
similarity index 69%
rename from packages/website-legacy/src/app/tools/tankopedia/components/NoResults.tsx
rename to packages/website/src/components/TankSearch/components/NoResults.tsx
index 2082e60ac..78694928a 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/components/NoResults.tsx
+++ b/packages/website/src/components/TankSearch/components/NoResults.tsx
@@ -1,13 +1,11 @@
import { Flex, Link, Text } from '@radix-ui/themes';
-import * as TankFilters from '../../../../stores/tankFilters';
+import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
interface NoResultsProps {
type?: 'filters' | 'search';
}
export function NoResults({ type = 'filters' }: NoResultsProps) {
- const tankFiltersStore = TankFilters.useStore();
-
return (
@@ -16,9 +14,9 @@ export function NoResults({ type = 'filters' }: NoResultsProps) {
href="#"
underline="always"
color="red"
- onClick={() =>
- tankFiltersStore.setState(tankFiltersStore.getInitialState(), true)
- }
+ onClick={() => {
+ $tankFilters.set(initialTankFilters);
+ }}
>
{type === 'filters' ? 'Try clearing filters' : 'Try searching again'}
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/QuickLink.tsx b/packages/website/src/components/TankSearch/components/QuickLink.tsx
similarity index 68%
rename from packages/website-legacy/src/app/tools/tankopedia/components/QuickLink.tsx
rename to packages/website/src/components/TankSearch/components/QuickLink.tsx
index 6d408f2ad..2839c066e 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/components/QuickLink.tsx
+++ b/packages/website/src/components/TankSearch/components/QuickLink.tsx
@@ -1,17 +1,17 @@
import { type TankDefinition } from '@blitzkit/core';
+import { useStore } from '@nanostores/react';
import { CaretRightIcon } from '@radix-ui/react-icons';
import { Button, Link, TextField } from '@radix-ui/themes';
-import * as TankFilters from '../../../../stores/tankFilters';
+import { $tankFilters } from '../../../stores/tankFilters';
interface QuickLinkProps {
topResult?: TankDefinition;
}
export function QuickLink({ topResult }: QuickLinkProps) {
- const search = TankFilters.use((state) => state.search);
- const searching = TankFilters.use((state) => state.searching);
+ const tankFilters = useStore($tankFilters);
- if (!search || !topResult || searching) return null;
+ if (!tankFilters.search || !topResult || tankFilters.searching) return null;
return (
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/SearchBar.tsx b/packages/website/src/components/TankSearch/components/SearchBar.tsx
similarity index 59%
rename from packages/website-legacy/src/app/tools/tankopedia/components/SearchBar.tsx
rename to packages/website/src/components/TankSearch/components/SearchBar.tsx
index 0472d7a5e..8cb1d5484 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/components/SearchBar.tsx
+++ b/packages/website/src/components/TankSearch/components/SearchBar.tsx
@@ -1,11 +1,12 @@
-import { TankDefinition } from '@blitzkit/core';
+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 { useRouter } from 'next/navigation';
-import { KeyboardEventHandler, useCallback, useRef } from 'react';
-import * as TankFilters from '../../../../stores/tankFilters';
+import { type KeyboardEventHandler, useCallback, useRef } from 'react';
import { QuickLink } from './QuickLink';
+import { $tankFilters } from '../../../stores/tankFilters';
import { Sort } from './Sort';
interface SearchBarProps {
@@ -14,35 +15,41 @@ interface SearchBarProps {
}
export function SearchBar({ topResult, onSelect }: SearchBarProps) {
- const tankFiltersStore = TankFilters.useStore();
- const lastSearch = tankFiltersStore.getState().search;
+ const tankFilters = useStore($tankFilters);
const router = useRouter();
const input = useRef(null);
- const searching = TankFilters.use((state) => state.searching);
const performSearch = useCallback(
debounce(() => {
- tankFiltersStore.setState({ searching: false });
+ // tankFiltersStore.setState({ searching: false });
+ $tankFilters.setKey('searching', false);
if (!input.current) return;
const sanitized = input.current.value.trim();
- tankFiltersStore.setState({
- search: sanitized.length === 0 ? undefined : sanitized,
- });
+ // tankFiltersStore.setState({
+ // search: sanitized.length === 0 ? undefined : sanitized,
+ // });
+ $tankFilters.setKey(
+ 'search',
+ sanitized.length === 0 ? undefined : sanitized,
+ );
}, 500),
[],
);
const handleChange = useCallback(() => {
- if (!tankFiltersStore.getState().searching) {
- tankFiltersStore.setState({ searching: true });
+ // if (!tankFiltersStore.getState().searching) {
+ // tankFiltersStore.setState({ searching: true });
+ // }
+ if (!tankFilters.searching) {
+ $tankFilters.setKey('searching', true);
}
performSearch();
- }, []);
+ }, [tankFilters]);
const handleKeyDown = useCallback(
(event) => {
- if (event.key !== 'Enter' || !topResult || searching) return;
+ if (event.key !== 'Enter' || !topResult || tankFilters.searching) return;
event.preventDefault();
@@ -59,7 +66,7 @@ export function SearchBar({ topResult, onSelect }: SearchBarProps) {
- {searching ? : }
+ {tankFilters.searching ? : }
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/SkeletonTankCard.tsx b/packages/website/src/components/TankSearch/components/SkeletonTankCard.tsx
similarity index 89%
rename from packages/website-legacy/src/app/tools/tankopedia/components/SkeletonTankCard.tsx
rename to packages/website/src/components/TankSearch/components/SkeletonTankCard.tsx
index a955951fc..a60ff849c 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/components/SkeletonTankCard.tsx
+++ b/packages/website/src/components/TankSearch/components/SkeletonTankCard.tsx
@@ -1,6 +1,6 @@
import { Flex, Skeleton } from '@radix-ui/themes';
import { memo, useRef } from 'react';
-import { useIntersection } from '../../../../hooks/useIntersection';
+import { useIntersection } from '../../../hooks/useIntersection';
interface SkeletonTankCardProps {
onIntersection?: () => void;
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/Sort.tsx b/packages/website/src/components/TankSearch/components/Sort.tsx
similarity index 52%
rename from packages/website-legacy/src/app/tools/tankopedia/components/Sort.tsx
rename to packages/website/src/components/TankSearch/components/Sort.tsx
index 2a0db3ff1..329c41724 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/components/Sort.tsx
+++ b/packages/website/src/components/TankSearch/components/Sort.tsx
@@ -1,11 +1,11 @@
+import { useStore } from '@nanostores/react';
import { CaretDownIcon } from '@radix-ui/react-icons';
import { Button, DropdownMenu } from '@radix-ui/themes';
-import { TankopediaSortDirection } from '../../../../stores/tankopediaPersistent';
-import * as TankopediaSort from '../../../../stores/tankopediaSort';
+import type { TankopediaSortDirection } from '../../../stores/tankopediaPersistent';
+import { $tankopediaSort } from '../../../stores/tankopediaSort';
export function Sort() {
- const sort = TankopediaSort.use();
- const mutateTankopediaSort = TankopediaSort.useMutation();
+ const tankopediaSort = useStore($tankopediaSort);
return (
@@ -18,11 +18,12 @@ export function Sort() {
{
- mutateTankopediaSort((draft) => {
- draft.by = 'meta.none';
- });
+ // mutateTankopediaSort((draft) => {
+ // draft.by = 'meta.none';
+ // });
+ $tankopediaSort.setKey('by', 'meta.none');
}}
>
No sort
@@ -34,131 +35,123 @@ export function Sort() {
Fire
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.dpm';
- });
+ // mutateTankopediaSort((draft) => {
+ // draft.by = 'fire.dpm';
+ // });
+ $tankopediaSort.setKey('by', 'fire.dpm');
}}
>
Standard DPM
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.dpmPremium';
- });
+ // mutateTankopediaSort((draft) => {
+ // draft.by = 'fire.dpmPremium';
+ // });
+ $tankopediaSort.setKey('by', 'fire.dpmPremium');
}}
>
Premium DPM
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.reload';
- });
+ // mutateTankopediaSort((draft) => {
+ // draft.by = 'fire.reload';
+ // });
+ $tankopediaSort.setKey('by', 'fire.reload');
}}
>
Reload
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.caliber';
- });
+ // mutateTankopediaSort((draft) => {
+ // draft.by = 'fire.caliber';
+ // });
+ $tankopediaSort.setKey('by', 'fire.caliber');
}}
>
Caliber
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.standardPenetration';
- });
+ // mutateTankopediaSort((draft) => {
+ // draft.by = 'fire.standardPenetration';
+ // });
+ $tankopediaSort.setKey('by', 'fire.standardPenetration');
}}
>
Standard penetration
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.premiumPenetration';
- });
+ // mutateTankopediaSort((draft) => {
+ // draft.by = 'fire.premiumPenetration';
+ // });
+ $tankopediaSort.setKey('by', 'fire.premiumPenetration');
}}
>
Premium penetration
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.damage';
- });
+ $tankopediaSort.setKey('by', 'fire.damage');
}}
>
Damage
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.shellVelocity';
- });
+ $tankopediaSort.setKey('by', 'fire.shellVelocity');
}}
>
Shell velocity
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.aimTime';
- });
+ $tankopediaSort.setKey('by', 'fire.aimTime');
}}
>
Aim time
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.dispersionStill';
- });
+ $tankopediaSort.setKey('by', 'fire.dispersionStill');
}}
>
Dispersion still
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.dispersionMoving';
- });
+ $tankopediaSort.setKey('by', 'fire.dispersionMoving');
}}
>
Dispersion moving
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.gunDepression';
- });
+ $tankopediaSort.setKey('by', 'fire.gunDepression');
}}
>
Gun depression
{
- mutateTankopediaSort((draft) => {
- draft.by = 'fire.gunElevation';
- });
+ $tankopediaSort.setKey('by', 'fire.gunElevation');
}}
>
Gun elevation
@@ -170,61 +163,49 @@ export function Sort() {
Maneuverability
{
- mutateTankopediaSort((draft) => {
- draft.by = 'maneuverability.forwardsSpeed';
- });
+ $tankopediaSort.setKey('by', 'maneuverability.forwardsSpeed');
}}
>
Forwards speed
{
- mutateTankopediaSort((draft) => {
- draft.by = 'maneuverability.backwardsSpeed';
- });
+ $tankopediaSort.setKey('by', 'maneuverability.backwardsSpeed');
}}
>
Backwards speed
{
- mutateTankopediaSort((draft) => {
- draft.by = 'maneuverability.power';
- });
+ $tankopediaSort.setKey('by', 'maneuverability.power');
}}
>
Power
{
- mutateTankopediaSort((draft) => {
- draft.by = 'maneuverability.powerToWeight';
- });
+ $tankopediaSort.setKey('by', 'maneuverability.powerToWeight');
}}
>
Power to weight
{
- mutateTankopediaSort((draft) => {
- draft.by = 'maneuverability.weight';
- });
+ $tankopediaSort.setKey('by', 'maneuverability.weight');
}}
>
Weight
{
- mutateTankopediaSort((draft) => {
- draft.by = 'maneuverability.traverseSpeed';
- });
+ $tankopediaSort.setKey('by', 'maneuverability.traverseSpeed');
}}
>
Traverse speed
@@ -236,71 +217,60 @@ export function Sort() {
Survivability
{
- mutateTankopediaSort((draft) => {
- draft.by = 'survivability.health';
- });
+ $tankopediaSort.setKey('by', 'survivability.health');
}}
>
Health
{
- mutateTankopediaSort((draft) => {
- draft.by = 'survivability.viewRange';
- });
+ $tankopediaSort.setKey('by', 'survivability.viewRange');
}}
>
View range
{
- mutateTankopediaSort((draft) => {
- draft.by = 'survivability.camouflageStill';
- });
+ $tankopediaSort.setKey('by', 'survivability.camouflageStill');
}}
>
Camouflage
{
- mutateTankopediaSort((draft) => {
- draft.by = 'survivability.camouflageMoving';
- });
+ $tankopediaSort.setKey('by', 'survivability.camouflageMoving');
}}
>
Camouflage moving
{
- mutateTankopediaSort((draft) => {
- draft.by = 'survivability.camouflageShooting';
- });
+ $tankopediaSort.setKey(
+ 'by',
+ 'survivability.camouflageShooting',
+ );
}}
>
Camouflage shooting
{
- mutateTankopediaSort((draft) => {
- draft.by = 'survivability.volume';
- });
+ $tankopediaSort.setKey('by', 'survivability.volume');
}}
>
Volume
{
- mutateTankopediaSort((draft) => {
- draft.by = 'survivability.length';
- });
+ $tankopediaSort.setKey('by', 'survivability.length');
}}
>
Length
@@ -310,12 +280,13 @@ export function Sort() {
Direction
- mutateTankopediaSort((draft) => {
- draft.direction = value as TankopediaSortDirection;
- })
- }
+ value={tankopediaSort.direction}
+ onValueChange={(value) => {
+ $tankopediaSort.setKey(
+ 'direction',
+ value as TankopediaSortDirection,
+ );
+ }}
>
Ascending
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/TankCard.css.ts b/packages/website/src/components/TankSearch/components/TankCard.css.ts
similarity index 100%
rename from packages/website-legacy/src/app/tools/tankopedia/components/TankCard.css.ts
rename to packages/website/src/components/TankSearch/components/TankCard.css.ts
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/TankCard.tsx b/packages/website/src/components/TankSearch/components/TankCard.tsx
similarity index 90%
rename from packages/website-legacy/src/app/tools/tankopedia/components/TankCard.tsx
rename to packages/website/src/components/TankSearch/components/TankCard.tsx
index ab5a2d635..11b0393cc 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/components/TankCard.tsx
+++ b/packages/website/src/components/TankSearch/components/TankCard.tsx
@@ -4,17 +4,19 @@ import {
normalizeBoundingBox,
resolveDpm,
resolveNearPenetration,
- TankDefinition,
tankIcon,
unionBoundingBox,
+ type TankDefinition,
} from '@blitzkit/core';
+import { useStore } from '@nanostores/react';
import { Flex, Text } from '@radix-ui/themes';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import Link from 'next/link';
-import { use, useMemo } from 'react';
-import { classIcons } from '../../../../components/ClassIcon';
-import { resolveReload } from '../../../../core/blitzkit/resolveReload';
-import * as TankopediaSort from '../../../../stores/tankopediaSort';
+import { useMemo } from 'react';
+import { resolveReload } from '../../../core/blitzkit/resolveReload';
+import { useAwait } from '../../../hooks/useAwait';
+import { $tankopediaSort } from '../../../stores/tankopediaSort';
+import { classIcons } from '../../ClassIcon';
import * as styles from './TankCard.css';
interface TankCardProps {
@@ -23,11 +25,11 @@ interface TankCardProps {
}
export function TankCard({ tank, onSelect }: TankCardProps) {
- const awaitedModelDefinitions = use(modelDefinitions);
+ const awaitedModelDefinitions = useAwait(modelDefinitions);
const Icon = classIcons[tank.class];
- const sortBy = TankopediaSort.use((state) => state.by);
+ const tankopediaSort = useStore($tankopediaSort);
const discriminator = useMemo(() => {
- if (sortBy.startsWith('meta')) return undefined;
+ if (tankopediaSort.by.startsWith('meta')) return undefined;
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
@@ -39,7 +41,7 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
const gunModelDefinition = turretModelDefinition.guns[gun.id];
- switch (sortBy) {
+ switch (tankopediaSort.by) {
case 'fire.aimTime':
return gun.aimTime.toFixed(2);
case 'fire.caliber':
@@ -131,7 +133,7 @@ export function TankCard({ tank, onSelect }: TankCardProps) {
),
).toFixed(0);
}
- }, [sortBy]);
+ }, [tankopediaSort.by]);
return (
onSelect(tank) : undefined}
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/TankCardWrapper.tsx b/packages/website/src/components/TankSearch/components/TankCardWrapper.tsx
similarity index 79%
rename from packages/website-legacy/src/app/tools/tankopedia/components/TankCardWrapper.tsx
rename to packages/website/src/components/TankSearch/components/TankCardWrapper.tsx
index 183dfa993..ea6cf9dbc 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/components/TankCardWrapper.tsx
+++ b/packages/website/src/components/TankSearch/components/TankCardWrapper.tsx
@@ -1,4 +1,4 @@
-import { Grid, GridProps } from '@radix-ui/themes';
+import { Grid, type GridProps } from '@radix-ui/themes';
export function TankCardWrapper(props: GridProps) {
return (
diff --git a/packages/website/src/components/TankSearch/components/TierFilter.tsx b/packages/website/src/components/TankSearch/components/TierFilter.tsx
new file mode 100644
index 000000000..e8cd36254
--- /dev/null
+++ b/packages/website/src/components/TankSearch/components/TierFilter.tsx
@@ -0,0 +1,60 @@
+import { type Tier, 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 { $tankopediaSort } from '../../../stores/tankopediaSort';
+
+export const TierFilter = memo(() => {
+ const tankFilters = useStore($tankFilters);
+ const tankopediaSort = useStore($tankopediaSort);
+
+ if (tankopediaSort.by !== 'meta.none' || tankFilters.search) return null;
+
+ return (
+
+
+ {times(10, (index) => {
+ const tier = (10 - index) as Tier;
+ const selected = tankFilters.tiers.includes(tier);
+
+ return (
+
+ // mutateTankFilters((draft) => {
+ // if (draft.tiers?.includes(tier)) {
+ // draft.tiers = draft.tiers?.filter((t) => t !== tier);
+ // } else {
+ // draft.tiers = [...(draft.tiers ?? []), tier];
+ // }
+ // })
+ {
+ if (tankFilters.tiers.includes(tier)) {
+ $tankFilters.setKey(
+ 'tiers',
+ tankFilters.tiers.filter((t) => t !== tier),
+ );
+ } else {
+ $tankFilters.setKey('tiers', [...tankFilters.tiers, tier]);
+ }
+ }
+ }
+ >
+ {TIER_ROMAN_NUMERALS[tier]}
+
+ );
+ })}
+
+
+ );
+});
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/TankSearch/constants.tsx b/packages/website/src/components/TankSearch/constants.tsx
similarity index 65%
rename from packages/website-legacy/src/app/tools/tankopedia/components/TankSearch/constants.tsx
rename to packages/website/src/components/TankSearch/constants.tsx
index 6590e55a1..7e0adadb3 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/components/TankSearch/constants.tsx
+++ b/packages/website/src/components/TankSearch/constants.tsx
@@ -1,4 +1,4 @@
-import { TreeType } from '@blitzkit/core';
+import type { TreeType } from '@blitzkit/core';
export const treeTypeOrder: TreeType[] = [
'researchable',
diff --git a/packages/website-legacy/src/app/tools/tankopedia/components/TankSearch/index.tsx b/packages/website/src/components/TankSearch/index.tsx
similarity index 86%
rename from packages/website-legacy/src/app/tools/tankopedia/components/TankSearch/index.tsx
rename to packages/website/src/components/TankSearch/index.tsx
index 0717dd230..f3b52b232 100644
--- a/packages/website-legacy/src/app/tools/tankopedia/components/TankSearch/index.tsx
+++ b/packages/website/src/components/TankSearch/index.tsx
@@ -1,6 +1,5 @@
import {
TANK_CLASSES,
- TankDefinition,
gameDefinitions,
modelDefinitions,
normalizeBoundingBox,
@@ -10,28 +9,31 @@ import {
tankDefinitionsArray,
tankNames,
unionBoundingBox,
+ type TankDefinition,
} from '@blitzkit/core';
-import { Callout, Flex, FlexProps, Link, Text } from '@radix-ui/themes';
-import { go } from 'fuzzysort';
+import { useStore } from '@nanostores/react';
+import { Callout, Flex, Link, Text, type FlexProps } from '@radix-ui/themes';
+import fuzzysort from 'fuzzysort';
import { times } from 'lodash-es';
-import { Fragment, memo, use, useEffect, useMemo, useState } from 'react';
-import { AdMidSectionResponsive } from '../../../../../components/AdMidSectionResponsive';
-import { ExperimentIcon } from '../../../../../components/ExperimentIcon';
-import { filterTank } from '../../../../../core/blitzkit/filterTank';
-import { resolveReload } from '../../../../../core/blitzkit/resolveReload';
-import { useAdExempt } from '../../../../../hooks/useAdExempt';
-import * as TankFilters from '../../../../../stores/tankFilters';
+import { Fragment, memo, useEffect, useMemo, useState } from 'react';
+import { filterTank } from '../../core/blitzkit/filterTank';
+import { resolveReload } from '../../core/blitzkit/resolveReload';
+import { useAdExempt } from '../../hooks/useAdExempt';
+import { useAwait } from '../../hooks/useAwait';
+import { $tankFilters } from '../../stores/tankFilters';
import {
SORT_NAMES,
SORT_UNITS,
-} from '../../../../../stores/tankopediaPersistent/constants';
-import * as TankopediaSort from '../../../../../stores/tankopediaSort';
-import { FilterControl } from '../FilterControl';
-import { NoResults } from '../NoResults';
-import { SearchBar } from '../SearchBar';
-import { SkeletonTankCard } from '../SkeletonTankCard';
-import { TankCard } from '../TankCard';
-import { TankCardWrapper } from '../TankCardWrapper';
+} from '../../stores/tankopediaPersistent/constants';
+import { $tankopediaSort } from '../../stores/tankopediaSort';
+import { AdMidSectionResponsive } from '../AdMidSectionResponsive';
+import { ExperimentIcon } from '../ExperimentIcon';
+import { FilterControl } from './components/FilterControl';
+import { NoResults } from './components/NoResults';
+import { SearchBar } from './components/SearchBar';
+import { SkeletonTankCard } from './components/SkeletonTankCard';
+import { TankCard } from './components/TankCard';
+import { TankCardWrapper } from './components/TankCardWrapper';
import { treeTypeOrder } from './constants';
type TankSearchProps = Omit & {
@@ -46,21 +48,23 @@ const DEFAULT_LOADED_CARDS = 75;
export const TankSearch = memo(
({ compact, onSelect, onSelectAll, ...props }) => {
const exempt = useAdExempt();
- const awaitedGameDefinitions = use(gameDefinitions);
- const awaitedModelDefinitions = use(modelDefinitions);
- const awaitedTankDefinitions = use(tankDefinitions);
- const awaitedTanksDefinitionsArray = use(tankDefinitionsArray);
- const awaitedTankNames = use(tankNames);
- const filters = TankFilters.use();
- const sort = TankopediaSort.use();
+ const awaitedGameDefinitions = useAwait(gameDefinitions);
+ const awaitedModelDefinitions = useAwait(modelDefinitions);
+ const awaitedTankDefinitions = useAwait(tankDefinitions);
+ const awaitedTanksDefinitionsArray = useAwait(tankDefinitionsArray);
+ const awaitedTankNames = useAwait(tankNames);
+ const tankFilters = useStore($tankFilters);
+ const tankopediaSort = useStore($tankopediaSort);
const tanksFiltered = useMemo(() => {
- if (filters.search === undefined) {
+ console.log(awaitedTanksDefinitionsArray);
+
+ if (tankFilters.search === undefined) {
const filtered = awaitedTanksDefinitionsArray.filter((tank) =>
- filterTank(filters, tank),
+ filterTank(tankFilters, tank),
);
let sorted: TankDefinition[];
- switch (sort.by) {
+ switch (tankopediaSort.by) {
case 'meta.none':
sorted = filtered
.sort((a, b) => b.tier - a.tier)
@@ -370,9 +374,11 @@ export const TankSearch = memo(
break;
}
- return sort.direction === 'ascending' ? sorted : sorted.reverse();
+ return tankopediaSort.direction === 'ascending'
+ ? sorted
+ : sorted.reverse();
} else {
- const searchedRaw = go(filters.search, awaitedTankNames, {
+ const searchedRaw = fuzzysort.go(tankFilters.search, awaitedTankNames, {
keys: ['searchableName', 'searchableNameDeburr', 'camouflages'],
});
const searchedTanks = searchedRaw.map(
@@ -380,19 +386,19 @@ export const TankSearch = memo(
);
return searchedTanks;
}
- }, [filters, sort]);
+ }, [tankFilters, tankopediaSort]);
const [loadedRows, setLoadedRows] = useState(DEFAULT_LOADED_CARDS);
const tanks = tanksFiltered.slice(0, loadedRows);
useEffect(() => {
setLoadedRows(DEFAULT_LOADED_CARDS);
- }, [filters, sort]);
+ }, [tankFilters, tankopediaSort]);
return (
- {!filters.search && !filters.searching && (
+ {!tankFilters.search && !tankFilters.searching && (
)}
@@ -413,18 +419,18 @@ export const TankSearch = memo(
)}
- {sort.by !== 'meta.none' && (
+ {tankopediaSort.by !== 'meta.none' && (
- Sorting by {SORT_NAMES[sort.by]}
- {SORT_UNITS[sort.by] === undefined
+ Sorting by {SORT_NAMES[tankopediaSort.by]}
+ {SORT_UNITS[tankopediaSort.by] === undefined
? ''
- : ` (${SORT_UNITS[sort.by]})`}
- , {sort.direction}
+ : ` (${SORT_UNITS[tankopediaSort.by]})`}
+ , {tankopediaSort.direction}
)}
- {filters.testing === 'only' && (
+ {tankFilters.testing === 'only' && (
@@ -438,7 +444,7 @@ export const TankSearch = memo(
)}
- {!filters.searching && (
+ {!tankFilters.searching && (
<>
{tanks.length > 0 && (
@@ -477,7 +483,7 @@ export const TankSearch = memo(
>
)}
- {filters.searching && (
+ {tankFilters.searching && (
{times(Math.round(10 + 10 * Math.random()), (index) => (
diff --git a/packages/website/src/constants/lightingEnvironments.ts b/packages/website/src/constants/lightingEnvironments.ts
new file mode 100644
index 000000000..4abf6a32d
--- /dev/null
+++ b/packages/website/src/constants/lightingEnvironments.ts
@@ -0,0 +1,7 @@
+export const ENVIRONMENTS = [
+ 'lobby',
+ 'apartment',
+ 'warehouse',
+ 'dawn',
+ 'sunset',
+] as const;
diff --git a/packages/website/src/hooks/useIntersection.ts b/packages/website/src/hooks/useIntersection.ts
index 72573df06..66a176f2b 100644
--- a/packages/website/src/hooks/useIntersection.ts
+++ b/packages/website/src/hooks/useIntersection.ts
@@ -1,4 +1,4 @@
-import { RefObject, useLayoutEffect, useRef } from 'react';
+import { type RefObject, useLayoutEffect, useRef } from 'react';
interface UseIntersectionOptions extends IntersectionObserverInit {
disabled: boolean;
diff --git a/packages/website/src/layouts/ToolLayout.astro b/packages/website/src/layouts/ToolLayout.astro
new file mode 100644
index 000000000..94627a386
--- /dev/null
+++ b/packages/website/src/layouts/ToolLayout.astro
@@ -0,0 +1,66 @@
+---
+import { imgur, ImgurSize } from '@blitzkit/core';
+import { Box, Flex, Heading, Text } from '@radix-ui/themes';
+import { TOOLS } from '../constants/tools';
+import RootLayout from './RootLayout.astro';
+
+const { pathname } = Astro.url;
+const pathSplit = pathname.split('/');
+const toolId = pathSplit.at(2);
+const isToolRoot = pathname === `/tools/${toolId}`;
+const tool = TOOLS.find((tool) => tool.id === toolId);
+---
+
+
+ {
+ tool && (
+ <>
+ {isToolRoot && (
+ <>
+ {tool.title}
+
+
+ {tool.pageDescription !== undefined && (
+ <>
+
+
+ >
+ )}
+ >
+ )}
+ >
+ )
+ }
+
+ {
+ pathSplit.length === 3 && (
+
+
+ {tool?.title}
+ {tool?.description}
+
+
+ )
+ }
+
+
+
diff --git a/packages/website/src/pages/TestComponent.tsx b/packages/website/src/pages/TestComponent.tsx
deleted file mode 100644
index 234f7ab73..000000000
--- a/packages/website/src/pages/TestComponent.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Button, Flex, Popover, Text } from '@radix-ui/themes';
-import { BlitzKitTheme } from '../components/BlitzKitTheme';
-
-export function TestComponent() {
- return (
-
-
-
-
-
-
-
-
- I should be in a dark mode popover
-
-
-
-
-
- );
-}
diff --git a/packages/website/src/pages/test.astro b/packages/website/src/pages/test.astro
deleted file mode 100644
index a8ef8d709..000000000
--- a/packages/website/src/pages/test.astro
+++ /dev/null
@@ -1,16 +0,0 @@
----
-import { BlitzKitTheme } from '../components/BlitzKitTheme';
-import { TestComponent } from './TestComponent';
----
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/website/src/pages/tools/tankopedia/index.astro b/packages/website/src/pages/tools/tankopedia/index.astro
new file mode 100644
index 000000000..fbdccaa27
--- /dev/null
+++ b/packages/website/src/pages/tools/tankopedia/index.astro
@@ -0,0 +1,8 @@
+---
+import { TankSearch } from '../../../components/TankSearch';
+import ToolLayout from '../../../layouts/ToolLayout.astro';
+---
+
+
+
+
diff --git a/packages/website/src/stores/tankFilters.ts b/packages/website/src/stores/tankFilters.ts
index 9686dae91..67fd41cae 100644
--- a/packages/website/src/stores/tankFilters.ts
+++ b/packages/website/src/stores/tankFilters.ts
@@ -1,6 +1,5 @@
-import { TankClass, Tier, TreeType } from '@blitzkit/core';
-import { create } from 'zustand';
-import { createContextualSafeStore } from '../core/zustand/createContextualSafeStore';
+import type { TankClass, Tier, TreeType } from '@blitzkit/core';
+import { map } from 'nanostores';
export interface TankFilters {
tiers: Tier[];
@@ -12,15 +11,14 @@ export interface TankFilters {
searching: boolean;
}
-export const { Provider, use, useMutation, useStore } =
- createContextualSafeStore(() =>
- create()(() => ({
- tiers: [],
- nations: [],
- classes: [],
- types: [],
- testing: 'include',
- search: undefined,
- searching: false,
- })),
- );
+export const initialTankFilters: TankFilters = {
+ tiers: [],
+ nations: [],
+ classes: [],
+ types: [],
+ testing: 'include',
+ search: undefined,
+ searching: false,
+};
+
+export const $tankFilters = map(initialTankFilters);
diff --git a/packages/website/src/stores/tankopediaPersistent/index.ts b/packages/website/src/stores/tankopediaPersistent/index.ts
index 6cd1f9b9b..a57f3efb1 100644
--- a/packages/website/src/stores/tankopediaPersistent/index.ts
+++ b/packages/website/src/stores/tankopediaPersistent/index.ts
@@ -2,7 +2,7 @@ import { TankClass, Tier, TreeType } from '@blitzkit/core';
import lodash from 'lodash-es';
import { create } from 'zustand';
import { persist, subscribeWithSelector } from 'zustand/middleware';
-import { ENVIRONMENTS } from '../../app/tools/tankopedia/[id]/components/Lighting';
+import { ENVIRONMENTS } from '../../constants/lightingEnvironments';
import { createContextualSafeStore } from '../../core/zustand/createContextualSafeStore';
import { SORT_NAMES, TankopediaDisplay } from './constants';
diff --git a/packages/website/src/stores/tankopediaSort.ts b/packages/website/src/stores/tankopediaSort.ts
index 6a7d6c6ef..986fe7ddf 100644
--- a/packages/website/src/stores/tankopediaSort.ts
+++ b/packages/website/src/stores/tankopediaSort.ts
@@ -1,8 +1,7 @@
-import { create } from 'zustand';
-import { createContextualSafeStore } from '../core/zustand/createContextualSafeStore';
+import { map } from 'nanostores';
import {
- TankopediaSortBy,
- TankopediaSortDirection,
+ type TankopediaSortBy,
+ type TankopediaSortDirection,
} from './tankopediaPersistent';
export interface TankopediaSort {
@@ -10,10 +9,7 @@ export interface TankopediaSort {
direction: TankopediaSortDirection;
}
-export const { Provider, use, useMutation, useStore } =
- createContextualSafeStore(() =>
- create()(() => ({
- by: 'meta.none',
- direction: 'descending',
- })),
- );
+export const $tankopediaSort = map({
+ by: 'meta.none',
+ direction: 'descending',
+});