diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json
index 1cd725156..0af384bb6 100644
--- a/packages/i18n/strings/en.json
+++ b/packages/i18n/strings/en.json
@@ -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",
diff --git a/packages/website/public/assets/images/tools/playlist.webp b/packages/website/public/assets/images/tools/playlist.webp
new file mode 100644
index 000000000..3504be291
Binary files /dev/null and b/packages/website/public/assets/images/tools/playlist.webp differ
diff --git a/packages/website/src/components/LocaleRedirector.astro b/packages/website/src/components/LocaleRedirector.astro
index b234ba4f3..68f61e45b 100644
--- a/packages/website/src/components/LocaleRedirector.astro
+++ b/packages/website/src/components/LocaleRedirector.astro
@@ -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 (
diff --git a/packages/website/src/components/PlaylistFilters.tsx b/packages/website/src/components/PlaylistFilters.tsx
new file mode 100644
index 000000000..8df4cc594
--- /dev/null
+++ b/packages/website/src/components/PlaylistFilters.tsx
@@ -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 (
+
+
+
+
+ {literals(strings.website.tools.playlist.pool_size, {
+ count: tanks.length,
+ })}
+
+
+
+
+ {tanksTrimmed.map((tank) => (
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/packages/website/src/constants/tools.ts b/packages/website/src/constants/tools.ts
index 6188a1b19..4dcb9ec69 100644
--- a/packages/website/src/constants/tools.ts
+++ b/packages/website/src/constants/tools.ts
@@ -35,14 +35,18 @@ export const tools: Record = {
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",
diff --git a/packages/website/src/layouts/BaseLayout.astro b/packages/website/src/layouts/BaseLayout.astro
index 006807c4f..dc1070859 100644
--- a/packages/website/src/layouts/BaseLayout.astro
+++ b/packages/website/src/layouts/BaseLayout.astro
@@ -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;
---
diff --git a/packages/website/src/layouts/ToolLayout.astro b/packages/website/src/layouts/ToolLayout.astro
index 11f4190ab..348fec199 100644
--- a/packages/website/src/layouts/ToolLayout.astro
+++ b/packages/website/src/layouts/ToolLayout.astro
@@ -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 =
-
+
-
+
diff --git a/packages/website/src/pages/[...locale]/playlist/_index.tsx b/packages/website/src/pages/[...locale]/playlist/_index.tsx
new file mode 100644
index 000000000..7aa84a8aa
--- /dev/null
+++ b/packages/website/src/pages/[...locale]/playlist/_index.tsx
@@ -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 (
+
+
+
+
+
+ );
+}
diff --git a/packages/website/src/pages/[...locale]/playlist/index.astro b/packages/website/src/pages/[...locale]/playlist/index.astro
new file mode 100644
index 000000000..33cce4318
--- /dev/null
+++ b/packages/website/src/pages/[...locale]/playlist/index.astro
@@ -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;
+---
+
+
+
+
+
+