mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: in-hero controls
This commit is contained in:
1 parent
c090ad6492
commit
64d346ccb6
2 files changed
+119
-99
No files matched your search
@@ -1,9 +1,13 @@
|
|||||||
import { TankType } from '@blitzkit/core';
|
import { assertSecret, TankType } from '@blitzkit/core';
|
||||||
import { Box, Flex, Heading, Spinner, Text } from '@radix-ui/themes';
|
import { CaretLeftIcon, MixIcon, UpdateIcon } from '@radix-ui/react-icons';
|
||||||
|
import { Box, Dialog, Flex, Heading, Link, Spinner } from '@radix-ui/themes';
|
||||||
import { times } from 'lodash-es';
|
import { times } from 'lodash-es';
|
||||||
import { Suspense, useEffect, useMemo, useRef } from 'react';
|
import { Suspense, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
||||||
|
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
|
||||||
|
import { awaitableProvisionDefinitions } from '../../../core/awaitables/provisionDefinitions';
|
||||||
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
||||||
|
import { tankToDuelMember } from '../../../core/blitzkit/tankToDuelMember';
|
||||||
import { Var } from '../../../core/radix/var';
|
import { Var } from '../../../core/radix/var';
|
||||||
import { useFullScreen } from '../../../hooks/useFullScreen';
|
import { useFullScreen } from '../../../hooks/useFullScreen';
|
||||||
import { useLocale } from '../../../hooks/useLocale';
|
import { useLocale } from '../../../hooks/useLocale';
|
||||||
@@ -12,18 +16,27 @@ import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
|||||||
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
|
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
|
||||||
import type { ThicknessRange } from '../../Armor/components/StaticArmor';
|
import type { ThicknessRange } from '../../Armor/components/StaticArmor';
|
||||||
import { classIcons } from '../../ClassIcon';
|
import { classIcons } from '../../ClassIcon';
|
||||||
|
import { ScienceIcon } from '../../ScienceIcon';
|
||||||
|
import { TankSearch } from '../../TankSearch';
|
||||||
import { Options } from './components/Options';
|
import { Options } from './components/Options';
|
||||||
import { TankSandbox } from './components/TankSandbox';
|
import { TankSandbox } from './components/TankSandbox';
|
||||||
|
|
||||||
const tankDefinitions = await awaitableTankDefinitions;
|
const [provisionDefinitions, modelDefinitions, tankDefinitions] =
|
||||||
|
await Promise.all([
|
||||||
|
awaitableProvisionDefinitions,
|
||||||
|
awaitableModelDefinitions,
|
||||||
|
awaitableTankDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
|
const [showSwapDialog, setShowSwapDialog] = useState(false);
|
||||||
const revealed = TankopediaEphemeral.use((state) => state.revealed);
|
const revealed = TankopediaEphemeral.use((state) => state.revealed);
|
||||||
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
||||||
const { unwrap } = useLocale();
|
const { unwrap, strings } = useLocale();
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||||
|
const antagonist = Duel.use((state) => state.antagonist.tank);
|
||||||
const Icon = classIcons[protagonist.class];
|
const Icon = classIcons[protagonist.class];
|
||||||
const treeColor =
|
const treeColor =
|
||||||
protagonist.type === TankType.COLLECTOR
|
protagonist.type === TankType.COLLECTOR
|
||||||
@@ -31,6 +44,10 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
: protagonist.type === TankType.PREMIUM
|
: protagonist.type === TankType.PREMIUM
|
||||||
? 'amber'
|
? 'amber'
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const compareTanks =
|
||||||
|
protagonist.id === antagonist.id
|
||||||
|
? [protagonist.id]
|
||||||
|
: [protagonist.id, antagonist.id];
|
||||||
const thicknessRange = useMemo(() => {
|
const thicknessRange = useMemo(() => {
|
||||||
const entries = Object.values(tankDefinitions.tanks);
|
const entries = Object.values(tankDefinitions.tanks);
|
||||||
const filtered = entries.filter(
|
const filtered = entries.filter(
|
||||||
@@ -152,10 +169,10 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
}}
|
}}
|
||||||
direction="column"
|
direction="column"
|
||||||
align={{ initial: 'center', md: 'start' }}
|
align={{ initial: 'center', md: 'center' }}
|
||||||
gap={disturbed ? '0' : { initial: '0', md: '2' }}
|
gap="1"
|
||||||
>
|
>
|
||||||
<Flex align="center" gap="3">
|
<Flex align="center" gap="3" width="100%" justify="start">
|
||||||
<Heading
|
<Heading
|
||||||
color={treeColor}
|
color={treeColor}
|
||||||
trim="end"
|
trim="end"
|
||||||
@@ -184,23 +201,101 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
</Heading>
|
</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Box ml="3">
|
<Flex
|
||||||
<Text
|
mb="4"
|
||||||
|
gap="4"
|
||||||
|
ml={disturbed ? '0' : '-2'}
|
||||||
|
style={{ transitionDuration: '200ms' }}
|
||||||
|
>
|
||||||
|
<Link color="gray" underline="always" href="/tools/tankopedia">
|
||||||
|
<Flex align="center" gap="1">
|
||||||
|
<CaretLeftIcon />
|
||||||
|
{strings.website.tools.tankopedia.meta.back}
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
color="gray"
|
color="gray"
|
||||||
size={{
|
underline="always"
|
||||||
initial: disturbed ? '3' : '4',
|
href={`/tools/compare?tanks=${compareTanks.join('%2C')}`}
|
||||||
lg: disturbed ? '4' : '5',
|
|
||||||
}}
|
|
||||||
weight="light"
|
|
||||||
ml={{
|
|
||||||
initial: '0',
|
|
||||||
md: disturbed ? 'var(--font-size-6)' : 'var(--font-size-7)',
|
|
||||||
lg: disturbed ? 'var(--font-size-7)' : 'var(--font-size-8)',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
BlitzKit Tankopedia
|
<Flex align="center" gap="1">
|
||||||
</Text>
|
<MixIcon />
|
||||||
</Box>
|
{strings.website.tools.tankopedia.meta.compare}
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Dialog.Root open={showSwapDialog} onOpenChange={setShowSwapDialog}>
|
||||||
|
<Dialog.Trigger>
|
||||||
|
<Link
|
||||||
|
color="gray"
|
||||||
|
underline="always"
|
||||||
|
href="#"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setShowSwapDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex align="center" gap="1">
|
||||||
|
<UpdateIcon />
|
||||||
|
{strings.website.tools.tankopedia.meta.swap.button}
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Title>
|
||||||
|
{strings.website.tools.tankopedia.meta.swap.title}
|
||||||
|
</Dialog.Title>
|
||||||
|
|
||||||
|
<TankSearch
|
||||||
|
compact
|
||||||
|
onSelect={(tank) => {
|
||||||
|
const model = modelDefinitions.models[tank.id];
|
||||||
|
|
||||||
|
setShowSwapDialog(false);
|
||||||
|
mutateTankopediaEphemeral((draft) => {
|
||||||
|
draft.model = modelDefinitions.models[tank.id];
|
||||||
|
});
|
||||||
|
mutateDuel((draft) => {
|
||||||
|
draft.protagonist = tankToDuelMember(
|
||||||
|
tank,
|
||||||
|
model,
|
||||||
|
provisionDefinitions,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.history.replaceState(
|
||||||
|
null,
|
||||||
|
'',
|
||||||
|
`/tools/tankopedia/${tank.id}`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
|
||||||
|
{assertSecret(import.meta.env.PUBLIC_PROMOTE_OPENTEST) ===
|
||||||
|
'true' && (
|
||||||
|
<Link
|
||||||
|
color="jade"
|
||||||
|
href={`https://${
|
||||||
|
assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH) ===
|
||||||
|
'opentest'
|
||||||
|
? ''
|
||||||
|
: 'opentest.'
|
||||||
|
}blitzkit.app/tools/tankopedia/${protagonist.id}`}
|
||||||
|
>
|
||||||
|
<Flex align="center" gap="1">
|
||||||
|
<ScienceIcon width="1.25em" height="1.25em" />
|
||||||
|
{assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH) ===
|
||||||
|
'opentest'
|
||||||
|
? strings.website.tools.tankopedia.meta.vanilla
|
||||||
|
: strings.website.tools.tankopedia.meta.opentest}
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ const [provisionDefinitions, modelDefinitions] = await Promise.all([
|
|||||||
export function MetaSection() {
|
export function MetaSection() {
|
||||||
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
||||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||||
const ClassIcon = classIcons[protagonist.class];
|
|
||||||
const antagonist = Duel.use((state) => state.antagonist.tank);
|
const antagonist = Duel.use((state) => state.antagonist.tank);
|
||||||
|
const ClassIcon = classIcons[protagonist.class];
|
||||||
const compareTanks =
|
const compareTanks =
|
||||||
protagonist.id === antagonist.id
|
protagonist.id === antagonist.id
|
||||||
? [protagonist.id]
|
? [protagonist.id]
|
||||||
@@ -43,82 +43,7 @@ export function MetaSection() {
|
|||||||
return (
|
return (
|
||||||
<Flex justify="center" align="center">
|
<Flex justify="center" align="center">
|
||||||
<Flex direction="column" align="center" gap="6">
|
<Flex direction="column" align="center" gap="6">
|
||||||
<Flex gap="2" wrap="wrap" justify="center" align="center">
|
|
||||||
<LinkI18n locale={locale} href="/tools/tankopedia">
|
|
||||||
<Button variant="outline">
|
|
||||||
<ChevronLeftIcon />
|
|
||||||
{strings.website.tools.tankopedia.meta.back}
|
|
||||||
</Button>
|
|
||||||
</LinkI18n>
|
|
||||||
|
|
||||||
<LinkI18n
|
|
||||||
locale={locale}
|
|
||||||
href={`/tools/compare?tanks=${compareTanks.join('%2C')}`}
|
|
||||||
>
|
|
||||||
<Button variant="outline">
|
|
||||||
<MixIcon />
|
|
||||||
{strings.website.tools.tankopedia.meta.compare}
|
|
||||||
</Button>
|
|
||||||
</LinkI18n>
|
|
||||||
|
|
||||||
{assertSecret(import.meta.env.PUBLIC_PROMOTE_OPENTEST) === 'true' && (
|
|
||||||
<Link
|
|
||||||
href={`https://${
|
|
||||||
assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH) === 'opentest'
|
|
||||||
? ''
|
|
||||||
: 'opentest.'
|
|
||||||
}blitzkit.app/tools/tankopedia/${protagonist.id}`}
|
|
||||||
>
|
|
||||||
<Button variant="outline" color="green">
|
|
||||||
<ScienceIcon height="1.25em" width="1.25em" />
|
|
||||||
{assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH) ===
|
|
||||||
'opentest'
|
|
||||||
? strings.website.tools.tankopedia.meta.vanilla
|
|
||||||
: strings.website.tools.tankopedia.meta.opentest}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Dialog.Root open={showSwapDialog} onOpenChange={setShowSwapDialog}>
|
|
||||||
<Dialog.Trigger>
|
|
||||||
<Button>
|
|
||||||
<UpdateIcon />
|
|
||||||
{strings.website.tools.tankopedia.meta.swap.button}
|
|
||||||
</Button>
|
|
||||||
</Dialog.Trigger>
|
|
||||||
|
|
||||||
<Dialog.Content>
|
|
||||||
<Dialog.Title>
|
|
||||||
{strings.website.tools.tankopedia.meta.swap.title}
|
|
||||||
</Dialog.Title>
|
|
||||||
|
|
||||||
<TankSearch
|
|
||||||
compact
|
|
||||||
onSelect={(tank) => {
|
|
||||||
const model = modelDefinitions.models[tank.id];
|
|
||||||
|
|
||||||
setShowSwapDialog(false);
|
|
||||||
mutateTankopediaEphemeral((draft) => {
|
|
||||||
draft.model = modelDefinitions.models[tank.id];
|
|
||||||
});
|
|
||||||
mutateDuel((draft) => {
|
|
||||||
draft.protagonist = tankToDuelMember(
|
|
||||||
tank,
|
|
||||||
model,
|
|
||||||
provisionDefinitions,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.history.replaceState(
|
|
||||||
null,
|
|
||||||
'',
|
|
||||||
`/tools/tankopedia/${tank.id}`,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Dialog.Content>
|
|
||||||
</Dialog.Root>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
gap={{ initial: '0', sm: '6' }}
|
gap={{ initial: '0', sm: '6' }}
|
||||||
|
|||||||
Reference in new issue
Block a user