From bf7046c91bcef5d94ff3fc4b53f11d6b8029689e Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 27 Oct 2026 11:51:14 -0500 Subject: [PATCH] skin switcher --- packages/website/src/components/TankModel.tsx | 1 - .../HeroSection/components/Options/index.tsx | 4 +- .../TankopediaSandbox/index.module.css | 2 + .../components/TankopediaSandbox/index.tsx | 3 ++ .../TankopediaSkinSwitcher/index.module.css | 18 +++++++ .../index.tsx} | 49 ++++++++----------- packages/website/src/hooks/useEquipment.ts | 1 + .../icons/camouflages/[id].webp.ts | 6 +-- 8 files changed, 50 insertions(+), 34 deletions(-) create mode 100644 packages/website/src/components/TankopediaSkinSwitcher/index.module.css rename packages/website/src/components/{Tankopedia/HeroSection/components/Options/components/SkinSwitcher.tsx => TankopediaSkinSwitcher/index.tsx} (60%) diff --git a/packages/website/src/components/TankModel.tsx b/packages/website/src/components/TankModel.tsx index 61e3469f5..b67dd00d2 100644 --- a/packages/website/src/components/TankModel.tsx +++ b/packages/website/src/components/TankModel.tsx @@ -23,7 +23,6 @@ export function TankModel() { const turretContainer = useRef(null!); const gunContainer = useRef(null!); const tankModelDefinition = useDuelModel("protagonist"); - const trackModelDefinition = tankModelDefinition.tracks[track.id]; const turretModelDefinition = tankModelDefinition.turrets[protagonist.turret]; const gunModelDefinition = turretModelDefinition.guns[protagonist.gun]; const { gltf } = useModel(protagonist.tank); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx index c78d930a3..0c180dd1d 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -37,11 +37,12 @@ import { Tankopedia } from "../../../../../stores/tankopedia"; import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent"; import { TankopediaDisplay } from "../../../../../stores/tankopediaPersistent/constants"; import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps"; -import type { ThicknessRange } from "../../../../StaticArmor"; import { EqualIcon } from "../../../../EqualIcon"; import { ModuleButton } from "../../../../ModuleButtons/ModuleButton"; import { ScreenshotButton } from "../../../../ScreenshotButton"; import { SmallTankIcon } from "../../../../SmallTankIcon"; +import type { ThicknessRange } from "../../../../StaticArmor"; +import { SkinSwitcher } from "../../../../TankopediaSkinSwitcher"; import { TankSearch } from "../../../../TankSearch"; import { CustomShellButton } from "./components/CustomShellButton"; import { DynamicArmorSwitcher } from "./components/DynamicArmorSwitcher"; @@ -50,7 +51,6 @@ import { SidebarIconButton, SidebarIconGroup, } from "./components/SidebarIconButton"; -import { SkinSwitcher } from "./components/SkinSwitcher"; import { SpallLinerSwitcher } from "./components/SpallLinerSwitcher"; import { Thicknesses } from "./components/Thicknesses"; diff --git a/packages/website/src/components/TankopediaSandbox/index.module.css b/packages/website/src/components/TankopediaSandbox/index.module.css index e545b2a26..dfa8ee09f 100644 --- a/packages/website/src/components/TankopediaSandbox/index.module.css +++ b/packages/website/src/components/TankopediaSandbox/index.module.css @@ -2,6 +2,8 @@ height: calc(100dvh - 16rem); position: relative; + overflow: hidden; + .sandbox { position: absolute; top: 0; diff --git a/packages/website/src/components/TankopediaSandbox/index.tsx b/packages/website/src/components/TankopediaSandbox/index.tsx index 5e50ba17c..c5c54550f 100644 --- a/packages/website/src/components/TankopediaSandbox/index.tsx +++ b/packages/website/src/components/TankopediaSandbox/index.tsx @@ -20,6 +20,7 @@ import { ShotDisplay } from "../ShotDisplay"; import { SmartCanvas } from "../SmartCanvas"; import { StaticArmor, type ThicknessRange } from "../StaticArmor"; import { TankModel } from "../TankModel"; +import { TankopediaSkinSwitcher } from "../TankopediaSkinSwitcher"; import styles from "./index.module.css"; type TankSandboxProps = MaybeSkeletonComponentProps & { @@ -158,6 +159,8 @@ export function TankopediaSandbox({ > {!skeleton && } + + ); } diff --git a/packages/website/src/components/TankopediaSkinSwitcher/index.module.css b/packages/website/src/components/TankopediaSkinSwitcher/index.module.css new file mode 100644 index 000000000..4624bf65a --- /dev/null +++ b/packages/website/src/components/TankopediaSkinSwitcher/index.module.css @@ -0,0 +1,18 @@ +.skin-switcher { + pointer-events: auto; + + display: flex; + flex-direction: column; + + transition: var(--duration-brief); + + position: absolute; + top: 50%; + right: var(--space-4); + + transform: translate(calc(100% + var(--space-4)), -50%); + + &[data-visible="true"] { + transform: translate(0, -50%); + } +} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SkinSwitcher.tsx b/packages/website/src/components/TankopediaSkinSwitcher/index.tsx similarity index 60% rename from packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SkinSwitcher.tsx rename to packages/website/src/components/TankopediaSkinSwitcher/index.tsx index 762ed5ca8..979ff9f91 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/SkinSwitcher.tsx +++ b/packages/website/src/components/TankopediaSkinSwitcher/index.tsx @@ -1,22 +1,26 @@ import { alias } from "@blitzkit/core"; -import { Flex, Tooltip } from "@radix-ui/themes"; import { uniqBy } from "lodash-es"; -import { SKIN_NUMBER } from "../../../../../../constants/skinNumber"; -import { api } from "../../../../../../core/blitzkit/api"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { Duel } from "../../../../../../stores/duel"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants"; -import { SidebarIconButton } from "./SidebarIconButton"; +import { api } from "../../api/dynamic"; +import { SKIN_NUMBER } from "../../constants/skinNumber"; +import { DuelModule, useDuel } from "../../hooks/useDuel"; +import { useUnwrapper } from "../../hooks/useUnwrapper"; +import { Tankopedia } from "../../stores/tankopedia"; +import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants"; +import { SidebarIconButton } from "../Tankopedia/HeroSection/components/Options/components/SidebarIconButton"; +import { Tooltip } from "../Tooltip"; +import styles from "./index.module.css"; -const camouflageDefinitions = await api.camouflageDefinitions(); +const camouflageDefinitions = await api.camouflages(); + +export function TankopediaSkinSwitcher() { + const unwrap = useUnwrapper(); + + const tank = useDuel("protagonist", DuelModule.Tank); -export function SkinSwitcher() { - const tank = Duel.use((state) => state.protagonist.tank); const skin = Tankopedia.use((state) => state.requestedSkin); const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); const disturbed = Tankopedia.use((state) => state.disturbed); - const { unwrap } = useLocale(); + const skins = uniqBy( Object.values(camouflageDefinitions.camouflages).filter( (camouflage) => camouflage.tank_id === tank.id, @@ -27,20 +31,9 @@ export function SkinSwitcher() { if (skins.length === 0) return null; return ( - {skins.map((camouflage, index) => { const name = unwrap(camouflage.name!).replace( @@ -51,7 +44,7 @@ export function SkinSwitcher() { const bottom = index === skins.length - 1 ? "var(--radius-full)" : "0"; return ( - + ); })} - + ); } diff --git a/packages/website/src/hooks/useEquipment.ts b/packages/website/src/hooks/useEquipment.ts index cc6fe6136..caef19042 100644 --- a/packages/website/src/hooks/useEquipment.ts +++ b/packages/website/src/hooks/useEquipment.ts @@ -5,6 +5,7 @@ import { Tankopedia } from "../stores/tankopedia"; const equipment = await api.equipment(); const tanks = await api.tanks(); +// TODO: use enum here instead export const duelSides = ["protagonist", "antagonist"] as const; export type DuelSide = (typeof duelSides)[number]; diff --git a/packages/website/src/pages/[...chunk_api]/icons/camouflages/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/icons/camouflages/[id].webp.ts index caff69054..aee319ce6 100644 --- a/packages/website/src/pages/[...chunk_api]/icons/camouflages/[id].webp.ts +++ b/packages/website/src/pages/[...chunk_api]/icons/camouflages/[id].webp.ts @@ -1,15 +1,15 @@ import type { CamouflagesXml } from "@blitzkit/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import sharp from "sharp"; +import { api } from "../../../../api/dynamic"; +import { mixStaticPaths } from "../../../../astro/mixStaticPaths"; import { extractPackedIcon } from "../../../../core/blitz/extractPackedIcon"; import { parsePackedSpriteRect } from "../../../../core/blitz/parsePackedSpriteRect"; -import { api } from "../../../../core/blitzkit/api"; -import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; import { vfs } from "../../../../core/blitzkit/vfs"; import { getStaticPaths as _getStaticPaths } from "../../_index"; export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { - const camouflageDefinitions = await api.camouflageDefinitions(); + const camouflageDefinitions = await api.camouflages(); const camouflagesXml = await vfs.xml<{ root: CamouflagesXml }>( "Data/XML/item_defs/vehicles/common/camouflages.xml", );