mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-11-14 13:16:11 +01:00
fix imports
This commit is contained in:
1 parent
10f8fa28f9
commit
45811b5c18
10 files changed
+106
-110
No files matched your search
-328
@@ -1,328 +0,0 @@
|
||||
import { literals } from "@blitzkit/i18n/src/literals";
|
||||
import { ShellType } from "@blitzkit/protos";
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
IconButton,
|
||||
Popover,
|
||||
Select,
|
||||
Slider,
|
||||
Text,
|
||||
TextField,
|
||||
} from "@radix-ui/themes";
|
||||
import { throttle } from "lodash-es";
|
||||
import { type ChangeEvent } from "react";
|
||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
|
||||
export function CustomShellButton() {
|
||||
const customShell = Tankopedia.use((state) => state.customShell);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
const antagonistShell = Duel.use((state) => state.antagonist.shell);
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
<Popover.Root
|
||||
onOpenChange={(open) => {
|
||||
if (!open || customShell) return;
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell = antagonistShell;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Popover.Trigger>
|
||||
<IconButton
|
||||
color={customShell ? undefined : "gray"}
|
||||
variant="soft"
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
radius="none"
|
||||
>
|
||||
<img
|
||||
alt="custom shell"
|
||||
src="/assets/images/icons/custom-shell.png"
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content>
|
||||
{customShell && (
|
||||
<>
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex align="center" justify="between" gap="4">
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.type
|
||||
}
|
||||
</Text>
|
||||
<Select.Root
|
||||
value={`${customShell.type}`}
|
||||
onValueChange={(value) => {
|
||||
const type = Number(value) as ShellType;
|
||||
const modified = { ...customShell, type };
|
||||
|
||||
switch (type) {
|
||||
case ShellType.SHELL_TYPE_AP: {
|
||||
modified.normalization = 5;
|
||||
modified.ricochet = 70;
|
||||
modified.explosion_radius = undefined;
|
||||
break;
|
||||
}
|
||||
case ShellType.SHELL_TYPE_APCR: {
|
||||
modified.normalization = 2;
|
||||
modified.ricochet = 70;
|
||||
modified.explosion_radius = undefined;
|
||||
break;
|
||||
}
|
||||
case ShellType.SHELL_TYPE_HEAT: {
|
||||
modified.normalization = undefined;
|
||||
modified.ricochet = undefined;
|
||||
modified.explosion_radius = undefined;
|
||||
}
|
||||
case ShellType.SHELL_TYPE_HE: {
|
||||
modified.normalization = undefined;
|
||||
modified.ricochet = undefined;
|
||||
modified.explosion_radius =
|
||||
gun.shells.find(
|
||||
(shell) => shell.type === ShellType.SHELL_TYPE_HE,
|
||||
)?.explosion_radius ?? 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell = modified;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Select.Trigger variant="classic" />
|
||||
|
||||
<Select.Content>
|
||||
<Select.Item value={`${ShellType.SHELL_TYPE_AP}`}>
|
||||
{strings.common.shells.ap}
|
||||
</Select.Item>
|
||||
<Select.Item value={`${ShellType.SHELL_TYPE_APCR}`}>
|
||||
{strings.common.shells.ap_cr}
|
||||
</Select.Item>
|
||||
<Select.Item value={`${ShellType.SHELL_TYPE_HEAT}`}>
|
||||
{strings.common.shells.hc}
|
||||
</Select.Item>
|
||||
<Select.Item value={`${ShellType.SHELL_TYPE_HE}`}>
|
||||
{strings.common.shells.he}
|
||||
</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" justify="between" gap="4">
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.caliber
|
||||
}
|
||||
</Text>
|
||||
<TextField.Root
|
||||
variant="classic"
|
||||
style={{ maxWidth: "6rem" }}
|
||||
defaultValue={customShell.caliber}
|
||||
type="number"
|
||||
onChange={(event) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell!.caliber = event.target.valueAsNumber;
|
||||
});
|
||||
}}
|
||||
onFocus={(event) => event.target.select()}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.key === "Enter") {
|
||||
(event.target as HTMLInputElement).blur();
|
||||
} else if (event.key === "Escape") {
|
||||
(event.target as HTMLInputElement).valueAsNumber =
|
||||
customShell.caliber;
|
||||
(event.target as HTMLInputElement).blur();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">
|
||||
{literals(strings.common.units.mm, { value: "" })}
|
||||
</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" justify="between" gap="4">
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.penetration
|
||||
}
|
||||
</Text>
|
||||
<TextField.Root
|
||||
variant="classic"
|
||||
style={{ maxWidth: "6rem" }}
|
||||
defaultValue={customShell.penetration!.near}
|
||||
type="number"
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell!.penetration!.near =
|
||||
event.target.valueAsNumber;
|
||||
});
|
||||
}}
|
||||
onFocus={(event) => event.target.select()}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.key === "Enter") {
|
||||
(event.target as HTMLInputElement).blur();
|
||||
} else if (event.key === "Escape") {
|
||||
(event.target as HTMLInputElement).valueAsNumber =
|
||||
customShell.penetration!.near;
|
||||
(event.target as HTMLInputElement).blur();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">
|
||||
{literals(strings.common.units.mm, { value: "" })}
|
||||
</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
|
||||
{(customShell.type === ShellType.SHELL_TYPE_AP ||
|
||||
customShell.type === ShellType.SHELL_TYPE_APCR) && (
|
||||
<>
|
||||
<Flex align="center" justify="between" gap="4" py="1">
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.normalization
|
||||
}
|
||||
</Text>
|
||||
|
||||
<Flex
|
||||
align="center"
|
||||
gap="2"
|
||||
flexGrow="1"
|
||||
maxWidth="15rem"
|
||||
minWidth="8rem"
|
||||
>
|
||||
<Text color="gray">
|
||||
{literals(strings.common.units.deg, {
|
||||
value: (customShell.normalization ?? 0).toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
<Slider
|
||||
variant="classic"
|
||||
min={0}
|
||||
max={90}
|
||||
step={Number.EPSILON}
|
||||
defaultValue={[customShell.normalization ?? 0]}
|
||||
onValueChange={([value]) => {
|
||||
console.log("asd");
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell!.normalization = value;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" justify="between" gap="4" py="1">
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.ricochet
|
||||
}
|
||||
</Text>
|
||||
|
||||
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
|
||||
<Text color="gray">
|
||||
{literals(strings.common.units.deg, {
|
||||
value: (customShell.ricochet ?? 90).toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
<Slider
|
||||
variant="classic"
|
||||
min={0}
|
||||
max={90}
|
||||
step={Number.EPSILON}
|
||||
defaultValue={[customShell.ricochet ?? 90]}
|
||||
onValueChange={throttle(([value]) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell!.ricochet = value;
|
||||
});
|
||||
}, 1000)}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
{customShell.type === ShellType.SHELL_TYPE_HE && (
|
||||
<Flex align="center" justify="between" gap="4">
|
||||
<Text>Explosion radius</Text>
|
||||
<TextField.Root
|
||||
variant="classic"
|
||||
style={{ maxWidth: "6rem" }}
|
||||
defaultValue={customShell.explosion_radius ?? 0}
|
||||
type="number"
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell!.explosion_radius =
|
||||
event.target.valueAsNumber;
|
||||
});
|
||||
}}
|
||||
onFocus={(event) => event.target.select()}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.key === "Enter") {
|
||||
(event.target as HTMLInputElement).blur();
|
||||
} else if (event.key === "Escape") {
|
||||
(event.target as HTMLInputElement).valueAsNumber =
|
||||
customShell.explosion_radius ?? 1;
|
||||
(event.target as HTMLInputElement).blur();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">m</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex justify="end" mt="6" gap="2">
|
||||
<Popover.Close>
|
||||
<Button color="red" variant="outline">
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.exit
|
||||
}
|
||||
</Button>
|
||||
</Popover.Close>
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.customShell = antagonistShell;
|
||||
});
|
||||
}}
|
||||
>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.custom_shell
|
||||
.reset
|
||||
}
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
);
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { IconButton } from "@radix-ui/themes";
|
||||
import { useArmor } from "../../../../../../hooks/useArmor";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
|
||||
export function DynamicArmorSwitcher() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const isDynamicArmorActive = Duel.use((state) =>
|
||||
state.protagonist.consumables.includes(73),
|
||||
);
|
||||
const { hasDynamicArmor } = useArmor(tank.id);
|
||||
|
||||
if (!hasDynamicArmor) return null;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
color={isDynamicArmorActive ? undefined : "gray"}
|
||||
variant="soft"
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
onClick={() => {
|
||||
Duel.mutate((draft) => {
|
||||
if (draft.protagonist.consumables.includes(73)) {
|
||||
draft.protagonist.consumables =
|
||||
draft.protagonist.consumables.filter((id) => id !== 73);
|
||||
} else {
|
||||
if (draft.protagonist.consumables.length === tank.max_consumables) {
|
||||
draft.protagonist.consumables[tank.max_consumables - 1] = 73;
|
||||
} else {
|
||||
draft.protagonist.consumables.push(73);
|
||||
}
|
||||
}
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="Dynamic Armor"
|
||||
src={alias("api", "/icons/consumables/73.webp")}
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
.quick-inputs {
|
||||
left: -5rem;
|
||||
}
|
||||
|
||||
.quick-inputs:focus-within,
|
||||
.tank-sandbox-container:hover .quick-inputs {
|
||||
left: 1rem;
|
||||
}
|
||||
-160
@@ -1,160 +0,0 @@
|
||||
import { HeightIcon, WidthIcon } from "@radix-ui/react-icons";
|
||||
import { Flex, TextField } from "@radix-ui/themes";
|
||||
import type { QuicklimeEvent } from "quicklime";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { degToRad, radToDeg } from "three/src/math/MathUtils.js";
|
||||
import { applyPitchYawLimits } from "../../../../../../../core/blitz/applyPitchYawLimits";
|
||||
import {
|
||||
type ModelTransformEventData,
|
||||
modelTransformEvent,
|
||||
} from "../../../../../../../core/blitzkit/modelTransform";
|
||||
import { useEquipment } from "../../../../../../../hooks/useEquipment";
|
||||
import { useTankModelDefinition } from "../../../../../../../hooks/useTankModelDefinition";
|
||||
import { Duel } from "../../../../../../../stores/duel";
|
||||
import "./index.css";
|
||||
|
||||
export function QuickInputs() {
|
||||
const protagonist = Duel.use((state) => state.protagonist);
|
||||
const yawInput = useRef<HTMLInputElement>(null);
|
||||
const pitchInput = useRef<HTMLInputElement>(null);
|
||||
const tankModelDefinition = useTankModelDefinition();
|
||||
const turretModelDefinition =
|
||||
tankModelDefinition.turrets[protagonist.turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||
const initialTurretPitch =
|
||||
tankModelDefinition.initial_turret_rotation?.pitch ?? 0;
|
||||
const hasImprovedVerticalStabilizer = useEquipment("protagonist", 122);
|
||||
const hasDownImprovedVerticalStabilizer = useEquipment("protagonist", 124);
|
||||
|
||||
function displayPitch(pitch: number) {
|
||||
return radToDeg(pitch) - initialTurretPitch;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function syncInputValues(data: ModelTransformEventData) {
|
||||
if (!pitchInput.current || !yawInput.current) return;
|
||||
|
||||
pitchInput.current.value = displayPitch(data.pitch).toFixed(1);
|
||||
yawInput.current.value = radToDeg(data.yaw).toFixed(1);
|
||||
}
|
||||
|
||||
function handleTransformEvent(
|
||||
event: QuicklimeEvent<ModelTransformEventData>,
|
||||
) {
|
||||
syncInputValues(event.data);
|
||||
}
|
||||
|
||||
modelTransformEvent.on(handleTransformEvent);
|
||||
syncInputValues(modelTransformEvent.last!);
|
||||
|
||||
return () => {
|
||||
modelTransformEvent.off(handleTransformEvent);
|
||||
};
|
||||
}, [initialTurretPitch]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align="center"
|
||||
gap="2"
|
||||
width="4rem"
|
||||
direction="column"
|
||||
style={{
|
||||
pointerEvents: "auto",
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
transitionDuration: "0.2s",
|
||||
}}
|
||||
className="quick-inputs"
|
||||
>
|
||||
<TextField.Root
|
||||
variant="classic"
|
||||
size="1"
|
||||
radius="full"
|
||||
style={{ flex: 1, textAlign: "right" }}
|
||||
defaultValue={radToDeg(modelTransformEvent.last!.yaw).toFixed(1)}
|
||||
onBlur={() => {
|
||||
const value = Number(yawInput.current!.value);
|
||||
|
||||
if (isNaN(value)) {
|
||||
yawInput.current!.value = radToDeg(
|
||||
modelTransformEvent.last!.yaw,
|
||||
).toFixed(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
modelTransformEvent.last!.pitch,
|
||||
degToRad(value),
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
yawInput.current!.value = radToDeg(
|
||||
modelTransformEvent.last!.yaw,
|
||||
).toFixed(1);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
yawInput.current?.blur();
|
||||
}
|
||||
}}
|
||||
onFocus={() => yawInput.current?.focus()}
|
||||
ref={yawInput}
|
||||
>
|
||||
<TextField.Slot style={{ userSelect: "none" }}>
|
||||
<WidthIcon />
|
||||
</TextField.Slot>
|
||||
<TextField.Slot />
|
||||
</TextField.Root>
|
||||
|
||||
<TextField.Root
|
||||
variant="classic"
|
||||
size="1"
|
||||
radius="full"
|
||||
style={{ flex: 1, textAlign: "right" }}
|
||||
defaultValue={displayPitch(modelTransformEvent.last!.pitch).toFixed(1)}
|
||||
onBlur={() => {
|
||||
const value = Number(pitchInput.current!.value);
|
||||
if (isNaN(value)) {
|
||||
pitchInput.current!.value = displayPitch(
|
||||
modelTransformEvent.last!.pitch,
|
||||
).toFixed(1);
|
||||
return;
|
||||
}
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
degToRad(value + initialTurretPitch),
|
||||
modelTransformEvent.last!.yaw,
|
||||
gunModelDefinition.pitch!,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
hasDownImprovedVerticalStabilizer,
|
||||
);
|
||||
modelTransformEvent.dispatch({ pitch, yaw });
|
||||
pitchInput.current!.value = displayPitch(
|
||||
modelTransformEvent.last!.pitch,
|
||||
).toFixed(1);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
pitchInput.current?.blur();
|
||||
}
|
||||
}}
|
||||
onFocus={() => pitchInput.current?.focus()}
|
||||
ref={pitchInput}
|
||||
>
|
||||
<TextField.Slot
|
||||
style={{
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<HeightIcon />
|
||||
</TextField.Slot>
|
||||
<TextField.Slot />
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
import { alias, availableProvisions } from "@blitzkit/core";
|
||||
import { ShuffleIcon } from "@radix-ui/react-icons";
|
||||
import { Flex, IconButton, Popover } from "@radix-ui/themes";
|
||||
import { api } from "../../../../../../core/blitzkit/api";
|
||||
import { SPALL_LINER_PROVISION_ID } from "../../../../../../core/blitzkit/spallLiner";
|
||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
|
||||
const provisionDefinitions = await api.provisionDefinitions();
|
||||
|
||||
export function SpallLinerSwitcher() {
|
||||
const { tank, gun, provisions } = Duel.use((state) => state.protagonist);
|
||||
const { unwrap } = useLocale();
|
||||
const isSpallLinerActive = provisions.includes(SPALL_LINER_PROVISION_ID);
|
||||
const hasSlotAvailable = provisions.length < tank.max_provisions;
|
||||
|
||||
const canEquipSpallLiner = availableProvisions(
|
||||
tank,
|
||||
gun,
|
||||
provisionDefinitions,
|
||||
).some((provision) => provision.id === SPALL_LINER_PROVISION_ID);
|
||||
|
||||
if (!canEquipSpallLiner) return null;
|
||||
|
||||
function equip(replacing?: number) {
|
||||
Duel.mutate((draft) => {
|
||||
if (replacing !== undefined) {
|
||||
const provisions = draft.protagonist.provisions;
|
||||
provisions[provisions.indexOf(replacing)] = SPALL_LINER_PROVISION_ID;
|
||||
} else {
|
||||
draft.protagonist.provisions.push(SPALL_LINER_PROVISION_ID);
|
||||
}
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
function unequip() {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.provisions = draft.protagonist.provisions.filter(
|
||||
(id) => id !== SPALL_LINER_PROVISION_ID,
|
||||
);
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
const button = (
|
||||
<IconButton
|
||||
color={isSpallLinerActive ? undefined : "gray"}
|
||||
variant="soft"
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
radius="medium"
|
||||
onClick={() => {
|
||||
if (isSpallLinerActive) {
|
||||
unequip();
|
||||
} else if (hasSlotAvailable) {
|
||||
equip();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(
|
||||
provisionDefinitions.provisions[SPALL_LINER_PROVISION_ID].name!,
|
||||
)}
|
||||
src={alias("api", `/icons/provisions/${SPALL_LINER_PROVISION_ID}.webp`)}
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
if (isSpallLinerActive || hasSlotAvailable) return button;
|
||||
|
||||
return (
|
||||
<Popover.Root>
|
||||
<Popover.Trigger>{button}</Popover.Trigger>
|
||||
|
||||
<Popover.Content>
|
||||
<Flex gap="2" align="center">
|
||||
<img
|
||||
alt={unwrap(
|
||||
provisionDefinitions.provisions[SPALL_LINER_PROVISION_ID].name!,
|
||||
)}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/provisions/${SPALL_LINER_PROVISION_ID}.webp`,
|
||||
)}
|
||||
style={{
|
||||
width: "1.25em",
|
||||
height: "1.25em",
|
||||
}}
|
||||
/>
|
||||
|
||||
<ShuffleIcon color="var(--gray-9)" />
|
||||
|
||||
<Flex gap="2">
|
||||
{provisions.map((id) => (
|
||||
<Popover.Close key={id}>
|
||||
<IconButton
|
||||
color="gray"
|
||||
variant="soft"
|
||||
size="3"
|
||||
radius="medium"
|
||||
onClick={() => equip(id)}
|
||||
>
|
||||
<img
|
||||
alt={unwrap(provisionDefinitions.provisions[id].name!)}
|
||||
src={alias("api", `/icons/provisions/${id}.webp`)}
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Popover.Close>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
);
|
||||
}
|
||||
-204
@@ -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 "../../../../../StaticArmor";
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,508 +0,0 @@
|
||||
import {
|
||||
alias,
|
||||
resolvePenetrationCoefficient,
|
||||
romanize,
|
||||
uniqueGuns,
|
||||
} from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n/src/literals";
|
||||
import {
|
||||
Crosshair1Icon,
|
||||
EnterFullScreenIcon,
|
||||
ExitFullScreenIcon,
|
||||
EyeOpenIcon,
|
||||
ShadowInnerIcon,
|
||||
ShadowNoneIcon,
|
||||
SunIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DropdownMenu,
|
||||
Flex,
|
||||
IconButton,
|
||||
Popover,
|
||||
SegmentedControl,
|
||||
Tabs,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@radix-ui/themes";
|
||||
import { Suspense, useState, type RefObject } from "react";
|
||||
import { Pose, poseEvent } from "../../../../../core/blitzkit/pose";
|
||||
import { useEquipment } from "../../../../../hooks/useEquipment";
|
||||
import { useFullScreen } from "../../../../../hooks/useFullScreen";
|
||||
import { useFullscreenAvailability } from "../../../../../hooks/useFullscreenAvailability";
|
||||
import { useLocale } from "../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent";
|
||||
import { TankopediaDisplay } from "../../../../../stores/tankopediaPersistent/constants";
|
||||
import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps";
|
||||
import { EqualIcon } from "../../../../EqualIcon";
|
||||
import { ModuleButton } from "../../../../ModuleButtons/ModuleButton";
|
||||
import { ScreenshotButton } from "../../../../ScreenshotButton";
|
||||
import { SmallTankIcon } from "../../../../SmallTankIcon";
|
||||
import type { ThicknessRange } from "../../../../StaticArmor";
|
||||
import {
|
||||
SidebarIconButton,
|
||||
SidebarIconGroup,
|
||||
} from "../../../../TankopediaSidebarIconButtons";
|
||||
import { SkinSwitcher } from "../../../../TankopediaSkinSwitcher";
|
||||
import { TankSearch } from "../../../../TankSearch";
|
||||
import { CustomShellButton } from "./components/CustomShellButton";
|
||||
import { DynamicArmorSwitcher } from "./components/DynamicArmorSwitcher";
|
||||
import { QuickInputs } from "./components/QuickInputs";
|
||||
import { SpallLinerSwitcher } from "./components/SpallLinerSwitcher";
|
||||
import { Thicknesses } from "./components/Thicknesses";
|
||||
|
||||
type OptionsProps = MaybeSkeletonComponentProps & {
|
||||
thicknessRange: ThicknessRange;
|
||||
canvas: RefObject<HTMLCanvasElement>;
|
||||
};
|
||||
|
||||
export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
const hasCustomShell = Tankopedia.use(
|
||||
(state) => state.customShell !== undefined,
|
||||
);
|
||||
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
||||
const isFullScreen = useFullScreen();
|
||||
const advancedHighlighting = TankopediaPersistent.use(
|
||||
(state) => state.advancedHighlighting,
|
||||
);
|
||||
const fullScreenAvailable = useFullscreenAvailability(true);
|
||||
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
||||
const antagonistGun = Duel.use((state) => state.antagonist.gun);
|
||||
const antagonistShell = Duel.use((state) => state.antagonist.shell);
|
||||
const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false);
|
||||
const antagonistTank = Duel.use((state) => state.antagonist.tank);
|
||||
const hasCalibratedShells = useEquipment("protagonist", 103, true);
|
||||
const [tab, setTab] = useState("search");
|
||||
const hasEnhancedArmor = useEquipment("protagonist", 110);
|
||||
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
||||
const { strings, unwrap } = useLocale();
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
const highGraphics = TankopediaPersistent.use((state) => state.highGraphics);
|
||||
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
const equalizer = Duel.use((state) => state.antagonist.tank.equalizer);
|
||||
|
||||
return (
|
||||
<>
|
||||
{disturbed && <QuickInputs />}
|
||||
|
||||
<Thicknesses skeleton={skeleton} thicknessRange={thicknessRange} />
|
||||
|
||||
<SkinSwitcher />
|
||||
|
||||
<Flex
|
||||
gap="2"
|
||||
direction="column"
|
||||
top="50%"
|
||||
right={
|
||||
requestedDisplay === TankopediaDisplay.DynamicArmor ? "3" : "-4rem"
|
||||
}
|
||||
style={{
|
||||
position: "absolute",
|
||||
transform: "translateY(-50%)",
|
||||
transitionDuration: "200ms",
|
||||
}}
|
||||
align="end"
|
||||
>
|
||||
{!hasCustomShell && (
|
||||
<Text color="gray" size={{ initial: "1", sm: "2" }}>
|
||||
{literals(strings.common.units.mm, {
|
||||
value: (
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
equalize,
|
||||
antagonistShell.type,
|
||||
equalizer,
|
||||
) * antagonistShell.penetration!.near
|
||||
).toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{antagonistUniqueGuns.length > 1 && (
|
||||
<Popover.Root>
|
||||
<Popover.Trigger>
|
||||
<IconButton
|
||||
radius="full"
|
||||
variant="soft"
|
||||
color="gray"
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
<Text
|
||||
size="1"
|
||||
color="gray"
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
mr="2"
|
||||
mb="1"
|
||||
>
|
||||
{romanize(antagonistGun.tier)}
|
||||
</Text>
|
||||
|
||||
<img
|
||||
alt="Antagonist Gun"
|
||||
src={alias("api", "/icons/modules/gun.webp")}
|
||||
style={{
|
||||
width: "65%",
|
||||
height: "65%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content side="left" align="center">
|
||||
<Flex gap="2">
|
||||
{[...antagonistUniqueGuns].reverse().map(({ gun, turret }) => (
|
||||
<ModuleButton
|
||||
module="gun"
|
||||
key={gun.id}
|
||||
onClick={() =>
|
||||
Duel.mutate((draft) => {
|
||||
draft.antagonist.turret = turret;
|
||||
draft.antagonist.gun = gun;
|
||||
draft.antagonist.shell = gun.shells[0];
|
||||
})
|
||||
}
|
||||
selected={gun.id === antagonistGun.id}
|
||||
discriminator={romanize(gun.tier)}
|
||||
secondaryDiscriminator={
|
||||
<Text style={{ fontSize: "0.75em" }}>
|
||||
{literals(strings.common.units.mm, {
|
||||
value: gun.shells[0].caliber.toFixed(0),
|
||||
})}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
)}
|
||||
|
||||
<SidebarIconGroup>
|
||||
{antagonistGun.shells.map((thisShell) => (
|
||||
<SidebarIconButton
|
||||
key={thisShell.id}
|
||||
selected={thisShell.id === antagonistShell.id && !hasCustomShell}
|
||||
alt={unwrap(thisShell.name!)}
|
||||
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
|
||||
onClick={() => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.antagonist.shell = thisShell;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
draft.customShell = undefined;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<CustomShellButton />
|
||||
</SidebarIconGroup>
|
||||
|
||||
<Flex
|
||||
direction="column"
|
||||
style={{
|
||||
pointerEvents: "auto",
|
||||
borderRadius: "var(--radius-full)",
|
||||
}}
|
||||
overflow="hidden"
|
||||
>
|
||||
<IconButton
|
||||
color={hasCalibratedShells ? undefined : "gray"}
|
||||
variant="soft"
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
radius="none"
|
||||
onClick={() => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.antagonist.equipmentMatrix[0][0] = hasCalibratedShells
|
||||
? 0
|
||||
: 1;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="Calibrated Shells"
|
||||
src={alias("api", "/icons/equipment/103.webp")}
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color={hasEnhancedArmor ? undefined : "gray"}
|
||||
variant="soft"
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
radius="none"
|
||||
onClick={() => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.protagonist.equipmentMatrix[1][1] = hasEnhancedArmor
|
||||
? 0
|
||||
: -1;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="Enhanced Armor"
|
||||
src={alias("api", "/icons/equipment/110.webp")}
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
<SpallLinerSwitcher />
|
||||
</Flex>
|
||||
|
||||
{!skeleton && modelRequested && (
|
||||
<Suspense>
|
||||
<DynamicArmorSwitcher />
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
variant="soft"
|
||||
size={{ initial: "2", sm: "3" }}
|
||||
color={equalize ? undefined : "gray"}
|
||||
onClick={() => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.equalize = !draft.equalize;
|
||||
});
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<EqualIcon />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
position="absolute"
|
||||
bottom={revealed ? "4" : "-100%"}
|
||||
left="50%"
|
||||
style={{ transform: "translateX(-50%)", transitionDuration: "200ms" }}
|
||||
gap="2"
|
||||
>
|
||||
<Flex
|
||||
gap="2"
|
||||
align="center"
|
||||
style={{ transitionDuration: "200ms" }}
|
||||
position="relative"
|
||||
>
|
||||
{disturbed && (
|
||||
<>
|
||||
{requestedDisplay === TankopediaDisplay.DynamicArmor && (
|
||||
<IconButton
|
||||
size="2"
|
||||
highContrast
|
||||
onClick={() => {
|
||||
TankopediaPersistent.mutate((draft) => {
|
||||
draft.advancedHighlighting = !draft.advancedHighlighting;
|
||||
});
|
||||
}}
|
||||
color={advancedHighlighting ? undefined : "gray"}
|
||||
variant={advancedHighlighting ? "solid" : "surface"}
|
||||
>
|
||||
{advancedHighlighting ? (
|
||||
<ShadowInnerIcon />
|
||||
) : (
|
||||
<ShadowNoneIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<IconButton
|
||||
size="2"
|
||||
variant="surface"
|
||||
highContrast
|
||||
color="gray"
|
||||
>
|
||||
<EyeOpenIcon />
|
||||
</IconButton>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Item
|
||||
onClick={() => poseEvent.emit(Pose.HullDown)}
|
||||
>
|
||||
{strings.website.tools.tankopedia.sandbox.poses.hull_down}
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
onClick={() => poseEvent.emit(Pose.FaceHug)}
|
||||
>
|
||||
{strings.website.tools.tankopedia.sandbox.poses.face_hug}
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
onClick={() => poseEvent.emit(Pose.Default)}
|
||||
>
|
||||
{strings.website.tools.tankopedia.sandbox.poses.default}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<IconButton
|
||||
size="2"
|
||||
highContrast
|
||||
onClick={() => {
|
||||
TankopediaPersistent.mutate((draft) => {
|
||||
draft.highGraphics = !draft.highGraphics;
|
||||
});
|
||||
}}
|
||||
color={highGraphics ? undefined : "gray"}
|
||||
variant={highGraphics ? "solid" : "surface"}
|
||||
>
|
||||
<SunIcon />
|
||||
</IconButton>
|
||||
|
||||
<ScreenshotButton
|
||||
color="gray"
|
||||
size="2"
|
||||
variant="surface"
|
||||
highContrast
|
||||
canvas={canvas}
|
||||
name={protagonistTank.slug}
|
||||
/>
|
||||
|
||||
{fullScreenAvailable && (
|
||||
<IconButton
|
||||
size="2"
|
||||
highContrast
|
||||
onClick={() => {
|
||||
if (isFullScreen) {
|
||||
document.exitFullscreen();
|
||||
} else document.body.requestFullscreen();
|
||||
}}
|
||||
color={isFullScreen ? undefined : "gray"}
|
||||
variant={isFullScreen ? "solid" : "surface"}
|
||||
>
|
||||
{isFullScreen ? (
|
||||
<ExitFullScreenIcon />
|
||||
) : (
|
||||
<EnterFullScreenIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex gap="3" align="center" mt="2">
|
||||
<SegmentedControl.Root
|
||||
variant="classic"
|
||||
value={`${disturbed ? requestedDisplay : -1}`}
|
||||
onValueChange={(value) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.disturbed = true;
|
||||
draft.requestedDisplay = Number(value);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.Model}`}>
|
||||
<Tooltip
|
||||
content={strings.website.tools.tankopedia.sandbox.model.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img
|
||||
src="/assets/images/icons/tankopedia-model.png"
|
||||
style={{ height: "1.25em" }}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.DynamicArmor}`}>
|
||||
<Tooltip
|
||||
content={strings.website.tools.tankopedia.sandbox.dynamic.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img
|
||||
src="/assets/images/icons/tankopedia-dynamic-armor.png"
|
||||
style={{ height: "1.25em" }}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.StaticArmor}`}>
|
||||
<Tooltip
|
||||
content={strings.website.tools.tankopedia.sandbox.static.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img
|
||||
src="/assets/images/icons/tankopedia-static-armor.png"
|
||||
style={{ height: "1.25em" }}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
</SegmentedControl.Root>
|
||||
|
||||
{requestedDisplay === TankopediaDisplay.DynamicArmor && (
|
||||
<Dialog.Root
|
||||
open={antagonistSelectorOpen}
|
||||
onOpenChange={setAntagonistSelectorOpen}
|
||||
>
|
||||
<Dialog.Trigger>
|
||||
<Button variant="solid" highContrast>
|
||||
<Crosshair1Icon />
|
||||
{unwrap(antagonistTank.name!)}
|
||||
<SmallTankIcon id={antagonistTank.id} size={16} />
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content>
|
||||
<Dialog.Title align="center">
|
||||
{strings.website.common.tank_search.select_dialog_title}
|
||||
</Dialog.Title>
|
||||
|
||||
<Tabs.Root
|
||||
value={tab}
|
||||
onValueChange={setTab}
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
<TankSearch
|
||||
compact
|
||||
onSelect={(tank) => {
|
||||
Duel.mutate((draft) => {
|
||||
draft.antagonist.tank = tank;
|
||||
draft.antagonist.engine = tank.engines.at(-1)!;
|
||||
draft.antagonist.track = tank.tracks.at(-1)!;
|
||||
draft.antagonist.turret = tank.turrets.at(-1)!;
|
||||
draft.antagonist.gun =
|
||||
draft.antagonist.turret.guns.at(-1)!;
|
||||
draft.antagonist.shell = draft.antagonist.gun.shells[0];
|
||||
});
|
||||
setAntagonistSelectorOpen(false);
|
||||
}}
|
||||
/>
|
||||
</Tabs.Root>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { Duel } from "../../../stores/duel";
|
||||
import { Tankopedia } from "../../../stores/tankopedia";
|
||||
import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps";
|
||||
import type { ThicknessRange } from "../../StaticArmor";
|
||||
import { Options } from "./components/Options";
|
||||
import { TankopediaOptions } from "../../TankopediaOptions";
|
||||
import { TankSandbox } from "./components/TankSandbox";
|
||||
import { Title } from "./components/TankSandbox/Title";
|
||||
|
||||
@@ -142,7 +142,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
|
||||
<Title skeleton={skeleton} />
|
||||
|
||||
<Options
|
||||
<TankopediaOptions
|
||||
skeleton={skeleton}
|
||||
canvas={canvas}
|
||||
thicknessRange={thicknessRange}
|
||||
|
||||
Reference in new issue
Block a user