mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc9ab2067d | ||
|
|
e8e1f7bafb | ||
|
|
e50a456025 | ||
|
|
d442bbca1a | ||
|
|
92e35bd0ac |
No files matched your search
@@ -1,4 +1,6 @@
|
||||
# blitzkrieg
|
||||
# Blitzkrieg
|
||||
|
||||
[](https://codeium.com)
|
||||
|
||||
Tools for everything World of Tanks Blitz
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Blitzkrieg 1.13.2
|
||||
|
||||
One lonely albeit important fix.
|
||||
|
||||
## Fixes
|
||||
|
||||
- Gradients for `/stats` now use Radix's new hex values
|
||||
@@ -0,0 +1,7 @@
|
||||
# Blitzkrieg 1.13.3
|
||||
|
||||
Streamer's delight!
|
||||
|
||||
## Features
|
||||
|
||||
- Added config context menu for the session tracker embed
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "blitzkrieg",
|
||||
"version": "1.13.1",
|
||||
"version": "1.13.3",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"main": "src/server.ts",
|
||||
"type": "module",
|
||||
|
||||
+125
-108
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { ContextMenu } from '@radix-ui/themes';
|
||||
import { useEffect, useState } from 'react';
|
||||
import * as Breakdown from '../../../components/Breakdown';
|
||||
import { TreeTypeEnum } from '../../../components/Tanks';
|
||||
@@ -13,6 +14,7 @@ import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
||||
import { useSession } from '../../../stores/session';
|
||||
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
|
||||
import { CustomColumnDisplay } from '../../tools/session/components/CustomColumn';
|
||||
import { Menu } from '../../tools/session/components/Menu';
|
||||
|
||||
const REFRESH_RATE = 1 / 5;
|
||||
|
||||
@@ -193,118 +195,133 @@ export default function SessionPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Breakdown.Root>
|
||||
{session.showTotal &&
|
||||
session.isTracking &&
|
||||
since !== undefined &&
|
||||
diff !== undefined && (
|
||||
<Breakdown.Row
|
||||
minimized={!session.showCareer}
|
||||
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];
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger>
|
||||
<div>
|
||||
<Breakdown.Root>
|
||||
{session.showTotal &&
|
||||
session.isTracking &&
|
||||
since !== undefined &&
|
||||
diff !== undefined && (
|
||||
<Breakdown.Row
|
||||
minimized={!session.showCareer}
|
||||
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;
|
||||
if (!rowStat) return undefined;
|
||||
|
||||
return {
|
||||
...rowStat,
|
||||
delta:
|
||||
customColumn.showDelta && rowStat.percentile === undefined
|
||||
? rowStat.delta
|
||||
: undefined,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
return {
|
||||
...rowStat,
|
||||
delta:
|
||||
customColumn.showDelta &&
|
||||
rowStat.percentile === undefined
|
||||
? rowStat.delta
|
||||
: undefined,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
|
||||
{diff?.list
|
||||
.sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time)
|
||||
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => {
|
||||
const rowStats: Record<
|
||||
CustomColumnDisplay,
|
||||
Breakdown.RowStatItem | 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}
|
||||
minimized={!session.showCareer}
|
||||
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,
|
||||
{diff?.list
|
||||
.sort(
|
||||
(a, b) => b.stats.last_battle_time - a.stats.last_battle_time,
|
||||
)
|
||||
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => {
|
||||
const rowStats: Record<
|
||||
CustomColumnDisplay,
|
||||
Breakdown.RowStatItem | 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}
|
||||
minimized={!session.showCareer}
|
||||
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>
|
||||
</Breakdown.Root>
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<Menu Builder={ContextMenu} />
|
||||
</ContextMenu.Root>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DropdownMenu } from '@radix-ui/themes';
|
||||
import { ContextMenu, DropdownMenu } from '@radix-ui/themes';
|
||||
import mutateSession, { useSession } from '../../../../stores/session';
|
||||
|
||||
export type CustomColumnDisplay =
|
||||
@@ -11,20 +11,21 @@ export type CustomColumnDisplay =
|
||||
const NO_DELTA: CustomColumnDisplay[] = ['battles', 'wn8', 'none'];
|
||||
|
||||
interface CustomColumn {
|
||||
Builder: typeof DropdownMenu | typeof ContextMenu;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export function CustomColumn({ column }: CustomColumn) {
|
||||
export function CustomColumn({ column, Builder }: CustomColumn) {
|
||||
const customColumn = useSession((state) => state.customColumns[column]);
|
||||
|
||||
return (
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger>Column {column + 1}</DropdownMenu.SubTrigger>
|
||||
<Builder.Sub>
|
||||
<Builder.SubTrigger>Column {column + 1}</Builder.SubTrigger>
|
||||
|
||||
<DropdownMenu.SubContent>
|
||||
<DropdownMenu.Label>Display</DropdownMenu.Label>
|
||||
<Builder.SubContent>
|
||||
<Builder.Label>Display</Builder.Label>
|
||||
|
||||
<DropdownMenu.RadioGroup
|
||||
<Builder.RadioGroup
|
||||
value={customColumn.display}
|
||||
onValueChange={(event) =>
|
||||
mutateSession((draft) => {
|
||||
@@ -33,22 +34,18 @@ export function CustomColumn({ column }: CustomColumn) {
|
||||
})
|
||||
}
|
||||
>
|
||||
<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>
|
||||
<Builder.RadioItem value="battles">Battles</Builder.RadioItem>
|
||||
<Builder.RadioItem value="winrate">Winrate</Builder.RadioItem>
|
||||
<Builder.RadioItem value="wn8">WN8</Builder.RadioItem>
|
||||
<Builder.RadioItem value="damage">Damage</Builder.RadioItem>
|
||||
<Builder.RadioItem value="none">None</Builder.RadioItem>
|
||||
</Builder.RadioGroup>
|
||||
|
||||
{!NO_DELTA.includes(customColumn.display) && (
|
||||
<>
|
||||
<DropdownMenu.Separator />
|
||||
<Builder.Separator />
|
||||
|
||||
<DropdownMenu.CheckboxItem
|
||||
<Builder.CheckboxItem
|
||||
checked={customColumn.showDelta}
|
||||
onCheckedChange={(checked) =>
|
||||
mutateSession((draft) => {
|
||||
@@ -57,10 +54,10 @@ export function CustomColumn({ column }: CustomColumn) {
|
||||
}
|
||||
>
|
||||
Show delta
|
||||
</DropdownMenu.CheckboxItem>
|
||||
</Builder.CheckboxItem>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
</Builder.SubContent>
|
||||
</Builder.Sub>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ContextMenu, DropdownMenu } from '@radix-ui/themes';
|
||||
import { useSession } from '../../../../stores/session';
|
||||
import { CustomColumn } from './CustomColumn';
|
||||
|
||||
interface ContextMenuProps {
|
||||
Builder: typeof DropdownMenu | typeof ContextMenu;
|
||||
}
|
||||
|
||||
export function Menu({ Builder }: ContextMenuProps) {
|
||||
const session = useSession();
|
||||
|
||||
return (
|
||||
<Builder.Content>
|
||||
<CustomColumn Builder={Builder} column={0} />
|
||||
<CustomColumn Builder={Builder} column={1} />
|
||||
<CustomColumn Builder={Builder} column={2} />
|
||||
<CustomColumn Builder={Builder} column={3} />
|
||||
|
||||
<Builder.Separator />
|
||||
|
||||
<Builder.CheckboxItem
|
||||
checked={session.showTotal}
|
||||
onCheckedChange={(checked) =>
|
||||
useSession.setState({ showTotal: checked })
|
||||
}
|
||||
>
|
||||
Show total
|
||||
</Builder.CheckboxItem>
|
||||
<Builder.CheckboxItem
|
||||
checked={session.showCareer}
|
||||
onCheckedChange={(checked) =>
|
||||
useSession.setState({ showCareer: checked })
|
||||
}
|
||||
>
|
||||
Show career
|
||||
</Builder.CheckboxItem>
|
||||
</Builder.Content>
|
||||
);
|
||||
}
|
||||
@@ -28,7 +28,7 @@ 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 { Menu } from './components/Menu';
|
||||
import * as styles from './page.css';
|
||||
|
||||
export default function Page() {
|
||||
@@ -231,31 +231,7 @@ export default function Page() {
|
||||
</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.CheckboxItem
|
||||
checked={session.showCareer}
|
||||
onCheckedChange={(checked) =>
|
||||
useSession.setState({ showCareer: checked })
|
||||
}
|
||||
>
|
||||
Show career
|
||||
</DropdownMenu.CheckboxItem>
|
||||
</DropdownMenu.Content>
|
||||
<Menu Builder={DropdownMenu} />
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Percentile } from '../../../../../constants/percentiles';
|
||||
import hslToHex from '../../../../../core/node/hslToHex';
|
||||
import { PERCENTILE_COLORS } from '../../../../PercentileIndicator';
|
||||
|
||||
export enum GlowSide {
|
||||
@@ -14,8 +13,8 @@ export interface GlowProps {
|
||||
|
||||
export function Glow({ percentile, side }: GlowProps) {
|
||||
const color = PERCENTILE_COLORS[percentile];
|
||||
const color1 = hslToHex(color);
|
||||
const color2 = `${color1}00`;
|
||||
const color1 = color;
|
||||
const color2 = `${color}00`;
|
||||
|
||||
const background = `linear-gradient(${
|
||||
side === GlowSide.Top ? 0 : 180
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
const numberRegex = /\d+(\.\d+)?/g;
|
||||
|
||||
/**
|
||||
* Thanks to icl7126!
|
||||
* https://stackoverflow.com/a/44134328/12294756
|
||||
*/
|
||||
export default function hslToHex(hsl: string) {
|
||||
let [h, s, l] = Array.from(hsl.matchAll(numberRegex)).map((m) =>
|
||||
parseFloat(m[0]),
|
||||
);
|
||||
|
||||
l /= 100;
|
||||
const a = (s * Math.min(l, 1 - l)) / 100;
|
||||
const f = (n: number) => {
|
||||
const k = (n + h / 30) % 12;
|
||||
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
return Math.round(255 * color)
|
||||
.toString(16)
|
||||
.padStart(2, '0'); // convert to Hex and prefix "0" if needed
|
||||
};
|
||||
return `#${f(0)}${f(8)}${f(4)}`;
|
||||
}
|
||||
Reference in new issue
Block a user