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

-10
View File
@@ -54,7 +54,6 @@
"crc": "^4.3.2",
"dotenv": "^16.4.5",
"lz4js": "^0.2.0",
"ts-proto": "^2.2.2",
"yaml": "^2.5.0",
"ts-deepmerge": "^7.0.2",
"@types/decompress": "^4.2.7",
@@ -69,34 +68,25 @@
"progress": "^2.0.3",
"puppeteer": "^23.10.4",
"@astrojs/check": "^0.9.4",
"@astrojs/sitemap": "^3.2.1",
"@playform/compress": "^0.1.9",
"@playform/inline": "^0.1.2",
"@types/js-cookie": "^3.0.6",
"@types/three": "^0.160.0",
"astro-compressor": "^1.0.0",
"@astrojs/partytown": "^2.1.4",
"@fontsource/inter": "^5.2.5",
"@nanostores/persistent": "^0.10.2",
"@nanostores/react": "^0.7.3",
"@nivo/bar": "^0.87.0",
"@nivo/line": "^0.88.0",
"@react-three/drei": "^9.115.0",
"@react-three/fiber": "^8.17.10",
"compare-versions": "^6.1.1",
"echarts": "^5.5.1",
"immer": "^10.1.1",
"js-cookie": "^3.0.5",
"markdown-to-jsx": "^7.5.0",
"nanostores": "^0.11.3",
"react-device-detect": "^2.2.3",
"react-promise-suspense": "^0.3.4",
"three": "^0.169.0",
"urlon": "^3.1.0",
"zustand": "^4.5.5",
"@types/bun": "latest",
"@types/node": "^22.14.1",
"prettier": "^3.5.3",
"prisma": "^6.6.0",
"typescript": "^5.8.3",
"googleapis": "^144.0.0",
BIN
View File
Binary file not shown.
+4 -1
View File
@@ -39,5 +39,8 @@
"build:ids": "cd packages/scripts && bun build:ids",
"prunestrings": "cd packages/scripts && bun prunestrings"
},
"license": "MIT"
"license": "MIT",
"dependencies": {
"prettier": "^3.5.3"
}
}
+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>
);
}