import { clsx } from "clsx"; import type { Namespace, TFunction } from "i18next"; import { Calculator, ChartColumnBig, ChevronLeft, Flame, FlaskConical, ImageIcon, SlidersHorizontal, Users, Videotape, } from "lucide-react"; import type * as React from "react"; import { ListBox, ListBoxItem } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { Image } from "~/components/Image"; import type { MainWeaponId } from "~/modules/in-game-lists/types"; import { filterWeapon } from "~/modules/in-game-lists/utils"; import { canonicalWeaponSplId, mainWeaponIds, weaponIdToBaseWeaponId, } from "~/modules/in-game-lists/weapon-ids"; import { ANALYZER_URL, LFG_PAGE, mainWeaponImageUrl, mySlugify, VODS_PAGE, weaponBuildPage, weaponBuildPopularPage, weaponBuildStatsPage, weaponParamsPage, } from "~/utils/urls"; import styles from "./GlobalSearch.module.css"; const WEAPON_DESTINATIONS = [ "builds", "popular", "stats", "analyzer", "params", "vods", "art", "lfg", ] as const; export type WeaponDestination = (typeof WEAPON_DESTINATIONS)[number]; export interface SelectedWeapon { id: MainWeaponId; name: string; englishName: string; slug: string; paramsSlug: string; } /** * Builds the {@link SelectedWeapon} for a main weapon id: its localized name plus the English-derived * url slugs (the build pages slug from the weapon's canonical id, the params page slug from its base * id). The caller's `t` must have the `weapons` namespace available. */ export function weaponToSelectedWeapon( id: MainWeaponId, t: TFunction, ): SelectedWeapon { return { id, name: t(`weapons:MAIN_${id}` as never), englishName: t(`weapons:MAIN_${id}` as never, { lng: "en" }), slug: mySlugify( t(`weapons:MAIN_${canonicalWeaponSplId(id)}` as never, { lng: "en" }), ), paramsSlug: mySlugify( t(`weapons:MAIN_${weaponIdToBaseWeaponId(id)}` as never, { lng: "en" }), ), }; } export function filterWeaponResults( query: string, t: TFunction<["common", "weapons"]>, ): SelectedWeapon[] { if (!query) return []; const matches: SelectedWeapon[] = []; for (const id of mainWeaponIds) { const isMatch = filterWeapon({ weapon: { type: "MAIN", id }, weaponName: t(`weapons:MAIN_${id}`), searchTerm: query, }); if (isMatch) { matches.push(weaponToSelectedWeapon(id, t)); } if (matches.length >= 10) break; } return matches; } function getWeaponDestinationUrl( key: WeaponDestination, weapon: SelectedWeapon, ): string { const destinations: Record = { builds: weaponBuildPage(weapon.slug), popular: weaponBuildPopularPage(weapon.slug), stats: weaponBuildStatsPage(weapon.slug), analyzer: `${ANALYZER_URL}?weapon=${weapon.id}`, params: weaponParamsPage(weapon.paramsSlug), vods: `${VODS_PAGE}?weapon=${weapon.id}`, art: `/art?tab=showcase&tag=${encodeURIComponent(weapon.englishName.toLowerCase())}`, lfg: `${LFG_PAGE}?q=w.${weapon.id}`, }; return destinations[key]; } export function WeaponDestinationMenu({ selectedWeapon, onBack, onSelect, listBoxRef, }: { selectedWeapon: SelectedWeapon; onBack: () => void; onSelect: (key: React.Key) => void; listBoxRef: React.RefObject; }) { const { t } = useTranslation(["common"]); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Escape") { e.stopPropagation(); onBack(); } }; return ( // biome-ignore lint/a11y/noStaticElementInteractions: keyboard navigation for Escape to go back
{selectedWeapon.name}
{t("common:pages.builds")}
{t("common:pages.popularBuilds")}
{t("common:pages.abilityStats")}
{t("common:pages.analyzer")}
{t("common:pages.params")}
{t("common:pages.vods")}
{t("common:pages.art")}
{t("common:pages.lfg")}
); } export function WeaponResultsList({ weaponResults, recentWeapons, onSelect, hasQuery, listBoxRef, }: { weaponResults: SelectedWeapon[]; recentWeapons: SelectedWeapon[]; onSelect: (key: React.Key) => void; hasQuery: boolean; listBoxRef: React.RefObject; }) { const { t } = useTranslation(["common"]); const displayedWeapons = hasQuery ? weaponResults : recentWeapons; const showNoResults = hasQuery && weaponResults.length === 0; const showHint = !hasQuery && recentWeapons.length === 0; return ( showNoResults ? (
{t("common:search.noResults")}
) : showHint ? (
{t("common:search.hint")}
) : null } > {displayedWeapons.map((weapon) => (
{weapon.name}
))}
); } const RECENT_WEAPONS_KEY = "command-palette-recent-weapons"; const MAX_RECENT_WEAPONS = 5; export function getRecentWeapons(): MainWeaponId[] { if (typeof window === "undefined") return []; try { const stored = localStorage.getItem(RECENT_WEAPONS_KEY); if (!stored) return []; const parsed = JSON.parse(stored); if (!Array.isArray(parsed)) return []; return parsed.filter( (id): id is MainWeaponId => typeof id === "number" && mainWeaponIds.includes(id as MainWeaponId), ); } catch { return []; } } export function saveRecentWeapon(weaponId: MainWeaponId): void { try { const recent = getRecentWeapons(); const filtered = recent.filter((id) => id !== weaponId); const updated = [weaponId, ...filtered].slice(0, MAX_RECENT_WEAPONS); localStorage.setItem(RECENT_WEAPONS_KEY, JSON.stringify(updated)); } catch { // localStorage may be unavailable } }