From 7f431b07e140c0f4a1903ffe9df3a0f9fb3179a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 1 Oct 2023 18:43:40 -0500 Subject: [PATCH] feat: added options --- package.json | 1 + src/app/embeds/session/page.tsx | 278 ++++++++++-------- .../tools/session/components/CustomColumn.tsx | 66 +++++ src/app/tools/session/page.tsx | 39 ++- src/components/Breakdown/components/Row.tsx | 39 +-- src/stores/session.ts | 19 ++ yarn.lock | 5 + 7 files changed, 305 insertions(+), 142 deletions(-) create mode 100644 src/app/tools/session/components/CustomColumn.tsx diff --git a/package.json b/package.json index 520ce4f0e..97e38c2e1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/embeds/session/page.tsx b/src/app/embeds/session/page.tsx index d04180de6..4bff801db 100644 --- a/src/app/embeds/session/page.tsx +++ b/src/app/embeds/session/page.tsx @@ -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 ( - {session.isTracking && since !== undefined && diff !== undefined && ( - 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 && ( + { + 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 }) => ( - { - 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 ( + { + 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, + }; + })} + /> + ); + })} ); } diff --git a/src/app/tools/session/components/CustomColumn.tsx b/src/app/tools/session/components/CustomColumn.tsx new file mode 100644 index 000000000..ee4a788e3 --- /dev/null +++ b/src/app/tools/session/components/CustomColumn.tsx @@ -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 ( + + Column {column + 1} + + + Display + + + mutateSession((draft) => { + draft.customColumns[column].display = + event as CustomColumnDisplay; + }) + } + > + + Battles + + + Winrate + + WN8 + Damage + None + + + {!NO_DELTA.includes(customColumn.display) && ( + <> + + + + mutateSession((draft) => { + draft.customColumns[column].showDelta = checked; + }) + } + > + Show delta + + + )} + + + ); +} diff --git a/src/app/tools/session/page.tsx b/src/app/tools/session/page.tsx index e267eee47..294337774 100644 --- a/src/app/tools/session/page.tsx +++ b/src/app/tools/session/page.tsx @@ -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(null); const [searchResults, setSearchResults] = useState< @@ -150,6 +154,7 @@ export default function Page() { + + + + + + + + + + + + + + + + useSession.setState({ showTotal: checked }) + } + > + Show total + + + diff --git a/src/components/Breakdown/components/Row.tsx b/src/components/Breakdown/components/Row.tsx index 5acd25cf8..b7ac74ab2 100644 --- a/src/components/Breakdown/components/Row.tsx +++ b/src/components/Breakdown/components/Row.tsx @@ -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) => ( - - ))} + {props.stats.map( + (row, index) => + row && ( + + ), + )} ); diff --git a/src/stores/session.ts b/src/stores/session.ts index 02cd818d3..8e36c4895 100644 --- a/src/stores/session.ts +++ b/src/stores/session.ts @@ -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()( @@ -26,6 +33,14 @@ export const useSession = create()( (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()( ), ), ); + +export default function mutateSession(recipe: (draft: Session) => void) { + useSession.setState(produce(recipe)); +} diff --git a/yarn.lock b/yarn.lock index 6a0c7fd15..d85a8246c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"