mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +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": {},
|
"nodes": {},
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"api": "/api",
|
"api": "https://api.beta.blitzkit.app",
|
||||||
"static": "https://static.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 "./getAccountInfo";
|
||||||
export * from "./getClanAccountInfo";
|
export * from "./getClanAccountInfo";
|
||||||
export * from "./getClanInfo";
|
export * from "./getClanInfo";
|
||||||
export * from "./getLeagueFromScore";
|
|
||||||
export * from "./getRatingInfo";
|
|
||||||
export * from "./getRatingLeague";
|
|
||||||
export * from "./getRatingNeighbors";
|
|
||||||
export * from "./getTankStats";
|
export * from "./getTankStats";
|
||||||
export * from "./idToRegion";
|
export * from "./idToRegion";
|
||||||
export * from "./isOnGoingRatingSeason";
|
|
||||||
export * from "./isValidBlitzId";
|
export * from "./isValidBlitzId";
|
||||||
export * from "./nations";
|
export * from "./nations";
|
||||||
export * from "./normalizeExtra";
|
export * from "./normalizeExtra";
|
||||||
@@ -28,6 +23,5 @@ export * from "./regions";
|
|||||||
export * from "./regionSubdomainToRegion";
|
export * from "./regionSubdomainToRegion";
|
||||||
export * from "./regionToRegionSubdomain";
|
export * from "./regionToRegionSubdomain";
|
||||||
export * from "./searchClansAcrossRegions";
|
export * from "./searchClansAcrossRegions";
|
||||||
export * from "./searchCurrentRatingPlayers";
|
|
||||||
export * from "./searchPlayersAcrossRegions";
|
export * from "./searchPlayersAcrossRegions";
|
||||||
export * from "./toUniqueId";
|
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 { fetchPB } from "../../protobuf";
|
||||||
import {
|
import {
|
||||||
CamouflageDefinitions,
|
CamouflageDefinitions,
|
||||||
@@ -14,38 +15,50 @@ import { BlitzKitAPI } from "./base";
|
|||||||
|
|
||||||
export class ClientBlitzKitAPI extends BlitzKitAPI {
|
export class ClientBlitzKitAPI extends BlitzKitAPI {
|
||||||
gameDefinitions() {
|
gameDefinitions() {
|
||||||
return fetchPB("/api/definitions/game.pb", GameDefinitions);
|
return fetchPB(alias("api", "/definitions/game.pb"), GameDefinitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
consumableDefinitions() {
|
consumableDefinitions() {
|
||||||
return fetchPB("/api/definitions/consumables.pb", ConsumableDefinitions);
|
return fetchPB(
|
||||||
|
alias("api", "/definitions/consumables.pb"),
|
||||||
|
ConsumableDefinitions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
tankDefinitions() {
|
tankDefinitions() {
|
||||||
return fetchPB("/api/definitions/tanks.pb", TankDefinitions);
|
return fetchPB(alias("api", "/definitions/tanks.pb"), TankDefinitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
camouflageDefinitions() {
|
camouflageDefinitions() {
|
||||||
return fetchPB("/api/definitions/camouflage.pb", CamouflageDefinitions);
|
return fetchPB(
|
||||||
|
alias("api", "/definitions/camouflage.pb"),
|
||||||
|
CamouflageDefinitions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
provisionDefinitions() {
|
provisionDefinitions() {
|
||||||
return fetchPB("/api/definitions/provisions.pb", ProvisionDefinitions);
|
return fetchPB(
|
||||||
|
alias("api", "/definitions/provisions.pb"),
|
||||||
|
ProvisionDefinitions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
modelDefinitions() {
|
modelDefinitions() {
|
||||||
return fetchPB("/api/definitions/models.pb", ModelDefinitions);
|
return fetchPB(alias("api", "/definitions/models.pb"), ModelDefinitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
skillDefinitions() {
|
skillDefinitions() {
|
||||||
return fetchPB("/api/definitions/skills.pb", SkillDefinitions);
|
return fetchPB(alias("api", "/definitions/skills.pb"), SkillDefinitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
equipmentDefinitions() {
|
equipmentDefinitions() {
|
||||||
return fetchPB("/api/definitions/equipment.pb", EquipmentDefinitions);
|
return fetchPB(
|
||||||
|
alias("api", "/definitions/equipment.pb"),
|
||||||
|
EquipmentDefinitions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryDefinitions() {
|
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 { 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 locales from "@blitzkit/i18n/locales.json";
|
||||||
import { extname } from "path";
|
import { extname } from "path";
|
||||||
import { Cache } from "./0_base";
|
import { Cache } from "./0_base";
|
||||||
|
import { ServerBlitzKitAPI9 } from "./9_gameDefinitions";
|
||||||
|
|
||||||
export abstract class ServerBlitzKitAPI10 extends ServerBlitzKitAPI9 {
|
export abstract class ServerBlitzKitAPI10 extends ServerBlitzKitAPI9 {
|
||||||
@Cache()
|
@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 "./assertSecret";
|
||||||
export * from "./availableProvisions";
|
export * from "./availableProvisions";
|
||||||
export * from "./averageDefinitions";
|
export * from "./averageDefinitions";
|
||||||
@@ -7,6 +8,7 @@ export * from "./createDefaultSkills";
|
|||||||
export * from "./degressiveStat";
|
export * from "./degressiveStat";
|
||||||
export * from "./discoveredIdDefinitions";
|
export * from "./discoveredIdDefinitions";
|
||||||
export * from "./emblemURL";
|
export * from "./emblemURL";
|
||||||
|
export * from "./environment";
|
||||||
export * from "./eventManager";
|
export * from "./eventManager";
|
||||||
export * from "./getTimeDaysAgo";
|
export * from "./getTimeDaysAgo";
|
||||||
export * from "./isExplosive";
|
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 const BLITZKIT_TANK_ICON_SIZE = { width: 40 * 16, height: 30 * 16 };
|
||||||
|
|
||||||
export function tankIcon(id: number, size: "big" | "small" = "big") {
|
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 { Flex, Heading, IconButton, Table } from "@radix-ui/themes";
|
||||||
import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics";
|
import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics";
|
||||||
import { useLocale } from "../../hooks/useLocale";
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
@@ -57,7 +58,10 @@ export function Body({ stats }: BodyProps) {
|
|||||||
alt={unwrap(thisShell.name!)}
|
alt={unwrap(thisShell.name!)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
src={`/api/icons/shells/${thisShell.icon}.webp`}
|
src={alias(
|
||||||
|
"api",
|
||||||
|
`/icons/shells/${thisShell.icon}.webp`,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</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 { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
|
||||||
import { ComponentPlaceholderIcon } from "@radix-ui/react-icons";
|
import { ComponentPlaceholderIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
@@ -231,7 +231,10 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
alt={module}
|
alt={module}
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
src={`/api/icons/modules/${module}.webp`}
|
src={alias(
|
||||||
|
"api",
|
||||||
|
`/icons/modules/${module}.webp`,
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: index > 0 ? -8 : 0,
|
marginLeft: index > 0 ? -8 : 0,
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
@@ -320,7 +323,10 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
.name!,
|
.name!,
|
||||||
)}
|
)}
|
||||||
key={provision}
|
key={provision}
|
||||||
src={`/api//icons/provisions/${provision}.webp`}
|
src={alias(
|
||||||
|
"api",
|
||||||
|
`//icons/provisions/${provision}.webp`,
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
left: "50%",
|
left: "50%",
|
||||||
/**
|
/**
|
||||||
@@ -525,7 +531,10 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
].name!,
|
].name!,
|
||||||
)}
|
)}
|
||||||
key={consumable}
|
key={consumable}
|
||||||
src={`/api/icons/consumables/${consumable}.webp`}
|
src={alias(
|
||||||
|
"api",
|
||||||
|
`/icons/consumables/${consumable}.webp`,
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
left: "50%",
|
left: "50%",
|
||||||
/**
|
/**
|
||||||
@@ -617,7 +626,7 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
variant={camouflage ? "solid" : "outline"}
|
variant={camouflage ? "solid" : "outline"}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`/api/icons/camo.webp`}
|
src={alias("api", `/icons/camo.webp`)}
|
||||||
style={{
|
style={{
|
||||||
width: "1em",
|
width: "1em",
|
||||||
height: "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 { Flex } from "@radix-ui/themes";
|
||||||
import { api } from "../core/blitzkit/api";
|
import { api } from "../core/blitzkit/api";
|
||||||
import { GenericTankComponentButton } from "./ModuleButtons/GenericTankComponentButton";
|
import { GenericTankComponentButton } from "./ModuleButtons/GenericTankComponentButton";
|
||||||
@@ -27,7 +27,7 @@ export function CrewSkillManager({
|
|||||||
special={!permanentSkills.includes(skill)}
|
special={!permanentSkills.includes(skill)}
|
||||||
selected={level > 0}
|
selected={level > 0}
|
||||||
discriminator={level === 0 ? undefined : romanize(level)}
|
discriminator={level === 0 ? undefined : romanize(level)}
|
||||||
icon={`/api/icons/skills/${skill}.webp`}
|
icon={alias("api", `/icons/skills/${skill}.webp`)}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
if (!onChange) return;
|
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 { Cross1Icon, DownloadIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -18,7 +18,10 @@ type GalleryCardProps = MaybeSkeletonComponentProps<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function GalleryCard(props: GalleryCardProps) {
|
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 card = useRef<HTMLDivElement>(null!);
|
||||||
const { unwrap } = useLocale();
|
const { unwrap } = useLocale();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
alias,
|
||||||
SEARCH_KEYS,
|
SEARCH_KEYS,
|
||||||
TankDefinition,
|
TankDefinition,
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
@@ -149,7 +150,10 @@ export function Guesser() {
|
|||||||
prefix={
|
prefix={
|
||||||
<img
|
<img
|
||||||
style={{ width: "1em", height: "1em" }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
src={`/api/flags/circle/${result.nation}.webp`}
|
src={alias(
|
||||||
|
"api",
|
||||||
|
`/flags/circle/${result.nation}.webp`,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
discriminator={
|
discriminator={
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||||
import type { TankComponentButtonProps } from "./TankComponentButton";
|
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ interface ModuleProps extends TankComponentButtonProps {
|
|||||||
export function ModuleButton({ module, ...props }: ModuleProps) {
|
export function ModuleButton({ module, ...props }: ModuleProps) {
|
||||||
return (
|
return (
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
icon={`/api/icons/modules/${module}.webp`}
|
icon={alias("api", `/icons/modules/${module}.webp`)}
|
||||||
iconStyles={{
|
iconStyles={{
|
||||||
top: "50%",
|
top: "50%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import { api } from "../../core/blitzkit/api";
|
import { api } from "../../core/blitzkit/api";
|
||||||
import { useLocale } from "../../hooks/useLocale";
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||||
@@ -15,7 +16,7 @@ export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) {
|
|||||||
return (
|
return (
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
tooltip={unwrap(provisionDefinitions.provisions[provision].name!)}
|
tooltip={unwrap(provisionDefinitions.provisions[provision].name!)}
|
||||||
icon={`/api/icons/provisions/${provision}.webp`}
|
icon={alias("api", `/icons/provisions/${provision}.webp`)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||||
import type { TankComponentButtonProps } from "./TankComponentButton";
|
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ interface ShellButtonProps extends TankComponentButtonProps {
|
|||||||
export function ShellButton({ shell, ...props }: ShellButtonProps) {
|
export function ShellButton({ shell, ...props }: ShellButtonProps) {
|
||||||
return (
|
return (
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
icon={`/api/icons/shells/${shell}.webp`}
|
icon={alias("api", `/icons/shells/${shell}.webp`)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
|
|
||||||
interface SmallTankIconProps {
|
interface SmallTankIconProps {
|
||||||
id: number;
|
id: number;
|
||||||
size?: number;
|
size?: number;
|
||||||
@@ -17,7 +19,7 @@ export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={`Tank ${id}`}
|
alt={`Tank ${id}`}
|
||||||
src={`/api/tanks/${id}/icons/small.webp`}
|
src={alias("api", `/tanks/${id}/icons/small.webp`)}
|
||||||
style={{
|
style={{
|
||||||
transform: `scale(${(size / HEIGHT) * 100}%)`,
|
transform: `scale(${(size / HEIGHT) * 100}%)`,
|
||||||
transformOrigin: "top left",
|
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 { Flex, Text, type TextProps } from "@radix-ui/themes";
|
||||||
import { uniq } from "lodash-es";
|
import { uniq } from "lodash-es";
|
||||||
import { forwardRef, type ReactNode } from "react";
|
import { forwardRef, type ReactNode } from "react";
|
||||||
@@ -60,7 +60,10 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
className="tank-search-card"
|
className="tank-search-card"
|
||||||
data-provide-link={provideLink}
|
data-provide-link={provideLink}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(/api/flags/scratched/${tank.nation}.webp)`,
|
backgroundImage: `url(${alias(
|
||||||
|
"api",
|
||||||
|
`/flags/scratched/${tank.nation}.webp`,
|
||||||
|
)})`,
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -76,7 +79,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(tank.name!)}
|
alt={unwrap(tank.name!)}
|
||||||
src={`/api/tanks/${tank.id}/icons/big.webp`}
|
src={alias("api", `/tanks/${tank.id}/icons/big.webp`)}
|
||||||
className="image"
|
className="image"
|
||||||
draggable={false}
|
draggable={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
alias,
|
||||||
GunDefinition,
|
GunDefinition,
|
||||||
ShellType,
|
ShellType,
|
||||||
TANK_CLASSES,
|
TANK_CLASSES,
|
||||||
@@ -309,7 +310,7 @@ function NationsFilter() {
|
|||||||
width: "1.25em",
|
width: "1.25em",
|
||||||
height: "1.25em",
|
height: "1.25em",
|
||||||
}}
|
}}
|
||||||
src={`/api/flags/circle/${nation}.webp`}
|
src={alias("api", `/flags/circle/${nation}.webp`)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -346,7 +347,7 @@ function NationsFilter() {
|
|||||||
right="0"
|
right="0"
|
||||||
mr="-2"
|
mr="-2"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${`/api/flags/scratched/${nation}.webp`})`,
|
backgroundImage: `url(${alias("api", `/flags/scratched/${nation}.webp`)})`,
|
||||||
backgroundSize: "100%",
|
backgroundSize: "100%",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundPosition: "5rem center",
|
backgroundPosition: "5rem center",
|
||||||
@@ -679,9 +680,10 @@ function IndividualShellFilter({
|
|||||||
{shells[index] !== null && (
|
{shells[index] !== null && (
|
||||||
<img
|
<img
|
||||||
style={{ width: "1em", height: "1em" }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
src={`/api/icons/shells/${shellTypeIcons[shells[index]]}${
|
src={alias(
|
||||||
premium ? "_premium" : ""
|
"api",
|
||||||
}.webp`}
|
`/icons/shells/${shellTypeIcons[shells[index]]}${premium ? "_premium" : ""}.webp`,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -712,9 +714,12 @@ function IndividualShellFilter({
|
|||||||
color={selected ? undefined : "gray"}
|
color={selected ? undefined : "gray"}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`/api/icons/shells/${shellTypeIcons[shellType]}${
|
src={alias(
|
||||||
premium ? "_premium" : ""
|
"api",
|
||||||
}.webp`}
|
`/icons/shells/${shellTypeIcons[shellType]}${
|
||||||
|
premium ? "_premium" : ""
|
||||||
|
}.webp`,
|
||||||
|
)}
|
||||||
style={{ width: "1.25em", height: "1.25em" }}
|
style={{ width: "1.25em", height: "1.25em" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -883,7 +888,7 @@ function ConsumablesFilter() {
|
|||||||
height: "1.25em",
|
height: "1.25em",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={`/api/icons/consumables/${consumable}.webp`}
|
src={alias("api", `/icons/consumables/${consumable}.webp`)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
@@ -928,7 +933,7 @@ function ConsumablesFilter() {
|
|||||||
height: "1.25em",
|
height: "1.25em",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={`/api/icons/consumables/${consumable}.webp`}
|
src={alias("api", `/icons/consumables/${consumable}.webp`)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{unwrap(consumableDefinition.name!)}
|
{unwrap(consumableDefinition.name!)}
|
||||||
@@ -977,7 +982,7 @@ function ProvisionsFilter() {
|
|||||||
height: "1.25em",
|
height: "1.25em",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={`/api/icons/provisions/${provision}.webp`}
|
src={alias("api", `/icons/provisions/${provision}.webp`)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
@@ -1022,7 +1027,7 @@ function ProvisionsFilter() {
|
|||||||
height: "1.25em",
|
height: "1.25em",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={`/api/icons/provisions/${provision}.webp`}
|
src={alias("api", `/icons/provisions/${provision}.webp`)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{unwrap(provisionDefinition.name!)}
|
{unwrap(provisionDefinition.name!)}
|
||||||
@@ -1151,7 +1156,10 @@ function GameModeAbilitiesFilter() {
|
|||||||
height: "1.25em",
|
height: "1.25em",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={`/api/icons/consumables/${consumable}.webp`}
|
src={alias(
|
||||||
|
"api",
|
||||||
|
`/icons/consumables/${consumable}.webp`,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{unwrap(abilityDefinition.name!)}
|
{unwrap(abilityDefinition.name!)}
|
||||||
@@ -1200,7 +1208,10 @@ function GameModeAbilitiesFilter() {
|
|||||||
height: "1.25em",
|
height: "1.25em",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={`/api/icons/provisions/${provision}.webp`}
|
src={alias(
|
||||||
|
"api",
|
||||||
|
`/icons/provisions/${provision}.webp`,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{unwrap(powerDefinition.name!)}
|
{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 { literals } from "@blitzkit/i18n";
|
||||||
import { Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes";
|
import { Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes";
|
||||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||||
@@ -62,7 +62,7 @@ export function Firepower({
|
|||||||
alt={unwrap(thisShell.name!)}
|
alt={unwrap(thisShell.name!)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
src={`/api/icons/shells/${thisShell.icon}.webp`}
|
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import { Flex, IconButton, Tooltip } from "@radix-ui/themes";
|
import { Flex, IconButton, Tooltip } from "@radix-ui/themes";
|
||||||
import { useLocale } from "../../../../../../../../hooks/useLocale";
|
import { useLocale } from "../../../../../../../../hooks/useLocale";
|
||||||
import { Duel } from "../../../../../../../../stores/duel";
|
import { Duel } from "../../../../../../../../stores/duel";
|
||||||
@@ -37,7 +38,7 @@ export function ShellSwitcher() {
|
|||||||
<img
|
<img
|
||||||
alt={unwrap(thisShell.name!)}
|
alt={unwrap(thisShell.name!)}
|
||||||
style={{ width: "1.125rem", height: "1.125rem" }}
|
style={{ width: "1.125rem", height: "1.125rem" }}
|
||||||
src={`/api/icons/shells/${thisShell.icon}.webp`}
|
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
import { isExplosive } from "@blitzkit/core";
|
import { alias, isExplosive } from "@blitzkit/core";
|
||||||
import { literals } from "@blitzkit/i18n";
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Box, Flex, Text } from "@radix-ui/themes";
|
import { Box, Flex, Text } from "@radix-ui/themes";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
@@ -124,7 +124,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src={`/api/icons/shells/${shell.icon}.webp`}
|
src={alias("api", `/icons/shells/${shell.icon}.webp`)}
|
||||||
style={{
|
style={{
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
transform: "translate(-50%, -50%) rotate(-45deg)",
|
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 { Badge, Box, Flex, Heading, Switch, Text } from "@radix-ui/themes";
|
||||||
import { useLocale } from "../../../../hooks/useLocale";
|
import { useLocale } from "../../../../hooks/useLocale";
|
||||||
import { Duel } from "../../../../stores/duel";
|
import { Duel } from "../../../../stores/duel";
|
||||||
@@ -33,7 +34,7 @@ export function Equalizer() {
|
|||||||
style={{
|
style={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
borderRadius: "var(--radius-3)",
|
borderRadius: "var(--radius-3)",
|
||||||
backgroundImage: "url(/api/gamemodes/45/banner.webp)",
|
backgroundImage: `url(${alias("api", `/gamemodes/45/banner.webp`)})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
|||||||
+3
-2
@@ -1,3 +1,4 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import { Flex, Heading } from "@radix-ui/themes";
|
import { Flex, Heading } from "@radix-ui/themes";
|
||||||
import { useLocale } from "../../../../hooks/useLocale";
|
import { useLocale } from "../../../../hooks/useLocale";
|
||||||
import { Duel } from "../../../../stores/duel";
|
import { Duel } from "../../../../stores/duel";
|
||||||
@@ -21,7 +22,7 @@ export function Miscellaneous() {
|
|||||||
<Flex gap="2" align="center">
|
<Flex gap="2" align="center">
|
||||||
{!tank.fixed_camouflage && (
|
{!tank.fixed_camouflage && (
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
icon={"/api/icons/camo.webp"}
|
icon={alias("api", "/icons/camo.webp")}
|
||||||
selected={camouflage}
|
selected={camouflage}
|
||||||
iconStyles={{
|
iconStyles={{
|
||||||
top: "50%",
|
top: "50%",
|
||||||
@@ -36,7 +37,7 @@ export function Miscellaneous() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
icon={"/api/icons/boosters/equipment.webp"}
|
icon={alias("api", "/icons/boosters/equipment.webp")}
|
||||||
selected={cooldownBooster > 0}
|
selected={cooldownBooster > 0}
|
||||||
banner={
|
banner={
|
||||||
cooldownBooster === 0
|
cooldownBooster === 0
|
||||||
|
|||||||
+6
-2
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
alias,
|
||||||
EngineDefinition,
|
EngineDefinition,
|
||||||
formatCompact,
|
formatCompact,
|
||||||
GunDefinition,
|
GunDefinition,
|
||||||
@@ -110,7 +111,7 @@ function ModuleButton({
|
|||||||
|
|
||||||
<img
|
<img
|
||||||
alt={unlock.cost!.type}
|
alt={unlock.cost!.type}
|
||||||
src={`/api/icons/currencies/${unlock.cost!.type}.webp`}
|
src={alias("api", `/icons/currencies/${unlock.cost!.type}.webp`)}
|
||||||
style={{
|
style={{
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12,
|
height: 12,
|
||||||
@@ -125,7 +126,10 @@ function ModuleButton({
|
|||||||
src={
|
src={
|
||||||
isTank
|
isTank
|
||||||
? tankIcon(unlock.id)
|
? tankIcon(unlock.id)
|
||||||
: `/api/icons/modules/${moduleTypeTypeIconName[unlock.type]}.webp`
|
: alias(
|
||||||
|
"api",
|
||||||
|
`/icons/modules/${moduleTypeTypeIconName[unlock.type]}.webp`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
width: isTank ? 64 : 32,
|
width: isTank ? 64 : 32,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import { Box, Flex, Heading, Text } from "@radix-ui/themes";
|
import { Box, Flex, Heading, Text } from "@radix-ui/themes";
|
||||||
import { api } from "../../core/blitzkit/api";
|
import { api } from "../../core/blitzkit/api";
|
||||||
import { useLocale } from "../../hooks/useLocale";
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
@@ -37,7 +38,10 @@ export function GameModeSection() {
|
|||||||
direction="column"
|
direction="column"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: "var(--radius-3)",
|
borderRadius: "var(--radius-3)",
|
||||||
background: `url(/api/gamemodes/${id}/banner.webp)`,
|
background: `url(${alias(
|
||||||
|
"api",
|
||||||
|
`/gamemodes/${id}/banner.webp`,
|
||||||
|
)})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
}}
|
}}
|
||||||
@@ -45,7 +49,10 @@ export function GameModeSection() {
|
|||||||
<Box
|
<Box
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
style={{
|
style={{
|
||||||
background: `url(/api/gamemodes/${id}/banner.webp)`,
|
background: `url(${alias(
|
||||||
|
"api",
|
||||||
|
`/gamemodes/${id}/banner.webp`,
|
||||||
|
)})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
}}
|
}}
|
||||||
@@ -80,7 +87,7 @@ export function GameModeSection() {
|
|||||||
height: "1.5em",
|
height: "1.5em",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={`/api/icons/provisions/${id}.webp`}
|
src={alias("api", `/icons/provisions/${id}.webp`)}
|
||||||
alt={unwrap(provisions.name!)}
|
alt={unwrap(provisions.name!)}
|
||||||
/>
|
/>
|
||||||
{unwrap(provisions.name!)}
|
{unwrap(provisions.name!)}
|
||||||
@@ -101,7 +108,7 @@ export function GameModeSection() {
|
|||||||
height: "1.5em",
|
height: "1.5em",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={`/api/icons/consumables/${id}.webp`}
|
src={alias("api", `/icons/consumables/${id}.webp`)}
|
||||||
alt={unwrap(consumable.name!)}
|
alt={unwrap(consumable.name!)}
|
||||||
/>
|
/>
|
||||||
{unwrap(consumable.name!)}
|
{unwrap(consumable.name!)}
|
||||||
|
|||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import { IconButton } from "@radix-ui/themes";
|
import { IconButton } from "@radix-ui/themes";
|
||||||
import { useArmor } from "../../../../../../hooks/useArmor";
|
import { useArmor } from "../../../../../../hooks/useArmor";
|
||||||
import { Duel } from "../../../../../../stores/duel";
|
import { Duel } from "../../../../../../stores/duel";
|
||||||
@@ -37,7 +38,7 @@ export function DynamicArmorSwitcher() {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Dynamic Armor"
|
alt="Dynamic Armor"
|
||||||
src={"/api/icons/consumables/73.webp"}
|
src={alias("api", "/icons/consumables/73.webp")}
|
||||||
style={{
|
style={{
|
||||||
width: "50%",
|
width: "50%",
|
||||||
height: "50%",
|
height: "50%",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
alias,
|
||||||
resolvePenetrationCoefficient,
|
resolvePenetrationCoefficient,
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
uniqueGuns,
|
uniqueGuns,
|
||||||
@@ -139,7 +140,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
|
|
||||||
<img
|
<img
|
||||||
alt="Antagonist Gun"
|
alt="Antagonist Gun"
|
||||||
src={"/api/icons/modules/gun.webp"}
|
src={alias("api", "/icons/modules/gun.webp")}
|
||||||
style={{
|
style={{
|
||||||
width: "65%",
|
width: "65%",
|
||||||
height: "65%",
|
height: "65%",
|
||||||
@@ -208,7 +209,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(thisShell.name!)}
|
alt={unwrap(thisShell.name!)}
|
||||||
src={`/api/icons/shells/${thisShell.icon}.webp`}
|
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
|
||||||
style={{
|
style={{
|
||||||
width: "50%",
|
width: "50%",
|
||||||
height: "50%",
|
height: "50%",
|
||||||
@@ -245,7 +246,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Calibrated Shells"
|
alt="Calibrated Shells"
|
||||||
src={"/api/icons/equipment/103.webp"}
|
src={alias("api", "/icons/equipment/103.webp")}
|
||||||
style={{
|
style={{
|
||||||
width: "50%",
|
width: "50%",
|
||||||
height: "50%",
|
height: "50%",
|
||||||
@@ -270,7 +271,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Enhanced Armor"
|
alt="Enhanced Armor"
|
||||||
src={"/api/icons/equipment/110.webp"}
|
src={alias("api", "/icons/equipment/110.webp")}
|
||||||
style={{
|
style={{
|
||||||
width: "50%",
|
width: "50%",
|
||||||
height: "50%",
|
height: "50%",
|
||||||
|
|||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import { Flex, type FlexProps, Progress } from "@radix-ui/themes";
|
import { Flex, type FlexProps, Progress } from "@radix-ui/themes";
|
||||||
import { useProgress } from "@react-three/drei";
|
import { useProgress } from "@react-three/drei";
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) {
|
|||||||
return (
|
return (
|
||||||
<Flex width="100%" height="100%" align="center" justify="center" {...props}>
|
<Flex width="100%" height="100%" align="center" justify="center" {...props}>
|
||||||
<img
|
<img
|
||||||
src={`/api/tanks/${id}/icons/big.webp`}
|
src={alias("api", `/tanks/${id}/icons/big.webp`)}
|
||||||
style={{ height: "75%", filter: "blur(1rem)" }}
|
style={{ height: "75%", filter: "blur(1rem)" }}
|
||||||
/>
|
/>
|
||||||
<Progress
|
<Progress
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Flex, Text, type TextProps } from '@radix-ui/themes';
|
import { Flex, Text, type TextProps } from "@radix-ui/themes";
|
||||||
import { Var } from '../../../../core/radix/var';
|
import { Var } from "../../../../core/radix/var";
|
||||||
|
|
||||||
type ListingProps = TextProps & {
|
type ListingProps = TextProps & {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -11,7 +11,7 @@ export function Listing({ label, ...props }: ListingProps) {
|
|||||||
minWidth="17rem"
|
minWidth="17rem"
|
||||||
justify="between"
|
justify="between"
|
||||||
gap="4"
|
gap="4"
|
||||||
style={{ borderBottom: `1px solid ${Var('gray-a9')}` }}
|
style={{ borderBottom: `1px solid ${Var("gray-a9")}` }}
|
||||||
>
|
>
|
||||||
<Text size="4">{label}</Text>
|
<Text size="4">{label}</Text>
|
||||||
<Text align="right" size="4" weight="medium" {...props} />
|
<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 { Box, Flex } from "@radix-ui/themes";
|
||||||
import { Var } from "../../../core/radix/var";
|
import { Var } from "../../../core/radix/var";
|
||||||
import { useLocale } from "../../../hooks/useLocale";
|
import { useLocale } from "../../../hooks/useLocale";
|
||||||
@@ -35,7 +40,7 @@ export function MetaSection() {
|
|||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
background: `url(${`/api/flags/scratched/${protagonist.nation}.webp`})`,
|
background: `url(${alias("api", `/flags/scratched/${protagonist.nation}.webp`)})`,
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundSize: "min(53rem, 80vw)",
|
backgroundSize: "min(53rem, 80vw)",
|
||||||
backgroundPosition: "-8rem 50%",
|
backgroundPosition: "-8rem 50%",
|
||||||
@@ -60,7 +65,10 @@ export function MetaSection() {
|
|||||||
<img
|
<img
|
||||||
style={{ width: "1em", height: "1em" }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt={protagonist.nation}
|
alt={protagonist.nation}
|
||||||
src={`/api/flags/circle/${protagonist.nation}.webp`}
|
src={alias(
|
||||||
|
"api",
|
||||||
|
`/flags/circle/${protagonist.nation}.webp`,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
strings.common.nations[
|
strings.common.nations[
|
||||||
@@ -130,7 +138,7 @@ export function MetaSection() {
|
|||||||
<img
|
<img
|
||||||
style={{ width: "1em", height: "1em" }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt="gold"
|
alt="gold"
|
||||||
src={"/api/icons/currencies/gold.webp"}
|
src={alias("api", "/icons/currencies/gold.webp")}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Listing>
|
</Listing>
|
||||||
@@ -147,12 +155,15 @@ export function MetaSection() {
|
|||||||
<img
|
<img
|
||||||
style={{ width: "1em", height: "1em" }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt={TankPriceType[protagonist.price!.type]}
|
alt={TankPriceType[protagonist.price!.type]}
|
||||||
src={`/api/icons/currencies/${
|
src={alias(
|
||||||
protagonist.price!.type ===
|
"api",
|
||||||
TankPriceType.TANK_PRICE_TYPE_GOLD
|
`/icons/currencies/${
|
||||||
? "gold"
|
protagonist.price!.type ===
|
||||||
: "silver"
|
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||||
}.webp`}
|
? "gold"
|
||||||
|
: "silver"
|
||||||
|
}.webp`,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Listing>
|
</Listing>
|
||||||
@@ -162,12 +173,15 @@ export function MetaSection() {
|
|||||||
<img
|
<img
|
||||||
style={{ width: "1em", height: "1em" }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt={TankPriceType[protagonist.price!.type]}
|
alt={TankPriceType[protagonist.price!.type]}
|
||||||
src={`/api/icons/currencies/${
|
src={alias(
|
||||||
protagonist.price!.type ===
|
"api",
|
||||||
TankPriceType.TANK_PRICE_TYPE_GOLD
|
`/icons/currencies/${
|
||||||
? "gold"
|
protagonist.price!.type ===
|
||||||
: "silver"
|
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||||
}.webp`}
|
? "gold"
|
||||||
|
: "silver"
|
||||||
|
}.webp`,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Listing>
|
</Listing>
|
||||||
@@ -183,7 +197,7 @@ export function MetaSection() {
|
|||||||
<img
|
<img
|
||||||
style={{ width: "1em", height: "1em" }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt="xp"
|
alt="xp"
|
||||||
src={"/api/icons/currencies/xp.webp"}
|
src={alias("api", "/icons/currencies/xp.webp")}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Listing>
|
</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 { literals } from "@blitzkit/i18n/src/literals";
|
||||||
import { Box, Flex, Skeleton, Text } from "@radix-ui/themes";
|
import { Box, Flex, Skeleton, Text } from "@radix-ui/themes";
|
||||||
import { awaitableAverageDefinitions } from "../../../../core/awaitables/averageDefinitions";
|
import { awaitableAverageDefinitions } from "../../../../core/awaitables/averageDefinitions";
|
||||||
@@ -58,7 +58,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
mx={highlight ? "2" : "0"}
|
mx={highlight ? "2" : "0"}
|
||||||
style={{
|
style={{
|
||||||
background: highlight
|
background: highlight
|
||||||
? `url(${`/api/flags/fade/${tank.nation}.webp`})`
|
? `url(${alias("api", `/flags/fade/${tank.nation}.webp`)})`
|
||||||
: undefined,
|
: undefined,
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
@@ -79,7 +79,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(tank.name!)}
|
alt={unwrap(tank.name!)}
|
||||||
src={`/api/tanks/${id}/icons/big.webp`}
|
src={alias("api", `/tanks/${id}/icons/big.webp`)}
|
||||||
width={64}
|
width={64}
|
||||||
height={64}
|
height={64}
|
||||||
style={{
|
style={{
|
||||||
@@ -99,7 +99,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
<Flex gap="1" align="center">
|
<Flex gap="1" align="center">
|
||||||
<img
|
<img
|
||||||
alt="XP"
|
alt="XP"
|
||||||
src={"/api/icons/currencies/xp.webp"}
|
src={alias("api", "/icons/currencies/xp.webp")}
|
||||||
style={{
|
style={{
|
||||||
width: "1em",
|
width: "1em",
|
||||||
height: "1em",
|
height: "1em",
|
||||||
@@ -114,7 +114,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
<Flex gap="1" align="center">
|
<Flex gap="1" align="center">
|
||||||
<img
|
<img
|
||||||
alt="Silver"
|
alt="Silver"
|
||||||
src={"/api/icons/currencies/silver.webp"}
|
src={alias("api", "/icons/currencies/silver.webp")}
|
||||||
style={{
|
style={{
|
||||||
width: "1em",
|
width: "1em",
|
||||||
height: "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 { literals } from "@blitzkit/i18n/src/literals";
|
||||||
import { CaretLeftIcon, CaretRightIcon, PlusIcon } from "@radix-ui/react-icons";
|
import { CaretLeftIcon, CaretRightIcon, PlusIcon } from "@radix-ui/react-icons";
|
||||||
import { Flex, Heading, IconButton, ScrollArea, Text } from "@radix-ui/themes";
|
import { Flex, Heading, IconButton, ScrollArea, Text } from "@radix-ui/themes";
|
||||||
@@ -105,7 +105,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
<Flex gap="1" align="center">
|
<Flex gap="1" align="center">
|
||||||
<img
|
<img
|
||||||
alt="XP"
|
alt="XP"
|
||||||
src={"/api/icons/currencies/xp.webp"}
|
src={alias("api", "/icons/currencies/xp.webp")}
|
||||||
style={{
|
style={{
|
||||||
width: "1em",
|
width: "1em",
|
||||||
height: "1em",
|
height: "1em",
|
||||||
@@ -121,7 +121,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
<Flex gap="1" align="center">
|
<Flex gap="1" align="center">
|
||||||
<img
|
<img
|
||||||
alt="Silver"
|
alt="Silver"
|
||||||
src={"/api/icons/currencies/silver.webp"}
|
src={alias("api", "/icons/currencies/silver.webp")}
|
||||||
style={{
|
style={{
|
||||||
width: "1em",
|
width: "1em",
|
||||||
height: "1em",
|
height: "1em",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { assertSecret } from "@blitzkit/core";
|
import { assertSecret, environmentConfig } from "@blitzkit/core";
|
||||||
import type { GetStaticPaths } from "astro";
|
import type { GetStaticPaths } from "astro";
|
||||||
|
|
||||||
export function chunkedStaticPaths(chunk: string) {
|
export function chunkedStaticPaths(chunk: string) {
|
||||||
return function () {
|
return function () {
|
||||||
|
const environment = environmentConfig();
|
||||||
const chunksEnv = assertSecret(import.meta.env.CHUNK);
|
const chunksEnv = assertSecret(import.meta.env.CHUNK);
|
||||||
const chunks = chunksEnv.split(",");
|
const chunks = chunksEnv.split(",");
|
||||||
|
|
||||||
@@ -10,6 +11,8 @@ export function chunkedStaticPaths(chunk: string) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [{ params: { [`chunk_${chunk}`]: undefined } }];
|
return [
|
||||||
|
{ params: { [`chunk_${chunk}`]: environment.paths[chunk] ?? undefined } },
|
||||||
|
];
|
||||||
} satisfies GetStaticPaths;
|
} satisfies GetStaticPaths;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import { useLoader } from "@react-three/fiber";
|
import { useLoader } from "@react-three/fiber";
|
||||||
import { Mesh, MeshStandardMaterial } from "three";
|
import { Mesh, MeshStandardMaterial } from "three";
|
||||||
import { GLTFLoader } from "three-stdlib";
|
import { GLTFLoader } from "three-stdlib";
|
||||||
import { useDispose } from "./useDispose";
|
import { useDispose } from "./useDispose";
|
||||||
|
|
||||||
export function useModel(id: number) {
|
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);
|
const gltf = useLoader(GLTFLoader, path);
|
||||||
|
|
||||||
useDispose(gltf, 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 {
|
import {
|
||||||
|
alias,
|
||||||
GunDefinition,
|
GunDefinition,
|
||||||
TankDefinition,
|
TankDefinition,
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
@@ -110,7 +111,7 @@ function ModuleChooser({
|
|||||||
height: "4em",
|
height: "4em",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={`/api/tanks/${tank}/icons/big.webp`}
|
src={alias("api", `/tanks/${tank}/icons/big.webp`)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { AstroSeo } from "@astrolib/seo";
|
import { AstroSeo } from "@astrolib/seo";
|
||||||
import {
|
import {
|
||||||
|
alias,
|
||||||
assertSecret,
|
assertSecret,
|
||||||
createDefaultProvisions,
|
createDefaultProvisions,
|
||||||
createDefaultSkills,
|
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 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 turret = tank.turrets.at(-1)!;
|
||||||
const gun = turret.guns.at(-1)!;
|
const gun = turret.guns.at(-1)!;
|
||||||
const shell = gun.shells[0];
|
const shell = gun.shells[0];
|
||||||
|
|||||||
Reference in new issue
Block a user