From ea048da7a24f68fdc32648a3ce3ff8eb86916e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Oct 2025 21:36:37 -0500 Subject: [PATCH] feat: tracking --- packages/i18n/strings/en.json | 12 +- .../src/components/PlaylistActions.tsx | 63 ++++++++- .../src/components/PlaylistGenerator.tsx | 124 ++++++++++-------- .../website/src/components/PlaylistOrder.tsx | 2 + .../website/src/components/PlaylistTable.tsx | 14 +- .../src/components/PlaylistTankEntry.tsx | 79 ++++++++--- .../src/components/PlaylistTracking.tsx | 14 ++ .../src/core/blitzkit/generatePlaylist.ts | 19 ++- .../src/core/blitzkit/updatePlaylist.ts | 32 +++++ .../pages/[...locale]/playlist/index.astro | 9 +- packages/website/src/stores/playlist.ts | 10 +- 11 files changed, 272 insertions(+), 106 deletions(-) create mode 100644 packages/website/src/components/PlaylistTracking.tsx create mode 100644 packages/website/src/core/blitzkit/updatePlaylist.ts diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 35dce2336..367e61d45 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -862,14 +862,22 @@ "playlist": { "reset": "Reset", "shuffle": "Shuffle", - "sort": "Sort by", + "remove": "Remove played", + + "sort": { + "button": "Sort by", + "last_played": "Last played", + "least_played": "Least played" + }, "table": { "index": "#", "never": "Never", "tank": "Tank", "last_played": "Last played", - "battles": "Battles" + "battles": "Career battles", + "unrecorded": "--", + "wins": "Wins" }, "no_login_warning": "You are not logged in. BlitzKit will not keep track of your played tanks.", "generate": "Generate playlist", diff --git a/packages/website/src/components/PlaylistActions.tsx b/packages/website/src/components/PlaylistActions.tsx index 3d8392122..117c85da6 100644 --- a/packages/website/src/components/PlaylistActions.tsx +++ b/packages/website/src/components/PlaylistActions.tsx @@ -1,11 +1,13 @@ import { fisherYates } from "@blitzkit/core"; import { CaretDownIcon } from "@radix-ui/react-icons"; -import { Button, Flex } from "@radix-ui/themes"; +import { Button, DropdownMenu, Flex } from "@radix-ui/themes"; import { useLocale } from "../hooks/useLocale"; +import { App } from "../stores/app"; import { Playlist } from "../stores/playlist"; export function PlaylistActions() { const { strings } = useLocale(); + const wargaming = App.use((state) => state.logins.wargaming); return ( @@ -22,7 +24,7 @@ export function PlaylistActions() { - + {wargaming && ( + <> + + + + + + + + + { + Playlist.mutate((draft) => { + draft.list = draft.list?.sort( + (a, b) => (a.then?.last ?? 0) - (b.then?.last ?? 0) + ); + }); + }} + > + {strings.website.tools.playlist.sort.last_played} + + { + Playlist.mutate((draft) => { + draft.list = draft.list?.sort( + (a, b) => (a.then?.battles ?? 0) - (b.then?.battles ?? 0) + ); + }); + }} + > + {strings.website.tools.playlist.sort.least_played} + + + + + )} ); } diff --git a/packages/website/src/components/PlaylistGenerator.tsx b/packages/website/src/components/PlaylistGenerator.tsx index 1656e22a3..8a7260079 100644 --- a/packages/website/src/components/PlaylistGenerator.tsx +++ b/packages/website/src/components/PlaylistGenerator.tsx @@ -33,73 +33,81 @@ export function PlaylistGenerator() { const tanksTrimmed = useMemo(() => tanks.slice(0, 65), [filters]); return ( - - - - - - - {!wargaming && ( - - - - {strings.website.tools.playlist.no_login_warning} - - - )} - - + - + + + + + {!wargaming && ( + + + + {strings.website.tools.playlist.no_login_warning} + + + )} + + + - {tanksTrimmed.map((tank) => ( - + {tanksTrimmed.map((tank) => ( + + ))} + + - ))} + - - - - - {literals(strings.website.tools.playlist.pool_size, { - count: tanks.length, - })} - + + {literals(strings.website.tools.playlist.pool_size, { + count: tanks.length, + })} + + ); diff --git a/packages/website/src/components/PlaylistOrder.tsx b/packages/website/src/components/PlaylistOrder.tsx index e5e8fcec2..f9fa71ab9 100644 --- a/packages/website/src/components/PlaylistOrder.tsx +++ b/packages/website/src/components/PlaylistOrder.tsx @@ -2,6 +2,7 @@ import { Box, Flex } from "@radix-ui/themes"; import { CurrentlyPlaying } from "./CurrentlyPlaying"; import { PlaylistActions } from "./PlaylistActions"; import { PlaylistTable } from "./PlaylistTable"; +import { PlaylistTracking } from "./PlaylistTracking"; export function PlaylistOrder() { return ( @@ -15,6 +16,7 @@ export function PlaylistOrder() { > + diff --git a/packages/website/src/components/PlaylistTable.tsx b/packages/website/src/components/PlaylistTable.tsx index dcd91cd63..6759d0635 100644 --- a/packages/website/src/components/PlaylistTable.tsx +++ b/packages/website/src/components/PlaylistTable.tsx @@ -31,27 +31,31 @@ export function PlaylistTable() { + + + + {strings.website.tools.playlist.table.tank} - {strings.website.tools.playlist.table.last_played} + {strings.website.tools.playlist.table.wins} {strings.website.tools.playlist.table.battles} - - - + + {strings.website.tools.playlist.table.last_played} + {list.map((props, index) => ( - + ))} diff --git a/packages/website/src/components/PlaylistTankEntry.tsx b/packages/website/src/components/PlaylistTankEntry.tsx index f6f47dbd2..db424a9df 100644 --- a/packages/website/src/components/PlaylistTankEntry.tsx +++ b/packages/website/src/components/PlaylistTankEntry.tsx @@ -1,6 +1,11 @@ -import { MinusCircledIcon } from "@radix-ui/react-icons"; -import { Checkbox, IconButton, Table } from "@radix-ui/themes"; +import { + MinusCircledIcon, + TriangleDownIcon, + TriangleUpIcon, +} from "@radix-ui/react-icons"; +import { Checkbox, Flex, IconButton, Table, Text } from "@radix-ui/themes"; import { memo } from "react"; +import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; import { useLocale } from "../hooks/useLocale"; import { Playlist, type PlaylistEntry } from "../stores/playlist"; import { TankRowHeaderCell } from "./TankRowHeaderCell"; @@ -9,27 +14,19 @@ interface Props extends PlaylistEntry { index: number; } +const tankDefinitions = await awaitableTankDefinitions; + export const PlaylistTankEntry = memo( - ({ tank, checked, last, battles, index }) => { + ({ id, checked, then, now, index }) => { const { locale, strings } = useLocale(); const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" }); - const nowDays = Date.now() / (1000 * 60 * 60 * 24); - const lastDays = last ? last / (60 * 60 * 24) : undefined; + const dateNow = Date.now(); + const tank = tankDefinitions.tanks[id]; return ( {index + 1} - - - - {lastDays - ? rtf.format(Math.floor(lastDays - nowDays), "day") - : strings.website.tools.playlist.table.never} - - - {battles} - { Playlist.mutate((draft) => { @@ -37,7 +34,16 @@ export const PlaylistTankEntry = memo( }); }} > - + then.battles) || + checked + } + /> @@ -53,13 +59,48 @@ export const PlaylistTankEntry = memo( + + + + + {now && then && now.battles !== then.battles ? ( + + + + + {now.wins - then.wins} + + + + + + + {now.battles - then.battles - now.wins + then.wins} + + + + ) : ( + strings.website.tools.playlist.table.unrecorded + )} + + + + {now?.battles ?? strings.website.tools.playlist.table.unrecorded} + + + + {now && now.last !== 0 + ? rtf.format( + Math.ceil((now.last - dateNow / 1000) / (60 * 60 * 24)), + "day" + ) + : strings.website.tools.playlist.table.unrecorded} + ); }, (a, b) => a.checked === b.checked && - a.battles === b.battles && - a.last === b.last && - a.wins === b.wins && + a.then?.battles === b.then?.battles && a.index === b.index ); diff --git a/packages/website/src/components/PlaylistTracking.tsx b/packages/website/src/components/PlaylistTracking.tsx new file mode 100644 index 000000000..dd8e3b34b --- /dev/null +++ b/packages/website/src/components/PlaylistTracking.tsx @@ -0,0 +1,14 @@ +import { useEffect } from "react"; +import { updatePlaylist } from "../core/blitzkit/updatePlaylist"; + +export function PlaylistTracking() { + useEffect(() => { + const interval = setInterval(updatePlaylist, 5000); + + return () => { + clearInterval(interval); + }; + }, []); + + return null; +} diff --git a/packages/website/src/core/blitzkit/generatePlaylist.ts b/packages/website/src/core/blitzkit/generatePlaylist.ts index a86510fbd..ccd8a13c7 100644 --- a/packages/website/src/core/blitzkit/generatePlaylist.ts +++ b/packages/website/src/core/blitzkit/generatePlaylist.ts @@ -28,12 +28,17 @@ export async function generatePlaylist() { fisherYates(filtered); Playlist.mutate((draft) => { - draft.list = filtered.map((tank) => ({ - tank, - battles: stats[tank.id]?.all.battles, - wins: stats[tank.id]?.all.wins, - last: stats[tank.id]?.last_battle_time, - checked: false, - })); + draft.list = filtered.map((tank) => { + const tankStats = stats[tank.id]; + const then = tankStats + ? { + battles: tankStats.all.battles, + wins: tankStats.all.wins, + last: tankStats.last_battle_time, + } + : undefined; + + return { id: tank.id, then, now: then }; + }); }); } diff --git a/packages/website/src/core/blitzkit/updatePlaylist.ts b/packages/website/src/core/blitzkit/updatePlaylist.ts new file mode 100644 index 000000000..d49435766 --- /dev/null +++ b/packages/website/src/core/blitzkit/updatePlaylist.ts @@ -0,0 +1,32 @@ +import { + getTankStats, + idToRegion, + type IndividualTankStats, +} from "@blitzkit/core"; +import { App } from "../../stores/app"; +import { Playlist } from "../../stores/playlist"; + +export async function updatePlaylist() { + const id = App.state.logins.wargaming?.id; + const statsList = id ? await getTankStats(idToRegion(id), id) : undefined; + const stats: Record = {}; + + if (statsList) { + for (const tankStat of statsList) { + stats[tankStat.tank_id] = tankStat; + } + } + + Playlist.mutate((draft) => { + draft.list?.map((item) => { + const tankStats = stats[item.id]; + item.now = tankStats + ? { + battles: tankStats.all.battles, + wins: tankStats.all.wins, + last: tankStats.last_battle_time, + } + : undefined; + }); + }); +} diff --git a/packages/website/src/pages/[...locale]/playlist/index.astro b/packages/website/src/pages/[...locale]/playlist/index.astro index 298a243d7..47582ba99 100644 --- a/packages/website/src/pages/[...locale]/playlist/index.astro +++ b/packages/website/src/pages/[...locale]/playlist/index.astro @@ -1,6 +1,7 @@ --- +import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; +import BaseLayout from "../../../layouts/BaseLayout.astro"; import { Page } from "./_index.tsx"; export { getStaticPaths } from "../index.astro"; @@ -8,8 +9,10 @@ export { getStaticPaths } from "../index.astro"; const { locale } = Astro.params; --- - + + + - + diff --git a/packages/website/src/stores/playlist.ts b/packages/website/src/stores/playlist.ts index b0fcb496a..f2f2ba132 100644 --- a/packages/website/src/stores/playlist.ts +++ b/packages/website/src/stores/playlist.ts @@ -1,12 +1,10 @@ -import type { TankDefinition } from "@blitzkit/core"; import { Varuna } from "varuna"; export interface PlaylistEntry { - tank: TankDefinition; - battles?: number; - wins?: number; - checked: boolean; - last?: number; + id: number; + then?: { battles: number; wins: number; last: number }; + now?: { battles: number; wins: number; last: number }; + checked?: boolean; } export interface Playlist {