feat: skeleton tankopedia page

This commit is contained in:
tresabhi committed 2024-11-09 18:38:04 -06:00
1 parent 0722ff6bfb
commit cb45faf83c
6 files changed
+40 -20

No files matched your search

@@ -6,6 +6,7 @@ import { NAVBAR_HEIGHT } from '../../../constants/navbar';
import { Var } from '../../../core/radix/var'; import { Var } from '../../../core/radix/var';
import { useFullScreen } from '../../../hooks/useFullScreen'; import { useFullScreen } from '../../../hooks/useFullScreen';
import { Duel } from '../../../stores/duel'; import { Duel } from '../../../stores/duel';
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 { Options } from './components/Options'; import { Options } from './components/Options';
@@ -14,7 +15,7 @@ import { TankSandboxLoader } from './components/TankSandboxLoader';
const tankDefinitions = await fetchTankDefinitions(); const tankDefinitions = await fetchTankDefinitions();
export function HeroSection() { export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
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);
@@ -124,6 +125,8 @@ export function HeroSection() {
> >
<Box position="absolute" width="100%" height="100%"> <Box position="absolute" width="100%" height="100%">
<Box width="100%" height="100%"> <Box width="100%" height="100%">
{skeleton && <TankSandboxLoader id={protagonist.id} />}
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}> <Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
<TankSandbox ref={canvas} thicknessRange={thicknessRange} /> <TankSandbox ref={canvas} thicknessRange={thicknessRange} />
</Suspense> </Suspense>
@@ -5,19 +5,20 @@ import {
formatCompact, formatCompact,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from '@blitzkit/core'; } from '@blitzkit/core';
import { Box, Flex, Link, Text } from '@radix-ui/themes'; import { Box, Flex, Link, Skeleton, Text } from '@radix-ui/themes';
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../../types/maybeSkeletonComponentProps';
interface NodeProps { type NodeProps = MaybeSkeletonComponentProps & {
id: number; id: number;
nextIds?: number[]; nextIds?: number[];
highlight?: boolean; highlight?: boolean;
} };
const tankDefinitions = await fetchTankDefinitions(); const tankDefinitions = await fetchTankDefinitions();
const averageDefinitions = await fetchAverageDefinitions(); const averageDefinitions = await fetchAverageDefinitions();
export function Node({ id, highlight, nextIds }: NodeProps) { export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier); const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
const tank = tankDefinitions.tanks[id]; const tank = tankDefinitions.tanks[id];
const nextTanks = nextIds?.map((id) => tankDefinitions.tanks[id]); const nextTanks = nextIds?.map((id) => tankDefinitions.tanks[id]);
@@ -125,7 +126,12 @@ export function Node({ id, highlight, nextIds }: NodeProps) {
{averages && nextTanks && ( {averages && nextTanks && (
<Text color="gray" size="1" mt="2"> <Text color="gray" size="1" mt="2">
{skeleton && <Skeleton height="1em" width="4em" />}
{!skeleton && (
<>
{games!} {games === 1 ? 'battle' : 'battles'} {games!} {games === 1 ? 'battle' : 'battles'}
</>
)}
</Text> </Text>
)} )}
</Flex> </Flex>
@@ -10,6 +10,7 @@ import { Flex, Heading, IconButton, ScrollArea, Text } from '@radix-ui/themes';
import { Fragment, useEffect, useMemo, useRef, useState } from 'react'; import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
import { Duel } from '../../../stores/duel'; import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
import { Arrow } from './components/Arrow'; import { Arrow } from './components/Arrow';
import { Node } from './components/Node'; import { Node } from './components/Node';
@@ -19,7 +20,7 @@ const tankDefinitions = await fetchTankDefinitions();
export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10]; export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10];
export function TechTreeSection() { export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
const master = Duel.use((state) => state.protagonist.tank); const master = Duel.use((state) => state.protagonist.tank);
if (master.type !== TankType.RESEARCHABLE || master.deprecated) return null; if (master.type !== TankType.RESEARCHABLE || master.deprecated) return null;
@@ -184,6 +185,7 @@ export function TechTreeSection() {
id={id} id={id}
nextIds={last ? tank.successors : [line[index + 1]]} nextIds={last ? tank.successors : [line[index + 1]]}
highlight={last} highlight={last}
skeleton={skeleton}
/> />
</Fragment> </Fragment>
); );
@@ -199,7 +201,7 @@ export function TechTreeSection() {
<PlusIcon /> <PlusIcon />
</Text> </Text>
)} )}
<Node key={`${id}-node}`} id={id} /> <Node key={`${id}-node}`} id={id} skeleton={skeleton} />
</Fragment> </Fragment>
))} ))}
</> </>
@@ -1,13 +1,14 @@
import { fetchReviews, youtubers } from '@blitzkit/core'; import { fetchReviews, youtubers } from '@blitzkit/core';
import { Box, Flex, Heading, Link, Text } from '@radix-ui/themes'; import { Box, Flex, Heading, Link, Skeleton, Text } from '@radix-ui/themes';
import { Var } from '../../core/radix/var'; import { Var } from '../../core/radix/var';
import { Duel } from '../../stores/duel'; import { Duel } from '../../stores/duel';
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
import { PageWrapper } from '../PageWrapper'; import { PageWrapper } from '../PageWrapper';
import { VerifiedIcon } from '../VerifiedIcon'; import { VerifiedIcon } from '../VerifiedIcon';
const reviews = await fetchReviews(); const reviews = await fetchReviews();
export function VideoSection() { export function VideoSection({ skeleton }: MaybeSkeletonComponentProps) {
const tank = Duel.use((state) => state.protagonist.tank); const tank = Duel.use((state) => state.protagonist.tank);
const videos = reviews.reviews[tank.id]?.videos; const videos = reviews.reviews[tank.id]?.videos;
@@ -45,14 +46,16 @@ export function VideoSection() {
style={{ style={{
borderRadius: 'var(--radius-3)', borderRadius: 'var(--radius-3)',
overflow: 'hidden', overflow: 'hidden',
background: `url(${image})`, background: skeleton
? Var('color-panel-solid')
: `url(${image})`,
backgroundPosition: '0 18px', backgroundPosition: '0 18px',
backgroundSize: 'cover', backgroundSize: 'cover',
}} }}
> >
<Box <Box
style={{ style={{
background: `url(${image})`, background: skeleton ? 'transparent' : `url(${image})`,
backgroundSize: 'cover', backgroundSize: 'cover',
backgroundPosition: 'center', backgroundPosition: 'center',
height: 128, height: 128,
@@ -70,8 +73,11 @@ export function VideoSection() {
align="center" align="center"
gap="1" gap="1"
> >
<Text>{youtuber.name}</Text> <Text>
<VerifiedIcon width="1em" height="1em" /> {!skeleton && youtuber.name}
{skeleton && <Skeleton height="1em" width="7rem" />}
</Text>
{!skeleton && <VerifiedIcon width="1em" height="1em" />}
</Flex> </Flex>
</Flex> </Flex>
</Link> </Link>
@@ -17,16 +17,17 @@ import { App } from '../../../../stores/app';
import { Duel } from '../../../../stores/duel'; import { Duel } from '../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent'; import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent';
import type { MaybeSkeletonComponentProps } from '../../../../types/maybeSkeletonComponentProps';
interface PageProps { type PageProps = MaybeSkeletonComponentProps & {
id: number; id: number;
} };
const tankDefinitions = await fetchTankDefinitions(); const tankDefinitions = await fetchTankDefinitions();
const provisionDefinitions = await fetchProvisionDefinitions(); const provisionDefinitions = await fetchProvisionDefinitions();
const modelDefinitions = await fetchModelDefinitions(); const modelDefinitions = await fetchModelDefinitions();
export function Page({ id }: PageProps) { export function Page({ id, skeleton }: PageProps) {
const tank = tankDefinitions.tanks[id]; const tank = tankDefinitions.tanks[id];
const model = modelDefinitions.models[id]; const model = modelDefinitions.models[id];
@@ -45,14 +46,14 @@ export function Page({ id }: PageProps) {
gap="9" gap="9"
pb="9" pb="9"
> >
<HeroSection /> <HeroSection skeleton={skeleton} />
<ShotDisplaySection /> <ShotDisplaySection />
<CalloutsSection /> <CalloutsSection />
<MetaSection /> <MetaSection />
<TechTreeSection /> <TechTreeSection skeleton={skeleton} />
<CharacteristicsSection /> <CharacteristicsSection />
<GameModeSection /> <GameModeSection />
<VideoSection /> <VideoSection skeleton={skeleton} />
<HistorySection /> <HistorySection />
</PageWrapper> </PageWrapper>
</Duel.Provider> </Duel.Provider>
@@ -30,5 +30,7 @@ const tank = tankDefinitions.tanks[id];
</title> </title>
<BaseLayout> <BaseLayout>
<Page id={id} client:load /> <Page id={id} client:only="react">
<Page id={id} skeleton slot="fallback" />
</Page>
</BaseLayout> </BaseLayout>