Files
blitzkit/packages/website/src/components/TankopediaSkinSwitcher/index.tsx
T

115 lines
3.8 KiB
TypeScript
Raw Normal View History

import { alias } from "@blitzkit/core";
2026-11-09 22:07:56 -06:00
import { PauseIcon, PlayIcon } from "@radix-ui/react-icons";
import { uniqBy } from "lodash-es";
2026-10-27 11:51:14 -05:00
import { api } from "../../api/dynamic";
import { SKIN_NUMBER } from "../../constants/skinNumber";
import { DuelModule, useDuel } from "../../hooks/useDuel";
2026-11-09 22:07:56 -06:00
import { hasSkinAnimations } from "../../hooks/useSkin";
2026-10-27 11:51:14 -05:00
import { useUnwrapper } from "../../hooks/useUnwrapper";
import { Tankopedia } from "../../stores/tankopedia";
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
2026-10-27 16:49:52 -05:00
import { IconButton } from "../IconButton";
2026-10-27 11:51:14 -05:00
import { Tooltip } from "../Tooltip";
import styles from "./index.module.css";
2026-10-27 11:51:14 -05:00
const camouflageDefinitions = await api.camouflages();
export function TankopediaSkinSwitcher() {
const unwrap = useUnwrapper();
const tank = useDuel("protagonist", DuelModule.Tank);
2026-10-31 13:38:57 -05:00
const skin = Tankopedia.use((state) => state.loaders.skin.requested);
2026-10-31 13:57:56 -05:00
const showControls = Tankopedia.use((state) => state.showControls);
const display = Tankopedia.use((state) => state.loaders.display.loaded);
2026-11-09 22:07:56 -06:00
const loadedSkin = Tankopedia.use((state) => state.loaders.skin.loaded);
const loadedAttachment = Tankopedia.use(
(state) => state.loaders.attachment.loaded,
);
const playing = Tankopedia.use((state) => state.animate);
2026-10-27 11:51:14 -05:00
const skins = uniqBy(
Object.values(camouflageDefinitions.camouflages).filter(
(camouflage) => camouflage.tank_id === tank.id,
),
(camouflage) => unwrap(camouflage.name!),
);
if (skins.length === 0) return null;
2026-10-31 13:48:15 -05:00
const groups = [
skins.filter((camouflage) => !camouflage.attachment),
skins.filter((camouflage) => camouflage.attachment),
].filter((group) => group.length > 0);
2026-10-31 13:57:56 -05:00
const isVisible = showControls && display === TankopediaDisplay.Model;
2026-11-09 22:07:56 -06:00
const hasAnimations =
hasSkinAnimations(tank.id, loadedSkin) ||
hasSkinAnimations(tank.id, loadedAttachment);
return (
2026-10-31 13:57:56 -05:00
<div className={styles["skin-switcher"]} data-visible={isVisible}>
2026-10-31 13:48:15 -05:00
{groups.map((group, index) => (
<div className={styles.group} key={index}>
{group.map((camouflage) => {
const name = unwrap(camouflage.name!).replace(
"%(camo_num)",
SKIN_NUMBER,
);
2026-10-27 16:49:52 -05:00
2026-10-31 13:48:15 -05:00
const selected = skin === camouflage.id;
2026-10-31 13:48:15 -05:00
return (
<Tooltip key={camouflage.id} tooltip={name} side="left">
<IconButton
parentListChild="vertical"
color={selected ? undefined : "gray"}
variant={selected ? "solid" : "soft"}
highContrast={selected}
size="major"
onClick={() => {
Tankopedia.mutate((draft) => {
draft.loaders.skin.requested =
draft.loaders.skin.requested === camouflage.id
? undefined
: camouflage.id;
});
}}
>
<img
className={styles["camouflage-icon"]}
alt={name}
src={alias(
"api",
`/icons/camouflages/${camouflage.id}.webp`,
)}
/>
</IconButton>
</Tooltip>
);
})}
</div>
))}
2026-11-09 22:07:56 -06:00
{hasAnimations && (
<Tooltip
tooltip={playing ? "Pause animations" : "Play animations"}
side="left"
>
<IconButton
color="gray"
variant="soft"
size="major"
onClick={() => {
Tankopedia.mutate((draft) => {
draft.animate = !draft.animate;
});
}}
>
{playing ? <PauseIcon /> : <PlayIcon />}
</IconButton>
</Tooltip>
)}
2026-10-27 11:51:14 -05:00
</div>
);
}