feat: floating editor

This commit is contained in:
TrèsAbhi committed 2024-08-01 14:00:43 -05:00
1 parent 0dc708ab2a
commit 71e0e8e09e
5 files changed
+95 -137

No files matched your search

@@ -1,96 +0,0 @@
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) || input < 0) return;
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>
);
}
-3
View File
@@ -10,7 +10,6 @@ import { HistorySection } from './components/HistorySection';
import { CharacteristicsSection } from './components/Model/CharacteristicsSection'; import { CharacteristicsSection } from './components/Model/CharacteristicsSection';
import { HeroSection } from './components/Model/HeroSection'; import { HeroSection } from './components/Model/HeroSection';
import { TechTreeSection } from './components/Model/TechTreeSection'; import { TechTreeSection } from './components/Model/TechTreeSection';
import { Prompts } from './components/Prompts';
import { ShotDisplaySection } from './components/ShotDisplaySection'; import { ShotDisplaySection } from './components/ShotDisplaySection';
import { VideoSection } from './components/VideoSection'; import { VideoSection } from './components/VideoSection';
@@ -75,8 +74,6 @@ export default function Page({ params }: { params: { id: string } }) {
return ( return (
<PageWrapper p="0" noMaxWidth color="purple" size={1600}> <PageWrapper p="0" noMaxWidth color="purple" size={1600}>
<Prompts />
<HeroSection id={id} /> <HeroSection id={id} />
<ShotDisplaySection /> <ShotDisplaySection />
{!exempt && <AdMidSectionResponsive />} {!exempt && <AdMidSectionResponsive />}
@@ -1,7 +1,8 @@
import { Card, Code, Flex, Text } from '@radix-ui/themes'; import { Card, Code, Flex, Text, TextField } from '@radix-ui/themes';
import { Html } from '@react-three/drei'; import { Html } from '@react-three/drei';
import { useRef } from 'react'; import { useRef } from 'react';
import { radToDeg } from 'three/src/math/MathUtils'; import { radToDeg } from 'three/src/math/MathUtils';
import { resolveArmorIndex } from '../../../core/blitzkit/resolveArmorIndex';
import * as App from '../../../stores/app'; import * as App from '../../../stores/app';
import * as Duel from '../../../stores/duel'; import * as Duel from '../../../stores/duel';
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral'; import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
@@ -16,12 +17,16 @@ export function ArmorPlateDisplay() {
const developerMode = App.use((state) => state.developerMode); const developerMode = App.use((state) => state.developerMode);
const devThicknessInput = useRef<HTMLInputElement>(null); const devThicknessInput = useRef<HTMLInputElement>(null);
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
const input = useRef<HTMLInputElement>(null);
if (highlightArmor === undefined) return null; if (highlightArmor === undefined) return null;
return ( return (
<group position={highlightArmor.point}> <group position={highlightArmor.point}>
<Html center> <Html
center
style={{ pointerEvents: highlightArmor.editingPlate ? 'auto' : 'none' }}
>
<Card <Card
mb="4" mb="4"
style={{ style={{
@@ -38,6 +43,7 @@ export function ArmorPlateDisplay() {
mm mm
</Text> </Text>
</Text> </Text>
{highlightArmor.type !== ArmorType.External && ( {highlightArmor.type !== ArmorType.External && (
<Text size="2" color="gray"> <Text size="2" color="gray">
{highlightArmor.thicknessAngled.toFixed(0)} {highlightArmor.thicknessAngled.toFixed(0)}
@@ -54,6 +60,67 @@ export function ArmorPlateDisplay() {
</Text> </Text>
</> </>
)} )}
{highlightArmor.editingPlate && (
<TextField.Root
mt="2"
ref={input}
type="number"
autoFocus
onFocus={(event) => event.target.select()}
onKeyDown={(event) => {
if (!highlightArmor.editingPlate) return;
const index = resolveArmorIndex(highlightArmor.name);
if (index === undefined) return;
const thickness = event.currentTarget.valueAsNumber;
if (isNaN(thickness) || thickness < 0) return;
if (event.key === 'Enter') {
input.current?.blur();
mutateTankopediaEphemeral((draft) => {
draft.highlightArmor = undefined;
const { protagonist } = duelStore.getState();
const tank = draft.model;
const turret = tank.turrets[protagonist.turret.id];
const gun = turret.guns[protagonist.gun.id];
const track = tank.tracks[protagonist.track.id];
console.log(highlightArmor.name);
if (highlightArmor.name.startsWith('hull_')) {
tank.armor.thickness[index] = thickness;
} else if (highlightArmor.name.startsWith('turret_')) {
turret.armor.thickness[index] = thickness;
} else if (highlightArmor.name.startsWith('gun_')) {
if (highlightArmor.name.includes('_armor_')) {
gun.armor.thickness[index] = thickness;
} else {
gun.thickness = thickness;
}
} else if (highlightArmor.name.startsWith('chassis_')) {
track.thickness = thickness;
}
});
} else if (event.key === 'Escape') {
input.current?.blur();
mutateTankopediaEphemeral((draft) => {
draft.highlightArmor = undefined;
});
}
}}
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
}}
defaultValue={highlightArmor.thickness.toFixed(0)}
>
<TextField.Slot side="right">mm</TextField.Slot>
</TextField.Root>
)}
</Flex> </Flex>
</Card> </Card>
</Html> </Html>
@@ -173,41 +173,31 @@ export function StaticArmorSceneComponent({
const { editStatic } = tankopediaEphemeralStore.getState(); const { editStatic } = tankopediaEphemeralStore.getState();
if (editStatic) { const bounds = new Box3().setFromObject(event.object);
mutateTankopediaEphemeralStore((draft) => { const point = bounds.min
draft.editingPlate = { .clone()
name: name, .add(bounds.max)
default: thickness, .divideScalar(2)
}; .setY(bounds.max.y);
}); const cameraNormal = camera.position.clone().sub(point).normalize();
} else { const surfaceNormal = event
const bounds = new Box3().setFromObject(event.object); .normal!.clone()
const point = bounds.min .applyQuaternion(event.object.getWorldQuaternion(new Quaternion()));
.clone() const angle = surfaceNormal.angleTo(cameraNormal);
.add(bounds.max) const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle);
.divideScalar(2) console.log(name);
.setY(bounds.max.y); mutateTankopediaEphemeralStore((draft) => {
const cameraNormal = camera.position.clone().sub(point).normalize(); draft.highlightArmor = {
const surfaceNormal = event type: props.type,
.normal!.clone() name,
.applyQuaternion( point,
event.object.getWorldQuaternion(new Quaternion()), thickness,
); thicknessAngled,
const angle = surfaceNormal.angleTo(cameraNormal); angle,
const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle); color: `#${color.getHexString()}`,
editingPlate: editStatic,
mutateTankopediaEphemeralStore((draft) => { };
draft.highlightArmor = { });
type: props.type,
name,
point,
thickness,
thicknessAngled,
angle,
color: `#${color.getHexString()}`,
};
});
}
}, },
})} })}
+1 -1
View File
@@ -14,8 +14,8 @@ interface TankopediaEphemeral {
controlsEnabled: boolean; controlsEnabled: boolean;
model: ModelDefinition; model: ModelDefinition;
editStatic: boolean; editStatic: boolean;
editingPlate?: { name: string; default: number };
highlightArmor?: { highlightArmor?: {
editingPlate: boolean;
name: string; name: string;
point: Vector3; point: Vector3;
thickness: number; thickness: number;