diff --git a/src/app/tools/tankopedia/[id]/components/AntagonistBar/components/SearchResults.tsx b/src/app/tools/tankopedia/[id]/components/AntagonistBar/components/SearchResults.tsx deleted file mode 100644 index 696f71e7a..000000000 --- a/src/app/tools/tankopedia/[id]/components/AntagonistBar/components/SearchResults.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button, Dialog, Flex, Text } from '@radix-ui/themes'; -import { produce } from 'immer'; -import { Dispatch, SetStateAction, use } from 'react'; -import { SmallTankIcon } from '../../../../../../../components/SmallTankIcon'; -import { - TIER_ROMAN_NUMERALS, - tankDefinitions, -} from '../../../../../../../core/blitzkrieg/tankDefinitions'; -import { Duel } from '../../../page'; - -interface SearchResultsProps { - ids: number[]; - setDuel: Dispatch>; -} - -export function SearchResults({ ids, setDuel }: SearchResultsProps) { - const awaitedTankDefinitions = use(tankDefinitions); - - return ( - - - {ids.map((id) => { - const tank = awaitedTankDefinitions[id]; - - return ( - - - - ); - })} - - - ); -} diff --git a/src/app/tools/tankopedia/[id]/components/AntagonistBar/index.tsx b/src/app/tools/tankopedia/[id]/components/AntagonistBar/index.tsx index ce741b8d2..0c4e7a74c 100644 --- a/src/app/tools/tankopedia/[id]/components/AntagonistBar/index.tsx +++ b/src/app/tools/tankopedia/[id]/components/AntagonistBar/index.tsx @@ -1,9 +1,4 @@ -import { - CaretRightIcon, - Cross1Icon, - MagnifyingGlassIcon, - ShuffleIcon, -} from '@radix-ui/react-icons'; +import { CaretRightIcon, Cross1Icon, ShuffleIcon } from '@radix-ui/react-icons'; import { Button, Card, @@ -12,23 +7,17 @@ import { Heading, Tabs, Text, - TextField, Tooltip, } from '@radix-ui/themes'; -import { go } from 'fuzzysort'; import { produce } from 'immer'; -import { debounce } from 'lodash'; -import { Dispatch, SetStateAction, use, useRef, useState } from 'react'; +import { Dispatch, SetStateAction, useState } from 'react'; import { ModuleButton } from '../../../../../../components/ModuleButton'; import { SmallTankIcon } from '../../../../../../components/SmallTankIcon'; import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration'; -import { - SHELL_NAMES, - tankNamesDiacritics, -} from '../../../../../../core/blitzkrieg/tankDefinitions'; +import { SHELL_NAMES } from '../../../../../../core/blitzkrieg/tankDefinitions'; import { useTankopediaTemporary } from '../../../../../../stores/tankopedia'; +import { TankSearch } from '../../../components/TankSearch'; import { Duel } from '../../page'; -import { SearchResults } from './components/SearchResults'; interface AntagonistBarProps { duel: Duel; @@ -40,13 +29,9 @@ export function AntagonistBar({ setDuel, }: AntagonistBarProps) { const mode = useTankopediaTemporary((state) => state.mode); - const awaitedTankNamesDiacritics = use(tankNamesDiacritics); const [tab, setTab] = useState('search'); - const searchInput = useRef(null); - const [searchResults, setSearchResults] = useState([]); - const chunkSize = window.innerWidth > 512 ? 8 : Infinity; - const firstChunk = searchResults.slice(0, chunkSize); - const secondChunk = searchResults.slice(chunkSize); + const [antagonistSelectorOpen, setAntagonistSelectorVisible] = + useState(false); if (mode !== 'armor') return null; @@ -79,7 +64,10 @@ export function AntagonistBar({ Versus - + + ))} + + + ); +} diff --git a/src/app/tools/tankopedia/components/Options/index.css.ts b/src/app/tools/tankopedia/components/TankSearch/components/Options/index.css.ts similarity index 100% rename from src/app/tools/tankopedia/components/Options/index.css.ts rename to src/app/tools/tankopedia/components/TankSearch/components/Options/index.css.ts diff --git a/src/app/tools/tankopedia/components/Options/index.tsx b/src/app/tools/tankopedia/components/TankSearch/components/Options/index.tsx similarity index 98% rename from src/app/tools/tankopedia/components/Options/index.tsx rename to src/app/tools/tankopedia/components/TankSearch/components/Options/index.tsx index a44979bd8..8ec7f7b23 100644 --- a/src/app/tools/tankopedia/components/Options/index.tsx +++ b/src/app/tools/tankopedia/components/TankSearch/components/Options/index.tsx @@ -3,23 +3,23 @@ import { CaretDownIcon } from '@radix-ui/react-icons'; import { Button, DropdownMenu, Flex } from '@radix-ui/themes'; import { use } from 'react'; -import { Flag } from '../../../../../components/Flag'; +import { Flag } from '../../../../../../../components/Flag'; import { TANK_TYPES, TREE_TYPES, TREE_TYPE_ICONS, TREE_TYPE_IMAGES, TreeTypeEnum, -} from '../../../../../components/Tanks'; +} from '../../../../../../../components/Tanks'; import { NATIONS, TIERS, TIER_ROMAN_NUMERALS, -} from '../../../../../core/blitzkrieg/tankDefinitions'; +} from '../../../../../../../core/blitzkrieg/tankDefinitions'; import mutateTankopediaPersistent, { TankopediaTestTankDisplay, useTankopediaPersistent, -} from '../../../../../stores/tankopedia'; +} from '../../../../../../../stores/tankopedia'; import * as styles from './index.css'; export function Options() { diff --git a/src/app/tools/tankopedia/components/TankSearch/index.tsx b/src/app/tools/tankopedia/components/TankSearch/index.tsx new file mode 100644 index 000000000..dc0498076 --- /dev/null +++ b/src/app/tools/tankopedia/components/TankSearch/index.tsx @@ -0,0 +1,316 @@ +import { slateDark } from '@radix-ui/colors'; +import { CaretDownIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons'; +import { + Button, + Card, + DropdownMenu, + Flex, + Inset, + Text, + TextField, +} from '@radix-ui/themes'; +import { go } from 'fuzzysort'; +import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react'; +import { asset } from '../../../../../core/blitzkrieg/asset'; +import { + TANK_ICONS, + TANK_ICONS_COLLECTOR, + TANK_ICONS_PREMIUM, + TIER_ROMAN_NUMERALS, + TankDefinition, + tanksDefinitionsArray, +} from '../../../../../core/blitzkrieg/tankDefinitions'; +import { tankIcon } from '../../../../../core/blitzkrieg/tankIcon'; +import { theme } from '../../../../../stitches.config'; +import mutateTankopediaPersistent, { + TankopediaSortBy, + TankopediaSortDirection, + useTankopediaPersistent, +} from '../../../../../stores/tankopedia'; +import * as styles from '../../page.css'; +import { PageTurner } from '../PageTurner'; +import { CompactSearchResultRow } from './components/CompactSearchResultRow'; +import { Options } from './components/Options'; + +interface TankSearchProps { + compact?: boolean; + onSelect?: (tank: TankDefinition) => void; +} + +export function TankSearch({ compact, onSelect = () => {} }: TankSearchProps) { + const tanksPerPage = compact ? 16 : 24; + const filters = useTankopediaPersistent((state) => state.filters); + const sort = useTankopediaPersistent((state) => state.sort); + const awaitedTanks = use(tanksDefinitionsArray); + const input = useRef(null); + const searchableTanks = useMemo( + () => + awaitedTanks + .filter( + (tank) => + (filters.tiers.length === 0 + ? true + : filters.tiers.includes(tank.tier)) && + (filters.types.length === 0 + ? true + : filters.types.includes(tank.type)) && + (filters.treeTypes.length === 0 + ? true + : filters.treeTypes.includes(tank.tree_type)) && + (filters.nations.length === 0 + ? true + : filters.nations.includes(tank.nation)) && + (filters.test === 'include' + ? true + : filters.test === 'exclude' + ? !tank.testing + : tank.testing), + ) + .sort((a, b) => { + let diff = 0; + + if (sort.by === 'tier') { + diff = a.tier - b.tier; + } + if (sort.by === 'name') { + diff = a.name.localeCompare(b.name); + } + + return sort.direction === 'ascending' ? diff : -diff; + }), + [filters, sort], + ); + const [searchResults, setSearchedList] = useState(searchableTanks); + const page = useTankopediaPersistent((state) => state.filters.page); + const chunkSize = window.innerWidth > 512 ? tanksPerPage / 2 : Infinity; + const searchResultsPageSlice = searchResults.slice( + page * tanksPerPage, + (page + 1) * tanksPerPage, + ); + const firstChunk = searchResultsPageSlice.slice(0, chunkSize); + const secondChunk = searchResultsPageSlice.slice(chunkSize); + + useEffect(() => { + setSearchedList(searchableTanks); + if (input.current) input.current.value = ''; + }, [searchableTanks]); + useEffect(() => { + mutateTankopediaPersistent((draft) => { + draft.filters.page = Math.min( + Math.max(0, page), + Math.ceil(searchResults.length / tanksPerPage) - 1, + ); + }); + }, [searchResults]); + + return ( + <> + + + + + + + { + if (event.target.value.length === 0) { + setSearchedList(searchableTanks); + } else { + setSearchedList( + go(event.target.value, searchableTanks, { + keys: ['name', 'id'] satisfies (keyof TankDefinition)[], + }).map(({ obj }) => obj), + ); + } + }} + /> + + + + + + + + By + + mutateTankopediaPersistent((draft) => { + draft.sort.by = value as TankopediaSortBy; + }) + } + > + + Tier + + + Name + + + + + + Direction + + mutateTankopediaPersistent((draft) => { + draft.sort.direction = value as TankopediaSortDirection; + }) + } + > + + Ascending + + + Descending + + + + + + + + Loading...}> + + + + + + {!compact && ( + + )} + + + {!compact && + searchResultsPageSlice.map((tank) => ( + onSelect(tank)} + className={styles.listing} + style={{ + flex: 1, + cursor: 'pointer', + }} + > + + + +
+ +
+ +
+ + + + + + {tank.name} + + + + + {TIER_ROMAN_NUMERALS[tank.tier]} + + + + + ))} + + {compact && ( + + )} + + {compact && secondChunk.length > 0 && ( + + )} + + + + + ); +} diff --git a/src/app/tools/tankopedia/page.tsx b/src/app/tools/tankopedia/page.tsx index cafc0ed3c..1a5fa2b0f 100644 --- a/src/app/tools/tankopedia/page.tsx +++ b/src/app/tools/tankopedia/page.tsx @@ -1,303 +1,22 @@ 'use client'; -import { slateDark } from '@radix-ui/colors'; -import { CaretDownIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons'; -import { - Button, - Card, - DropdownMenu, - Flex, - Inset, - Text, - TextField, -} from '@radix-ui/themes'; -import { go } from 'fuzzysort'; -import Link from 'next/link'; -import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react'; +import { Text } from '@radix-ui/themes'; +import { useRouter } from 'next/navigation'; +import { Suspense } from 'react'; import PageWrapper from '../../../components/PageWrapper'; -import { asset } from '../../../core/blitzkrieg/asset'; -import { - TANK_ICONS, - TANK_ICONS_COLLECTOR, - TANK_ICONS_PREMIUM, - TIER_ROMAN_NUMERALS, - TankDefinition, - tanksDefinitionsArray, -} from '../../../core/blitzkrieg/tankDefinitions'; -import { tankIcon } from '../../../core/blitzkrieg/tankIcon'; -import { theme } from '../../../stitches.config'; -import mutateTankopediaPersistent, { - TankopediaSortBy, - TankopediaSortDirection, - useTankopediaPersistent, -} from '../../../stores/tankopedia'; -import { Options } from './components/Options'; -import { PageTurner } from './components/PageTurner'; -import * as styles from './page.css'; - -const TANKS_PER_PAGE = 3 * 2 ** 3; +import { TankSearch } from './components/TankSearch'; export default function Page() { - const filters = useTankopediaPersistent((state) => state.filters); - const sort = useTankopediaPersistent((state) => state.sort); - const awaitedTanks = use(tanksDefinitionsArray); - const input = useRef(null); - const searchableTanks = useMemo( - () => - awaitedTanks - .filter( - (tank) => - (filters.tiers.length === 0 - ? true - : filters.tiers.includes(tank.tier)) && - (filters.types.length === 0 - ? true - : filters.types.includes(tank.type)) && - (filters.treeTypes.length === 0 - ? true - : filters.treeTypes.includes(tank.tree_type)) && - (filters.nations.length === 0 - ? true - : filters.nations.includes(tank.nation)) && - (filters.test === 'include' - ? true - : filters.test === 'exclude' - ? !tank.testing - : tank.testing), - ) - .sort((a, b) => { - let diff = 0; - - if (sort.by === 'tier') { - diff = a.tier - b.tier; - } - if (sort.by === 'name') { - diff = a.name.localeCompare(b.name); - } - - return sort.direction === 'ascending' ? diff : -diff; - }), - [filters, sort], - ); - const [searchedList, setSearchedList] = useState(searchableTanks); - const page = useTankopediaPersistent((state) => state.filters.page); - - useEffect(() => { - setSearchedList(searchableTanks); - if (input.current) input.current.value = ''; - }, [searchableTanks]); - - useEffect(() => { - mutateTankopediaPersistent((draft) => { - draft.filters.page = Math.min( - Math.max(0, page), - Math.ceil(searchedList.length / TANKS_PER_PAGE) - 1, - ); - }); - }, [searchedList]); + const router = useRouter(); return ( Loading...}> - - - - - - - { - if (event.target.value.length === 0) { - setSearchedList(searchableTanks); - } else { - setSearchedList( - go(event.target.value, searchableTanks, { - keys: ['name', 'id'] satisfies (keyof TankDefinition)[], - }).map(({ obj }) => obj), - ); - } - }} - /> - - - - - - - - By - - mutateTankopediaPersistent((draft) => { - draft.sort.by = value as TankopediaSortBy; - }) - } - > - - Tier - - - Name - - - - - - Direction - - mutateTankopediaPersistent((draft) => { - draft.sort.direction = value as TankopediaSortDirection; - }) - } - > - - Ascending - - - Descending - - - - - - - - Loading...}> - - - - - - - - - {searchedList - .map((tank) => ( - - - - - -
- -
- -
- - - - - - {tank.name} - - - - - {TIER_ROMAN_NUMERALS[tank.tier]} - - - - - - )) - .slice(page * TANKS_PER_PAGE, (page + 1) * TANKS_PER_PAGE)} - - - + { + router.push(`./tankopedia/${tank.id}`); + }} + /> );