mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 11:39:51 +01:00
feat: sort
This commit is contained in:
1 parent
16bee48989
commit
2dcfd13457
5 files changed
+199
-65
No files matched your search
@@ -1,49 +1,62 @@
|
|||||||
import { Table } from '@radix-ui/themes';
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CaretDownIcon,
|
||||||
|
CaretSortIcon,
|
||||||
|
CaretUpIcon,
|
||||||
|
} from '@radix-ui/react-icons';
|
||||||
|
import { Flex, IconButton, Table } from '@radix-ui/themes';
|
||||||
|
import {
|
||||||
|
tankPerformanceSortTypeNames,
|
||||||
|
tankPerformanceSortTypeNamesArray,
|
||||||
|
useTankPerformanceSort,
|
||||||
|
} from '../../../../stores/tankPerformanceSort';
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
|
const sort = useTankPerformanceSort();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Header style={{ whiteSpace: 'nowrap' }}>
|
<Table.Header style={{ whiteSpace: 'nowrap' }}>
|
||||||
<Table.Row align="end">
|
<Table.Row align="center">
|
||||||
<Table.ColumnHeaderCell>Tank</Table.ColumnHeaderCell>
|
<Table.ColumnHeaderCell>Tank</Table.ColumnHeaderCell>
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
{tankPerformanceSortTypeNamesArray.map((type) => {
|
||||||
Winrate
|
const isSelected = sort.type === type;
|
||||||
</Table.ColumnHeaderCell>
|
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
return (
|
||||||
Players (30d)
|
<Table.ColumnHeaderCell
|
||||||
</Table.ColumnHeaderCell>
|
key={type}
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
width="0"
|
||||||
Damage
|
justify="center"
|
||||||
</Table.ColumnHeaderCell>
|
minWidth="0px"
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
>
|
||||||
Survival
|
<Flex align="center" gap="1">
|
||||||
</Table.ColumnHeaderCell>
|
<IconButton
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
variant={isSelected ? 'soft' : 'ghost'}
|
||||||
XP
|
color={isSelected ? undefined : 'gray'}
|
||||||
</Table.ColumnHeaderCell>
|
highContrast={!isSelected}
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
onClick={() => {
|
||||||
Kills
|
if (isSelected) {
|
||||||
</Table.ColumnHeaderCell>
|
useTankPerformanceSort.setState({
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
direction: -sort.direction as 1 | -1,
|
||||||
Spots
|
});
|
||||||
</Table.ColumnHeaderCell>
|
} else {
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
useTankPerformanceSort.setState({ type });
|
||||||
Accuracy
|
}
|
||||||
</Table.ColumnHeaderCell>
|
}}
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
>
|
||||||
Shots
|
{!isSelected && <CaretSortIcon />}
|
||||||
</Table.ColumnHeaderCell>
|
{isSelected && (
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
<>
|
||||||
Hits
|
{sort.direction === 1 && <CaretUpIcon />}
|
||||||
</Table.ColumnHeaderCell>
|
{sort.direction === -1 && <CaretDownIcon />}
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
</>
|
||||||
Dmg. ratio
|
)}
|
||||||
</Table.ColumnHeaderCell>
|
</IconButton>
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
{tankPerformanceSortTypeNames[type]}
|
||||||
Dmg. taken
|
</Flex>
|
||||||
</Table.ColumnHeaderCell>
|
</Table.ColumnHeaderCell>
|
||||||
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
|
);
|
||||||
Cap points
|
})}
|
||||||
</Table.ColumnHeaderCell>
|
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export function Info() {
|
|||||||
const ratio =
|
const ratio =
|
||||||
awaitedDiscoveredIdsDefinitions.count /
|
awaitedDiscoveredIdsDefinitions.count /
|
||||||
awaitedAverageDefinitions.scanned_players;
|
awaitedAverageDefinitions.scanned_players;
|
||||||
|
const samples = ratio * awaitedAverageDefinitions.sampled_players;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="center">
|
<Flex justify="center">
|
||||||
@@ -21,12 +22,12 @@ export function Info() {
|
|||||||
<InfoCircledIcon />
|
<InfoCircledIcon />
|
||||||
</Callout.Icon>
|
</Callout.Icon>
|
||||||
<Callout.Text>
|
<Callout.Text>
|
||||||
Career stats based on{' '}
|
Career stats based on {numberFormat.format(Math.round(samples))}{' '}
|
||||||
{numberFormat.format(
|
|
||||||
Math.round(ratio * awaitedAverageDefinitions.sampled_players),
|
|
||||||
)}{' '}
|
|
||||||
players with at least 5K career battles and 1 battle in the past 30
|
players with at least 5K career battles and 1 battle in the past 30
|
||||||
days. Updated daily.
|
days; updated daily.
|
||||||
|
<br />
|
||||||
|
May differ from BlitzStars because BlitzKit includes{' '}
|
||||||
|
{Math.round(samples / 1e4)}x more players.
|
||||||
</Callout.Text>
|
</Callout.Text>
|
||||||
</Callout.Root>
|
</Callout.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ export const TankRow = memo<TankRowProps>(
|
|||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{numberFormat.format(Math.round(ratio * averages.samples))}
|
{numberFormat.format(Math.round(ratio * averages.samples))}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
|
<Table.Cell align="center">
|
||||||
|
{numberFormat.format(
|
||||||
|
Math.round(ratio * averages.mu.battles * averages.samples),
|
||||||
|
)}
|
||||||
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{Math.round(
|
{Math.round(
|
||||||
averages.mu.damage_dealt / averages.mu.battles,
|
averages.mu.damage_dealt / averages.mu.battles,
|
||||||
@@ -49,13 +54,7 @@ export const TankRow = memo<TankRowProps>(
|
|||||||
{(averages.mu.spotted / averages.mu.battles).toFixed(2)}
|
{(averages.mu.spotted / averages.mu.battles).toFixed(2)}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{Math.round(
|
{Math.round((averages.mu.hits / averages.mu.shots) * 100)}%
|
||||||
(averages.mu.hits /
|
|
||||||
averages.mu.battles /
|
|
||||||
(averages.mu.shots / averages.mu.battles)) *
|
|
||||||
100,
|
|
||||||
)}
|
|
||||||
%
|
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{(averages.mu.shots / averages.mu.battles).toFixed(1)}
|
{(averages.mu.shots / averages.mu.battles).toFixed(1)}
|
||||||
@@ -64,11 +63,7 @@ export const TankRow = memo<TankRowProps>(
|
|||||||
{(averages.mu.hits / averages.mu.battles).toFixed(1)}
|
{(averages.mu.hits / averages.mu.battles).toFixed(1)}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{(
|
{(averages.mu.damage_dealt / averages.mu.damage_received).toFixed(2)}
|
||||||
averages.mu.damage_dealt /
|
|
||||||
averages.mu.battles /
|
|
||||||
(averages.mu.damage_received / averages.mu.battles)
|
|
||||||
).toFixed(2)}
|
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{Math.round(
|
{Math.round(
|
||||||
|
|||||||
@@ -4,19 +4,103 @@ import { Table } from '@radix-ui/themes';
|
|||||||
import { Suspense, use, useMemo } from 'react';
|
import { Suspense, use, useMemo } from 'react';
|
||||||
import { averageDefinitionsArray } from '../../../../core/blitzkit/averageDefinitions';
|
import { averageDefinitionsArray } from '../../../../core/blitzkit/averageDefinitions';
|
||||||
import { tankDefinitions } from '../../../../core/blitzkit/tankDefinitions';
|
import { tankDefinitions } from '../../../../core/blitzkit/tankDefinitions';
|
||||||
|
import { useTankPerformanceSort } from '../../../../stores/tankPerformanceSort';
|
||||||
import { RowLoader } from './RowLoader';
|
import { RowLoader } from './RowLoader';
|
||||||
import { TankRow } from './TankRow';
|
import { TankRow } from './TankRow';
|
||||||
|
|
||||||
export function Tanks() {
|
export function Tanks() {
|
||||||
const awaitedTankDefinitions = use(tankDefinitions);
|
const awaitedTankDefinitions = use(tankDefinitions);
|
||||||
const awaitedAverageDefinitionsArray = use(averageDefinitionsArray);
|
const awaitedAverageDefinitionsArray = use(averageDefinitionsArray);
|
||||||
const tankAverages = useMemo(
|
const sort = useTankPerformanceSort();
|
||||||
() =>
|
const tankAverages = useMemo(() => {
|
||||||
awaitedAverageDefinitionsArray.sort(
|
switch (sort.type) {
|
||||||
(a, b) => b.mu.wins / b.mu.battles - a.mu.wins / a.mu.battles,
|
case 'accuracy':
|
||||||
),
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
[],
|
(a, b) =>
|
||||||
);
|
sort.direction * (a.mu.hits / a.mu.shots - b.mu.hits / b.mu.shots),
|
||||||
|
);
|
||||||
|
case 'battles':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.samples * a.mu.battles - b.samples * b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'capturePoints':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.capture_points / a.mu.battles -
|
||||||
|
b.mu.capture_points / b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'damage':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.damage_dealt / a.mu.battles -
|
||||||
|
b.mu.damage_dealt / b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'damageRatio':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.damage_dealt / a.mu.damage_received -
|
||||||
|
b.mu.damage_dealt / b.mu.damage_received),
|
||||||
|
);
|
||||||
|
case 'damageTaken':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.damage_received / a.mu.battles -
|
||||||
|
b.mu.damage_received / b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'hits':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.hits / a.mu.battles - b.mu.hits / b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'kills':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.frags / a.mu.battles - b.mu.frags / b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'shots':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.shots / a.mu.battles - b.mu.shots / b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'players':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) => sort.direction * (a.samples - b.samples),
|
||||||
|
);
|
||||||
|
case 'spots':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.spotted / a.mu.battles - b.mu.spotted / b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'survival':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.survived_battles / a.mu.battles -
|
||||||
|
b.mu.survived_battles / b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'winrate':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction *
|
||||||
|
(a.mu.wins / a.mu.battles - b.mu.wins / b.mu.battles),
|
||||||
|
);
|
||||||
|
case 'xp':
|
||||||
|
return awaitedAverageDefinitionsArray.sort(
|
||||||
|
(a, b) =>
|
||||||
|
sort.direction * (a.mu.xp / a.mu.battles - b.mu.xp / b.mu.battles),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [sort]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { produce } from 'immer';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
export const tankPerformanceSortTypeNames = {
|
||||||
|
winrate: 'Winrate',
|
||||||
|
players: 'Players (30d)',
|
||||||
|
battles: 'Battles (career)',
|
||||||
|
damage: 'Damage',
|
||||||
|
survival: 'Survival',
|
||||||
|
xp: 'XP',
|
||||||
|
kills: 'Kills',
|
||||||
|
spots: 'Spots',
|
||||||
|
accuracy: 'Accuracy',
|
||||||
|
shots: 'Shots',
|
||||||
|
hits: 'Hits',
|
||||||
|
damageRatio: 'Dmg. ratio',
|
||||||
|
damageTaken: 'Dmg. taken',
|
||||||
|
capturePoints: 'Cap points',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const tankPerformanceSortTypeNamesArray = Object.keys(
|
||||||
|
tankPerformanceSortTypeNames,
|
||||||
|
) as TankPerformanceSortType[];
|
||||||
|
|
||||||
|
type TankPerformanceSortType = keyof typeof tankPerformanceSortTypeNames;
|
||||||
|
|
||||||
|
export interface TankPerformanceSort {
|
||||||
|
type: TankPerformanceSortType;
|
||||||
|
direction: -1 | 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTankPerformanceSort = create<TankPerformanceSort>()(() => ({
|
||||||
|
type: 'battles',
|
||||||
|
direction: -1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function mutateTankPerformanceSort(
|
||||||
|
recipe: (draft: TankPerformanceSort) => void,
|
||||||
|
) {
|
||||||
|
useTankPerformanceSort.setState(produce(recipe));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user