From e1a6e4e38d4a59e9e23bdc12ae6aff121bd29416 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 12:11:31 -0500
Subject: [PATCH] feat: spotify like table
---
packages/i18n/strings/en.json | 5 +-
.../src/components/CurrentlyPlaying.tsx | 7 ++
.../website/src/components/PlaylistOrder.tsx | 44 +++++-------
.../website/src/components/PlaylistTable.tsx | 55 ++++++++++++++
.../src/components/PlaylistTankEntry.tsx | 72 ++++++++-----------
.../src/components/StickyRowHeaderCell.tsx | 11 ++-
.../src/components/TankRowHeaderCell.tsx | 51 +++++++------
.../src/core/blitzkit/generatePlaylist.ts | 2 +
packages/website/src/layouts/ToolLayout.astro | 6 +-
.../src/pages/[...locale]/playlist/_index.tsx | 2 +-
.../pages/[...locale]/playlist/index.astro | 2 +-
packages/website/src/stores/playlist.ts | 16 +++--
12 files changed, 159 insertions(+), 114 deletions(-)
create mode 100644 packages/website/src/components/CurrentlyPlaying.tsx
create mode 100644 packages/website/src/components/PlaylistTable.tsx
diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json
index 515686ada..1b7b3c9d3 100644
--- a/packages/i18n/strings/en.json
+++ b/packages/i18n/strings/en.json
@@ -862,7 +862,10 @@
"playlist": {
"clear": "Clear",
"table": {
- "tank": "Tank"
+ "index": "#",
+ "never": "Never",
+ "tank": "Tank",
+ "last_played": "Last played"
},
"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/CurrentlyPlaying.tsx b/packages/website/src/components/CurrentlyPlaying.tsx
new file mode 100644
index 000000000..08427e3f1
--- /dev/null
+++ b/packages/website/src/components/CurrentlyPlaying.tsx
@@ -0,0 +1,7 @@
+import { Flex } from "@radix-ui/themes";
+
+export function CurrentlyPlaying() {
+ return
+
+ ;
+}
diff --git a/packages/website/src/components/PlaylistOrder.tsx b/packages/website/src/components/PlaylistOrder.tsx
index c73238131..514e9d206 100644
--- a/packages/website/src/components/PlaylistOrder.tsx
+++ b/packages/website/src/components/PlaylistOrder.tsx
@@ -1,16 +1,23 @@
-import { Button, Flex, Table } from "@radix-ui/themes";
+import { Box, Button, Flex } from "@radix-ui/themes";
import { useLocale } from "../hooks/useLocale";
import { Playlist } from "../stores/playlist";
-import { PlaylistTankEntry } from "./PlaylistTankEntry";
+import { CurrentlyPlaying } from "./CurrentlyPlaying";
+import { PlaylistTable } from "./PlaylistTable";
export function PlaylistOrder() {
const { strings } = useLocale();
- const list = Playlist.use((state) => state.list);
-
- if (!list) return null;
return (
-
+
+
+
-
-
-
-
-
- {strings.website.tools.playlist.table.tank}
-
-
-
-
-
-
- {list.map(({ tank, checked }, index) => (
-
- ))}
-
-
+
+
+
);
}
diff --git a/packages/website/src/components/PlaylistTable.tsx b/packages/website/src/components/PlaylistTable.tsx
new file mode 100644
index 000000000..92ed64efb
--- /dev/null
+++ b/packages/website/src/components/PlaylistTable.tsx
@@ -0,0 +1,55 @@
+import { Table, Text } from "@radix-ui/themes";
+import { useLocale } from "../hooks/useLocale";
+import { Playlist } from "../stores/playlist";
+import { PlaylistTankEntry } from "./PlaylistTankEntry";
+import { StickyColumnHeaderCell } from "./StickyColumnHeaderCell";
+import { StickyTableRoot } from "./StickyTableRoot";
+
+export function PlaylistTable() {
+ const { strings } = useLocale();
+ const list = Playlist.use((state) => state.list);
+
+ if (!list) return null;
+
+ return (
+
+
+
+
+
+ {strings.website.tools.playlist.table.index}
+
+
+
+
+ {strings.website.tools.playlist.table.tank}
+
+
+
+ {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 1e4c2a517..923adba00 100644
--- a/packages/website/src/components/PlaylistTankEntry.tsx
+++ b/packages/website/src/components/PlaylistTankEntry.tsx
@@ -1,18 +1,31 @@
-import type { TankDefinition } from "@blitzkit/core";
-import { DragHandleDots2Icon, MinusCircledIcon } from "@radix-ui/react-icons";
-import { Checkbox, Flex, IconButton, Table, Text } from "@radix-ui/themes";
-import { Playlist } from "../stores/playlist";
+import { MinusCircledIcon } from "@radix-ui/react-icons";
+import { Checkbox, IconButton, Table } from "@radix-ui/themes";
+import { useLocale } from "../hooks/useLocale";
+import { Playlist, type PlaylistEntry } from "../stores/playlist";
import { TankRowHeaderCell } from "./TankRowHeaderCell";
-interface Props {
- tank: TankDefinition;
- checked: boolean;
+interface Props extends PlaylistEntry {
index: number;
}
-export function PlaylistTankEntry({ tank, checked, index }: Props) {
+export function PlaylistTankEntry({ tank, checked, last, index }: Props) {
+ 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;
+
return (
+ {index + 1}
+
+
+
+
+ {lastDays
+ ? rtf.format(Math.floor(lastDays - nowDays), "day")
+ : strings.website.tools.playlist.table.never}
+
+
{
Playlist.mutate((draft) => {
@@ -23,41 +36,18 @@ export function PlaylistTankEntry({ tank, checked, index }: Props) {
-
-
- {
+ Playlist.mutate((draft) => {
+ draft.list!.splice(index, 1);
+ });
+ }}
>
- {
- Playlist.mutate((draft) => {
- draft.list!.splice(index, 1);
- });
- }}
- >
-
-
-
-
-
-
-
-
-
+
+
);
diff --git a/packages/website/src/components/StickyRowHeaderCell.tsx b/packages/website/src/components/StickyRowHeaderCell.tsx
index 17c14fd38..8e0957278 100644
--- a/packages/website/src/components/StickyRowHeaderCell.tsx
+++ b/packages/website/src/components/StickyRowHeaderCell.tsx
@@ -1,15 +1,14 @@
-import { Table } from '@radix-ui/themes';
-import type { RowHeaderCellProps } from '@radix-ui/themes/src/components/table.js';
+import { Table } from "@radix-ui/themes";
+import type { RowHeaderCellProps } from "@radix-ui/themes/src/components/table.js";
export function StickyRowHeaderCell({ style, ...props }: RowHeaderCellProps) {
return (
+
@@ -36,34 +33,34 @@ export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) {
locale={locale}
color={
tank.type === TankType.COLLECTOR
- ? 'blue'
+ ? "blue"
: tank.type === TankType.PREMIUM
- ? 'amber'
- : 'gray'
+ ? "amber"
+ : "gray"
}
highContrast={tank.type === TankType.RESEARCHABLE}
underline="hover"
wrap="nowrap"
href={`/tanks/${tank.slug}`}
style={{
- paddingLeft: 'var(--space-2)',
- display: 'flex',
- alignItems: 'center',
- gap: 'var(--space-1)',
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
+ paddingLeft: "var(--space-2)",
+ display: "flex",
+ alignItems: "center",
+ gap: "var(--space-1)",
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
}}
- size={{ initial: '1', sm: '2' }}
+ size={{ initial: "1", sm: "2" }}
>
{tank.testing && (
-
+
)}
{unwrap(tank.name)}
diff --git a/packages/website/src/core/blitzkit/generatePlaylist.ts b/packages/website/src/core/blitzkit/generatePlaylist.ts
index 0a3ddf209..717226b6c 100644
--- a/packages/website/src/core/blitzkit/generatePlaylist.ts
+++ b/packages/website/src/core/blitzkit/generatePlaylist.ts
@@ -35,6 +35,8 @@ export async function generatePlaylist() {
tank,
battles: stats[tank.id]?.all.battles,
wins: stats[tank.id]?.all.wins,
+ last: stats[tank.id]?.last_battle_time,
+ checked: false,
}));
});
}
diff --git a/packages/website/src/layouts/ToolLayout.astro b/packages/website/src/layouts/ToolLayout.astro
index 348fec199..be9997afe 100644
--- a/packages/website/src/layouts/ToolLayout.astro
+++ b/packages/website/src/layouts/ToolLayout.astro
@@ -8,9 +8,11 @@ import BaseLayout from "./BaseLayout.astro";
interface Props {
tool: Tool;
locale: string;
+ noNavbar?: boolean;
+ noFooter?: boolean;
}
-const { tool, locale } = Astro.props;
+const { tool, locale, noNavbar, noFooter } = Astro.props;
const strings = getStrings(locale);
const toolStrings =
strings.website.tools[
@@ -20,7 +22,7 @@ const toolStrings =
-
+
-
+
{!list && }
{list && }
diff --git a/packages/website/src/pages/[...locale]/playlist/index.astro b/packages/website/src/pages/[...locale]/playlist/index.astro
index 33cce4318..298a243d7 100644
--- a/packages/website/src/pages/[...locale]/playlist/index.astro
+++ b/packages/website/src/pages/[...locale]/playlist/index.astro
@@ -8,7 +8,7 @@ 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 387545808..b0fcb496a 100644
--- a/packages/website/src/stores/playlist.ts
+++ b/packages/website/src/stores/playlist.ts
@@ -1,14 +1,16 @@
import type { TankDefinition } from "@blitzkit/core";
import { Varuna } from "varuna";
+export interface PlaylistEntry {
+ tank: TankDefinition;
+ battles?: number;
+ wins?: number;
+ checked: boolean;
+ last?: number;
+}
+
export interface Playlist {
- list?: {
- index: number;
- tank: TankDefinition;
- battles?: number;
- wins?: number;
- checked: boolean;
- }[];
+ list?: PlaylistEntry[];
}
export const Playlist = new Varuna({}, "playlist");