feat: stacking

This commit is contained in:
TrèsAbhi committed 2025-05-30 14:09:30 -05:00
1 parent 1eca71d28c
commit 9375f134db
8 files changed
+122 -45

No files matched your search

+4 -1
View File
@@ -2,5 +2,8 @@
"name": "@blitzkit/core",
"private": true,
"type": "module",
"main": "src/index.ts"
"main": "src/index.ts",
"dependencies": {
"ts-proto": "^2.2.2"
}
}
+1
View File
@@ -1254,6 +1254,7 @@
},
"gallery": {
"name": "Gallery",
"unnamed_avatar": "Unnamed avatar",
"button": "Browse all",
"description": "Blitz player avatars",
"seo_description": "All World of Tanks Blitz player avatars",
-1
View File
@@ -3,7 +3,6 @@ import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
// https://astro.build/config
export default defineConfig({
integrations: [react()],
devToolbar: { enabled: false },
+10 -2
View File
@@ -8,22 +8,30 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/partytown": "^2.1.4",
"@astrojs/react": "^4.3.0",
"@astrojs/sitemap": "^3.2.1",
"@astrolib/seo": "^1.0.0-beta.8",
"@blitzkit/closed": "workspace:*",
"@blitzkit/core": "workspace:*",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/themes": "^3.1.4",
"@react-three/drei": "^9.115.0",
"@react-three/fiber": "^8.17.10",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12",
"@types/react": "^19.1.5",
"@types/react-dom": "^19.1.5",
"@types/three": "^0.160.0",
"astro": "^5.8.0",
"immer": "^10.1.1",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21",
"minimatch": "^10.0.1",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"three": "^0.169.0",
"zustand": "^4.5.5"
}
}
@@ -1,11 +1,17 @@
import { ProfileAvatarComponent } from '@protos/blitz_static_profile_avatar_component';
import { StuffUIComponent } from '@protos/blitz_static_stuff_ui_component';
import { BlitzkitAllAvatarsComponent } from '@protos/blitzkit_static_all_avatars_component';
import { Flex, Text } from '@radix-ui/themes';
import { Badge, Box, Flex, Grid, Text } from '@radix-ui/themes';
import { CatalogItemAccessor } from 'packages/core/src/blitz/catalogItemAccessor';
import { PageWrapper } from 'packages/website-ue/src/components/PageWrapper';
import { metadata } from 'packages/website-ue/src/core/blitz/metadata';
import { Var } from 'packages/website-ue/src/core/radix/var';
import type { LocaleAcceptorProps } from 'packages/website-ue/src/hooks/useLocale';
import {
LocaleProvider,
useLocale,
type LocaleAcceptorProps,
} from 'packages/website-ue/src/hooks/useLocale';
import { useMemo } from 'react';
const allAvatars = await metadata
.get('BlitzkitAllAvatarsEntity.blitzkit_all_avatars')
@@ -17,40 +23,107 @@ const allAvatars = await metadata
export function Page({ localeData }: LocaleAcceptorProps) {
return (
<Flex wrap="wrap" gapX="4" gapY="6" justify="center" p="9">
{allAvatars.map((avatar) => {
const item = new CatalogItemAccessor(avatar);
<LocaleProvider localeData={localeData}>
<Content />
</LocaleProvider>
);
}
const dx = '4px';
const MAX_AVATARS_PREVIEW = 5;
function Content() {
const { gameStrings, strings } = useLocale();
const groups = useMemo(() => {
const avatars = allAvatars
.map((avatar) => {
const item = new CatalogItemAccessor(avatar);
const stuffComponent = item.get(StuffUIComponent, 'UIComponent');
const avatarComponent = item.get(
ProfileAvatarComponent,
'profileAvatarComponent',
);
const name =
gameStrings[stuffComponent.display_name] ??
strings.website.tools.gallery.unnamed_avatar;
return (
<Flex
direction="column"
gap="2"
key={avatar.catalog_id}
style={{
width: '8rem',
}}
>
<img
alt="apple sauce"
src={avatarComponent.avatar}
style={{
width: '100%',
aspectRatio: '1 / 1',
objectFit: 'fill',
borderRadius: Var('radius-1'),
boxShadow: Var('shadow-2'),
}}
/>
<Text align="center">{stuffComponent.display_name}</Text>
</Flex>
);
})}
</Flex>
return { avatar, name, stuffComponent, avatarComponent };
})
.sort((a, b) => a.stuffComponent.grade - b.stuffComponent.grade)
.sort((a, b) => a.name.localeCompare(b.name));
const groups: Record<string, typeof avatars> = {};
for (const avatar of avatars) {
if (avatar.name in groups) {
groups[avatar.name].push(avatar);
} else {
groups[avatar.name] = [avatar];
}
}
return Object.entries(groups);
}, []);
return (
<PageWrapper>
<Grid
columns="repeat(auto-fill, minmax(8rem, 1fr))"
flow="dense"
gapX="4"
gapY="6"
flexGrow="1"
>
{groups.map(([name, avatars]) => {
const avatarsReduced = [...avatars]
.reverse()
.slice(0, MAX_AVATARS_PREVIEW);
return (
<Flex direction="column" gap="3" key={name}>
<Box
width="100%"
style={{ aspectRatio: '1 / 1' }}
position="relative"
>
{avatarsReduced.map((avatar, index) => (
<Box
position="absolute"
top={`calc(${dx} * ${index})`}
left={`calc(${dx} * ${index})`}
width={`calc(100% - ${dx} * ${avatarsReduced.length - 1})`}
height={`calc(100% - ${dx} * ${avatarsReduced.length - 1})`}
style={{
filter: `brightness(${(index + 1) / avatarsReduced.length})`,
borderRadius: Var('radius-2'),
boxShadow: Var('shadow-3'),
backgroundImage: `url(${avatar.avatarComponent.avatar})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
))}
{avatars.length > 1 && (
<Box
position="absolute"
bottom="0"
left="50%"
style={{
transform: 'translate(-50%, 25%)',
}}
>
<Badge variant="solid" color="gray" highContrast size="3">
x{avatars.length}
</Badge>
</Box>
)}
</Box>
<Text size="2">{name}</Text>
</Flex>
);
})}
</Grid>
</PageWrapper>
);
}