mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-26 03:59:53 +01:00
feat: view range visualizer
This commit is contained in:
1 parent
2443ecde2f
commit
c174f32e15
9 files changed
+156
-2
No files matched your search
@@ -873,6 +873,13 @@
|
|||||||
"ricochet": {
|
"ricochet": {
|
||||||
"safe": "Safe <%s1°",
|
"safe": "Safe <%s1°",
|
||||||
"unsafe": "Unsafe ≥%s1°"
|
"unsafe": "Unsafe ≥%s1°"
|
||||||
|
},
|
||||||
|
|
||||||
|
"view_range": {
|
||||||
|
"camo": "Camo",
|
||||||
|
"percentage": "%s1%",
|
||||||
|
"desert_sands": "Desert Sands",
|
||||||
|
"new_bay": "New Bay"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 199 KiB |
File renamed without changes.
File renamed without changes.
+145
@@ -0,0 +1,145 @@
|
|||||||
|
import { literals } from '@blitzkit/i18n';
|
||||||
|
import { EyeOpenIcon } from '@radix-ui/react-icons';
|
||||||
|
import { Box, Card, Code, Flex, Slider, Tabs, Text } from '@radix-ui/themes';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import { Var } from '../../../../../../core/radix/var';
|
||||||
|
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||||
|
import type { VisualizerProps } from './HullTraverseVisualizer';
|
||||||
|
|
||||||
|
const maps = [
|
||||||
|
{
|
||||||
|
id: 'new-bay',
|
||||||
|
string: 'new_bay',
|
||||||
|
size: 512,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'desert-sands',
|
||||||
|
string: 'desert_sands',
|
||||||
|
size: 512,
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function ViewRangeVisualizer({ stats }: VisualizerProps) {
|
||||||
|
const [mapIndex, setMapIndex] = useState(0);
|
||||||
|
const [camouflage, setCamouflage] = useState(0);
|
||||||
|
const map = maps[mapIndex];
|
||||||
|
const resolvedViewRange = stats.viewRange * (1 - camouflage);
|
||||||
|
const fraction = resolvedViewRange / map.size;
|
||||||
|
const fractionPercentage = `${fraction * 100}%`;
|
||||||
|
const { strings } = useLocale();
|
||||||
|
const container = useRef<HTMLDivElement>(null);
|
||||||
|
const range = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ aspectRatio: '1 / 1' }} mb="6">
|
||||||
|
<Box
|
||||||
|
ref={container}
|
||||||
|
onPointerMove={(event) => {
|
||||||
|
if (!container.current || !range.current) return;
|
||||||
|
|
||||||
|
const rect = container.current.getBoundingClientRect();
|
||||||
|
range.current.style.left = `${event.clientX - rect.left}px`;
|
||||||
|
range.current.style.top = `${event.clientY - rect.top - 16}px`;
|
||||||
|
}}
|
||||||
|
flexGrow="1"
|
||||||
|
position="absolute"
|
||||||
|
top="0"
|
||||||
|
left="0"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(/assets/images/tankopedia/visualizers/range/${map.id}.webp)`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
ref={range}
|
||||||
|
position="absolute"
|
||||||
|
top="50%"
|
||||||
|
left="50%"
|
||||||
|
width={fractionPercentage}
|
||||||
|
align="center"
|
||||||
|
justify="center"
|
||||||
|
style={{
|
||||||
|
aspectRatio: '1 / 1',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
background: `radial-gradient(${Var('gray-a1')}, ${Var('gray-a12')})`,
|
||||||
|
borderRadius: '50%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
<Flex align="center" gap="2">
|
||||||
|
<EyeOpenIcon width="1.25rem" height="1.25rem" />
|
||||||
|
{literals(strings.common.units.m, [resolvedViewRange.toFixed(0)])}
|
||||||
|
</Flex>
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
top="0"
|
||||||
|
left="0"
|
||||||
|
width="100%"
|
||||||
|
p="2"
|
||||||
|
gap="3"
|
||||||
|
align="center"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(${Var('black-a9')}, ${Var('black-a4')})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
{strings.website.tools.tankopedia.visualizers.view_range.camo}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Slider
|
||||||
|
variant="classic"
|
||||||
|
value={[camouflage]}
|
||||||
|
min={0}
|
||||||
|
max={0.5}
|
||||||
|
step={Number.EPSILON}
|
||||||
|
onValueChange={([value]) => setCamouflage(value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Code highContrast color="gray" variant="ghost">
|
||||||
|
<Flex justify="center" width="3em">
|
||||||
|
{literals(
|
||||||
|
strings.website.tools.tankopedia.visualizers.view_range
|
||||||
|
.percentage,
|
||||||
|
[`${Math.round(camouflage * 100)}`.padStart(3, ' ')],
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Code>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
justify="center"
|
||||||
|
position="absolute"
|
||||||
|
bottom="0"
|
||||||
|
left="0"
|
||||||
|
width="100%"
|
||||||
|
style={{
|
||||||
|
background: `radial-gradient(at 50% 100%, ${Var('gray-1')}, transparent 70%)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs.Root
|
||||||
|
value={`${mapIndex}`}
|
||||||
|
onValueChange={(index) => setMapIndex(Number(index))}
|
||||||
|
>
|
||||||
|
<Tabs.List>
|
||||||
|
{maps.map((map) => (
|
||||||
|
<Tabs.Trigger key={map.id} value={`${maps.indexOf(map)}`}>
|
||||||
|
{
|
||||||
|
strings.website.tools.tankopedia.visualizers.view_range[
|
||||||
|
map.string
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</Tabs.Trigger>
|
||||||
|
))}
|
||||||
|
</Tabs.List>
|
||||||
|
</Tabs.Root>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
+4
-2
@@ -34,7 +34,8 @@ import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
|||||||
import { HullTraverseVisualizer } from './components/HullTraverseVisualizer';
|
import { HullTraverseVisualizer } from './components/HullTraverseVisualizer';
|
||||||
import { Info } from './components/Info';
|
import { Info } from './components/Info';
|
||||||
import { InfoWithDelta } from './components/InfoWithDelta';
|
import { InfoWithDelta } from './components/InfoWithDelta';
|
||||||
import { RicochetVisualizer } from './components/RicochetVisuazlizer';
|
import { RicochetVisualizer } from './components/RicochetVisualizer';
|
||||||
|
import { ViewRangeVisualizer } from './components/ViewRangeVisualizer';
|
||||||
|
|
||||||
const [equipmentDefinitions, provisionDefinitions] = await Promise.all([
|
const [equipmentDefinitions, provisionDefinitions] = await Promise.all([
|
||||||
awaitableEquipmentDefinitions,
|
awaitableEquipmentDefinitions,
|
||||||
@@ -398,7 +399,7 @@ export function Characteristics() {
|
|||||||
/>
|
/>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
stats={stats}
|
stats={stats}
|
||||||
unit="m/s"
|
unit="m"
|
||||||
decimals={0}
|
decimals={0}
|
||||||
value="shellRange"
|
value="shellRange"
|
||||||
/>
|
/>
|
||||||
@@ -786,6 +787,7 @@ export function Characteristics() {
|
|||||||
unit="m"
|
unit="m"
|
||||||
decimals={0}
|
decimals={0}
|
||||||
/>
|
/>
|
||||||
|
<ViewRangeVisualizer stats={stats} />
|
||||||
<Info
|
<Info
|
||||||
name={
|
name={
|
||||||
strings.website.tools.tankopedia.characteristics.values.camouflage
|
strings.website.tools.tankopedia.characteristics.values.camouflage
|
||||||
|
|||||||
Reference in new issue
Block a user