feat: module icons

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
TrèsAbhiandCopilot committed 2026-04-29 16:27:13 -05:00
1 parent 3a60535564
commit 44e7cd7505
16 files changed
+206 -14

No files matched your search

+4
View File
@@ -193,6 +193,10 @@
},
"tanks": {
"loadout": {
"modules": "Modules"
},
"search": {
"placeholder": "Search tanks..."
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

@@ -90,3 +90,23 @@ import "@fontsource-variable/roboto-flex";
--tank-card-min-width: 8rem;
}
</style>
<style is:global>
.composable-radius {
&[data-radius="1"] {
border-radius: var(--space-3);
}
&[data-radius="2"] {
border-radius: var(--space-4);
}
&[data-radius="3"] {
border-radius: var(--space-5);
}
&[data-radius="max"] {
border-radius: var(--max-size);
}
}
</style>
@@ -7,7 +7,6 @@
border-radius: var(--max-size);
padding: var(--space-3) var(--space-4);
background-color: var(--accent-9);
border: none;
font-family: var(--font-family-regular);
@@ -23,4 +22,14 @@
&[data-light-text="true"] {
color: var(--white-a12);
}
&[data-variant="solid"] {
background-color: var(--accent-8);
}
&[data-variant="surface"] {
background-color: var(--accent-3);
outline: var(--space-1) solid var(--accent-4);
}
}
@@ -5,7 +5,8 @@ import styles from "./index.module.css";
export interface ButtonProps extends ComponentProps<"button"> {
color?: Color;
variant?: "solid";
radius?: `${1 | 2 | 3}` | "max";
variant?: "solid" | "surface";
}
export const LIGHT_TEXT_COLORS = new Set<Color>([
@@ -28,14 +29,16 @@ export function Button({
color,
variant = "solid",
style,
radius = "max",
...props
}: ButtonProps) {
return (
<button
className={classNames(styles.button, className)}
className={classNames("composable-radius", styles.button, className)}
data-color={color}
data-variant={variant}
data-light-text={variant === "solid"}
data-radius={radius}
style={{
backgroundColor: color ? `var(--${color}-9)` : undefined,
...style,
@@ -0,0 +1,7 @@
.icon-button {
padding: var(--space-3);
aspect-ratio: 1 / 1;
font-size: 2rem;
}
@@ -0,0 +1,18 @@
import { classNames } from "../../core/ui/classNames";
import type { Color } from "../../core/ui/color";
import { Button, type ButtonProps } from "../Button";
import styles from "./index.module.css";
export interface IconButtonProps extends ButtonProps {
color?: Color;
variant?: "solid";
}
export function IconButton({ className, ...props }: ButtonProps) {
return (
<Button
className={classNames(className, styles["icon-button"])}
{...props}
/>
);
}
@@ -0,0 +1,20 @@
.lines {
display: flex;
gap: var(--space-3);
.line {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2);
.stage {
img {
width: 1em;
height: 1em;
}
}
}
}
@@ -0,0 +1,110 @@
import type { UpgradeLine } from "@blitzkit/closed/protos/game/proto/legacy/blitz_static_tank_upgrade_line";
import { CaretDownIcon } from "@radix-ui/react-icons";
import { api } from "../../core/api/dynamic";
import { useAwait } from "../../hooks/useAwait";
import { useStrings } from "../../hooks/useStrings";
import { Tankopedia } from "../../stores/tankopedia";
import { Heading } from "../Heading";
import { IconButton } from "../IconButton";
import { Section } from "../Section";
import styles from "./index.module.css";
export function TankopediaLoadout() {
const strings = useStrings();
const id = Tankopedia.use((state) => state.protagonist.id);
const tank = useAwait(() => api.tank(id), `tank-${id}`);
return (
<Section>
<Heading size="3">{strings.tanks.loadout.modules}</Heading>
<div className={styles.lines}>
{tank.tank!.upgrade_lines.map((line) => (
<Line line={line} />
))}
</div>
</Section>
);
}
interface LineProps {
line: UpgradeLine;
}
function Line({ line }: LineProps) {
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
const id = Tankopedia.use((state) => state.protagonist.id);
const tank = useAwait(() => api.tank(id), `tank-${id}`);
return (
<div className={styles.line}>
{line.stages.map((stage, index) => {
const isSelected = upgrades[line.name] === index;
return (
<>
{index !== 0 && <CaretDownIcon width="1em" height="1em" />}
<IconButton
variant={isSelected ? "solid" : "surface"}
radius="1"
className={styles.stage}
onClick={() => {
Tankopedia.mutate((draft) => {
draft.protagonist.upgrades[line.name] = index;
for (const required of stage.required_upgrades) {
for (const line of tank.tank!.upgrade_lines) {
let i = 0;
for (const stage of line.stages) {
if (stage.tech_name === required) {
draft.protagonist.upgrades[line.name] = Math.max(
draft.protagonist.upgrades[line.name],
i,
);
}
i++;
}
}
}
for (const line of tank.tank!.upgrade_lines) {
let i = draft.protagonist.upgrades[line.name];
while (i > 0) {
const currentStage = line.stages[i];
const valid = currentStage.required_upgrades.every(
(required) => {
for (const line of tank.tank!.upgrade_lines) {
const idx = draft.protagonist.upgrades[line.name];
if (line.stages[idx]?.tech_name === required) {
return true;
}
}
return false;
},
);
if (valid) break;
i--;
}
draft.protagonist.upgrades[line.name] = i;
}
});
}}
>
<img src={`/api/tanks/modules/${stage.stage_type}.webp`} />
</IconButton>
</>
);
})}
</div>
);
}
+6 -6
View File
@@ -46,7 +46,7 @@ export abstract class AbstractAPI {
return supported;
}
@Cache<[string, string, boolean]>((...args) => args.join("-"))
@Cache()
async groupedGameStrings(locale: string, group: string, prefix: boolean) {
this.assertLocale(locale);
@@ -64,9 +64,7 @@ export abstract class AbstractAPI {
}
}
export function Cache<Arguments extends unknown[]>(
discriminator: (...args: Arguments) => string = (...args) => args.join("-"),
) {
export function Cache<Arguments extends unknown[]>(disableInDev = false) {
const cache = new WeakMap<object, Map<string, Promise<unknown>>>();
return function (
@@ -77,7 +75,7 @@ export function Cache<Arguments extends unknown[]>(
const original = descriptor.value;
descriptor.value = function (...args: Arguments) {
const key = discriminator(...args);
const key = args.join("-");
let thisCache = cache.get(this);
@@ -86,7 +84,9 @@ export function Cache<Arguments extends unknown[]>(
cache.set(this, thisCache);
}
if (thisCache.has(key)) {
const disabled = disableInDev && import.meta.env.DEV;
if (thisCache.has(key) && !disabled) {
return thisCache.get(key);
}
+1 -1
View File
@@ -186,7 +186,7 @@ export class ServerAPI extends AbstractAPI {
return tiers;
}
@Cache()
@Cache(true)
async strings(locale: string) {
const localized = (await globbedStrings[
`../../../../i18n/strings/${locale}.json`
@@ -1,6 +1,5 @@
import { Grade } from "@blitzkit/closed/protos/game/proto/legacy/blitz_static_standard_grades_enum";
import { StageParameters_StageType } from "@blitzkit/closed/protos/game/proto/legacy/blitz_static_tank_upgrade_single_stage";
import { useMemo } from "react";
import { TankopediaLoadout } from "../../../../components/TankopediaLoadout";
import { api } from "../../../../core/api/dynamic";
import { characteristicsOrder } from "../../../../core/tankopedia/characteristicsOrder";
import { computeCharacteristics } from "../../../../core/tankopedia/computeCharacteristics";
@@ -48,7 +47,9 @@ function Content() {
<br />
<h2>upgrades</h2>
<TankopediaLoadout />
{/* <h2>upgrades</h2>
{protagonistTank.tank!.upgrade_lines.map((line) => {
return (
@@ -145,7 +146,7 @@ function Content() {
</div>
</>
);
})}
})} */}
<h2>characteristics</h2>