feat: better plate editing integration

This commit is contained in:
TrèsAbhi committed 2024-07-31 13:11:25 -05:00
1 parent 15cf4ec0f1
commit 2c0f9184db
6 files changed
+157 -64

No files matched your search

@@ -0,0 +1,96 @@
import { Button, Dialog, Flex, Text, TextField } from '@radix-ui/themes';
import { useRef } from 'react';
import { ModelArmor } from '../../../../../core/blitzkit/modelDefinitions';
import { resolveArmorIndex } from '../../../../../core/blitzkit/resolveArmorIndex';
import * as Duel from '../../../../../stores/duel';
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
export function Prompts() {
const input = useRef<HTMLInputElement>(null);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const editingPlate = TankopediaEphemeral.use((state) => state.editingPlate);
const duelStore = Duel.useStore();
return (
<Dialog.Root
open={editingPlate !== undefined}
onOpenChange={() => {
mutateTankopediaEphemeral((draft) => {
draft.editingPlate = undefined;
});
}}
>
<Dialog.Content style={{ width: 'fit-content' }}>
<Flex align="center" gap="2" direction="column">
<Text>Edit thickness</Text>
<TextField.Root
mt="2"
ref={input}
type="number"
onFocus={() => {
input.current?.select();
}}
onKeyDown={(event) => {
if (!editingPlate) return;
if (event.key === 'Enter') {
input.current?.blur();
mutateTankopediaEphemeral((draft) => {
draft.editingPlate = undefined;
draft.highlightArmor = undefined;
const { protagonist } = duelStore.getState();
let armor: ModelArmor;
if (editingPlate.name.startsWith('hull_')) {
armor = draft.model.armor;
} else if (editingPlate.name.startsWith('turret_')) {
armor = draft.model.turrets[protagonist.turret.id].armor;
} else if (editingPlate.name.startsWith('gun_')) {
armor =
draft.model.turrets[protagonist.turret.id].guns[
protagonist.gun.id
].armor;
} else return;
const index = resolveArmorIndex(editingPlate.name);
if (index === undefined) return;
const input = event.currentTarget.valueAsNumber;
if (!isNaN(input)) {
armor.thickness[index] = input;
}
});
} else if (event.key === 'Escape') {
input.current?.blur();
mutateTankopediaEphemeral((draft) => {
draft.highlightArmor = undefined;
});
}
}}
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
}}
defaultValue={editingPlate?.default.toFixed(0)}
>
<TextField.Slot side="right">mm</TextField.Slot>
</TextField.Root>
<Flex gap="2" width="100%">
<Dialog.Close>
<Button style={{ flex: 1 }} color="red" variant="outline">
Cancel
</Button>
</Dialog.Close>
<Dialog.Close>
<Button style={{ flex: 1 }}>Apply</Button>
</Dialog.Close>
</Flex>
</Flex>
</Dialog.Content>
</Dialog.Root>
);
}
@@ -1,5 +1,5 @@
import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons';
import { Box, Flex, Text } from '@radix-ui/themes';
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes';
import { ThicknessRange } from '../../../../../components/StaticArmor/components/StaticArmorScene';
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
@@ -20,6 +20,8 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const editStatic = TankopediaEphemeral.use((state) => state.editStatic);
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
return (
<Flex
@@ -136,6 +138,37 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
}}
/>
</Flex>
<Flex align="center" gap="2" mt="2">
<Button
color="red"
size="1"
variant="ghost"
onClick={() => {
mutateTankopediaEphemeral((draft) => {
draft.editStatic = false;
draft.model = tankopediaEphemeralStore.getInitialState().model;
});
}}
>
<ReloadIcon />
Reset
</Button>
<Flex
gap="2"
style={{ cursor: 'pointer' }}
onClick={() => {
mutateTankopediaEphemeral((draft) => {
draft.editStatic = !draft.editStatic;
draft.highlightArmor = undefined;
});
}}
>
<Text size="1">Edit</Text>
<Checkbox size="2" checked={editStatic} />
</Flex>
</Flex>
</Flex>
);
}
+3
View File
@@ -10,6 +10,7 @@ import { HistorySection } from './components/HistorySection';
import { CharacteristicsSection } from './components/Model/CharacteristicsSection';
import { HeroSection } from './components/Model/HeroSection';
import { TechTreeSection } from './components/Model/TechTreeSection';
import { Prompts } from './components/Prompts';
import { ShotDisplaySection } from './components/ShotDisplaySection';
import { VideoSection } from './components/VideoSection';
@@ -74,6 +75,8 @@ export default function Page({ params }: { params: { id: string } }) {
return (
<PageWrapper p="0" noMaxWidth color="purple" size={1600}>
<Prompts />
<HeroSection id={id} />
<ShotDisplaySection />
{!exempt && <AdMidSectionResponsive />}
@@ -1,9 +1,7 @@
import { Card, Code, Flex, Text, TextField } from '@radix-ui/themes';
import { Card, Code, Flex, Text } from '@radix-ui/themes';
import { Html } from '@react-three/drei';
import { useEffect, useRef } from 'react';
import { useRef } from 'react';
import { radToDeg } from 'three/src/math/MathUtils';
import { ModelArmor } from '../../../core/blitzkit/modelDefinitions';
import { resolveArmorIndex } from '../../../core/blitzkit/resolveArmorIndex';
import * as App from '../../../stores/app';
import * as Duel from '../../../stores/duel';
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
@@ -19,10 +17,6 @@ export function ArmorPlateDisplay() {
const devThicknessInput = useRef<HTMLInputElement>(null);
const duelStore = Duel.useStore();
useEffect(() => {
devThicknessInput.current?.focus();
});
if (highlightArmor === undefined) return null;
return (
@@ -52,60 +46,12 @@ export function ArmorPlateDisplay() {
</Text>
</Text>
)}
{developerMode && (
<>
<Text mt="2" color="gray" size="2">
<b>DEV:</b> <Code>{highlightArmor.name}</Code>
</Text>
<TextField.Root
type="number"
style={{ width: 96 }}
onKeyDown={(event) => {
if (event.key === 'Enter') {
devThicknessInput.current?.blur();
mutateTankopediaEphemeral((draft) => {
draft.highlightArmor = undefined;
const { protagonist } = duelStore.getState();
let armor: ModelArmor;
if (highlightArmor.name.startsWith('hull_')) {
armor = draft.model.armor;
} else if (highlightArmor.name.startsWith('turret_')) {
armor =
draft.model.turrets[protagonist.turret.id].armor;
} else if (highlightArmor.name.startsWith('gun_')) {
armor =
draft.model.turrets[protagonist.turret.id].guns[
protagonist.gun.id
].armor;
} else return;
const index = resolveArmorIndex(highlightArmor.name);
if (index === undefined) return;
const input = event.currentTarget.valueAsNumber;
if (!isNaN(input)) {
armor.thickness[index] = input;
}
});
} else if (event.key === 'Escape') {
devThicknessInput.current?.blur();
mutateTankopediaEphemeral((draft) => {
draft.highlightArmor = undefined;
});
}
}}
key={highlightArmor.name}
defaultValue={highlightArmor.thickness.toFixed(0)}
mt="1"
autoFocus
ref={devThicknessInput}
size="1"
>
<TextField.Slot side="right">mm</TextField.Slot>
</TextField.Root>
</>
)}
</Flex>
@@ -104,7 +104,6 @@ export function SpacedArmorSceneComponent({
let color: Color;
let opacity: number;
let renderOrder = 1;
let depthWrite = true;
switch (props.type) {
@@ -121,7 +120,6 @@ export function SpacedArmorSceneComponent({
case ArmorType.External:
color = externalModuleColor;
opacity = 1 / 8;
renderOrder = 0;
depthWrite = false;
break;
}
@@ -142,7 +140,7 @@ export function SpacedArmorSceneComponent({
const outlineMaterial = useMemo(
() =>
new LineBasicMaterial({
color: color.clone().multiplyScalar(1 - 2 ** -3),
color: color.clone().multiplyScalar(2 ** 2),
}),
[thickness],
);
@@ -210,13 +208,26 @@ export function SpacedArmorSceneComponent({
onClick(event) {
event.stopPropagation();
const { editStatic } = tankopediaEphemeralStore.getState();
if (editStatic) {
mutateTankopediaEphemeralStore((draft) => {
draft.editingPlate = {
name: props.name,
default: thickness,
};
});
} else {
const bounds = new Box3().setFromObject(event.object);
const point = bounds.min
.clone()
.add(bounds.max)
.divideScalar(2)
.setY(bounds.max.y);
const cameraNormal = camera.position.clone().sub(point).normalize();
const cameraNormal = camera.position
.clone()
.sub(point)
.normalize();
const surfaceNormal = event
.normal!.clone()
.applyQuaternion(
@@ -236,11 +247,12 @@ export function SpacedArmorSceneComponent({
color: `#${color.getHexString()}`,
};
});
}
},
})}
<lineSegments material={outlineMaterial}>
<edgesGeometry args={[(node as Mesh).geometry]} />
<edgesGeometry args={[(node as Mesh).geometry, 45]} />
</lineSegments>
</>
);
+3
View File
@@ -13,6 +13,8 @@ interface TankopediaEphemeral {
skills: Record<string, number>;
controlsEnabled: boolean;
model: ModelDefinition;
editStatic: boolean;
editingPlate?: { name: string; default: number };
highlightArmor?: {
name: string;
point: Vector3;
@@ -91,6 +93,7 @@ export const { Provider, use, useMutation, useStore } = createNextSafeStore(
(model: ModelDefinition) =>
create<TankopediaEphemeral>()(
subscribeWithSelector<TankopediaEphemeral>(() => ({
editStatic: false,
skills: {},
model,
controlsEnabled: true,