mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: migrate performance
This commit is contained in:
1 parent
b31c7a6738
commit
8d51ce2bea
20 files changed
+122
-149
No files matched your search
@@ -1,13 +0,0 @@
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { RowLoader } from './RowLoader';
|
||||
|
||||
export function BodyLoader() {
|
||||
return (
|
||||
<Table.Body>
|
||||
{times(10, (index) => (
|
||||
<RowLoader key={index} />
|
||||
))}
|
||||
</Table.Body>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { FilterControl } from '../../../../../../website/src/components/TankSearch/components/FilterControl';
|
||||
import { PlayerCountControl } from './PlayerCountControl';
|
||||
|
||||
export function Controls() {
|
||||
return (
|
||||
<>
|
||||
<FilterControl />
|
||||
<PlayerCountControl />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import {
|
||||
CaretDownIcon,
|
||||
CaretSortIcon,
|
||||
CaretUpIcon,
|
||||
} from '@radix-ui/react-icons';
|
||||
import { Flex, IconButton, Table } from '@radix-ui/themes';
|
||||
import { StickyColumnHeaderCell } from '../../../../components/StickyColumnHeaderCell';
|
||||
import * as TankPerformanceSort from '../../../../stores/tankPerformanceSort';
|
||||
import {
|
||||
TankPerformanceSortTypeNames,
|
||||
TankPerformanceSortTypeNamesArray,
|
||||
} from '../../../../stores/tankPerformanceSort/constants';
|
||||
|
||||
export function Header() {
|
||||
const sort = TankPerformanceSort.use();
|
||||
const store = TankPerformanceSort.useStore();
|
||||
|
||||
return (
|
||||
<Table.Header>
|
||||
<Table.Row align="center">
|
||||
<StickyColumnHeaderCell>Tank</StickyColumnHeaderCell>
|
||||
{TankPerformanceSortTypeNamesArray.map((type) => {
|
||||
const isSelected = sort.type === type;
|
||||
|
||||
return (
|
||||
<StickyColumnHeaderCell
|
||||
px="2"
|
||||
key={type}
|
||||
width="0"
|
||||
justify="center"
|
||||
>
|
||||
<Flex align="center" gap="1">
|
||||
{TankPerformanceSortTypeNames[type]}
|
||||
|
||||
<IconButton
|
||||
size={isSelected ? '1' : '2'}
|
||||
variant={isSelected ? 'soft' : 'ghost'}
|
||||
color={isSelected ? undefined : 'gray'}
|
||||
highContrast={!isSelected}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
store.setState({ direction: -sort.direction as 1 | -1 });
|
||||
} else {
|
||||
store.setState({ type, direction: -1 });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!isSelected && <CaretSortIcon />}
|
||||
{isSelected && (
|
||||
<>
|
||||
{sort.direction === 1 && <CaretUpIcon />}
|
||||
{sort.direction === -1 && <CaretDownIcon />}
|
||||
</>
|
||||
)}
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</StickyColumnHeaderCell>
|
||||
);
|
||||
})}
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import {
|
||||
fetchAverageDefinitions,
|
||||
fetchDiscoveredIdsDefinitions,
|
||||
formatCompact,
|
||||
} from '@blitzkit/core';
|
||||
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
||||
import { Callout, Flex } from '@radix-ui/themes';
|
||||
|
||||
const discoveredIdsDefinitions = await fetchDiscoveredIdsDefinitions();
|
||||
const averageDefinitions = await fetchAverageDefinitions();
|
||||
|
||||
export function Info() {
|
||||
const samples = discoveredIdsDefinitions.count;
|
||||
const minutesAgo = Math.floor(
|
||||
(Date.now() - averageDefinitions.time) / (1000 * 60),
|
||||
);
|
||||
const hoursAgo = Math.floor(minutesAgo / 60);
|
||||
|
||||
return (
|
||||
<Flex justify="center">
|
||||
<Callout.Root style={{ width: 'fit-content' }}>
|
||||
<Callout.Icon>
|
||||
<InfoCircledIcon />
|
||||
</Callout.Icon>
|
||||
<Callout.Text>
|
||||
Career stats based on {formatCompact(Math.round(samples))} players.
|
||||
Updated {hoursAgo === 0 ? minutesAgo : hoursAgo}{' '}
|
||||
{hoursAgo === 0 ? 'minutes' : 'hours'} ago.
|
||||
</Callout.Text>
|
||||
</Callout.Root>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Skeleton, Table } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { memo, useRef } from 'react';
|
||||
import { useIntersection } from '../../../../hooks/useIntersection';
|
||||
import { TankPerformanceSortTypeNamesArray } from '../../../../stores/tankPerformanceSort/constants';
|
||||
|
||||
interface RowLoaderProps {
|
||||
onIntersection?: () => void;
|
||||
}
|
||||
|
||||
export const RowLoader = memo<RowLoaderProps>(({ onIntersection }) => {
|
||||
const row = useRef<HTMLTableRowElement>(null);
|
||||
|
||||
useIntersection(() => onIntersection?.(), row, {
|
||||
disabled: onIntersection === undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
<Table.Row ref={row}>
|
||||
<Table.RowHeaderCell align="center">
|
||||
<Skeleton height="100%" width="128px" />
|
||||
</Table.RowHeaderCell>
|
||||
|
||||
{times(TankPerformanceSortTypeNamesArray.length, (index) => (
|
||||
<Table.Cell key={index} align="center">
|
||||
<Skeleton height="1em" width="32px" />
|
||||
</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
);
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Flex } from '@radix-ui/themes';
|
||||
import { Suspense } from 'react';
|
||||
import { StickyTableRoot } from '../../../../components/StickyTableRoot';
|
||||
import { BodyLoader } from './BodyLoader';
|
||||
import { Header } from './Header';
|
||||
import { Tanks } from './Tanks';
|
||||
|
||||
export function TankTable() {
|
||||
return (
|
||||
<Flex justify="center">
|
||||
<StickyTableRoot
|
||||
size={{ initial: '1', sm: '2' }}
|
||||
variant="surface"
|
||||
style={{ maxWidth: '100%', maxHeight: 'calc(100vh - 256px)' }}
|
||||
>
|
||||
<Header />
|
||||
<Suspense fallback={<BodyLoader />}>
|
||||
<Tanks />
|
||||
</Suspense>
|
||||
</StickyTableRoot>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import {
|
||||
TankDefinition,
|
||||
averageDefinitions,
|
||||
formatCompact,
|
||||
} from '@blitzkit/core';
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { memo, use } from 'react';
|
||||
import { TankRowHeaderCell } from '../../../../components/TankRowHeaderCell';
|
||||
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
|
||||
import * as TankPerformancePersistent from '../../../../stores/tankPerformancePersistent';
|
||||
|
||||
interface TankRowProps {
|
||||
tank: TankDefinition;
|
||||
}
|
||||
|
||||
export const TankRow = memo<TankRowProps>(
|
||||
({ tank }) => {
|
||||
const awaitedAverageDefinitions = use(averageDefinitions);
|
||||
const averages = awaitedAverageDefinitions.averages[tank.id];
|
||||
const ratio = useAveragesExclusionRatio();
|
||||
const playerCountPeriod = TankPerformancePersistent.use(
|
||||
(state) => state.playerCountPeriod,
|
||||
);
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<TankRowHeaderCell tank={tank} />
|
||||
|
||||
<Table.Cell align="center">
|
||||
{((averages.mu.wins / averages.mu.battles) * 100).toFixed(1)}%
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{formatCompact(
|
||||
Math.round(ratio * averages.samples[playerCountPeriod]),
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{Math.round(
|
||||
averages.mu.damage_dealt / averages.mu.battles,
|
||||
).toLocaleString()}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{Math.round(
|
||||
(averages.mu.survived_battles / averages.mu.battles) * 100,
|
||||
)}
|
||||
%
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{Math.round(averages.mu.xp / averages.mu.battles).toLocaleString()}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{(averages.mu.frags / averages.mu.battles).toFixed(2)}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{(averages.mu.spotted / averages.mu.battles).toFixed(2)}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{Math.round((averages.mu.hits / averages.mu.shots) * 100)}%
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{(averages.mu.shots / averages.mu.battles).toFixed(1)}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{(averages.mu.hits / averages.mu.battles).toFixed(1)}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{(averages.mu.damage_dealt / averages.mu.damage_received).toFixed(2)}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{Math.round(
|
||||
averages.mu.damage_received / averages.mu.battles,
|
||||
).toLocaleString()}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{(averages.mu.capture_points / averages.mu.battles).toFixed(2)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
},
|
||||
(prev, next) => prev.tank.id === next.tank.id,
|
||||
);
|
||||
@@ -1,143 +0,0 @@
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { Suspense, use, useEffect, useMemo, useState } from 'react';
|
||||
import { RowLoader } from './RowLoader';
|
||||
import { TankRow } from './TankRow';
|
||||
import { Total } from './Total';
|
||||
|
||||
const PREVIEW_COUNT = 10;
|
||||
const DEFAULT_LOADED_ROWS = 25;
|
||||
|
||||
export function Tanks() {
|
||||
const awaitedTankDefinitions = use(tankDefinitions);
|
||||
const awaitedAverageDefinitionsArray = use(averageDefinitionsArray);
|
||||
const filters = TankFilters.use();
|
||||
const sort = TankPerformanceSort.use();
|
||||
const tankPerformancePersistentStore = TankPerformancePersistent.useStore();
|
||||
const tanksSorted = useMemo(() => {
|
||||
const { playerCountPeriod } = tankPerformancePersistentStore.getState();
|
||||
|
||||
switch (sort.type) {
|
||||
case 'accuracy':
|
||||
return awaitedAverageDefinitionsArray.sort(
|
||||
(a, b) =>
|
||||
sort.direction * (a.mu.hits / a.mu.shots - b.mu.hits / b.mu.shots),
|
||||
);
|
||||
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[playerCountPeriod] - b.samples[playerCountPeriod]),
|
||||
);
|
||||
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]);
|
||||
const tanks = useMemo(() => {
|
||||
return tanksSorted.filter((tank) =>
|
||||
filterTank(filters, awaitedTankDefinitions[tank.id]),
|
||||
);
|
||||
}, [filters, sort]);
|
||||
const [loadedRows, setLoadedRows] = useState(DEFAULT_LOADED_ROWS);
|
||||
|
||||
useEffect(() => {
|
||||
setLoadedRows(DEFAULT_LOADED_ROWS);
|
||||
}, [filters, sort]);
|
||||
|
||||
return (
|
||||
<Table.Body>
|
||||
<Total tanks={tanks} />
|
||||
|
||||
{tanks.slice(0, loadedRows).map((averages) => {
|
||||
const tank = awaitedTankDefinitions[averages.id];
|
||||
|
||||
if (tank === undefined) return null;
|
||||
|
||||
return (
|
||||
<Suspense key={tank.id} fallback={<RowLoader />}>
|
||||
<TankRow tank={tank} />
|
||||
</Suspense>
|
||||
);
|
||||
})}
|
||||
|
||||
{times(Math.min(PREVIEW_COUNT, tanks.length - loadedRows), (index) => {
|
||||
return (
|
||||
<RowLoader
|
||||
key={index}
|
||||
onIntersection={() => {
|
||||
setLoadedRows((state) => Math.min(state + 2, tanks.length));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Table.Body>
|
||||
);
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import {
|
||||
formatCompact,
|
||||
TANK_CLASSES,
|
||||
TankClass,
|
||||
Tier,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
} from '@blitzkit/core';
|
||||
import strings from '@blitzkit/core/lang/en-US.json';
|
||||
import { ResponsiveBar } from '@nivo/bar';
|
||||
import { Box, Flex, Heading } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { use } from 'react';
|
||||
import { nivoTheme } from '../../../../core/nivo/theme';
|
||||
|
||||
export function TierBreakdown() {
|
||||
const awaitedAverageDefinitionsArray = use(averageDefinitionsArray);
|
||||
const awaitedTankDefinitions = use(tankDefinitions);
|
||||
|
||||
return (
|
||||
<Flex justify="center" py="6">
|
||||
<Flex direction="column" flexGrow="1" gap="4" maxWidth="640px">
|
||||
<Heading ml="2" size="4">
|
||||
Players (30d) per tier
|
||||
</Heading>
|
||||
|
||||
<Box height="320px" width="100%">
|
||||
<ResponsiveBar
|
||||
{...nivoTheme}
|
||||
data={times(10, (index) => {
|
||||
const tier = (index + 1) as Tier;
|
||||
const tierTanks = awaitedAverageDefinitionsArray.filter(
|
||||
({ id }) => awaitedTankDefinitions[id]?.tier === tier,
|
||||
);
|
||||
const populations: Record<TankClass, number> = {
|
||||
'AT-SPG': 0,
|
||||
heavyTank: 0,
|
||||
lightTank: 0,
|
||||
mediumTank: 0,
|
||||
};
|
||||
|
||||
tierTanks.forEach((stats) => {
|
||||
const tank = awaitedTankDefinitions[stats.id];
|
||||
populations[tank.class] +=
|
||||
stats.mu.battles * stats.samples.d_30;
|
||||
});
|
||||
|
||||
TANK_CLASSES.forEach((tankClass) => {
|
||||
populations[tankClass] = Math.round(populations[tankClass]);
|
||||
});
|
||||
|
||||
return { tier: TIER_ROMAN_NUMERALS[tier], ...populations };
|
||||
})}
|
||||
keys={[...TANK_CLASSES].reverse()}
|
||||
indexBy="tier"
|
||||
colors={{ scheme: 'dark2' }}
|
||||
label={(d) => formatCompact(d.value as number)}
|
||||
margin={{ right: 96, bottom: 20, left: 40 }}
|
||||
legendLabel={(d) =>
|
||||
strings.common.tank_class_short[d.id as TankClass]
|
||||
}
|
||||
axisLeft={{
|
||||
tickSize: 0,
|
||||
format: (value) => formatCompact(value as number),
|
||||
}}
|
||||
axisBottom={{ tickSize: 0 }}
|
||||
legends={[
|
||||
{
|
||||
dataFrom: 'keys',
|
||||
anchor: 'bottom-right',
|
||||
direction: 'column',
|
||||
translateX: 120,
|
||||
itemsSpacing: 4,
|
||||
itemWidth: 100,
|
||||
itemHeight: 20,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import {
|
||||
averageDefinitions,
|
||||
AverageDefinitionsEntryWithId,
|
||||
BlitzkitStats,
|
||||
formatCompact,
|
||||
} from '@blitzkit/core';
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { memo, use, useCallback } from 'react';
|
||||
import { StickyRowHeaderCell } from '../../../../components/StickyRowHeaderCell';
|
||||
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
|
||||
import * as TankPerformancePersistent from '../../../../stores/tankPerformancePersistent';
|
||||
|
||||
interface TotalProps {
|
||||
tanks: AverageDefinitionsEntryWithId[];
|
||||
}
|
||||
|
||||
export const Total = memo<TotalProps>(
|
||||
({ tanks }) => {
|
||||
const awaitedAverageDefinitions = use(averageDefinitions);
|
||||
const playerCountPeriod = TankPerformancePersistent.use(
|
||||
(state) => state.playerCountPeriod,
|
||||
);
|
||||
const ratio = useAveragesExclusionRatio();
|
||||
const sum = useCallback(
|
||||
(slice: (tank: BlitzkitStats) => number) => {
|
||||
return tanks.reduce(
|
||||
(acc, tank) => acc + tank.samples.total * slice(tank.mu),
|
||||
0,
|
||||
);
|
||||
},
|
||||
[tanks],
|
||||
);
|
||||
|
||||
const battles = sum(({ battles }) => battles);
|
||||
const winrate = sum(({ wins }) => wins) / battles;
|
||||
const damage = sum(({ damage_dealt }) => damage_dealt) / battles;
|
||||
const survival = sum(({ survived_battles }) => survived_battles) / battles;
|
||||
const xp = sum(({ xp }) => xp) / battles;
|
||||
const kills = sum(({ frags }) => frags) / battles;
|
||||
const spots = sum(({ spotted }) => spotted) / battles;
|
||||
const hits = sum(({ hits }) => hits) / battles;
|
||||
const shots = sum(({ shots }) => shots) / battles;
|
||||
const accuracy = hits / shots;
|
||||
const damageReceived =
|
||||
sum(({ damage_received }) => damage_received) / battles;
|
||||
const damageRatio = damage / damageReceived;
|
||||
const capturePoints = sum(({ capture_points }) => capture_points) / battles;
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<StickyRowHeaderCell style={{ overflow: 'hidden', display: 'flex' }}>
|
||||
Total
|
||||
</StickyRowHeaderCell>
|
||||
<Table.Cell align="center">{(winrate * 100).toFixed(1)}%</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{formatCompact(
|
||||
ratio * awaitedAverageDefinitions.samples[playerCountPeriod],
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{Math.round(damage).toLocaleString()}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{Math.round(survival * 100).toFixed(1)}%
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{Math.round(xp).toLocaleString()}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">{kills.toFixed(2)}</Table.Cell>
|
||||
<Table.Cell align="center">{spots.toFixed(2)}</Table.Cell>
|
||||
<Table.Cell align="center">{(accuracy * 100).toFixed(0)}%</Table.Cell>
|
||||
<Table.Cell align="center">{shots.toFixed(1)}</Table.Cell>
|
||||
<Table.Cell align="center">{hits.toFixed(1)}</Table.Cell>
|
||||
<Table.Cell align="center">{damageRatio.toFixed(2)}</Table.Cell>
|
||||
<Table.Cell align="center">
|
||||
{Math.round(damageReceived).toLocaleString()}
|
||||
</Table.Cell>
|
||||
<Table.Cell align="center">{capturePoints.toFixed(2)}</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
},
|
||||
(prev, next) => prev.tanks === next.tanks,
|
||||
);
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Controls } from '../../../../../website/src/components/Performance/Controls';
|
||||
import { Info } from '../../../../../website/src/components/Performance/PerformanceInfo';
|
||||
import { TankTable } from '../../../../../website/src/components/Performance/Table';
|
||||
import { PageWrapper } from '../../../components/PageWrapper';
|
||||
import { Controls } from './components/Controls';
|
||||
import { Info } from './components/Info';
|
||||
import { TankTable } from './components/Table';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
|
||||
@@ -7,5 +7,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*", ".next/types/**/*.ts"]
|
||||
"include": [
|
||||
"src/**/*",
|
||||
".next/types/**/*.ts",
|
||||
"../website/src/components/Performance/PerformanceInfo.tsx"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user