cleaner equipment and modules ui

This commit is contained in:
tresabhi committed 2026-05-17 13:46:53 -05:00
1 parent 26cc0cc723
commit c100a1e655
4 files changed
+150 -122

No files matched your search

@@ -1,11 +1,11 @@
.price { .price {
display: flex; display: flex;
align-items: center;
gap: var(--space-3); gap: var(--space-3);
.entry { .entry {
display: block; display: block;
line-height: 1;
.wrapper { .wrapper {
display: flex; display: flex;
@@ -20,25 +20,32 @@
.slots { .slots {
display: flex; display: flex;
flex-direction: column;
gap: var(--space-2);
align-items: center;
.slot { .options {
aspect-ratio: 1 / 1; display: flex;
padding: var(--space-3);
font-size: var(--space-5); .slot {
aspect-ratio: 1 / 1;
padding: var(--space-3);
&:first-child { font-size: var(--space-5);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&:last-child { &:first-child {
border-top-left-radius: 0; border-top-right-radius: 0;
border-bottom-left-radius: 0; border-bottom-right-radius: 0;
} }
&[data-selected="true"] { &:last-child {
z-index: 1; border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
&[data-selected="true"] {
z-index: 1;
}
} }
} }
} }
@@ -53,37 +60,41 @@
.line { .line {
display: flex; display: flex;
align-items: center;
gap: var(--space-3); gap: var(--space-3);
.stage { .wrapper {
position: relative; display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2);
padding: var(--space-3); .stage {
position: relative;
font-size: 2em; padding: var(--space-3);
.icon { font-size: 2em;
width: 1em;
height: 1em;
padding: var(--space-3) 0; .icon {
width: 1em;
height: 1em;
transform: translate(var(--space-1), calc(-1 * var(--space-1))); transform: translate(var(--space-1), var(--space-1));
} }
.tier { .tier {
position: absolute; position: absolute;
left: var(--space-2); left: var(--space-2);
top: var(--space-2); top: var(--space-2);
} }
.price { .price {
position: absolute; position: absolute;
left: 50%; left: 50%;
bottom: var(--space-2); bottom: var(--space-2);
transform: translateX(-50%); transform: translateX(-50%);
}
} }
} }
} }
@@ -39,10 +39,7 @@ export function TankopediaLoadout() {
function Equipment() { function Equipment() {
const strings = useStrings(); const strings = useStrings();
const tank = useProtagonist(); const tank = useProtagonist();
const equipment = useEquipment( const equipment = useEquipment(tank.tank!);
tank.tank!.equipment_preset_catalog_id,
tank.tank!.equipment_price_preset_catalog_id,
);
return ( return (
<div className={styles.section}> <div className={styles.section}>
@@ -86,16 +83,25 @@ interface EquipmentSlotProps {
} }
function EquipmentSlot({ slot, rowIndex, columnIndex }: EquipmentSlotProps) { function EquipmentSlot({ slot, rowIndex, columnIndex }: EquipmentSlotProps) {
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
const tank = useProtagonist();
const equipment = useEquipment(tank.tank!);
const { price } = equipment.price.unlock_slot_prices[equipmentIndex];
return ( return (
<div className={styles.slots}> <div className={styles.slots}>
{slot.options_catalog_i_ds.map((id, index) => ( <div className={styles.options}>
<EquipmentOption {slot.options_catalog_i_ds.map((id, index) => (
key={id} <EquipmentOption
optionIndex={index} key={id}
rowIndex={rowIndex} optionIndex={index}
columnIndex={columnIndex} rowIndex={rowIndex}
/> columnIndex={columnIndex}
))} />
))}
</div>
<Price price={price!} />
</div> </div>
); );
} }
@@ -112,11 +118,17 @@ function EquipmentOption({
columnIndex, columnIndex,
}: EquipmentOptionProps) { }: EquipmentOptionProps) {
const equipmentIndex = rowIndex * equipmentColumns + columnIndex; const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
const tank = useProtagonist();
const equipment = useEquipment(tank.tank!);
const isSelected = Tankopedia.use( const isSelected = Tankopedia.use(
(state) => (state) =>
equipmentIndex in state.protagonist.equipment && equipmentIndex in state.protagonist.equipment &&
state.protagonist.equipment[equipmentIndex] === optionIndex, state.protagonist.equipment[equipmentIndex] === optionIndex,
); );
const name =
equipment.preset.slots[equipmentIndex].options_catalog_i_ds[optionIndex];
const equipmentData = equipment.equipments[name];
return ( return (
<Button <Button
@@ -292,87 +304,91 @@ function LineElement({ index, lineName, stage }: LineElementProps) {
} }
return ( return (
<Button <div className={styles.wrapper}>
color={isSelected ? undefined : "gray"} <Button
variant={isSelected ? "surface" : "soft"} className={styles.stage}
radius="1" color={isSelected ? undefined : "gray"}
className={styles.stage} variant={isSelected ? "surface" : "soft"}
onClick={() => { radius="1"
Tankopedia.mutate((draft) => { onClick={() => {
const tankData = tank.tank!; Tankopedia.mutate((draft) => {
const tankData = tank.tank!;
if (isAlternativeLine(lineName)) { if (isAlternativeLine(lineName)) {
const originalLine = originalLineName(lineName)!; const originalLine = originalLineName(lineName)!;
draft.protagonist.alternates[originalLine] = true; draft.protagonist.alternates[originalLine] = true;
draft.protagonist.upgrades[lineName] = index; draft.protagonist.upgrades[lineName] = index;
} else { } else {
draft.protagonist.alternates[lineName] = false; draft.protagonist.alternates[lineName] = false;
draft.protagonist.upgrades[lineName] = index; draft.protagonist.upgrades[lineName] = index;
} }
for (const required of stage.required_upgrades) { for (const required of stage.required_upgrades) {
for (const line of tankData.upgrade_lines) { for (const line of tankData.upgrade_lines) {
let i = 0; let i = 0;
for (const candidateStage of line.stages) { for (const candidateStage of line.stages) {
if (candidateStage.tech_name === required) { if (candidateStage.tech_name === required) {
draft.protagonist.upgrades[line.name] = Math.max( draft.protagonist.upgrades[line.name] = Math.max(
draft.protagonist.upgrades[line.name], draft.protagonist.upgrades[line.name],
i, i,
); );
}
i++;
} }
i++;
} }
} }
}
for (const line of tankData.upgrade_lines) { for (const line of tankData.upgrade_lines) {
if (draft.protagonist.alternates[line.name]) { if (draft.protagonist.alternates[line.name]) {
continue; continue;
}
let i = draft.protagonist.upgrades[line.name];
while (i > 0) {
const currentStage = line.stages[i];
const valid = currentStage.required_upgrades.every(
(required) => {
for (const otherLine of tankData.upgrade_lines) {
const idx = draft.protagonist.upgrades[otherLine.name];
if (otherLine.stages[idx]?.tech_name === required) {
return true;
}
}
return false;
},
);
if (valid) break;
i--;
}
draft.protagonist.upgrades[line.name] = i;
} }
});
}}
>
<img
className={styles.icon}
src={`/api/tanks/modules/${stage.stage_type}.webp`}
/>
{/* {stage.display_name} */}
let i = draft.protagonist.upgrades[line.name]; <Text weight="light" lowContrast size="minor" className={styles.tier}>
{isAlternativeLine(lineName)
while (i > 0) { ? strings.tanks.loadout.alternative
const currentStage = line.stages[i]; : romanize(stage.number)}
</Text>
const valid = currentStage.required_upgrades.every((required) => { </Button>
for (const otherLine of tankData.upgrade_lines) {
const idx = draft.protagonist.upgrades[otherLine.name];
if (otherLine.stages[idx]?.tech_name === required) {
return true;
}
}
return false;
});
if (valid) break;
i--;
}
draft.protagonist.upgrades[line.name] = i;
}
});
}}
>
<img
className={styles.icon}
src={`/api/tanks/modules/${stage.stage_type}.webp`}
/>
{/* {stage.display_name} */}
<Text weight="light" lowContrast size="minor" className={styles.tier}>
{isAlternativeLine(lineName)
? strings.tanks.loadout.alternative
: romanize(stage.number)}
</Text>
{price && <Price className={styles.price} price={price} />} {price && <Price className={styles.price} price={price} />}
</Button> </div>
); );
} }
@@ -1,10 +1,11 @@
import type { TankCatalogComponent } from "@protos/blitz_static_tank_component";
import { api } from "../api/dynamic"; import { api } from "../api/dynamic";
import { useAwait } from "./useAwait"; import { useAwait } from "./useAwait";
export function useEquipment(presetId: string, priceId: string) { export function useEquipment(tank: TankCatalogComponent) {
const equipment = useAwait(() => api.equipment(), "equipment"); const equipment = useAwait(() => api.equipment(), "equipment");
const preset = equipment.presets[presetId]; const preset = equipment.presets[tank.equipment_preset_catalog_id];
const price = equipment.prices[priceId]; const price = equipment.prices[tank.equipment_price_preset_catalog_id];
return { preset, price, equipment }; return { preset, price, equipments: equipment.equipments };
} }