tank search

This commit is contained in:
tresabhi committed 2026-08-28 16:59:07 -05:00
1 parent 5477616917
commit 4a0fccc526
6 files changed
+54 -73

No files matched your search

+36 -23
View File
@@ -1,6 +1,6 @@
import { BlitzKitAPI } from "@blitzkit/core"; import { BlitzKitAPI } from "@blitzkit/core/src/blitzkit/api/base";
import locales from "@blitzkit/i18n/locales.json"; import locales from "@blitzkit/i18n/locales.json";
import { deburr } from "lodash-es"; import { deburr, times } from "lodash-es";
import { I18nString } from "../protos"; import { I18nString } from "../protos";
export async function fetchTankNames(api: BlitzKitAPI) { export async function fetchTankNames(api: BlitzKitAPI) {
@@ -12,40 +12,53 @@ export async function fetchTankNames(api: BlitzKitAPI) {
return await Promise.all( return await Promise.all(
tankDefinitionsArray.map(async (tank) => { tankDefinitionsArray.map(async (tank) => {
const searchableNameDeburr: I18nString = { locales: {} }; const name_deburr: I18nString = I18nString.create();
const camouflages_deburr: I18nString[] = [];
const camouflages = tank.camouflages.map(
(id) => camouflageDefinitions.camouflages[id].name!,
);
Object.entries(tank.name!).forEach(([key, value]) => { Object.entries(tank.name!).forEach(([key, value]) => {
searchableNameDeburr.locales[key] = deburr(value); name_deburr.locales[key] = deburr(value);
});
camouflages.forEach((camouflage, index) => {
camouflages_deburr[index] = I18nString.create();
Object.entries(camouflage).forEach(([key, value]) => {
camouflages_deburr[index].locales[key] = deburr(value);
});
}); });
return { return {
id: tank.id, id: tank.id,
name: tank.name, slug: tank.slug,
searchableName: tank.name, dev_name: tank.dev_name,
searchableNameDeburr,
camouflages: tank.camouflages name: tank.name!,
?.map((id) => name_deburr,
locales.supported.map(
({ locale }) => camouflages,
camouflageDefinitions.camouflages[id]?.name!.locales[locale], camouflages_deburr,
),
)
.flat()
.filter(Boolean)
.map(deburr)
.join(" "),
treeType: tank.type,
}; };
}), }),
); );
} }
export const SEARCH_KEYS = [ export const SEARCH_KEYS = [
"id",
"slug",
"dev_name",
...locales.supported ...locales.supported
.map(({ locale }) => [ .map(({ locale }) => [
`searchableName.locales.${locale}`, `name.locales.${locale}`,
`searchableNameDeburr.locales.${locale}`, `name_deburr.locales.${locale}`,
...times(8, (index) => [
`camouflages.${index}.locales.${locale}`,
`camouflages_deburr.${index}.locales.${locale}`,
]),
]) ])
.flat(), .flat(2),
"camouflages",
]; ];
@@ -1,4 +1,5 @@
.recent { .recent {
margin-top: var(--space-4);
padding: var(--space-4); padding: var(--space-4);
background-color: var(--gray-a2); background-color: var(--gray-a2);
@@ -1,5 +1,8 @@
import { useLocale } from "../../../hooks/useLocale"; import { useLocale } from "../../../hooks/useLocale";
import { TankFilters } from "../../../stores/tankFilters"; import { TankFilters } from "../../../stores/tankFilters";
import { Flex } from "../../Flex";
import { Link } from "../../Link";
import { Text } from "../../Text";
interface NoResultsProps { interface NoResultsProps {
type?: "filters" | "search"; type?: "filters" | "search";
@@ -9,8 +12,8 @@ export function NoResults({ type = "filters" }: NoResultsProps) {
const { strings } = useLocale(); const { strings } = useLocale();
return ( return (
<Flex flexGrow="1" align="center" justify="center"> <Flex align="center" justify="center">
<Text color="gray"> <Text lowContrast>
{strings.website.common.tank_search.no_tanks_found.body}{" "} {strings.website.common.tank_search.no_tanks_found.body}{" "}
<Link <Link
href="#" href="#"
@@ -73,9 +73,7 @@ export const TankSearch = memo<TankSearchProps>(
} else { } else {
return Promise.resolve( return Promise.resolve(
fuzzysort fuzzysort
.go(tankFilters.search, tankNames, { .go(tankFilters.search, tankNames, { keys: SEARCH_KEYS })
keys: SEARCH_KEYS,
})
.map((result) => tankDefinitions.tanks[result.obj.id]), .map((result) => tankDefinitions.tanks[result.obj.id]),
); );
} }
@@ -437,7 +435,7 @@ export const TankSearch = memo<TankSearchProps>(
onSelect={onSelect} onSelect={onSelect}
/> />
{!tankFilters.search && !tankFilters.searching && <TankSearchFilters />} {!tankFilters.search && <TankSearchFilters />}
{!skeleton && !compact && <RecentlyViewedTanks />} {!skeleton && !compact && <RecentlyViewedTanks />}
@@ -497,7 +495,7 @@ export const TankSearch = memo<TankSearchProps>(
</div> </div>
)} )}
{!skeleton && !tankFilters.searching && ( {!skeleton && (
<> <>
{tanks.length > 0 && ( {tanks.length > 0 && (
<TankCardWrapper> <TankCardWrapper>
@@ -530,19 +528,6 @@ export const TankSearch = memo<TankSearchProps>(
{tanks.length === 0 && <NoResults type="search" />} {tanks.length === 0 && <NoResults type="search" />}
</> </>
)} )}
{(skeleton || tankFilters.searching) && (
<TankCardWrapper>
{times(
Math.round(
skeleton ? DEFAULT_LOADED_CARDS : 10 + 10 * Math.random(),
),
(index) => (
<TankCardSkeleton key={index} />
),
)}
</TankCardWrapper>
)}
</Flex> </Flex>
); );
}, },
@@ -1,12 +1,10 @@
import type { TankDefinition } from "@blitzkit/core"; import type { TankDefinition } from "@blitzkit/core";
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
import { debounce } from "lodash-es";
import { type KeyboardEventHandler, useCallback, useRef } from "react"; import { type KeyboardEventHandler, useCallback, useRef } from "react";
import { useLocale } from "../../hooks/useLocale"; import { useLocale } from "../../hooks/useLocale";
import { TankFilters } from "../../stores/tankFilters"; import { TankFilters } from "../../stores/tankFilters";
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
import { Flex } from "../Flex"; import { Flex } from "../Flex";
import { Spinner } from "../Spinner";
import { Sort } from "../TankSearch/components/Sort"; import { Sort } from "../TankSearch/components/Sort";
import { TextField } from "../TextField"; import { TextField } from "../TextField";
@@ -22,36 +20,19 @@ export function TankSearchBar({
}: SearchBarProps) { }: SearchBarProps) {
const { strings } = useLocale(); const { strings } = useLocale();
const search = TankFilters.use((state) => state.search); const search = TankFilters.use((state) => state.search);
const searching = TankFilters.use((state) => state.searching);
const input = useRef<HTMLInputElement>(null); const input = useRef<HTMLInputElement>(null);
const performSearch = useCallback(
debounce(() => {
TankFilters.mutate((draft) => {
draft.searching = false;
});
if (!input.current) return;
const sanitized = input.current.value.trim();
TankFilters.mutate((draft) => {
draft.search = sanitized.length === 0 ? null : sanitized;
});
}, 500),
[],
);
const handleChange = useCallback(() => { const handleChange = useCallback(() => {
if (!searching) { if (!input.current) return;
TankFilters.mutate((draft) => {
draft.searching = true;
});
}
performSearch(); const sanitized = input.current.value.trim();
}, [searching]);
TankFilters.mutate((draft) => {
draft.search = sanitized.length === 0 ? null : sanitized;
});
}, []);
const handleKeyDown = useCallback<KeyboardEventHandler>( const handleKeyDown = useCallback<KeyboardEventHandler>(
(event) => { (event) => {
if (event.key !== "Enter" || !topResult || searching) return; if (event.key !== "Enter" || !topResult) return;
event.preventDefault(); event.preventDefault();
@@ -75,7 +56,7 @@ export function TankSearchBar({
onChange={handleChange} onChange={handleChange}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
> >
{searching ? <Spinner /> : <MagnifyingGlassIcon />} <MagnifyingGlassIcon />
</TextField> </TextField>
<Sort /> <Sort />
@@ -18,7 +18,6 @@ export interface TankFilters {
classes: TankClass[]; classes: TankClass[];
types: TankType[]; types: TankType[];
search: string | null; search: string | null;
searching: boolean;
gunType: CaseType<GunDefinition>[]; gunType: CaseType<GunDefinition>[];
shells: [ShellType | null, ShellType | null, ShellType | null]; shells: [ShellType | null, ShellType | null, ShellType | null];
consumables: number[]; consumables: number[];
@@ -38,7 +37,6 @@ export const TankFilters = new Varuna<TankFilters>({
classes: [], classes: [],
types: [], types: [],
search: null, search: null,
searching: false,
gunType: [], gunType: [],
shells: [null, null, null], shells: [null, null, null],
consumables: [], consumables: [],