mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
feat: tracking
This commit is contained in:
1 parent
263f292a59
commit
ea048da7a2
11 files changed
+272
-106
No files matched your search
@@ -862,14 +862,22 @@
|
|||||||
"playlist": {
|
"playlist": {
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"shuffle": "Shuffle",
|
"shuffle": "Shuffle",
|
||||||
"sort": "Sort by",
|
"remove": "Remove played",
|
||||||
|
|
||||||
|
"sort": {
|
||||||
|
"button": "Sort by",
|
||||||
|
"last_played": "Last played",
|
||||||
|
"least_played": "Least played"
|
||||||
|
},
|
||||||
|
|
||||||
"table": {
|
"table": {
|
||||||
"index": "#",
|
"index": "#",
|
||||||
"never": "Never",
|
"never": "Never",
|
||||||
"tank": "Tank",
|
"tank": "Tank",
|
||||||
"last_played": "Last played",
|
"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.",
|
"no_login_warning": "You are not logged in. BlitzKit will not keep track of your played tanks.",
|
||||||
"generate": "Generate playlist",
|
"generate": "Generate playlist",
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { fisherYates } from "@blitzkit/core";
|
import { fisherYates } from "@blitzkit/core";
|
||||||
import { CaretDownIcon } from "@radix-ui/react-icons";
|
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 { useLocale } from "../hooks/useLocale";
|
||||||
|
import { App } from "../stores/app";
|
||||||
import { Playlist } from "../stores/playlist";
|
import { Playlist } from "../stores/playlist";
|
||||||
|
|
||||||
export function PlaylistActions() {
|
export function PlaylistActions() {
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
const wargaming = App.use((state) => state.logins.wargaming);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
@@ -22,7 +24,7 @@ export function PlaylistActions() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant={wargaming ? "outline" : undefined}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Playlist.mutate((draft) => {
|
Playlist.mutate((draft) => {
|
||||||
if (draft.list) fisherYates(draft.list);
|
if (draft.list) fisherYates(draft.list);
|
||||||
@@ -32,10 +34,59 @@ export function PlaylistActions() {
|
|||||||
{strings.website.tools.playlist.shuffle}
|
{strings.website.tools.playlist.shuffle}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button>
|
{wargaming && (
|
||||||
{strings.website.tools.playlist.sort}
|
<>
|
||||||
<CaretDownIcon />
|
<Button
|
||||||
</Button>
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
Playlist.mutate((draft) => {
|
||||||
|
draft.list = draft.list?.filter(
|
||||||
|
(entry) =>
|
||||||
|
!entry.now ||
|
||||||
|
!entry.then ||
|
||||||
|
entry.now.battles === entry.then.battles
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{strings.website.tools.playlist.remove}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger>
|
||||||
|
<Button>
|
||||||
|
{strings.website.tools.playlist.sort.button}
|
||||||
|
<CaretDownIcon />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
|
||||||
|
<DropdownMenu.Content>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
onClick={() => {
|
||||||
|
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}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
onClick={() => {
|
||||||
|
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}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -33,73 +33,81 @@ export function PlaylistGenerator() {
|
|||||||
const tanksTrimmed = useMemo(() => tanks.slice(0, 65), [filters]);
|
const tanksTrimmed = useMemo(() => tanks.slice(0, 65), [filters]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="center" width="100%" gap="9" align="center">
|
<Flex justify="center" width="100%" align="center">
|
||||||
<Flex direction="column" gap="4" align="center">
|
|
||||||
<FilterControl />
|
|
||||||
|
|
||||||
<Button onClick={generatePlaylist} size="3">
|
|
||||||
{strings.website.tools.playlist.generate}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{!wargaming && (
|
|
||||||
<Text color="gray">
|
|
||||||
<Flex align="center" gap="1">
|
|
||||||
<ExclamationTriangleIcon />
|
|
||||||
{strings.website.tools.playlist.no_login_warning}
|
|
||||||
</Flex>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
flexBasis="0"
|
gap="9"
|
||||||
align="center"
|
align="center"
|
||||||
gap="4"
|
justify="center"
|
||||||
|
maxWidth="80rem"
|
||||||
>
|
>
|
||||||
<Grid
|
<Flex direction="column" gap="4" align="center">
|
||||||
columns="repeat(auto-fill, minmax(4rem, 1fr))"
|
<FilterControl />
|
||||||
flow="dense"
|
|
||||||
overflow="hidden"
|
<Button onClick={generatePlaylist} size="3">
|
||||||
position="relative"
|
{strings.website.tools.playlist.generate}
|
||||||
maxHeight={POOL_HEIGHT}
|
</Button>
|
||||||
width="100%"
|
|
||||||
|
{!wargaming && (
|
||||||
|
<Text color="gray">
|
||||||
|
<Flex align="center" gap="1">
|
||||||
|
<ExclamationTriangleIcon />
|
||||||
|
{strings.website.tools.playlist.no_login_warning}
|
||||||
|
</Flex>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
flexGrow="1"
|
||||||
|
flexBasis="0"
|
||||||
|
align="center"
|
||||||
|
gap="4"
|
||||||
>
|
>
|
||||||
{tanksTrimmed.map((tank) => (
|
<Grid
|
||||||
<LinkI18n
|
columns="repeat(auto-fill, minmax(4rem, 1fr))"
|
||||||
target="_blank"
|
flow="dense"
|
||||||
locale={locale}
|
overflow="hidden"
|
||||||
href={`/tanks/${tank.slug}`}
|
position="relative"
|
||||||
key={tank.id}
|
maxHeight={POOL_HEIGHT}
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
{tanksTrimmed.map((tank) => (
|
||||||
|
<LinkI18n
|
||||||
|
target="_blank"
|
||||||
|
locale={locale}
|
||||||
|
href={`/tanks/${tank.slug}`}
|
||||||
|
key={tank.id}
|
||||||
|
style={{
|
||||||
|
aspectRatio: "4 / 3",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
backgroundImage: `url(${tankIcon(tank.id)})`,
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top="0"
|
||||||
|
left="0"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
style={{
|
style={{
|
||||||
aspectRatio: "4 / 3",
|
pointerEvents: "none",
|
||||||
backgroundSize: "cover",
|
background: `linear-gradient(transparent, var(--gray-1) ${POOL_HEIGHT})`,
|
||||||
backgroundPosition: "center",
|
|
||||||
backgroundImage: `url(${tankIcon(tank.id)})`,
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
</Grid>
|
||||||
|
|
||||||
<Box
|
<Text color="gray">
|
||||||
position="absolute"
|
{literals(strings.website.tools.playlist.pool_size, {
|
||||||
top="0"
|
count: tanks.length,
|
||||||
left="0"
|
})}
|
||||||
width="100%"
|
</Text>
|
||||||
height="100%"
|
</Flex>
|
||||||
style={{
|
|
||||||
pointerEvents: "none",
|
|
||||||
background: `linear-gradient(transparent, var(--gray-1) ${POOL_HEIGHT})`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Text color="gray">
|
|
||||||
{literals(strings.website.tools.playlist.pool_size, {
|
|
||||||
count: tanks.length,
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Box, Flex } from "@radix-ui/themes";
|
|||||||
import { CurrentlyPlaying } from "./CurrentlyPlaying";
|
import { CurrentlyPlaying } from "./CurrentlyPlaying";
|
||||||
import { PlaylistActions } from "./PlaylistActions";
|
import { PlaylistActions } from "./PlaylistActions";
|
||||||
import { PlaylistTable } from "./PlaylistTable";
|
import { PlaylistTable } from "./PlaylistTable";
|
||||||
|
import { PlaylistTracking } from "./PlaylistTracking";
|
||||||
|
|
||||||
export function PlaylistOrder() {
|
export function PlaylistOrder() {
|
||||||
return (
|
return (
|
||||||
@@ -15,6 +16,7 @@ export function PlaylistOrder() {
|
|||||||
>
|
>
|
||||||
<CurrentlyPlaying />
|
<CurrentlyPlaying />
|
||||||
<PlaylistActions />
|
<PlaylistActions />
|
||||||
|
<PlaylistTracking />
|
||||||
|
|
||||||
<Box flexGrow="1" width="100%" position="relative">
|
<Box flexGrow="1" width="100%" position="relative">
|
||||||
<PlaylistTable />
|
<PlaylistTable />
|
||||||
|
|||||||
@@ -31,27 +31,31 @@ export function PlaylistTable() {
|
|||||||
</Text>
|
</Text>
|
||||||
</StickyColumnHeaderCell>
|
</StickyColumnHeaderCell>
|
||||||
|
|
||||||
|
<StickyColumnHeaderCell width="0" />
|
||||||
|
|
||||||
|
<StickyColumnHeaderCell width="0" />
|
||||||
|
|
||||||
<StickyColumnHeaderCell width="1">
|
<StickyColumnHeaderCell width="1">
|
||||||
{strings.website.tools.playlist.table.tank}
|
{strings.website.tools.playlist.table.tank}
|
||||||
</StickyColumnHeaderCell>
|
</StickyColumnHeaderCell>
|
||||||
|
|
||||||
<StickyColumnHeaderCell width="1">
|
<StickyColumnHeaderCell width="1">
|
||||||
{strings.website.tools.playlist.table.last_played}
|
{strings.website.tools.playlist.table.wins}
|
||||||
</StickyColumnHeaderCell>
|
</StickyColumnHeaderCell>
|
||||||
|
|
||||||
<StickyColumnHeaderCell width="1">
|
<StickyColumnHeaderCell width="1">
|
||||||
{strings.website.tools.playlist.table.battles}
|
{strings.website.tools.playlist.table.battles}
|
||||||
</StickyColumnHeaderCell>
|
</StickyColumnHeaderCell>
|
||||||
|
|
||||||
<StickyColumnHeaderCell width="0" />
|
<StickyColumnHeaderCell width="1">
|
||||||
|
{strings.website.tools.playlist.table.last_played}
|
||||||
<StickyColumnHeaderCell width="0" />
|
</StickyColumnHeaderCell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
|
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
{list.map((props, index) => (
|
{list.map((props, index) => (
|
||||||
<PlaylistTankEntry key={props.tank.id} index={index} {...props} />
|
<PlaylistTankEntry key={props.id} index={index} {...props} />
|
||||||
))}
|
))}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</StickyTableRoot>
|
</StickyTableRoot>
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { MinusCircledIcon } from "@radix-ui/react-icons";
|
import {
|
||||||
import { Checkbox, IconButton, Table } from "@radix-ui/themes";
|
MinusCircledIcon,
|
||||||
|
TriangleDownIcon,
|
||||||
|
TriangleUpIcon,
|
||||||
|
} from "@radix-ui/react-icons";
|
||||||
|
import { Checkbox, Flex, IconButton, Table, Text } from "@radix-ui/themes";
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
|
import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions";
|
||||||
import { useLocale } from "../hooks/useLocale";
|
import { useLocale } from "../hooks/useLocale";
|
||||||
import { Playlist, type PlaylistEntry } from "../stores/playlist";
|
import { Playlist, type PlaylistEntry } from "../stores/playlist";
|
||||||
import { TankRowHeaderCell } from "./TankRowHeaderCell";
|
import { TankRowHeaderCell } from "./TankRowHeaderCell";
|
||||||
@@ -9,27 +14,19 @@ interface Props extends PlaylistEntry {
|
|||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export const PlaylistTankEntry = memo<Props>(
|
export const PlaylistTankEntry = memo<Props>(
|
||||||
({ tank, checked, last, battles, index }) => {
|
({ id, checked, then, now, index }) => {
|
||||||
const { locale, strings } = useLocale();
|
const { locale, strings } = useLocale();
|
||||||
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
||||||
const nowDays = Date.now() / (1000 * 60 * 60 * 24);
|
const dateNow = Date.now();
|
||||||
const lastDays = last ? last / (60 * 60 * 24) : undefined;
|
const tank = tankDefinitions.tanks[id];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell>{index + 1}</Table.Cell>
|
<Table.Cell>{index + 1}</Table.Cell>
|
||||||
|
|
||||||
<TankRowHeaderCell tank={tank} />
|
|
||||||
|
|
||||||
<Table.Cell>
|
|
||||||
{lastDays
|
|
||||||
? rtf.format(Math.floor(lastDays - nowDays), "day")
|
|
||||||
: strings.website.tools.playlist.table.never}
|
|
||||||
</Table.Cell>
|
|
||||||
|
|
||||||
<Table.Cell>{battles}</Table.Cell>
|
|
||||||
|
|
||||||
<Table.Cell
|
<Table.Cell
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Playlist.mutate((draft) => {
|
Playlist.mutate((draft) => {
|
||||||
@@ -37,7 +34,16 @@ export const PlaylistTankEntry = memo<Props>(
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Checkbox variant="classic" checked={checked} />
|
<Checkbox
|
||||||
|
variant="classic"
|
||||||
|
checked={
|
||||||
|
(checked === undefined &&
|
||||||
|
now &&
|
||||||
|
then &&
|
||||||
|
now.battles > then.battles) ||
|
||||||
|
checked
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
|
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
@@ -53,13 +59,48 @@ export const PlaylistTankEntry = memo<Props>(
|
|||||||
<MinusCircledIcon />
|
<MinusCircledIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
|
|
||||||
|
<TankRowHeaderCell tank={tank} />
|
||||||
|
|
||||||
|
<Table.Cell>
|
||||||
|
{now && then && now.battles !== then.battles ? (
|
||||||
|
<Flex gap="2">
|
||||||
|
<Text color="green">
|
||||||
|
<Flex align="center">
|
||||||
|
<TriangleUpIcon />
|
||||||
|
{now.wins - then.wins}
|
||||||
|
</Flex>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text color="tomato">
|
||||||
|
<Flex align="center">
|
||||||
|
<TriangleDownIcon />
|
||||||
|
{now.battles - then.battles - now.wins + then.wins}
|
||||||
|
</Flex>
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
strings.website.tools.playlist.table.unrecorded
|
||||||
|
)}
|
||||||
|
</Table.Cell>
|
||||||
|
|
||||||
|
<Table.Cell>
|
||||||
|
{now?.battles ?? strings.website.tools.playlist.table.unrecorded}
|
||||||
|
</Table.Cell>
|
||||||
|
|
||||||
|
<Table.Cell>
|
||||||
|
{now && now.last !== 0
|
||||||
|
? rtf.format(
|
||||||
|
Math.ceil((now.last - dateNow / 1000) / (60 * 60 * 24)),
|
||||||
|
"day"
|
||||||
|
)
|
||||||
|
: strings.website.tools.playlist.table.unrecorded}
|
||||||
|
</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.checked === b.checked &&
|
a.checked === b.checked &&
|
||||||
a.battles === b.battles &&
|
a.then?.battles === b.then?.battles &&
|
||||||
a.last === b.last &&
|
|
||||||
a.wins === b.wins &&
|
|
||||||
a.index === b.index
|
a.index === b.index
|
||||||
);
|
);
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -28,12 +28,17 @@ export async function generatePlaylist() {
|
|||||||
fisherYates(filtered);
|
fisherYates(filtered);
|
||||||
|
|
||||||
Playlist.mutate((draft) => {
|
Playlist.mutate((draft) => {
|
||||||
draft.list = filtered.map((tank) => ({
|
draft.list = filtered.map((tank) => {
|
||||||
tank,
|
const tankStats = stats[tank.id];
|
||||||
battles: stats[tank.id]?.all.battles,
|
const then = tankStats
|
||||||
wins: stats[tank.id]?.all.wins,
|
? {
|
||||||
last: stats[tank.id]?.last_battle_time,
|
battles: tankStats.all.battles,
|
||||||
checked: false,
|
wins: tankStats.all.wins,
|
||||||
}));
|
last: tankStats.last_battle_time,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return { id: tank.id, then, now: then };
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -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<number, IndividualTankStats> = {};
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
|
import ToolOpenGraph from "../../../components/ToolOpenGraph.astro";
|
||||||
import { tools } from "../../../constants/tools";
|
import { tools } from "../../../constants/tools";
|
||||||
import ToolLayout from "../../../layouts/ToolLayout.astro";
|
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
||||||
import { Page } from "./_index.tsx";
|
import { Page } from "./_index.tsx";
|
||||||
|
|
||||||
export { getStaticPaths } from "../index.astro";
|
export { getStaticPaths } from "../index.astro";
|
||||||
@@ -8,8 +9,10 @@ export { getStaticPaths } from "../index.astro";
|
|||||||
const { locale } = Astro.params;
|
const { locale } = Astro.params;
|
||||||
---
|
---
|
||||||
|
|
||||||
<ToolLayout {locale} tool={tools.playlist} noFooter>
|
<ToolOpenGraph tool={tools.playlist} />
|
||||||
|
|
||||||
|
<BaseLayout {locale} noFooter>
|
||||||
<Page {locale} client:only="react">
|
<Page {locale} client:only="react">
|
||||||
<Page {locale} skeleton slot="fallback" />
|
<Page {locale} skeleton slot="fallback" />
|
||||||
</Page>
|
</Page>
|
||||||
</ToolLayout>
|
</BaseLayout>
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import type { TankDefinition } from "@blitzkit/core";
|
|
||||||
import { Varuna } from "varuna";
|
import { Varuna } from "varuna";
|
||||||
|
|
||||||
export interface PlaylistEntry {
|
export interface PlaylistEntry {
|
||||||
tank: TankDefinition;
|
id: number;
|
||||||
battles?: number;
|
then?: { battles: number; wins: number; last: number };
|
||||||
wins?: number;
|
now?: { battles: number; wins: number; last: number };
|
||||||
checked: boolean;
|
checked?: boolean;
|
||||||
last?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Playlist {
|
export interface Playlist {
|
||||||
|
|||||||
Reference in new issue
Block a user