From a57734cdee05a598ad78f53db4ee68fd10e186f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:03:20 -0500 Subject: [PATCH] feat: perf --- .../tools/tankopedia/components/Results.tsx | 131 ++---------------- .../tools/tankopedia/components/TankCard.tsx | 94 +++++++++++++ .../tools/tankopedia/components/TierCard.tsx | 55 ++++++++ src/app/tools/tankopedia/page.tsx | 64 ++++----- src/core/blitzkit/tankopediaFilterTank.ts | 17 +++ src/stores/tankopediaFilters.ts | 22 +++ 6 files changed, 225 insertions(+), 158 deletions(-) create mode 100644 src/app/tools/tankopedia/components/TankCard.tsx create mode 100644 src/app/tools/tankopedia/components/TierCard.tsx create mode 100644 src/core/blitzkit/tankopediaFilterTank.ts create mode 100644 src/stores/tankopediaFilters.ts diff --git a/src/app/tools/tankopedia/components/Results.tsx b/src/app/tools/tankopedia/components/Results.tsx index 4ff27706d..5947a9d55 100644 --- a/src/app/tools/tankopedia/components/Results.tsx +++ b/src/app/tools/tankopedia/components/Results.tsx @@ -1,50 +1,25 @@ -import { - Callout, - Flex, - Grid, - Heading, - Link, - Separator, - Text, -} from '@radix-ui/themes'; +import { Callout, Flex } from '@radix-ui/themes'; import { range } from 'lodash'; import { memo, use } from 'react'; -import { classIcons } from '../../../../components/ClassIcon'; import { ExperimentIcon } from '../../../../components/ExperimentIcon'; import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants'; -import { asset } from '../../../../core/blitzkit/asset'; import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions'; import { Tier, tanksDefinitionsArray, } from '../../../../core/blitzkit/tankDefinitions'; -import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants'; -import { tankIcon } from '../../../../core/blitzkit/tankIcon'; -import { TankopediaSearchPageFilters } from '../page'; +import { useTankopediaFilters } from '../../../../stores/tankopediaFilters'; import { treeTypeOrder } from './TankSearch/constants'; +import { TierCard } from './TierCard'; -interface ResultsProps { - filters: TankopediaSearchPageFilters; -} - -export const Results = memo(({ filters }) => { - const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray); - const tanksFiltered = awaitedTanksDefinitionsArray.filter((tank) => { - return ( - (filters.tier === undefined || filters.tier === tank.tier) && - (filters.nation === undefined || filters.nation === tank.nation) && - (filters.class === undefined || filters.class === tank.class) && - (filters.type === undefined || filters.type === tank.treeType) && - (filters.testing === 'include' || - (filters.testing === 'only' && tank.testing) || - (filters.testing === 'exclude' && !tank.testing)) - ); - }); +export const Results = memo(() => { + const tanks = use(tanksDefinitionsArray); const awaitedGameDefinitions = use(gameDefinitions); + const testing = useTankopediaFilters((state) => state.testing); return ( - {filters.testing === 'only' && ( + {testing === 'only' && ( @@ -58,7 +33,7 @@ export const Results = memo(({ filters }) => { {range(10, 0).map((tierUntyped) => { const tier = tierUntyped as Tier; - const tierTanks = tanksFiltered + const tierTanks = tanks .filter((tank) => tank.tier === tier) .sort( (a, b) => @@ -75,95 +50,7 @@ export const Results = memo(({ filters }) => { awaitedGameDefinitions.nations.indexOf(b.nation), ); - if (tierTanks.length === 0) return null; - - return ( - - - - Tier {TIER_ROMAN_NUMERALS[tier]} - - - - - - {tierTanks.map((tank) => { - const Icon = classIcons[tank.class]; - - return ( - - {tank.name} - - - - - {tank.name} - - - - ); - })} - - - ); + return ; })} ); diff --git a/src/app/tools/tankopedia/components/TankCard.tsx b/src/app/tools/tankopedia/components/TankCard.tsx new file mode 100644 index 000000000..e1b08d6f9 --- /dev/null +++ b/src/app/tools/tankopedia/components/TankCard.tsx @@ -0,0 +1,94 @@ +import { Flex, Link, Text } from '@radix-ui/themes'; +import { useEffect, useRef } from 'react'; +import { classIcons } from '../../../../components/ClassIcon'; +import { asset } from '../../../../core/blitzkit/asset'; +import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions'; +import { tankIcon } from '../../../../core/blitzkit/tankIcon'; +import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank'; +import { useTankopediaFilters } from '../../../../stores/tankopediaFilters'; + +interface TankCardProps { + tank: TankDefinition; +} + +export function TankCard({ tank }: TankCardProps) { + const Icon = classIcons[tank.class]; + const link = useRef(null); + + useEffect(() => { + const unsubscribe = useTankopediaFilters.subscribe((filters) => { + if (!link.current) return; + + const visible = tankopediaFilterTank(filters, tank); + link.current.style.display = visible ? 'flex' : 'none'; + }); + + return unsubscribe; + }, []); + + return ( + + {tank.name} + + + + + {tank.name} + + + + ); +} diff --git a/src/app/tools/tankopedia/components/TierCard.tsx b/src/app/tools/tankopedia/components/TierCard.tsx new file mode 100644 index 000000000..3dc4b32a2 --- /dev/null +++ b/src/app/tools/tankopedia/components/TierCard.tsx @@ -0,0 +1,55 @@ +import { Flex, Grid, Heading, Separator } from '@radix-ui/themes'; +import { useEffect, useRef } from 'react'; +import { + TankDefinition, + Tier, +} from '../../../../core/blitzkit/tankDefinitions'; +import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants'; +import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank'; +import { useTankopediaFilters } from '../../../../stores/tankopediaFilters'; +import { TankCard } from './TankCard'; + +interface TierCardProps { + tier: Tier; + tanks: TankDefinition[]; +} + +export function TierCard({ tier, tanks }: TierCardProps) { + const container = useRef(null); + + useEffect(() => { + const unsubscribe = useTankopediaFilters.subscribe((filters) => { + if (!container.current) return; + + const visible = tanks.some((tank) => tankopediaFilterTank(filters, tank)); + container.current.style.display = visible ? 'flex' : 'none'; + }); + + return unsubscribe; + }, []); + + return ( + + + Tier {TIER_ROMAN_NUMERALS[tier]} + + + + + {tanks.map((tank) => ( + + ))} + + + ); +} diff --git a/src/app/tools/tankopedia/page.tsx b/src/app/tools/tankopedia/page.tsx index c325ba59f..7a474f9ee 100644 --- a/src/app/tools/tankopedia/page.tsx +++ b/src/app/tools/tankopedia/page.tsx @@ -2,44 +2,28 @@ import { TrashIcon } from '@radix-ui/react-icons'; import { Box, Button, Flex, IconButton, Text } from '@radix-ui/themes'; -import { produce } from 'immer'; import { times } from 'lodash'; -import { use, useState } from 'react'; +import { use } from 'react'; import { classIcons } from '../../../components/ClassIcon'; import { ExperimentIcon } from '../../../components/ExperimentIcon'; import PageWrapper from '../../../components/PageWrapper'; import { ResearchedIcon } from '../../../components/ResearchedIcon'; import { ScienceIcon } from '../../../components/ScienceIcon'; import { ScienceOffIcon } from '../../../components/ScienceOffIcon'; -import { TankClass, TreeType } from '../../../components/Tanks'; import { TANK_CLASSES } from '../../../components/Tanks/components/Item/constants'; import { asset } from '../../../core/blitzkit/asset'; import { gameDefinitions } from '../../../core/blitzkit/gameDefinitions'; import { Tier } from '../../../core/blitzkit/tankDefinitions'; import { TIER_ROMAN_NUMERALS } from '../../../core/blitzkit/tankDefinitions/constants'; +import { + mutateTankopediaFilters, + useTankopediaFilters, +} from '../../../stores/tankopediaFilters'; import { Results } from './components/Results'; -export interface TankopediaSearchPageFilters { - tier?: Tier; - nation?: string; - class?: TankClass; - type?: TreeType; - testing: 'include' | 'exclude' | 'only'; -} - -const defaultTankopediaSearchPageFilters: TankopediaSearchPageFilters = { - testing: 'include', -}; - export default function Page() { const awaitedGameDefinitions = use(gameDefinitions); - const [filters, setFilters] = useState( - defaultTankopediaSearchPageFilters, - ); - - function mutateFilters(recipe: (draft: TankopediaSearchPageFilters) => void) { - setFilters(produce(recipe)); - } + const filters = useTankopediaFilters(); return ( @@ -98,7 +82,7 @@ export default function Page() { color={selected ? undefined : 'gray'} highContrast onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.tier = draft.tier === tier ? undefined : tier; }) } @@ -126,7 +110,7 @@ export default function Page() { color={selected ? undefined : 'gray'} highContrast onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.tier = draft.tier === tier ? undefined : tier; }) } @@ -166,7 +150,7 @@ export default function Page() { highContrast radius="none" onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.nation = draft.nation === nation ? undefined : nation; }) @@ -199,7 +183,7 @@ export default function Page() { highContrast radius="none" onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.nation = draft.nation === nation ? undefined : nation; }) @@ -237,7 +221,7 @@ export default function Page() { color={selected ? undefined : 'gray'} highContrast onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.class = draft.class === tankClass ? undefined : tankClass; }) @@ -265,7 +249,7 @@ export default function Page() { color={filters.type === 'researchable' ? undefined : 'gray'} highContrast onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.type = draft.type === 'researchable' ? undefined : 'researchable'; }) @@ -279,7 +263,7 @@ export default function Page() { color={filters.type === 'premium' ? 'amber' : 'gray'} highContrast onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.type = draft.type === 'premium' ? undefined : 'premium'; }) } @@ -297,7 +281,7 @@ export default function Page() { color={filters.type === 'collector' ? 'blue' : 'gray'} highContrast onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.type = draft.type === 'collector' ? undefined : 'collector'; }) @@ -328,7 +312,7 @@ export default function Page() { color={filters.testing === 'include' ? undefined : 'gray'} highContrast onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.testing = 'include'; }) } @@ -343,7 +327,7 @@ export default function Page() { color={filters.testing === 'exclude' ? undefined : 'gray'} highContrast onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.testing = 'exclude'; }) } @@ -358,7 +342,7 @@ export default function Page() { color={filters.testing === 'only' ? undefined : 'gray'} highContrast onClick={() => - mutateFilters((draft) => { + mutateTankopediaFilters((draft) => { draft.testing = 'only'; }) } @@ -373,7 +357,11 @@ export default function Page() { setFilters(defaultTankopediaSearchPageFilters)} + onClick={() => + useTankopediaFilters.setState( + useTankopediaFilters.getInitialState(), + ) + } > @@ -382,14 +370,18 @@ export default function Page() { - + ); diff --git a/src/core/blitzkit/tankopediaFilterTank.ts b/src/core/blitzkit/tankopediaFilterTank.ts new file mode 100644 index 000000000..9c41665c8 --- /dev/null +++ b/src/core/blitzkit/tankopediaFilterTank.ts @@ -0,0 +1,17 @@ +import { TankopediaFilters } from '../../stores/tankopediaFilters'; +import { TankDefinition } from './tankDefinitions'; + +export function tankopediaFilterTank( + filters: TankopediaFilters, + tank: TankDefinition, +) { + return ( + (filters.tier === undefined || filters.tier === tank.tier) && + (filters.nation === undefined || filters.nation === tank.nation) && + (filters.class === undefined || filters.class === tank.class) && + (filters.type === undefined || filters.type === tank.treeType) && + (filters.testing === 'include' || + (filters.testing === 'only' && tank.testing) || + (filters.testing === 'exclude' && !tank.testing)) + ); +} diff --git a/src/stores/tankopediaFilters.ts b/src/stores/tankopediaFilters.ts new file mode 100644 index 000000000..18b0e7fac --- /dev/null +++ b/src/stores/tankopediaFilters.ts @@ -0,0 +1,22 @@ +import { produce } from 'immer'; +import { create } from 'zustand'; +import { TankClass, TreeType } from '../components/Tanks'; +import { Tier } from '../core/blitzkit/tankDefinitions'; + +export interface TankopediaFilters { + tier?: Tier; + nation?: string; + class?: TankClass; + type?: TreeType; + testing: 'include' | 'exclude' | 'only'; +} + +export const useTankopediaFilters = create()(() => ({ + testing: 'include', +})); + +export function mutateTankopediaFilters( + recipe: (draft: TankopediaFilters) => void, +) { + useTankopediaFilters.setState(produce(recipe)); +}