diff --git a/packages/core/src/protos/tanks.proto b/packages/core/src/protos/tanks.proto index e284d7438..69f29c099 100644 --- a/packages/core/src/protos/tanks.proto +++ b/packages/core/src/protos/tanks.proto @@ -1,13 +1,7 @@ syntax = "proto2"; -import "packages/core/src/protos/tank.proto"; - package blitzkit; message Tanks { repeated string tanks = 1; } - -message TanksFull { - repeated Tank tanks = 1; -} \ No newline at end of file diff --git a/packages/core/src/protos/tanks_full.proto b/packages/core/src/protos/tanks_full.proto new file mode 100644 index 000000000..7c80b92d1 --- /dev/null +++ b/packages/core/src/protos/tanks_full.proto @@ -0,0 +1,9 @@ +syntax = "proto2"; + +import "packages/core/src/protos/tank.proto"; + +package blitzkit; + +message TanksFull { + repeated Tank tanks = 1; +} \ No newline at end of file diff --git a/packages/website/src/core/blitzkit/blobMirror.ts b/packages/website/src/core/blitzkit/blobMirror.ts index 31b9cfe8f..99bc1344f 100644 --- a/packages/website/src/core/blitzkit/blobMirror.ts +++ b/packages/website/src/core/blitzkit/blobMirror.ts @@ -1,7 +1,6 @@ import { fetchPB, type MessageFns } from '@blitzkit/core'; -import type { APIRoute } from 'astro'; -export async function blobMirror(url: string) { +export async function blobProxy(url: string) { if (import.meta.env.MODE === 'development') { return Response.redirect(url); } else { @@ -11,16 +10,23 @@ export async function blobMirror(url: string) { } } -export function apiMirror(path: string) { - const get: APIRoute = () => blobMirror(`http://localhost:5000${path}`); - return get; +export function apiBlobProxy(path: string) { + return blobProxy(`http://localhost:5000${path}`); } -export function jsonMirror(path: string, message: MessageFns) { - const get: APIRoute = async () => { - return new Response( - JSON.stringify(await fetchPB(`http://localhost:5000${path}`, message)), - ); - }; - return get; +export function apiBlobProxyCurry(path: string) { + return () => apiBlobProxy(path); +} + +export async function jsonMirror( + path: string, + message: MessageFns, +) { + return new Response( + JSON.stringify(await fetchPB(`http://localhost:5000${path}`, message)), + ); +} + +export function jsonMirrorCurry(path: string, message: MessageFns) { + return () => jsonMirror(path, message); } diff --git a/packages/website/src/pages/[...locale]/api/_index.tsx b/packages/website/src/pages/[...locale]/api/_index.tsx index 5bbaeb6f2..ff466fa94 100644 --- a/packages/website/src/pages/[...locale]/api/_index.tsx +++ b/packages/website/src/pages/[...locale]/api/_index.tsx @@ -1,7 +1,21 @@ -import { OpenInNewWindowIcon } from '@radix-ui/react-icons'; -import { Box, Code, IconButton, Link, Table } from '@radix-ui/themes'; -import { useState } from 'react'; -import { Var } from '../../../core/radix/var'; +import { assertSecret } from '@blitzkit/core'; +import { + FileTextIcon, + OpenInNewWindowIcon, + RocketIcon, +} from '@radix-ui/react-icons'; +import { + Button, + Code, + Dialog, + Flex, + IconButton, + Inset, + Link, + Separator, + Table, +} from '@radix-ui/themes'; +import { BlitzKitTheme } from '../../../components/BlitzKitTheme'; import type { APIPath } from './index.astro'; interface SlugsTableProps { @@ -9,13 +23,12 @@ interface SlugsTableProps { path: string; } -const MAX_UNEXPANDED = 3; - -export function SlugsTable({ slugs, path }: SlugsTableProps) { - if (slugs === undefined) return null; - - const [expanded, setExpanded] = useState(false); +interface TableRootProps { + slugs: NonNullable; + path: string; +} +function TableRoot({ slugs, path }: TableRootProps) { return ( @@ -34,7 +47,6 @@ export function SlugsTable({ slugs, path }: SlugsTableProps) { {slugs.groups.map((group, groupIndex) => { - if (!expanded && groupIndex >= MAX_UNEXPANDED) return null; let draftPath = path; slugs.titles.forEach((title) => { @@ -60,53 +72,84 @@ export function SlugsTable({ slugs, path }: SlugsTableProps) { ))} - + - - + + ); })} - - - {!expanded && ( - - )} - - - - { - event.preventDefault(); - setExpanded((state) => !state); - }} - > - {expanded - ? 'Show less' - : `View all ${slugs.groups.length.toLocaleString('en')} slugs`} - - ); } + +export function SlugsTable({ slugs, path }: SlugsTableProps) { + if (slugs === undefined) return null; + + return ( + + + + + + + + + + + + + + ); +} + +interface SchemaProps { + schema: string; + name: string; +} + +export function Schema({ schema, name }: SchemaProps) { + return ( + + + + + + + + + + {name}.proto on GitHub + + + + + + {schema} + + + + + + ); +} diff --git a/packages/website/src/pages/[...locale]/api/index.astro b/packages/website/src/pages/[...locale]/api/index.astro index 4121c73cb..4e2f20065 100644 --- a/packages/website/src/pages/[...locale]/api/index.astro +++ b/packages/website/src/pages/[...locale]/api/index.astro @@ -1,6 +1,7 @@ --- -import { OpenInNewWindowIcon } from '@radix-ui/react-icons'; +import { RocketIcon } from '@radix-ui/react-icons'; import { + Button, Code, Flex, Heading, @@ -10,10 +11,11 @@ import { type CodeProps, } from '@radix-ui/themes'; import type { GetStaticPaths, GetStaticPathsOptions } from 'astro'; +import { readFile } from 'fs/promises'; import { PageWrapper } from '../../../components/PageWrapper'; import { apiTool } from '../../../constants/tools'; import ToolLayout from '../../../layouts/ToolLayout.astro'; -import { SlugsTable } from './_index'; +import { Schema, SlugsTable } from './_index'; export { getStaticPaths } from '../index.astro'; @@ -32,6 +34,10 @@ export interface APIPath { titles: string[]; groups: SlugGroup[]; }; + schema?: { + name: string; + content: string; + }; } type SlugGroup = Record; @@ -88,9 +94,11 @@ for (const rawPath in globRaw) { ); const path = rawPath.replace(ROOT, '').replace('.ts', ''); let comment = ''; + let schema: APIPath['schema']; const tags: Tag[] = []; - commentWithTags.split('\n').forEach((line) => { + + for (const line of commentWithTags.split('\n')) { const match = line.match(tagRegex); if (match) { @@ -103,6 +111,16 @@ for (const rawPath in globRaw) { const firstWordMatch = descriptionRaw.match(firstWordRegex)!; discriminator = firstWordMatch[1]; description = firstWordMatch[2]; + } else if (tag === '@proto') { + const firstWordMatch = descriptionRaw.match(firstWordRegex)!; + const fileName = firstWordMatch[1]; + const buffer = await readFile( + `../../packages/core/src/protos/${fileName}.proto`, + ); + const content = buffer.toString(); + + schema = { name: fileName, content }; + discriminator = firstWordMatch[1]; } else { description = descriptionRaw; } @@ -115,10 +133,10 @@ for (const rawPath in globRaw) { } else { comment += `${line}\n`; } - }); + } comment = comment.trim(); - apis.push({ path, content, comment, tags, slugs }); + apis.push({ path, content, comment, tags, slugs, schema }); } apis.sort((a, b) => a.path.localeCompare(b.path)); @@ -131,21 +149,35 @@ apis.sort((a, b) => a.path.localeCompare(b.path)); apis.map((api, index) => ( <> - + - - {api.path} - - {!api.slugs && ( - - )} - + {api.path} - + + + {api.schema && ( + + )} + + {!api.slugs && ( + + + + )} + + + + {api.comment} @@ -180,11 +212,7 @@ apis.sort((a, b) => a.path.localeCompare(b.path)); )} - - - {index < apis.length - 1 && ( - - )} + {index < apis.length - 1 && } )) } diff --git a/packages/website/src/pages/api/meta.json.ts b/packages/website/src/pages/api/meta.json.ts index cd1e023e9..04f33bf41 100644 --- a/packages/website/src/pages/api/meta.json.ts +++ b/packages/website/src/pages/api/meta.json.ts @@ -1,5 +1,6 @@ import type { APIRoute } from 'astro'; import packageJSON from '../../../../../package.json'; +import { TOOLS } from '../../constants/tools'; /** * Returns information on the current build of BlitzKit like the version @@ -8,6 +9,10 @@ import packageJSON from '../../../../../package.json'; * @todo Add more meta data. * @returns JSON. */ -export const GET: APIRoute = async () => { - return Response.json({ version: packageJSON.version }); +export const GET: APIRoute = async ({ request }) => { + return Response.json({ + url: new URL(request.url).origin, + version: packageJSON.version, + tools: TOOLS.map((tool) => tool.id), + }); }; diff --git a/packages/website/src/pages/api/tanks/[id]/icons/big.png.ts b/packages/website/src/pages/api/tanks/[id]/icons/big.png.ts index b59b1ac6e..aa80c7504 100644 --- a/packages/website/src/pages/api/tanks/[id]/icons/big.png.ts +++ b/packages/website/src/pages/api/tanks/[id]/icons/big.png.ts @@ -1,6 +1,6 @@ import { asset } from '@blitzkit/core'; import type { APIRoute } from 'astro'; -import { blobMirror } from '../../../../../core/blitzkit/blobMirror'; +import { blobProxy } from '../../../../../core/blitzkit/blobMirror'; export { getStaticPaths } from '../meta.json'; @@ -11,5 +11,5 @@ export { getStaticPaths } from '../meta.json'; * @returns PNG. */ export const GET: APIRoute<{}, { id: string }> = async ({ params }) => { - return await blobMirror(asset(`/tanks/${params.id}/icons/big.png`)); + return await blobProxy(asset(`/tanks/${params.id}/icons/big.png`)); }; diff --git a/packages/website/src/pages/api/tanks/[id]/icons/small.png.ts b/packages/website/src/pages/api/tanks/[id]/icons/small.png.ts index 1ec917a21..390765720 100644 --- a/packages/website/src/pages/api/tanks/[id]/icons/small.png.ts +++ b/packages/website/src/pages/api/tanks/[id]/icons/small.png.ts @@ -1,6 +1,6 @@ import { asset } from '@blitzkit/core'; import type { APIRoute } from 'astro'; -import { blobMirror } from '../../../../../core/blitzkit/blobMirror'; +import { blobProxy } from '../../../../../core/blitzkit/blobMirror'; export { getStaticPaths } from '../meta.json'; @@ -11,5 +11,5 @@ export { getStaticPaths } from '../meta.json'; * @returns PNG. */ export const GET: APIRoute<{}, { id: string }> = async ({ params }) => { - return await blobMirror(asset(`/tanks/${params.id}/icons/small.png`)); + return await blobProxy(asset(`/tanks/${params.id}/icons/small.png`)); }; diff --git a/packages/website/src/pages/api/tanks/[id]/meta.json.ts b/packages/website/src/pages/api/tanks/[id]/meta.json.ts index d969a0ec2..73a26278b 100644 --- a/packages/website/src/pages/api/tanks/[id]/meta.json.ts +++ b/packages/website/src/pages/api/tanks/[id]/meta.json.ts @@ -1,5 +1,6 @@ -import { fetchTank, fetchTanks } from '@blitzkit/core'; +import { fetchTanks, Tank } from '@blitzkit/core'; import type { APIRoute, GetStaticPaths } from 'astro'; +import { jsonMirror } from '../../../../core/blitzkit/blobMirror'; export const getStaticPaths = (async () => { const tanks = await fetchTanks(); @@ -7,15 +8,10 @@ export const getStaticPaths = (async () => { }) satisfies GetStaticPaths; /** - * Returns the meta data on a tank which includes its modules, characteristics, - * ancestry, descendants, equipment, characteristics, etc. However, this does - * not include information on the tank model. Use tanks/list.json to get all - * valid ids. + * Identical to tanks/[id]/meta.pb but in JSON. * * @param id The tank id. * @returns JSON. */ -export const GET: APIRoute<{}, { id: string }> = async ({ params }) => { - const tank = await fetchTank(params.id); - return Response.json(tank); -}; +export const GET: APIRoute<{}, { id: string }> = ({ params }) => + jsonMirror(`/tanks/${params.id}.pb`, Tank); diff --git a/packages/website/src/pages/api/tanks/[id]/meta.pb.ts b/packages/website/src/pages/api/tanks/[id]/meta.pb.ts new file mode 100644 index 000000000..c0135a7d6 --- /dev/null +++ b/packages/website/src/pages/api/tanks/[id]/meta.pb.ts @@ -0,0 +1,20 @@ +import { fetchTanks } from '@blitzkit/core'; +import type { APIRoute, GetStaticPaths } from 'astro'; +import { apiBlobProxy } from '../../../../core/blitzkit/blobMirror'; + +export const getStaticPaths = (async () => { + const tanks = await fetchTanks(); + return tanks.tanks.map((id) => ({ params: { id } })); +}) satisfies GetStaticPaths; + +/** + * Returns the meta data on a tank which includes its modules, characteristics, + * ancestry, descendants, equipment, characteristics, etc. However, this does + * not include information on the tank model. Use tanks/list.json to get all + * valid ids. + * + * @param id The tank id. + * @proto tank + */ +export const GET: APIRoute<{}, { id: string }> = ({ params }) => + apiBlobProxy(`/tanks/${params.id}.pb`); diff --git a/packages/website/src/pages/api/tanks/full.json.ts b/packages/website/src/pages/api/tanks/full.json.ts index c4a06fd8c..546a5accf 100644 --- a/packages/website/src/pages/api/tanks/full.json.ts +++ b/packages/website/src/pages/api/tanks/full.json.ts @@ -1,5 +1,5 @@ import { TanksFull } from '@blitzkit/core'; -import { jsonMirror } from '../../../core/blitzkit/blobMirror'; +import { jsonMirrorCurry } from '../../../core/blitzkit/blobMirror'; /** * Identical to tanks/full.pb but in JSON. @@ -7,4 +7,4 @@ import { jsonMirror } from '../../../core/blitzkit/blobMirror'; * @warning This is a large response. Consider using tanks/[id]/meta.json for individual tanks. * @returns JSON. */ -export const GET = jsonMirror('/tanks/full.pb', TanksFull); +export const GET = jsonMirrorCurry('/tanks/full.pb', TanksFull); diff --git a/packages/website/src/pages/api/tanks/full.pb.ts b/packages/website/src/pages/api/tanks/full.pb.ts index 392220bc2..9c8c3a049 100644 --- a/packages/website/src/pages/api/tanks/full.pb.ts +++ b/packages/website/src/pages/api/tanks/full.pb.ts @@ -1,8 +1,8 @@ -import { apiMirror } from '../../../core/blitzkit/blobMirror'; +import { apiBlobProxyCurry } from '../../../core/blitzkit/blobMirror'; /** * Returns meta data similar to tanks/[id].json but for all tanks as an array. There is no guarantee of order. * - * @proto TanksFull + * @proto tanks_full */ -export const GET = apiMirror('/tanks/full.pb'); +export const GET = apiBlobProxyCurry('/tanks/full.pb'); diff --git a/packages/website/src/pages/api/tanks/list.json.ts b/packages/website/src/pages/api/tanks/list.json.ts index 4b1dc8577..79233795a 100644 --- a/packages/website/src/pages/api/tanks/list.json.ts +++ b/packages/website/src/pages/api/tanks/list.json.ts @@ -1,9 +1,9 @@ import { Tanks } from '@blitzkit/core'; -import { jsonMirror } from '../../../core/blitzkit/blobMirror'; +import { jsonMirrorCurry } from '../../../core/blitzkit/blobMirror'; /** * Identical to tanks/list.pb but in JSON. * * @returns JSON. */ -export const GET = jsonMirror('/tanks/list.pb', Tanks); +export const GET = jsonMirrorCurry('/tanks/list.pb', Tanks); diff --git a/packages/website/src/pages/api/tanks/list.pb.ts b/packages/website/src/pages/api/tanks/list.pb.ts index 4d3d8c8d4..0bd6a77b1 100644 --- a/packages/website/src/pages/api/tanks/list.pb.ts +++ b/packages/website/src/pages/api/tanks/list.pb.ts @@ -1,8 +1,8 @@ -import { apiMirror } from '../../../core/blitzkit/blobMirror'; +import { apiBlobProxyCurry } from '../../../core/blitzkit/blobMirror'; /** * Returns a list of all tank ids. There is no guarantee of order. * - * @returns JSON. + * @proto tanks */ -export const GET = apiMirror('/tanks/list.pb'); +export const GET = apiBlobProxyCurry('/tanks/list.pb'); diff --git a/src/BlitzKit.CLI/Functions/Api.cs b/src/BlitzKit.CLI/Functions/Api.cs index ababef3a6..e226e86de 100644 --- a/src/BlitzKit.CLI/Functions/Api.cs +++ b/src/BlitzKit.CLI/Functions/Api.cs @@ -1,5 +1,9 @@ +using System.Threading.Tasks; +using AssetRipper.TextureDecoder.Rgb.Channels; +using Blitzkit; using Google.Protobuf; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; namespace BlitzKit.CLI.Functions { @@ -9,19 +13,19 @@ namespace BlitzKit.CLI.Functions readonly Mangler mangler = new(args); void Map(string path, Func> func) + where T : IMessage => + app.MapGet(path, async context => await Octet(context, await func())); + + void Map(string path, Func func) + where T : IMessage => Map(path, () => Task.FromResult(func())); + + static async Task Octet(HttpContext context, T message) where T : IMessage { - app.MapGet( - path, - async (context) => - { - var message = await func(); - var bytes = message.ToByteArray(); + var bytes = message.ToByteArray(); - context.Response.ContentType = "application/octet-stream"; - await context.Response.Body.WriteAsync(bytes); - } - ); + context.Response.ContentType = "application/octet-stream"; + await context.Response.Body.WriteAsync(bytes); } public async Task Run() @@ -31,6 +35,11 @@ namespace BlitzKit.CLI.Functions Map("/tanks/full.pb", mangler.TanksFull); Map("/tanks/list.pb", mangler.Tanks); + app.MapGet( + "/tanks/{id}.pb", + (HttpContext context, string id) => Octet(context, mangler.Tank(id)) + ); + app.Run(); } } diff --git a/src/BlitzKit.CLI/Functions/Mangler.cs b/src/BlitzKit.CLI/Functions/Mangler.cs index 07e94de1a..1b474ab78 100644 --- a/src/BlitzKit.CLI/Functions/Mangler.cs +++ b/src/BlitzKit.CLI/Functions/Mangler.cs @@ -49,11 +49,21 @@ namespace BlitzKit.CLI.Functions readonly BlitzProvider provider = new(args.Contains("--depot")); Locales? locales; readonly Dictionary> strings = []; + TanksFull tanksFull = null!; - public async Task TanksFull() + public async Task Initialize() { + await FetchStrings(); + tanksFull = TanksFull(); + } + + public TanksFull TanksFull() + { + if (tanksFull != null) + return tanksFull; + var dirs = provider.RootDirectory.GetDirectory("Blitz/Content/Tanks").Directories; - TanksFull tanksFull = new(); + tanksFull = new(); foreach (var dir in dirs) { @@ -62,7 +72,7 @@ namespace BlitzKit.CLI.Functions foreach (var tankDir in dir.Value.Directories) { - var tank = MangleTank(tankDir.Value); + var tank = Tank(tankDir.Value); tanksFull.Tanks.Add(tank); } } @@ -72,7 +82,25 @@ namespace BlitzKit.CLI.Functions return tanksFull; } - Tank MangleTank(VFS tankDir) + public Tanks Tanks() + { + var tanksFull = TanksFull(); + Tanks tanks = new(); + + foreach (var tank in tanksFull.Tanks) + { + tanks.Tanks_.Add(tank.Id); + } + + return tanks; + } + + public Tank Tank(string id) + { + return tanksFull.Tanks.First(tank => tank.Id == id); + } + + Tank Tank(VFS tankDir) { var pdaName = $"PDA_{tankDir.Name}"; var pda = provider.LoadObject($"{tankDir.Path}/{pdaName}.{pdaName}"); @@ -94,24 +122,6 @@ namespace BlitzKit.CLI.Functions return tank; } - public async Task Tanks() - { - var tanksFull = await TanksFull(); - Tanks tanks = new(); - - foreach (var tank in tanksFull.Tanks) - { - tanks.Tanks_.Add(tank.Id); - } - - return tanks; - } - - public async Task Initialize() - { - await FetchStrings(); - } - /////////////////////////////////////////////////////////////////////////// I18n GetString(string name)