mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-22 11:17:05 +02:00
feat: api
This commit is contained in:
1 parent
2dc4f12246
commit
7e11c1e5ce
26 files changed
+149
-866
No files matched your search
@@ -6,6 +6,7 @@
|
||||
"dependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ts-proto": "^2.2.2"
|
||||
"ts-proto": "^2.2.2",
|
||||
"@blitzkit/closed": "workspace:*"
|
||||
}
|
||||
}
|
||||
+29
-48
@@ -1,9 +1,7 @@
|
||||
import { CatalogItem } from '@protos/blitz_items';
|
||||
import { Metadata } from '@protos/blitz_metadata';
|
||||
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 { 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 { load } from 'js-yaml';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { CatalogItemAccessor } from 'packages/core/src/blitz/catalogItemAccessor';
|
||||
@@ -12,12 +10,35 @@ interface LocalizationConfig {
|
||||
namespaces: string[];
|
||||
}
|
||||
|
||||
export abstract class MetadataAccessor {
|
||||
abstract get(item: string): Promise<CatalogItemAccessor>;
|
||||
export class MetadataAccessor {
|
||||
items: Record<string, CatalogItem> = {};
|
||||
|
||||
abstract add(item: CatalogItemAccessor): void;
|
||||
constructor(metadata: Metadata) {
|
||||
for (const item of metadata.catalog_items) {
|
||||
this.items[item.catalog_id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
abstract filter(predicate: (item: string) => boolean): CatalogItemAccessor[];
|
||||
get(item: string) {
|
||||
if (item in this.items) return new CatalogItemAccessor(this.items[item]);
|
||||
throw new Error(`Item ${item} does not exist in socket metadata`);
|
||||
}
|
||||
|
||||
add(item: CatalogItemAccessor) {
|
||||
this.items[item.id] = item.pack();
|
||||
}
|
||||
|
||||
filter(predicate: (item: string) => boolean) {
|
||||
const items: CatalogItemAccessor[] = [];
|
||||
|
||||
for (const item in this.items) {
|
||||
if (predicate(item)) {
|
||||
items.push(new CatalogItemAccessor(this.items[item]));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
async strings(locale: string) {
|
||||
const clientConfig = await this.get(
|
||||
@@ -97,44 +118,4 @@ export abstract class MetadataAccessor {
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
injectBlitzkit() {
|
||||
this.injectBlitzkitProfileAvatars();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
injectBlitzkitProfileAvatars() {
|
||||
const allAvatars: BlitzkitAllAvatarsComponent = { avatars: [] };
|
||||
|
||||
for (const item of this.filter((item) =>
|
||||
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(
|
||||
CatalogItemAccessor.fromComponent(
|
||||
'BlitzkitAllAvatarsEntity.blitzkit_all_avatars',
|
||||
{
|
||||
type_url:
|
||||
'./blitzkit_static_all_avatars_component.BlitzkitAllAvatarsComponent',
|
||||
value: BlitzkitAllAvatarsComponent.encode(allAvatars).finish(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './abstract';
|
||||
export * from './remote';
|
||||
export * from './socket';
|
||||
@@ -1,30 +0,0 @@
|
||||
import { CatalogItem } from '@protos/blitz_items';
|
||||
import { CatalogItemAccessor } from '../catalogItemAccessor';
|
||||
import { MetadataAccessor } from './abstract';
|
||||
|
||||
export class RemoteMetadataAccessor extends MetadataAccessor {
|
||||
constructor(private base: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
async get(item: string) {
|
||||
const response = await fetch(`${this.base}/${item}.pb`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Item ${item} does not exist in remote metadata`);
|
||||
}
|
||||
|
||||
const buffer = new Uint8Array(await response.arrayBuffer());
|
||||
const catalogItem = CatalogItem.decode(buffer);
|
||||
|
||||
return new CatalogItemAccessor(catalogItem);
|
||||
}
|
||||
|
||||
add() {
|
||||
throw new Error('Cannot add items to remote metadata');
|
||||
}
|
||||
|
||||
filter(): CatalogItemAccessor[] {
|
||||
throw new Error('Cannot filter remote metadata');
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { CatalogItem } from '@protos/blitz_items';
|
||||
import { Metadata } from '@protos/blitz_metadata';
|
||||
import { CatalogItemAccessor } from '../catalogItemAccessor';
|
||||
import { MetadataAccessor } from './abstract';
|
||||
|
||||
export class SocketMetadataAccessor extends MetadataAccessor {
|
||||
items: Record<string, CatalogItem> = {};
|
||||
|
||||
constructor(metadata: Metadata) {
|
||||
super();
|
||||
|
||||
for (const item of metadata.catalog_items) {
|
||||
this.items[item.catalog_id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
async get(item: string) {
|
||||
if (item in this.items) return new CatalogItemAccessor(this.items[item]);
|
||||
throw new Error(`Item ${item} does not exist in socket metadata`);
|
||||
}
|
||||
|
||||
add(item: CatalogItemAccessor) {
|
||||
this.items[item.id] = item.pack();
|
||||
}
|
||||
|
||||
filter(predicate: (item: string) => boolean) {
|
||||
const items: CatalogItemAccessor[] = [];
|
||||
|
||||
for (const item in this.items) {
|
||||
if (predicate(item)) {
|
||||
items.push(new CatalogItemAccessor(this.items[item]));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
|
||||
export abstract class BlitzkitAPI {
|
||||
abstract metadata: MetadataAccessor;
|
||||
|
||||
abstract avatars(): Promise<BlitzKitAvatars>;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
import { BlitzkitAPI } from './abstract';
|
||||
|
||||
export class BlitzkitClientAPI extends BlitzkitAPI {
|
||||
get metadata(): MetadataAccessor {
|
||||
throw new Error('Cannot access metadata from client');
|
||||
}
|
||||
|
||||
async avatars() {
|
||||
const response = await fetch('/api/internal/avatars.pb');
|
||||
const buffer = await response.arrayBuffer();
|
||||
return BlitzKitAvatars.decode(new Uint8Array(buffer));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './abstract';
|
||||
export * from './client';
|
||||
export * from './server';
|
||||
@@ -0,0 +1,37 @@
|
||||
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 { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { BlitzkitAPI } from '.';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
|
||||
export class BlitzkitServerAPI extends BlitzkitAPI {
|
||||
constructor(public metadata: MetadataAccessor) {
|
||||
super();
|
||||
}
|
||||
|
||||
async avatars() {
|
||||
const avatars: BlitzKitAvatars = { avatars: [] };
|
||||
|
||||
for (const item of this.metadata.filter((item) =>
|
||||
item.startsWith('ProfileAvatarEntity.'),
|
||||
)) {
|
||||
const stuff = item.get(StuffUIComponent, 'UIComponent');
|
||||
const avatar = item.get(ProfileAvatarComponent, 'profileAvatarComponent');
|
||||
const sellable = item.getOptional(SellableComponent, 'sellableComponent');
|
||||
|
||||
avatars.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,
|
||||
});
|
||||
}
|
||||
|
||||
return avatars;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ export * from './uniqueGuns';
|
||||
export * from './unwrapBlitzkitResponse';
|
||||
export * from './votes';
|
||||
// export * from './withCORSProxy';
|
||||
export * from './api';
|
||||
export * from './createDefaultProvisions';
|
||||
export * from './gallery';
|
||||
export * from './metaSortTank';
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { RemoteMetadataAccessor } from 'packages/core/src';
|
||||
|
||||
export const remoteMetadata = new RemoteMetadataAccessor(
|
||||
'/api/internal/catalog',
|
||||
);
|
||||
@@ -1,6 +0,0 @@
|
||||
import { fetchMetadata } from '@blitzkit/closed';
|
||||
import { SocketMetadataAccessor } from 'packages/core/src';
|
||||
|
||||
export const socketMetadata = new SocketMetadataAccessor(
|
||||
await fetchMetadata(),
|
||||
).injectBlitzkit();
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { MetadataAccessor } from 'packages/core/src';
|
||||
|
||||
let accessor: MetadataAccessor;
|
||||
|
||||
if (import.meta.env.SSR) {
|
||||
accessor = await import('./metadata.socket').then((m) => m.socketMetadata);
|
||||
} else {
|
||||
accessor = await import('./metadata.remote').then((m) => m.remoteMetadata);
|
||||
}
|
||||
|
||||
export const metadata = accessor;
|
||||
@@ -1,11 +0,0 @@
|
||||
let base: string;
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
base = 'http://localhost:4321';
|
||||
} else {
|
||||
base = import.meta.env.SITE;
|
||||
}
|
||||
|
||||
export function api(path: string) {
|
||||
return `${base}/api/${path}`;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { BlitzkitClientAPI } from '@blitzkit/core';
|
||||
|
||||
export const api = new BlitzkitClientAPI();
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { BlitzkitAPI } from 'packages/core/src';
|
||||
|
||||
let api: BlitzkitAPI;
|
||||
|
||||
if (import.meta.env.SSR) {
|
||||
api = await import('./server').then((module) => module.api);
|
||||
} else {
|
||||
api = await import('./client').then((module) => module.api);
|
||||
}
|
||||
|
||||
export { api };
|
||||
@@ -0,0 +1,6 @@
|
||||
import { fetchMetadata } from '@blitzkit/closed';
|
||||
import { BlitzkitServerAPI, MetadataAccessor } from '@blitzkit/core';
|
||||
|
||||
const metadata = await fetchMetadata();
|
||||
const accessor = new MetadataAccessor(metadata);
|
||||
export const api = new BlitzkitServerAPI(accessor);
|
||||
@@ -1,11 +1,10 @@
|
||||
import { BlitzkitAllAvatarsComponent } from '@protos/blitzkit_static_all_avatars_component';
|
||||
import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Grid, Spinner, TextField } from '@radix-ui/themes';
|
||||
import fuzzysort from 'fuzzysort';
|
||||
import { debounce, times } from 'lodash-es';
|
||||
import { AvatarGroup } from 'packages/website-ue/src/components/Avatars/Group';
|
||||
import { PageWrapper } from 'packages/website-ue/src/components/PageWrapper';
|
||||
import { metadata } from 'packages/website-ue/src/core/blitz/metadata';
|
||||
import { api } from 'packages/website-ue/src/core/blitzkit/api';
|
||||
import {
|
||||
LocaleProvider,
|
||||
useLocale,
|
||||
@@ -14,13 +13,7 @@ import {
|
||||
import type { MaybeSkeletonComponentProps } from 'packages/website-ue/src/types/maybeSkeletonComponentProps';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
const allAvatars = await metadata
|
||||
.get('BlitzkitAllAvatarsEntity.blitzkit_all_avatars')
|
||||
.then((entity) =>
|
||||
entity
|
||||
.get(BlitzkitAllAvatarsComponent, 'blitzkitAllAvatarsComponent')
|
||||
.avatars.reverse(),
|
||||
);
|
||||
const { avatars } = await api.avatars();
|
||||
|
||||
export function Page({ localeData }: LocaleAcceptorProps) {
|
||||
return (
|
||||
@@ -39,7 +32,7 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const [search, setSearch] = useState<string | null>(null);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const groups = useMemo(() => {
|
||||
const avatars = allAvatars
|
||||
const avatarsSorted = avatars
|
||||
.map((avatar) => ({
|
||||
avatar,
|
||||
name: gameStrings[avatar.name] as string | undefined,
|
||||
@@ -52,9 +45,9 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
? -1
|
||||
: a.name.localeCompare(b.name),
|
||||
);
|
||||
const groups = new Map<string | undefined, typeof avatars>();
|
||||
const groups = new Map<string | undefined, typeof avatarsSorted>();
|
||||
|
||||
for (const avatar of avatars) {
|
||||
for (const avatar of avatarsSorted) {
|
||||
if (groups.has(avatar.name)) {
|
||||
groups.get(avatar.name)!.push(avatar);
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import { avatarsTool } from 'packages/website-ue/src/constants/tools';
|
||||
import { metadata } from 'packages/website-ue/src/core/blitz/metadata';
|
||||
import { api } from 'packages/website-ue/src/core/blitzkit/api';
|
||||
import ToolLayout from 'packages/website-ue/src/layouts/ToolLayout.astro';
|
||||
import { Page } from './_index';
|
||||
|
||||
@@ -8,11 +8,11 @@ export { getStaticPaths } from '../index.astro';
|
||||
|
||||
const { locale } = Astro.props;
|
||||
|
||||
const gameStrings = await metadata.stringsPartial(locale, [
|
||||
const gameStrings = await api.metadata.stringsPartial(locale, [
|
||||
'ProfileAvatarEntity__*',
|
||||
]);
|
||||
---
|
||||
|
||||
<ToolLayout tool={avatarsTool} {locale} >
|
||||
<ToolLayout tool={avatarsTool} {locale}>
|
||||
<Page localeData={{ locale, gameStrings }} client:load />
|
||||
</ToolLayout>
|
||||
@@ -1,29 +1,28 @@
|
||||
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';
|
||||
import type { BlitzKitAvatar } from '@protos/blitzkit';
|
||||
import type { APIRoute, GetStaticPaths } from 'astro';
|
||||
import { api } from 'packages/website-ue/src/core/blitzkit/api';
|
||||
|
||||
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;
|
||||
const { avatars } = await api.avatars();
|
||||
return avatars.map((avatar) => ({
|
||||
params: { id: avatar.id },
|
||||
props: { avatar },
|
||||
}));
|
||||
};
|
||||
|
||||
export const GET: APIRoute<{}, { id: string }> = async ({ params }) => {
|
||||
const item = await socketMetadata.get(`ProfileAvatarEntity.${params.id}`);
|
||||
export const GET: APIRoute<
|
||||
{ avatar: BlitzKitAvatar },
|
||||
{ id: string }
|
||||
> = async ({ params }) => {
|
||||
const item = api.metadata.get(`ProfileAvatarEntity.${params.id}`);
|
||||
const avatar = item.get(ProfileAvatarComponent, 'profileAvatarComponent');
|
||||
|
||||
try {
|
||||
return await fetch(avatar.avatar);
|
||||
const response = await fetch(avatar.avatar);
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
||||
return new Response(buffer, { headers: response.headers });
|
||||
} catch {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import type { APIRoute } from 'astro';
|
||||
import { api } from 'packages/website-ue/src/core/blitzkit/api';
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
return new Response(BlitzKitAvatars.encode(await api.avatars()).finish());
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { APIRoute, GetStaticPaths, GetStaticPathsItem } from 'astro';
|
||||
import { socketMetadata } from 'packages/website-ue/src/core/blitz/metadata.socket';
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = () => {
|
||||
const paths: GetStaticPathsItem[] = [];
|
||||
|
||||
for (const item in socketMetadata.items) {
|
||||
paths.push({ params: { item } });
|
||||
}
|
||||
|
||||
return paths;
|
||||
};
|
||||
|
||||
export const GET: APIRoute<{}, { item: string }> = async ({ params }) => {
|
||||
return new Response(
|
||||
await socketMetadata.get(params.item).then((item) => item.encode()),
|
||||
{ headers: { 'Content-Type': 'application/octet-stream' } },
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user