mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: variable player count
This commit is contained in:
1 parent
d6fc037d4f
commit
03ce75c15d
15 files changed
+149
-28
No files matched your search
@@ -0,0 +1,15 @@
|
|||||||
|
# BlitzKit 1.21.0
|
||||||
|
|
||||||
|
Introducing the Tank performance tool!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Added the Tank performance tool
|
||||||
|
- The tankopedia search page now loads much faster
|
||||||
|
- Not all tanks are loaded at once; rather, more tanks are loaded as you scroll down the page
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- The footer is now better centered
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkit",
|
"name": "blitzkit",
|
||||||
"version": "1.20.5",
|
"version": "1.21.0",
|
||||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||||
"main": "src/server.ts",
|
"main": "src/server.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
["1.10.0","1.10.1","1.10.2","1.11.0","1.12.0","1.12.1","1.12.2","1.12.3","1.12.4","1.12.5","1.12.6","1.12.7","1.12.8","1.12.9","1.13.0","1.13.1","1.13.2","1.13.3","1.14.0","1.14.1","1.14.2","1.14.3","1.14.4","1.15.0","1.15.1","1.15.10","1.15.11","1.15.12","1.15.13","1.15.2","1.15.3","1.15.4","1.15.5","1.15.6","1.15.7","1.15.8","1.15.9","1.16.0","1.16.1","1.16.2","1.16.3","1.16.4","1.17.0","1.17.1","1.17.2","1.18.0","1.18.1","1.18.2","1.18.3","1.19.0","1.19.1","1.19.2","1.19.3","1.19.4","1.19.5","1.19.6","1.20.0","1.20.1","1.20.2","1.20.3","1.20.4","1.20.5","1.3.0","1.3.1","1.3.2","1.3.3","1.4.0","1.4.1","1.5.0","1.5.1","1.5.2","1.5.3","1.5.4","1.6.0","1.7.0","1.7.1","1.7.2","1.7.3-1.7.4","1.7.5","1.7.6","1.7.7","1.8.0","1.8.1","1.9.0","1.9.1","1.9.2","1.9.3","1.9.4","1.9.5"]
|
["1.10.0","1.10.1","1.10.2","1.11.0","1.12.0","1.12.1","1.12.2","1.12.3","1.12.4","1.12.5","1.12.6","1.12.7","1.12.8","1.12.9","1.13.0","1.13.1","1.13.2","1.13.3","1.14.0","1.14.1","1.14.2","1.14.3","1.14.4","1.15.0","1.15.1","1.15.10","1.15.11","1.15.12","1.15.13","1.15.2","1.15.3","1.15.4","1.15.5","1.15.6","1.15.7","1.15.8","1.15.9","1.16.0","1.16.1","1.16.2","1.16.3","1.16.4","1.17.0","1.17.1","1.17.2","1.18.0","1.18.1","1.18.2","1.18.3","1.19.0","1.19.1","1.19.2","1.19.3","1.19.4","1.19.5","1.19.6","1.20.0","1.20.1","1.20.2","1.20.3","1.20.4","1.20.5","1.21.0","1.3.0","1.3.1","1.3.2","1.3.3","1.4.0","1.4.1","1.5.0","1.5.1","1.5.2","1.5.3","1.5.4","1.6.0","1.7.0","1.7.1","1.7.2","1.7.3-1.7.4","1.7.5","1.7.6","1.7.7","1.8.0","1.8.1","1.9.0","1.9.1","1.9.2","1.9.3","1.9.4","1.9.5"]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { FilterControl } from '../../tankopedia/components/FilterControl';
|
||||||
|
import { PlayerCountControl } from './PlayerCountControl';
|
||||||
|
|
||||||
|
export function Controls() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterControl />
|
||||||
|
<PlayerCountControl />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ import { Flex, IconButton, Table } from '@radix-ui/themes';
|
|||||||
import { StickyColumnHeaderCell } from '../../../../components/StickyColumnHeaderCell';
|
import { StickyColumnHeaderCell } from '../../../../components/StickyColumnHeaderCell';
|
||||||
import * as TankPerformanceSort from '../../../../stores/tankPerformanceSort';
|
import * as TankPerformanceSort from '../../../../stores/tankPerformanceSort';
|
||||||
import {
|
import {
|
||||||
tankPerformanceSortTypeNames,
|
TankPerformanceSortTypeNames,
|
||||||
tankPerformanceSortTypeNamesArray,
|
TankPerformanceSortTypeNamesArray,
|
||||||
} from '../../../../stores/tankPerformanceSort/constants';
|
} from '../../../../stores/tankPerformanceSort/constants';
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
@@ -21,7 +21,7 @@ export function Header() {
|
|||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row align="center">
|
<Table.Row align="center">
|
||||||
<StickyColumnHeaderCell>Tank</StickyColumnHeaderCell>
|
<StickyColumnHeaderCell>Tank</StickyColumnHeaderCell>
|
||||||
{tankPerformanceSortTypeNamesArray.map((type) => {
|
{TankPerformanceSortTypeNamesArray.map((type) => {
|
||||||
const isSelected = sort.type === type;
|
const isSelected = sort.type === type;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -32,7 +32,7 @@ export function Header() {
|
|||||||
justify="center"
|
justify="center"
|
||||||
>
|
>
|
||||||
<Flex align="center" gap="1">
|
<Flex align="center" gap="1">
|
||||||
{tankPerformanceSortTypeNames[type]}
|
{TankPerformanceSortTypeNames[type]}
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size={isSelected ? '1' : '2'}
|
size={isSelected ? '1' : '2'}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Flex, Select, Text } from '@radix-ui/themes';
|
||||||
|
import * as TankPerformancePersistent from '../../../../stores/tankPerformancePersistent';
|
||||||
|
import { PlayerCountPeriod } from '../../../../stores/tankPerformancePersistent/constants';
|
||||||
|
|
||||||
|
export function PlayerCountControl() {
|
||||||
|
const playerCountPeriod = TankPerformancePersistent.use(
|
||||||
|
(state) => state.playerCountPeriod,
|
||||||
|
);
|
||||||
|
const mutateTankPerformanceSort = TankPerformancePersistent.useMutation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex justify="center" align="center" gap="2">
|
||||||
|
<Text>Player count</Text>
|
||||||
|
<Select.Root
|
||||||
|
value={playerCountPeriod}
|
||||||
|
onValueChange={(period) => {
|
||||||
|
mutateTankPerformanceSort((draft) => {
|
||||||
|
draft.playerCountPeriod = period as PlayerCountPeriod;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Trigger />
|
||||||
|
<Select.Content>
|
||||||
|
<Select.Item value={PlayerCountPeriod.Past120Days}>
|
||||||
|
Past 120 days
|
||||||
|
</Select.Item>
|
||||||
|
<Select.Item value={PlayerCountPeriod.Past90Days}>
|
||||||
|
Past 90 days
|
||||||
|
</Select.Item>
|
||||||
|
<Select.Item value={PlayerCountPeriod.Past60Days}>
|
||||||
|
Past 60 days
|
||||||
|
</Select.Item>
|
||||||
|
<Select.Item value={PlayerCountPeriod.ThisMonth}>
|
||||||
|
This month
|
||||||
|
</Select.Item>
|
||||||
|
<Select.Item value={PlayerCountPeriod.ThisWeek}>
|
||||||
|
This week
|
||||||
|
</Select.Item>
|
||||||
|
<Select.Item value={PlayerCountPeriod.Yesterday}>
|
||||||
|
Yesterday
|
||||||
|
</Select.Item>
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { Skeleton, Table } from '@radix-ui/themes';
|
|||||||
import { times } from 'lodash';
|
import { times } from 'lodash';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
import { useIntersection } from '../../../../hooks/useIntersection';
|
import { useIntersection } from '../../../../hooks/useIntersection';
|
||||||
import { tankPerformanceSortTypeNamesArray } from '../../../../stores/tankPerformanceSort/constants';
|
import { TankPerformanceSortTypeNamesArray } from '../../../../stores/tankPerformanceSort/constants';
|
||||||
|
|
||||||
interface RowLoaderProps {
|
interface RowLoaderProps {
|
||||||
onIntersection?: () => void;
|
onIntersection?: () => void;
|
||||||
@@ -21,7 +21,7 @@ export const RowLoader = memo<RowLoaderProps>(({ onIntersection }) => {
|
|||||||
<Skeleton height="100%" width="128px" />
|
<Skeleton height="100%" width="128px" />
|
||||||
</Table.RowHeaderCell>
|
</Table.RowHeaderCell>
|
||||||
|
|
||||||
{times(tankPerformanceSortTypeNamesArray.length, (index) => (
|
{times(TankPerformanceSortTypeNamesArray.length, (index) => (
|
||||||
<Table.Cell key={index} align="center">
|
<Table.Cell key={index} align="center">
|
||||||
<Skeleton height="1em" width="32px" />
|
<Skeleton height="1em" width="32px" />
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { averageDefinitions } from '../../../../core/blitzkit/averageDefinitions
|
|||||||
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
||||||
import { formatCompact } from '../../../../core/math/formatCompact';
|
import { formatCompact } from '../../../../core/math/formatCompact';
|
||||||
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
|
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
|
||||||
|
import * as TankPerformancePersistent from '../../../../stores/tankPerformancePersistent';
|
||||||
|
|
||||||
interface TankRowProps {
|
interface TankRowProps {
|
||||||
tank: TankDefinition;
|
tank: TankDefinition;
|
||||||
@@ -15,6 +16,9 @@ export const TankRow = memo<TankRowProps>(
|
|||||||
const awaitedAverageDefinitions = use(averageDefinitions);
|
const awaitedAverageDefinitions = use(averageDefinitions);
|
||||||
const averages = awaitedAverageDefinitions.averages[tank.id];
|
const averages = awaitedAverageDefinitions.averages[tank.id];
|
||||||
const ratio = useAveragesExclusionRatio();
|
const ratio = useAveragesExclusionRatio();
|
||||||
|
const playerCountPeriod = TankPerformancePersistent.use(
|
||||||
|
(state) => state.playerCountPeriod,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
@@ -24,7 +28,9 @@ export const TankRow = memo<TankRowProps>(
|
|||||||
{((averages.mu.wins / averages.mu.battles) * 100).toFixed(1)}%
|
{((averages.mu.wins / averages.mu.battles) * 100).toFixed(1)}%
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{formatCompact(Math.round(ratio * averages.samples.d_30))}
|
{formatCompact(
|
||||||
|
Math.round(ratio * averages.samples[playerCountPeriod]),
|
||||||
|
)}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{formatCompact(
|
{formatCompact(
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { Table } from '@radix-ui/themes';
|
import { Table } from '@radix-ui/themes';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, use, useCallback } from 'react';
|
||||||
import { StickyRowHeaderCell } from '../../../../components/StickyRowHeaderCell';
|
import { StickyRowHeaderCell } from '../../../../components/StickyRowHeaderCell';
|
||||||
import {
|
import {
|
||||||
|
averageDefinitions,
|
||||||
AverageDefinitionsAllStats,
|
AverageDefinitionsAllStats,
|
||||||
AverageDefinitionsEntryWithId,
|
AverageDefinitionsEntryWithId,
|
||||||
} from '../../../../core/blitzkit/averageDefinitions';
|
} from '../../../../core/blitzkit/averageDefinitions';
|
||||||
import { formatCompact } from '../../../../core/math/formatCompact';
|
import { formatCompact } from '../../../../core/math/formatCompact';
|
||||||
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
|
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
|
||||||
|
import * as TankPerformancePersistent from '../../../../stores/tankPerformancePersistent';
|
||||||
|
|
||||||
interface TotalProps {
|
interface TotalProps {
|
||||||
tanks: AverageDefinitionsEntryWithId[];
|
tanks: AverageDefinitionsEntryWithId[];
|
||||||
@@ -14,6 +16,10 @@ interface TotalProps {
|
|||||||
|
|
||||||
export const Total = memo<TotalProps>(
|
export const Total = memo<TotalProps>(
|
||||||
({ tanks }) => {
|
({ tanks }) => {
|
||||||
|
const awaitedAverageDefinitions = use(averageDefinitions);
|
||||||
|
const playerCountPeriod = TankPerformancePersistent.use(
|
||||||
|
(state) => state.playerCountPeriod,
|
||||||
|
);
|
||||||
const ratio = useAveragesExclusionRatio();
|
const ratio = useAveragesExclusionRatio();
|
||||||
const sum = useCallback(
|
const sum = useCallback(
|
||||||
(slice: (tank: AverageDefinitionsAllStats) => number) => {
|
(slice: (tank: AverageDefinitionsAllStats) => number) => {
|
||||||
@@ -25,7 +31,6 @@ export const Total = memo<TotalProps>(
|
|||||||
[tanks],
|
[tanks],
|
||||||
);
|
);
|
||||||
|
|
||||||
const players = sum(() => 1);
|
|
||||||
const battles = sum(({ battles }) => battles);
|
const battles = sum(({ battles }) => battles);
|
||||||
const winrate = sum(({ wins }) => wins) / battles;
|
const winrate = sum(({ wins }) => wins) / battles;
|
||||||
const damage = sum(({ damage_dealt }) => damage_dealt) / battles;
|
const damage = sum(({ damage_dealt }) => damage_dealt) / battles;
|
||||||
@@ -43,14 +48,15 @@ export const Total = memo<TotalProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<StickyRowHeaderCell
|
<StickyRowHeaderCell style={{ overflow: 'hidden', display: 'flex' }}>
|
||||||
// maxWidth={{ initial: '128px', sm: '240px' }}
|
|
||||||
style={{ overflow: 'hidden', display: 'flex' }}
|
|
||||||
>
|
|
||||||
Total
|
Total
|
||||||
</StickyRowHeaderCell>
|
</StickyRowHeaderCell>
|
||||||
<Table.Cell align="center">{(winrate * 100).toFixed(1)}%</Table.Cell>
|
<Table.Cell align="center">{(winrate * 100).toFixed(1)}%</Table.Cell>
|
||||||
<Table.Cell align="center">{formatCompact(ratio * players)}</Table.Cell>
|
<Table.Cell align="center">
|
||||||
|
{formatCompact(
|
||||||
|
ratio * awaitedAverageDefinitions.samples[playerCountPeriod],
|
||||||
|
)}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell align="center">{formatCompact(ratio * battles)}</Table.Cell>
|
<Table.Cell align="center">{formatCompact(ratio * battles)}</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{Math.round(damage).toLocaleString()}
|
{Math.round(damage).toLocaleString()}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import * as TankFilters from '../../../stores/tankFilters';
|
import * as TankFilters from '../../../stores/tankFilters';
|
||||||
|
import * as TankPerformancePersistent from '../../../stores/tankPerformancePersistent';
|
||||||
import * as TankPerformanceSort from '../../../stores/tankPerformanceSort';
|
import * as TankPerformanceSort from '../../../stores/tankPerformanceSort';
|
||||||
|
|
||||||
export default function Layout({ children }: { children: ReactNode }) {
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<TankPerformanceSort.Provider>
|
<TankPerformancePersistent.Provider>
|
||||||
<TankFilters.Provider>{children}</TankFilters.Provider>
|
<TankPerformanceSort.Provider>
|
||||||
</TankPerformanceSort.Provider>
|
<TankFilters.Provider>{children}</TankFilters.Provider>
|
||||||
|
</TankPerformanceSort.Provider>
|
||||||
|
</TankPerformancePersistent.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import PageWrapper from '../../../components/PageWrapper';
|
import PageWrapper from '../../../components/PageWrapper';
|
||||||
import { FilterControl } from '../tankopedia/components/FilterControl';
|
import { Controls } from './components/Controls';
|
||||||
import { Info } from './components/Info';
|
import { Info } from './components/Info';
|
||||||
import { TankTable } from './components/Table';
|
import { TankTable } from './components/Table';
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ export default function Page() {
|
|||||||
<PageWrapper color="jade" noMaxWidth>
|
<PageWrapper color="jade" noMaxWidth>
|
||||||
<Info />
|
<Info />
|
||||||
{/* <TierBreakdown /> */}
|
{/* <TierBreakdown /> */}
|
||||||
<FilterControl />
|
<Controls />
|
||||||
<TankTable />
|
<TankTable />
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export enum PlayerCountPeriod {
|
||||||
|
Past120Days = 'd_120',
|
||||||
|
Past90Days = 'd_90',
|
||||||
|
Past60Days = 'd_60',
|
||||||
|
ThisMonth = 'd_30',
|
||||||
|
ThisWeek = 'd_7',
|
||||||
|
Yesterday = 'd_1',
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { merge } from 'lodash';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
||||||
|
import { createNextSafeStore } from '../../core/zustand/createNextSafeStore';
|
||||||
|
import { PlayerCountPeriod } from './constants';
|
||||||
|
|
||||||
|
export interface TankPerformancePersistent {
|
||||||
|
playerCountPeriod: PlayerCountPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const { Provider, use, useMutation, useStore } = createNextSafeStore(
|
||||||
|
() =>
|
||||||
|
create<TankPerformancePersistent>()(
|
||||||
|
persist(
|
||||||
|
subscribeWithSelector<TankPerformancePersistent>(() => ({
|
||||||
|
playerCountPeriod: PlayerCountPeriod.ThisMonth,
|
||||||
|
})),
|
||||||
|
{ name: 'tank-performance', merge: (a, b) => merge(b, a) },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { TankPerformanceSortType } from '.';
|
import { TankPerformanceSortType } from '.';
|
||||||
|
|
||||||
export const tankPerformanceSortTypeNames = {
|
export const TankPerformanceSortTypeNames = {
|
||||||
winrate: 'Winrate',
|
winrate: 'Winrate',
|
||||||
players: 'Players (30d)',
|
players: 'Players',
|
||||||
battles: 'Battles (career)',
|
battles: 'Battles (total)',
|
||||||
damage: 'Damage',
|
damage: 'Damage',
|
||||||
survival: 'Survival',
|
survival: 'Survival',
|
||||||
xp: 'XP',
|
xp: 'XP',
|
||||||
@@ -16,6 +16,7 @@ export const tankPerformanceSortTypeNames = {
|
|||||||
damageTaken: 'Dmg. taken',
|
damageTaken: 'Dmg. taken',
|
||||||
capturePoints: 'Cap points',
|
capturePoints: 'Cap points',
|
||||||
} as const;
|
} as const;
|
||||||
export const tankPerformanceSortTypeNamesArray = Object.keys(
|
|
||||||
tankPerformanceSortTypeNames,
|
export const TankPerformanceSortTypeNamesArray = Object.keys(
|
||||||
|
TankPerformanceSortTypeNames,
|
||||||
) as TankPerformanceSortType[];
|
) as TankPerformanceSortType[];
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { createNextSafeStore } from '../../core/zustand/createNextSafeStore';
|
import { createNextSafeStore } from '../../core/zustand/createNextSafeStore';
|
||||||
import { tankPerformanceSortTypeNames } from './constants';
|
import { TankPerformanceSortTypeNames } from './constants';
|
||||||
|
|
||||||
export type TankPerformanceSortType = keyof typeof tankPerformanceSortTypeNames;
|
export type TankPerformanceSortType = keyof typeof TankPerformanceSortTypeNames;
|
||||||
|
|
||||||
export interface TankPerformanceSort {
|
export interface TankPerformanceSort {
|
||||||
type: TankPerformanceSortType;
|
type: TankPerformanceSortType;
|
||||||
|
|||||||
Reference in new issue
Block a user