feat: added options

This commit is contained in:
TrèsAbhi committed 2023-10-01 18:43:40 -05:00
1 parent 8264f19ad4
commit 7f431b07e1
7 files changed
+305 -142

No files matched your search

+1
View File
@@ -37,6 +37,7 @@
"esbuild": "^0.19.3",
"express": "^4.18.2",
"fuzzysort": "^2.0.4",
"immer": "^10.0.2",
"lodash": "^4.17.21",
"markdown-escape": "^2.0.0",
"next": "^13.5.2",
+156 -122
View File
@@ -12,6 +12,7 @@ import { TankopediaEntry, tankopedia } from '../../../core/blitz/tankopedia';
import { tankAverages } from '../../../core/blitzstars/tankAverages';
import { useSession } from '../../../stores/session';
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
import { CustomColumnDisplay } from '../../tools/session/components/CustomColumn';
const REFRESH_RATE = 1 / 5;
@@ -139,136 +140,169 @@ export default function SessionPage() {
return () => clearInterval(interval);
}, [session]);
const totalRowStats: Record<
CustomColumnDisplay,
Breakdown.RowStat | undefined
> = {
battles: {
title: 'Battles',
current: sessionBattles?.toLocaleString(),
career: tankStatsArray
.reduce((accumulator, stats) => accumulator + stats.all.battles, 0)
.toLocaleString(),
},
winrate: {
title: 'Winrate',
current: sessionBattles ? `${(100 * sessionWinrate).toFixed(2)}%` : '--',
career: `${(100 * careerWinrate).toFixed(2)}%`,
delta: sessionBattles === 0 ? 0 : sessionWinrate - careerWinrate,
},
wn8: (() => {
if (!diff) return undefined;
const battlesWithWN8 = diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
(currentWN8 === undefined ? 0 : stats.all.battles) + accumulator,
0,
);
const currentWN8 =
diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
stats.all.battles * (currentWN8 ?? 0) + accumulator,
0,
) / battlesWithWN8!;
return {
title: 'WN8',
current: battlesWithWN8
? Math.round(currentWN8!).toLocaleString()
: '--',
percentile: battlesWithWN8 ? getWN8Percentile(currentWN8!) : undefined,
career: Math.round(diff.careerWN8).toLocaleString(),
};
})(),
damage: {
title: 'Damage',
delta: sessionBattles === 0 ? 0 : currentDamage - careerDamage,
current: sessionBattles
? Math.round(currentDamage).toLocaleString()
: '--',
career: Math.round(careerDamage).toLocaleString(),
},
none: undefined,
};
return (
<Breakdown.Root>
{session.isTracking && since !== undefined && diff !== undefined && (
<Breakdown.Row
title={`Since ${since.toLocaleString(undefined, {
hour: 'numeric',
minute: 'numeric',
hour12: true,
month: 'short',
year: 'numeric',
day: 'numeric',
})}`}
stats={[
{
title: 'Battles',
current: sessionBattles!.toLocaleString(),
career: tankStatsArray
.reduce(
(accumulator, stats) => accumulator + stats.all.battles,
0,
)
.toLocaleString(),
},
{
title: 'Winrate',
current: sessionBattles
? `${(100 * sessionWinrate).toFixed(2)}%`
: '--',
career: `${(100 * careerWinrate).toFixed(2)}%`,
delta: sessionBattles === 0 ? 0 : sessionWinrate - careerWinrate,
},
(() => {
const battlesWithWN8 = diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
(currentWN8 === undefined ? 0 : stats.all.battles) +
accumulator,
0,
);
const currentWN8 =
diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
stats.all.battles * (currentWN8 ?? 0) + accumulator,
0,
) / battlesWithWN8;
{session.showTotal &&
session.isTracking &&
since !== undefined &&
diff !== undefined && (
<Breakdown.Row
title={`Since ${since.toLocaleString(undefined, {
hour: 'numeric',
minute: 'numeric',
hour12: true,
month: 'short',
year: 'numeric',
day: 'numeric',
})}`}
stats={session.customColumns.map((customColumn) => {
const rowStat = totalRowStats[customColumn.display];
if (!rowStat) return undefined;
return {
title: 'WN8',
current: battlesWithWN8
? Math.round(currentWN8).toLocaleString()
: '--',
percentile: battlesWithWN8
? getWN8Percentile(currentWN8)
: undefined,
career: Math.round(diff.careerWN8).toLocaleString(),
...rowStat,
delta:
customColumn.showDelta && rowStat.percentile === undefined
? rowStat.delta
: undefined,
};
})(),
{
title: 'Damage',
delta: sessionBattles === 0 ? 0 : currentDamage - careerDamage,
current: sessionBattles
? Math.round(currentDamage).toLocaleString()
: '--',
career: Math.round(careerDamage).toLocaleString(),
},
]}
/>
)}
})}
/>
)}
{diff?.list
.sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time)
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => (
<Breakdown.Row
key={stats.tank_id}
title={tankopedia?.name ?? `Unknown tank ${stats.tank_id}`}
type="tank"
tankType={tankopedia?.type}
treeType={(() => {
if (tankopedia?.is_collectible) return TreeTypeEnum.Collector;
if (tankopedia?.is_premium) return TreeTypeEnum.Premium;
})()}
stats={[
{
title: 'Battles',
current: stats.all.battles.toLocaleString(),
career: career.all.battles.toLocaleString(),
},
{
title: 'Winrate',
current: `${(
100 *
(stats.all.wins / stats.all.battles)
).toFixed(2)}%`,
career: `${(
100 *
(career.all.wins / career.all.battles)
).toFixed(2)}%`,
delta:
stats.all.wins / stats.all.battles -
career.all.wins / career.all.battles,
},
{
title: 'WN8',
current:
currentWN8 === undefined
? undefined
: Math.round(currentWN8).toLocaleString(),
career:
careerWN8 === undefined
? undefined
: Math.round(careerWN8).toLocaleString(),
percentile:
currentWN8 === undefined
? undefined
: getWN8Percentile(currentWN8),
},
{
title: 'Damage',
current: Math.round(
stats.all.damage_dealt / stats.all.battles,
).toLocaleString(),
career: Math.round(
career.all.damage_dealt / career.all.battles,
).toLocaleString(),
delta:
stats.all.damage_dealt / stats.all.battles -
career.all.damage_dealt / career.all.battles,
},
]}
/>
))}
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => {
const rowStats: Record<
CustomColumnDisplay,
Breakdown.RowStat | undefined
> = {
battles: {
title: 'Battles',
current: stats.all.battles.toLocaleString(),
career: career.all.battles.toLocaleString(),
},
winrate: {
title: 'Winrate',
current: `${(100 * (stats.all.wins / stats.all.battles)).toFixed(
2,
)}%`,
career: `${(100 * (career.all.wins / career.all.battles)).toFixed(
2,
)}%`,
delta:
stats.all.wins / stats.all.battles -
career.all.wins / career.all.battles,
},
wn8: {
title: 'WN8',
current:
currentWN8 === undefined
? undefined
: Math.round(currentWN8).toLocaleString(),
career:
careerWN8 === undefined
? undefined
: Math.round(careerWN8).toLocaleString(),
percentile:
currentWN8 === undefined
? undefined
: getWN8Percentile(currentWN8),
},
damage: {
title: 'Damage',
current: Math.round(
stats.all.damage_dealt / stats.all.battles,
).toLocaleString(),
career: Math.round(
career.all.damage_dealt / career.all.battles,
).toLocaleString(),
delta:
stats.all.damage_dealt / stats.all.battles -
career.all.damage_dealt / career.all.battles,
},
none: undefined,
};
return (
<Breakdown.Row
key={stats.tank_id}
title={tankopedia?.name ?? `Unknown tank ${stats.tank_id}`}
type="tank"
tankType={tankopedia?.type}
treeType={(() => {
if (tankopedia?.is_collectible) return TreeTypeEnum.Collector;
if (tankopedia?.is_premium) return TreeTypeEnum.Premium;
})()}
stats={session.customColumns.map((customColumn) => {
const rowStat = rowStats[customColumn.display];
if (!rowStat) return undefined;
return {
...rowStat,
delta:
customColumn.showDelta && rowStat.percentile === undefined
? rowStat.delta
: undefined,
};
})}
/>
);
})}
</Breakdown.Root>
);
}
@@ -0,0 +1,66 @@
import { DropdownMenu } from '@radix-ui/themes';
import mutateSession, { useSession } from '../../../../stores/session';
export type CustomColumnDisplay =
| 'battles'
| 'winrate'
| 'wn8'
| 'damage'
| 'none';
const NO_DELTA: CustomColumnDisplay[] = ['battles', 'wn8', 'none'];
interface CustomColumn {
column: number;
}
export function CustomColumn({ column }: CustomColumn) {
const customColumn = useSession((state) => state.customColumns[column]);
return (
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Column {column + 1}</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Label>Display</DropdownMenu.Label>
<DropdownMenu.RadioGroup
value={customColumn.display}
onValueChange={(event) =>
mutateSession((draft) => {
draft.customColumns[column].display =
event as CustomColumnDisplay;
})
}
>
<DropdownMenu.RadioItem value="battles">
Battles
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="winrate">
Winrate
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="wn8">WN8</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="damage">Damage</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="none">None</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
{!NO_DELTA.includes(customColumn.display) && (
<>
<DropdownMenu.Separator />
<DropdownMenu.CheckboxItem
checked={customColumn.showDelta}
onCheckedChange={(checked) =>
mutateSession((draft) => {
draft.customColumns[column].showDelta = checked;
})
}
>
Show delta
</DropdownMenu.CheckboxItem>
</>
)}
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
);
}
+36 -3
View File
@@ -1,6 +1,11 @@
'use client';
import { CopyIcon, PersonIcon, ReloadIcon } from '@radix-ui/react-icons';
import {
CaretDownIcon,
CopyIcon,
PersonIcon,
ReloadIcon,
} from '@radix-ui/react-icons';
import {
AlertDialog,
Button,
@@ -23,10 +28,9 @@ import listPlayers, {
import { useSession } from '../../../stores/session';
import { NormalizedTankStats, TanksStats } from '../../../types/tanksStats';
import SessionPage from '../../embeds/session/page';
import { CustomColumn } from './components/CustomColumn';
import * as styles from './page.css';
// 1 once per 5 seconds
export default function Page() {
const input = useRef<HTMLInputElement>(null);
const [searchResults, setSearchResults] = useState<
@@ -150,6 +154,7 @@ export default function Page() {
<AlertDialog.Root>
<AlertDialog.Trigger>
<Button
variant="soft"
onClick={(event) => {
if (!session.promptBeforeReset) {
event.preventDefault();
@@ -206,6 +211,7 @@ export default function Page() {
<Tooltip content="Copy to clipboard">
<Button
variant="soft"
className={styles.toolbarButton}
onClick={() =>
navigator.clipboard.writeText(
@@ -216,6 +222,33 @@ export default function Page() {
<CopyIcon width="16" height="16" /> Embed
</Button>
</Tooltip>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="soft" className={styles.toolbarButton}>
Options
<CaretDownIcon />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<CustomColumn column={0} />
<CustomColumn column={1} />
<CustomColumn column={2} />
<CustomColumn column={3} />
<DropdownMenu.Separator />
<DropdownMenu.CheckboxItem
checked={session.showTotal}
onCheckedChange={(checked) =>
useSession.setState({ showTotal: checked })
}
>
Show total
</DropdownMenu.CheckboxItem>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
</div>
+22 -17
View File
@@ -4,6 +4,14 @@ import { theme } from '../../../stitches.config';
import { TREE_TYPE_ICONS, TreeTypeEnum } from '../../Tanks';
import { RowStat } from './RowStat';
export interface RowStat {
title: string;
current?: number | string;
career?: number | string;
delta?: number;
percentile?: Percentile;
}
interface RowProps {
type?: 'tank' | 'summary';
title: string;
@@ -11,13 +19,7 @@ interface RowProps {
treeType?: TreeTypeEnum;
tankType?: TankType;
stats: {
title: string;
current?: number | string;
career?: number | string;
delta?: number;
percentile?: Percentile;
}[];
stats: (RowStat | undefined)[];
}
export function Row(props: RowProps) {
@@ -77,16 +79,19 @@ export function Row(props: RowProps) {
paddingBottom: 8,
}}
>
{props.stats.map((row, index) => (
<RowStat
minimized={props.minimized}
key={index}
name={`${row.title} • ${row.career ?? '--'}`}
value={row.current ?? '--'}
delta={row.delta}
percentile={row.percentile}
/>
))}
{props.stats.map(
(row, index) =>
row && (
<RowStat
minimized={props.minimized}
key={index}
name={`${row.title} • ${row.career ?? '--'}`}
value={row.current ?? '--'}
delta={row.delta}
percentile={row.percentile}
/>
),
)}
</div>
</div>
);
+19
View File
@@ -1,5 +1,7 @@
import { produce } from 'immer';
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { CustomColumnDisplay } from '../app/tools/session/components/CustomColumn';
import { Region } from '../constants/regions';
import { NormalizedTankStats } from '../types/tanksStats';
@@ -18,6 +20,11 @@ type Session = (
}
) & {
promptBeforeReset: boolean;
customColumns: {
display: CustomColumnDisplay;
showDelta: boolean;
}[] & { length: 4 };
showTotal: boolean;
};
export const useSession = create<Session>()(
@@ -26,6 +33,14 @@ export const useSession = create<Session>()(
(set) => ({
isTracking: false,
promptBeforeReset: true,
showTotal: true,
customColumns: [
{ display: 'battles', showDelta: false },
{ display: 'winrate', showDelta: true },
{ display: 'wn8', showDelta: false },
{ display: 'damage', showDelta: true },
],
}),
{
name: 'session',
@@ -33,3 +48,7 @@ export const useSession = create<Session>()(
),
),
);
export default function mutateSession(recipe: (draft: Session) => void) {
useSession.setState(produce(recipe));
}
+5
View File
@@ -3078,6 +3078,11 @@ ignore@^5.1.8, ignore@^5.2.4:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
immer@^10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/immer/-/immer-10.0.2.tgz#11636c5b77acf529e059582d76faf338beb56141"
integrity sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==
indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"