feat: clean up tiers ui

This commit is contained in:
TrèsAbhi committed 2026-01-04 15:11:20 -06:00
1 parent 5a0b309b0b
commit 7997f490fc
4 files changed
+147 -133

No files matched your search

+4 -1
View File
@@ -930,7 +930,10 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!", "description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel", "cancel": "Cancel",
"reveal": "Reveal" "reveal": "Reveal"
} },
"clear": "Clear",
"select_all": "Select all",
"tier": "Tier {tier}"
}, },
"tankopedia": { "tankopedia": {
+123 -59
View File
@@ -9,13 +9,17 @@ import {
ArrowRightIcon, ArrowRightIcon,
EyeOpenIcon, EyeOpenIcon,
MagnifyingGlassIcon, MagnifyingGlassIcon,
MixerVerticalIcon,
PaperPlaneIcon, PaperPlaneIcon,
StarFilledIcon,
TrashIcon,
} from "@radix-ui/react-icons"; } from "@radix-ui/react-icons";
import { import {
AlertDialog, AlertDialog,
Box, Box,
Button, Button,
Card, Card,
DropdownMenu,
Flex, Flex,
IconButton, IconButton,
Spinner, Spinner,
@@ -23,7 +27,7 @@ import {
TextField, TextField,
} from "@radix-ui/themes"; } from "@radix-ui/themes";
import fuzzysort from "fuzzysort"; import fuzzysort from "fuzzysort";
import { debounce } from "lodash-es"; import { debounce, times } from "lodash-es";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions";
import { awaitableTankNames } from "../core/awaitables/tankNames"; import { awaitableTankNames } from "../core/awaitables/tankNames";
@@ -41,11 +45,7 @@ const [tankNames, tankDefinitions] = await Promise.all([
const ids = Object.keys(tankDefinitions.tanks); const ids = Object.keys(tankDefinitions.tanks);
interface GuesserProps { export function Guesser() {
selectedTiers: number[];
}
export function Guesser({ selectedTiers }: GuesserProps) {
const tank = Guess.use((state) => state.tank); const tank = Guess.use((state) => state.tank);
const guessState = Guess.use((state) => state.guessState); const guessState = Guess.use((state) => state.guessState);
const correctGuesses = Guess.use((state) => state.correctGuesses); const correctGuesses = Guess.use((state) => state.correctGuesses);
@@ -57,6 +57,7 @@ export function Guesser({ selectedTiers }: GuesserProps) {
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const [results, setResults] = useState<TankDefinition[] | null>(null); const [results, setResults] = useState<TankDefinition[] | null>(null);
const [selected, setSelected] = useState<TankDefinition | null>(null); const [selected, setSelected] = useState<TankDefinition | null>(null);
const tiers = Guess.use((state) => state.tiers);
const requestSearch = useCallback(() => { const requestSearch = useCallback(() => {
setSelected(null); setSelected(null);
@@ -101,60 +102,60 @@ export function Guesser({ selectedTiers }: GuesserProps) {
width="100%" width="100%"
p="4" p="4"
maxWidth="25rem" maxWidth="25rem"
gap="4" gap="3"
> >
<Box position="relative"> <Box position="relative">
{results !== null && (
<Card variant="classic">
<Box py="2" px="3">
{results.length === 0 && (
<Flex justify="center">
<Text color="gray">
{strings.website.tools.guess.search.no_results}
</Text>
</Flex>
)}
{results !== null && ( <SearchResults.Root>
<Card variant="classic"> {results.map((result) => {
<Box py="2" px="3"> const Icon = classIcons[result.class];
{results.length === 0 && (
<Flex justify="center">
<Text color="gray">
{strings.website.tools.guess.search.no_results}
</Text>
</Flex>
)}
<SearchResults.Root> return (
{results.map((result) => { <SearchResults.Item
const Icon = classIcons[result.class]; key={result.id}
onClick={() => {
if (!input.current) return;
return ( input.current.value = unwrap(result.name);
<SearchResults.Item setSelected(result);
key={result.id} setResults(null);
onClick={() => { }}
if (!input.current) return; text={unwrap(result.name)}
prefix={
input.current.value = unwrap(result.name); <img
setSelected(result); style={{ width: "1em", height: "1em" }}
setResults(null); src={asset(`flags/circle/${result.nation}.webp`)}
}} />
text={unwrap(result.name)} }
prefix={ discriminator={
<img <Flex
style={{ width: "1em", height: "1em" }} align="center"
src={asset(`flags/circle/${result.nation}.webp`)} gap="1"
/> width="38px"
} justify="center"
discriminator={ >
<Flex <Icon width="1em" height="1em" />
align="center" {TIER_ROMAN_NUMERALS[result.tier]}
gap="1" </Flex>
width="38px" }
justify="center" />
> );
<Icon width="1em" height="1em" /> })}
{TIER_ROMAN_NUMERALS[result.tier]} </SearchResults.Root>
</Flex> </Box>
} </Card>
/> )}
); </Box>
})}
</SearchResults.Root>
</Box>
</Card>
)}
<Flex justify="center" style={{ userSelect: "none" }}> <Flex justify="center" style={{ userSelect: "none" }}>
<Text> <Text>
@@ -167,11 +168,75 @@ export function Guesser({ selectedTiers }: GuesserProps) {
</Flex> </Flex>
<Flex gap="3"> <Flex gap="3">
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger>
<IconButton size="3" variant="surface" color="gray">
<MixerVerticalIcon />
</IconButton>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{times(10, (index) => {
const tier = 10 - index;
const isSelected = tiers.includes(tier);
return (
<DropdownMenu.CheckboxItem
checked={isSelected}
key={tier}
onClick={(event) => {
event.preventDefault();
Guess.mutate((draft) => {
if (isSelected) {
draft.tiers = draft.tiers.filter((t) => t !== tier);
} else {
draft.tiers = [...draft.tiers, tier];
}
});
}}
>
{literals(strings.website.tools.guess.tier, {
tier: TIER_ROMAN_NUMERALS[tier],
})}
</DropdownMenu.CheckboxItem>
);
})}
<DropdownMenu.Separator />
<DropdownMenu.Item
onClick={(event) => {
event.preventDefault();
Guess.mutate((draft) => {
draft.tiers = Guess.initial.tiers;
});
}}
>
<StarFilledIcon /> {strings.website.tools.guess.select_all}
</DropdownMenu.Item>
<DropdownMenu.Item
color="red"
onClick={(event) => {
event.preventDefault();
Guess.mutate((draft) => {
draft.tiers = [];
});
}}
>
<TrashIcon /> {strings.website.tools.guess.clear}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<TextField.Root <TextField.Root
disabled={guessState !== GuessState.NotGuessed} disabled={guessState !== GuessState.NotGuessed}
ref={input} ref={input}
onChange={requestSearch} onChange={requestSearch}
style={{ flex: 1 }} style={{ flex: 1, minWidth: "14rem" }}
placeholder={strings.website.tools.guess.search.placeholder} placeholder={strings.website.tools.guess.search.placeholder}
size="3" size="3"
variant="classic" variant="classic"
@@ -225,7 +290,7 @@ export function Guesser({ selectedTiers }: GuesserProps) {
<Button <Button
size="3" size="3"
disabled={selectedTiers.length === 0} disabled={tiers.length === 0}
color={ color={
guessState === GuessState.NotGuessed && selected === null guessState === GuessState.NotGuessed && selected === null
? "red" ? "red"
@@ -247,7 +312,7 @@ export function Guesser({ selectedTiers }: GuesserProps) {
} else { } else {
const filteredIds = ids.filter((id) => { const filteredIds = ids.filter((id) => {
const tank = tankDefinitions.tanks[Number(id)]; const tank = tankDefinitions.tanks[Number(id)];
return selectedTiers.includes(tank.tier); return tiers.includes(tank.tier);
}); });
const id = Number( const id = Number(
filteredIds[Math.floor(Math.random() * filteredIds.length)] filteredIds[Math.floor(Math.random() * filteredIds.length)]
@@ -283,7 +348,6 @@ export function Guesser({ selectedTiers }: GuesserProps) {
)} )}
</Button> </Button>
</Flex> </Flex>
</Box>
</Flex> </Flex>
); );
} }
@@ -1,8 +1,5 @@
import { Box, DropdownMenu, Flex, Heading, IconButton, Text } from "@radix-ui/themes"; import { Box, Flex, Heading, Text } from "@radix-ui/themes";
import { Suspense, useEffect, useState } from "react"; import { Suspense, useEffect } from "react";
import { MixerVerticalIcon } from "@radix-ui/react-icons";
import { times } from "lodash-es";
import { TIER_ROMAN_NUMERALS } from "@blitzkit/core";
import { GuessBackground } from "../../../components/GuessBackground"; import { GuessBackground } from "../../../components/GuessBackground";
import { Guesser } from "../../../components/Guesser"; import { Guesser } from "../../../components/Guesser";
import { GuessRenderer } from "../../../components/GuessRenderer"; import { GuessRenderer } from "../../../components/GuessRenderer";
@@ -58,26 +55,28 @@ function Container({ skeleton }: MaybeSkeletonComponentProps) {
} }
function Content({ skeleton }: MaybeSkeletonComponentProps) { function Content({ skeleton }: MaybeSkeletonComponentProps) {
const { unwrap, strings } = useLocale(); const { unwrap } = useLocale();
const tank = Guess.use((state) => state.tank); const tank = Guess.use((state) => state.tank);
const guessState = Guess.use((state) => state.guessState); const guessState = Guess.use((state) => state.guessState);
const isRevealed = guessState !== GuessState.NotGuessed; const isRevealed = guessState !== GuessState.NotGuessed;
const name = unwrap(tank.name); const name = unwrap(tank.name);
const fontSize = `min(48vh, ${55 / name.length}vw)`; const fontSize = `min(48vh, ${55 / name.length}vw)`;
const transitionDuration = isRevealed ? "2s" : undefined; const transitionDuration = isRevealed ? "2s" : undefined;
const [selectedTiers, setSelectedTiers] = useState<number[]>([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); const tiers = Guess.use((state) => state.tiers);
useEffect(() => { useEffect(() => {
if (selectedTiers.length === 0) return; if (tiers.length === 0) return;
const filteredIds = ids.filter((id) => { const filteredIds = ids.filter((id) => {
const tank = tankDefinitions.tanks[Number(id)]; const tank = tankDefinitions.tanks[Number(id)];
return selectedTiers.includes(tank.tier); return tiers.includes(tank.tier);
}); });
if (filteredIds.length === 0) return; if (filteredIds.length === 0) return;
const id = Number(filteredIds[Math.floor(Math.random() * filteredIds.length)]); const id = Number(
filteredIds[Math.floor(Math.random() * filteredIds.length)]
);
const newTank = tankDefinitions.tanks[id]; const newTank = tankDefinitions.tanks[id];
Guess.mutate((draft) => { Guess.mutate((draft) => {
@@ -85,72 +84,13 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
draft.guessState = GuessState.NotGuessed; draft.guessState = GuessState.NotGuessed;
draft.helpingReveal = false; draft.helpingReveal = false;
}); });
}, [selectedTiers]); }, [tiers]);
return ( return (
<Flex flexGrow="1" position="relative" overflow="hidden"> <Flex flexGrow="1" position="relative" overflow="hidden">
<GuessBackground /> <GuessBackground />
<DropdownMenu.Root modal={false}> {tiers.length === 0 && (
<DropdownMenu.Trigger>
<IconButton
size="3"
variant="surface"
color="gray"
style={{
position: "fixed",
top: "80px",
right: "16px",
zIndex: 1000,
}}
>
<MixerVerticalIcon />
</IconButton>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{times(10, (index) => {
const tier = 10 - index;
const isSelected = selectedTiers.includes(tier);
return (
<DropdownMenu.Item
key={tier}
color={isSelected ? "blue" : undefined}
onClick={(event) => {
event.preventDefault();
setSelectedTiers((prev) => {
if (prev.includes(tier)) {
return prev.filter((t) => t !== tier);
} else {
return [...prev, tier];
}
});
}}
>
{TIER_ROMAN_NUMERALS[tier]}
</DropdownMenu.Item>
);
})}
<DropdownMenu.Separator />
<DropdownMenu.Item
onClick={() => setSelectedTiers([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])}
>
Select All
</DropdownMenu.Item>
<DropdownMenu.Item
color="red"
onClick={() => setSelectedTiers([])}
>
Clear All
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
{selectedTiers.length === 0 && (
<Box <Box
position="absolute" position="absolute"
top="50%" top="50%"
@@ -161,7 +101,11 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
textAlign: "center", textAlign: "center",
}} }}
> >
<Text size="8" weight="bold" style={{ color: "white", textShadow: "0 2px 8px rgba(0,0,0,0.5)" }}> <Text
size="8"
weight="bold"
style={{ color: "white", textShadow: "0 2px 8px rgba(0,0,0,0.5)" }}
>
Select tier(s) to start playing Select tier(s) to start playing
</Text> </Text>
</Box> </Box>
@@ -216,7 +160,7 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
{name} {name}
</Heading> </Heading>
<Guesser selectedTiers={selectedTiers} /> <Guesser />
</Flex> </Flex>
); );
} }
+3
View File
@@ -1,4 +1,5 @@
import type { TankDefinition } from "@blitzkit/core"; import type { TankDefinition } from "@blitzkit/core";
import { times } from "lodash-es";
import { Varuna } from "varuna"; import { Varuna } from "varuna";
export enum GuessState { export enum GuessState {
@@ -14,6 +15,7 @@ export interface Guess {
correctGuesses: number; correctGuesses: number;
streak: number; streak: number;
helpingReveal: boolean; helpingReveal: boolean;
tiers: number[];
} }
export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({ export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
@@ -23,4 +25,5 @@ export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
correctGuesses: 0, correctGuesses: 0,
streak: 0, streak: 0,
helpingReveal: false, helpingReveal: false,
tiers: times(10, (index) => index + 1),
})); }));