From bc2b9f1267012a5ebb3e16602d843fffcd04ad03 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:01:51 -0500 Subject: [PATCH] glossary --- packages/core/src/blitz/fetchGlossary.ts | 4 +- packages/core/src/blitzkit/api/base.ts | 2 + packages/core/src/blitzkit/api/client.ts | 5 ++ .../api/server/10_galleryDefinitions.ts | 67 ++++++++++++++++ .../core/src/blitzkit/api/server/index.ts | 4 +- .../website/src/components/Gallery/Card.tsx | 6 +- .../website/src/components/Gallery/List.tsx | 8 +- .../website/src/core/awaitables/gallery.ts | 3 - .../[...chunk_api]/definitions/gallery.pb.ts | 11 +++ .../[...chunk_api]/gallery/avatars/[name].ts | 79 +++++++++++++++++++ 10 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts delete mode 100644 packages/website/src/core/awaitables/gallery.ts create mode 100644 packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts create mode 100644 packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts diff --git a/packages/core/src/blitz/fetchGlossary.ts b/packages/core/src/blitz/fetchGlossary.ts index a6a92a333..00309ea98 100644 --- a/packages/core/src/blitz/fetchGlossary.ts +++ b/packages/core/src/blitz/fetchGlossary.ts @@ -4,8 +4,8 @@ import type { BlitzGlossary } from "../types"; export async function fetchGlossary(locale: string) { const response = await fetch( `${assertSecret( - import.meta.env.WOTB_GLOSSARY_DOMAIN - )}/${locale}${assertSecret(import.meta.env.WOTB_GLOSSARY_PATH)}` + import.meta.env.WOTB_GLOSSARY_DOMAIN, + )}/${locale}${assertSecret(import.meta.env.WOTB_GLOSSARY_PATH)}`, ); return (await response.json()) as BlitzGlossary; } diff --git a/packages/core/src/blitzkit/api/base.ts b/packages/core/src/blitzkit/api/base.ts index 49173042d..92038adc6 100644 --- a/packages/core/src/blitzkit/api/base.ts +++ b/packages/core/src/blitzkit/api/base.ts @@ -2,6 +2,7 @@ import { CamouflageDefinitions, ConsumableDefinitions, EquipmentDefinitions, + Gallery, GameDefinitions, MapDefinitions, ModelDefinitions, @@ -20,4 +21,5 @@ export abstract class BlitzKitAPI { abstract camouflageDefinitions(): Promise; abstract tankDefinitions(): Promise; abstract gameDefinitions(): Promise; + abstract galleryDefinitions(): Promise; } diff --git a/packages/core/src/blitzkit/api/client.ts b/packages/core/src/blitzkit/api/client.ts index 38b57c408..2a28275a4 100644 --- a/packages/core/src/blitzkit/api/client.ts +++ b/packages/core/src/blitzkit/api/client.ts @@ -3,6 +3,7 @@ import { CamouflageDefinitions, ConsumableDefinitions, EquipmentDefinitions, + Gallery, GameDefinitions, ModelDefinitions, ProvisionDefinitions, @@ -43,4 +44,8 @@ export class ClientBlitzKitAPI extends BlitzKitAPI { equipmentDefinitions() { return fetchPB("/api/definitions/equipment.pb", EquipmentDefinitions); } + + galleryDefinitions() { + return fetchPB("/api/definitions/gallery.pb", Gallery); + } } diff --git a/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts b/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts new file mode 100644 index 000000000..fede7e381 --- /dev/null +++ b/packages/core/src/blitzkit/api/server/10_galleryDefinitions.ts @@ -0,0 +1,67 @@ +import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core"; +import { ServerBlitzKitAPI9 } from "@blitzkit/core/src/blitzkit/api/server/9_gameDefinitions"; +import locales from "@blitzkit/i18n/locales.json"; +import { extname } from "path"; +import { Cache } from "./0_base"; + +export abstract class ServerBlitzKitAPI10 extends ServerBlitzKitAPI9 { + @Cache() + async galleryDefinitions() { + const avatars: Record = {}; + + await Promise.all( + locales.supported.map(async (supported) => { + const glossary = await fetchGlossary( + supported.blitz ?? supported.locale, + ); + + console.log( + `Found ${Object.keys(glossary).length} things for ${supported.locale}`, + ); + + for (const key in glossary) { + const glossaryEntry = glossary[key]; + + if ( + !key.startsWith("avatar") || + key.endsWith("_part") || + glossaryEntry.image_url === null + ) { + continue; + } + + if (key in avatars) { + avatars[key].avatar.name!.locales[supported.locale] = + glossaryEntry.title; + } else { + const extension = extname(glossaryEntry.image_url); + + avatars[key] = { + url: glossaryEntry.image_url, + avatar: { + id: key, + name: { locales: { [supported.locale]: glossaryEntry.title } }, + extension, + }, + }; + } + } + }), + ); + + const gallery: Gallery = { avatars: [] }; + + await Promise.all( + Object.entries(avatars).map(async ([key, { url, avatar }]) => { + if (!(locales.default in avatars[key].avatar.name!.locales)) { + console.warn(`Avatar ${key} has no ${locales.default} name`); + return; + } + + gallery.avatars.push(avatar); + }), + ); + + return gallery; + } +} diff --git a/packages/core/src/blitzkit/api/server/index.ts b/packages/core/src/blitzkit/api/server/index.ts index 33f1f2273..3b9c459a9 100644 --- a/packages/core/src/blitzkit/api/server/index.ts +++ b/packages/core/src/blitzkit/api/server/index.ts @@ -1,3 +1,3 @@ -import { ServerBlitzKitAPI9 } from "./9_gameDefinitions"; +import { ServerBlitzKitAPI10 } from "@blitzkit/core/src/blitzkit/api/server/10_galleryDefinitions"; -export class ServerBlitzKitAPI extends ServerBlitzKitAPI9 {} +export class ServerBlitzKitAPI extends ServerBlitzKitAPI10 {} diff --git a/packages/website/src/components/Gallery/Card.tsx b/packages/website/src/components/Gallery/Card.tsx index 8345b5ded..aa524492d 100644 --- a/packages/website/src/components/Gallery/Card.tsx +++ b/packages/website/src/components/Gallery/Card.tsx @@ -1,4 +1,4 @@ -import { asset, Avatar } from "@blitzkit/core"; +import { Avatar } from "@blitzkit/core"; import { Cross1Icon, DownloadIcon } from "@radix-ui/react-icons"; import { Dialog, @@ -18,9 +18,7 @@ type GalleryCardProps = MaybeSkeletonComponentProps<{ }>; export function GalleryCard(props: GalleryCardProps) { - const src = asset( - `gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`, - ); + const src = `/api/gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`; const card = useRef(null!); const { unwrap } = useLocale(); diff --git a/packages/website/src/components/Gallery/List.tsx b/packages/website/src/components/Gallery/List.tsx index aa1ac26cf..7d92a9752 100644 --- a/packages/website/src/components/Gallery/List.tsx +++ b/packages/website/src/components/Gallery/List.tsx @@ -4,7 +4,7 @@ import { Flex, Text } from "@radix-ui/themes"; import fuzzysort from "fuzzysort"; import { times } from "lodash-es"; import { useMemo, useState } from "react"; -import { awaitableGallery } from "../../core/awaitables/gallery"; +import { api } from "../../core/blitzkit/api"; import { useLocale } from "../../hooks/useLocale"; import { Gallery } from "../../stores/gallery"; import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; @@ -16,7 +16,7 @@ export interface Avatar { id: string; } -const gallery = await awaitableGallery; +const gallery = await api.galleryDefinitions(); const DEFAULT_LOADED = 42; const PREVIEW_COUNT = 28; @@ -35,7 +35,7 @@ export function GalleryList({ skeleton }: MaybeSkeletonComponentProps) { .go(search, gallery.avatars, { keys: [ ...locales.supported.map( - (supported) => `name.locales.${supported.locale}` + (supported) => `name.locales.${supported.locale}`, ), "id", ], @@ -72,7 +72,7 @@ export function GalleryList({ skeleton }: MaybeSkeletonComponentProps) { skeleton onIntersection={() => setLoadedCards((state) => state + 2)} /> - ) + ), )} diff --git a/packages/website/src/core/awaitables/gallery.ts b/packages/website/src/core/awaitables/gallery.ts deleted file mode 100644 index 3a446670e..000000000 --- a/packages/website/src/core/awaitables/gallery.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchGallery } from "@blitzkit/core"; - -export const awaitableGallery = fetchGallery(); diff --git a/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts b/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts new file mode 100644 index 000000000..49453741e --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/definitions/gallery.pb.ts @@ -0,0 +1,11 @@ +import { Gallery } from "@blitzkit/core"; +import { api } from "../../../core/blitzkit/api"; + +export { getStaticPaths } from "../_index"; + +export async function GET() { + const definitions = await api.galleryDefinitions(); + const bytes = Gallery.encode(definitions).finish(); + + return new Response(bytes); +} diff --git a/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts b/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts new file mode 100644 index 000000000..032e1908d --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/gallery/avatars/[name].ts @@ -0,0 +1,79 @@ +import { fetchGlossary, type Avatar } from "@blitzkit/core"; +import locales from "@blitzkit/i18n/locales.json"; +import type { APIContext, GetStaticPathsItem } from "astro"; +import { extname } from "path"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const paths: GetStaticPathsItem[] = []; + + const avatars: Record = {}; + + await Promise.all( + locales.supported.map(async (supported) => { + const glossary = await fetchGlossary(supported.blitz ?? supported.locale); + + console.log( + `Found ${Object.keys(glossary).length} things for ${supported.locale}`, + ); + + for (const key in glossary) { + const glossaryEntry = glossary[key]; + + if ( + !key.startsWith("avatar") || + key.endsWith("_part") || + glossaryEntry.image_url === null + ) { + continue; + } + + if (key in avatars) { + avatars[key].avatar.name!.locales[supported.locale] = + glossaryEntry.title; + } else { + const extension = extname(glossaryEntry.image_url); + + avatars[key] = { + url: glossaryEntry.image_url, + avatar: { + id: key, + name: { locales: { [supported.locale]: glossaryEntry.title } }, + extension, + }, + }; + } + } + }), + ); + + await Promise.all( + Object.entries(avatars).map(async ([key, { url, avatar }]) => { + if (!(locales.default in avatars[key].avatar.name!.locales)) { + console.warn(`Avatar ${key} has no ${locales.default} name`); + return; + } + + const name = `${key}${avatar.extension}`; + + paths.push({ + params: { name }, + props: { url }, + }); + }), + ); + + return paths; +}); + +export async function GET({ props }: APIContext<{ url: string }>) { + if (import.meta.env.DEV) { + return Response.redirect(props.url); + } + + const response = await fetch(props.url); + const buffer = await response.arrayBuffer(); + + return new Response(buffer); +}