mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
aliases
This commit is contained in:
1 parent
13cda7aec0
commit
b26c5137f3
53 files changed
+236
-932
No files matched your search
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"nodes": {},
|
||||
"aliases": {
|
||||
"api": "/api",
|
||||
"api": "https://api.beta.blitzkit.app",
|
||||
"static": "https://static.blitzkit.app"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { LEAGUES } from "./rating";
|
||||
|
||||
export function getLeagueFromScore(score: number) {
|
||||
return LEAGUES.find(({ minScore }) => score >= minScore)!;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { context } from "../blitzkit/context";
|
||||
import { patientFetch } from "../blitzkit/patientFetch";
|
||||
import type { RatingLeague, RatingReward } from "./rating";
|
||||
import type { Region } from "./regions";
|
||||
import { regionToRegionSubdomain } from "./regionToRegionSubdomain";
|
||||
|
||||
export type RatingInfo =
|
||||
| {
|
||||
detail: undefined;
|
||||
|
||||
title: string;
|
||||
icon: string | null;
|
||||
start_at: string;
|
||||
finish_at: string;
|
||||
current_season: number;
|
||||
updated_at: string;
|
||||
count: number;
|
||||
|
||||
rewards: RatingReward[];
|
||||
leagues: RatingLeague[];
|
||||
}
|
||||
| { detail: { error: string } };
|
||||
|
||||
const cache: Partial<Record<Region, RatingInfo>> = {};
|
||||
|
||||
export async function getRatingInfo(region: Region) {
|
||||
if (cache[region]) return cache[region]!;
|
||||
|
||||
const response = await patientFetch(
|
||||
context === "website"
|
||||
? `/api/${region}/rating/current/info`
|
||||
: `https://${regionToRegionSubdomain(
|
||||
region
|
||||
)}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
||||
undefined
|
||||
);
|
||||
const data = (await response.json()) as RatingInfo;
|
||||
|
||||
cache[region] = data;
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { context } from "../blitzkit/context";
|
||||
import { patientFetch } from "../blitzkit/patientFetch";
|
||||
import type { RatingPlayer } from "../statistics";
|
||||
import type { LeagueTop } from "./rating";
|
||||
import type { Region } from "./regions";
|
||||
import { regionToRegionSubdomain } from "./regionToRegionSubdomain";
|
||||
|
||||
export async function getRatingLeague(
|
||||
region: Region,
|
||||
league: number,
|
||||
usePatientFetcher = false
|
||||
) {
|
||||
const fetcher = (url: string) =>
|
||||
usePatientFetcher
|
||||
? patientFetch(url, undefined, { cache: "no-store" })
|
||||
: fetch(url, { cache: "no-store" });
|
||||
const response = await fetcher(
|
||||
context === "website"
|
||||
? `/api/${region}/rating/current/league/${league}`
|
||||
: `https://${regionToRegionSubdomain(
|
||||
region
|
||||
)}.wotblitz.com/en/api/rating-leaderboards/league/${league}/top/`
|
||||
);
|
||||
const json = await response.json();
|
||||
|
||||
return context === "website"
|
||||
? (json as RatingPlayer[])
|
||||
: (json as LeagueTop).result;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { patientFetch } from "../blitzkit/patientFetch";
|
||||
import { withCORSProxy } from "../blitzkit/withCORSProxy";
|
||||
import type { RatingNeighbors } from "./rating";
|
||||
import type { Region } from "./regions";
|
||||
import { regionToRegionSubdomain } from "./regionToRegionSubdomain";
|
||||
|
||||
export async function getRatingNeighbors(
|
||||
region: Region,
|
||||
id: number,
|
||||
count: number,
|
||||
usePatientFetcher = false
|
||||
) {
|
||||
const fetcher = (url: string) =>
|
||||
usePatientFetcher
|
||||
? patientFetch(url, undefined, { cache: "no-store" })
|
||||
: fetch(url, { cache: "no-store" });
|
||||
const response = await fetcher(
|
||||
withCORSProxy(
|
||||
`https://${regionToRegionSubdomain(
|
||||
region
|
||||
)}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`
|
||||
)
|
||||
);
|
||||
const json = (await response.json()) as RatingNeighbors;
|
||||
|
||||
return json;
|
||||
}
|
||||
@@ -10,13 +10,8 @@ export * from "./getAccountAchievements";
|
||||
export * from "./getAccountInfo";
|
||||
export * from "./getClanAccountInfo";
|
||||
export * from "./getClanInfo";
|
||||
export * from "./getLeagueFromScore";
|
||||
export * from "./getRatingInfo";
|
||||
export * from "./getRatingLeague";
|
||||
export * from "./getRatingNeighbors";
|
||||
export * from "./getTankStats";
|
||||
export * from "./idToRegion";
|
||||
export * from "./isOnGoingRatingSeason";
|
||||
export * from "./isValidBlitzId";
|
||||
export * from "./nations";
|
||||
export * from "./normalizeExtra";
|
||||
@@ -28,6 +23,5 @@ export * from "./regions";
|
||||
export * from "./regionSubdomainToRegion";
|
||||
export * from "./regionToRegionSubdomain";
|
||||
export * from "./searchClansAcrossRegions";
|
||||
export * from "./searchCurrentRatingPlayers";
|
||||
export * from "./searchPlayersAcrossRegions";
|
||||
export * from "./toUniqueId";
|
||||
@@ -1,6 +0,0 @@
|
||||
import { getRatingInfo } from "./getRatingInfo";
|
||||
|
||||
export async function isOnGoingRatingSeason() {
|
||||
const ratingInfo = await getRatingInfo("com");
|
||||
return ratingInfo.detail === undefined;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { context } from "../blitzkit/context";
|
||||
import { type Region } from "./regions";
|
||||
import { regionToRegionSubdomain } from "./regionToRegionSubdomain";
|
||||
|
||||
type CurrentRatingPlayerSearch = Record<
|
||||
number,
|
||||
{
|
||||
spa_id: number;
|
||||
nickname: string;
|
||||
clan_tag: string;
|
||||
} & (
|
||||
| {
|
||||
skip: true;
|
||||
}
|
||||
| {
|
||||
skip: false;
|
||||
mmr: number;
|
||||
season_number: number;
|
||||
calibrationBattlesLeft: number;
|
||||
number: number;
|
||||
updated_at: string;
|
||||
neighbors: [];
|
||||
}
|
||||
)
|
||||
>;
|
||||
|
||||
export async function searchCurrentRatingPlayers(region: Region, name: string) {
|
||||
const encodedSearch = encodeURIComponent(name);
|
||||
const response = await fetch(
|
||||
context === "website"
|
||||
? `/api/${region}/rating/current/search/${encodedSearch}`
|
||||
: `https://${regionToRegionSubdomain(
|
||||
region
|
||||
)}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`,
|
||||
{ cache: "no-store" }
|
||||
);
|
||||
const accountList = (await response.json()) as CurrentRatingPlayerSearch;
|
||||
|
||||
return accountList;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { environmentConfig } from "@blitzkit/core";
|
||||
|
||||
export function alias(alias: string, path: string) {
|
||||
const environment = environmentConfig();
|
||||
|
||||
if (!(alias in environment.aliases)) {
|
||||
throw new Error(`Invalid alias "${alias}"`);
|
||||
}
|
||||
|
||||
const base = environment.aliases[alias];
|
||||
|
||||
return `${base}${path}`;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { fetchPB } from "../../protobuf";
|
||||
import {
|
||||
CamouflageDefinitions,
|
||||
@@ -14,38 +15,50 @@ import { BlitzKitAPI } from "./base";
|
||||
|
||||
export class ClientBlitzKitAPI extends BlitzKitAPI {
|
||||
gameDefinitions() {
|
||||
return fetchPB("/api/definitions/game.pb", GameDefinitions);
|
||||
return fetchPB(alias("api", "/definitions/game.pb"), GameDefinitions);
|
||||
}
|
||||
|
||||
consumableDefinitions() {
|
||||
return fetchPB("/api/definitions/consumables.pb", ConsumableDefinitions);
|
||||
return fetchPB(
|
||||
alias("api", "/definitions/consumables.pb"),
|
||||
ConsumableDefinitions,
|
||||
);
|
||||
}
|
||||
|
||||
tankDefinitions() {
|
||||
return fetchPB("/api/definitions/tanks.pb", TankDefinitions);
|
||||
return fetchPB(alias("api", "/definitions/tanks.pb"), TankDefinitions);
|
||||
}
|
||||
|
||||
camouflageDefinitions() {
|
||||
return fetchPB("/api/definitions/camouflage.pb", CamouflageDefinitions);
|
||||
return fetchPB(
|
||||
alias("api", "/definitions/camouflage.pb"),
|
||||
CamouflageDefinitions,
|
||||
);
|
||||
}
|
||||
|
||||
provisionDefinitions() {
|
||||
return fetchPB("/api/definitions/provisions.pb", ProvisionDefinitions);
|
||||
return fetchPB(
|
||||
alias("api", "/definitions/provisions.pb"),
|
||||
ProvisionDefinitions,
|
||||
);
|
||||
}
|
||||
|
||||
modelDefinitions() {
|
||||
return fetchPB("/api/definitions/models.pb", ModelDefinitions);
|
||||
return fetchPB(alias("api", "/definitions/models.pb"), ModelDefinitions);
|
||||
}
|
||||
|
||||
skillDefinitions() {
|
||||
return fetchPB("/api/definitions/skills.pb", SkillDefinitions);
|
||||
return fetchPB(alias("api", "/definitions/skills.pb"), SkillDefinitions);
|
||||
}
|
||||
|
||||
equipmentDefinitions() {
|
||||
return fetchPB("/api/definitions/equipment.pb", EquipmentDefinitions);
|
||||
return fetchPB(
|
||||
alias("api", "/definitions/equipment.pb"),
|
||||
EquipmentDefinitions,
|
||||
);
|
||||
}
|
||||
|
||||
galleryDefinitions() {
|
||||
return fetchPB("/api/definitions/gallery.pb", Gallery);
|
||||
return fetchPB(alias("api", "/definitions/gallery.pb"), Gallery);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
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";
|
||||
import { ServerBlitzKitAPI9 } from "./9_gameDefinitions";
|
||||
|
||||
export abstract class ServerBlitzKitAPI10 extends ServerBlitzKitAPI9 {
|
||||
@Cache()
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { assertSecret } from "@blitzkit/core";
|
||||
|
||||
interface EnvironmentConfig {
|
||||
paths: Record<string, string | null>;
|
||||
nodes: Record<string, string>;
|
||||
aliases: Record<string, string>;
|
||||
}
|
||||
|
||||
export const environments = import.meta.glob(
|
||||
"../../../../environments/*.json",
|
||||
{
|
||||
eager: true,
|
||||
import: "default",
|
||||
},
|
||||
) as Record<string, EnvironmentConfig>;
|
||||
const root = "../../../../environments";
|
||||
|
||||
for (const key in environments) {
|
||||
const trimmed = key.slice(root.length + 1, -5);
|
||||
|
||||
environments[trimmed] = environments[key];
|
||||
delete environments[key];
|
||||
}
|
||||
|
||||
export function environmentConfig() {
|
||||
const environment = assertSecret(import.meta.env.ENVIRONMENT);
|
||||
|
||||
if (!(environment in environments)) {
|
||||
throw new Error(`Invalid environment "${environment}"`);
|
||||
}
|
||||
|
||||
return environments[environment];
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { getRatingInfo } from "../blitz/getRatingInfo";
|
||||
import { assertSecret } from "./assertSecret";
|
||||
|
||||
interface GitHubTrees {
|
||||
tree: { path: string; url: string }[];
|
||||
}
|
||||
|
||||
let cachedLatestArchivedSeasonNumber: number | null = null;
|
||||
|
||||
export async function getArchivedLatestSeasonNumber() {
|
||||
if (cachedLatestArchivedSeasonNumber !== null) {
|
||||
return cachedLatestArchivedSeasonNumber;
|
||||
}
|
||||
|
||||
const ratingInfo = await getRatingInfo("com");
|
||||
|
||||
if (ratingInfo.detail) {
|
||||
const regionsURL = (
|
||||
(await fetch(
|
||||
`https://api.github.com/repos/${assertSecret(
|
||||
import.meta.env.PUBLIC_ASSET_REPO
|
||||
)}/git/trees/main`
|
||||
).then((response) => response.json())) as GitHubTrees
|
||||
).tree.find(({ path }) => path === "regions")!.url;
|
||||
const comURL = (
|
||||
(await fetch(regionsURL).then((response) =>
|
||||
response.json()
|
||||
)) as GitHubTrees
|
||||
).tree.find(({ path }) => path === "com")!.url;
|
||||
const ratingURL = (
|
||||
(await fetch(comURL).then((response) => response.json())) as {
|
||||
tree: [{ url: string }];
|
||||
}
|
||||
).tree[0].url;
|
||||
const latestSeason = Math.max(
|
||||
...(
|
||||
(await fetch(ratingURL).then((response) => response.json())) as {
|
||||
tree: { path: string }[];
|
||||
}
|
||||
).tree.map(({ path }) => Number(path))
|
||||
);
|
||||
|
||||
cachedLatestArchivedSeasonNumber = latestSeason;
|
||||
} else {
|
||||
cachedLatestArchivedSeasonNumber = ratingInfo.current_season - 1;
|
||||
}
|
||||
|
||||
return cachedLatestArchivedSeasonNumber;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// import { RatingInfo, Region, asset } from '@blitzkit/core';
|
||||
|
||||
// const cache: Record<
|
||||
// Region,
|
||||
// Record<
|
||||
// number,
|
||||
// RatingInfo & {
|
||||
// detail: undefined;
|
||||
// }
|
||||
// >
|
||||
// > = {
|
||||
// com: {},
|
||||
// eu: {},
|
||||
// asia: {},
|
||||
// };
|
||||
|
||||
// export async function getArchivedRatingInfo(region: Region, season: number) {
|
||||
// if (!cache[region][season]) {
|
||||
// const info = await fetch(
|
||||
// asset(`regions/${region}/rating/${season}/info.cdon.lz4`),
|
||||
// )
|
||||
// .then((response) => response.arrayBuffer())
|
||||
// .then((buffer) =>
|
||||
// superDecompress<RatingInfo & { detail: undefined }>(buffer),
|
||||
// );
|
||||
// cache[region][season] = info;
|
||||
// }
|
||||
|
||||
// return cache[region][season];
|
||||
// }
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./alias";
|
||||
export * from "./assertSecret";
|
||||
export * from "./availableProvisions";
|
||||
export * from "./averageDefinitions";
|
||||
@@ -7,6 +8,7 @@ export * from "./createDefaultSkills";
|
||||
export * from "./degressiveStat";
|
||||
export * from "./discoveredIdDefinitions";
|
||||
export * from "./emblemURL";
|
||||
export * from "./environment";
|
||||
export * from "./eventManager";
|
||||
export * from "./getTimeDaysAgo";
|
||||
export * from "./isExplosive";
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
|
||||
export const BLITZKIT_TANK_ICON_SIZE = { width: 40 * 16, height: 30 * 16 };
|
||||
|
||||
export function tankIcon(id: number, size: "big" | "small" = "big") {
|
||||
return `/api/tanks/${id}/icons/${size}.webp`;
|
||||
return alias("api", `/tanks/${id}/icons/${size}.webp`);
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
import {
|
||||
getAccountInfo,
|
||||
patientFetchJSON,
|
||||
RatingInfo,
|
||||
RatingLeaderboard,
|
||||
RatingLeaderboardEntryV1,
|
||||
RatingLeaderboardEntryV2,
|
||||
RatingNeighbors,
|
||||
RatingPlayer,
|
||||
Region,
|
||||
REGIONS,
|
||||
regionToRegionSubdomain,
|
||||
} from "@blitzkit/core";
|
||||
import { chunk } from "lodash-es";
|
||||
import { argv } from "process";
|
||||
import { AssetUploader } from "./core/github/assetUploader";
|
||||
|
||||
const region = argv
|
||||
.find((arg) => arg.startsWith("--region="))
|
||||
?.split("=")[1] as Region | undefined;
|
||||
|
||||
if (region === undefined || !REGIONS.includes(region)) {
|
||||
throw new Error("No valid region specified");
|
||||
}
|
||||
|
||||
console.log(`Building rating leaderboard for ${region}`);
|
||||
|
||||
const regionSubdomain = regionToRegionSubdomain(region);
|
||||
const NEIGHBORS = 2 ** 8;
|
||||
const PLAYERS = await patientFetchJSON<RatingInfo>(
|
||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`
|
||||
).then((data) => (data.detail === undefined ? data.count : undefined));
|
||||
|
||||
if (PLAYERS === undefined) {
|
||||
throw new Error("No current rating season running");
|
||||
}
|
||||
|
||||
const players: Record<number, RatingPlayer> = {};
|
||||
const entriesV1: RatingLeaderboardEntryV1[] = [];
|
||||
|
||||
let registered = 0;
|
||||
|
||||
async function branchFromPlayer(id: number) {
|
||||
const { neighbors } = await patientFetchJSON<RatingNeighbors>(
|
||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`
|
||||
);
|
||||
|
||||
const firstPlayer = neighbors[0];
|
||||
const lastPlayer = neighbors[neighbors.length - 1];
|
||||
|
||||
const branches: Promise<void>[] = [];
|
||||
if (!players[firstPlayer.number])
|
||||
branches.push(branchFromPlayer(firstPlayer.spa_id));
|
||||
if (!players[lastPlayer.number])
|
||||
branches.push(branchFromPlayer(lastPlayer.spa_id));
|
||||
|
||||
neighbors.forEach((neighbor) => {
|
||||
if (players[neighbor.number]) return;
|
||||
|
||||
players[neighbor.number] = neighbor;
|
||||
registered++;
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Registered ${registered} / ${PLAYERS} (${(
|
||||
(registered / PLAYERS!) *
|
||||
100
|
||||
).toFixed(2)}%)`
|
||||
);
|
||||
|
||||
await Promise.all(branches);
|
||||
}
|
||||
|
||||
async function branchFromLeague(id: number) {
|
||||
const { result } = await patientFetchJSON<{ result: RatingPlayer[] }>(
|
||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`
|
||||
);
|
||||
|
||||
const firstPlayer = result[0];
|
||||
const lastPlayer = result.at(-1)!;
|
||||
|
||||
await Promise.all([
|
||||
branchFromPlayer(firstPlayer.spa_id),
|
||||
branchFromPlayer(lastPlayer.spa_id),
|
||||
]);
|
||||
}
|
||||
|
||||
console.log("Branching from leagues...");
|
||||
await Promise.all([
|
||||
branchFromLeague(0),
|
||||
branchFromLeague(1),
|
||||
branchFromLeague(2),
|
||||
branchFromLeague(3),
|
||||
branchFromLeague(4),
|
||||
]);
|
||||
|
||||
console.log("Converting to an array...");
|
||||
for (let index = 0; index < PLAYERS; index++) {
|
||||
const listing = players[index + 1];
|
||||
if (listing) {
|
||||
entriesV1[index] = { id: listing.spa_id, score: listing.score };
|
||||
} else {
|
||||
console.warn(`Position ${index + 1} had no player`);
|
||||
entriesV1[index] = { id: 0, score: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Requesting further stats...");
|
||||
const entries: RatingLeaderboardEntryV2[] = [];
|
||||
let chunkIndex = 0;
|
||||
for (const leaderboardChunk of chunk(entriesV1, 100)) {
|
||||
console.log(
|
||||
`Chunk ${chunkIndex + 1} of ${Math.ceil(entriesV1.length / 100)}`
|
||||
);
|
||||
|
||||
const stats = await getAccountInfo(
|
||||
region,
|
||||
leaderboardChunk.map(({ id }) => id),
|
||||
["statistics.rating"]
|
||||
);
|
||||
|
||||
stats.forEach((stat, index) => {
|
||||
const trueIndex = chunkIndex * 100 + index;
|
||||
|
||||
if (
|
||||
leaderboardChunk[index].id === 0 ||
|
||||
stat?.statistics.rating === undefined
|
||||
) {
|
||||
entries[trueIndex] = {
|
||||
battles: 0,
|
||||
damage: 0,
|
||||
id: 0,
|
||||
kills: 0,
|
||||
score: 0,
|
||||
survived: 0,
|
||||
wins: 0,
|
||||
};
|
||||
} else {
|
||||
entries[trueIndex] = {
|
||||
id: leaderboardChunk[index].id,
|
||||
score: leaderboardChunk[index].score,
|
||||
battles: stat.statistics.rating.battles,
|
||||
damage: stat.statistics.rating.damage_dealt,
|
||||
kills: stat.statistics.rating.frags,
|
||||
survived: stat.statistics.rating.survived_battles,
|
||||
wins: stat.statistics.rating.wins,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
chunkIndex++;
|
||||
}
|
||||
|
||||
const info = await patientFetchJSON<RatingInfo & { detail: undefined }>(
|
||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`
|
||||
);
|
||||
const normalizedServer = regionSubdomain === "na" ? "com" : regionSubdomain;
|
||||
const content = RatingLeaderboard.encode({
|
||||
version: { $case: "v2", value: { entries } },
|
||||
}).finish();
|
||||
|
||||
using uploader = new AssetUploader(`rating leaderboard ${regionSubdomain}`);
|
||||
|
||||
await uploader.add({
|
||||
path: `regions/${normalizedServer}/rating/${info.current_season}.pb`,
|
||||
content,
|
||||
});
|
||||
|
||||
await uploader.flush();
|
||||
@@ -1,39 +0,0 @@
|
||||
import decompress from "decompress";
|
||||
import { existsSync } from "fs";
|
||||
import { mkdir, readdir, writeFile } from "fs/promises";
|
||||
|
||||
if (!existsSync("../../temp")) {
|
||||
console.log("Creating temp directory...");
|
||||
await mkdir("../../temp");
|
||||
}
|
||||
|
||||
console.log("Downloading depot downloader...");
|
||||
await fetch(
|
||||
"https://api.github.com/repos/SteamRE/DepotDownloader/releases/latest"
|
||||
)
|
||||
.then(
|
||||
(response) =>
|
||||
response.json() as Promise<{
|
||||
assets: { browser_download_url: string; name: string }[];
|
||||
}>
|
||||
)
|
||||
.then(({ assets }) => {
|
||||
const asset = assets.find(({ name }) => name.includes("linux-x64"));
|
||||
if (!asset) throw new Error("No asset found");
|
||||
return fetch(asset.browser_download_url);
|
||||
})
|
||||
.then((response) => response.arrayBuffer())
|
||||
.then((arrayBuffer) =>
|
||||
writeFile("../../temp/depotDownloader.zip", new Uint8Array(arrayBuffer))
|
||||
);
|
||||
|
||||
console.log("Unzipping...");
|
||||
await decompress(
|
||||
"../../temp/depotDownloader.zip",
|
||||
"../../temp/depotDownloader"
|
||||
);
|
||||
|
||||
console.log(
|
||||
"Download complete; files:",
|
||||
await readdir("../../temp/depotDownloader")
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { Flex, Heading, IconButton, Table } from "@radix-ui/themes";
|
||||
import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
@@ -57,7 +58,10 @@ export function Body({ stats }: BodyProps) {
|
||||
alt={unwrap(thisShell.name!)}
|
||||
width={16}
|
||||
height={16}
|
||||
src={`/api/icons/shells/${thisShell.icon}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/shells/${thisShell.icon}.webp`,
|
||||
)}
|
||||
/>
|
||||
</IconButton>
|
||||
))}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { availableProvisions, permanentSkills } from "@blitzkit/core";
|
||||
import { alias, availableProvisions, permanentSkills } from "@blitzkit/core";
|
||||
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
|
||||
import { ComponentPlaceholderIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
@@ -231,7 +231,10 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
alt={module}
|
||||
width={24}
|
||||
height={24}
|
||||
src={`/api/icons/modules/${module}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/modules/${module}.webp`,
|
||||
)}
|
||||
style={{
|
||||
marginLeft: index > 0 ? -8 : 0,
|
||||
objectFit: "contain",
|
||||
@@ -320,7 +323,10 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
.name!,
|
||||
)}
|
||||
key={provision}
|
||||
src={`/api//icons/provisions/${provision}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`//icons/provisions/${provision}.webp`,
|
||||
)}
|
||||
style={{
|
||||
left: "50%",
|
||||
/**
|
||||
@@ -525,7 +531,10 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
].name!,
|
||||
)}
|
||||
key={consumable}
|
||||
src={`/api/icons/consumables/${consumable}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/consumables/${consumable}.webp`,
|
||||
)}
|
||||
style={{
|
||||
left: "50%",
|
||||
/**
|
||||
@@ -617,7 +626,7 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
variant={camouflage ? "solid" : "outline"}
|
||||
>
|
||||
<img
|
||||
src={`/api/icons/camo.webp`}
|
||||
src={alias("api", `/icons/camo.webp`)}
|
||||
style={{
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { permanentSkills, romanize } from "@blitzkit/core";
|
||||
import { alias, permanentSkills, romanize } from "@blitzkit/core";
|
||||
import { Flex } from "@radix-ui/themes";
|
||||
import { api } from "../core/blitzkit/api";
|
||||
import { GenericTankComponentButton } from "./ModuleButtons/GenericTankComponentButton";
|
||||
@@ -27,7 +27,7 @@ export function CrewSkillManager({
|
||||
special={!permanentSkills.includes(skill)}
|
||||
selected={level > 0}
|
||||
discriminator={level === 0 ? undefined : romanize(level)}
|
||||
icon={`/api/icons/skills/${skill}.webp`}
|
||||
icon={alias("api", `/icons/skills/${skill}.webp`)}
|
||||
onClick={(event) => {
|
||||
if (!onChange) return;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Avatar } from "@blitzkit/core";
|
||||
import { alias, Avatar } from "@blitzkit/core";
|
||||
import { Cross1Icon, DownloadIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -18,7 +18,10 @@ type GalleryCardProps = MaybeSkeletonComponentProps<{
|
||||
}>;
|
||||
|
||||
export function GalleryCard(props: GalleryCardProps) {
|
||||
const src = `/api/gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`;
|
||||
const src = alias(
|
||||
"api",
|
||||
`/gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`,
|
||||
);
|
||||
const card = useRef<HTMLDivElement>(null!);
|
||||
const { unwrap } = useLocale();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
alias,
|
||||
SEARCH_KEYS,
|
||||
TankDefinition,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
@@ -149,7 +150,10 @@ export function Guesser() {
|
||||
prefix={
|
||||
<img
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
src={`/api/flags/circle/${result.nation}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/flags/circle/${result.nation}.webp`,
|
||||
)}
|
||||
/>
|
||||
}
|
||||
discriminator={
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||
|
||||
@@ -8,7 +9,7 @@ interface ModuleProps extends TankComponentButtonProps {
|
||||
export function ModuleButton({ module, ...props }: ModuleProps) {
|
||||
return (
|
||||
<GenericTankComponentButton
|
||||
icon={`/api/icons/modules/${module}.webp`}
|
||||
icon={alias("api", `/icons/modules/${module}.webp`)}
|
||||
iconStyles={{
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { api } from "../../core/blitzkit/api";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||
@@ -15,7 +16,7 @@ export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) {
|
||||
return (
|
||||
<GenericTankComponentButton
|
||||
tooltip={unwrap(provisionDefinitions.provisions[provision].name!)}
|
||||
icon={`/api/icons/provisions/${provision}.webp`}
|
||||
icon={alias("api", `/icons/provisions/${provision}.webp`)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||
|
||||
@@ -8,7 +9,7 @@ interface ShellButtonProps extends TankComponentButtonProps {
|
||||
export function ShellButton({ shell, ...props }: ShellButtonProps) {
|
||||
return (
|
||||
<GenericTankComponentButton
|
||||
icon={`/api/icons/shells/${shell}.webp`}
|
||||
icon={alias("api", `/icons/shells/${shell}.webp`)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
|
||||
interface SmallTankIconProps {
|
||||
id: number;
|
||||
size?: number;
|
||||
@@ -17,7 +19,7 @@ export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) {
|
||||
>
|
||||
<img
|
||||
alt={`Tank ${id}`}
|
||||
src={`/api/tanks/${id}/icons/small.webp`}
|
||||
src={alias("api", `/tanks/${id}/icons/small.webp`)}
|
||||
style={{
|
||||
transform: `scale(${(size / HEIGHT) * 100}%)`,
|
||||
transformOrigin: "top left",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TankType, type TankDefinition } from "@blitzkit/core";
|
||||
import { alias, TankType, type TankDefinition } from "@blitzkit/core";
|
||||
import { Flex, Text, type TextProps } from "@radix-ui/themes";
|
||||
import { uniq } from "lodash-es";
|
||||
import { forwardRef, type ReactNode } from "react";
|
||||
@@ -60,7 +60,10 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
className="tank-search-card"
|
||||
data-provide-link={provideLink}
|
||||
style={{
|
||||
backgroundImage: `url(/api/flags/scratched/${tank.nation}.webp)`,
|
||||
backgroundImage: `url(${alias(
|
||||
"api",
|
||||
`/flags/scratched/${tank.nation}.webp`,
|
||||
)})`,
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
@@ -76,7 +79,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
>
|
||||
<img
|
||||
alt={unwrap(tank.name!)}
|
||||
src={`/api/tanks/${tank.id}/icons/big.webp`}
|
||||
src={alias("api", `/tanks/${tank.id}/icons/big.webp`)}
|
||||
className="image"
|
||||
draggable={false}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
alias,
|
||||
GunDefinition,
|
||||
ShellType,
|
||||
TANK_CLASSES,
|
||||
@@ -309,7 +310,7 @@ function NationsFilter() {
|
||||
width: "1.25em",
|
||||
height: "1.25em",
|
||||
}}
|
||||
src={`/api/flags/circle/${nation}.webp`}
|
||||
src={alias("api", `/flags/circle/${nation}.webp`)}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
@@ -346,7 +347,7 @@ function NationsFilter() {
|
||||
right="0"
|
||||
mr="-2"
|
||||
style={{
|
||||
backgroundImage: `url(${`/api/flags/scratched/${nation}.webp`})`,
|
||||
backgroundImage: `url(${alias("api", `/flags/scratched/${nation}.webp`)})`,
|
||||
backgroundSize: "100%",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "5rem center",
|
||||
@@ -679,9 +680,10 @@ function IndividualShellFilter({
|
||||
{shells[index] !== null && (
|
||||
<img
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
src={`/api/icons/shells/${shellTypeIcons[shells[index]]}${
|
||||
premium ? "_premium" : ""
|
||||
}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/shells/${shellTypeIcons[shells[index]]}${premium ? "_premium" : ""}.webp`,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
@@ -712,9 +714,12 @@ function IndividualShellFilter({
|
||||
color={selected ? undefined : "gray"}
|
||||
>
|
||||
<img
|
||||
src={`/api/icons/shells/${shellTypeIcons[shellType]}${
|
||||
premium ? "_premium" : ""
|
||||
}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/shells/${shellTypeIcons[shellType]}${
|
||||
premium ? "_premium" : ""
|
||||
}.webp`,
|
||||
)}
|
||||
style={{ width: "1.25em", height: "1.25em" }}
|
||||
/>
|
||||
|
||||
@@ -883,7 +888,7 @@ function ConsumablesFilter() {
|
||||
height: "1.25em",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={`/api/icons/consumables/${consumable}.webp`}
|
||||
src={alias("api", `/icons/consumables/${consumable}.webp`)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -928,7 +933,7 @@ function ConsumablesFilter() {
|
||||
height: "1.25em",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={`/api/icons/consumables/${consumable}.webp`}
|
||||
src={alias("api", `/icons/consumables/${consumable}.webp`)}
|
||||
/>
|
||||
|
||||
{unwrap(consumableDefinition.name!)}
|
||||
@@ -977,7 +982,7 @@ function ProvisionsFilter() {
|
||||
height: "1.25em",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={`/api/icons/provisions/${provision}.webp`}
|
||||
src={alias("api", `/icons/provisions/${provision}.webp`)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1022,7 +1027,7 @@ function ProvisionsFilter() {
|
||||
height: "1.25em",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={`/api/icons/provisions/${provision}.webp`}
|
||||
src={alias("api", `/icons/provisions/${provision}.webp`)}
|
||||
/>
|
||||
|
||||
{unwrap(provisionDefinition.name!)}
|
||||
@@ -1151,7 +1156,10 @@ function GameModeAbilitiesFilter() {
|
||||
height: "1.25em",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={`/api/icons/consumables/${consumable}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/consumables/${consumable}.webp`,
|
||||
)}
|
||||
/>
|
||||
|
||||
{unwrap(abilityDefinition.name!)}
|
||||
@@ -1200,7 +1208,10 @@ function GameModeAbilitiesFilter() {
|
||||
height: "1.25em",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={`/api/icons/provisions/${provision}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/provisions/${provision}.webp`,
|
||||
)}
|
||||
/>
|
||||
|
||||
{unwrap(powerDefinition.name!)}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { isExplosive } from "@blitzkit/core";
|
||||
import { alias, isExplosive } from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes";
|
||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||
@@ -62,7 +62,7 @@ export function Firepower({
|
||||
alt={unwrap(thisShell.name!)}
|
||||
width={16}
|
||||
height={16}
|
||||
src={`/api/icons/shells/${thisShell.icon}.webp`}
|
||||
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { Flex, IconButton, Tooltip } from "@radix-ui/themes";
|
||||
import { useLocale } from "../../../../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../../../../stores/duel";
|
||||
@@ -37,7 +38,7 @@ export function ShellSwitcher() {
|
||||
<img
|
||||
alt={unwrap(thisShell.name!)}
|
||||
style={{ width: "1.125rem", height: "1.125rem" }}
|
||||
src={`/api/icons/shells/${thisShell.icon}.webp`}
|
||||
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { isExplosive } from "@blitzkit/core";
|
||||
import { alias, isExplosive } from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { Box, Flex, Text } from "@radix-ui/themes";
|
||||
import { useRef, useState } from "react";
|
||||
@@ -124,7 +124,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||
/>
|
||||
|
||||
<img
|
||||
src={`/api/icons/shells/${shell.icon}.webp`}
|
||||
src={alias("api", `/icons/shells/${shell.icon}.webp`)}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
transform: "translate(-50%, -50%) rotate(-45deg)",
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { Badge, Box, Flex, Heading, Switch, Text } from "@radix-ui/themes";
|
||||
import { useLocale } from "../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
@@ -33,7 +34,7 @@ export function Equalizer() {
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
borderRadius: "var(--radius-3)",
|
||||
backgroundImage: "url(/api/gamemodes/45/banner.webp)",
|
||||
backgroundImage: `url(${alias("api", `/gamemodes/45/banner.webp`)})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
overflow: "hidden",
|
||||
|
||||
+3
-2
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { Flex, Heading } from "@radix-ui/themes";
|
||||
import { useLocale } from "../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../stores/duel";
|
||||
@@ -21,7 +22,7 @@ export function Miscellaneous() {
|
||||
<Flex gap="2" align="center">
|
||||
{!tank.fixed_camouflage && (
|
||||
<GenericTankComponentButton
|
||||
icon={"/api/icons/camo.webp"}
|
||||
icon={alias("api", "/icons/camo.webp")}
|
||||
selected={camouflage}
|
||||
iconStyles={{
|
||||
top: "50%",
|
||||
@@ -36,7 +37,7 @@ export function Miscellaneous() {
|
||||
/>
|
||||
)}
|
||||
<GenericTankComponentButton
|
||||
icon={"/api/icons/boosters/equipment.webp"}
|
||||
icon={alias("api", "/icons/boosters/equipment.webp")}
|
||||
selected={cooldownBooster > 0}
|
||||
banner={
|
||||
cooldownBooster === 0
|
||||
|
||||
+6
-2
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
alias,
|
||||
EngineDefinition,
|
||||
formatCompact,
|
||||
GunDefinition,
|
||||
@@ -110,7 +111,7 @@ function ModuleButton({
|
||||
|
||||
<img
|
||||
alt={unlock.cost!.type}
|
||||
src={`/api/icons/currencies/${unlock.cost!.type}.webp`}
|
||||
src={alias("api", `/icons/currencies/${unlock.cost!.type}.webp`)}
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
@@ -125,7 +126,10 @@ function ModuleButton({
|
||||
src={
|
||||
isTank
|
||||
? tankIcon(unlock.id)
|
||||
: `/api/icons/modules/${moduleTypeTypeIconName[unlock.type]}.webp`
|
||||
: alias(
|
||||
"api",
|
||||
`/icons/modules/${moduleTypeTypeIconName[unlock.type]}.webp`,
|
||||
)
|
||||
}
|
||||
style={{
|
||||
width: isTank ? 64 : 32,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { Box, Flex, Heading, Text } from "@radix-ui/themes";
|
||||
import { api } from "../../core/blitzkit/api";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
@@ -37,7 +38,10 @@ export function GameModeSection() {
|
||||
direction="column"
|
||||
style={{
|
||||
borderRadius: "var(--radius-3)",
|
||||
background: `url(/api/gamemodes/${id}/banner.webp)`,
|
||||
background: `url(${alias(
|
||||
"api",
|
||||
`/gamemodes/${id}/banner.webp`,
|
||||
)})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
}}
|
||||
@@ -45,7 +49,10 @@ export function GameModeSection() {
|
||||
<Box
|
||||
flexGrow="1"
|
||||
style={{
|
||||
background: `url(/api/gamemodes/${id}/banner.webp)`,
|
||||
background: `url(${alias(
|
||||
"api",
|
||||
`/gamemodes/${id}/banner.webp`,
|
||||
)})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
}}
|
||||
@@ -80,7 +87,7 @@ export function GameModeSection() {
|
||||
height: "1.5em",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={`/api/icons/provisions/${id}.webp`}
|
||||
src={alias("api", `/icons/provisions/${id}.webp`)}
|
||||
alt={unwrap(provisions.name!)}
|
||||
/>
|
||||
{unwrap(provisions.name!)}
|
||||
@@ -101,7 +108,7 @@ export function GameModeSection() {
|
||||
height: "1.5em",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={`/api/icons/consumables/${id}.webp`}
|
||||
src={alias("api", `/icons/consumables/${id}.webp`)}
|
||||
alt={unwrap(consumable.name!)}
|
||||
/>
|
||||
{unwrap(consumable.name!)}
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { IconButton } from "@radix-ui/themes";
|
||||
import { useArmor } from "../../../../../../hooks/useArmor";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
@@ -37,7 +38,7 @@ export function DynamicArmorSwitcher() {
|
||||
>
|
||||
<img
|
||||
alt="Dynamic Armor"
|
||||
src={"/api/icons/consumables/73.webp"}
|
||||
src={alias("api", "/icons/consumables/73.webp")}
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
alias,
|
||||
resolvePenetrationCoefficient,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
uniqueGuns,
|
||||
@@ -139,7 +140,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
|
||||
<img
|
||||
alt="Antagonist Gun"
|
||||
src={"/api/icons/modules/gun.webp"}
|
||||
src={alias("api", "/icons/modules/gun.webp")}
|
||||
style={{
|
||||
width: "65%",
|
||||
height: "65%",
|
||||
@@ -208,7 +209,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
>
|
||||
<img
|
||||
alt={unwrap(thisShell.name!)}
|
||||
src={`/api/icons/shells/${thisShell.icon}.webp`}
|
||||
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
@@ -245,7 +246,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
>
|
||||
<img
|
||||
alt="Calibrated Shells"
|
||||
src={"/api/icons/equipment/103.webp"}
|
||||
src={alias("api", "/icons/equipment/103.webp")}
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
@@ -270,7 +271,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
>
|
||||
<img
|
||||
alt="Enhanced Armor"
|
||||
src={"/api/icons/equipment/110.webp"}
|
||||
src={alias("api", "/icons/equipment/110.webp")}
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { Flex, type FlexProps, Progress } from "@radix-ui/themes";
|
||||
import { useProgress } from "@react-three/drei";
|
||||
|
||||
@@ -11,7 +12,7 @@ export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) {
|
||||
return (
|
||||
<Flex width="100%" height="100%" align="center" justify="center" {...props}>
|
||||
<img
|
||||
src={`/api/tanks/${id}/icons/big.webp`}
|
||||
src={alias("api", `/tanks/${id}/icons/big.webp`)}
|
||||
style={{ height: "75%", filter: "blur(1rem)" }}
|
||||
/>
|
||||
<Progress
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Flex, Text, type TextProps } from '@radix-ui/themes';
|
||||
import { Var } from '../../../../core/radix/var';
|
||||
import { Flex, Text, type TextProps } from "@radix-ui/themes";
|
||||
import { Var } from "../../../../core/radix/var";
|
||||
|
||||
type ListingProps = TextProps & {
|
||||
label: string;
|
||||
@@ -11,7 +11,7 @@ export function Listing({ label, ...props }: ListingProps) {
|
||||
minWidth="17rem"
|
||||
justify="between"
|
||||
gap="4"
|
||||
style={{ borderBottom: `1px solid ${Var('gray-a9')}` }}
|
||||
style={{ borderBottom: `1px solid ${Var("gray-a9")}` }}
|
||||
>
|
||||
<Text size="4">{label}</Text>
|
||||
<Text align="right" size="4" weight="medium" {...props} />
|
||||
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { StarFilledIcon, StarIcon } from '@radix-ui/react-icons';
|
||||
import { Box, type BoxProps } from '@radix-ui/themes';
|
||||
|
||||
type PartialStarProps = BoxProps & {
|
||||
fill: number;
|
||||
};
|
||||
|
||||
export function PartialStar({ fill, ...props }: PartialStarProps) {
|
||||
return (
|
||||
<Box height="1em" width="1em" position="relative" {...props}>
|
||||
<StarIcon width="1em" height="1em" />
|
||||
{fill > 0 && (
|
||||
<Box
|
||||
top="0"
|
||||
left="0"
|
||||
width={`${fill * 100}%`}
|
||||
position="absolute"
|
||||
overflow="hidden"
|
||||
>
|
||||
<StarFilledIcon width="1em" height="1em" />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import type { StarsInt } from '@blitzkit/core';
|
||||
import { type TextProps, Flex, Text } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { PartialStar } from './components/PartialStar';
|
||||
|
||||
type StarsProps = TextProps & {
|
||||
stars: number | null;
|
||||
onCast?: (value: StarsInt) => void;
|
||||
};
|
||||
|
||||
export function Stars({ stars, onCast, ...props }: StarsProps) {
|
||||
let color: TextProps['color'] = 'green';
|
||||
|
||||
if (stars === null) color = 'gray';
|
||||
else if (stars < 1.5) color = 'tomato';
|
||||
else if (stars < 2.5) color = 'orange';
|
||||
else if (stars < 3.5) color = 'amber';
|
||||
else if (stars < 4.5) color = 'blue';
|
||||
|
||||
return (
|
||||
<Text color={color} {...props}>
|
||||
<Flex style={{ cursor: onCast ? 'pointer' : 'unset' }}>
|
||||
{stars !== null &&
|
||||
times(Math.floor(stars), (index) => (
|
||||
<PartialStar
|
||||
fill={1}
|
||||
key={index}
|
||||
onClick={() => onCast?.((index + 1) as StarsInt)}
|
||||
/>
|
||||
))}
|
||||
{stars !== null && stars % 1 !== 0 && <PartialStar fill={stars % 1} />}
|
||||
{times(5 - Math.ceil(stars ?? 0), (index) => (
|
||||
<PartialStar
|
||||
fill={0}
|
||||
key={index}
|
||||
onClick={() =>
|
||||
onCast?.((Math.floor(stars ?? 0) + index + 1) as StarsInt)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import { Flex, Text } from '@radix-ui/themes';
|
||||
import { Stars } from '../../Stars';
|
||||
|
||||
interface StarRowProps {
|
||||
stars: number | null;
|
||||
children: string;
|
||||
}
|
||||
|
||||
export function StarRow({ stars, children }: StarRowProps) {
|
||||
return (
|
||||
<Text color={stars === null ? 'gray' : undefined}>
|
||||
<Flex align="center" justify="between" width="15rem">
|
||||
{children}
|
||||
<Stars stars={stars} />
|
||||
</Flex>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
import type { BlitzkitResponse, StarsInt } from "@blitzkit/core";
|
||||
import { PaperPlaneIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
AlertDialog,
|
||||
Button,
|
||||
Dialog,
|
||||
Flex,
|
||||
Spinner,
|
||||
Text,
|
||||
} from "@radix-ui/themes";
|
||||
import { useState } from "react";
|
||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||
import { App } from "../../../../../../stores/app";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Stars } from "../../Stars";
|
||||
|
||||
export function VoteCaster() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const [easiness, setEasiness] = useState<StarsInt | null>(null);
|
||||
const [firepower, setFirepower] = useState<StarsInt | null>(null);
|
||||
const [maneuverability, setManeuverability] = useState<StarsInt | null>(null);
|
||||
const [survivability, setSurvivability] = useState<StarsInt | null>(null);
|
||||
// true = vote successful, false = vote failed, null = hide alert
|
||||
const [postVote, setPostVote] = useState<boolean | null>(null);
|
||||
// true = open, false = closed, null = casting
|
||||
const [showDialogue, setShowDialogue] = useState<boolean | null>(false);
|
||||
const { unwrap } = useLocale();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog.Root open={showDialogue !== false} onOpenChange={setShowDialogue}>
|
||||
<Dialog.Trigger>
|
||||
<Button variant="surface">
|
||||
<PaperPlaneIcon /> Vote
|
||||
</Button>
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content maxWidth="23rem">
|
||||
<Dialog.Title>Voting for {unwrap(tank.name!)}</Dialog.Title>
|
||||
|
||||
<Flex direction="column" mt="4">
|
||||
<Flex align="center" justify="between">
|
||||
<Text>
|
||||
Easiness{" "}
|
||||
<Text color="gray" size="2">
|
||||
{easiness === null ? "(empty)" : `(${easiness})`}
|
||||
</Text>
|
||||
</Text>
|
||||
<Stars stars={easiness} size="5" onCast={setEasiness} />
|
||||
</Flex>
|
||||
<Flex align="center" justify="between">
|
||||
<Text>
|
||||
Firepower{" "}
|
||||
<Text color="gray" size="2">
|
||||
{firepower === null ? "(empty)" : `(${firepower})`}
|
||||
</Text>
|
||||
</Text>
|
||||
<Stars stars={firepower} size="5" onCast={setFirepower} />
|
||||
</Flex>
|
||||
<Flex align="center" justify="between">
|
||||
<Text>
|
||||
Maneuverability{" "}
|
||||
<Text color="gray" size="2">
|
||||
{maneuverability === null
|
||||
? "(empty)"
|
||||
: `(${maneuverability})`}
|
||||
</Text>
|
||||
</Text>
|
||||
<Stars
|
||||
stars={maneuverability}
|
||||
size="5"
|
||||
onCast={setManeuverability}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex align="center" justify="between">
|
||||
<Text>
|
||||
Survivability{" "}
|
||||
<Text color="gray" size="2">
|
||||
{survivability === null ? "(empty)" : `(${survivability})`}
|
||||
</Text>
|
||||
</Text>
|
||||
<Stars stars={survivability} size="5" onCast={setSurvivability} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex justify="end" mt="4" gap="2">
|
||||
<Dialog.Close>
|
||||
<Button variant="outline" color="red">
|
||||
Cancel
|
||||
</Button>
|
||||
</Dialog.Close>
|
||||
|
||||
<Button
|
||||
disabled={
|
||||
showDialogue === null ||
|
||||
(easiness === null &&
|
||||
firepower === null &&
|
||||
maneuverability === null &&
|
||||
survivability === null)
|
||||
}
|
||||
onClick={() => {
|
||||
const { wargaming } = App.state.logins;
|
||||
|
||||
if (!wargaming) {
|
||||
return setShowDialogue(false);
|
||||
}
|
||||
|
||||
setShowDialogue(null);
|
||||
|
||||
fetch(
|
||||
`/api/tank-voting/${tank.id}/cast?${
|
||||
easiness === null ? "" : `easiness=${easiness}&`
|
||||
}${firepower === null ? "" : `firepower=${firepower}&`}${
|
||||
maneuverability === null
|
||||
? ""
|
||||
: `maneuverability=${maneuverability}&`
|
||||
}${
|
||||
survivability === null
|
||||
? ""
|
||||
: `survivability=${survivability}&`
|
||||
}player=${wargaming.id}&token=${wargaming.token}`,
|
||||
)
|
||||
.then(
|
||||
(response) => response.json() as Promise<BlitzkitResponse>,
|
||||
)
|
||||
.then((json) => {
|
||||
setShowDialogue(false);
|
||||
setPostVote(json.status === "ok");
|
||||
|
||||
if (json.status === "error") {
|
||||
console.error("vote failed", json);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{showDialogue === null ? <Spinner /> : <PaperPlaneIcon />} Vote
|
||||
</Button>
|
||||
</Flex>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
<AlertDialog.Root
|
||||
open={postVote !== null}
|
||||
onOpenChange={() => setPostVote(null)}
|
||||
>
|
||||
{postVote === true && (
|
||||
<AlertDialog.Content maxWidth="20rem">
|
||||
<AlertDialog.Title>Vote casted</AlertDialog.Title>
|
||||
<AlertDialog.Description>
|
||||
It may take a few minutes for it to show up.
|
||||
</AlertDialog.Description>
|
||||
|
||||
<Flex justify="end" mt="4">
|
||||
<AlertDialog.Cancel>
|
||||
<Button>Okay</Button>
|
||||
</AlertDialog.Cancel>
|
||||
</Flex>
|
||||
</AlertDialog.Content>
|
||||
)}
|
||||
|
||||
{postVote === false && (
|
||||
<AlertDialog.Content maxWidth="20rem">
|
||||
<AlertDialog.Title>Voting failed</AlertDialog.Title>
|
||||
<AlertDialog.Description>
|
||||
So sorry about this! If you're a nerd, feel free to check out the
|
||||
console.
|
||||
</AlertDialog.Description>
|
||||
|
||||
<Flex justify="end" mt="4">
|
||||
<AlertDialog.Cancel>
|
||||
<Button>Gotcha</Button>
|
||||
</AlertDialog.Cancel>
|
||||
</Flex>
|
||||
</AlertDialog.Content>
|
||||
)}
|
||||
</AlertDialog.Root>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import { formatCompact } from '@blitzkit/core';
|
||||
import { Flex, Heading, Skeleton, Text } from '@radix-ui/themes';
|
||||
import { memo } from 'react';
|
||||
import { useLocale } from '../../../../../hooks/useLocale';
|
||||
import { useTankVotes } from '../../../../../hooks/useTankVotes';
|
||||
import { App } from '../../../../../stores/app';
|
||||
import { Duel } from '../../../../../stores/duel';
|
||||
import { WargamingLoginButton } from '../../../../WargamingLoginButton';
|
||||
import { StarRow } from './components/StarRow';
|
||||
import { VoteCaster } from './components/VoteCaster';
|
||||
|
||||
export const Votes = memo(
|
||||
() => {
|
||||
const id = Duel.use((state) => state.protagonist.tank.id);
|
||||
const votes = useTankVotes(id);
|
||||
const wargaming = App.use((state) => state.logins.wargaming);
|
||||
const { locale } = useLocale();
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="3">
|
||||
<Flex justify="between" align="center">
|
||||
<Heading size="4">
|
||||
{!votes && <Skeleton height="1em" width="4rem" />}
|
||||
{votes && formatCompact(locale, votes.votes)}{' '}
|
||||
{votes && (votes.votes > 1 ? 'votes' : 'vote')}
|
||||
</Heading>
|
||||
|
||||
{!wargaming && (
|
||||
<WargamingLoginButton variant="surface">
|
||||
Login to vote
|
||||
</WargamingLoginButton>
|
||||
)}
|
||||
{wargaming && <VoteCaster />}
|
||||
</Flex>
|
||||
|
||||
<Flex gap="0" direction="column">
|
||||
<Flex
|
||||
gap={{ initial: '0', sm: '6' }}
|
||||
direction={{ initial: 'column', sm: 'row' }}
|
||||
>
|
||||
<StarRow
|
||||
stars={votes?.categories.easiness ?? null}
|
||||
children="Easiness"
|
||||
/>
|
||||
<StarRow
|
||||
stars={votes?.categories.firepower ?? null}
|
||||
children="Firepower"
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
gap={{ initial: '0', sm: '6' }}
|
||||
direction={{ initial: 'column', sm: 'row' }}
|
||||
>
|
||||
<StarRow
|
||||
stars={votes?.categories.maneuverability ?? null}
|
||||
children="Maneuverability"
|
||||
/>
|
||||
<StarRow
|
||||
stars={votes?.categories.survivability ?? null}
|
||||
children="Survivability"
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex justify="center">
|
||||
<Text size="2" color="gray" mt="2">
|
||||
<>
|
||||
{votes ? (
|
||||
wargaming ? (
|
||||
votes.last_updated ? (
|
||||
`You voted on ${new Date(
|
||||
votes.last_updated,
|
||||
).toLocaleDateString()}`
|
||||
) : (
|
||||
`You haven't voted yet`
|
||||
)
|
||||
) : (
|
||||
'Login to vote'
|
||||
)
|
||||
) : (
|
||||
<Skeleton height="1em" width="5rem" />
|
||||
)}
|
||||
</>
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
() => true,
|
||||
);
|
||||
@@ -1,4 +1,9 @@
|
||||
import { TankPriceType, TankType, TIER_ROMAN_NUMERALS } from "@blitzkit/core";
|
||||
import {
|
||||
alias,
|
||||
TankPriceType,
|
||||
TankType,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
} from "@blitzkit/core";
|
||||
import { Box, Flex } from "@radix-ui/themes";
|
||||
import { Var } from "../../../core/radix/var";
|
||||
import { useLocale } from "../../../hooks/useLocale";
|
||||
@@ -35,7 +40,7 @@ export function MetaSection() {
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
background: `url(${`/api/flags/scratched/${protagonist.nation}.webp`})`,
|
||||
background: `url(${alias("api", `/flags/scratched/${protagonist.nation}.webp`)})`,
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "min(53rem, 80vw)",
|
||||
backgroundPosition: "-8rem 50%",
|
||||
@@ -60,7 +65,10 @@ export function MetaSection() {
|
||||
<img
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt={protagonist.nation}
|
||||
src={`/api/flags/circle/${protagonist.nation}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/flags/circle/${protagonist.nation}.webp`,
|
||||
)}
|
||||
/>
|
||||
{
|
||||
strings.common.nations[
|
||||
@@ -130,7 +138,7 @@ export function MetaSection() {
|
||||
<img
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt="gold"
|
||||
src={"/api/icons/currencies/gold.webp"}
|
||||
src={alias("api", "/icons/currencies/gold.webp")}
|
||||
/>
|
||||
</Flex>
|
||||
</Listing>
|
||||
@@ -147,12 +155,15 @@ export function MetaSection() {
|
||||
<img
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt={TankPriceType[protagonist.price!.type]}
|
||||
src={`/api/icons/currencies/${
|
||||
protagonist.price!.type ===
|
||||
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||
? "gold"
|
||||
: "silver"
|
||||
}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/currencies/${
|
||||
protagonist.price!.type ===
|
||||
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||
? "gold"
|
||||
: "silver"
|
||||
}.webp`,
|
||||
)}
|
||||
/>
|
||||
</Flex>
|
||||
</Listing>
|
||||
@@ -162,12 +173,15 @@ export function MetaSection() {
|
||||
<img
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt={TankPriceType[protagonist.price!.type]}
|
||||
src={`/api/icons/currencies/${
|
||||
protagonist.price!.type ===
|
||||
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||
? "gold"
|
||||
: "silver"
|
||||
}.webp`}
|
||||
src={alias(
|
||||
"api",
|
||||
`/icons/currencies/${
|
||||
protagonist.price!.type ===
|
||||
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||
? "gold"
|
||||
: "silver"
|
||||
}.webp`,
|
||||
)}
|
||||
/>
|
||||
</Flex>
|
||||
</Listing>
|
||||
@@ -183,7 +197,7 @@ export function MetaSection() {
|
||||
<img
|
||||
style={{ width: "1em", height: "1em" }}
|
||||
alt="xp"
|
||||
src={"/api/icons/currencies/xp.webp"}
|
||||
src={alias("api", "/icons/currencies/xp.webp")}
|
||||
/>
|
||||
</Flex>
|
||||
</Listing>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { formatCompact, TIER_ROMAN_NUMERALS } from "@blitzkit/core";
|
||||
import { alias, formatCompact, TIER_ROMAN_NUMERALS } from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n/src/literals";
|
||||
import { Box, Flex, Skeleton, Text } from "@radix-ui/themes";
|
||||
import { awaitableAverageDefinitions } from "../../../../core/awaitables/averageDefinitions";
|
||||
@@ -58,7 +58,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
mx={highlight ? "2" : "0"}
|
||||
style={{
|
||||
background: highlight
|
||||
? `url(${`/api/flags/fade/${tank.nation}.webp`})`
|
||||
? `url(${alias("api", `/flags/fade/${tank.nation}.webp`)})`
|
||||
: undefined,
|
||||
backgroundPosition: "center",
|
||||
backgroundSize: "cover",
|
||||
@@ -79,7 +79,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
>
|
||||
<img
|
||||
alt={unwrap(tank.name!)}
|
||||
src={`/api/tanks/${id}/icons/big.webp`}
|
||||
src={alias("api", `/tanks/${id}/icons/big.webp`)}
|
||||
width={64}
|
||||
height={64}
|
||||
style={{
|
||||
@@ -99,7 +99,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
<Flex gap="1" align="center">
|
||||
<img
|
||||
alt="XP"
|
||||
src={"/api/icons/currencies/xp.webp"}
|
||||
src={alias("api", "/icons/currencies/xp.webp")}
|
||||
style={{
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
@@ -114,7 +114,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
<Flex gap="1" align="center">
|
||||
<img
|
||||
alt="Silver"
|
||||
src={"/api/icons/currencies/silver.webp"}
|
||||
src={alias("api", "/icons/currencies/silver.webp")}
|
||||
style={{
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TankType } from "@blitzkit/core";
|
||||
import { alias, TankType } from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n/src/literals";
|
||||
import { CaretLeftIcon, CaretRightIcon, PlusIcon } from "@radix-ui/react-icons";
|
||||
import { Flex, Heading, IconButton, ScrollArea, Text } from "@radix-ui/themes";
|
||||
@@ -105,7 +105,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
<Flex gap="1" align="center">
|
||||
<img
|
||||
alt="XP"
|
||||
src={"/api/icons/currencies/xp.webp"}
|
||||
src={alias("api", "/icons/currencies/xp.webp")}
|
||||
style={{
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
@@ -121,7 +121,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
<Flex gap="1" align="center">
|
||||
<img
|
||||
alt="Silver"
|
||||
src={"/api/icons/currencies/silver.webp"}
|
||||
src={alias("api", "/icons/currencies/silver.webp")}
|
||||
style={{
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { assertSecret } from "@blitzkit/core";
|
||||
import { assertSecret, environmentConfig } from "@blitzkit/core";
|
||||
import type { GetStaticPaths } from "astro";
|
||||
|
||||
export function chunkedStaticPaths(chunk: string) {
|
||||
return function () {
|
||||
const environment = environmentConfig();
|
||||
const chunksEnv = assertSecret(import.meta.env.CHUNK);
|
||||
const chunks = chunksEnv.split(",");
|
||||
|
||||
@@ -10,6 +11,8 @@ export function chunkedStaticPaths(chunk: string) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [{ params: { [`chunk_${chunk}`]: undefined } }];
|
||||
return [
|
||||
{ params: { [`chunk_${chunk}`]: environment.paths[chunk] ?? undefined } },
|
||||
];
|
||||
} satisfies GetStaticPaths;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { useLoader } from "@react-three/fiber";
|
||||
import { Mesh, MeshStandardMaterial } from "three";
|
||||
import { GLTFLoader } from "three-stdlib";
|
||||
import { useDispose } from "./useDispose";
|
||||
|
||||
export function useModel(id: number) {
|
||||
const path = `/api/tanks/${id}/model.glb`;
|
||||
const path = alias("api", `/tanks/${id}/model.glb`);
|
||||
const gltf = useLoader(GLTFLoader, path);
|
||||
|
||||
useDispose(gltf, path);
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import {
|
||||
unwrapBlitzkitResponse,
|
||||
type BlitzkitResponse,
|
||||
type TankVotes,
|
||||
} from '@blitzkit/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { App } from '../stores/app';
|
||||
|
||||
const cache: Record<string, Promise<TankVotes> | TankVotes> = {};
|
||||
|
||||
export function useTankVotes(id: number) {
|
||||
const [votes, setVotes] = useState<TankVotes | null>(null);
|
||||
const wargaming = App.use((state) => state.logins.wargaming);
|
||||
|
||||
useEffect(() => {
|
||||
if (!wargaming) return;
|
||||
|
||||
if (id in cache) {
|
||||
Promise.all([cache[id]]).then(([votes]) => setVotes(votes));
|
||||
} else {
|
||||
const cacheable = fetch(
|
||||
`/api/tank-voting/${id}/average${
|
||||
wargaming ? `?player=${wargaming.id}&token=${wargaming.token}` : ''
|
||||
}`,
|
||||
)
|
||||
.then(
|
||||
(response) => response.json() as Promise<BlitzkitResponse<TankVotes>>,
|
||||
)
|
||||
.then(unwrapBlitzkitResponse);
|
||||
cacheable.then(setVotes);
|
||||
cache[id] = cacheable;
|
||||
}
|
||||
}, [id, wargaming]);
|
||||
|
||||
return votes;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
alias,
|
||||
GunDefinition,
|
||||
TankDefinition,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
@@ -110,7 +111,7 @@ function ModuleChooser({
|
||||
height: "4em",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
src={`/api/tanks/${tank}/icons/big.webp`}
|
||||
src={alias("api", `/tanks/${tank}/icons/big.webp`)}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import { AstroSeo } from "@astrolib/seo";
|
||||
import {
|
||||
alias,
|
||||
assertSecret,
|
||||
createDefaultProvisions,
|
||||
createDefaultSkills,
|
||||
@@ -51,7 +52,7 @@ const title = literals(strings.website.tools.tankopedia.tank_seo_title, {
|
||||
});
|
||||
const url = import.meta.env.SITE + Astro.url.pathname;
|
||||
|
||||
const icon = `/api/tanks/${id}/icons/big.webp`;
|
||||
const icon = alias("api", `/tanks/${id}/icons/big.webp`);
|
||||
const turret = tank.turrets.at(-1)!;
|
||||
const gun = turret.guns.at(-1)!;
|
||||
const shell = gun.shells[0];
|
||||
|
||||
Reference in new issue
Block a user