feat: code split

This commit is contained in:
TrèsAbhi committed 2024-01-06 18:17:04 -06:00
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>
);
}
@@ -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]}
+51 -229
View File
@@ -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>
+5 -2
View File
@@ -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) {