mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: download
This commit is contained in:
1 parent
354aca5057
commit
2dc4f12246
17 files changed
+203
-66
No files matched your search
Vendored
+1
@@ -192,6 +192,7 @@
|
|||||||
"TYPELESS",
|
"TYPELESS",
|
||||||
"ubjson",
|
"ubjson",
|
||||||
"Uncompiled",
|
"Uncompiled",
|
||||||
|
"undiscriminated",
|
||||||
"Unicum",
|
"Unicum",
|
||||||
"UNLOCALIZED",
|
"UNLOCALIZED",
|
||||||
"UNORM",
|
"UNORM",
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
"satori": "^0.10.14",
|
"satori": "^0.10.14",
|
||||||
"@resvg/resvg-js": "^2.6.2",
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
"discord.js": "^14.16.3",
|
"discord.js": "^14.16.3",
|
||||||
"fuzzysort": "^2.0.4",
|
|
||||||
"bepaint": "^0.1.0-beta.3",
|
"bepaint": "^0.1.0-beta.3",
|
||||||
"markdown-escape": "^2.0.0",
|
"markdown-escape": "^2.0.0",
|
||||||
"@types/markdown-escape": "^1.1.3",
|
"@types/markdown-escape": "^1.1.3",
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ export class CatalogItemAccessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
undiscriminatedId() {
|
||||||
|
return this.id.split('.').slice(1).join('.');
|
||||||
|
}
|
||||||
|
|
||||||
static fromComponent(id: string, component: Any) {
|
static fromComponent(id: string, component: Any) {
|
||||||
return this.fromComponents(id, [component]);
|
return this.fromComponents(id, [component]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { LocalizationResourcesComponent } from '@protos/blitz_static_localization_resources_component';
|
import { LocalizationResourcesComponent } from '@protos/blitz_static_localization_resources_component';
|
||||||
|
import { ProfileAvatarComponent } from '@protos/blitz_static_profile_avatar_component';
|
||||||
import { RemoteStorageComponent } from '@protos/blitz_static_remote_storage_component';
|
import { RemoteStorageComponent } from '@protos/blitz_static_remote_storage_component';
|
||||||
|
import { SellableComponent } from '@protos/blitz_static_sellable_component';
|
||||||
|
import { StuffUIComponent } from '@protos/blitz_static_stuff_ui_component';
|
||||||
import { BlitzkitAllAvatarsComponent } from '@protos/blitzkit_static_all_avatars_component';
|
import { BlitzkitAllAvatarsComponent } from '@protos/blitzkit_static_all_avatars_component';
|
||||||
import { load } from 'js-yaml';
|
import { load } from 'js-yaml';
|
||||||
import { minimatch } from 'minimatch';
|
import { minimatch } from 'minimatch';
|
||||||
import { CatalogItemAccessor } from '../catalogItemAccessor';
|
import { CatalogItemAccessor } from 'packages/core/src/blitz/catalogItemAccessor';
|
||||||
|
|
||||||
interface LocalizationConfig {
|
interface LocalizationConfig {
|
||||||
namespaces: string[];
|
namespaces: string[];
|
||||||
@@ -102,11 +105,26 @@ export abstract class MetadataAccessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
injectBlitzkitProfileAvatars() {
|
injectBlitzkitProfileAvatars() {
|
||||||
const allAvatars: BlitzkitAllAvatarsComponent = {
|
const allAvatars: BlitzkitAllAvatarsComponent = { avatars: [] };
|
||||||
avatars: this.filter((item) =>
|
|
||||||
item.startsWith('ProfileAvatarEntity.'),
|
for (const item of this.filter((item) =>
|
||||||
).map((avatar) => avatar.pack()),
|
item.startsWith('ProfileAvatarEntity.'),
|
||||||
};
|
)) {
|
||||||
|
const stuff = item.get(StuffUIComponent, 'UIComponent');
|
||||||
|
const avatar = item.get(ProfileAvatarComponent, 'profileAvatarComponent');
|
||||||
|
const sellable = item.getOptional(SellableComponent, 'sellableComponent');
|
||||||
|
|
||||||
|
allAvatars.avatars.push({
|
||||||
|
id: item.undiscriminatedId(),
|
||||||
|
name: stuff.display_name,
|
||||||
|
obtaining: stuff.obtaining_methods,
|
||||||
|
description: stuff.description,
|
||||||
|
grade: stuff.grade,
|
||||||
|
category: avatar.category,
|
||||||
|
hidden_if_not_obtained: avatar.hidden_if_not_obtained,
|
||||||
|
sale: sellable?.reward,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.add(
|
this.add(
|
||||||
CatalogItemAccessor.fromComponent(
|
CatalogItemAccessor.fromComponent(
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export class SocketMetadataAccessor extends MetadataAccessor {
|
|||||||
|
|
||||||
async get(item: string) {
|
async get(item: string) {
|
||||||
if (item in this.items) return new CatalogItemAccessor(this.items[item]);
|
if (item in this.items) return new CatalogItemAccessor(this.items[item]);
|
||||||
|
|
||||||
throw new Error(`Item ${item} does not exist in socket metadata`);
|
throw new Error(`Item ${item} does not exist in socket metadata`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1270,14 +1270,16 @@
|
|||||||
|
|
||||||
"no_description": "This avatar has no description.",
|
"no_description": "This avatar has no description.",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
|
"enlarge": "Enlarge",
|
||||||
"hidden": "Hidden",
|
"hidden": "Hidden",
|
||||||
"unnamed_avatars": "Unnamed avatars",
|
"unnamed_avatars": "Unnamed avatars",
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Search avatars...",
|
"hint": "Search avatars...",
|
||||||
"results": "%s1 avatars"
|
"results": "%s1 avatars"
|
||||||
},
|
},
|
||||||
|
|
||||||
"categories": {
|
"categories": {
|
||||||
"0": "Unspecified category",
|
"0": "Unspecified",
|
||||||
"1": "Premium",
|
"1": "Premium",
|
||||||
"2": "Special",
|
"2": "Special",
|
||||||
"3": "Accumulative",
|
"3": "Accumulative",
|
||||||
|
|||||||
@@ -26,12 +26,14 @@
|
|||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
"@types/three": "^0.160.0",
|
"@types/three": "^0.160.0",
|
||||||
"astro": "^5.8.0",
|
"astro": "^5.8.0",
|
||||||
|
"fuzzysort": "^3.1.0",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
|
"sharp": "^0.34.2",
|
||||||
"three": "^0.169.0",
|
"three": "^0.169.0",
|
||||||
"zustand": "^4.5.5"
|
"zustand": "^4.5.5"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,6 +1,4 @@
|
|||||||
import { type ProfileAvatarComponent } from '@protos/blitz_static_profile_avatar_component';
|
import type { BlitzKitAvatar } from '@protos/blitzkit_static_all_avatars_component';
|
||||||
import type { SellableComponent } from '@protos/blitz_static_sellable_component';
|
|
||||||
import type { StuffUIComponent } from '@protos/blitz_static_stuff_ui_component';
|
|
||||||
import { Dialog } from '@radix-ui/themes';
|
import { Dialog } from '@radix-ui/themes';
|
||||||
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
||||||
import { Content } from './Content';
|
import { Content } from './Content';
|
||||||
@@ -8,11 +6,7 @@ import { Popup } from './Popup';
|
|||||||
|
|
||||||
export interface AvatarGroupProps {
|
export interface AvatarGroupProps {
|
||||||
name?: string;
|
name?: string;
|
||||||
avatars: {
|
avatars: { avatar: BlitzKitAvatar }[];
|
||||||
stuff: StuffUIComponent;
|
|
||||||
avatar: ProfileAvatarComponent;
|
|
||||||
sellable?: SellableComponent;
|
|
||||||
}[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AvatarGroup(
|
export function AvatarGroup(
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { Grade } from '@protos/blitz_static_standard_grades_enum';
|
import { Grade } from '@protos/blitz_static_standard_grades_enum';
|
||||||
import { DownloadIcon, StarFilledIcon } from '@radix-ui/react-icons';
|
import {
|
||||||
import { Badge, Box, Button, Dialog, Flex, Text } from '@radix-ui/themes';
|
CardStackIcon,
|
||||||
|
DownloadIcon,
|
||||||
|
ExternalLinkIcon,
|
||||||
|
EyeNoneIcon,
|
||||||
|
StarFilledIcon,
|
||||||
|
} from '@radix-ui/react-icons';
|
||||||
|
import { Badge, Box, Button, Dialog, Flex, Link, Text } from '@radix-ui/themes';
|
||||||
import { Var } from '../../core/radix/var';
|
import { Var } from '../../core/radix/var';
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from '../../hooks/useLocale';
|
||||||
import { GRADE_COLORS } from './Colors';
|
import { GRADE_COLORS } from './Colors';
|
||||||
@@ -13,8 +19,8 @@ export function Popup({ name, avatars }: AvatarGroupProps) {
|
|||||||
<>
|
<>
|
||||||
<Dialog.Title>{name}</Dialog.Title>
|
<Dialog.Title>{name}</Dialog.Title>
|
||||||
|
|
||||||
<Flex direction="column" gap="3" mt="5">
|
<Flex direction="column" gap="5" mt="5">
|
||||||
{avatars.map(({ avatar, stuff }, index) => (
|
{[...avatars].reverse().map(({ avatar }, index) => (
|
||||||
<Flex key={index} gap="5">
|
<Flex key={index} gap="5">
|
||||||
<Box
|
<Box
|
||||||
m="1"
|
m="1"
|
||||||
@@ -23,16 +29,27 @@ export function Popup({ name, avatars }: AvatarGroupProps) {
|
|||||||
style={{
|
style={{
|
||||||
aspectRatio: '7 / 8',
|
aspectRatio: '7 / 8',
|
||||||
borderRadius: Var('radius-4'),
|
borderRadius: Var('radius-4'),
|
||||||
outline: `${Var('space-1')} solid ${Var(`${GRADE_COLORS[stuff.grade]}-9`)}`,
|
outline: `${Var('space-1')} solid ${Var(`${GRADE_COLORS[avatar.grade]}-9`)}`,
|
||||||
boxShadow: `0 0 ${
|
boxShadow: `0 0 ${
|
||||||
1.5 * (stuff.grade / Grade.GRADE_LEGENDARY)
|
1.5 * (avatar.grade / Grade.GRADE_LEGENDARY)
|
||||||
}rem ${Var(`${GRADE_COLORS[stuff.grade]}-9`)}`,
|
}rem ${Var(`${GRADE_COLORS[avatar.grade]}-9`)}`,
|
||||||
backgroundImage: `url(${avatar.avatar})`,
|
backgroundImage: 'url(/assets/sad-ghost.png)',
|
||||||
backgroundSize: 'cover',
|
backgroundRepeat: 'no-repeat',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
|
backgroundSize: '3rem',
|
||||||
}}
|
}}
|
||||||
position="relative"
|
position="relative"
|
||||||
>
|
>
|
||||||
|
<Box
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(/api/avatars/${avatar.id}.webp)`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Badge
|
<Badge
|
||||||
mt="1"
|
mt="1"
|
||||||
style={{
|
style={{
|
||||||
@@ -42,25 +59,54 @@ export function Popup({ name, avatars }: AvatarGroupProps) {
|
|||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
}}
|
}}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color={GRADE_COLORS[stuff.grade]}
|
color={GRADE_COLORS[avatar.grade]}
|
||||||
>
|
>
|
||||||
<StarFilledIcon /> {strings.common.grades[stuff.grade]}
|
<StarFilledIcon /> {strings.common.grades[avatar.grade]}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Flex direction="column" gap="3" justify="center">
|
<Flex direction="column" gap="3" justify="center">
|
||||||
|
<Flex gap="1">
|
||||||
|
<Badge color="gray" variant="surface">
|
||||||
|
<CardStackIcon />
|
||||||
|
{strings.website.tools.avatars.categories[avatar.category]}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
{avatar.hidden_if_not_obtained && (
|
||||||
|
<Badge color="gray" variant="surface">
|
||||||
|
<EyeNoneIcon /> {strings.website.tools.avatars.hidden}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{avatar.sale && (
|
||||||
|
<Badge>
|
||||||
|
{JSON.stringify(
|
||||||
|
avatar.sale.reward_list[0].currency_reward?.amount,
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
{gameStrings[stuff.description] ??
|
{gameStrings[avatar.description] ??
|
||||||
(gameStrings[stuff.obtaining_methods]
|
(gameStrings[avatar.obtaining]
|
||||||
? undefined
|
? undefined
|
||||||
: strings.website.tools.avatars.no_description)}{' '}
|
: strings.website.tools.avatars.no_description)}{' '}
|
||||||
{gameStrings[stuff.obtaining_methods]}
|
{gameStrings[avatar.obtaining]}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex>
|
<Flex gap="2">
|
||||||
<Button variant="outline">
|
<Link href={`/api/avatars/${avatar.id}.webp`} download>
|
||||||
<DownloadIcon /> {strings.website.tools.avatars.download}
|
<Button variant="solid">
|
||||||
</Button>
|
<DownloadIcon /> {strings.website.tools.avatars.download}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href={`/api/avatars/${avatar.id}.webp`} target="_blank">
|
||||||
|
<Button variant="outline">
|
||||||
|
<ExternalLinkIcon /> {strings.website.tools.avatars.enlarge}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function Stack({ avatars }: { avatars: AvatarGroupProps['avatars'] }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{avatars.map((avatar, index) => (
|
{avatars.map(({ avatar }, index) => (
|
||||||
<Box
|
<Box
|
||||||
key={index}
|
key={index}
|
||||||
position="absolute"
|
position="absolute"
|
||||||
@@ -23,12 +23,24 @@ export function Stack({ avatars }: { avatars: AvatarGroupProps['avatars'] }) {
|
|||||||
filter: `brightness(${(index + 1) / avatars.length})`,
|
filter: `brightness(${(index + 1) / avatars.length})`,
|
||||||
borderRadius: Var('radius-4'),
|
borderRadius: Var('radius-4'),
|
||||||
boxShadow: Var('shadow-2'),
|
boxShadow: Var('shadow-2'),
|
||||||
backgroundImage: `url(${avatar.avatar.avatar})`,
|
overflow: 'hidden',
|
||||||
backgroundSize: 'cover',
|
backgroundImage: 'url(/assets/sad-ghost.png)',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
|
backgroundSize: '3rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{avatars.length > 1 && (
|
<Box
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(/api/avatars/${avatar.id}.webp)`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{avatars.length > 1 && index === avatars.length - 1 && (
|
||||||
<Box
|
<Box
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { RemoteMetadataAccessor } from 'packages/core/src';
|
import { RemoteMetadataAccessor } from 'packages/core/src';
|
||||||
|
|
||||||
export const remoteMetadata = new RemoteMetadataAccessor(
|
export const remoteMetadata = new RemoteMetadataAccessor(
|
||||||
'/api/metadata/catalog',
|
'/api/internal/catalog',
|
||||||
);
|
);
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import { ProfileAvatarComponent } from '@protos/blitz_static_profile_avatar_component';
|
|
||||||
import { SellableComponent } from '@protos/blitz_static_sellable_component';
|
|
||||||
import { StuffUIComponent } from '@protos/blitz_static_stuff_ui_component';
|
|
||||||
import { BlitzkitAllAvatarsComponent } from '@protos/blitzkit_static_all_avatars_component';
|
import { BlitzkitAllAvatarsComponent } from '@protos/blitzkit_static_all_avatars_component';
|
||||||
import { Grid } from '@radix-ui/themes';
|
import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
|
||||||
import { times } from 'lodash-es';
|
import { Flex, Grid, Spinner, TextField } from '@radix-ui/themes';
|
||||||
import { CatalogItemAccessor } from 'packages/core/src/blitz/catalogItemAccessor';
|
import fuzzysort from 'fuzzysort';
|
||||||
|
import { debounce, times } from 'lodash-es';
|
||||||
import { AvatarGroup } from 'packages/website-ue/src/components/Avatars/Group';
|
import { AvatarGroup } from 'packages/website-ue/src/components/Avatars/Group';
|
||||||
import { PageWrapper } from 'packages/website-ue/src/components/PageWrapper';
|
import { PageWrapper } from 'packages/website-ue/src/components/PageWrapper';
|
||||||
import { metadata } from 'packages/website-ue/src/core/blitz/metadata';
|
import { metadata } from 'packages/website-ue/src/core/blitz/metadata';
|
||||||
@@ -14,7 +12,7 @@ import {
|
|||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from 'packages/website-ue/src/hooks/useLocale';
|
} from 'packages/website-ue/src/hooks/useLocale';
|
||||||
import type { MaybeSkeletonComponentProps } from 'packages/website-ue/src/types/maybeSkeletonComponentProps';
|
import type { MaybeSkeletonComponentProps } from 'packages/website-ue/src/types/maybeSkeletonComponentProps';
|
||||||
import { useMemo, useState } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
const allAvatars = await metadata
|
const allAvatars = await metadata
|
||||||
.get('BlitzkitAllAvatarsEntity.blitzkit_all_avatars')
|
.get('BlitzkitAllAvatarsEntity.blitzkit_all_avatars')
|
||||||
@@ -36,25 +34,17 @@ const DEFAULT_LOADED = 42;
|
|||||||
const PREVIEW_COUNT = 28;
|
const PREVIEW_COUNT = 28;
|
||||||
|
|
||||||
function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
const { gameStrings } = useLocale();
|
const { strings, gameStrings } = useLocale();
|
||||||
|
const input = useRef<HTMLInputElement>(null);
|
||||||
|
const [search, setSearch] = useState<string | null>(null);
|
||||||
|
const [searching, setSearching] = useState(false);
|
||||||
const groups = useMemo(() => {
|
const groups = useMemo(() => {
|
||||||
const avatars = allAvatars
|
const avatars = allAvatars
|
||||||
.map((id) => {
|
.map((avatar) => ({
|
||||||
const item = new CatalogItemAccessor(id);
|
avatar,
|
||||||
const stuff = item.get(StuffUIComponent, 'UIComponent');
|
name: gameStrings[avatar.name] as string | undefined,
|
||||||
const avatar = item.get(
|
}))
|
||||||
ProfileAvatarComponent,
|
.sort((a, b) => a.avatar.grade - b.avatar.grade)
|
||||||
'profileAvatarComponent',
|
|
||||||
);
|
|
||||||
const sellable = item.getOptional(
|
|
||||||
SellableComponent,
|
|
||||||
'sellableComponent',
|
|
||||||
);
|
|
||||||
const name = gameStrings[stuff.display_name] as string | undefined;
|
|
||||||
|
|
||||||
return { avatar, name, stuff, sellable };
|
|
||||||
})
|
|
||||||
.sort((a, b) => a.stuff.grade - b.stuff.grade)
|
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
a.name === undefined
|
a.name === undefined
|
||||||
? 1
|
? 1
|
||||||
@@ -75,10 +65,50 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
return [...groups].map(([name, avatars]) => ({ name, avatars }));
|
return [...groups].map(([name, avatars]) => ({ name, avatars }));
|
||||||
}, []);
|
}, []);
|
||||||
const [loadedAvatars, setLoadedAvatars] = useState(DEFAULT_LOADED);
|
const [loadedAvatars, setLoadedAvatars] = useState(DEFAULT_LOADED);
|
||||||
const filtered = useMemo(() => groups, []);
|
const filtered = useMemo(() => {
|
||||||
|
setSearching(false);
|
||||||
|
|
||||||
|
if (search === null || search.trim().length === 0) return groups;
|
||||||
|
|
||||||
|
return fuzzysort
|
||||||
|
.go(search, groups, { key: 'name' })
|
||||||
|
.map((result) => result.obj);
|
||||||
|
}, [search]);
|
||||||
|
const requestSearch = useCallback(
|
||||||
|
debounce(() => {
|
||||||
|
if (!input.current) return;
|
||||||
|
setSearch(input.current.value);
|
||||||
|
}, 500),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper color="amber">
|
<PageWrapper color="amber">
|
||||||
|
<Flex justify="center">
|
||||||
|
<TextField.Root
|
||||||
|
ref={input}
|
||||||
|
my="7"
|
||||||
|
placeholder={strings.website.tools.avatars.search.hint}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
maxWidth: '30rem',
|
||||||
|
}}
|
||||||
|
onChange={(event) => {
|
||||||
|
if (event.target.value.trim().length === 0) {
|
||||||
|
setSearching(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestSearch();
|
||||||
|
setSearching(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField.Slot>
|
||||||
|
{searching ? <Spinner /> : <MagnifyingGlassIcon />}
|
||||||
|
</TextField.Slot>
|
||||||
|
</TextField.Root>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
columns={{
|
columns={{
|
||||||
initial: 'repeat(auto-fill, minmax(6rem, 1fr))',
|
initial: 'repeat(auto-fill, minmax(6rem, 1fr))',
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { ProfileAvatarComponent } from '@protos/blitz_static_profile_avatar_component';
|
||||||
|
import type { APIRoute, GetStaticPaths, GetStaticPathsItem } from 'astro';
|
||||||
|
import { socketMetadata } from 'packages/website-ue/src/core/blitz/metadata.socket';
|
||||||
|
|
||||||
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
|
const paths: GetStaticPathsItem[] = [];
|
||||||
|
|
||||||
|
for (const item in socketMetadata.items) {
|
||||||
|
if (!item.startsWith('ProfileAvatarEntity.')) continue;
|
||||||
|
|
||||||
|
const id = await socketMetadata
|
||||||
|
.get(item)
|
||||||
|
.then((item) => item.undiscriminatedId());
|
||||||
|
|
||||||
|
paths.push({ params: { id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GET: APIRoute<{}, { id: string }> = async ({ params }) => {
|
||||||
|
const item = await socketMetadata.get(`ProfileAvatarEntity.${params.id}`);
|
||||||
|
const avatar = item.get(ProfileAvatarComponent, 'profileAvatarComponent');
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fetch(avatar.avatar);
|
||||||
|
} catch {
|
||||||
|
return new Response(null, { status: 404 });
|
||||||
|
}
|
||||||
|
};
|
||||||
File renamed without changes.
Submodule submodules/blitzkit-closed updated: c390ed62f3...7f4ec5f5bc.
Reference in new issue
Block a user