feat: playlist

This commit is contained in:
TrèsAbhi committed 2025-10-12 18:51:28 -05:00
1 parent 5ba64f231a
commit ed0dde37d2
10 files changed
+141 -11

No files matched your search

+8
View File
@@ -859,6 +859,14 @@
},
"tools": {
"playlist": {
"name": "Playlist",
"description": "Tank playlist generator",
"seo_description": "Don't know what to play in World of Tanks Blitz? Use BlitzKit Playlist to get suggestions!",
"button": "Generate",
"pool_size": "{count} possible tanks"
},
"mixer": {
"name": "Mixer",
"button": "Mix and match",
Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

@@ -4,11 +4,11 @@
const isAPI = window.location.pathname.startsWith("/api");
const isI18nPath = locales.supported.some(({ locale }) =>
window.location.pathname.startsWith(`/${locale}`)
window.location.pathname.startsWith(`/${locale}/`)
);
const bestLocale = getBestLocale();
const currentPath = locales.supported.find(({ locale }) =>
window.location.pathname.startsWith(`/${locale}`)
window.location.pathname.startsWith(`/${locale}/`)
);
if (
@@ -0,0 +1,82 @@
import { tankIcon } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n";
import { Box, Flex, Grid, Text } from "@radix-ui/themes";
import { useMemo } from "react";
import usePromise from "react-promise-suspense";
import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions";
import { filterTanks } from "../core/blitzkit/filterTanks";
import { useLocale } from "../hooks/useLocale";
import { TankFilters } from "../stores/tankFilters";
import { FilterControl } from "./TankSearch/components/FilterControl";
const tankDefinitions = await awaitableTankDefinitions;
export function PlaylistPool() {
const { strings } = useLocale();
const filters = TankFilters.use();
const randomTankList = useMemo(() => {
const list = Object.values(tankDefinitions.tanks);
for (let i = list.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[list[i], list[j]] = [list[j], list[i]];
}
return list;
}, [filters]);
const tanks = usePromise(
() => filterTanks(filters, randomTankList),
// react-promise-suspense has awful type annotations
[filters] as any
);
const tanksTrimmed = useMemo(() => tanks.slice(0, 48), [filters]);
return (
<Flex direction="column" gap="4" align="center">
<FilterControl />
<Text color="gray">
{literals(strings.website.tools.playlist.pool_size, {
count: tanks.length,
})}
</Text>
<Box position="relative" height="100%" width="100%" overflow="hidden">
<Grid
columns="repeat(auto-fill, minmax(4rem, 1fr))"
flow="dense"
overflow="hidden"
position="absolute"
top="0"
left="0"
width="100%"
>
{tanksTrimmed.map((tank) => (
<Box
key={tank.id}
style={{
aspectRatio: "4 / 3",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundImage: `url(${tankIcon(tank.id)})`,
backgroundRepeat: "no-repeat",
}}
/>
))}
</Grid>
<Box
position="absolute"
top="0"
left="0"
width="100%"
height="100%"
style={{
background:
"linear-gradient(transparent, var(--gray-1) calc(100% - 2rem))",
}}
/>
</Box>
</Flex>
);
}
+7 -3
View File
@@ -35,14 +35,18 @@ export const tools: Record<string, Tool> = {
disabled: true,
button: "bronze",
},
guess: {
id: "guess",
button: "cyan",
playlist: {
id: "playlist",
button: "tomato",
},
mixer: {
id: "mixer",
button: "gray",
},
guess: {
id: "guess",
button: "cyan",
},
gallery: {
id: "gallery",
button: "gold",
@@ -16,11 +16,11 @@ export interface Props {
noFooter?: boolean;
noNavbar?: boolean;
integrated?: boolean;
locale: string;
}
const servicing = import.meta.env.SERVICING;
const { noFooter, noNavbar, integrated } = Astro.props;
const locale = Astro.params.locale;
const { noFooter, noNavbar, integrated, locale } = Astro.props;
---
<html lang={locale}>
@@ -7,10 +7,10 @@ import BaseLayout from "./BaseLayout.astro";
interface Props {
tool: Tool;
locale: string;
}
const { tool } = Astro.props;
const { locale } = Astro.params;
const { tool, locale } = Astro.props;
const strings = getStrings(locale);
const toolStrings =
strings.website.tools[
@@ -20,7 +20,7 @@ const toolStrings =
<ToolOpenGraph {tool} />
<BaseLayout>
<BaseLayout {locale}>
<Box
style={{
backgroundImage: `url(/assets/images/tools/${tool.id}.webp)`,
@@ -12,7 +12,7 @@ const { locale } = Astro.params;
<ToolOpenGraph tool={tools.performance} />
<BaseLayout noFooter>
<BaseLayout {locale} noFooter>
<Page client:only="react" {locale}>
<Page skeleton slot="fallback" {locale} />
</Page>
@@ -0,0 +1,21 @@
import { PageWrapper } from "../../../components/PageWrapper";
import { PlaylistPool } from "../../../components/PlaylistFilters";
import { LocaleProvider } from "../../../hooks/useLocale";
import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps";
interface Props {
locale: string;
}
export function Page({
locale,
skeleton,
}: Props & MaybeSkeletonComponentProps) {
return (
<LocaleProvider locale={locale}>
<PageWrapper color="tomato" direction="row">
<PlaylistPool />
</PageWrapper>
</LocaleProvider>
);
}
@@ -0,0 +1,15 @@
---
import { tools } from "../../../constants/tools";
import ToolLayout from "../../../layouts/ToolLayout.astro";
import { Page } from "./_index.tsx";
export { getStaticPaths } from "../index.astro";
const { locale } = Astro.params;
---
<ToolLayout {locale} tool={tools.playlist}>
<Page {locale} client:only="react">
<Page {locale} skeleton slot="fallback" />
</Page>
</ToolLayout>