Compare commits

...
17 Commits
Author SHA1 Message Date
tresabhi b8742bae66 fix linting errors 2026-08-05 14:17:33 -05:00
tresabhi af58b4614d master deploy 2026-08-05 14:09:00 -05:00
tresabhi db6c4679b7 fix app id 2026-08-05 13:50:25 -05:00
tresabhi 8169dfd85d no .env 2026-08-05 13:43:10 -05:00
tresabhi e6d7f78ea4 game dir 2026-08-05 13:38:50 -05:00
tresabhi f1f29b85b7 s'more fixes 2026-08-05 13:32:33 -05:00
tresabhi f587bc0983 fix legacy redirect 2026-08-05 13:30:20 -05:00
tresabhi 7ef4e0b9af envs 2026-08-05 13:29:55 -05:00
tresabhi b52919ebfe update matrix 2026-08-05 13:22:33 -05:00
tresabhi 84290b1c0e remove assets 2026-08-05 13:19:27 -05:00
tresabhi 24297dcae8 remove historic averages 2026-08-05 13:18:27 -05:00
tresabhi c7ee3dbf7e update changelogs 2026-08-05 13:16:32 -05:00
tresabhi dd769a9aa5 update settings and docs 2026-08-05 13:15:47 -05:00
tresabhi 645cfacecb update embed and fix varuna bug 2026-08-05 13:13:48 -05:00
tresabhi d2041d0ee6 update tier list 2026-08-05 13:05:31 -05:00
tresabhi ed7dd76ee5 update session 2026-08-05 13:04:11 -05:00
tresabhi bc2b9f1267 glossary 2026-08-05 13:01:51 -05:00
74 changed files with 439 additions and 790 deletions

No files matched your search

-83
View File
@@ -1,83 +0,0 @@
name: Assets
on:
push:
branches: production
workflow_dispatch:
inputs:
asset_repo:
type: choice
required: false
description: What asset repo to write to
options:
- blitzkit/assets
- blitzkit/assets-preview
- blitzkit/assets-dev
default: blitzkit/assets
asset_source:
type: string
required: false
description: "Archive URL or Steam to download (example: steam:APP_ID/DEPOT_ID or zip:URL_TO_ZIP)"
default: "steam:444200/444202"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.asset_repo }}
cancel-in-progress: true
env:
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_REPO: ${{ github.event.inputs.asset_repo != '' && github.event.inputs.asset_repo || secrets.PUBLIC_ASSET_REPO }}
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: true
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }}
PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }}
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
PUBLIC_DEBUG_MISSING_I18N: false
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_FAST_USER: blitzkit
PUBLIC_ASSET_FAST_REPO: assets
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
jobs:
build_assets:
name: Build assets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- uses: oven-sh/setup-bun@v2
- run: bun run setup
- name: AppArmor hack
run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
- name: Install patches
run: bun run setup:patches --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }}
- name: Build assets
run: bun run build:assets --all-targets --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }}
+1
View File
@@ -51,4 +51,5 @@ jobs:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
run: bun run build:averages --production
+115 -35
View File
@@ -9,64 +9,144 @@ on:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
env:
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: ${{ secrets.ALLOW_ROBOTS }}
# blitzkit
PUBLIC_BRANCH: main
PUBLIC_PROMOTE_OPENTEST: false
PUBLIC_STATIC_ASSET_BASE: https://assets.blitzkit.app
PUBLIC_GUIDE_WEBSITE: https://guidesblitz.com
# blitz
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
SERVICING: ${{ secrets.SERVICING }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GAME_DIR: temp/game
# google
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
# wargaming
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
# steam
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
STEAM_APP: 444200
STEAM_OS: windows
jobs:
build:
name: Build website
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- chunk: default
node: blitzkit/blitzkit.github.io
path: dist/website
- chunk: api
node: blitzkit/api
path: dist/website/api
steps:
- uses: actions/checkout@v6
- name: Free disk space
uses: jlumbroso/free-disk-space@main
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- uses: oven-sh/setup-bun@v2
- uses: arduino/setup-protoc@v3
- name: Setup bun
uses: oven-sh/setup-bun@v2
- name: Setup protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- uses: actions/configure-pages@v6
- run: bun run setup
- name: Build website
run: bun run build:website
- name: Configure pages
uses: actions/configure-pages@v6
- name: Install dependencies
run: bun install
- name: Setup DepotDownloader
run: |
REPO_USER="SteamRE"
REPO_NAME="DepotDownloader"
TEMP="temp"
OUT="$TEMP/$REPO_NAME"
ZIP="$TEMP/$REPO_NAME.zip"
URL=$(
curl -s "https://api.github.com/repos/$REPO_USER/$REPO_NAME/releases/latest" \
| jq -r '.assets[]
| select(.name | contains("linux-x64"))
| .browser_download_url' \
| head -n 1
)
mkdir -p "$TEMP"
curl -L "$URL" -o "$ZIP"
unzip -p "$ZIP" "$REPO_NAME" > "$OUT"
rm "$ZIP"
chmod +x "$OUT"
- name: Download game
run: |
TEMP="temp"
DEPOT_DOWNLOADER="$TEMP/DepotDownloader"
GAME="$TEMP/game"
$DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME
- name: Build
run: bun run build
env:
BUILD_CHUNK: ${{ matrix.chunk }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v5
id: upload
uses: actions/upload-artifact@v4
with:
path: dist/website
name: chunk-${{ matrix.chunk }}
path: ${{ matrix.path }}
deploy-github-pages:
if: ${{ github.ref == 'refs/heads/production' }}
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
needs: build
environment:
name: Production
url: ${{ steps.deployment.outputs.page_url }}
- name: Save artifact ID
run: |
echo "${{ steps.upload.outputs.artifact-id }}"
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
- name: Trigger deployment
env:
TOKEN: ${{ secrets.GH_NODE_DEPLOY_TOKEN }}
ARTIFACT_ID: ${{ steps.upload.outputs.artifact-id }}
TARGET_REPO: ${{ matrix.node }}
run: |
curl \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $TOKEN" \
"https://api.github.com/repos/$TARGET_REPO/dispatches" \
-d "{
\"event_type\": \"deploy-pages\",
\"client_payload\": {
\"source_repo\": \"${{ github.repository }}\",
\"run_id\": \"${{ github.run_id }}\",
\"artifact_id\": \"$ARTIFACT_ID\",
\"chunk\": \"${{ matrix.chunk }}\"
}
}"
+4 -2
View File
@@ -1,10 +1,12 @@
{
"name": "blitzkit",
"version": "1.30.8",
"version": "1.31.0",
"license": "BlitzKit License",
"private": true,
"scripts": {
"dev": "cd packages/website && bun run dev"
"dev": "cd packages/website && bun run dev",
"build": "cd packages/website && bun run build",
"build:averages": "cd packages/scripts && bun run build:averages"
},
"type": "module",
"workspaces": [
+2 -2
View File
@@ -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;
}
+2
View File
@@ -2,6 +2,7 @@ import {
CamouflageDefinitions,
ConsumableDefinitions,
EquipmentDefinitions,
Gallery,
GameDefinitions,
MapDefinitions,
ModelDefinitions,
@@ -20,4 +21,5 @@ export abstract class BlitzKitAPI {
abstract camouflageDefinitions(): Promise<CamouflageDefinitions>;
abstract tankDefinitions(): Promise<TankDefinitions>;
abstract gameDefinitions(): Promise<GameDefinitions>;
abstract galleryDefinitions(): Promise<Gallery>;
}
+5
View File
@@ -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);
}
}
@@ -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<string, { avatar: Avatar; url: string }> = {};
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;
}
}
@@ -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 {}
+1 -20
View File
@@ -2,7 +2,6 @@ import {
AverageDefinitions,
AverageDefinitionsAllStats,
AverageDefinitionsEntry,
type AverageDefinitionsManifest,
type IndividualTankStats,
REGIONS,
type Region,
@@ -23,14 +22,6 @@ interface DataPoint {
w: number;
}
type OptionalSecondLevel<T> = {
[P in keyof T]: P extends "samples"
? T[P]
: {
[K in keyof T[P]]?: T[P][K];
};
};
const MINUTE = 60 * 1000;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
@@ -223,26 +214,16 @@ async function postWork() {
});
const time = Date.now();
const latest = Math.round(time / DAY);
const averageDefinitions = {
averages,
samples,
time,
} satisfies AverageDefinitions;
const manifest: AverageDefinitionsManifest = {
version: 1,
latest,
};
await uploader.add({
path: `averages/${latest}.pb`,
path: "averages.pb",
content: AverageDefinitions.encode(averageDefinitions).finish(),
});
await uploader.add({
path: "averages/manifest.json",
content: new TextEncoder().encode(JSON.stringify(manifest)),
});
await uploader.flush();
}
@@ -1,43 +0,0 @@
import { assertSecret } from "@blitzkit/core";
import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract";
import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local";
import { MixedVFS } from "@blitzkit/core/src/blitzkit/vfs/mixed";
import { SteamVFS } from "@blitzkit/core/src/blitzkit/vfs/steam";
import { ZipVFS } from "@blitzkit/core/src/blitzkit/vfs/zip";
export const PATCHES_ROOT = "../../temp/patches";
const steamPattern = /steam:(\d+)\/(\d+)/;
const zipPattern = /zip:(.+)/;
const localePattern = /local:"?(.+)"?/;
const source = Bun.argv
.find((arg) => arg.startsWith("--source="))
?.split("=")[1];
let resolved: AbstractVFS;
if (!source) {
throw new Error("No source specified");
} else if (steamPattern.test(source)) {
const [, app, depot] = source.match(steamPattern)!.map(Number);
resolved = await new SteamVFS(
assertSecret(import.meta.env.STEAM_USERNAME),
assertSecret(import.meta.env.STEAM_PASSWORD),
app,
depot,
).init();
} else if (zipPattern.test(source)) {
const [, url] = source.match(zipPattern)!;
resolved = await new ZipVFS(url).init();
} else if (localePattern.test(source)) {
const [, path] = source.match(localePattern)!;
resolved = await new LocalVFS(path).init();
} else {
throw new Error("No valid source specified");
}
resolved = new MixedVFS([new LocalVFS(PATCHES_ROOT), resolved]);
export const vfs = resolved;
@@ -1,73 +0,0 @@
import sharp from "sharp";
import { AssetUploader } from "../core/github/assetUploader";
import { vfs } from "./constants";
import { OptionalDevices } from "./definitions";
export async function equipmentIcons() {
console.log("Building equipment icons...");
using uploader = new AssetUploader("equipment icons");
const optionalDevices = await vfs.xml<{ root: OptionalDevices }>(
`Data/XML/item_defs/vehicles/common/optional_devices.xml`,
);
const mappings = await vfs.yaml<Mappings>(
`Data/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml`,
);
const image = sharp(
await vfs.file(
`Data/Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp`,
),
);
for (const optionalDeviceKey in optionalDevices.root) {
const optionalDevice = optionalDevices.root[optionalDeviceKey];
if (optionalDeviceKey === "nextAvailableId") continue;
const mapping = mappings.StyleSheets.find((mapping) =>
mapping.selectors.includes(
`.optional_device_item.${optionalDevice.icon} #Img`,
),
);
if (!mapping) {
console.warn(`No mapping found for ${optionalDevice.icon}; skipping...`);
continue;
}
const configPathRaw = mapping.properties["bg-sprite"];
const configPath = configPathRaw.replace("~res:/", "");
if (configPath.startsWith("Gfx/Lobby")) {
const configPathWebp = configPath.replace(".txt", "");
const image = sharp(await vfs.file(`Data/${configPathWebp}.packed.webp`));
const content = await image.trim().toBuffer();
await uploader.add({
path: `icons/equipment/${optionalDevice.id}.webp`,
content,
});
} else {
const config = await vfs.text(`Data/${configPath}`);
const sizes = config.split("\n")[4].split(" ").map(Number);
const content = await image
.clone()
.extract({
left: sizes[0],
top: sizes[1],
width: sizes[2],
height: sizes[3],
})
.toBuffer();
await uploader.add({
path: `icons/equipment/${optionalDevice.id}.webp`,
content,
});
}
}
await uploader.flush();
}
-64
View File
@@ -1,64 +0,0 @@
import { config } from "dotenv";
import { argv } from "process";
import { boosterIcons } from "./boosterIcons";
import { camouflageIcons } from "./camouflageIcons";
import { vfs } from "./constants";
import { consumableProvisionIcons } from "./consumableProvisionIcons";
import { currencies } from "./currencies";
import { definitions } from "./definitions";
import { equipmentIcons } from "./equipmentIcons";
import { flags } from "./flags";
import { gameModeBanners } from "./gameModeBanners";
import { glossary } from "./glossary";
import { migration } from "./migration";
import { moduleIcons } from "./moduleIcons";
import { popularTanks } from "./popularTanks";
import { shellIcons } from "./shellIcons";
import { skillIcons } from "./skillIcons";
import { tankArmors } from "./tankArmors";
import { tankIcons } from "./tankIcons";
import { tankModels } from "./tankModels";
config();
const allTargets = argv.includes("--all-targets");
const targets = argv
.find((argument) => argument.startsWith("--target"))
?.split("=")[1]
.split(",");
if (!targets && !allTargets) throw new Error("No target(s) specified");
const methods = [
definitions,
tankIcons,
tankModels,
tankArmors,
gameModeBanners,
currencies,
skillIcons,
flags,
shellIcons,
moduleIcons,
equipmentIcons,
consumableProvisionIcons,
camouflageIcons,
boosterIcons,
glossary,
migration,
popularTanks,
];
for (const method of methods) {
if (allTargets || targets?.includes(method.name)) {
try {
await method();
} catch (error) {
console.warn(`Failed method ${method.name}; skipping...`);
console.error(error);
}
}
}
vfs.dispose();
process.exit(0);
@@ -1,34 +0,0 @@
import { toUniqueId } from "@blitzkit/core";
import { AssetUploader } from "../core/github/assetUploader";
import { vfs } from "./constants";
import { VehicleDefinitionList } from "./definitions";
export async function migration() {
console.log("Building migration map...");
using uploader = new AssetUploader("migration map");
const map: Record<string, number> = {};
const nations = await vfs
.dir(`Data/XML/item_defs/vehicles`)
.then((files) => files.filter((nation) => nation !== "common"));
for (const nation of nations) {
const tankList = await vfs.xml<{ root: VehicleDefinitionList }>(
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
);
for (const tankKey in tankList.root) {
const tank = tankList.root[tankKey];
const tankId = toUniqueId(nation, tank.id);
const davaKey = `${nation}:${tankKey}`;
map[davaKey] = tankId;
}
}
await uploader.add({
path: "definitions/migration.json",
content: new TextEncoder().encode(JSON.stringify(map)),
});
await uploader.flush();
}
@@ -1,77 +0,0 @@
import {
assertSecret,
fetchTankDefinitions,
PopularTanks,
} from "@blitzkit/core";
import { google } from "googleapis";
import { AssetUploader } from "../core/github/assetUploader";
export async function popularTanks() {
using uploader = new AssetUploader("popular tanks");
const tankDefinitions = await fetchTankDefinitions();
const auth = await google.auth.getClient({
keyFile: import.meta.env.GOOGLE_APPLICATION_CREDENTIALS,
scopes: ["https://www.googleapis.com/auth/analytics.readonly"],
});
const analytics = google.analyticsdata({ version: "v1beta", auth });
const report = await analytics.properties.runReport({
property: `properties/${assertSecret(
import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID,
)}`,
requestBody: {
dateRanges: [{ startDate: "7daysAgo", endDate: "today" }],
dimensions: [{ name: "pagePath" }],
metrics: [{ name: "screenPageViews" }],
orderBys: [{ metric: { metricName: "screenPageViews" }, desc: true }],
dimensionFilter: {
filter: {
fieldName: "pagePath",
stringFilter: {
matchType: "BEGINS_WITH",
value: "/tanks/",
},
},
},
},
});
if (!report.data.rows) {
throw new Error("No rows in report");
}
const tanks = report.data.rows
.filter(
(row) =>
row.dimensionValues &&
row.dimensionValues[0].value &&
row.dimensionValues[0].value !== "/tanks/" &&
row.metricValues &&
row.metricValues[0].value,
)
.map((row) => ({
id: Number(
row.dimensionValues![0].value!.match(
/\/tools\/tankopedia\/(\d+)\/?/,
)?.[1],
),
views: Number(row.metricValues![0].value!),
}))
.filter((row) => {
if (!(row.id in tankDefinitions.tanks)) {
console.warn("Skipping unknown popular tank", row.id);
return false;
}
return row.id in tankDefinitions.tanks;
})
.slice(0, 8)
.map(({ id, views }) => ({ id, views }));
const popularTanks: PopularTanks = { tanks };
await uploader.add({
path: "definitions/popular-tanks.pb",
content: PopularTanks.encode(popularTanks).finish(),
});
await uploader.flush();
}
@@ -1,64 +0,0 @@
import { NATION_IDS } from "@blitzkit/core";
import sharp from "sharp";
import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect";
import { AssetUploader } from "../core/github/assetUploader";
import { vfs } from "./constants";
import type { VehicleDefinitionList } from "./definitions";
export async function tankIcons() {
console.log("Building tank icons...");
using uploader = new AssetUploader("tank icons");
const nations = await vfs
.dir(`Data/XML/item_defs/vehicles`)
.then((files) => files.filter((nation) => nation !== "common"));
for (const nation of nations) {
const tanks = await vfs.xml<{ root: VehicleDefinitionList }>(
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
);
for (const tankKey in tanks.root) {
const tank = tanks.root[tankKey];
if (tankKey.includes("tutorial_bot")) continue;
const nationVehicleId = tank.id;
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
const parameters = await vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
const smallPath = `Data/${parameters.resourcesPath.smallIconPath
.replace(/~res:\//, "")
.replace(/\..+/, "")}.packed.webp`;
const bigPath = `Data/${parameters.resourcesPath.bigIconPath
.replace(/~res:\//, "")
.replace(/\..+/, "")}.packed.webp`;
const big = await extractPackedTankIcon(
await vfs.file(bigPath),
`tank ${tankKey} big`,
);
const small = await extractPackedTankIcon(
await vfs.file(smallPath),
`tank ${tankKey} small`,
);
if (big) {
await uploader.add({
content: big,
path: `icons/tanks/big/${id}.webp`,
});
}
if (small) {
await uploader.add({
content: small,
path: `icons/tanks/small/${id}.webp`,
});
}
}
}
await uploader.flush();
}
@@ -1,44 +0,0 @@
import { toUniqueId } from "@blitzkit/core";
import { NodeIO } from "@gltf-transform/core";
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
import { extractModel } from "../core/blitz/extractModel";
import { AssetUploader } from "../core/github/assetUploader";
import { vfs } from "./constants";
import { VehicleDefinitionList } from "./definitions";
import { TankParameters } from "./tankIcons";
export async function tankModels() {
console.log("Building tank models...");
using uploader = new AssetUploader("tank models");
const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS);
const nations = await vfs
.dir(`Data/XML/item_defs/vehicles`)
.then((files) => files.filter((nation) => nation !== "common"));
for (const nationIndex in nations) {
const nation = nations[nationIndex];
const tanks = await vfs.xml<{ root: VehicleDefinitionList }>(
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
);
const entries = Object.entries(tanks.root);
for (const [tankKey, tank] of entries) {
if (tankKey.includes("tutorial_bot")) continue;
const id = toUniqueId(nation, tank.id);
const parameters = await vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
const model = await extractModel(
vfs,
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ""),
);
const content = Buffer.from(await nodeIO.writeBinary(model));
await uploader.add({ path: `3d/tanks/models/${id}.glb`, content });
}
}
await uploader.flush();
}
+1 -11
View File
@@ -1,4 +1,3 @@
import node from "@astrojs/node";
import react from "@astrojs/react";
import { wrapper } from "@blitzkit/i18n";
import locales from "@blitzkit/i18n/locales.json";
@@ -10,14 +9,6 @@ import { defineConfig } from "astro/config";
import packageJSON from "../../package.json";
import { tools } from "./src/constants/tools";
const POSSIBLE_TARGETS = ["app", "web"];
const target = process.env.PUBLIC_TARGET;
if (!target || !POSSIBLE_TARGETS.includes(target)) {
throw new Error("Invalid target");
}
export default defineConfig({
devToolbar: { enabled: false },
@@ -26,13 +17,12 @@ export default defineConfig({
esbuild: { target: "es2022" },
},
output: target === "web" ? "static" : "server",
output: "static",
site: "https://blitzkit.app",
outDir: "../../dist/website",
prefetch: { defaultStrategy: "hover", prefetchAll: true },
build: { concurrency: 4 },
adapter: target === "app" ? node({ mode: "standalone" }) : undefined,
integrations: [
favicons({
+4 -4
View File
@@ -2,10 +2,10 @@
"name": "@blitzkit/website",
"type": "module",
"scripts": {
"dev": "PUBLIC_TARGET=web astro dev",
"build": "PUBLIC_TARGET=web astro build",
"preview": "PUBLIC_TARGET=web astro preview",
"lint": "PUBLIC_TARGET=web astro check",
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"lint": "astro check",
"astro": "astro"
},
"dependencies": {
@@ -1,5 +1,5 @@
import { Switch } from "@radix-ui/themes";
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index";
import { EmbedState } from "../../stores/embedState";
export function Boolean({ configKey }: EmbedPreviewControllerProps) {
@@ -1,4 +1,4 @@
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index";
import { EmbedState } from "../../stores/embedState";
import { ColorControllerRaw } from "./ColorControllerRaw";
@@ -1,6 +1,6 @@
import { Select } from "@radix-ui/themes";
import { useLocale } from "../../hooks/useLocale";
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index";
import { EmbedState } from "../../stores/embedState";
import type {
EmbedConfigItemType,
@@ -1,7 +1,7 @@
import { Box } from '@radix-ui/themes';
import { useEffect, useRef } from 'react';
import { embedPreviews } from '../../constants/embeds';
import { useEmbedStateCurry } from '../../stores/embedState/utilities';
import { Box } from "@radix-ui/themes";
import { useEffect, useRef } from "react";
import { embedPreviews } from "../../constants/embeds";
import { useEmbedStateCurry } from "../../stores/embedState/utilities";
interface PreviewWrapperProps {
name: keyof typeof embedPreviews;
@@ -10,17 +10,20 @@ interface PreviewWrapperProps {
export function PreviewWrapper({ name, naked }: PreviewWrapperProps) {
const { useEmbedState } = useEmbedStateCurry();
const width = useEmbedState('width');
const height = useEmbedState('height');
const width = useEmbedState("width");
const height = useEmbedState("height");
const Preview = embedPreviews[name];
const wrapper = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!wrapper.current) return;
wrapper.current.classList.remove('preview-container-animated');
wrapper.current.classList.remove("preview-container-animated");
void wrapper.current.offsetWidth;
wrapper.current.classList.add('preview-container-animated');
wrapper.current.classList.add("preview-container-animated");
}, [width, height]);
if (naked) {
@@ -29,7 +32,7 @@ export function PreviewWrapper({ name, naked }: PreviewWrapperProps) {
position="absolute"
top="0"
left="50%"
style={{ transform: 'translate(-50%, 0) scale(0.75)' }}
style={{ transform: "translate(-50%, 0) scale(0.75)" }}
width={`${width}px`}
height={`${height}px`}
>
@@ -44,7 +47,7 @@ export function PreviewWrapper({ name, naked }: PreviewWrapperProps) {
top="50%"
left="50%"
overflow="hidden"
style={{ transform: naked ? 'scale(0.25)' : 'translate(-50%, -50%)' }}
style={{ transform: naked ? "scale(0.25)" : "translate(-50%, -50%)" }}
ref={wrapper}
className="preview-container-animated"
width={`${width}px`}
@@ -1,5 +1,5 @@
import { Slider } from "@radix-ui/themes";
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index";
import { EmbedState, type RadixRadius } from "../../stores/embedState";
export function Radius({ configKey }: EmbedPreviewControllerProps) {
@@ -1,6 +1,6 @@
import { FontBoldIcon, FontSizeIcon } from "@radix-ui/react-icons";
import { Flex, Slider, Text } from "@radix-ui/themes";
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index";
import { EmbedState, type RadixSizeWithout0 } from "../../stores/embedState";
import {
radixTextWeights,
@@ -12,7 +12,9 @@ import { ColorControllerRaw } from "./ColorControllerRaw";
export function RichText({ configKey }: EmbedPreviewControllerProps) {
const state = EmbedState.use(
(state) =>
state[configKey] as EmbedConfigItemType<EmbedItemType.RichText>["default"]
state[
configKey
] as EmbedConfigItemType<EmbedItemType.RichText>["default"],
);
return (
@@ -1,5 +1,5 @@
import { Slider } from "@radix-ui/themes";
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index";
import { EmbedState, type RadixSize } from "../../stores/embedState";
export function Size({ configKey }: EmbedPreviewControllerProps) {
@@ -1,5 +1,5 @@
import { Slider } from "@radix-ui/themes";
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index";
import { EmbedState, type RadixSizeWithout0 } from "../../stores/embedState";
export function SizeWithout0({ configKey }: EmbedPreviewControllerProps) {
@@ -10,7 +10,7 @@ export function SizeWithout0({ configKey }: EmbedPreviewControllerProps) {
max={9}
value={[
EmbedState.use((state) =>
parseInt(state[configKey] as RadixSizeWithout0)
parseInt(state[configKey] as RadixSizeWithout0),
),
]}
onValueChange={([value]) => {
@@ -1,5 +1,5 @@
import { Slider as RadixSlider } from "@radix-ui/themes";
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index";
import { EmbedState, type RadixSize } from "../../stores/embedState";
import type {
EmbedConfigItemType,
@@ -1,5 +1,5 @@
import { TextField } from "@radix-ui/themes";
import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index";
import type { EmbedPreviewControllerProps } from "../../pages/[...chunk_default]/[...locale]/embed/[embed]/_index";
import { EmbedState } from "../../stores/embedState";
export function TextController({ configKey }: EmbedPreviewControllerProps) {
@@ -14,7 +14,7 @@ import { ContextMenu } from "@radix-ui/themes";
import { useEffect, useMemo, useState } from "react";
import { breakdownConfig } from "../../constants/embeds";
import { awaitableAverageDefinitions } from "../../core/awaitables/averageDefinitions";
import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions";
import { api } from "../../core/blitzkit/api";
import { EmbedBreakdown } from "../../stores/embedBreakdown";
import { useEmbedStateCurry } from "../../stores/embedState/utilities";
import { BreakdownEmbedCard, BreakdownEmbedWrapper } from "../TanksEmbed";
@@ -30,7 +30,8 @@ export const compositeStatsKeysOptions = compositeStatsKeys.map((value) => ({
}));
const [tankDefinitions, averageDefinitions] = await Promise.all([
awaitableTankDefinitions,
api.tankDefinitions(),
awaitableAverageDefinitions,
]);
@@ -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<HTMLDivElement>(null!);
const { unwrap } = useLocale();
@@ -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)}
/>
)
),
)}
</Flex>
</>
@@ -2,7 +2,7 @@ import { literals } from "@blitzkit/i18n/src/literals";
import { EyeOpenIcon } from "@radix-ui/react-icons";
import { Box, Flex, Heading, Text } from "@radix-ui/themes";
import { awaitablePopularTanks } from "../core/awaitables/popularTanks";
import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions";
import { api } from "../core/blitzkit/api";
import {
LocaleProvider,
useLocale,
@@ -11,7 +11,7 @@ import {
import { TankCard } from "./TankCard";
const [tankDefinitions, popularTanks] = await Promise.all([
awaitableTankDefinitions,
api.tankDefinitions(),
awaitablePopularTanks,
]);
@@ -1,6 +1,5 @@
import { asset } from '@blitzkit/core';
import { GenericTankComponentButton } from './GenericTankComponentButton';
import type { TankComponentButtonProps } from './TankComponentButton';
import { GenericTankComponentButton } from "./GenericTankComponentButton";
import type { TankComponentButtonProps } from "./TankComponentButton";
interface ShellButtonProps extends TankComponentButtonProps {
shell: string;
@@ -9,7 +8,7 @@ interface ShellButtonProps extends TankComponentButtonProps {
export function ShellButton({ shell, ...props }: ShellButtonProps) {
return (
<GenericTankComponentButton
icon={asset(`icons/shells/${shell}.webp`)}
icon={`/api/icons/shells/${shell}.webp`}
{...props}
/>
);
@@ -1,4 +1,3 @@
import { asset } from "@blitzkit/core";
import { Flex, type FlexProps, Progress } from "@radix-ui/themes";
import { useProgress } from "@react-three/drei";
@@ -12,7 +11,7 @@ export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) {
return (
<Flex width="100%" height="100%" align="center" justify="center" {...props}>
<img
src={asset(`icons/tanks/big/${id}.webp`)}
src={`/api/tanks/${id}/icons/big.webp`}
style={{ height: "75%", filter: "blur(1rem)" }}
/>
<Progress
@@ -1,16 +1,16 @@
import { Flex, Table } from '@radix-ui/themes';
import { useEffect, useRef } from 'react';
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
import { TierList } from '../../../stores/tierList';
import { tierListRowElements } from '../Table/constants';
import { TierListTile } from '../Tile';
import { Header } from './components/Header';
import { Flex, Table } from "@radix-ui/themes";
import { useEffect, useRef } from "react";
import { api } from "../../../core/blitzkit/api";
import { TierList } from "../../../stores/tierList";
import { tierListRowElements } from "../Table/constants";
import { TierListTile } from "../Tile";
import { Header } from "./components/Header";
interface TierListRowProps {
index: number;
}
const tankDefinitions = await awaitableTankDefinitions;
const tankDefinitions = await api.tankDefinitions();
export function TierListRow({ index }: TierListRowProps) {
const tanks = TierList.use((state) => state.rows[index].tanks);
@@ -2,8 +2,7 @@ import { metaSortTank } from "@blitzkit/core";
import { times } from "lodash-es";
import { useMemo, useState } from "react";
import usePromise from "react-promise-suspense";
import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions";
import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions";
import { api } from "../../core/blitzkit/api";
import { filterTanks } from "../../core/blitzkit/filterTanks";
import { TankFilters } from "../../stores/tankFilters";
import { TierList } from "../../stores/tierList";
@@ -12,8 +11,8 @@ import { TankCardWrapper } from "../TankSearch/components/TankCardWrapper";
import { TierListTile } from "./Tile";
const [tankDefinitions, gameDefinitions] = await Promise.all([
awaitableTankDefinitions,
awaitableGameDefinitions,
api.tankDefinitions(),
api.gameDefinitions(),
]);
const tanks = Object.values(tankDefinitions.tanks);
@@ -27,14 +26,14 @@ export function TierListTiles() {
const filteredTanks = usePromise(
() =>
filterTanks(filters, tanks).then((tanks) =>
tanks.filter(({ id }) => !placedTanks.has(id))
tanks.filter(({ id }) => !placedTanks.has(id)),
),
// react-promise-suspense has awful type annotations
[filters, placedTanks] as any
[filters, placedTanks] as any,
);
const sorted = useMemo(
() => metaSortTank(filteredTanks, gameDefinitions).reverse(),
[filters, placedTanks]
[filters, placedTanks],
);
const [loadedTiles, setLoadedTiles] = useState(DEFAULT_LOADED_CARDS);
@@ -1,9 +1,9 @@
import { useEffect } from 'react';
import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions';
import { generateTierListParams } from '../../core/blitzkit/generateTierListParams';
import { TierList } from '../../stores/tierList';
import { useEffect } from "react";
import { api } from "../../core/blitzkit/api";
import { generateTierListParams } from "../../core/blitzkit/generateTierListParams";
import { TierList } from "../../stores/tierList";
const tankDefinitions = await awaitableTankDefinitions;
const tankDefinitions = await api.tankDefinitions();
export function URLManager() {
const rows = TierList.use((state) => state.rows);
@@ -12,10 +12,10 @@ export function URLManager() {
const params = new URLSearchParams(window.location.search);
params.forEach((value, key) => {
if (key.startsWith('row-')) {
if (key.startsWith("row-")) {
const index = Number.parseInt(key.slice(4), 10);
const values = value
.split(',')
.split(",")
.map(Number)
.filter((id) => id in tankDefinitions.tanks);
@@ -27,7 +27,7 @@ export function URLManager() {
}, []);
useEffect(() => {
window.history.replaceState(null, '', `?${generateTierListParams(rows)}`);
window.history.replaceState(null, "", `?${generateTierListParams(rows)}`);
}, [rows]);
return null;
+23 -22
View File
@@ -1,16 +1,16 @@
import { type CompositeStatsKey } from '@blitzkit/core';
import { grayA, grayDark, grayDarkA } from '@radix-ui/colors';
import type { ReactNode } from 'react';
import { type CompositeStatsKey } from "@blitzkit/core";
import { grayA, grayDark, grayDarkA } from "@radix-ui/colors";
import type { ReactNode } from "react";
import {
BreakdownPreview,
BreakdownRenderer,
compositeStatsKeysOptions,
} from '../components/Embeds/breakdown';
} from "../components/Embeds/breakdown";
import type {
EmbedConfig,
ExtractEmbedConfigTypes,
} from '../stores/embedState';
import { EmbedItemType } from '../stores/embedState/constants';
} from "../stores/embedState";
import { EmbedItemType } from "../stores/embedState/constants";
export const breakdownConfig = {
width: { type: EmbedItemType.Slider, default: 320, min: 128, max: 640 },
@@ -18,7 +18,7 @@ export const breakdownConfig = {
showTotal: { type: EmbedItemType.Boolean, default: true, pad: true },
listGap: { type: EmbedItemType.Size, default: '2' },
listGap: { type: EmbedItemType.Size, default: "2" },
listMaxTanks: {
type: EmbedItemType.Slider,
default: 4,
@@ -27,15 +27,15 @@ export const breakdownConfig = {
pad: true,
},
cardRadius: { type: EmbedItemType.Radius, default: '2' },
cardRadius: { type: EmbedItemType.Radius, default: "2" },
cardHeaderBackgroundColor: {
type: EmbedItemType.Color,
default: grayDarkA.grayA3,
},
cardHeaderPadding: { type: EmbedItemType.Size, default: '1' },
cardHeaderPadding: { type: EmbedItemType.Size, default: "1" },
cardTitle: {
type: EmbedItemType.RichText,
default: { color: grayDark.gray12, weight: 'bold', size: '3' },
default: { color: grayDark.gray12, weight: "bold", size: "3" },
},
cardTitleTypeColor: { type: EmbedItemType.Boolean, default: true },
cardTitleClassIcon: { type: EmbedItemType.Boolean, default: true },
@@ -45,45 +45,45 @@ export const breakdownConfig = {
},
cardBodyPadding: {
type: EmbedItemType.Size,
default: '2',
default: "2",
pad: true,
},
columnGap: { type: EmbedItemType.Size, default: '0' },
columnGap: { type: EmbedItemType.Size, default: "0" },
columnValue: {
type: EmbedItemType.RichText,
default: { color: grayDark.gray12, size: '3', weight: 'regular' },
default: { color: grayDark.gray12, size: "3", weight: "regular" },
},
columnLabel: {
type: EmbedItemType.RichText,
default: { color: grayDark.gray11, size: '2', weight: 'regular' },
default: { color: grayDark.gray11, size: "2", weight: "regular" },
pad: true,
},
column1: {
type: EmbedItemType.Enum,
default: 'cumulative_battles' satisfies CompositeStatsKey,
default: "cumulative_battles" satisfies CompositeStatsKey,
options: compositeStatsKeysOptions,
},
column2: {
type: EmbedItemType.Enum,
default: 'normalized_wins' satisfies CompositeStatsKey,
default: "normalized_wins" satisfies CompositeStatsKey,
options: compositeStatsKeysOptions,
},
column3: {
type: EmbedItemType.Enum,
default: 'cumulative_wn8' satisfies CompositeStatsKey,
default: "cumulative_wn8" satisfies CompositeStatsKey,
options: compositeStatsKeysOptions,
},
column4: {
type: EmbedItemType.Enum,
default: 'normalized_damage_dealt' satisfies CompositeStatsKey,
default: "normalized_damage_dealt" satisfies CompositeStatsKey,
options: compositeStatsKeysOptions,
},
column1CustomLabel: { type: EmbedItemType.String, default: '' },
column2CustomLabel: { type: EmbedItemType.String, default: '' },
column3CustomLabel: { type: EmbedItemType.String, default: '' },
column4CustomLabel: { type: EmbedItemType.String, default: '' },
column1CustomLabel: { type: EmbedItemType.String, default: "" },
column2CustomLabel: { type: EmbedItemType.String, default: "" },
column3CustomLabel: { type: EmbedItemType.String, default: "" },
column4CustomLabel: { type: EmbedItemType.String, default: "" },
} satisfies EmbedConfig;
export const embedConfigurations = {
@@ -93,6 +93,7 @@ export const embedConfigurations = {
export const embedPreviews = {
breakdown: BreakdownPreview,
} satisfies Record<string, () => ReactNode>;
export function extractEmbedConfigDefaults<Config extends EmbedConfig>(
config: Config,
) {
@@ -1,3 +0,0 @@
import { fetchGallery } from "@blitzkit/core";
export const awaitableGallery = fetchGallery();
@@ -1,5 +1,4 @@
export function isHalloween() {
const data = new Date();
return false;
return data.getMonth() === 9 && data.getDate() === 31;
}
+2 -3
View File
@@ -1,5 +1,4 @@
import { assertSecret } from "@blitzkit/core";
import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local";
export const vfs = new LocalVFS(
"/run/media/tresabhi/Windows/Program Files/WindowsApps/7458BE2C.WorldofTanksBlitz_11.19.272.0_x64__x4tje2y229k00",
);
export const vfs = new LocalVFS(assertSecret(import.meta.env.GAME_DIR));
@@ -1,15 +1,15 @@
import { TankDefinition } from '@blitzkit/core';
import { sumBy } from 'lodash-es';
import { useMemo } from 'react';
import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions';
import { Duel } from '../stores/duel';
import { TankDefinition } from "@blitzkit/core";
import { sumBy } from "lodash-es";
import { useMemo } from "react";
import { api } from "../core/blitzkit/api";
import { Duel } from "../stores/duel";
export enum UseEccentricityMode {
Tier,
Class,
}
const tankDefinitions = await awaitableTankDefinitions;
const tankDefinitions = await api.tankDefinitions();
export function useEccentricity(mode: UseEccentricityMode) {
const tank = Duel.use((state) => state.protagonist.tank);
@@ -1,6 +1,6 @@
import { awaitableEquipmentDefinitions } from "../core/awaitables/equipmentDefinitions";
import { api } from "../core/blitzkit/api";
const equipmentDefinitions = await awaitableEquipmentDefinitions;
const equipmentDefinitions = await api.equipmentDefinitions();
export function useEquipmentPreset(preset: string) {
return equipmentDefinitions.presets[preset];
@@ -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);
}
@@ -1,13 +1,13 @@
import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core";
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 ProgressBar from "progress";
import { AssetUploader } from "../core/github/assetUploader";
import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths";
import { getStaticPaths as _getStaticPaths } from "../../_index";
export async function glossary() {
console.log("Building glossary...");
export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
const paths: GetStaticPathsItem[] = [];
using uploader = new AssetUploader("glossary");
const avatars: Record<string, { avatar: Avatar; url: string }> = {};
await Promise.all(
@@ -48,42 +48,32 @@ export async function glossary() {
}),
);
const gallery: Gallery = { avatars: [] };
const totalCount = Object.keys(avatars).length;
const bar = new ProgressBar(
`Fetching avatars of ${totalCount} things :bar`,
totalCount,
);
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`);
bar.tick();
return;
}
gallery.avatars.push(avatar);
const name = `${key}${avatar.extension}`;
const content = await fetch(url)
.then((response) => response.arrayBuffer())
.then((buffer) => new Uint8Array(buffer));
await uploader.add({
path: `gallery/avatars/${key}${avatar.extension}`,
content,
paths.push({
params: { name },
props: { url },
});
bar.tick();
}),
);
await uploader.add({
path: "definitions/gallery.pb",
content: Gallery.encode(gallery).finish(),
});
return paths;
});
await uploader.flush();
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);
}
@@ -105,7 +105,7 @@ async function extractModel(vfs: AbstractVFS, path: string) {
}
if (textures) {
const { image: baseColor, hasAlpha } = await readBaseColor(
const baseColor = await readBaseColor(
`Data/3d/${dirname(path)}/${textures.baseColorMap ?? textures.albedo}`,
textures.miscMap
? `Data/3d/${dirname(path)}/${textures.miscMap}`
@@ -369,7 +369,6 @@ async function extractModel(vfs: AbstractVFS, path: string) {
}
async function readBaseColor(path: string, occlusionPath?: string) {
let hasAlpha = false;
const baseRaw = await readTexture(path);
const occlusionRaw = occlusionPath
? await readTexture(occlusionPath)
@@ -399,7 +398,6 @@ async function readBaseColor(path: string, occlusionPath?: string) {
}
const alpha = base[i * 4 + 3];
hasAlpha ||= alpha < 255;
combined[i * 4 + 0] = Math.round(base[i * 4 + 0] * c);
combined[i * 4 + 1] = Math.round(base[i * 4 + 1] * c);
@@ -413,7 +411,7 @@ async function readBaseColor(path: string, occlusionPath?: string) {
.webp()
.toBuffer();
return { hasAlpha, image };
return image;
}
async function readRoughnessMetallic(path: string) {
@@ -1,5 +1,5 @@
import { useEffect } from "react";
import { App } from "../../../../stores/app";
import { App } from "../../../../../stores/app";
export const AUTH_PROVIDERS = ["wargaming"] as const;
@@ -1,8 +1,8 @@
---
import { Flex, Heading, Text } from "@radix-ui/themes";
import type { GetStaticPaths } from "astro";
import { PageWrapper } from "../../../../components/PageWrapper";
import BaseLayout from "../../../../layouts/BaseLayout.astro";
import { PageWrapper } from "../../../../../components/PageWrapper";
import BaseLayout from "../../../../../layouts/BaseLayout.astro";
import { getStaticPaths as parentGetStaticPaths } from "../../index.astro";
import { AUTH_PROVIDER_NAMES, AUTH_PROVIDERS, Authorize } from "./_index";
@@ -10,7 +10,7 @@ export const getStaticPaths = (() => {
const parentPaths = parentGetStaticPaths();
return AUTH_PROVIDERS.map((provider) =>
parentPaths.map((parent) => ({ params: { ...parent.params, provider } }))
parentPaths.map((parent) => ({ params: { ...parent.params, provider } })),
).flat();
}) satisfies GetStaticPaths;
@@ -1,5 +1,5 @@
import { Markdown } from "../../../../../components/Markdown";
import { PageWrapper } from "../../../../../components/PageWrapper";
import { Markdown } from "../../../../../../components/Markdown";
import { PageWrapper } from "../../../../../../components/PageWrapper";
interface PageProps {
content: string;
@@ -2,14 +2,14 @@
import { AstroSeo } from "@astrolib/seo";
import type { GetStaticPaths } from "astro";
import { readFile } from "fs/promises";
import BaseLayout from "../../../../../layouts/BaseLayout.astro";
import BaseLayout from "../../../../../../layouts/BaseLayout.astro";
import { getStaticPaths as parentGetStaticPaths } from "../../../index.astro";
import { Page } from "./_index";
export const getStaticPaths = (async () => {
const parentPaths = parentGetStaticPaths();
const ROOT = "../../../../../../../../docs/";
const files = import.meta.glob("../../../../../../../../docs/**/*.md", {
const ROOT = "../../../../../../../../../docs/";
const files = import.meta.glob("../../../../../../../../../docs/**/*.md", {
eager: true,
});
@@ -19,7 +19,7 @@ export const getStaticPaths = (async () => {
const couple = file.replace(ROOT, "").split("/");
couple[1] = couple[1].replace(".md", "");
const content = await readFile(
`../../docs/${couple[0]}/${couple[1]}.md`
`../../docs/${couple[0]}/${couple[1]}.md`,
).then((buffer) => buffer.toString());
return parentPaths.map((parent) => ({
@@ -30,7 +30,7 @@ export const getStaticPaths = (async () => {
},
props: { ...parent.props, content },
}));
})
}),
)
).flat();
}) satisfies GetStaticPaths;
@@ -3,21 +3,21 @@ import { AstroSeo } from "@astrolib/seo";
import { MagicWandIcon, StarFilledIcon } from "@radix-ui/react-icons";
import { Flex, Heading, Link } from "@radix-ui/themes";
import { compareVersions } from "compare-versions";
import { PageWrapper } from "../../../../components/PageWrapper";
import { getStrings } from "../../../../core/i18n/getStrings";
import BaseLayout from "../../../../layouts/BaseLayout.astro";
import { PageWrapper } from "../../../../../components/PageWrapper";
import { getStrings } from "../../../../../core/i18n/getStrings";
import BaseLayout from "../../../../../layouts/BaseLayout.astro";
export { getStaticPaths } from "../../index.astro";
// this can't be used in template literal :(
const ROOT = "../../../../../../../docs/changelogs/";
const ROOT = "../../../../../../../../docs/changelogs/";
const { locale } = Astro.props;
const strings = getStrings(locale);
const versions = Object.keys(
import.meta.glob("../../../../../../../docs/changelogs/*.md", {
import.meta.glob("../../../../../../../../docs/changelogs/*.md", {
eager: true,
})
}),
)
.map((file) => file.replace(ROOT, "").replace(".md", ""))
.sort(compareVersions)
@@ -8,32 +8,32 @@ import {
} from "@radix-ui/react-icons";
import { Box, Button, Flex, Heading, ScrollArea, Text } from "@radix-ui/themes";
import { useEffect, useRef, useState, type ReactNode } from "react";
import { CopyButton } from "../../../../components/CopyButton";
import { Boolean } from "../../../../components/Embeds/Boolean";
import { Color } from "../../../../components/Embeds/Color";
import { Enum } from "../../../../components/Embeds/Enum";
import { GenerateURL } from "../../../../components/Embeds/GenerateURL";
import { Import } from "../../../../components/Embeds/Import";
import { PreviewWrapper } from "../../../../components/Embeds/PreviewWrapper";
import { Radius } from "../../../../components/Embeds/Radius";
import { RichText } from "../../../../components/Embeds/RichText";
import { Size } from "../../../../components/Embeds/Size";
import { SizeWithout0 } from "../../../../components/Embeds/SizeWithout0";
import { Slider } from "../../../../components/Embeds/Slider";
import { TextController } from "../../../../components/Embeds/TextController";
import { PageWrapper } from "../../../../components/PageWrapper";
import { CopyButton } from "../../../../../components/CopyButton";
import { Boolean } from "../../../../../components/Embeds/Boolean";
import { Color } from "../../../../../components/Embeds/Color";
import { Enum } from "../../../../../components/Embeds/Enum";
import { GenerateURL } from "../../../../../components/Embeds/GenerateURL";
import { Import } from "../../../../../components/Embeds/Import";
import { PreviewWrapper } from "../../../../../components/Embeds/PreviewWrapper";
import { Radius } from "../../../../../components/Embeds/Radius";
import { RichText } from "../../../../../components/Embeds/RichText";
import { Size } from "../../../../../components/Embeds/Size";
import { SizeWithout0 } from "../../../../../components/Embeds/SizeWithout0";
import { Slider } from "../../../../../components/Embeds/Slider";
import { TextController } from "../../../../../components/Embeds/TextController";
import { PageWrapper } from "../../../../../components/PageWrapper";
import {
embedConfigurations,
extractEmbedConfigDefaults,
} from "../../../../constants/embeds";
import { NAVBAR_HEIGHT } from "../../../../constants/navbar";
} from "../../../../../constants/embeds";
import { NAVBAR_HEIGHT } from "../../../../../constants/navbar";
import {
LocaleProvider,
useLocale,
type LocaleAcceptorProps,
} from "../../../../hooks/useLocale";
import { EmbedState, type EmbedConfig } from "../../../../stores/embedState";
import { EmbedItemType } from "../../../../stores/embedState/constants";
} from "../../../../../hooks/useLocale";
import { EmbedState, type EmbedConfig } from "../../../../../stores/embedState";
import { EmbedItemType } from "../../../../../stores/embedState/constants";
export interface EmbedPreviewControllerProps {
configKey: string;
@@ -58,7 +58,7 @@ export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
function Content({ embed }: PageProps) {
const config = embedConfigurations[embed] as EmbedConfig;
const [backgroundImage, setBackgroundImage] = useState(
"/assets/images/backgrounds/embed-default.webp"
"/assets/images/backgrounds/embed-default.webp",
);
const fileInput = useRef<HTMLInputElement>(null!);
const { strings } = useLocale();
@@ -1,22 +1,22 @@
import { Box } from "@radix-ui/themes";
import { useMemo } from "react";
import { parse } from "urlon";
import { BlitzKitTheme } from "../../../../../components/BlitzKitTheme";
import { BlitzKitTheme } from "../../../../../../components/BlitzKitTheme";
import {
embedConfigurations,
embedRenderers,
extractEmbedConfigDefaults,
} from "../../../../../constants/embeds";
} from "../../../../../../constants/embeds";
import {
LocaleProvider,
type LocaleAcceptorProps,
} from "../../../../../hooks/useLocale";
} from "../../../../../../hooks/useLocale";
import {
EmbedState,
type EmbedConfig,
type ExtractEmbedConfigTypes,
} from "../../../../../stores/embedState";
import { useEmbedStateCurry } from "../../../../../stores/embedState/utilities";
} from "../../../../../../stores/embedState";
import { useEmbedStateCurry } from "../../../../../../stores/embedState/utilities";
interface PageProps {
embed: keyof typeof embedConfigurations;
@@ -1,5 +1,5 @@
---
import EmbedLayout from "../../../../../layouts/EmbedLayout.astro";
import EmbedLayout from "../../../../../../layouts/EmbedLayout.astro";
import type { getStaticPaths } from "../index.astro";
import { Page } from "./_index";
@@ -2,8 +2,8 @@
import { AstroSeo } from "@astrolib/seo";
import type { GetStaticPaths } from "astro";
import { capitalize } from "lodash-es";
import { embedConfigurations } from "../../../../constants/embeds";
import BaseLayout from "../../../../layouts/BaseLayout.astro";
import { embedConfigurations } from "../../../../../constants/embeds";
import BaseLayout from "../../../../../layouts/BaseLayout.astro";
import { getStaticPaths as parentGetStaticPaths } from "../index.astro";
import { Page } from "./_index";
@@ -17,7 +17,7 @@ export const getStaticPaths = (() => {
...parent.params,
embed: embed as keyof typeof embedConfigurations,
},
}))
})),
)
.flat();
}) satisfies GetStaticPaths;
@@ -1,14 +1,14 @@
import { Box, Flex, Link, Text } from "@radix-ui/themes";
import { PreviewWrapper } from "../../../components/Embeds/PreviewWrapper";
import { LinkI18n } from "../../../components/LinkI18n";
import { PageWrapper } from "../../../components/PageWrapper";
import { embedConfigurations } from "../../../constants/embeds";
import { Var } from "../../../core/radix/var";
import { PreviewWrapper } from "../../../../components/Embeds/PreviewWrapper";
import { LinkI18n } from "../../../../components/LinkI18n";
import { PageWrapper } from "../../../../components/PageWrapper";
import { embedConfigurations } from "../../../../constants/embeds";
import { Var } from "../../../../core/radix/var";
import {
type LocaleAcceptorProps,
LocaleProvider,
useLocale,
} from "../../../hooks/useLocale";
} from "../../../../hooks/useLocale";
export function Page({ locale }: LocaleAcceptorProps) {
return (
@@ -1,6 +1,6 @@
---
import { tools } from "../../../constants/tools";
import ToolLayout from "../../../layouts/ToolLayout.astro";
import { tools } from "../../../../constants/tools";
import ToolLayout from "../../../../layouts/ToolLayout.astro";
import { Page } from "./_index";
export { getStaticPaths } from "../index.astro";
@@ -33,21 +33,21 @@ import {
} from "@radix-ui/themes";
import { debounce } from "lodash-es";
import { useEffect, useMemo, useRef, useState } from "react";
import { PageWrapper } from "../../../components/PageWrapper";
import { StickyRowHeaderCell } from "../../../components/StickyRowHeaderCell";
import { StickyTableRoot } from "../../../components/StickyTableRoot";
import { TankRowHeaderCell } from "../../../components/TankRowHeaderCell";
import { awaitableAverageDefinitions } from "../../../core/awaitables/averageDefinitions";
import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions";
import { PageWrapper } from "../../../../components/PageWrapper";
import { StickyRowHeaderCell } from "../../../../components/StickyRowHeaderCell";
import { StickyTableRoot } from "../../../../components/StickyTableRoot";
import { TankRowHeaderCell } from "../../../../components/TankRowHeaderCell";
import { awaitableAverageDefinitions } from "../../../../core/awaitables/averageDefinitions";
import { api } from "../../../../core/blitzkit/api";
import {
type LocaleAcceptorProps,
LocaleProvider,
useLocale,
} from "../../../hooks/useLocale";
import { Session, type SessionTracking } from "../../../stores/session";
} from "../../../../hooks/useLocale";
import { Session, type SessionTracking } from "../../../../stores/session";
const [tankDefinitions, averageDefinitions] = await Promise.all([
awaitableTankDefinitions,
api.tankDefinitions(),
awaitableAverageDefinitions,
]);
@@ -1,6 +1,6 @@
---
import { tools } from "../../../constants/tools";
import ToolLayout from "../../../layouts/ToolLayout.astro";
import { tools } from "../../../../constants/tools";
import ToolLayout from "../../../../layouts/ToolLayout.astro";
import type { getStaticPaths } from "../index.astro";
import { Page } from "./_index";
@@ -1,12 +1,12 @@
import { Flex, Switch, Text } from "@radix-ui/themes";
import { LocaleSwitcher } from "../../../components/LocaleSwitcher";
import { PageWrapper } from "../../../components/PageWrapper";
import { LocaleSwitcher } from "../../../../components/LocaleSwitcher";
import { PageWrapper } from "../../../../components/PageWrapper";
import {
LocaleProvider,
useLocale,
type LocaleAcceptorProps,
} from "../../../hooks/useLocale";
import { App } from "../../../stores/app";
} from "../../../../hooks/useLocale";
import { App } from "../../../../stores/app";
export function Page({ locale }: LocaleAcceptorProps) {
return (
@@ -1,7 +1,7 @@
---
import { AstroSeo } from "@astrolib/seo";
import { getStrings } from "../../../core/i18n/getStrings";
import BaseLayout from "../../../layouts/BaseLayout.astro";
import { getStrings } from "../../../../core/i18n/getStrings";
import BaseLayout from "../../../../layouts/BaseLayout.astro";
import { Page } from "./_index";
export { getStaticPaths } from "../index.astro";
@@ -1,13 +1,13 @@
import { PageWrapper } from "../../../components/PageWrapper";
import { FilterControl } from "../../../components/TankSearch/components/FilterControl";
import { TierListControls } from "../../../components/TierList/Controls";
import { TierListTable } from "../../../components/TierList/Table";
import { TierListTiles } from "../../../components/TierList/Tiles";
import { URLManager } from "../../../components/TierList/URLManager";
import { PageWrapper } from "../../../../components/PageWrapper";
import { FilterControl } from "../../../../components/TankSearch/components/FilterControl";
import { TierListControls } from "../../../../components/TierList/Controls";
import { TierListTable } from "../../../../components/TierList/Table";
import { TierListTiles } from "../../../../components/TierList/Tiles";
import { URLManager } from "../../../../components/TierList/URLManager";
import {
LocaleProvider,
type LocaleAcceptorProps,
} from "../../../hooks/useLocale";
} from "../../../../hooks/useLocale";
export function Page({ locale }: LocaleAcceptorProps) {
return (
@@ -1,6 +1,6 @@
---
import { tools } from "../../../constants/tools";
import ToolLayout from "../../../layouts/ToolLayout.astro";
import { tools } from "../../../../constants/tools";
import ToolLayout from "../../../../layouts/ToolLayout.astro";
import { Page } from "./_index";
export { getStaticPaths } from "../index.astro";
@@ -1,5 +1,5 @@
---
import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro";
import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro";
export { getStaticPaths } from "../../index.astro";
@@ -1,7 +1,7 @@
---
import type { GetStaticPaths } from "astro";
import { embedConfigurations } from "../../../../../constants/embeds";
import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro";
import { embedConfigurations } from "../../../../../../constants/embeds";
import LegacyRedirect from "../../../../../../layouts/LegacyRedirect.astro";
import { getStaticPaths as parentGetStaticPaths } from "../index.astro";
export const getStaticPaths = (() => {
@@ -14,7 +14,7 @@ export const getStaticPaths = (() => {
...parent.params,
embed: embed as keyof typeof embedConfigurations,
},
}))
})),
)
.flat();
}) satisfies GetStaticPaths;
@@ -1,5 +1,5 @@
---
import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro";
import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro";
export { getStaticPaths } from "../../index.astro";
@@ -1,5 +1,5 @@
---
import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro";
import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro";
export { getStaticPaths } from "../../index.astro";
@@ -1,5 +1,5 @@
---
import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro";
import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro";
export { getStaticPaths } from "../../index.astro";
@@ -1,5 +1,5 @@
---
import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro";
import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro";
export { getStaticPaths } from "../../index.astro";
@@ -1,11 +1,11 @@
---
import { fetchTankDefinitions } from "@blitzkit/core";
import type { GetStaticPaths } from "astro";
import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro";
import { api } from "../../../../../../core/blitzkit/api";
import LegacyRedirect from "../../../../../../layouts/LegacyRedirect.astro";
import { getStaticPaths as parentGetStaticPaths } from "../index.astro";
export const getStaticPaths = (async () => {
const tankDefinitions = await fetchTankDefinitions();
const tankDefinitions = await api.tankDefinitions();
const parentPaths = parentGetStaticPaths();
return Object.values(tankDefinitions.tanks)
@@ -20,7 +20,7 @@ export const getStaticPaths = (async () => {
const { id } = Astro.params;
const { locale } = Astro.props;
const tankDefinitions = await fetchTankDefinitions();
const tankDefinitions = await api.tankDefinitions();
const tank = tankDefinitions.tanks[id];
---
@@ -1,5 +1,5 @@
---
import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro";
import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro";
export { getStaticPaths } from "../../index.astro";
@@ -1,5 +1,5 @@
---
import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro";
import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro";
export { getStaticPaths } from "../../index.astro";
@@ -1,8 +1,15 @@
import type { TextProps } from '@radix-ui/themes';
import { EmbedState, type EmbedConfig, type ExtractEmbedConfigTypes } from '.';
import type { EmbedConfigItemType, EmbedItemType } from './constants';
import type { TextProps } from "@radix-ui/themes";
import { EmbedState, type EmbedConfig, type ExtractEmbedConfigTypes } from ".";
import {
breakdownConfig,
extractEmbedConfigDefaults,
} from "../../constants/embeds";
import type { EmbedConfigItemType, EmbedItemType } from "./constants";
export function useEmbedStateCurry<Config extends EmbedConfig>() {
// TODO: find a way to initialize with the correct state preset
EmbedState.useInitialization(extractEmbedConfigDefaults(breakdownConfig));
function useEmbedState<Key extends keyof Config>(key: Key) {
return EmbedState.use((state) => {
return (state as ExtractEmbedConfigTypes<Config>)[key];
@@ -18,7 +25,7 @@ export function useEmbedStateCurry<Config extends EmbedConfig>() {
>(key: Key) {
const { color, ...state } = useEmbedState(
key,
) as EmbedConfigItemType<EmbedItemType.RichText>['default'];
) as EmbedConfigItemType<EmbedItemType.RichText>["default"];
return {
style: { color },