currency icons

This commit is contained in:
tresabhi committed 2026-05-15 18:16:06 -05:00
1 parent f0e0d849d5
commit d9ce16b41a
6 files changed
+83 -35

No files matched your search

@@ -36,13 +36,12 @@ function PriceEntry({ entry }: PriceEntryProps) {
throw new Error("Unsupported price type");
}
const [, iconName] = entry.currency_price.currency_catalog_id.split(".");
return (
<Text size="minor" lowContrast className={styles.entry}>
<div className={styles.wrapper}>
<img
className={styles.icon}
src="https://static.vecteezy.com/system/resources/thumbnails/047/493/988/small/hairy-fluffy-cat-playing-png.png"
/>
<img className={styles.icon} src={`/api/currencies/${iconName}.webp`} />
{formatCompact(locale, entry.currency_price.amount)}
</div>
</Text>
@@ -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 {
@@ -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 (
<IconButton
<Button
color={isSelected ? undefined : "gray"}
variant={isSelected ? "surface" : "soft"}
radius="1"
@@ -183,6 +183,6 @@ function LineElement({ index, lineName, stage }: LineElementProps) {
</Text>
{price && <Price className={styles.price} price={price} />}
</IconButton>
</Button>
);
}
@@ -41,6 +41,8 @@ export abstract class AbstractAPI {
abstract background(id: string): Promise<Background>;
abstract backgrounds(): Promise<Backgrounds>;
abstract currencyIcon(id: string): Promise<string>;
private assertLocale(locale: string) {
const supported = locales.supported.find(
(supported) => supported.locale === locale,
+57 -27
View File
@@ -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<string, string> = {};
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}`;
}
}
@@ -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);
}