diff --git a/packages/website-ue/src/components/Price/index.tsx b/packages/website-ue/src/components/Price/index.tsx index 5cb2d9efe..a2f262204 100644 --- a/packages/website-ue/src/components/Price/index.tsx +++ b/packages/website-ue/src/components/Price/index.tsx @@ -36,13 +36,12 @@ function PriceEntry({ entry }: PriceEntryProps) { throw new Error("Unsupported price type"); } + const [, iconName] = entry.currency_price.currency_catalog_id.split("."); + return ( - + {formatCompact(locale, entry.currency_price.amount)} diff --git a/packages/website-ue/src/components/TankopediaLoadout/index.module.css b/packages/website-ue/src/components/TankopediaLoadout/index.module.css index d9716fdc4..d9cadd4df 100644 --- a/packages/website-ue/src/components/TankopediaLoadout/index.module.css +++ b/packages/website-ue/src/components/TankopediaLoadout/index.module.css @@ -11,13 +11,17 @@ gap: var(--space-3); .stage { + padding: var(--space-3); position: relative; + aspect-ratio: 3 / 4; + + font-size: 2em; .icon { width: 1em; height: 1em; - transform: translateX(var(--space-1)); + transform: translate(var(--space-1), calc(-1 * var(--space-1))); } .tier { diff --git a/packages/website-ue/src/components/TankopediaLoadout/index.tsx b/packages/website-ue/src/components/TankopediaLoadout/index.tsx index 66e0d190b..0fd49d5f9 100644 --- a/packages/website-ue/src/components/TankopediaLoadout/index.tsx +++ b/packages/website-ue/src/components/TankopediaLoadout/index.tsx @@ -8,8 +8,8 @@ import { useProtagonist } from "../../hooks/useProtagonist"; import { useStrings } from "../../hooks/useStrings"; import { useUpgradePreset } from "../../hooks/useUpgradePreset"; import { Tankopedia } from "../../stores/tankopedia"; +import { Button } from "../Button"; import { Heading } from "../Heading"; -import { IconButton } from "../IconButton"; import { Price } from "../Price"; import { Section } from "../Section"; import { Text } from "../Text"; @@ -117,7 +117,7 @@ function LineElement({ index, lineName, stage }: LineElementProps) { const isSelected = upgrades[lineName] === index; return ( - {price && } - + ); } diff --git a/packages/website-ue/src/core/api/abstract.ts b/packages/website-ue/src/core/api/abstract.ts index 881416ba7..2a3d61dc2 100644 --- a/packages/website-ue/src/core/api/abstract.ts +++ b/packages/website-ue/src/core/api/abstract.ts @@ -41,6 +41,8 @@ export abstract class AbstractAPI { abstract background(id: string): Promise; abstract backgrounds(): Promise; + abstract currencyIcon(id: string): Promise; + private assertLocale(locale: string) { const supported = locales.supported.find( (supported) => supported.locale === locale, diff --git a/packages/website-ue/src/core/api/server.ts b/packages/website-ue/src/core/api/server.ts index 19e4a58ad..f825bd0cd 100644 --- a/packages/website-ue/src/core/api/server.ts +++ b/packages/website-ue/src/core/api/server.ts @@ -35,6 +35,40 @@ export class ServerAPI extends AbstractAPI { super(); } + resolveRemoteUrl(remoteUrls: string[]) { + let remoteStorage: RemoteStorageComponent | undefined = undefined; + + for (const remoteUrl of remoteUrls) { + const item = this.metadata.item(remoteUrl); + const candidate = item.RemoteStorage(); + + if ( + remoteStorage === undefined || + candidate.download_speed_weight > remoteStorage.download_speed_weight + ) { + remoteStorage = candidate; + } + } + + if (remoteStorage === undefined) { + throw new Error("No suitable production remote storage found"); + } + + return remoteStorage; + } + + resolveClientConfig() { + const group = this.metadata.group("ClientConfigsEntity"); + + if (group.length !== 1) { + throw new RangeError( + `Don't know how to handle ${group.length} ClientConfigsEntities`, + ); + } + + return this.metadata.item(group[0].id); + } + @Cache() async tankList() { const group = this.metadata.group("TankEntity"); @@ -207,37 +241,14 @@ export class ServerAPI extends AbstractAPI { @Cache() async gameStrings(locale: string) { - const group = this.metadata.group("ClientConfigsEntity"); - - if (group.length !== 1) { - throw new RangeError( - `Don't know how to handle ${group.length} ClientConfigsEntities`, - ); - } - - const clientConfig = this.metadata.item(group[0].id); + const clientConfig = this.resolveClientConfig(); const localizationResources = clientConfig.LocalizationResources(); const strings: Record = {}; for (const remoteStorageSettings of localizationResources.remote_storages) { - let remoteStorage: RemoteStorageComponent | undefined = undefined; - - for (const remoteUrl of remoteStorageSettings.remote_urls) { - const item = this.metadata.item(remoteUrl); - const candidate = item.RemoteStorage(); - - if ( - remoteStorage === undefined || - candidate.download_speed_weight > remoteStorage.download_speed_weight - ) { - remoteStorage = candidate; - } - } - - if (remoteStorage === undefined) { - throw new Error("No suitable production remote storage found"); - } - + const remoteStorage = this.resolveRemoteUrl( + remoteStorageSettings.remote_urls, + ); const configPath = `${remoteStorage.url}${remoteStorage.relative_path}/config.yaml`; const config = await fetch(configPath) .then((response) => response.text()) @@ -344,4 +355,23 @@ export class ServerAPI extends AbstractAPI { return { backgrounds }; } + + @Cache() + async currencyIcon(id: string) { + const item = this.metadata.item(`CurrencyEntity.${id}`); + const stuffUI = item.StuffUI("UIComponent"); + const config = this.resolveClientConfig(); + const metroResources = config.MetroResources(); + const remoteStorage = this.resolveRemoteUrl( + metroResources.remote_storage!.remote_urls, + ); + + const iconPathHack = stuffUI.icon.replace("\n", ""); + + if (!stuffUI.icon.includes("\n")) { + throw new Error("Icon path doesn't include newline, can't apply hack"); + } + + return `${remoteStorage.url}${remoteStorage.relative_path}${iconPathHack}`; + } } diff --git a/packages/website-ue/src/pages/api/currencies/[id].webp.ts b/packages/website-ue/src/pages/api/currencies/[id].webp.ts new file mode 100644 index 000000000..71d8c3ef9 --- /dev/null +++ b/packages/website-ue/src/pages/api/currencies/[id].webp.ts @@ -0,0 +1,13 @@ +import type { GetStaticPaths } from "astro"; +import { api } from "../../../core/api/dynamic"; +import { imageProxy } from "../../../core/api/imageProxy"; + +export const getStaticPaths = (async () => { + const group = api.metadata.group("CurrencyEntity"); + return group.map(({ name }) => ({ params: { id: name } })); +}) satisfies GetStaticPaths; + +export async function GET({ params }: { params: { id: string } }) { + const icon = await api.currencyIcon(params.id); + return await imageProxy(icon); +}