feat: popover custom shell

This commit is contained in:
tresabhi committed 2024-11-10 21:09:09 -06:00
1 parent dbc3d0df3a
commit 3e2bfc453a
2 files changed
+187 -194

No files matched your search

@@ -1,16 +1,15 @@
import { imgur, ShellType } from '@blitzkit/core'; import { imgur, ShellType } from '@blitzkit/core';
import { import {
Button, Button,
Dialog,
Flex, Flex,
IconButton, IconButton,
Popover,
Select, Select,
Slider, Slider,
Text, Text,
TextField, TextField,
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { invalidate } from '@react-three/fiber'; import { type ChangeEvent } from 'react';
import { useEffect, useState, type ChangeEvent } from 'react';
import { Duel } from '../../../../../../stores/duel'; import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
@@ -18,17 +17,18 @@ export function CustomShellButton() {
const customShell = TankopediaEphemeral.use((state) => state.customShell); const customShell = TankopediaEphemeral.use((state) => state.customShell);
const antagonistShell = Duel.use((state) => state.antagonist.shell); const antagonistShell = Duel.use((state) => state.antagonist.shell);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const [customShellDraft, setCustomShellDraft] = useState(
customShell ?? antagonistShell,
);
useEffect(() => {
setCustomShellDraft(customShell ?? antagonistShell);
}, [antagonistShell, customShell]);
return ( return (
<Dialog.Root> <Popover.Root
<Dialog.Trigger> onOpenChange={(open) => {
if (!open) return;
mutateTankopediaEphemeral((draft) => {
draft.customShell = antagonistShell;
});
}}
>
<Popover.Trigger>
<IconButton <IconButton
color={customShell ? undefined : 'gray'} color={customShell ? undefined : 'gray'}
variant="soft" variant="soft"
@@ -44,185 +44,174 @@ export function CustomShellButton() {
}} }}
/> />
</IconButton> </IconButton>
</Dialog.Trigger> </Popover.Trigger>
<Dialog.Content> <Popover.Content>
<Dialog.Title>Custom shell</Dialog.Title> {/* <Popover.Title>Custom shell</Popover.Title> */}
<Flex direction="column" gap="2"> {customShell && (
<Flex align="center" justify="between"> <>
<Text>Type</Text> <Flex direction="column" gap="2">
<Select.Root <Flex align="center" justify="between" gap="4">
value={`${customShellDraft.type}`} <Text>Type</Text>
onValueChange={(value) => { <Select.Root
const type = Number(value) as ShellType; value={`${customShell.type}`}
const modified = { ...customShellDraft, type }; onValueChange={(value) => {
const type = Number(value) as ShellType;
const modified = { ...customShell, type };
switch (type) { switch (type) {
case ShellType.AP: { case ShellType.AP: {
modified.normalization = 5; modified.normalization = 5;
modified.ricochet = 70; modified.ricochet = 70;
break; break;
} }
case ShellType.APCR: { case ShellType.APCR: {
modified.normalization = 2; modified.normalization = 2;
modified.ricochet = 70; modified.ricochet = 70;
break; break;
} }
case ShellType.HEAT: case ShellType.HEAT:
case ShellType.HE: { case ShellType.HE: {
modified.normalization = undefined; modified.normalization = undefined;
modified.ricochet = undefined; modified.ricochet = undefined;
break; break;
} }
}
setCustomShellDraft(modified);
}}
>
<Select.Trigger />
<Select.Content>
<Select.Item value={`${ShellType.AP}`}>AP</Select.Item>
<Select.Item value={`${ShellType.APCR}`}>APCR</Select.Item>
<Select.Item value={`${ShellType.HEAT}`}>HEAT</Select.Item>
<Select.Item value={`${ShellType.HE}`}>HE</Select.Item>
</Select.Content>
</Select.Root>
</Flex>
<Flex align="center" justify="between">
<Text>Caliber</Text>
<TextField.Root
style={{ maxWidth: '6rem' }}
defaultValue={customShellDraft.caliber}
type="number"
onBlur={(event: ChangeEvent<HTMLInputElement>) => {
setCustomShellDraft({
...customShellDraft,
caliber: event.target.valueAsNumber,
});
}}
onFocus={(event) => event.target.select()}
onKeyDown={(event) => {
if (event.key === 'Enter') {
(event.target as HTMLInputElement).blur();
} else if (event.key === 'Escape') {
(event.target as HTMLInputElement).valueAsNumber =
customShellDraft.caliber;
(event.target as HTMLInputElement).blur();
}
}}
>
<TextField.Slot side="right">mm</TextField.Slot>
</TextField.Root>
</Flex>
<Flex align="center" justify="between">
<Text>Penetration</Text>
<TextField.Root
style={{ maxWidth: '6rem' }}
defaultValue={customShellDraft.penetration.near}
type="number"
onBlur={(event: ChangeEvent<HTMLInputElement>) => {
setCustomShellDraft({
...customShellDraft,
penetration: {
near: event.target.valueAsNumber,
far: event.target.valueAsNumber,
},
});
}}
onFocus={(event) => event.target.select()}
onKeyDown={(event) => {
if (event.key === 'Enter') {
(event.target as HTMLInputElement).blur();
} else if (event.key === 'Escape') {
(event.target as HTMLInputElement).valueAsNumber =
customShellDraft.penetration.near;
(event.target as HTMLInputElement).blur();
}
}}
>
<TextField.Slot side="right">mm</TextField.Slot>
</TextField.Root>
</Flex>
{(customShellDraft.type === ShellType.AP ||
customShellDraft.type === ShellType.APCR) && (
<>
<Flex align="center" justify="between" gap="4" py="1">
<Text>Normalization</Text>
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
<Text color="gray">
{customShellDraft.normalization ?? 0}°
</Text>
<Slider
min={0}
max={90}
value={[customShellDraft.normalization ?? 0]}
onValueChange={([value]) =>
setCustomShellDraft({
...customShellDraft,
normalization: value,
})
} }
/>
</Flex> mutateTankopediaEphemeral((draft) => {
draft.customShell = modified;
});
}}
>
<Select.Trigger />
<Select.Content>
<Select.Item value={`${ShellType.AP}`}>AP</Select.Item>
<Select.Item value={`${ShellType.APCR}`}>APCR</Select.Item>
<Select.Item value={`${ShellType.HEAT}`}>HEAT</Select.Item>
<Select.Item value={`${ShellType.HE}`}>HE</Select.Item>
</Select.Content>
</Select.Root>
</Flex> </Flex>
<Flex align="center" justify="between" gap="4" py="1"> <Flex align="center" justify="between" gap="4">
<Text>Ricochet</Text> <Text>Caliber</Text>
<TextField.Root
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem"> style={{ maxWidth: '6rem' }}
<Text color="gray">{customShellDraft.ricochet ?? 90}°</Text> defaultValue={customShell.caliber}
<Slider type="number"
min={0} onChange={(event) => {
max={90} mutateTankopediaEphemeral((draft) => {
value={[customShellDraft.ricochet ?? 90]} draft.customShell!.caliber = event.target.valueAsNumber;
onValueChange={([value]) => });
setCustomShellDraft({ }}
...customShellDraft, onFocus={(event) => event.target.select()}
ricochet: value, onKeyDown={(event) => {
}) 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();
} }
/> }}
</Flex> >
<TextField.Slot side="right">mm</TextField.Slot>
</TextField.Root>
</Flex> </Flex>
</>
)}
</Flex>
<Flex justify="end" mt="6" gap="2"> <Flex align="center" justify="between" gap="4">
<Dialog.Close> <Text>Penetration</Text>
<Button color="red" variant="outline"> <TextField.Root
Cancel style={{ maxWidth: '6rem' }}
</Button> defaultValue={customShell.penetration.near}
</Dialog.Close> type="number"
<Button onChange={(event: ChangeEvent<HTMLInputElement>) => {
color="red" mutateTankopediaEphemeral((draft) => {
onClick={() => { draft.customShell!.penetration.near =
setCustomShellDraft(antagonistShell); event.target.valueAsNumber;
}} });
> }}
Reset onFocus={(event) => event.target.select()}
</Button> onKeyDown={(event) => {
<Dialog.Close> if (event.key === 'Enter') {
<Button (event.target as HTMLInputElement).blur();
onClick={() => { } else if (event.key === 'Escape') {
invalidate(); (event.target as HTMLInputElement).valueAsNumber =
mutateTankopediaEphemeral((draft) => { customShell.penetration.near;
draft.shot = undefined; (event.target as HTMLInputElement).blur();
draft.customShell = customShellDraft; }
}); }}
}} >
> <TextField.Slot side="right">mm</TextField.Slot>
Apply </TextField.Root>
</Button> </Flex>
</Dialog.Close>
</Flex> {(customShell.type === ShellType.AP ||
</Dialog.Content> customShell.type === ShellType.APCR) && (
</Dialog.Root> <>
<Flex align="center" justify="between" gap="4" py="1">
<Text>Normalization</Text>
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
<Text color="gray">
{customShell.normalization ?? 0}°
</Text>
<Slider
min={0}
max={90}
value={[customShell.normalization ?? 0]}
onValueChange={([value]) => {
mutateTankopediaEphemeral((draft) => {
draft.customShell!.normalization = value;
});
}}
/>
</Flex>
</Flex>
<Flex align="center" justify="between" gap="4" py="1">
<Text>Ricochet</Text>
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
<Text color="gray">{customShell.ricochet ?? 90}°</Text>
<Slider
min={0}
max={90}
value={[customShell.ricochet ?? 90]}
onValueChange={([value]) => {
mutateTankopediaEphemeral((draft) => {
draft.customShell!.ricochet = value;
});
}}
/>
</Flex>
</Flex>
</>
)}
</Flex>
<Flex justify="end" mt="6" gap="2">
<Popover.Close>
<Button color="red" variant="outline">
Exit
</Button>
</Popover.Close>
<Button
color="red"
onClick={() => {
mutateTankopediaEphemeral((draft) => {
draft.customShell = antagonistShell;
});
}}
>
Reset
</Button>
</Flex>
</>
)}
</Popover.Content>
</Popover.Root>
); );
} }
@@ -53,7 +53,9 @@ interface OptionsProps {
} }
export function Options({ thicknessRange, canvas }: OptionsProps) { export function Options({ thicknessRange, canvas }: OptionsProps) {
const customShell = TankopediaEphemeral.use((state) => state.customShell); const hasCustomShell = TankopediaEphemeral.use(
(state) => state.customShell !== undefined,
);
const display = TankopediaPersistent.useDeferred( const display = TankopediaPersistent.useDeferred(
(state) => state.display, (state) => state.display,
TankopediaDisplay.Model, TankopediaDisplay.Model,
@@ -101,15 +103,17 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
style={{ position: 'absolute', transform: 'translateY(-50%)' }} style={{ position: 'absolute', transform: 'translateY(-50%)' }}
align="end" align="end"
> >
<Text color="gray" size={{ initial: '1', sm: '2' }}> {!hasCustomShell && (
{( <Text color="gray" size={{ initial: '1', sm: '2' }}>
resolvePenetrationCoefficient( {(
hasCalibratedShells, resolvePenetrationCoefficient(
antagonistShell.type, hasCalibratedShells,
) * antagonistShell.penetration.near antagonistShell.type,
).toFixed(0)} ) * antagonistShell.penetration.near
mm ).toFixed(0)}
</Text> mm
</Text>
)}
{antagonistUniqueGuns.length > 1 && ( {antagonistUniqueGuns.length > 1 && (
<Popover.Root> <Popover.Root>
<Popover.Trigger> <Popover.Trigger>
@@ -189,7 +193,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
{antagonistGun.gun_type!.value.base.shells.map((thisShell) => ( {antagonistGun.gun_type!.value.base.shells.map((thisShell) => (
<IconButton <IconButton
color={ color={
thisShell.id === antagonistShell.id && !customShell thisShell.id === antagonistShell.id && !hasCustomShell
? undefined ? undefined
: 'gray' : 'gray'
} }