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; +--- + + + + + +