From bfceacdf3cfaecd609f8b9df6a2dab2c3bffcb20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 12 Oct 2025 19:37:44 -0500 Subject: [PATCH] feat: playlist table --- bun.lock | 7 +++- packages/i18n/strings/en.json | 5 +++ packages/varuna/package.json | 3 +- .../src/components/PlaylistFilters.tsx | 12 ++++-- .../src/components/PlaylistGenerator.tsx | 34 ++++++++++++++++ .../website/src/components/PlaylistOrder.tsx | 38 ++++++++++++++++++ .../src/core/blitzkit/generatePlaylist.ts | 40 +++++++++++++++++++ packages/website/src/core/blitzkit/pwa.ts | 0 .../src/pages/[...locale]/playlist/_index.tsx | 21 +++++++++- packages/website/src/stores/playlist.ts | 13 ++++++ 10 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 packages/website/src/components/PlaylistGenerator.tsx create mode 100644 packages/website/src/components/PlaylistOrder.tsx create mode 100644 packages/website/src/core/blitzkit/generatePlaylist.ts delete mode 100644 packages/website/src/core/blitzkit/pwa.ts create mode 100644 packages/website/src/stores/playlist.ts diff --git a/bun.lock b/bun.lock index 79e088ab6..8c5daddbe 100644 --- a/bun.lock +++ b/bun.lock @@ -118,8 +118,9 @@ "packages/varuna": { "name": "varuna", "dependencies": { + "@types/react": "^19.2.2", "immer": "^10.1.3", - "react": "^19.1.1", + "react": "^19.2.0", }, }, "packages/website": { @@ -2880,6 +2881,10 @@ "unbzip2-stream/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + "varuna/@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + + "varuna/react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + "vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 0af384bb6..9a7815e7c 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -860,6 +860,11 @@ "tools": { "playlist": { + "table": { + "tank": "Tank" + }, + "no_login_warning": "You are not logged in. BlitzKit will not keep track of your played tanks.", + "generate": "Generate 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!", diff --git a/packages/varuna/package.json b/packages/varuna/package.json index ef1e420e5..afc1716cb 100644 --- a/packages/varuna/package.json +++ b/packages/varuna/package.json @@ -3,7 +3,8 @@ "type": "module", "main": "src/index.ts", "dependencies": { + "@types/react": "^19.2.2", "immer": "^10.1.3", - "react": "^19.1.1" + "react": "^19.2.0" } } diff --git a/packages/website/src/components/PlaylistFilters.tsx b/packages/website/src/components/PlaylistFilters.tsx index 8df4cc594..5f8e83fb6 100644 --- a/packages/website/src/components/PlaylistFilters.tsx +++ b/packages/website/src/components/PlaylistFilters.tsx @@ -7,12 +7,13 @@ import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; import { filterTanks } from "../core/blitzkit/filterTanks"; import { useLocale } from "../hooks/useLocale"; import { TankFilters } from "../stores/tankFilters"; +import { LinkI18n } from "./LinkI18n"; import { FilterControl } from "./TankSearch/components/FilterControl"; const tankDefinitions = await awaitableTankDefinitions; export function PlaylistPool() { - const { strings } = useLocale(); + const { strings, locale } = useLocale(); const filters = TankFilters.use(); const randomTankList = useMemo(() => { const list = Object.values(tankDefinitions.tanks); @@ -52,7 +53,10 @@ export function PlaylistPool() { width="100%" > {tanksTrimmed.map((tank) => ( - diff --git a/packages/website/src/components/PlaylistGenerator.tsx b/packages/website/src/components/PlaylistGenerator.tsx new file mode 100644 index 000000000..e8a968c1d --- /dev/null +++ b/packages/website/src/components/PlaylistGenerator.tsx @@ -0,0 +1,34 @@ +import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; +import { Button, Flex, Text } from "@radix-ui/themes"; +import { generatePlaylist } from "../core/blitzkit/generatePlaylist"; +import { useLocale } from "../hooks/useLocale"; +import { App } from "../stores/app"; + +export function PlaylistGenerator() { + const { strings } = useLocale(); + const wargaming = App.use((state) => state.logins.wargaming); + + return ( + + + + {!wargaming && ( + + + + {strings.website.tools.playlist.no_login_warning} + + + )} + + ); +} diff --git a/packages/website/src/components/PlaylistOrder.tsx b/packages/website/src/components/PlaylistOrder.tsx new file mode 100644 index 000000000..e1439384f --- /dev/null +++ b/packages/website/src/components/PlaylistOrder.tsx @@ -0,0 +1,38 @@ +import { Checkbox, Flex, Table } from "@radix-ui/themes"; +import { useLocale } from "../hooks/useLocale"; +import { Playlist } from "../stores/playlist"; +import { TankRowHeaderCell } from "./TankRowHeaderCell"; + +export function PlaylistOrder() { + const { unwrap, strings } = useLocale(); + const list = Playlist.use((state) => state.list); + + if (!list) return null; + + return ( + + + + + + + {strings.website.tools.playlist.table.tank} + + + + + + {list.map(({ tank }) => ( + + + 0.5} /> + + + + + ))} + + + + ); +} diff --git a/packages/website/src/core/blitzkit/generatePlaylist.ts b/packages/website/src/core/blitzkit/generatePlaylist.ts new file mode 100644 index 000000000..b47235906 --- /dev/null +++ b/packages/website/src/core/blitzkit/generatePlaylist.ts @@ -0,0 +1,40 @@ +import { + getTankStats, + idToRegion, + type IndividualTankStats, +} from "@blitzkit/core"; +import { App } from "../../stores/app"; +import { Playlist } from "../../stores/playlist"; +import { TankFilters } from "../../stores/tankFilters"; +import { awaitableTankDefinitions } from "../awaitables/tankDefinitions"; +import { filterTanks } from "./filterTanks"; + +const tankDefinitions = await awaitableTankDefinitions; +const tanks = Object.values(tankDefinitions.tanks); + +export async function generatePlaylist() { + const id = App.state.logins.wargaming?.id; + const filtered = await filterTanks(TankFilters.state, tanks); + const statsList = id ? await getTankStats(idToRegion(id), id) : undefined; + const stats: Record = {}; + + if (statsList) { + for (const tankStat of statsList) { + stats[tankStat.tank_id] = tankStat; + } + } + + for (let i = filtered.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [filtered[i], filtered[j]] = [filtered[j], filtered[i]]; + } + + Playlist.mutate((draft) => { + draft.list = filtered.map((tank, index) => ({ + index, + tank, + battles: stats[tank.id]?.all.battles, + wins: stats[tank.id]?.all.wins, + })); + }); +} diff --git a/packages/website/src/core/blitzkit/pwa.ts b/packages/website/src/core/blitzkit/pwa.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/website/src/pages/[...locale]/playlist/_index.tsx b/packages/website/src/pages/[...locale]/playlist/_index.tsx index 7aa84a8aa..ce45efe53 100644 --- a/packages/website/src/pages/[...locale]/playlist/_index.tsx +++ b/packages/website/src/pages/[...locale]/playlist/_index.tsx @@ -1,6 +1,12 @@ +import { useEffect } from "react"; import { PageWrapper } from "../../../components/PageWrapper"; import { PlaylistPool } from "../../../components/PlaylistFilters"; +import { PlaylistGenerator } from "../../../components/PlaylistGenerator"; +import { PlaylistOrder } from "../../../components/PLaylistOrder"; import { LocaleProvider } from "../../../hooks/useLocale"; +import { App } from "../../../stores/app"; +import { Playlist } from "../../../stores/playlist"; +import { TankFilters } from "../../../stores/tankFilters"; import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; interface Props { @@ -11,10 +17,23 @@ export function Page({ locale, skeleton, }: Props & MaybeSkeletonComponentProps) { + const list = Playlist.use((state) => state.list); + const wargaming = App.use((state) => state.logins.wargaming); + + useEffect(() => { + if (wargaming) { + TankFilters.mutate((draft) => { + draft.ownership = "owned"; + }); + } + }, []); + return ( - + + {!list && } + {list && } ); diff --git a/packages/website/src/stores/playlist.ts b/packages/website/src/stores/playlist.ts new file mode 100644 index 000000000..2084e15e7 --- /dev/null +++ b/packages/website/src/stores/playlist.ts @@ -0,0 +1,13 @@ +import type { TankDefinition } from "@blitzkit/core"; +import { Varuna } from "varuna"; + +export interface Playlist { + list?: { + index: number; + tank: TankDefinition; + battles?: number; + wins?: number; + }[]; +} + +export const Playlist = new Varuna({}, "playlist");