mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: code split
This commit is contained in:
1 parent
857cba902a
commit
5208cf8678
6 files changed
+253
-251
No files matched your search
@@ -14,5 +14,7 @@ export function Controls() {
|
||||
orbitControls.current?.target.set(0, 1.5, 0);
|
||||
}, []);
|
||||
|
||||
return <OrbitControls ref={orbitControls} enabled={enabled} />;
|
||||
return (
|
||||
<OrbitControls ref={orbitControls} enabled={enabled} rotateSpeed={0.75} />
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import {
|
||||
EnterFullScreenIcon,
|
||||
ExitFullScreenIcon,
|
||||
FrameIcon,
|
||||
} from '@radix-ui/react-icons';
|
||||
import { Button, Card, Flex, Tabs, Theme } from '@radix-ui/themes';
|
||||
import { Html } from '@react-three/drei';
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { Suspense, useEffect, useRef, useState } from 'react';
|
||||
import { Group } from 'three';
|
||||
import { theme } from '../../../../../../stitches.config';
|
||||
import mutateTankopedia, {
|
||||
TankopediaMode,
|
||||
useTankopedia,
|
||||
} from '../../../../../../stores/tankopedia';
|
||||
import { Loader } from '../../../../components/Loader';
|
||||
import { Controls } from '../Control';
|
||||
import { RotationInputs } from '../RotationInputs';
|
||||
import { SceneProps } from '../SceneProps';
|
||||
import { TankModel } from './components/TankModel';
|
||||
|
||||
interface TankDisplayProps {
|
||||
tankId: number;
|
||||
turretId: number;
|
||||
gunId: number;
|
||||
}
|
||||
|
||||
export function TankDisplay({ tankId, turretId, gunId }: TankDisplayProps) {
|
||||
const canvas = useRef<HTMLCanvasElement>(null);
|
||||
const canvasWrapper = useRef<HTMLDivElement>(null);
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
const mode = useTankopedia((state) => state.mode);
|
||||
const hullContainer = useRef<Group>(null);
|
||||
const showGrid = useTankopedia((state) => state.model.showGrid);
|
||||
|
||||
function handlePointerDown() {
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
event.preventDefault();
|
||||
}
|
||||
function handlePointerUp(event: PointerEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function handleFullScreenChange() {
|
||||
setIsFullScreen(document.fullscreenElement !== null);
|
||||
}
|
||||
|
||||
document.addEventListener('fullscreenchange', handleFullScreenChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('fullscreenchange', handleFullScreenChange);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Theme radius={isFullScreen ? 'none' : undefined}>
|
||||
<Card
|
||||
style={{
|
||||
position: 'relative',
|
||||
border: isFullScreen ? 'none' : 'unset',
|
||||
}}
|
||||
ref={canvasWrapper}
|
||||
>
|
||||
<Theme radius="full" style={{ height: '100%' }}>
|
||||
<Flex
|
||||
style={{
|
||||
height: isFullScreen ? '100%' : '75vh',
|
||||
maxHeight: isFullScreen ? 'unset' : 576,
|
||||
}}
|
||||
direction="column"
|
||||
gap="2"
|
||||
>
|
||||
<Tabs.Root
|
||||
value={mode}
|
||||
onValueChange={(mode) => {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.mode = mode as TankopediaMode;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="model">Model</Tabs.Trigger>
|
||||
<Tabs.Trigger value="armor">Armor</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
|
||||
<div style={{ height: '100%' }}>
|
||||
<Canvas
|
||||
shadows
|
||||
ref={canvas}
|
||||
camera={{ fov: 20 }}
|
||||
onPointerDown={handlePointerDown}
|
||||
>
|
||||
<Controls />
|
||||
<SceneProps />
|
||||
|
||||
<Suspense
|
||||
fallback={
|
||||
<Html center position={[0, 1.5, 0]}>
|
||||
<Loader
|
||||
naked
|
||||
color={theme.colors.textHighContrast_purple}
|
||||
/>
|
||||
</Html>
|
||||
}
|
||||
>
|
||||
<TankModel
|
||||
gunId={gunId}
|
||||
tankId={tankId}
|
||||
turretId={turretId}
|
||||
ref={hullContainer}
|
||||
/>
|
||||
</Suspense>
|
||||
</Canvas>
|
||||
</div>
|
||||
|
||||
<Flex
|
||||
gap="4"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 18,
|
||||
right: 18,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
color={showGrid ? 'purple' : 'gray'}
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.model.showGrid = !draft.model.showGrid;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FrameIcon />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
if (isFullScreen) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
canvasWrapper.current?.requestFullscreen();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isFullScreen ? (
|
||||
<ExitFullScreenIcon />
|
||||
) : (
|
||||
<EnterFullScreenIcon />
|
||||
)}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<RotationInputs gunId={gunId} tankId={tankId} turretId={turretId} />
|
||||
</Flex>
|
||||
</Theme>
|
||||
</Card>
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||
+6
-6
@@ -8,17 +8,17 @@ import {
|
||||
} from 'react';
|
||||
import { Group, Vector3 } from 'three';
|
||||
import { GLTFLoader } from 'three-stdlib';
|
||||
import { X_AXIS } from '../../../../../constants/axis';
|
||||
import { X_AXIS } from '../../../../../../../constants/axis';
|
||||
import {
|
||||
ModelDefinitions,
|
||||
modelDefinitions,
|
||||
} from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||
} from '../../../../../../../core/blitzkrieg/modelDefinitions';
|
||||
import mutateTankopedia, {
|
||||
useTankopedia,
|
||||
} from '../../../../../stores/tankopedia';
|
||||
import { GunContainer } from './GunContainer';
|
||||
import { HullContainer } from './HullContainer';
|
||||
import { TurretContainer } from './TurretContainer';
|
||||
} from '../../../../../../../stores/tankopedia';
|
||||
import { GunContainer } from '../../GunContainer';
|
||||
import { HullContainer } from '../../HullContainer';
|
||||
import { TurretContainer } from '../../TurretContainer';
|
||||
|
||||
interface TankModelProps {
|
||||
tankId: number;
|
||||
@@ -1,22 +1,29 @@
|
||||
import { Color } from 'three';
|
||||
import InfiniteGridHelper from '../../../../../components/InfiniteGridHelper';
|
||||
import { useTankopedia } from '../../../../../stores/tankopedia';
|
||||
|
||||
export function SceneProps() {
|
||||
const showGrid = useTankopedia((state) => state.model.showGrid);
|
||||
|
||||
return (
|
||||
<>
|
||||
<InfiniteGridHelper
|
||||
size1={1 / 5}
|
||||
size2={1}
|
||||
distance={20}
|
||||
color={new Color('#ffffff')}
|
||||
/>
|
||||
<InfiniteGridHelper
|
||||
position={[0, 1e-4, 0]}
|
||||
size1={0}
|
||||
size2={100}
|
||||
distance={25}
|
||||
color={new Color('red')}
|
||||
/>
|
||||
{showGrid && (
|
||||
<>
|
||||
<InfiniteGridHelper
|
||||
size1={1 / 5}
|
||||
size2={1}
|
||||
distance={20}
|
||||
color={new Color('#ffffff')}
|
||||
/>
|
||||
<InfiniteGridHelper
|
||||
position={[0, 1e-4, 0]}
|
||||
size1={0}
|
||||
size2={100}
|
||||
distance={25}
|
||||
color={new Color('red')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<directionalLight
|
||||
position={[1, 1, -1]}
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
CaretRightIcon,
|
||||
EnterFullScreenIcon,
|
||||
ExitFullScreenIcon,
|
||||
GearIcon,
|
||||
MagnifyingGlassIcon,
|
||||
} from '@radix-ui/react-icons';
|
||||
import {
|
||||
@@ -15,20 +12,17 @@ import {
|
||||
Dialog,
|
||||
Flex,
|
||||
Heading,
|
||||
Slider,
|
||||
Tabs,
|
||||
Text,
|
||||
TextField,
|
||||
Theme,
|
||||
Tooltip,
|
||||
} from '@radix-ui/themes';
|
||||
import { Html } from '@react-three/drei';
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { go } from 'fuzzysort';
|
||||
import { debounce } from 'lodash';
|
||||
import Link from 'next/link';
|
||||
import { Suspense, use, useEffect, useRef, useState } from 'react';
|
||||
import { Group, Vector3 } from 'three';
|
||||
import { use, useRef, useState } from 'react';
|
||||
import { Vector3 } from 'three';
|
||||
import { Flag } from '../../../../components/Flag';
|
||||
import { ModuleButtons } from '../../../../components/ModuleButton';
|
||||
import PageWrapper from '../../../../components/PageWrapper';
|
||||
@@ -38,13 +32,8 @@ import {
|
||||
tankDefinitions,
|
||||
tankNamesDiacritics,
|
||||
} from '../../../../core/blitzkrieg/tankDefinitions';
|
||||
import { theme } from '../../../../stitches.config';
|
||||
import mutateTankopedia from '../../../../stores/tankopedia';
|
||||
import { Loader } from '../../components/Loader';
|
||||
import { Controls } from './components/Control';
|
||||
import { RotationInputs } from './components/RotationInputs';
|
||||
import { SceneProps } from './components/SceneProps';
|
||||
import { TankModel } from './components/TankModel';
|
||||
import { useTankopedia } from '../../../../stores/tankopedia';
|
||||
import { TankDisplay } from './components/Model/TankDisplay';
|
||||
|
||||
const X_AXIS = new Vector3(1, 0, 0);
|
||||
|
||||
@@ -56,9 +45,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
const [turret, setTurret] = useState(tank.turrets.at(-1)!);
|
||||
const [gun, setGun] = useState(turret.guns.at(-1)!);
|
||||
const [crew, setCrew] = useState(100);
|
||||
const [mode, setMode] = useState('model');
|
||||
const versusTankSearchInput = useRef<HTMLInputElement>(null);
|
||||
const hullContainer = useRef<Group>(null);
|
||||
const [versusTankSearchResults, setVersusTankSearchResults] = useState<
|
||||
number[]
|
||||
>([]);
|
||||
@@ -66,35 +53,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
const [versusTurret, setVersusTurret] = useState(versusTank.turrets.at(-1)!);
|
||||
const [versusGun, setVersusGun] = useState(versusTurret.guns.at(-1)!);
|
||||
const [versusTankTab, setVersusTankTab] = useState('search');
|
||||
const canvas = useRef<HTMLCanvasElement>(null);
|
||||
const canvasWrapper = useRef<HTMLDivElement>(null);
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
|
||||
function handlePointerDown() {
|
||||
window.addEventListener('pointermove', handlePointerMove);
|
||||
window.addEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
function handlePointerMove(event: PointerEvent) {
|
||||
event.preventDefault();
|
||||
}
|
||||
function handlePointerUp(event: PointerEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
window.removeEventListener('pointermove', handlePointerMove);
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function handleFullScreenChange() {
|
||||
setIsFullScreen(document.fullscreenElement !== null);
|
||||
}
|
||||
|
||||
document.addEventListener('fullscreenchange', handleFullScreenChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('fullscreenchange', handleFullScreenChange);
|
||||
};
|
||||
});
|
||||
const mode = useTankopedia((state) => state.mode);
|
||||
|
||||
return (
|
||||
<PageWrapper color="purple">
|
||||
@@ -117,153 +76,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Theme radius={isFullScreen ? 'none' : undefined}>
|
||||
<Card
|
||||
style={{
|
||||
position: 'relative',
|
||||
border: isFullScreen ? 'none' : 'unset',
|
||||
}}
|
||||
ref={canvasWrapper}
|
||||
>
|
||||
<Theme radius="full" style={{ height: '100%' }}>
|
||||
<Flex
|
||||
style={{
|
||||
height: isFullScreen ? '100%' : '75vh',
|
||||
maxHeight: isFullScreen ? 'unset' : 576,
|
||||
}}
|
||||
direction="column"
|
||||
gap="2"
|
||||
>
|
||||
<Tabs.Root value={mode} onValueChange={setMode}>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="model">Model</Tabs.Trigger>
|
||||
<Tabs.Trigger value="armor">Armor</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
|
||||
<div style={{ height: '100%' }}>
|
||||
<Canvas
|
||||
shadows
|
||||
ref={canvas}
|
||||
camera={{ fov: 20 }}
|
||||
onPointerDown={handlePointerDown}
|
||||
>
|
||||
<Controls />
|
||||
<SceneProps />
|
||||
|
||||
<Suspense
|
||||
fallback={
|
||||
<Html center position={[0, 1.5, 0]}>
|
||||
<Loader
|
||||
naked
|
||||
color={theme.colors.textHighContrast_purple}
|
||||
/>
|
||||
</Html>
|
||||
}
|
||||
>
|
||||
<TankModel
|
||||
gunId={gun.id}
|
||||
tankId={tank.id}
|
||||
turretId={turret.id}
|
||||
ref={hullContainer}
|
||||
/>
|
||||
</Suspense>
|
||||
</Canvas>
|
||||
</div>
|
||||
|
||||
<Flex
|
||||
gap="4"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 18,
|
||||
right: 18,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
mutateTankopedia((draft) => {
|
||||
draft.showSettings = true;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<GearIcon />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
if (isFullScreen) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
canvasWrapper.current?.requestFullscreen();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isFullScreen ? (
|
||||
<ExitFullScreenIcon />
|
||||
) : (
|
||||
<EnterFullScreenIcon />
|
||||
)}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<RotationInputs
|
||||
gunId={gun.id}
|
||||
tankId={tank.id}
|
||||
turretId={turret.id}
|
||||
/>
|
||||
</Flex>
|
||||
</Theme>
|
||||
</Card>
|
||||
</Theme>
|
||||
|
||||
<Theme radius="small">
|
||||
<Flex gap="4">
|
||||
<Flex gap="1">
|
||||
{tank.turrets.map((thisTurret) => {
|
||||
return (
|
||||
<Tooltip content={thisTurret.name} key={thisTurret.id}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTurret(thisTurret);
|
||||
setGun(thisTurret.guns.at(-1)!);
|
||||
}}
|
||||
variant={turret.id === thisTurret.id ? 'solid' : 'soft'}
|
||||
>
|
||||
<img
|
||||
src={asset('icons/modules/turret.webp')}
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
{TIER_ROMAN_NUMERALS[thisTurret.tier]}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
|
||||
<Flex gap="1">
|
||||
{turret.guns.map((thisGun) => {
|
||||
return (
|
||||
<Tooltip content={thisGun.name} key={thisGun.id}>
|
||||
<Button
|
||||
onClick={() => setGun(thisGun)}
|
||||
variant={gun.id === thisGun.id ? 'solid' : 'soft'}
|
||||
>
|
||||
<img
|
||||
src={asset('icons/modules/gun.webp')}
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
{TIER_ROMAN_NUMERALS[thisGun.tier]}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Theme>
|
||||
<TankDisplay gunId={gun.id} tankId={tank.id} turretId={turret.id} />
|
||||
|
||||
{mode === 'armor' && (
|
||||
<Card>
|
||||
@@ -464,7 +277,7 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
|
||||
<Flex gap="2" align="center">
|
||||
<Checkbox defaultChecked />
|
||||
<Text>Calibrated shells</Text>
|
||||
<Text>Calibrated</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="1">
|
||||
@@ -493,44 +306,53 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
</Flex>
|
||||
</Card>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex gap="6" direction="column">
|
||||
<Heading>Build</Heading>
|
||||
<Theme radius="small">
|
||||
<Flex gap="4">
|
||||
<Flex gap="1">
|
||||
{tank.turrets.map((thisTurret) => {
|
||||
return (
|
||||
<Tooltip content={thisTurret.name} key={thisTurret.id}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTurret(thisTurret);
|
||||
setGun(thisTurret.guns.at(-1)!);
|
||||
}}
|
||||
variant={turret.id === thisTurret.id ? 'solid' : 'soft'}
|
||||
>
|
||||
<img
|
||||
src={asset('icons/modules/turret.webp')}
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
{TIER_ROMAN_NUMERALS[thisTurret.tier]}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2" direction="column">
|
||||
<Heading size="5" weight="regular">
|
||||
Modules
|
||||
</Heading>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2" direction="column">
|
||||
<Heading size="5" weight="regular">
|
||||
Crew
|
||||
</Heading>
|
||||
|
||||
<Flex gap="4" align="center">
|
||||
<Text>{crew}%</Text>
|
||||
|
||||
<Slider
|
||||
style={{ flex: 1 }}
|
||||
value={[crew]}
|
||||
min={50}
|
||||
max={100}
|
||||
onValueChange={([crew]) => setCrew(crew)}
|
||||
/>
|
||||
<Flex gap="1">
|
||||
{turret.guns.map((thisGun) => {
|
||||
return (
|
||||
<Tooltip content={thisGun.name} key={thisGun.id}>
|
||||
<Button
|
||||
onClick={() => setGun(thisGun)}
|
||||
variant={gun.id === thisGun.id ? 'solid' : 'soft'}
|
||||
>
|
||||
<img
|
||||
src={asset('icons/modules/gun.webp')}
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
{TIER_ROMAN_NUMERALS[thisGun.tier]}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2" direction="column">
|
||||
<Heading size="5">Shells</Heading>
|
||||
|
||||
{gun.shells.map((shell) => (
|
||||
<Text key={shell.id}>
|
||||
{shell.name} ({shell.type}): {shell.damage.armor}
|
||||
</Text>
|
||||
))}
|
||||
</Theme>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</PageWrapper>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Tier } from '../core/blitzkrieg/tankDefinitions';
|
||||
export type TankopediaSortBy = 'tier' | 'name';
|
||||
export type TankopediaSortDirection = 'ascending' | 'descending';
|
||||
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
|
||||
export type TankopediaMode = 'model' | 'armor';
|
||||
|
||||
type Tankopedia = {
|
||||
sort: {
|
||||
@@ -24,8 +25,9 @@ type Tankopedia = {
|
||||
turretYaw: number;
|
||||
gunPitch: number;
|
||||
controlsEnabled: boolean;
|
||||
showGrid: boolean;
|
||||
};
|
||||
showSettings: boolean;
|
||||
mode: TankopediaMode;
|
||||
};
|
||||
|
||||
export const useTankopedia = create<Tankopedia>()(() => ({
|
||||
@@ -45,8 +47,9 @@ export const useTankopedia = create<Tankopedia>()(() => ({
|
||||
hullYaw: 0,
|
||||
turretYaw: 0,
|
||||
controlsEnabled: true,
|
||||
showGrid: true,
|
||||
},
|
||||
showSettings: false,
|
||||
mode: 'model',
|
||||
}));
|
||||
|
||||
export default function mutateTankopedia(recipe: (draft: Tankopedia) => void) {
|
||||
|
||||
Reference in new issue
Block a user