mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-22 03:07:04 +02:00
feat: revamp
This commit is contained in:
1 parent
468308ad22
commit
e3d69ab83c
4 files changed
+137
-198
No files matched your search
@@ -0,0 +1,103 @@
|
||||
import { EnterFullScreenIcon, ExitFullScreenIcon } from '@radix-ui/react-icons';
|
||||
import { Checkbox, Flex, Heading, IconButton, Text } from '@radix-ui/themes';
|
||||
import { TIER_ROMAN_NUMERALS } from '../../../../../../core/blitzkit/tankDefinitions/constants';
|
||||
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
||||
import strings from '../../../../../../lang/en-US.json';
|
||||
import { theme } from '../../../../../../stitches.config';
|
||||
import { useDuel } from '../../../../../../stores/duel';
|
||||
import mutateTankopediaPersistent, {
|
||||
useTankopediaPersistent,
|
||||
} from '../../../../../../stores/tankopedia';
|
||||
import { TankSandbox } from './TankSandbox';
|
||||
|
||||
export function HeroSection() {
|
||||
const tank = useDuel((state) => state.protagonist!.tank);
|
||||
const mode = useTankopediaPersistent((state) => state.mode);
|
||||
const isFullScreen = useFullScreen();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
justify="center"
|
||||
pl="9"
|
||||
style={{
|
||||
background: `linear-gradient(90deg, ${theme.colors.appBackground1}, ${theme.colors.appBackground2})`,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
style={{
|
||||
maxWidth: 1600,
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
justify="center"
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Heading size="7">{tank.name}</Heading>
|
||||
<Text color="gray">
|
||||
Tier {TIER_ROMAN_NUMERALS[tank.tier]}{' '}
|
||||
{(strings.common.nations as Record<string, string>)[tank.nation]}{' '}
|
||||
{strings.common.tank_class_short[tank.class]}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: 512,
|
||||
flex: 3,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<TankSandbox />
|
||||
</div>
|
||||
|
||||
<Flex
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 16,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
cursor: 'default',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
align="center"
|
||||
gap="2"
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.mode = draft.mode === 'armor' ? 'model' : 'armor';
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Checkbox checked={mode === 'armor'} />
|
||||
Armor
|
||||
</Flex>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 16,
|
||||
right: 16,
|
||||
}}
|
||||
onClick={() => {
|
||||
document.body.requestFullscreen();
|
||||
}}
|
||||
>
|
||||
{isFullScreen ? <ExitFullScreenIcon /> : <EnterFullScreenIcon />}
|
||||
</IconButton>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { LightningBoltIcon } from '@radix-ui/react-icons';
|
||||
import { Button, Card, Flex, Tabs, Text, Theme } from '@radix-ui/themes';
|
||||
import { PerspectiveCamera } from '@react-three/drei';
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { Suspense, use, useEffect, useRef, useState } from 'react';
|
||||
@@ -10,24 +8,18 @@ import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawL
|
||||
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
|
||||
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
||||
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
||||
import { tankIcon } from '../../../../../../core/blitzkit/tankIcon';
|
||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
||||
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
|
||||
import { BlitzkitWide } from '../../../../../../icons/BlitzkitWide';
|
||||
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
|
||||
import mutateTankopediaPersistent, {
|
||||
TankopediaMode,
|
||||
import {
|
||||
mutateTankopediaTemporary,
|
||||
useTankopediaPersistent,
|
||||
} from '../../../../../../stores/tankopedia';
|
||||
import { AntagonistBar } from '../AntagonistBar';
|
||||
import { Controls } from '../Control';
|
||||
import { Lighting } from '../Lighting';
|
||||
import { RotationInputs } from '../QuickInputs';
|
||||
import { SceneProps } from '../SceneProps';
|
||||
import { ModelLoader } from './components/ModelLoader';
|
||||
import { Options } from './components/Options';
|
||||
import { TankModel } from './components/TankModel';
|
||||
|
||||
export function TankSandbox() {
|
||||
@@ -138,137 +130,30 @@ export function TankSandbox() {
|
||||
}, [protagonist.gun, protagonist.turret]);
|
||||
|
||||
return (
|
||||
<Theme radius={isFullScreen ? 'none' : undefined}>
|
||||
<Card
|
||||
style={{
|
||||
position: isFullScreen ? 'fixed' : 'relative',
|
||||
border: isFullScreen ? 'none' : 'unset',
|
||||
width: isFullScreen ? '100vw' : 'unset',
|
||||
height: isFullScreen ? 'calc(100vh)' : 'unset',
|
||||
top: isFullScreen ? 0 : 'unset',
|
||||
left: isFullScreen ? 0 : 'unset',
|
||||
zIndex: isFullScreen ? 2 : 'unset',
|
||||
}}
|
||||
ref={canvasWrapper}
|
||||
>
|
||||
<Flex
|
||||
style={{
|
||||
height: isFullScreen ? '100%' : '75vh',
|
||||
maxHeight: isFullScreen ? 'unset' : 576,
|
||||
}}
|
||||
direction="column"
|
||||
gap="2"
|
||||
>
|
||||
<Tabs.Root
|
||||
value={mode}
|
||||
onValueChange={(mode) => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.mode = mode as TankopediaMode;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="model">
|
||||
<Flex gap="2" align="center">
|
||||
Model
|
||||
</Flex>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="armor">Armor</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
<Canvas
|
||||
gl={{
|
||||
clippingPlanes: Object.freeze([]),
|
||||
localClippingEnabled: true,
|
||||
}}
|
||||
shadows
|
||||
ref={canvas}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMissed={() => {
|
||||
mutateTankopediaTemporary((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PerspectiveCamera makeDefault fov={25} far={32} />
|
||||
<Controls />
|
||||
<SceneProps />
|
||||
|
||||
{wideFormat && (
|
||||
<Text
|
||||
color="gray"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: 10,
|
||||
}}
|
||||
>
|
||||
<BlitzkitWide height={32} />
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: 'calc(100% - 64px)',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
{loadModel ? (
|
||||
<div style={{ width: '100%', height: '100%' }}>
|
||||
<Canvas
|
||||
gl={{
|
||||
clippingPlanes: Object.freeze([]),
|
||||
localClippingEnabled: true,
|
||||
}}
|
||||
shadows
|
||||
ref={canvas}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMissed={() => {
|
||||
mutateTankopediaTemporary((draft) => {
|
||||
draft.shot = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PerspectiveCamera makeDefault fov={25} far={32} />
|
||||
<Controls />
|
||||
<SceneProps />
|
||||
|
||||
<Suspense fallback={<ModelLoader />}>
|
||||
<Lighting />
|
||||
<TankModel />
|
||||
<Armor />
|
||||
<ShotDisplay />
|
||||
</Suspense>
|
||||
</Canvas>
|
||||
</div>
|
||||
) : (
|
||||
<Flex
|
||||
align="center"
|
||||
justify="center"
|
||||
style={{ height: '100%', position: 'relative' }}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
zIndex: 1,
|
||||
}}
|
||||
onClick={() => {
|
||||
setLoadModel(true);
|
||||
}}
|
||||
>
|
||||
<LightningBoltIcon /> Load model
|
||||
</Button>
|
||||
|
||||
<Flex
|
||||
style={{
|
||||
filter: 'blur(16px)',
|
||||
width: 256,
|
||||
height: 128,
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transformOrigin: 'center center',
|
||||
transform: 'translate(25%, -25%) scale(300%)',
|
||||
}}
|
||||
>
|
||||
<img src={tankIcon(duel.antagonist!.tank.id, 'big')} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Options canvasWrapper={canvasWrapper} isFullScreen={isFullScreen} />
|
||||
|
||||
{isFullScreen && <AntagonistBar floating />}
|
||||
|
||||
{loadModel && <RotationInputs isFullScreen={isFullScreen} />}
|
||||
</Flex>
|
||||
</Card>
|
||||
</Theme>
|
||||
<Suspense fallback={<ModelLoader />}>
|
||||
<Lighting />
|
||||
<TankModel />
|
||||
<Armor />
|
||||
<ShotDisplay />
|
||||
</Suspense>
|
||||
</Canvas>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { Flex } from '@radix-ui/themes';
|
||||
import { useEffect } from 'react';
|
||||
import PageWrapper from '../../../../components/PageWrapper';
|
||||
import { assignDuelMember } from '../../../../core/blitzkit/assignDuelMember';
|
||||
import { useWideFormat } from '../../../../hooks/useWideFormat';
|
||||
import { mutateDuel, useDuel } from '../../../../stores/duel';
|
||||
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
|
||||
import { AntagonistBar } from './components/AntagonistBar';
|
||||
import { Characteristics } from './components/Characteristics';
|
||||
import { Consumables } from './components/Characteristics/components/Consumables';
|
||||
import { Equipment } from './components/Characteristics/components/Equipment';
|
||||
import { Miscellaneous } from './components/Characteristics/components/Miscellaneous';
|
||||
import { Modules } from './components/Characteristics/components/Modules';
|
||||
import { Provisions } from './components/Characteristics/components/Provisions';
|
||||
import { Skills } from './components/Characteristics/components/Skills';
|
||||
import { TankSandbox } from './components/Model/TankSandbox';
|
||||
import { Title } from './components/Title';
|
||||
import { Videos } from './components/Videos';
|
||||
import { HeroSection } from './components/Model/HeroSection';
|
||||
|
||||
export default function Page({ params }: { params: { id: string } }) {
|
||||
const initialId = parseInt(params.id);
|
||||
@@ -66,52 +55,10 @@ export default function Page({ params }: { params: { id: string } }) {
|
||||
}, [initialId, assigned]);
|
||||
|
||||
return (
|
||||
<PageWrapper color="purple" size={1600}>
|
||||
<PageWrapper noPadding noMaxWidth color="purple" size={1600}>
|
||||
{assigned && (
|
||||
<>
|
||||
<Title />
|
||||
|
||||
<Flex
|
||||
style={{ width: '100%' }}
|
||||
gap="8"
|
||||
direction={wideFormat ? 'row' : 'column'}
|
||||
align="start"
|
||||
justify="center"
|
||||
>
|
||||
<Flex
|
||||
gap="8"
|
||||
direction="column"
|
||||
style={{
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
top: 64 + 16,
|
||||
position: wideFormat ? 'sticky' : undefined,
|
||||
}}
|
||||
>
|
||||
<Flex gap="4" direction="column">
|
||||
<TankSandbox />
|
||||
<AntagonistBar />
|
||||
</Flex>
|
||||
<Flex gap="5" wrap="wrap">
|
||||
<Modules />
|
||||
<Provisions />
|
||||
<Consumables />
|
||||
|
||||
<Flex gap="5" wrap="wrap">
|
||||
<Skills />
|
||||
<Equipment />
|
||||
</Flex>
|
||||
|
||||
<Miscellaneous />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex style={{ width: wideFormat ? 320 : '100%' }}>
|
||||
<Characteristics />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Videos />
|
||||
<HeroSection />
|
||||
</>
|
||||
)}
|
||||
</PageWrapper>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ComponentProps } from 'react';
|
||||
type PageWrapperProps = FlexProps & {
|
||||
color?: ComponentProps<typeof Theme>['accentColor'];
|
||||
size?: number | string;
|
||||
noPadding?: boolean;
|
||||
noMaxWidth?: boolean;
|
||||
};
|
||||
|
||||
export default function PageWrapper({
|
||||
@@ -11,6 +13,8 @@ export default function PageWrapper({
|
||||
color,
|
||||
size = 800,
|
||||
children,
|
||||
noPadding = false,
|
||||
noMaxWidth = false,
|
||||
...props
|
||||
}: PageWrapperProps) {
|
||||
return (
|
||||
@@ -27,9 +31,9 @@ export default function PageWrapper({
|
||||
gap="4"
|
||||
style={{
|
||||
width: '100%',
|
||||
maxWidth: size,
|
||||
maxWidth: noMaxWidth ? undefined : size,
|
||||
margin: 'auto',
|
||||
padding: 16,
|
||||
padding: noPadding ? 0 : 16,
|
||||
boxSizing: 'border-box',
|
||||
flex: 1,
|
||||
...style,
|
||||
|
||||
Reference in new issue
Block a user