mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92e35bd0ac | ||
|
|
cb9d283f62 | ||
|
|
8aba19e05b | ||
|
|
b7b4035492 | ||
|
|
3667fd82b7 | ||
|
|
691d5cde27 | ||
|
|
7f431b07e1 | ||
|
|
8264f19ad4 | ||
|
|
e947cfba45 | ||
|
|
e69590c035 | ||
|
|
8b19393d7c | ||
|
|
9480603572 | ||
|
|
90c6535a4c | ||
|
|
258588921a | ||
|
|
525b309037 | ||
|
|
2f941b9224 | ||
|
|
259c4240b5 | ||
|
|
9eb7157fce |
No files matched your search
@@ -29,4 +29,4 @@ jobs:
|
|||||||
unzip bot.zip
|
unzip bot.zip
|
||||||
|
|
||||||
- name: Run bot
|
- name: Run bot
|
||||||
run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} WARGAMING_APPLICATION_ID=${{ WARGAMING_APPLICATION_ID }} node --enable-source-maps --trace-warnings bot/index.cjs
|
run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} node --enable-source-maps --trace-warnings index.cjs
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: CI Build
|
name: CI Build Bot
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: CI Build
|
name: CI Build Docs
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: CI Build
|
name: CI Build Website
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,44 @@
|
|||||||
# blitzkrieg
|
# Blitzkrieg
|
||||||
|
|
||||||
A Discord bot for anything and everything related to World of Tanks Blitz.
|
[](https://codeium.com)
|
||||||
|
|
||||||
## Invite the Bot
|
Tools for everything World of Tanks Blitz
|
||||||
|
|
||||||
Join the [Discord server](https://discord.gg/nDt7AjGJQH), click on the bot's profile, and click "Add to Server".
|
## Invite the bot
|
||||||
|
|
||||||
## Using
|
Go to [application directory for Blitzkrieg](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go.
|
||||||
|
|
||||||
### Installing dependencies
|

|
||||||
|
|
||||||
|
## Visit the website
|
||||||
|
|
||||||
|
### https://blitz-krieg.vercel.app/
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Visit the documentation website
|
||||||
|
|
||||||
|
### https://tresabhi.github.io/blitzkrieg/
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Run the bot
|
||||||
|
|
||||||
|
### Install dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running
|
### Create `.env`
|
||||||
|
|
||||||
|
```env
|
||||||
|
GH_TOKEN={YOUR_GITHUB_TOKEN_HERE}
|
||||||
|
DISCORD_TOKEN={YOUR_DISCORD_TOKE_HERE}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run the command
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn start discord-token=<token> wargaming-application-id=<id>
|
yarn dev:bot
|
||||||
```
|
```
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Blitzkrieg 1.13.1
|
||||||
|
|
||||||
|
The quickest minor update in town 🤠
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Added a title, description, and icon to the website
|
||||||
|
- Added display options to the session tracker
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Center aligned stats in breakdown to work better on low width devices
|
||||||
|
- Center aligned the Blitzkrieg logo in the navbar
|
||||||
|
|
||||||
|
## Technical Changes
|
||||||
|
|
||||||
|
- Added up-to-date information to `README.md` and embed docs
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Blitzkrieg 1.13.2
|
||||||
|
|
||||||
|
One lonely albeit important fix.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Gradients for `/stats` now use Radix's new hex values
|
||||||
+12
-12
@@ -1,23 +1,23 @@
|
|||||||
# Embeds
|
# Embeds
|
||||||
|
|
||||||
Embeds are generated by a few commands as a URL that can be loaded in a browser/streaming software.
|
Embeds are URLs generated by commands or tools that represent the "naked" version of the respective command or tool
|
||||||
|
|
||||||
## Creating Embeds
|
## Creating Embeds
|
||||||
|
|
||||||
1. Use a command of your liking (for example, `/stats`).
|
1. Use a command or tool and click on "Embed" if it's available
|
||||||
2. Customize the command however you like and the URL generated for the embed will match your needs.
|
2. Make sure to tweak the settings of the command or tool because embeds replicate everything
|
||||||
3. Send the command and when the bot is "done thinking," an "Embed" button will appear below the bot's reply.
|
|
||||||
|
|
||||||
## Using Embeds in Streaming Softwares
|
## Using Embeds in Streaming Softwares
|
||||||
|
|
||||||
1. Click on the "Embed" button.
|
1. Click on the "Embed" button
|
||||||
2. Copy the URL from your browser.
|
- If this was generated by a command, copy the URL from the browser
|
||||||
3. Paste the URL into a Browser Source.
|
- If this was generated by a tool, the URL has been copied to your clipboard already
|
||||||
4. Set the refresh rate/FPS to something very low like `0.0167` (that's 1 frame every 60 seconds).
|
2. Paste this URL into your streaming service as a "Browser Source"
|
||||||
|
- Tool embeds use local storage so data won't be transferred from you browser to your streaming software
|
||||||
|
- Make sure to visit the tool's page in your streaming software (using the "Interact" feature) and then open the embed
|
||||||
|
- Set your browser source refresh rate to `1Hz` because that's how fast embeds refresh
|
||||||
|
|
||||||
## How Embeds Behave
|
## How Embeds Behave
|
||||||
|
|
||||||
- They load like any normal website albeit with a significant delay.
|
- They load like any normal website
|
||||||
- They auto refresh every `60s`.
|
- They auto refresh every `1s`
|
||||||
- All embeds and images generated by Blitzkrieg have a width of `640px` but the height varies and must be found manually through trial and error.
|
|
||||||
- It's okay to have a Browser Source that has a height larger than needed as Blitzkrieg embeds are transparent.
|
|
||||||
+3
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkrieg",
|
"name": "blitzkrieg",
|
||||||
"version": "1.13.0",
|
"version": "1.13.2",
|
||||||
"description": "🎉 All-in-one Discord bot for everything World of Tanks Blitz",
|
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||||
"main": "src/server.ts",
|
"main": "src/server.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
"esbuild": "^0.19.3",
|
"esbuild": "^0.19.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"fuzzysort": "^2.0.4",
|
"fuzzysort": "^2.0.4",
|
||||||
|
"immer": "^10.0.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"markdown-escape": "^2.0.0",
|
"markdown-escape": "^2.0.0",
|
||||||
"next": "^13.5.2",
|
"next": "^13.5.2",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 177 KiB |
+158
-122
@@ -12,6 +12,7 @@ import { TankopediaEntry, tankopedia } from '../../../core/blitz/tankopedia';
|
|||||||
import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
||||||
import { useSession } from '../../../stores/session';
|
import { useSession } from '../../../stores/session';
|
||||||
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
|
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
|
||||||
|
import { CustomColumnDisplay } from '../../tools/session/components/CustomColumn';
|
||||||
|
|
||||||
const REFRESH_RATE = 1 / 5;
|
const REFRESH_RATE = 1 / 5;
|
||||||
|
|
||||||
@@ -139,136 +140,171 @@ export default function SessionPage() {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
|
const totalRowStats: Record<
|
||||||
|
CustomColumnDisplay,
|
||||||
|
Breakdown.RowStatItem | 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 (
|
return (
|
||||||
<Breakdown.Root>
|
<Breakdown.Root>
|
||||||
{session.isTracking && since !== undefined && diff !== undefined && (
|
{session.showTotal &&
|
||||||
<Breakdown.Row
|
session.isTracking &&
|
||||||
title={`Since ${since.toLocaleString(undefined, {
|
since !== undefined &&
|
||||||
hour: 'numeric',
|
diff !== undefined && (
|
||||||
minute: 'numeric',
|
<Breakdown.Row
|
||||||
hour12: true,
|
minimized={!session.showCareer}
|
||||||
month: 'short',
|
title={`Since ${since.toLocaleString(undefined, {
|
||||||
year: 'numeric',
|
hour: 'numeric',
|
||||||
day: 'numeric',
|
minute: 'numeric',
|
||||||
})}`}
|
hour12: true,
|
||||||
stats={[
|
month: 'short',
|
||||||
{
|
year: 'numeric',
|
||||||
title: 'Battles',
|
day: 'numeric',
|
||||||
current: sessionBattles!.toLocaleString(),
|
})}`}
|
||||||
career: tankStatsArray
|
stats={session.customColumns.map((customColumn) => {
|
||||||
.reduce(
|
const rowStat = totalRowStats[customColumn.display];
|
||||||
(accumulator, stats) => accumulator + stats.all.battles,
|
|
||||||
0,
|
if (!rowStat) return undefined;
|
||||||
)
|
|
||||||
.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;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: 'WN8',
|
...rowStat,
|
||||||
current: battlesWithWN8
|
delta:
|
||||||
? Math.round(currentWN8).toLocaleString()
|
customColumn.showDelta && rowStat.percentile === undefined
|
||||||
: '--',
|
? rowStat.delta
|
||||||
percentile: battlesWithWN8
|
: undefined,
|
||||||
? getWN8Percentile(currentWN8)
|
|
||||||
: undefined,
|
|
||||||
career: Math.round(diff.careerWN8).toLocaleString(),
|
|
||||||
};
|
};
|
||||||
})(),
|
})}
|
||||||
{
|
/>
|
||||||
title: 'Damage',
|
)}
|
||||||
delta: sessionBattles === 0 ? 0 : currentDamage - careerDamage,
|
|
||||||
current: sessionBattles
|
|
||||||
? Math.round(currentDamage).toLocaleString()
|
|
||||||
: '--',
|
|
||||||
career: Math.round(careerDamage).toLocaleString(),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{diff?.list
|
{diff?.list
|
||||||
.sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time)
|
.sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time)
|
||||||
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => (
|
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => {
|
||||||
<Breakdown.Row
|
const rowStats: Record<
|
||||||
key={stats.tank_id}
|
CustomColumnDisplay,
|
||||||
title={tankopedia?.name ?? `Unknown tank ${stats.tank_id}`}
|
Breakdown.RowStatItem | undefined
|
||||||
type="tank"
|
> = {
|
||||||
tankType={tankopedia?.type}
|
battles: {
|
||||||
treeType={(() => {
|
title: 'Battles',
|
||||||
if (tankopedia?.is_collectible) return TreeTypeEnum.Collector;
|
current: stats.all.battles.toLocaleString(),
|
||||||
if (tankopedia?.is_premium) return TreeTypeEnum.Premium;
|
career: career.all.battles.toLocaleString(),
|
||||||
})()}
|
},
|
||||||
stats={[
|
winrate: {
|
||||||
{
|
title: 'Winrate',
|
||||||
title: 'Battles',
|
current: `${(100 * (stats.all.wins / stats.all.battles)).toFixed(
|
||||||
current: stats.all.battles.toLocaleString(),
|
2,
|
||||||
career: career.all.battles.toLocaleString(),
|
)}%`,
|
||||||
},
|
career: `${(100 * (career.all.wins / career.all.battles)).toFixed(
|
||||||
{
|
2,
|
||||||
title: 'Winrate',
|
)}%`,
|
||||||
current: `${(
|
delta:
|
||||||
100 *
|
stats.all.wins / stats.all.battles -
|
||||||
(stats.all.wins / stats.all.battles)
|
career.all.wins / career.all.battles,
|
||||||
).toFixed(2)}%`,
|
},
|
||||||
career: `${(
|
wn8: {
|
||||||
100 *
|
title: 'WN8',
|
||||||
(career.all.wins / career.all.battles)
|
current:
|
||||||
).toFixed(2)}%`,
|
currentWN8 === undefined
|
||||||
delta:
|
? undefined
|
||||||
stats.all.wins / stats.all.battles -
|
: Math.round(currentWN8).toLocaleString(),
|
||||||
career.all.wins / career.all.battles,
|
career:
|
||||||
},
|
careerWN8 === undefined
|
||||||
{
|
? undefined
|
||||||
title: 'WN8',
|
: Math.round(careerWN8).toLocaleString(),
|
||||||
current:
|
percentile:
|
||||||
currentWN8 === undefined
|
currentWN8 === undefined
|
||||||
? undefined
|
? undefined
|
||||||
: Math.round(currentWN8).toLocaleString(),
|
: getWN8Percentile(currentWN8),
|
||||||
career:
|
},
|
||||||
careerWN8 === undefined
|
damage: {
|
||||||
? undefined
|
title: 'Damage',
|
||||||
: Math.round(careerWN8).toLocaleString(),
|
current: Math.round(
|
||||||
percentile:
|
stats.all.damage_dealt / stats.all.battles,
|
||||||
currentWN8 === undefined
|
).toLocaleString(),
|
||||||
? undefined
|
career: Math.round(
|
||||||
: getWN8Percentile(currentWN8),
|
career.all.damage_dealt / career.all.battles,
|
||||||
},
|
).toLocaleString(),
|
||||||
{
|
delta:
|
||||||
title: 'Damage',
|
stats.all.damage_dealt / stats.all.battles -
|
||||||
current: Math.round(
|
career.all.damage_dealt / career.all.battles,
|
||||||
stats.all.damage_dealt / stats.all.battles,
|
},
|
||||||
).toLocaleString(),
|
none: undefined,
|
||||||
career: Math.round(
|
};
|
||||||
career.all.damage_dealt / career.all.battles,
|
|
||||||
).toLocaleString(),
|
return (
|
||||||
delta:
|
<Breakdown.Row
|
||||||
stats.all.damage_dealt / stats.all.battles -
|
key={stats.tank_id}
|
||||||
career.all.damage_dealt / career.all.battles,
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,14 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={robotoFlex.className}>
|
<html lang="en" className={robotoFlex.className}>
|
||||||
|
<head>
|
||||||
|
<title>Blitzkrieg</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="🎉 Tools for everything World of Tanks Blitz"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
|
||||||
<body
|
<body
|
||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { CopyIcon, PersonIcon, ReloadIcon } from '@radix-ui/react-icons';
|
import {
|
||||||
|
CaretDownIcon,
|
||||||
|
CopyIcon,
|
||||||
|
PersonIcon,
|
||||||
|
ReloadIcon,
|
||||||
|
} from '@radix-ui/react-icons';
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
Button,
|
Button,
|
||||||
@@ -23,10 +28,9 @@ import listPlayers, {
|
|||||||
import { useSession } from '../../../stores/session';
|
import { useSession } from '../../../stores/session';
|
||||||
import { NormalizedTankStats, TanksStats } from '../../../types/tanksStats';
|
import { NormalizedTankStats, TanksStats } from '../../../types/tanksStats';
|
||||||
import SessionPage from '../../embeds/session/page';
|
import SessionPage from '../../embeds/session/page';
|
||||||
|
import { CustomColumn } from './components/CustomColumn';
|
||||||
import * as styles from './page.css';
|
import * as styles from './page.css';
|
||||||
|
|
||||||
// 1 once per 5 seconds
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const [searchResults, setSearchResults] = useState<
|
const [searchResults, setSearchResults] = useState<
|
||||||
@@ -150,6 +154,7 @@ export default function Page() {
|
|||||||
<AlertDialog.Root>
|
<AlertDialog.Root>
|
||||||
<AlertDialog.Trigger>
|
<AlertDialog.Trigger>
|
||||||
<Button
|
<Button
|
||||||
|
variant="soft"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
if (!session.promptBeforeReset) {
|
if (!session.promptBeforeReset) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -206,6 +211,7 @@ export default function Page() {
|
|||||||
|
|
||||||
<Tooltip content="Copy to clipboard">
|
<Tooltip content="Copy to clipboard">
|
||||||
<Button
|
<Button
|
||||||
|
variant="soft"
|
||||||
className={styles.toolbarButton}
|
className={styles.toolbarButton}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
@@ -216,6 +222,41 @@ export default function Page() {
|
|||||||
<CopyIcon width="16" height="16" /> Embed
|
<CopyIcon width="16" height="16" /> Embed
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</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.CheckboxItem
|
||||||
|
checked={session.showCareer}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
useSession.setState({ showCareer: checked })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Show career
|
||||||
|
</DropdownMenu.CheckboxItem>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Root>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Percentile } from '../../../../../constants/percentiles';
|
import { Percentile } from '../../../../../constants/percentiles';
|
||||||
import hslToHex from '../../../../../core/node/hslToHex';
|
|
||||||
import { PERCENTILE_COLORS } from '../../../../PercentileIndicator';
|
import { PERCENTILE_COLORS } from '../../../../PercentileIndicator';
|
||||||
|
|
||||||
export enum GlowSide {
|
export enum GlowSide {
|
||||||
@@ -14,8 +13,8 @@ export interface GlowProps {
|
|||||||
|
|
||||||
export function Glow({ percentile, side }: GlowProps) {
|
export function Glow({ percentile, side }: GlowProps) {
|
||||||
const color = PERCENTILE_COLORS[percentile];
|
const color = PERCENTILE_COLORS[percentile];
|
||||||
const color1 = hslToHex(color);
|
const color1 = color;
|
||||||
const color2 = `${color1}00`;
|
const color2 = `${color}00`;
|
||||||
|
|
||||||
const background = `linear-gradient(${
|
const background = `linear-gradient(${
|
||||||
side === GlowSide.Top ? 0 : 180
|
side === GlowSide.Top ? 0 : 180
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import { theme } from '../../../stitches.config';
|
|||||||
import { TREE_TYPE_ICONS, TreeTypeEnum } from '../../Tanks';
|
import { TREE_TYPE_ICONS, TreeTypeEnum } from '../../Tanks';
|
||||||
import { RowStat } from './RowStat';
|
import { RowStat } from './RowStat';
|
||||||
|
|
||||||
|
export interface RowStatItem {
|
||||||
|
title: string;
|
||||||
|
current?: number | string;
|
||||||
|
career?: number | string;
|
||||||
|
delta?: number;
|
||||||
|
percentile?: Percentile;
|
||||||
|
}
|
||||||
|
|
||||||
interface RowProps {
|
interface RowProps {
|
||||||
type?: 'tank' | 'summary';
|
type?: 'tank' | 'summary';
|
||||||
title: string;
|
title: string;
|
||||||
@@ -11,13 +19,7 @@ interface RowProps {
|
|||||||
treeType?: TreeTypeEnum;
|
treeType?: TreeTypeEnum;
|
||||||
tankType?: TankType;
|
tankType?: TankType;
|
||||||
|
|
||||||
stats: {
|
stats: (RowStatItem | undefined)[];
|
||||||
title: string;
|
|
||||||
current?: number | string;
|
|
||||||
career?: number | string;
|
|
||||||
delta?: number;
|
|
||||||
percentile?: Percentile;
|
|
||||||
}[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Row(props: RowProps) {
|
export function Row(props: RowProps) {
|
||||||
@@ -77,16 +79,19 @@ export function Row(props: RowProps) {
|
|||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.stats.map((row, index) => (
|
{props.stats.map(
|
||||||
<RowStat
|
(row, index) =>
|
||||||
minimized={props.minimized}
|
row && (
|
||||||
key={index}
|
<RowStat
|
||||||
name={`${row.title} • ${row.career ?? '--'}`}
|
minimized={props.minimized}
|
||||||
value={row.current ?? '--'}
|
key={index}
|
||||||
delta={row.delta}
|
name={`${row.title} • ${row.career ?? '--'}`}
|
||||||
percentile={row.percentile}
|
value={row.current ?? '--'}
|
||||||
/>
|
delta={row.delta}
|
||||||
))}
|
percentile={row.percentile}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export function RowStat({
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{value}
|
{value}
|
||||||
@@ -62,7 +63,13 @@ export function RowStat({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!minimized && (
|
{!minimized && (
|
||||||
<span style={{ color: theme.colors.textLowContrast, fontSize: 12 }}>
|
<span
|
||||||
|
style={{
|
||||||
|
color: theme.colors.textLowContrast,
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -19,7 +19,15 @@ export default function Navbar() {
|
|||||||
{/* <Button>
|
{/* <Button>
|
||||||
<HamburgerMenuIcon />
|
<HamburgerMenuIcon />
|
||||||
</Button> */}
|
</Button> */}
|
||||||
<Link href="/" style={{ color: 'inherit' }}>
|
<Link
|
||||||
|
href="/"
|
||||||
|
style={{
|
||||||
|
color: 'inherit',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<BlitzkriegWormWide />
|
<BlitzkriegWormWide />
|
||||||
</Link>
|
</Link>
|
||||||
{/* <Button>
|
{/* <Button>
|
||||||
|
|||||||
@@ -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)}`;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { produce } from 'immer';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { devtools, persist } from 'zustand/middleware';
|
import { devtools, persist } from 'zustand/middleware';
|
||||||
|
import { CustomColumnDisplay } from '../app/tools/session/components/CustomColumn';
|
||||||
import { Region } from '../constants/regions';
|
import { Region } from '../constants/regions';
|
||||||
import { NormalizedTankStats } from '../types/tanksStats';
|
import { NormalizedTankStats } from '../types/tanksStats';
|
||||||
|
|
||||||
@@ -18,6 +20,12 @@ type Session = (
|
|||||||
}
|
}
|
||||||
) & {
|
) & {
|
||||||
promptBeforeReset: boolean;
|
promptBeforeReset: boolean;
|
||||||
|
customColumns: {
|
||||||
|
display: CustomColumnDisplay;
|
||||||
|
showDelta: boolean;
|
||||||
|
}[] & { length: 4 };
|
||||||
|
showTotal: boolean;
|
||||||
|
showCareer: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSession = create<Session>()(
|
export const useSession = create<Session>()(
|
||||||
@@ -26,6 +34,15 @@ export const useSession = create<Session>()(
|
|||||||
(set) => ({
|
(set) => ({
|
||||||
isTracking: false,
|
isTracking: false,
|
||||||
promptBeforeReset: true,
|
promptBeforeReset: true,
|
||||||
|
showTotal: true,
|
||||||
|
showCareer: true,
|
||||||
|
|
||||||
|
customColumns: [
|
||||||
|
{ display: 'battles', showDelta: false },
|
||||||
|
{ display: 'winrate', showDelta: true },
|
||||||
|
{ display: 'wn8', showDelta: false },
|
||||||
|
{ display: 'damage', showDelta: true },
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'session',
|
name: 'session',
|
||||||
@@ -33,3 +50,7 @@ export const useSession = create<Session>()(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export default function mutateSession(recipe: (draft: Session) => void) {
|
||||||
|
useSession.setState(produce(recipe));
|
||||||
|
}
|
||||||
@@ -3078,6 +3078,11 @@ ignore@^5.1.8, ignore@^5.2.4:
|
|||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
||||||
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
|
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:
|
indent-string@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
||||||
|
|||||||
Reference in new issue
Block a user