thicknesses

This commit is contained in:
tresabhi committed 2026-11-09 23:05:19 -06:00
1 parent 263475ec3c
commit aa982d73de
4 files changed
+200 -204

No files matched your search

@@ -1,204 +0,0 @@
import { literals } from "@blitzkit/i18n/src/literals";
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from "@radix-ui/react-icons";
import { Button, Checkbox, Flex, Text } from "@radix-ui/themes";
import { Suspense } from "react";
import { useLocale } from "../../../hooks/useLocale";
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 "../../TankStaticArmor";
import { DynamicArmorSwitcher } from "./DynamicArmorSwitcher";
type ThicknessesProps = MaybeSkeletonComponentProps & {
thicknessRange: ThicknessRange;
};
export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
const display = Tankopedia.use((state) => state.display);
const showExternalModules = TankopediaPersistent.use(
(state) => state.showExternalModules,
);
const showSpacedArmor = TankopediaPersistent.use(
(state) => state.showSpacedArmor,
);
const showPrimaryArmor = TankopediaPersistent.use(
(state) => state.showPrimaryArmor,
);
const editStatic = Tankopedia.use((state) => state.editStatic);
const { strings } = useLocale();
return (
<Flex
position="absolute"
right="0"
top="50%"
mr="4"
style={{
transform: `translate(${display === TankopediaDisplay.StaticArmor ? "0" : "calc(100% + var(--space-4))"}, -50%)`,
userSelect: "none",
transitionDuration: "200ms",
}}
direction="column"
gap="5"
align="end"
>
<Flex direction="column" align="end" gap="3">
<Flex
height="64px"
gap="2"
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: "pointer" }}
onClick={() => {
TankopediaPersistent.mutate((draft) => {
draft.showPrimaryArmor = !draft.showPrimaryArmor;
});
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
});
}}
>
<Flex direction="column" align="end" justify="between">
<Text color="gray" size="1">
{literals(strings.common.units.mm, {
value: (thicknessRange.value * 1.5).toFixed(0),
})}
</Text>
<Text size="1">
{strings.website.tools.tankopedia.sandbox.static.primary}
</Text>
<Text color="gray" size="1">
{literals(strings.common.units.mm, { value: 0 })}
</Text>
</Flex>
<Flex
align="center"
width="1rem"
style={{
borderRadius: "var(--radius-full)",
overflow: "hidden",
background: `linear-gradient(rgb(64, 0, 0), rgb(255, 0, 0) 33%, rgb(255, 255, 0) 66%, rgb(0, 255, 0))`,
color: "black",
}}
>
{showPrimaryArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
</Flex>
</Flex>
<Flex
height="64px"
gap="2"
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: "pointer" }}
onClick={() => {
TankopediaPersistent.mutate((draft) => {
draft.showSpacedArmor = !draft.showSpacedArmor;
});
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
});
}}
>
<Flex direction="column" align="end" justify="between">
<Text color="gray" size="1">
{literals(strings.common.units.mm, {
value: thicknessRange.value.toFixed(0),
})}
</Text>
<Text size="1">
{strings.website.tools.tankopedia.sandbox.static.spaced}
</Text>
<Text color="gray" size="1">
{literals(strings.common.units.mm, { value: 0 })}
</Text>
</Flex>
<Flex
align="center"
width="1rem"
style={{
borderRadius: "var(--radius-full)",
overflow: "hidden",
background: `linear-gradient(rgb(32, 0, 225), rgb(255, 0, 255))`,
}}
>
{showSpacedArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
</Flex>
</Flex>
<Flex
gap="2"
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: "pointer" }}
onClick={() => {
TankopediaPersistent.mutate((draft) => {
draft.showExternalModules = !draft.showExternalModules;
});
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
});
}}
>
<Flex direction="column" align="end" justify="between">
<Text size="1">
{strings.website.tools.tankopedia.sandbox.static.external}
</Text>
</Flex>
<Flex
justify="center"
width="1rem"
height="1rem"
style={{
borderRadius: "var(--radius-full)",
overflow: "hidden",
backgroundColor: "#c0c0c0",
color: "black",
}}
>
{showExternalModules ? <EyeOpenIcon /> : <EyeClosedIcon />}
</Flex>
</Flex>
</Flex>
<Flex direction="column" align="end" gap="2">
<Flex align="center" gap="2">
<Flex
gap="2"
style={{ cursor: "pointer" }}
onClick={() => {
Tankopedia.mutate((draft) => {
draft.editStatic = !draft.editStatic;
draft.highlightArmor = undefined;
});
}}
>
<Text size="1">
{strings.website.tools.tankopedia.sandbox.static.edit.label}
</Text>
<Checkbox variant="classic" size="2" checked={editStatic} />
</Flex>
</Flex>
<Button
color="red"
size="1"
variant="ghost"
onClick={() => {
Tankopedia.mutate((draft) => {
draft.editStatic = false;
draft.model = Tankopedia.initial.model;
});
}}
>
<ReloadIcon />
{strings.website.tools.tankopedia.sandbox.static.edit.reset}
</Button>
</Flex>
{!skeleton && (
<Suspense>
<DynamicArmorSwitcher />
</Suspense>
)}
</Flex>
);
}
@@ -26,6 +26,7 @@ import { TankopediaOptions } from "../TankopediaOptions";
import { TankopediaSkinSwitcher } from "../TankopediaSkinSwitcher";
import { TankSandboxLoader } from "../TankSandboxLoader";
import { TankStaticArmor, type ThicknessRange } from "../TankStaticArmor";
import { Thicknesses } from "../Thicknesses";
import styles from "./index.module.css";
type TankSandboxProps = MaybeSkeletonComponentProps & {
@@ -176,6 +177,7 @@ export function TankopediaSandbox({
<TankSandboxLoader />
<TankopediaSkinSwitcher />
<Thicknesses thicknessRange={thicknessRange} />
<TankopediaAntagonistControl
antagonistCharacteristics={antagonistCharacteristics}
/>
@@ -0,0 +1,67 @@
.thicknesses {
pointer-events: auto;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: var(--space-4);
position: absolute;
right: var(--space-4);
top: 50%;
user-select: none;
transition: var(--duration-brief);
/* slides off the right edge when the static armor view isn't active */
transform: translate(calc(100% + var(--space-4)), -50%);
&[data-visible="true"] {
transform: translate(0, -50%);
}
}
.toggle {
display: flex;
gap: var(--space-3);
padding: 0;
border: none;
background: none;
font: inherit;
cursor: pointer;
opacity: 0.5;
&[data-visible="true"] {
opacity: 1;
}
&[data-scaled="true"] {
height: 4rem;
}
}
.labels {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
}
.swatch {
display: flex;
align-items: center;
justify-content: center;
width: 1rem;
border-radius: var(--radius-max);
overflow: hidden;
color: black;
/* the unscaled swatch is just an icon-sized dot */
.toggle:not([data-scaled="true"]) & {
height: 1rem;
}
}
@@ -0,0 +1,131 @@
import { literals } from "@blitzkit/i18n/src/literals";
import { EyeClosedIcon, EyeOpenIcon } from "@radix-ui/react-icons";
import { useCallback, type ReactNode } from "react";
import { useStrings } from "../../hooks/useStrings";
import { Tankopedia } from "../../stores/tankopedia";
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
import type { ThicknessRange } from "../TankStaticArmor";
import { Text } from "../Text";
import styles from "./index.module.css";
interface LayerToggleProps {
visible: boolean;
label: ReactNode;
/** Thickness labels for the top and bottom of the scale */
range?: [ReactNode, ReactNode];
swatch: string;
onToggle(): void;
}
function LayerToggle({
visible,
label,
range,
swatch,
onToggle,
}: LayerToggleProps) {
return (
<button
className={styles.toggle}
data-visible={visible}
data-scaled={range !== undefined}
onClick={onToggle}
>
<div className={styles.labels}>
{range && (
<Text size="minor" lowContrast>
{range[0]}
</Text>
)}
<Text size="minor">{label}</Text>
{range && (
<Text size="minor" lowContrast>
{range[1]}
</Text>
)}
</div>
<div className={styles.swatch} style={{ background: swatch }}>
{visible ? <EyeOpenIcon /> : <EyeClosedIcon />}
</div>
</button>
);
}
export interface ThicknessesProps {
thicknessRange: ThicknessRange;
}
export function Thicknesses({ thicknessRange }: ThicknessesProps) {
const strings = useStrings();
const display = Tankopedia.use((state) => state.loaders.display.loaded);
const showControls = Tankopedia.use((state) => state.showControls);
const showPrimaryArmor = TankopediaPersistent.use(
(state) => state.showPrimaryArmor,
);
const showSpacedArmor = TankopediaPersistent.use(
(state) => state.showSpacedArmor,
);
const showExternalModules = TankopediaPersistent.use(
(state) => state.showExternalModules,
);
const { element } = strings.tanks.shot_card;
const mm = useCallback(
(value: number) =>
literals(strings.common.units.mm, { value: value.toFixed(0) }),
[strings],
);
const clearHighlight = useCallback(() => {
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
});
}, []);
return (
<div
className={styles.thicknesses}
data-visible={showControls && display === TankopediaDisplay.StaticArmor}
>
<LayerToggle
visible={showPrimaryArmor}
label={element.primary}
range={[mm(thicknessRange.value * 1.5), mm(0)]}
swatch="linear-gradient(rgb(64, 0, 0), rgb(255, 0, 0) 33%, rgb(255, 255, 0) 66%, rgb(0, 255, 0))"
onToggle={() => {
TankopediaPersistent.mutate((draft) => {
draft.showPrimaryArmor = !draft.showPrimaryArmor;
});
clearHighlight();
}}
/>
<LayerToggle
visible={showSpacedArmor}
label={element.spaced}
range={[mm(thicknessRange.value), mm(0)]}
swatch="linear-gradient(rgb(32, 0, 225), rgb(255, 0, 255))"
onToggle={() => {
TankopediaPersistent.mutate((draft) => {
draft.showSpacedArmor = !draft.showSpacedArmor;
});
clearHighlight();
}}
/>
<LayerToggle
visible={showExternalModules}
label={element.external}
swatch="#c0c0c0"
onToggle={() => {
TankopediaPersistent.mutate((draft) => {
draft.showExternalModules = !draft.showExternalModules;
});
clearHighlight();
}}
/>
</div>
);
}