Compare commits

...
134 Commits
Author SHA1 Message Date
tresabhi 1a90e9312d feat: logs 2025-05-18 09:55:02 -05:00
tresabhi 2c7082fe04 feat: fast assets 2025-05-18 09:54:12 -05:00
tresabhi 518a83ee73 fix: bruh what 2025-05-16 23:03:56 -05:00
tresabhi 25c40bcea2 feat: logs 2025-05-16 22:33:31 -05:00
tresabhi f88ac8daf7 feat: more heuristic formats 2025-05-16 22:23:58 -05:00
tresabhi c4be6b78b6 feat: heuristics 2025-05-16 22:23:34 -05:00
tresabhi 2cfc65a990 feat: migrate tanks 2025-05-16 22:11:01 -05:00
tresabhi a8eb751048 feat: migrate tanks search page 2025-05-16 19:20:29 -05:00
tresabhi f030fd53b1 feat: slugs 2025-05-16 19:14:56 -05:00
tresabhi da506083c9 feat: migrate tier list 2025-05-16 18:15:27 -05:00
tresabhi 8bdc4d9b1b fix: sessio 2025-05-16 18:13:57 -05:00
tresabhi e0718d9ebb fix: migrate performance 2025-05-16 18:12:19 -05:00
tresabhi dbf1718d39 fix: migrate guess 2025-05-16 18:09:33 -05:00
tresabhi 84dd13c0ac feat: rename to path 2025-05-16 18:07:15 -05:00
tresabhi fdecef5f9c feat: migrate gallery 2025-05-16 18:06:04 -05:00
tresabhi 198e800958 feat: remove rybbit 2025-05-16 18:02:08 -05:00
tresabhi 29bb8ada06 feat: reduced navbar shadow 2025-05-16 18:01:10 -05:00
tresabhi 662f116afe fix: move guess components to src/components 2025-05-16 18:00:08 -05:00
tresabhi 02f0c078a5 fix: legacy redirect embed pages 2025-05-16 00:07:43 -05:00
tresabhi 4dd1113b6d feat: migrate embed and fix embed not rendering because of no context 2025-05-16 00:03:47 -05:00
tresabhi e656b83719 feat: migrate compare 2025-05-15 23:54:07 -05:00
tresabhi b90b355f8f feat: migrate urls 2025-05-15 23:44:20 -05:00
tresabhi 165a9cd719 feat: logs 2025-05-15 23:31:26 -05:00
tresabhi 5b8da36fb8 feat: effective and persistent angle 2025-05-15 23:21:01 -05:00
tresabhi bbe682fa73 feat: bigger icons 2025-05-15 23:03:10 -05:00
tresabhi af08df1526 feat: ricochet visualizer shell switcher 2025-05-15 23:02:15 -05:00
tresabhi 47a60adac7 feat: disable reload visualizer 2025-05-15 22:46:17 -05:00
tresabhi 10f3bc0026 fix: compare labels 2025-05-15 22:45:09 -05:00
tresabhi e7da1d582f fix: use pitch angle of transition of 15deg 2025-05-15 22:40:09 -05:00
tresabhi df70816f7e fix: use svh 2025-05-15 22:37:58 -05:00
tresabhi 091364d7f3 Merge branch 'main' into dev 2025-05-15 22:35:42 -05:00
tresabhi 753c06eee8 feat: logs 2025-05-07 20:54:24 -05:00
tresabhi 17713243b5 feat: final stuff 2025-05-07 20:51:52 -05:00
tresabhi 4e4f070368 feat: shinier 2025-05-07 19:43:40 -05:00
tresabhi 4064f163ec feat: remove plug 2025-05-07 19:42:49 -05:00
tresabhi b911f64847 feat: out animation 2025-05-07 19:41:47 -05:00
tresabhi b86b6e09d0 feat: font 2025-05-07 19:32:18 -05:00
tresabhi d2e7a477bb feat: fullscreen 2025-05-07 19:16:10 -05:00
tresabhi f9c182e68c feat: hide guess 2025-05-06 21:30:35 -05:00
tresabhi 7c497ab681 feat: more tintgs 2025-05-06 13:59:41 -05:00
tresabhi c840086c11 feat: tints 2025-05-06 13:56:56 -05:00
tresabhi 8649d09a94 feat: shadow 2025-05-06 13:39:07 -05:00
tresabhi c764564a96 feat: outline title 2025-05-06 13:29:40 -05:00
tresabhi ad8cbb96b7 feat: nerd time 2025-05-05 22:54:39 -05:00
tresabhi b409e26ca5 feat: reveal titles 2025-05-05 21:41:45 -05:00
tresabhi fbbfa5307c feat: skip 2025-05-05 21:22:05 -05:00
tresabhi 4165533838 feat: simplier hiding 2025-05-05 21:05:46 -05:00
tresabhi fb47ab3e64 feat: new auto rotate animation 2025-05-04 22:11:41 -05:00
tresabhi 57971453ad feat: stats 2025-05-04 21:45:38 -05:00
tresabhi c8cb490b48 feat: full logic 2025-05-04 21:39:09 -05:00
tresabhi 232f24181f feat: some guessing mechanics 2025-05-04 21:02:50 -05:00
tresabhi 46441d74d8 feat: some baswork 2025-05-04 13:08:28 -05:00
tresabhi a2274a6f0c idk man 2025-05-04 11:24:33 -05:00
tresabhi 0eb2827e7b Merge branch 'main' into dev 2025-05-02 21:28:54 -05:00
TrèsAbhi 41cb3f8511 Update Party3.astro 2025-05-02 09:32:22 -05:00
tresabhi 9405dccee9 feat: even more robust 2025-04-29 16:36:59 -05:00
tresabhi 83b0c6abc3 feat: persistent shells 2025-04-29 16:23:22 -05:00
tresabhi bf1621001b feat: more glow 2025-04-29 00:11:27 -05:00
tresabhi 8a5a4bf696 feat: glow and shells 2025-04-29 00:10:42 -05:00
tresabhi a99cce07dc feat: background 2025-04-28 17:43:29 -05:00
tresabhi d8c26ba24a feat: begennings of reload visuals 2025-04-28 17:21:42 -05:00
imgbot[bot]andImgBotApp f0b393c48f [ImgBot] Optimize images (#152)
*Total -- 1,028.23kb -> 900.50kb (12.42%)

/packages/website/public/assets/images/3d/grid.png -- 610.88kb -> 490.28kb (19.74%)
/packages/website/public/assets/images/icons/aim-caret.png -- 1.93kb -> 1.84kb (4.6%)
/packages/website/public/assets/images/icons/xp-multiplier.png -- 4.54kb -> 4.43kb (2.28%)
/packages/website/public/assets/images/third-party/wotb-news.png -- 404.33kb -> 397.41kb (1.71%)
/packages/website/public/assets/images/icons/custom-shell.png -- 6.55kb -> 6.54kb (0.18%)

Signed-off-by: ImgBotApp <[email protected]>
Co-authored-by: ImgBotApp <[email protected]>
2025-04-26 13:32:03 -05:00
TrèsAbhi d0d205bb93 New Crowdin updates (#150)
* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Russian)

* New translations en.json (Ukrainian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Chinese Simplified)

* New translations en.json (Russian)

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Russian)

* New translations en.json (French)
2025-04-26 13:31:52 -05:00
tresabhi aeaf52d3ac feat: darker view range 2025-04-24 22:06:28 -05:00
tresabhi e05358952a feat: stat search 2025-04-24 22:04:33 -05:00
tresabhi c1000e1608 feat: more responsive 2025-04-24 20:54:24 -05:00
tresabhi 46c86c5352 feat: more decimal places for dimensios 2025-04-24 20:37:31 -05:00
tresabhi 507b503560 feat: remove units and make ranks softer 2025-04-24 20:35:58 -05:00
tresabhi 37ca981d33 feat: more responsive 2025-04-24 20:24:12 -05:00
tresabhi efe6d2b1a5 feat: better navbar background 2025-04-24 20:07:17 -05:00
tresabhi 9f79f9c1e8 fix: no draggable images 2025-04-22 22:01:36 -05:00
tresabhi 38b4c62777 feat: new maps 2025-04-22 21:56:39 -05:00
tresabhi df1bc56a4c feat: isolate stat sections 2025-04-22 21:22:26 -05:00
tresabhi c3d2b063bc feat: new ricochet visualizer 2025-04-22 21:10:22 -05:00
tresabhi 1b94748668 feat: start of ricochet visual 2 2025-04-22 16:35:49 -05:00
tresabhi f4f804727f feat: slicker design 2025-04-22 14:42:04 -05:00
tresabhi 3729072594 feat: prettier 2025-04-22 14:36:31 -05:00
tresabhi c174f32e15 feat: view range visualizer 2025-04-22 14:35:34 -05:00
tresabhi 2443ecde2f feat: turretless support 2025-04-22 13:10:01 -05:00
tresabhi 9c027b3dde feat: puncture hole 2025-04-22 01:06:38 -05:00
tresabhi 22d794ac65 feat: animations 2025-04-22 00:50:14 -05:00
tresabhi 35073c184e test 2025-04-21 18:39:28 -05:00
tresabhi 4741406e4a fix: include prisma only in bot 2025-04-21 18:30:35 -05:00
tresabhi 24107433c6 fix: use built prisma 2025-04-21 18:24:19 -05:00
tresabhi 05c6fcfb34 fix: inline 2025-04-21 18:21:12 -05:00
tresabhi be7a16d399 feat: lower contrast 2025-04-21 18:19:05 -05:00
tresabhi c7f781dbb2 feat: ricochet visualizer 2025-04-21 18:09:57 -05:00
tresabhi a66c6ebb27 feat: classic styled components 2025-04-21 17:01:32 -05:00
tresabhi 83aeca2e5e Merge branch 'main' into dev 2025-04-21 16:39:49 -05:00
tresabhi 89a3db05d8 Merge branch 'main' of https://github.com/tresabhi/blitzkit 2025-04-21 16:39:08 -05:00
tresabhi 6050145b16 fix: home page hot tanks 2025-04-21 16:39:06 -05:00
tresabhi 77c78741b3 fix: remove unused type 2025-04-21 16:37:29 -05:00
TrèsAbhi f35670d6a8 Update Party3.astro 2025-04-21 14:36:24 -05:00
tresabhi 3392a57480 feat: rotation visualize 2025-04-20 23:00:08 -05:00
tresabhi ed64ee8228 feat: hull traverse visualizer 2025-04-20 22:51:53 -05:00
tresabhi 4f47d6ce85 Merge branch 'main' into dev 2025-04-20 21:35:06 -05:00
tresabhi 92d56ac086 fix: onPointerDown 2025-04-20 21:34:57 -05:00
tresabhi 671b8b60b9 feat: update prisma with custom output 2025-04-20 21:31:15 -05:00
tresabhi ba5a5245d9 feat: update prisma 2025-04-20 21:27:00 -05:00
tresabhi eee262add7 fix: submodules 2025-04-20 21:18:54 -05:00
tresabhi e5eaf3fd75 fix: submodules 2025-04-20 21:18:02 -05:00
tresabhi 5fdc9044ca fix: hero interactibility 2025-04-20 21:16:50 -05:00
tresabhi 0924a77776 fix: mas width for meta section 2025-04-20 21:13:40 -05:00
tresabhi 1a46e176f5 fix: tankopedia title 2025-04-20 21:11:16 -05:00
tresabhi efb8df9769 feat: logs 2025-04-20 21:00:57 -05:00
tresabhi 04e09b84ff feat: skeleton and proper file extensions 2025-04-20 20:49:54 -05:00
tresabhi b52c107af9 feat: parrelel api 2025-04-19 00:48:29 -05:00
tresabhi c5e6f5e2eb feat: glossary cron 2025-04-19 00:47:28 -05:00
tresabhi 365c68aa4d feat: move popular tanks to cron job 2025-04-18 23:57:04 -05:00
tresabhi ff9ec55da6 feat: increase title gap 2025-04-17 13:06:40 -05:00
tresabhi 2a2994ae69 feat: more responsive 2025-04-17 13:00:53 -05:00
tresabhi e3c6497d29 feat: better meta 2025-04-17 00:23:01 -05:00
tresabhi bdc013b4c8 fix: cleaner clear button 2025-04-16 23:34:30 -05:00
tresabhi e20d65d218 feat: darker 2025-04-16 23:22:12 -05:00
tresabhi 8b726c12d9 fix: invalidate frams on reveal 2025-04-16 23:20:44 -05:00
tresabhi da704e6181 feat: transition skeleton 2025-04-16 23:18:24 -05:00
tresabhi 64d346ccb6 feat: in-hero controls 2025-04-15 16:54:36 -05:00
tresabhi c090ad6492 feat: disturb on tab change and no title select 2025-04-15 16:37:10 -05:00
tresabhi 8b6610151e fix: keys 2025-04-15 16:29:14 -05:00
tresabhi 545b655d4e feat: slicker loading 2025-04-15 00:35:12 -05:00
tresabhi 0e6aa734cb feat: advanced jsxTree 2025-04-15 00:11:49 -05:00
tresabhi 2bef96475c feat: quick controls 2025-04-14 23:56:29 -05:00
tresabhi 45938c875f feat: skeleton search 2025-04-14 23:28:35 -05:00
tresabhi 9d916a588d feat: cooler header 2025-04-14 19:26:41 -05:00
tresabhi f0295079cc feat: quiz input angles animaiton 2025-04-14 18:56:02 -05:00
tresabhi 6d8387fa9d feat: foggy transition 2025-04-14 18:52:49 -05:00
tresabhi 7a18de6650 fix: don't reset camera on tab change 2025-04-14 17:30:51 -05:00
tresabhi cc6c4faa2d feat: migration 2025-04-06 22:57:35 -05:00
TrèsAbhi 4253cd3e1b fix: allow 2025-04-06 22:04:01 -05:00
TrèsAbhi 75cf827082 Update website.yml 2025-04-06 19:59:27 -05:00
TrèsAbhi 73c8807f14 no, no, no, NOW all hail pyogenics i messed up the last time 2025-04-06 16:03:04 -05:00
TrèsAbhi 9fac6dd40d all hail pyogenics 2025-04-06 15:53:23 -05:00
TrèsAbhi c06c338536 fix: add env and remove compressors 2025-04-06 10:48:08 -05:00
TrèsAbhi 9258623931 Update astro.config.mjs 2025-04-06 00:12:01 -05:00
239 changed files with 8360 additions and 4158 deletions

No files matched your search

+1
View File
@@ -50,6 +50,7 @@ env:
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }} PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }} PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
PUBLIC_DEBUG_MISSING_I18N: false PUBLIC_DEBUG_MISSING_I18N: false
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
jobs: jobs:
download_client: download_client:
+1 -1
View File
@@ -21,7 +21,7 @@ env:
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: true ALLOW_ROBOTS: "true"
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
+2 -1
View File
@@ -27,4 +27,5 @@ target
/.vercel /.vercel
/.astro /.astro
.vercel .vercel
/src /src
/packages/bot/src/prisma
+6
View File
@@ -1,3 +1,9 @@
[submodule "submodules/blitzkit-closed"] [submodule "submodules/blitzkit-closed"]
path = submodules/blitzkit-closed path = submodules/blitzkit-closed
url = https://github.com/tresabhi/blitzkit-closed url = https://github.com/tresabhi/blitzkit-closed
[submodule "submodules/CUE4Parse"]
path = submodules/CUE4Parse
url = https://github.com/FabianFG/CUE4Parse
[submodule "submodules/UAssetAPI"]
path = submodules/UAssetAPI
url = https://github.com/atenfyr/UAssetAPI
+5
View File
@@ -36,6 +36,7 @@
"cacheable", "cacheable",
"Callouts", "Callouts",
"Camo", "Camo",
"Carro",
"CCPAOPTOUT", "CCPAOPTOUT",
"CDON", "CDON",
"classname", "classname",
@@ -43,6 +44,7 @@
"closedkrieg", "closedkrieg",
"cockroackdb", "cockroackdb",
"Coef", "Coef",
"Combattimento",
"cooldown", "cooldown",
"corelations", "corelations",
"corsproxy", "corsproxy",
@@ -54,6 +56,7 @@
"dbid", "dbid",
"Ddpf", "Ddpf",
"Ddsd", "Ddsd",
"deadrail",
"debugflags", "debugflags",
"deburr", "deburr",
"decalmask", "decalmask",
@@ -131,6 +134,7 @@
"outdir", "outdir",
"outfile", "outfile",
"ownedtanks", "ownedtanks",
"padder",
"partytown", "partytown",
"PIXELFORMAT", "PIXELFORMAT",
"playerachievements", "playerachievements",
@@ -184,6 +188,7 @@
"TEXCOORD", "TEXCOORD",
"tresabhi", "tresabhi",
"tungstentip", "tungstentip",
"Turretless",
"TYPELESS", "TYPELESS",
"ubjson", "ubjson",
"Unicum", "Unicum",
BIN
View File
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
# BlitzKit 1.27.10
This update changes the URLs across the whole website, removing the "tools" prefix and simplifying other aspects too.
## Changes
- Removed "full" tank names
- For example, tanks like the "Carro 45t" had full names like "Carro Combattimento 45t" which remained pretty much unused, so they have been removed
- Reduced the shadow below the navigation bar
- Removed the "tools" prefix from the URLs; here are the paths:
- `/tools/tankopedia` -> `/tanks`
- `/tools/tankopedia/12345` -> `/tanks/tank-name`
- `/tools/compare` -> `/compare`
- `/tools/embed` -> `/embed`
- `/tools/embed/embed-name` -> `/embed/embed-name`
- `/tools/gallery` -> `/gallery`
- `/tools/performance` -> `/performance`
- `/tools/session` -> `/session`
- `/tools/tier-list` -> `/tier-list`
## Technical Features
- The asset mangler now uses http headers and even more heuristics to minimize re-creating unchanged assets
+7
View File
@@ -0,0 +1,7 @@
# BlitzKit 1.27.11
This update affects both the bot and the website.
## Technical Changes
- Added a proper CDN domain to avoid error 429s and any slowdowns
+25
View File
@@ -0,0 +1,25 @@
# BlitzKit 1.27.7
![](https://i.imgur.com/qFepOaT.png)
This update only affects the website.
## Features
- Added a new foggy-wireframe transition between tabs to smooth out the jarring jumps
- Added animations pop-in and out animations on a few elements
- The tank name title now shrinks in size as soon as a disturbance is caused
- The shell selector, antagonist switcher, and thicknesses range now ease into frame
- The Tankopedia search page now loads as a skeleton by default before populating with tanks, allowing for the recently viewed tanks to load without cumulative shift
- Something similar was done to the Gallery tool
- Gave the meta section of the website some zest with nation-based backdrops
- The compare tabs in the Tankopedia are now centered
## Fixes
- The camera no longer snaps to the initial position when switching between tabs
## Technical Changes
- Backported some technologies from BlitzKit Reforged; you will not notice any differences in usage
- The popular tanks and gallery indexings have been moved to the asynchronous cron job
+12
View File
@@ -0,0 +1,12 @@
# BlitzKit 1.27.8
![](https://i.imgur.com/WO5MS94.png)
## Features
- The website now uses Inter as the font
- Added a cool new non-disturbed state for the Tankopedia because why not
## Changes
- Removed the Discord plug and language changer from the homepage
+27
View File
@@ -0,0 +1,27 @@
# BlitzKit 1.27.9
BlitzKit's great, but there's far too many numbers. This update adds a few visualizers to help new players understand how things relate numerically.
![](https://i.imgur.com/OV5ak4T.png)
## Features
- Added the turret and hull traverse speed visualizer
- Added the shell ricochet and normalization angles visualizer
- Added the view-range visualizer
- Added a search bar to the Tankopedia characteristics
- Bumped the decimal places for vehicle dimensions and volume
- Made tank ranks softer (the bars beneath the tank characteristics)
- Gave the navigation bar better distinction from the rest of the page
- Increased the number of characteristics rows to 3 on select devices
- Switched to a more contrasting classic style of components across the website (it's subtle, but beautiful)
## Changes
- Removed units
## Fixes
- Clarified a few confusing row labels in the Compare tool
- Made the Tankopedia hero section dynamically smaller on mobile phones to allow scrolling
- Tank ranks no longer reach 0 or go above the number of vehicles in the comparison
+9 -7
View File
@@ -1,7 +1,7 @@
{ {
"name": "blitzkit", "name": "blitzkit",
"private": true, "private": true,
"version": "1.27.6", "version": "1.27.11",
"description": "🎉 Tools for everything World of Tanks Blitz", "description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module", "type": "module",
"types": "./types.d.ts", "types": "./types.d.ts",
@@ -40,15 +40,17 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"sharp": "^0.33.5", "googleapis": "^144.0.0",
"lodash-es": "^4.17.21" "lodash-es": "^4.17.21",
"quicklime": "^1.2.0",
"sharp": "^0.33.5"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^22.5.4", "@types/node": "^22.14.1",
"prettier": "^3.3.3", "prettier": "^3.5.3",
"prisma": "^6.0.0", "prisma": "^6.6.0",
"typescript": "^5.5.4" "typescript": "^5.8.3"
} }
} }
+2 -2
View File
@@ -107,8 +107,8 @@ export const auctionCommand = new Promise<CommandRegistry>((resolve) => {
if (!tank) return ''; if (!tank) return '';
const name = `[${tank.name}](<https://blitzkit.app/tools/tankopedia/${ const name = `[${tank.name}](<https://blitzkit.app/tanks/${
tank.id tank.slug
}>)`; }>)`;
const next = const next =
data.next_price === null data.next_price === null
+2 -5
View File
@@ -1,11 +1,8 @@
import { import { getAccountInfo, getClanAccountInfo } from '@blitzkit/core';
getAccountInfo,
getClanAccountInfo,
linkBlitzAndDiscord,
} from '@blitzkit/core';
import { teal } from '@radix-ui/colors'; import { teal } from '@radix-ui/colors';
import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow'; import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow';
import { CommandWrapper } from '../components/CommandWrapper'; import { CommandWrapper } from '../components/CommandWrapper';
import { linkBlitzAndDiscord } from '../core/db/linkBlitzAndDiscord';
import { addUsernameChoices } from '../core/discord/addUsernameChoices'; import { addUsernameChoices } from '../core/discord/addUsernameChoices';
import { autocompleteUsername } from '../core/discord/autocompleteUsername'; import { autocompleteUsername } from '../core/discord/autocompleteUsername';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
+1 -1
View File
@@ -78,7 +78,7 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
) )
.map( .map(
(tank) => (tank) =>
`[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name))}](<https://blitzkit.app/tools/tankopedia/${tank.id}>)`, `[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name))}](<https://blitzkit.app/tanks/${tank.slug}>)`,
); );
const lines = [title, ...tankList]; const lines = [title, ...tankList];
@@ -1,5 +1,6 @@
import { idToRegion, usersDatabase } from '@blitzkit/core'; import { idToRegion } from '@blitzkit/core';
import { flagUserActivity } from '@blitzkit/core/src/blitzkit/flagUserActivity'; import { flagUserActivity } from '../db/flagUserActivity';
import { usersDatabase } from '../db/users';
export async function getBlitzFromDiscord(discordId: bigint) { export async function getBlitzFromDiscord(discordId: bigint) {
const unique = await usersDatabase.discordUser.findUnique({ const unique = await usersDatabase.discordUser.findUnique({
+1 -1
View File
@@ -1,4 +1,4 @@
import { usersDatabase } from '@blitzkit/core'; import { usersDatabase } from '../db/users';
import { client } from '../discord/client'; import { client } from '../discord/client';
const EXIT_EVENTS = [ const EXIT_EVENTS = [
@@ -1,4 +1,4 @@
import { usersDatabase } from '@blitzkit/core'; import { usersDatabase } from './users';
export async function flagUserActivity(blitzId: number) { export async function flagUserActivity(blitzId: number) {
await usersDatabase.user.upsert({ await usersDatabase.user.upsert({
@@ -1,5 +1,5 @@
import { usersDatabase } from '@blitzkit/core';
import { flagUserActivity } from './flagUserActivity'; import { flagUserActivity } from './flagUserActivity';
import { usersDatabase } from './users';
export async function linkBlitzAndDiscord(discordId: bigint, blitzId: number) { export async function linkBlitzAndDiscord(discordId: bigint, blitzId: number) {
await usersDatabase.discordUser.upsert({ await usersDatabase.discordUser.upsert({
@@ -1,3 +1,3 @@
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '../../prisma';
export const usersDatabase = new PrismaClient(); export const usersDatabase = new PrismaClient();
+1 -1
View File
@@ -6,7 +6,7 @@
"dependencies": { "dependencies": {
"@octokit/plugin-throttling": "^8.2.0", "@octokit/plugin-throttling": "^8.2.0",
"@octokit/rest": "^20.1.1", "@octokit/rest": "^20.1.1",
"@prisma/client": "^5.19.1", "@prisma/client": "^6.6.0",
"adm-zip": "^0.5.16", "adm-zip": "^0.5.16",
"crc": "^4.3.2", "crc": "^4.3.2",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
@@ -1,4 +1,5 @@
import { assertSecret, type BlitzGlossary } from '@blitzkit/core'; import { assertSecret } from '../blitzkit';
import { BlitzGlossary } from '../types';
export async function fetchGlossary(locale: string) { export async function fetchGlossary(locale: string) {
const response = await fetch( const response = await fetch(
+1
View File
@@ -4,6 +4,7 @@ export * from './classes';
export * from './constants'; export * from './constants';
export * from './equipmentPrice'; export * from './equipmentPrice';
export * from './fetchBlitz'; export * from './fetchBlitz';
export * from './fetchGlossary';
export * from './generateStats'; export * from './generateStats';
export * from './getAccountAchievements'; export * from './getAccountAchievements';
export * from './getAccountInfo'; export * from './getAccountInfo';
+8 -3
View File
@@ -1,7 +1,12 @@
import { assertSecret } from './assertSecret'; import { assertSecret } from './assertSecret';
const repo = assertSecret(import.meta.env.PUBLIC_ASSET_REPO);
const branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
const fastUser = assertSecret(import.meta.env.PUBLIC_ASSET_FAST_USER);
const fastRepo = assertSecret(import.meta.env.PUBLIC_ASSET_FAST_REPO);
export function asset(path: string) { export function asset(path: string) {
return `https://raw.githubusercontent.com/${assertSecret( return branch === 'main'
import.meta.env.PUBLIC_ASSET_REPO, ? `https://${fastUser}.github.io/${fastRepo}/${path}`
)}/${assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH)}/${path}`; : `https://raw.githubusercontent.com/${repo}/${branch}/${path}`;
} }
+7
View File
@@ -0,0 +1,7 @@
import { fetchPB } from '../protobuf';
import { Gallery } from '../protos';
import { asset } from './asset';
export function fetchGallery() {
return fetchPB(asset('definitions/gallery.pb'), Gallery);
}
+2 -2
View File
@@ -13,7 +13,6 @@ export * from './discoveredIdDefinitions';
export * from './emblemURL'; export * from './emblemURL';
export * from './equipmentDefinitions'; export * from './equipmentDefinitions';
export * from './eventManager'; export * from './eventManager';
// export * from './flagUserActivity';
export * from './gameDefinitions'; export * from './gameDefinitions';
export * from './getArchivedLatestSeasonNumber'; export * from './getArchivedLatestSeasonNumber';
// export * from './getArchivedRatingInfo'; // export * from './getArchivedRatingInfo';
@@ -21,7 +20,6 @@ export * from './getArchivedRatingLeaderboard';
// export * from './getArchivedRatingMidnightLeaderboard'; // export * from './getArchivedRatingMidnightLeaderboard';
export * from './getTimeDaysAgo'; export * from './getTimeDaysAgo';
export * from './isExplosive'; export * from './isExplosive';
export * from './linkBlitzAndDiscord';
export * from './mapDefinitions'; export * from './mapDefinitions';
export * from './modelDefinitions'; export * from './modelDefinitions';
export * from './normalizeBoundingBox'; export * from './normalizeBoundingBox';
@@ -44,6 +42,8 @@ export * from './unwrapBlitzkitResponse';
export * from './votes'; export * from './votes';
// export * from './withCORSProxy'; // export * from './withCORSProxy';
export * from './createDefaultProvisions'; export * from './createDefaultProvisions';
export * from './gallery';
export * from './metaSortTank'; export * from './metaSortTank';
export * from './popularTanks';
export * from './romanize'; export * from './romanize';
export * from './youtubers'; export * from './youtubers';
@@ -0,0 +1,7 @@
import { fetchPB } from '../protobuf';
import { PopularTanks } from '../protos';
import { asset } from './asset';
export function fetchPopularTanks() {
return fetchPB(asset('definitions/popular-tanks.pb'), PopularTanks);
}
+8 -4
View File
@@ -15,18 +15,22 @@ export async function fetchTankNames() {
tankDefinitionsArray.map(async (tank) => { tankDefinitionsArray.map(async (tank) => {
const searchableNameDeburr: I18nString = { locales: {} }; const searchableNameDeburr: I18nString = { locales: {} };
Object.entries(tank.name_full ?? tank.name).forEach(([key, value]) => { Object.entries(tank.name).forEach(([key, value]) => {
searchableNameDeburr.locales[key] = deburr(value); searchableNameDeburr.locales[key] = deburr(value);
}); });
return { return {
id: tank.id, id: tank.id,
name: tank.name, name: tank.name,
nameFull: tank.name_full, searchableName: tank.name,
searchableName: tank.name_full ?? tank.name,
searchableNameDeburr, searchableNameDeburr,
camouflages: tank.camouflages camouflages: tank.camouflages
?.map((id) => SUPPORTED_LOCALES.map((locale) => camouflageDefinitions.camouflages[id]?.name.locales[locale])) ?.map((id) =>
SUPPORTED_LOCALES.map(
(locale) =>
camouflageDefinitions.camouflages[id]?.name.locales[locale],
),
)
.flat() .flat()
.filter(Boolean) .filter(Boolean)
.map(deburr) .map(deburr)
-1
View File
@@ -1 +0,0 @@
export * from './users';
-1
View File
@@ -1,6 +1,5 @@
export * from './blitz'; export * from './blitz';
export * from './blitzkit'; export * from './blitzkit';
export * from './databases';
export * from './imgur'; export * from './imgur';
export * from './math'; export * from './math';
export * from './patreon'; export * from './patreon';
+15
View File
@@ -0,0 +1,15 @@
syntax = "proto2";
package blitzkit;
import "packages/core/src/protos/i18n.proto";
message Gallery {
repeated Avatar avatars = 1;
}
message Avatar {
required string id = 1;
required I18nString name = 2;
required string extension = 3;
}
+237
View File
@@ -0,0 +1,237 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.2
// protoc v5.28.2
// source: packages/core/src/protos/gallery.proto
/* eslint-disable */
import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire';
import { I18nString, createBaseI18nString } from './i18n';
export const protobufPackage = 'blitzkit';
export interface Gallery {
avatars: Avatar[];
}
export interface Avatar {
id: string;
name: I18nString;
extension: string;
}
function createBaseGallery(): Gallery {
return { avatars: [] };
}
export const Gallery: MessageFns<Gallery> = {
encode(
message: Gallery,
writer: BinaryWriter = new BinaryWriter(),
): BinaryWriter {
for (const v of message.avatars) {
Avatar.encode(v!, writer.uint32(10).fork()).join();
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): Gallery {
const reader =
input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseGallery();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break;
}
message.avatars.push(Avatar.decode(reader, reader.uint32()));
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): Gallery {
return {
avatars: globalThis.Array.isArray(object?.avatars)
? object.avatars.map((e: any) => Avatar.fromJSON(e))
: [],
};
},
toJSON(message: Gallery): unknown {
const obj: any = {};
if (message.avatars?.length) {
obj.avatars = message.avatars.map((e) => Avatar.toJSON(e));
}
return obj;
},
create<I extends Exact<DeepPartial<Gallery>, I>>(base?: I): Gallery {
return Gallery.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<Gallery>, I>>(object: I): Gallery {
const message = createBaseGallery();
message.avatars = object.avatars?.map((e) => Avatar.fromPartial(e)) || [];
return message;
},
};
function createBaseAvatar(): Avatar {
return { id: '', name: createBaseI18nString(), extension: '' };
}
export const Avatar: MessageFns<Avatar> = {
encode(
message: Avatar,
writer: BinaryWriter = new BinaryWriter(),
): BinaryWriter {
if (message.id !== '') {
writer.uint32(10).string(message.id);
}
if (message.name !== undefined) {
I18nString.encode(message.name, writer.uint32(18).fork()).join();
}
if (message.extension !== '') {
writer.uint32(26).string(message.extension);
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): Avatar {
const reader =
input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseAvatar();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break;
}
message.id = reader.string();
continue;
}
case 2: {
if (tag !== 18) {
break;
}
message.name = I18nString.decode(reader, reader.uint32());
continue;
}
case 3: {
if (tag !== 26) {
break;
}
message.extension = reader.string();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): Avatar {
return {
id: globalThis.String(assertSet('Avatar.id', object.id)),
name: I18nString.fromJSON(assertSet('Avatar.name', object.name)),
extension: globalThis.String(
assertSet('Avatar.extension', object.extension),
),
};
},
toJSON(message: Avatar): unknown {
const obj: any = {};
if (message.id !== '') {
obj.id = message.id;
}
if (message.name !== undefined) {
obj.name = I18nString.toJSON(message.name);
}
if (message.extension !== '') {
obj.extension = message.extension;
}
return obj;
},
create<I extends Exact<DeepPartial<Avatar>, I>>(base?: I): Avatar {
return Avatar.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<Avatar>, I>>(object: I): Avatar {
const message = createBaseAvatar();
message.id = object.id ?? '';
message.name =
object.name !== undefined && object.name !== null
? I18nString.fromPartial(object.name)
: createBaseI18nString();
message.extension = object.extension ?? '';
return message;
},
};
type Builtin =
| Date
| Function
| Uint8Array
| string
| number
| boolean
| undefined;
export type DeepPartial<T> = T extends Builtin
? T
: T extends globalThis.Array<infer U>
? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T extends { $case: string; value: unknown }
? { $case: T['$case']; value?: DeepPartial<T['value']> }
: T extends {}
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;
type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin
? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
[K in Exclude<keyof I, KeysOfUnion<P>>]: never;
};
function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
function assertSet<T>(field: string, value: T | undefined): T {
if (!isSet(value)) {
throw new TypeError(`Required field ${field} is not set`);
}
return value as T;
}
export interface MessageFns<T> {
encode(message: T, writer?: BinaryWriter): BinaryWriter;
decode(input: BinaryReader | Uint8Array, length?: number): T;
fromJSON(object: any): T;
toJSON(message: T): unknown;
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T;
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T;
}
+2
View File
@@ -3,10 +3,12 @@ export * from './average_definitions.ts';
export * from './camouflage_definitions.ts'; export * from './camouflage_definitions.ts';
export * from './consumable_definitions.ts'; export * from './consumable_definitions.ts';
export * from './equipment_definitions.ts'; export * from './equipment_definitions.ts';
export * from './gallery.ts';
export * from './game_definitions.ts'; export * from './game_definitions.ts';
export * from './i18n.ts'; export * from './i18n.ts';
export * from './map_definitions.ts'; export * from './map_definitions.ts';
export * from './model_definitions.ts'; export * from './model_definitions.ts';
export * from './popular_tanks.ts';
export * from './provision_definitions.ts'; export * from './provision_definitions.ts';
export * from './rating_leaderboard.ts'; export * from './rating_leaderboard.ts';
export * from './reviews.ts'; export * from './reviews.ts';
@@ -0,0 +1,12 @@
syntax = "proto2";
package blitzkit;
message PopularTanks {
repeated PopularTank tanks = 1;
}
message PopularTank {
required uint32 id = 1;
required uint32 views = 2; // views in the last 7 days
}
+190
View File
@@ -0,0 +1,190 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.2
// protoc v5.28.2
// source: packages/core/src/protos/popular_tanks.proto
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
export const protobufPackage = "blitzkit";
export interface PopularTanks {
tanks: PopularTank[];
}
export interface PopularTank {
id: number;
/** views in the last 7 days */
views: number;
}
function createBasePopularTanks(): PopularTanks {
return { tanks: [] };
}
export const PopularTanks: MessageFns<PopularTanks> = {
encode(message: PopularTanks, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
for (const v of message.tanks) {
PopularTank.encode(v!, writer.uint32(10).fork()).join();
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): PopularTanks {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBasePopularTanks();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break;
}
message.tanks.push(PopularTank.decode(reader, reader.uint32()));
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): PopularTanks {
return {
tanks: globalThis.Array.isArray(object?.tanks) ? object.tanks.map((e: any) => PopularTank.fromJSON(e)) : [],
};
},
toJSON(message: PopularTanks): unknown {
const obj: any = {};
if (message.tanks?.length) {
obj.tanks = message.tanks.map((e) => PopularTank.toJSON(e));
}
return obj;
},
create<I extends Exact<DeepPartial<PopularTanks>, I>>(base?: I): PopularTanks {
return PopularTanks.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<PopularTanks>, I>>(object: I): PopularTanks {
const message = createBasePopularTanks();
message.tanks = object.tanks?.map((e) => PopularTank.fromPartial(e)) || [];
return message;
},
};
function createBasePopularTank(): PopularTank {
return { id: 0, views: 0 };
}
export const PopularTank: MessageFns<PopularTank> = {
encode(message: PopularTank, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.id !== 0) {
writer.uint32(8).uint32(message.id);
}
if (message.views !== 0) {
writer.uint32(16).uint32(message.views);
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): PopularTank {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBasePopularTank();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break;
}
message.id = reader.uint32();
continue;
}
case 2: {
if (tag !== 16) {
break;
}
message.views = reader.uint32();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): PopularTank {
return {
id: globalThis.Number(assertSet("PopularTank.id", object.id)),
views: globalThis.Number(assertSet("PopularTank.views", object.views)),
};
},
toJSON(message: PopularTank): unknown {
const obj: any = {};
if (message.id !== 0) {
obj.id = Math.round(message.id);
}
if (message.views !== 0) {
obj.views = Math.round(message.views);
}
return obj;
},
create<I extends Exact<DeepPartial<PopularTank>, I>>(base?: I): PopularTank {
return PopularTank.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<PopularTank>, I>>(object: I): PopularTank {
const message = createBasePopularTank();
message.id = object.id ?? 0;
message.views = object.views ?? 0;
return message;
},
};
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
export type DeepPartial<T> = T extends Builtin ? T
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
: T extends { $case: string; value: unknown } ? { $case: T["$case"]; value?: DeepPartial<T["value"]> }
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;
type KeysOfUnion<T> = T extends T ? keyof T : never;
export type Exact<P, I extends P> = P extends Builtin ? P
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };
function isSet(value: any): boolean {
return value !== null && value !== undefined;
}
function assertSet<T>(field: string, value: T | undefined): T {
if (!isSet(value)) {
throw new TypeError(`Required field ${field} is not set`);
}
return value as T;
}
export interface MessageFns<T> {
encode(message: T, writer?: BinaryWriter): BinaryWriter;
decode(input: BinaryReader | Uint8Array, length?: number): T;
fromJSON(object: any): T;
toJSON(message: T): unknown;
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T;
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T;
}
+10 -10
View File
@@ -10,16 +10,16 @@ message TankDefinitions {
message TankDefinition { message TankDefinition {
required uint32 id = 1; required uint32 id = 1;
map<uint32, uint32> roles = 2; required string slug = 2;
required bool fixed_camouflage = 4; map<uint32, uint32> roles = 3;
repeated int32 camouflages = 5; required bool fixed_camouflage = 5;
repeated int32 ancestors = 6; repeated int32 camouflages = 6;
repeated int32 successors = 7; repeated int32 ancestors = 7;
repeated Crew crew = 8; repeated int32 successors = 8;
required uint32 health = 9; repeated Crew crew = 9;
required string nation = 10; required uint32 health = 10;
required I18nString name = 11; required string nation = 11;
optional I18nString name_full = 12; required I18nString name = 12;
required TankType type = 13; required TankType type = 13;
required uint32 max_consumables = 14; required uint32 max_consumables = 14;
required uint32 max_provisions = 15; required uint32 max_provisions = 15;
File diff suppressed because it is too large. Load diff
+14
View File
@@ -0,0 +1,14 @@
{
"default": "en",
"supported": [
{ "locale": "en" },
{ "locale": "es" },
{ "locale": "fr" },
{ "locale": "ja" },
{ "locale": "pl" },
{ "locale": "pt" },
{ "locale": "ru" },
{ "locale": "uk" },
{ "locale": "zh", "blitz": "zh_Hans_SG" }
]
}
+60 -10
View File
@@ -11,7 +11,9 @@
"mm": "%s1mm", "mm": "%s1mm",
"deg": "%s1°", "deg": "%s1°",
"hp": "%s1hp", "hp": "%s1hp",
"x": "x%s1" "x": "x%s1",
"percentage": "%s1%",
"deg_s": "%s1°/s"
}, },
"gun_types": { "gun_types": {
@@ -739,6 +741,11 @@
}, },
"website": { "website": {
"redirect": {
"title": "301: moved permanently",
"body": "This page has moved. Redirecting..."
},
"settings": { "settings": {
"dev_mode": "Development mode", "dev_mode": "Development mode",
"language": "Language" "language": "Language"
@@ -756,7 +763,7 @@
}, },
"tank_search": { "tank_search": {
"clear_filters": "Clear", "clear_filters": "Clear filters",
"search_bar_hint": "Search tanks...", "search_bar_hint": "Search tanks...",
"recent": "Recently viewed", "recent": "Recently viewed",
"select_all": "Select all", "select_all": "Select all",
@@ -853,7 +860,24 @@
}, },
"tools": { "tools": {
"guess": {
"name": "Guess the tank",
"button": "Guess away",
"description": "Silhouette mini-game",
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
"search": {
"placeholder": "Type to guess...",
"skip": "Skip",
"guess": "Guess",
"next": "Next",
"no_results": "No results"
},
"stats": "%s1 / %s2 guessed correctly"
},
"tankopedia": { "tankopedia": {
"search": "Search a stat...",
"name": "Tankopedia", "name": "Tankopedia",
"button": "Find tanks", "button": "Find tanks",
"description": "Blitz tank encyclopedia", "description": "Blitz tank encyclopedia",
@@ -861,6 +885,32 @@
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)", "tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.", "tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
"visualizers": {
"rotator": {
"hull": "Hull",
"turret": "Turret",
"hard": "Hard",
"medium": "Medium",
"soft": "Soft",
"effective": "Effective"
},
"ricochet": {
"angle": "Angle",
"normalization": "Normalization",
"effective_angle": "Effective angle",
"armor": "Armor",
"no_ricochet": "Explosive shells don't ricochet"
},
"view_range": {
"camo": "Camo",
"percentage": "%s1%",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
}
},
"sandbox": { "sandbox": {
"poses": { "poses": {
"hull_down": "Hull down", "hull_down": "Hull down",
@@ -1010,7 +1060,7 @@
"characteristics": { "characteristics": {
"title": "Characteristics", "title": "Characteristics",
"compare": { "compare": {
"label": "Compare against", "label": "Compare",
"class": "Tier %s1 %s2", "class": "Tier %s1 %s2",
"tier": "All of tier %s1", "tier": "All of tier %s1",
"all": "All tanks" "all": "All tanks"
@@ -1047,7 +1097,7 @@
"explosionRadius": "Explosion radius", "explosionRadius": "Explosion radius",
"shellVelocity": "Shell velocity", "shellVelocity": "Shell velocity",
"aimTime": "Aim time", "aimTime": "Aim time",
"dispersion": "Still", "dispersion": "Dispersion still",
"dispersionMoving": "Moving", "dispersionMoving": "Moving",
"dispersionHullTraversing": "Hull traversing", "dispersionHullTraversing": "Hull traversing",
"dispersionTurretTraversing": "Turret traversing", "dispersionTurretTraversing": "Turret traversing",
@@ -1061,28 +1111,28 @@
"gunRearElevation": "Rear elevation", "gunRearElevation": "Rear elevation",
"azimuthLeft": "Azimuth left", "azimuthLeft": "Azimuth left",
"azimuthRight": "Azimuth right", "azimuthRight": "Azimuth right",
"speedForwards": "Forwards", "speedForwards": "Speed forwards",
"speedBackwards": "Backwards", "speedBackwards": "Backwards",
"hardTerrainCoefficient": "Hard", "hardTerrainCoefficient": "Terrain coefficient hard",
"hardTerrainCoefficientRaw": "Hard", "hardTerrainCoefficientRaw": "Raw terrain coefficient hard",
"mediumTerrainCoefficient": "Medium", "mediumTerrainCoefficient": "Medium",
"mediumTerrainCoefficientRaw": "Medium", "mediumTerrainCoefficientRaw": "Medium",
"softTerrainCoefficient": "Soft", "softTerrainCoefficient": "Soft",
"softTerrainCoefficientRaw": "Soft", "softTerrainCoefficientRaw": "Soft",
"enginePower": "Engine power", "enginePower": "Engine power",
"powerToWeightRatioHardTerrain": "Hard", "powerToWeightRatioHardTerrain": "Power to weight ratio hard",
"powerToWeightRatioMediumTerrain": "Medium", "powerToWeightRatioMediumTerrain": "Medium",
"powerToWeightRatioSoftTerrain": "Soft", "powerToWeightRatioSoftTerrain": "Soft",
"weight": "Weight", "weight": "Weight",
"turretTraverseSpeed": "Turret traverse speed", "turretTraverseSpeed": "Turret traverse speed",
"gunTraverseSpeed": "Gun traverse speed", "gunTraverseSpeed": "Gun traverse speed",
"hullTraverseHardTerrain": "Hard", "hullTraverseHardTerrain": "Hull traverse hard",
"hullTraverseMediumTerrain": "Medium", "hullTraverseMediumTerrain": "Medium",
"hullTraverseSoftTerrain": "Soft", "hullTraverseSoftTerrain": "Soft",
"health": "Health", "health": "Health",
"fireChance": "Fire chance", "fireChance": "Fire chance",
"viewRange": "View range", "viewRange": "View range",
"camouflageStill": "Still", "camouflageStill": "Camouflage still",
"camouflageMoving": "Moving", "camouflageMoving": "Moving",
"camouflageShootingStill": "Still", "camouflageShootingStill": "Still",
"camouflageShootingMoving": "Moving", "camouflageShootingMoving": "Moving",
+13 -1
View File
@@ -665,6 +665,7 @@
"copied": "Copiado!" "copied": "Copiado!"
}, },
"tank_search": { "tank_search": {
"clear_filters": "Clear filters",
"search_bar_hint": "Buscar tanques...", "search_bar_hint": "Buscar tanques...",
"recent": "Visto recientemente", "recent": "Visto recientemente",
"select_all": "Seleccionar todo", "select_all": "Seleccionar todo",
@@ -731,6 +732,11 @@
} }
}, },
"home": { "home": {
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"action": "Discord"
},
"hero": { "hero": {
"credit": "Por %s1 ❤️" "credit": "Por %s1 ❤️"
}, },
@@ -744,6 +750,12 @@
"help_translate": "¡Por favor ayuda a traducir %s1 a %s2!", "help_translate": "¡Por favor ayuda a traducir %s1 a %s2!",
"seo_description": "Herramientas para todo lo que es World of Tanks Blitz" "seo_description": "Herramientas para todo lo que es World of Tanks Blitz"
}, },
"docs": {
"patches": {
"name": "BlitzKit Changelogs",
"seo_description": "Changelogs for BlitzKit Discord bot and website"
}
},
"tools": { "tools": {
"tankopedia": { "tankopedia": {
"name": "Tankopedia", "name": "Tankopedia",
@@ -888,7 +900,7 @@
"characteristics": { "characteristics": {
"title": "Características", "title": "Características",
"compare": { "compare": {
"label": "Comparar con", "label": "Comparar",
"class": "Nivel %s1 - %s2", "class": "Nivel %s1 - %s2",
"tier": "Todos de nivel %s1", "tier": "Todos de nivel %s1",
"all": "Todos los tanques" "all": "Todos los tanques"
+33 -21
View File
@@ -129,9 +129,9 @@
"asia": "Asie-Pacifique" "asia": "Asie-Pacifique"
}, },
"short": { "short": {
"com": "NA", "com": "Amérique du Nord",
"eu": "Europe", "eu": "Europe",
"asia": "APAC" "asia": "Asie-Pacifique"
} }
}, },
"leagues": { "leagues": {
@@ -350,7 +350,7 @@
} }
}, },
"body": { "body": {
"subtitle": "Research", "subtitle": "Recherche",
"total": "Total", "total": "Total",
"research": "Recherche: %s1", "research": "Recherche: %s1",
"upgrades": "Améliorations : %s1", "upgrades": "Améliorations : %s1",
@@ -377,7 +377,7 @@
"body": "# Commandes\n\nBlitzKit offre les commandes suivantes :\n\n" "body": "# Commandes\n\nBlitzKit offre les commandes suivantes :\n\n"
}, },
"embeds": { "embeds": {
"name": "embeds", "name": "intègre",
"description": "How embeds work" "description": "How embeds work"
}, },
"introduction": { "introduction": {
@@ -457,7 +457,7 @@
"body": { "body": {
"cards": { "cards": {
"games": { "games": {
"title": "Games", "title": "Parties",
"winrate": "Taux de victoires", "winrate": "Taux de victoires",
"battles": "Batailles", "battles": "Batailles",
"wins_losses": "Victoires/Défaites" "wins_losses": "Victoires/Défaites"
@@ -505,45 +505,45 @@
"description": "Search clans", "description": "Search clans",
"options": { "options": {
"limit": { "limit": {
"name": "limit", "name": "limite",
"description": "The max number of clan results (default: %s1)" "description": "The max number of clan results (default: %s1)"
} }
}, },
"body": { "body": {
"title": "Clan search for %s1 in region %s2", "title": "Clan search for %s1 in region %s2",
"no_results": "No clans found" "no_results": "Pas de clan trouvé"
} }
}, },
"search_players": { "search_players": {
"name": "search-players", "name": "recherche de joueur",
"description": "Search players", "description": "Recherche des joueurs",
"options": { "options": {
"limit": { "limit": {
"name": "limit", "name": "limite",
"description": "The max number of player results (default: %s1)" "description": "Le nombre maximum de résultats de joueur (par défaut: %s1)"
} }
}, },
"body": { "body": {
"title": "Player search for %s1 in region %s2", "title": "Recherche de %s1 dans la région %s2",
"no_results": "No players found" "no_results": "Pas de joueur de trouvé"
} }
}, },
"rating_leaderboard": { "rating_leaderboard": {
"name": "rating-leaderboard", "name": "Classement de côté",
"description": "Rating battles statistics", "description": "Statistiques des batailles de cotés",
"subcommands": { "subcommands": {
"neighbors": { "neighbors": {
"name": "neighbors", "name": "neighbors",
"description": "Neighboring players on the leaderboard" "description": "Neighboring players on the leaderboard"
}, },
"league": { "league": {
"name": "league", "name": "ligue",
"description": "Top players of a league" "description": "Top players of a league"
} }
}, },
"options": { "options": {
"limit": { "limit": {
"name": "limit", "name": "limite",
"description": "How many neighbors to display (default: %s1)" "description": "How many neighbors to display (default: %s1)"
}, },
"season": { "season": {
@@ -554,7 +554,7 @@
} }
}, },
"league": { "league": {
"name": "league", "name": "ligue",
"description": "The league to display" "description": "The league to display"
} }
}, },
@@ -572,7 +572,7 @@
"description": "Today's played tanks and statistics" "description": "Today's played tanks and statistics"
}, },
"stats": { "stats": {
"name": "stats", "name": "statistiques",
"description": "Quick statistics for a period" "description": "Quick statistics for a period"
}, },
"owned_tanks": { "owned_tanks": {
@@ -665,6 +665,7 @@
"copied": "Copied!" "copied": "Copied!"
}, },
"tank_search": { "tank_search": {
"clear_filters": "Clear filters",
"search_bar_hint": "Search tanks...", "search_bar_hint": "Search tanks...",
"recent": "Recently viewed", "recent": "Recently viewed",
"select_all": "Select all", "select_all": "Select all",
@@ -731,6 +732,11 @@
} }
}, },
"home": { "home": {
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"action": "Discord"
},
"hero": { "hero": {
"credit": "By %s1 ❤️" "credit": "By %s1 ❤️"
}, },
@@ -744,6 +750,12 @@
"help_translate": "Please help translate %s1 to %s2!", "help_translate": "Please help translate %s1 to %s2!",
"seo_description": "Tools for everything World of Tanks Blitz" "seo_description": "Tools for everything World of Tanks Blitz"
}, },
"docs": {
"patches": {
"name": "BlitzKit Changelogs",
"seo_description": "Changelogs for BlitzKit Discord bot and website"
}
},
"tools": { "tools": {
"tankopedia": { "tankopedia": {
"name": "Tankopedia", "name": "Tankopedia",
@@ -888,7 +900,7 @@
"characteristics": { "characteristics": {
"title": "Characteristics", "title": "Characteristics",
"compare": { "compare": {
"label": "Compare against", "label": "Compare",
"class": "Tier %s1 %s2", "class": "Tier %s1 %s2",
"tier": "All of tier %s1", "tier": "All of tier %s1",
"all": "All tanks" "all": "All tanks"
@@ -1092,7 +1104,7 @@
"lookup": "Look up a player to get started", "lookup": "Look up a player to get started",
"hint": "Search players...", "hint": "Search players...",
"searching": "Searching...", "searching": "Searching...",
"no_results": "No players found" "no_results": "Pas de joueur de trouvé"
}, },
"controls": { "controls": {
"title": "Tracking %s1", "title": "Tracking %s1",
+13 -1
View File
@@ -665,6 +665,7 @@
"copied": "コピーしました!" "copied": "コピーしました!"
}, },
"tank_search": { "tank_search": {
"clear_filters": "Clear filters",
"search_bar_hint": "車輌名を入力…", "search_bar_hint": "車輌名を入力…",
"recent": "最近の閲覧", "recent": "最近の閲覧",
"select_all": "全て選択", "select_all": "全て選択",
@@ -731,6 +732,11 @@
} }
}, },
"home": { "home": {
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"action": "Discord"
},
"hero": { "hero": {
"credit": "By %s1 ❤️" "credit": "By %s1 ❤️"
}, },
@@ -744,6 +750,12 @@
"help_translate": "%s1の %s2への翻訳にご協力ください!", "help_translate": "%s1の %s2への翻訳にご協力ください!",
"seo_description": "Tools for everything World of Tanks Blitz" "seo_description": "Tools for everything World of Tanks Blitz"
}, },
"docs": {
"patches": {
"name": "BlitzKit Changelogs",
"seo_description": "Changelogs for BlitzKit Discord bot and website"
}
},
"tools": { "tools": {
"tankopedia": { "tankopedia": {
"name": "Tankopedia", "name": "Tankopedia",
@@ -888,7 +900,7 @@
"characteristics": { "characteristics": {
"title": "性能値", "title": "性能値",
"compare": { "compare": {
"label": "比較対象", "label": "性能比較",
"class": "Tier %s1 %s2", "class": "Tier %s1 %s2",
"tier": "Tier %s1 全車輌", "tier": "Tier %s1 全車輌",
"all": "全車輌" "all": "全車輌"
+13 -1
View File
@@ -665,6 +665,7 @@
"copied": "Copied!" "copied": "Copied!"
}, },
"tank_search": { "tank_search": {
"clear_filters": "Clear filters",
"search_bar_hint": "Search tanks...", "search_bar_hint": "Search tanks...",
"recent": "Recently viewed", "recent": "Recently viewed",
"select_all": "Select all", "select_all": "Select all",
@@ -731,6 +732,11 @@
} }
}, },
"home": { "home": {
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"action": "Discord"
},
"hero": { "hero": {
"credit": "By %s1 ❤️" "credit": "By %s1 ❤️"
}, },
@@ -744,6 +750,12 @@
"help_translate": "Please help translate %s1 to %s2!", "help_translate": "Please help translate %s1 to %s2!",
"seo_description": "Tools for everything World of Tanks Blitz" "seo_description": "Tools for everything World of Tanks Blitz"
}, },
"docs": {
"patches": {
"name": "BlitzKit Changelogs",
"seo_description": "Changelogs for BlitzKit Discord bot and website"
}
},
"tools": { "tools": {
"tankopedia": { "tankopedia": {
"name": "Tankopedia", "name": "Tankopedia",
@@ -888,7 +900,7 @@
"characteristics": { "characteristics": {
"title": "Characteristics", "title": "Characteristics",
"compare": { "compare": {
"label": "Compare against", "label": "Compare",
"class": "Tier %s1 %s2", "class": "Tier %s1 %s2",
"tier": "All of tier %s1", "tier": "All of tier %s1",
"all": "All tanks" "all": "All tanks"
+13 -1
View File
@@ -665,6 +665,7 @@
"copied": "Copiado!" "copied": "Copiado!"
}, },
"tank_search": { "tank_search": {
"clear_filters": "Clear filters",
"search_bar_hint": "Procurar tanques...", "search_bar_hint": "Procurar tanques...",
"recent": "Visto recentemente", "recent": "Visto recentemente",
"select_all": "Selecionar todos", "select_all": "Selecionar todos",
@@ -731,6 +732,11 @@
} }
}, },
"home": { "home": {
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"action": "Discord"
},
"hero": { "hero": {
"credit": "Por %s1 ❤️" "credit": "Por %s1 ❤️"
}, },
@@ -744,6 +750,12 @@
"help_translate": "Por favor, ajude a traduzir %s1 para %s2!", "help_translate": "Por favor, ajude a traduzir %s1 para %s2!",
"seo_description": "Ferramentas para tudo World of Tanks Blitz" "seo_description": "Ferramentas para tudo World of Tanks Blitz"
}, },
"docs": {
"patches": {
"name": "BlitzKit Changelogs",
"seo_description": "Changelogs for BlitzKit Discord bot and website"
}
},
"tools": { "tools": {
"tankopedia": { "tankopedia": {
"name": "Tanquepedia", "name": "Tanquepedia",
@@ -888,7 +900,7 @@
"characteristics": { "characteristics": {
"title": "Características", "title": "Características",
"compare": { "compare": {
"label": "Comparar com", "label": "Comparar",
"class": "Nível %s1 %s2", "class": "Nível %s1 %s2",
"tier": "Tudo de nível %s1", "tier": "Tudo de nível %s1",
"all": "Todos os tanques" "all": "Todos os tanques"
+15 -3
View File
@@ -36,7 +36,7 @@
"local": "dev", "local": "dev",
"dev": "бета", "dev": "бета",
"opentest": "открытый тест", "opentest": "открытый тест",
"preview": "предварительный просмотр" "preview": "превью"
}, },
"locales": { "locales": {
"en": "Английский", "en": "Английский",
@@ -665,6 +665,7 @@
"copied": "Скопировано!" "copied": "Скопировано!"
}, },
"tank_search": { "tank_search": {
"clear_filters": "Сбросить фильтры",
"search_bar_hint": "Поиск танков...", "search_bar_hint": "Поиск танков...",
"recent": "Недавние", "recent": "Недавние",
"select_all": "Выбрать всё", "select_all": "Выбрать всё",
@@ -731,6 +732,11 @@
} }
}, },
"home": { "home": {
"discord": {
"join_short": "Присоединяйтесь к крутым ребятам!",
"join_long": "Присоединяйся к супер-дупер крутым ребятам!",
"action": "Discord"
},
"hero": { "hero": {
"credit": "От %s1 ❤️" "credit": "От %s1 ❤️"
}, },
@@ -744,6 +750,12 @@
"help_translate": "Пожалуйста, помогите перевести %s1 на %s2!", "help_translate": "Пожалуйста, помогите перевести %s1 на %s2!",
"seo_description": "Инструменты для World of Tanks Blitz" "seo_description": "Инструменты для World of Tanks Blitz"
}, },
"docs": {
"patches": {
"name": "Списки изменений BlitzKit",
"seo_description": "Список изменений для бота Discord и сайта BlitzKit"
}
},
"tools": { "tools": {
"tankopedia": { "tankopedia": {
"name": "Танкопедия", "name": "Танкопедия",
@@ -888,7 +900,7 @@
"characteristics": { "characteristics": {
"title": "Характеристики", "title": "Характеристики",
"compare": { "compare": {
"label": "Сравнивать с", "label": "Сравнение",
"class": "%s2 %s1 уровня", "class": "%s2 %s1 уровня",
"tier": "Все %s1 уровня", "tier": "Все %s1 уровня",
"all": "Все танки" "all": "Все танки"
@@ -1131,7 +1143,7 @@
"description": "Встраиваемые элементы для стримов", "description": "Встраиваемые элементы для стримов",
"seo_description": "Создать вставки для стримов по World of Tanks Blitz с невероятной кастомизации", "seo_description": "Создать вставки для стримов по World of Tanks Blitz с невероятной кастомизации",
"coming_soon": "Скоро появятся новые встраиваемые элементы.", "coming_soon": "Скоро появятся новые встраиваемые элементы.",
"suggest": "Предложить идеи.", "suggest": "Предложить идеи",
"preview": { "preview": {
"title": "Предпросмотр", "title": "Предпросмотр",
"upload": "Загрузить фон для теста" "upload": "Загрузить фон для теста"
+23 -11
View File
@@ -9,8 +9,8 @@
"m": "%s1м", "m": "%s1м",
"mm": "%s1мм", "mm": "%s1мм",
"deg": "%s1°", "deg": "%s1°",
"hp": "%s1hp", "hp": "%s1хп",
"x": "x%s1" "x": "х%s1"
}, },
"gun_types": { "gun_types": {
"auto_loader": "Магазинна система заряджання", "auto_loader": "Магазинна система заряджання",
@@ -184,11 +184,11 @@
"common": { "common": {
"errors": { "errors": {
"clan_not_found": "# Не вдалося знайти клан\nНе вдалося знайти клан \"%s1\". Спробуйте вибрати клан з пошукового результату.", "clan_not_found": "# Не вдалося знайти клан\nНе вдалося знайти клан \"%s1\". Спробуйте вибрати клан з пошукового результату.",
"player_not_found": "# Could not find user\nI couldn't find user \"%s1\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.", "player_not_found": "# Не вдалося знайти користувача\nЯ не зміг знайти користувача \"%s1\". Спробуйте вибрати користувача з результату пошуку, ввести коректний нікнейм, або використати команду '/link'.",
"player_not_linked": "# Use `/link` to get started\nI don't know who you are but you can tell me today!", "player_not_linked": "# Використайте команду \"/link\" щоб почати\nЯ не знаю, хто ви, але ви можете сказати мені сьогодні!",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"%s1\".", "tank_name_not_found": "# Танк не знайдено\nНе вдалось знайти танк за ім'ям \"%s1\".",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"%s1\". Try typing the name of the tank instead of a number.", "tank_id_not_found": "# Танк не знайдено\nНе вдалось знайти танк за ID \"%s1\". Спробуйте ввести назву танка замість числа.",
"no_tank_stats": "# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.", "no_tank_stats": "# Немає статистики для танка\nЦей гравець не має статистики.\nМожливо, це не той гравець, якого ви шукаєте.",
"get_help": "Допомогти Discord серверу", "get_help": "Допомогти Discord серверу",
"uncaught_error": "# Упс! BlitzKit зіткнувся з помилкою!\nНам дуже шкода. Можете попросити допомоги на [офіційному сервері Discord](https://discord.gg/nDt7AjGJQH).", "uncaught_error": "# Упс! BlitzKit зіткнувся з помилкою!\nНам дуже шкода. Можете попросити допомоги на [офіційному сервері Discord](https://discord.gg/nDt7AjGJQH).",
"preview": "# Ця команда є в попередньому перегляді\n\nВи можете отримати доступ лише до цієї команди на [офіційному сервері Discord](https://discord.gg/nDt7AjGJQH). Команда в цілому буде доступна найближчим часом.", "preview": "# Ця команда є в попередньому перегляді\n\nВи можете отримати доступ лише до цієї команди на [офіційному сервері Discord](https://discord.gg/nDt7AjGJQH). Команда в цілому буде доступна найближчим часом.",
@@ -399,8 +399,8 @@
} }
}, },
"breakdown": { "breakdown": {
"name": "breakdown", "name": "аналіз",
"description": "A period's breakdown by tanks played", "description": "Аналіз періоду за зіграними танками",
"body": { "body": {
"total": "Загалом", "total": "Загалом",
"battles": "Бої", "battles": "Бої",
@@ -511,7 +511,7 @@
}, },
"body": { "body": {
"title": "Clan search for %s1 in region %s2", "title": "Clan search for %s1 in region %s2",
"no_results": "No clans found" "no_results": "Жодного клану не знайдено"
} }
}, },
"search_players": { "search_players": {
@@ -665,6 +665,7 @@
"copied": "Скопійовано!" "copied": "Скопійовано!"
}, },
"tank_search": { "tank_search": {
"clear_filters": "Clear filters",
"search_bar_hint": "Шукайте техніку...", "search_bar_hint": "Шукайте техніку...",
"recent": "Недавно переглянуті", "recent": "Недавно переглянуті",
"select_all": "Вибрати все", "select_all": "Вибрати все",
@@ -731,6 +732,11 @@
} }
}, },
"home": { "home": {
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"action": "Discord"
},
"hero": { "hero": {
"credit": "Зроблено %s1 ❤" "credit": "Зроблено %s1 ❤"
}, },
@@ -744,6 +750,12 @@
"help_translate": "Будь ласка, допоможіть перекласти %s1 на %s2!", "help_translate": "Будь ласка, допоможіть перекласти %s1 на %s2!",
"seo_description": "Інструменти для всього, пов'язаного з WoT Blitz" "seo_description": "Інструменти для всього, пов'язаного з WoT Blitz"
}, },
"docs": {
"patches": {
"name": "BlitzKit Changelogs",
"seo_description": "Changelogs for BlitzKit Discord bot and website"
}
},
"tools": { "tools": {
"tankopedia": { "tankopedia": {
"name": "Танкопедія", "name": "Танкопедія",
@@ -888,7 +900,7 @@
"characteristics": { "characteristics": {
"title": "Characteristics", "title": "Characteristics",
"compare": { "compare": {
"label": "Compare against", "label": "Порівняти",
"class": "Tier %s1 %s2", "class": "Tier %s1 %s2",
"tier": "All of tier %s1", "tier": "All of tier %s1",
"all": "Усі танки" "all": "Усі танки"
+13 -1
View File
@@ -665,6 +665,7 @@
"copied": "已複製!" "copied": "已複製!"
}, },
"tank_search": { "tank_search": {
"clear_filters": "Clear filters",
"search_bar_hint": "搜尋戰車....", "search_bar_hint": "搜尋戰車....",
"recent": "最近查看", "recent": "最近查看",
"select_all": "選擇全部", "select_all": "選擇全部",
@@ -731,6 +732,11 @@
} }
}, },
"home": { "home": {
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"action": "Discord"
},
"hero": { "hero": {
"credit": "由 %s1 製作❤️" "credit": "由 %s1 製作❤️"
}, },
@@ -744,6 +750,12 @@
"help_translate": "請幫忙將 %s1 翻譯成 %s2!", "help_translate": "請幫忙將 %s1 翻譯成 %s2!",
"seo_description": "戰車世界閃擊戰 全方位工具" "seo_description": "戰車世界閃擊戰 全方位工具"
}, },
"docs": {
"patches": {
"name": "BlitzKit Changelogs",
"seo_description": "Changelogs for BlitzKit Discord bot and website"
}
},
"tools": { "tools": {
"tankopedia": { "tankopedia": {
"name": "戰車百科", "name": "戰車百科",
@@ -888,7 +900,7 @@
"characteristics": { "characteristics": {
"title": "属性", "title": "属性",
"compare": { "compare": {
"label": "比较", "label": "比較",
"class": "%s1 %s2 级", "class": "%s1 %s2 级",
"tier": "所有级别 %s1", "tier": "所有级别 %s1",
"all": "所有坦克" "all": "所有坦克"
+114 -9
View File
@@ -37,6 +37,7 @@ import {
SUPPORTED_LOCALES, SUPPORTED_LOCALES,
} from '@blitzkit/i18n'; } from '@blitzkit/i18n';
import { readdir } from 'fs/promises'; import { readdir } from 'fs/promises';
import { deburr } from 'lodash-es';
import { parse as parsePath } from 'path'; import { parse as parsePath } from 'path';
import { Vector3Tuple } from 'three'; import { Vector3Tuple } from 'three';
import { parse as parseYaml } from 'yaml'; import { parse as parseYaml } from 'yaml';
@@ -48,6 +49,23 @@ import { DATA } from './constants';
import { Avatar } from './skillIcons'; import { Avatar } from './skillIcons';
import { TankParameters } from './tankIcons'; import { TankParameters } from './tankIcons';
const nonAlphanumericRegex = /[^a-z0-9]/g;
const multipleDashesRegex = /--+/g;
const trailingDashRegex = /-$/g;
const leadingDashRegex = /^-/g;
const nationSlugDiscriminators = {
china: 'cn',
european: 'eu',
france: 'fr',
germany: 'de',
japan: 'jp',
other: 'ot',
uk: 'gb',
usa: 'us',
ussr: 'ru',
};
function parseResearchCost(raw: number | string) { function parseResearchCost(raw: number | string) {
if (typeof raw === 'number') { if (typeof raw === 'number') {
return { return {
@@ -502,7 +520,7 @@ const blitzShellKindToBlitzkit: Record<ShellKind, ShellType> = {
HIGH_EXPLOSIVE: ShellType.HE, HIGH_EXPLOSIVE: ShellType.HE,
HOLLOW_CHARGE: ShellType.HEAT, HOLLOW_CHARGE: ShellType.HEAT,
}; };
export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU(R?)))$/; export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/;
const blitzModuleTypeToBlitzkit: Record<keyof BlitzModuleType, ModuleType> = { const blitzModuleTypeToBlitzkit: Record<keyof BlitzModuleType, ModuleType> = {
chassis: ModuleType.TRACKS, chassis: ModuleType.TRACKS,
engine: ModuleType.ENGINE, engine: ModuleType.ENGINE,
@@ -703,6 +721,93 @@ export async function definitions() {
}; };
}); });
const slugRequesters = new Map<string, { id: number; key: string }[]>();
const idToNation: Record<number, string> = {};
await Promise.all(
nations.map(async (nation) => {
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml`,
);
for (const tankKey in tankList.root) {
if (botPattern.test(tankKey)) continue;
const tank = tankList.root[tankKey];
const tankId = toUniqueId(nation, tank.id);
const name = (
(tank.shortUserString
? getString(tank.shortUserString)
: undefined) ?? getString(tank.userString)
).locales.en;
let slug = deburr(name).toLowerCase();
slug = slug.replaceAll(nonAlphanumericRegex, '-');
slug = slug.replaceAll(multipleDashesRegex, '-');
slug = slug.replaceAll(trailingDashRegex, '');
slug = slug.replaceAll(leadingDashRegex, '');
idToNation[tankId] = nation;
if (slugRequesters.has(slug)) {
slugRequesters.get(slug)!.push({ id: tankId, key: tankKey });
} else {
slugRequesters.set(slug, [{ id: tankId, key: tankKey }]);
}
}
}),
);
const slugs = new Map<number, string>();
slugRequesters.forEach((requesters, slug) => {
if (requesters.length === 1) {
slugs.set(requesters[0].id, slug);
return;
}
console.warn(
`Multiple tanks share slug ${slug}: ${requesters.map(({ key }) => key).join(', ')}`,
);
if (requesters.length !== 2) {
throw new Error('Unresolvable number of duplicates :(');
}
const nonCanonical = requesters.find(({ key }) => key.endsWith('TUR'));
if (nonCanonical === undefined) {
console.log('Using nations to discriminate');
// both are non-tutorial tanks, will have to discriminate using nation
requesters.forEach(({ id, key }) => {
const nation = idToNation[id];
const discriminator =
nationSlugDiscriminators[
nation as keyof typeof nationSlugDiscriminators
];
console.log(`Solution: ${key} -> ${slug}-${discriminator}`);
slugs.set(id, `${slug}-${discriminator}`);
});
} else {
console.log('Using tutorial bot suffix to discriminate');
const canonical = requesters.find(({ key }) => !key.endsWith('TUR'));
if (canonical === undefined) {
throw new Error(
'Two tutorial bots share the same slug? The world is truly broken.',
);
}
console.log(`Solution: ${canonical.key} -> ${slug}`);
slugs.set(canonical.id, slug);
console.log(`Solution: ${nonCanonical.key} -> ${slug}-tur`);
slugs.set(nonCanonical.id, `${slug}-tur`);
}
});
await Promise.all( await Promise.all(
nations.map(async (nation) => { nations.map(async (nation) => {
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>( const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
@@ -787,6 +892,13 @@ export async function definitions() {
const hullArmor: Armor = { spaced: [], thickness: {} }; const hullArmor: Armor = { spaced: [], thickness: {} };
const equipment = tankDefinition.root.optDevicePreset; const equipment = tankDefinition.root.optDevicePreset;
tankStringIdMap[`${nation}:${tankKey}`] = tankId; tankStringIdMap[`${nation}:${tankKey}`] = tankId;
const slug = slugs.get(tankId);
if (slug === undefined) {
throw new Error(
`Could not find slug for ${nation}/${tankKey} (${tankId})`,
);
}
if (tank.sellPrice) { if (tank.sellPrice) {
tankPrice = { tankPrice = {
@@ -903,7 +1015,7 @@ export async function definitions() {
(tank.shortUserString (tank.shortUserString
? getString(tank.shortUserString) ? getString(tank.shortUserString)
: undefined) ?? getString(tank.userString), : undefined) ?? getString(tank.userString),
name_full: getString(tank.userString), slug,
nation, nation,
type: tankTags.includes('collectible') type: tankTags.includes('collectible')
? TankType.COLLECTOR ? TankType.COLLECTOR
@@ -939,13 +1051,6 @@ export async function definitions() {
}); });
} }
if (
tankDefinitions.tanks[tankId].name ===
tankDefinitions.tanks[tankId].name_full
) {
delete tankDefinitions.tanks[tankId].name_full;
}
modelDefinitions.models[tankId] = { modelDefinitions.models[tankId] = {
armor: hullArmor, armor: hullArmor,
turret_origin: vector3TupleToBlitzkit(turretOrigin), turret_origin: vector3TupleToBlitzkit(turretOrigin),
@@ -0,0 +1,90 @@
import { Avatar, encodePBBuffer, fetchGlossary, Gallery } from '@blitzkit/core';
import locales from '@blitzkit/i18n/locales.json';
import { extname } from 'path';
import ProgressBar from 'progress';
import { commitAssets } from '../core/github/commitAssets';
import { FileChange } from '../core/github/commitMultipleFiles';
export async function glossary() {
console.log('Building glossary...');
const avatars: Record<string, { avatar: Avatar; url: string }> = {};
await Promise.all(
locales.supported.map(async (supported) => {
const glossary = await fetchGlossary(supported.blitz ?? supported.locale);
console.log(
`Found ${Object.keys(glossary).length} things for ${supported.locale}`,
);
for (const key in glossary) {
const glossaryEntry = glossary[key];
if (
!key.startsWith('avatar') ||
key.endsWith('_part') ||
glossaryEntry.image_url === null
) {
continue;
}
if (key in avatars) {
avatars[key].avatar.name.locales[supported.locale] =
glossaryEntry.title;
} else {
const extension = extname(glossaryEntry.image_url);
avatars[key] = {
url: glossaryEntry.image_url,
avatar: {
id: key,
name: { locales: { [supported.locale]: glossaryEntry.title } },
extension,
},
};
}
}
}),
);
const changes: FileChange[] = [];
const gallery: Gallery = { avatars: [] };
const totalCount = Object.keys(avatars).length;
const bar = new ProgressBar(
`Fetching avatars of ${totalCount} things :bar`,
totalCount,
);
await Promise.all(
Object.entries(avatars).map(async ([key, { url, avatar }]) => {
if (!(locales.default in avatars[key].avatar.name.locales)) {
console.warn(`Avatar ${key} has no ${locales.default} name`);
bar.tick();
return;
}
gallery.avatars.push(avatar);
const content = await fetch(url).then((response) =>
response.arrayBuffer(),
);
changes.push({
path: `gallery/avatars/${key}${avatar.extension}`,
content: Buffer.from(content),
});
bar.tick();
}),
);
changes.push({
path: 'definitions/gallery.pb',
content: encodePBBuffer(Gallery, gallery),
});
await commitAssets('glossary', changes);
}
@@ -18,7 +18,10 @@ import { definitions } from './definitions';
import { equipmentIcons } from './equipmentIcons'; import { equipmentIcons } from './equipmentIcons';
import { flags } from './flags'; import { flags } from './flags';
import { gameModeBanners } from './gameModeBanners'; import { gameModeBanners } from './gameModeBanners';
import { glossary } from './glossary';
import { migration } from './migration';
import { moduleIcons } from './moduleIcons'; import { moduleIcons } from './moduleIcons';
import { popularTanks } from './popularTanks';
import { shellIcons } from './shellIcons'; import { shellIcons } from './shellIcons';
import { skillIcons } from './skillIcons'; import { skillIcons } from './skillIcons';
import { tankArmors } from './tankArmors'; import { tankArmors } from './tankArmors';
@@ -28,6 +31,7 @@ import { tankModels } from './tankModels';
if (!targets && !allTargets) throw new Error('No target(s) specified'); if (!targets && !allTargets) throw new Error('No target(s) specified');
const methods = [ const methods = [
migration,
definitions, definitions,
tankModels, tankModels,
tankArmors, tankArmors,
@@ -43,6 +47,8 @@ const methods = [
boosterIcons, boosterIcons,
tankIcons, tankIcons,
blitzkitTankIcons, blitzkitTankIcons,
popularTanks,
glossary,
]; ];
for (const method of methods) { for (const method of methods) {
@@ -0,0 +1,38 @@
import { toUniqueId } from '@blitzkit/core';
import { readdir } from 'fs/promises';
import { readXMLDVPL } from '../core/blitz/readXMLDVPL';
import { commitAssets } from '../core/github/commitAssets';
import { DATA } from './constants';
import { VehicleDefinitionList } from './definitions';
export async function migration() {
console.log('Building migration map...');
const map: Record<string, number> = {};
const nations = await readdir(`${DATA}/XML/item_defs/vehicles`).then(
(nations) => nations.filter((nation) => nation !== 'common'),
);
await Promise.all(
nations.map(async (nation) => {
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml`,
);
for (const tankKey in tankList.root) {
const tank = tankList.root[tankKey];
const tankId = toUniqueId(nation, tank.id);
const davaKey = `${nation}:${tankKey}`;
map[davaKey] = tankId;
}
}),
);
await commitAssets('migration', [
{
path: 'definitions/migration.json',
content: Buffer.from(JSON.stringify(map)),
},
]);
}
@@ -0,0 +1,76 @@
import {
assertSecret,
encodePBBuffer,
fetchTankDefinitions,
PopularTanks,
} from '@blitzkit/core';
import { google } from 'googleapis';
import { commitAssets } from '../core/github/commitAssets';
export async function popularTanks() {
const tankDefinitions = await fetchTankDefinitions();
const auth = await google.auth.getClient({
keyFile: import.meta.env.GOOGLE_APPLICATION_CREDENTIALS,
scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
});
const analytics = google.analyticsdata({ version: 'v1beta', auth });
const report = await analytics.properties.runReport({
property: `properties/${assertSecret(import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID)}`,
requestBody: {
dateRanges: [{ startDate: '7daysAgo', endDate: 'today' }],
dimensions: [{ name: 'pagePath' }],
metrics: [{ name: 'screenPageViews' }],
orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
dimensionFilter: {
filter: {
fieldName: 'pagePath',
stringFilter: {
matchType: 'BEGINS_WITH',
value: '/tanks/',
},
},
},
},
});
if (!report.data.rows) {
throw new Error('No rows in report');
}
const tanks = report.data.rows
.filter(
(row) =>
row.dimensionValues &&
row.dimensionValues[0].value &&
row.dimensionValues[0].value !== '/tanks/' &&
row.metricValues &&
row.metricValues[0].value,
)
.map((row) => ({
id: Number(
row.dimensionValues![0].value!.match(
/\/tools\/tankopedia\/(\d+)\/?/,
)?.[1],
),
views: Number(row.metricValues![0].value!),
}))
.filter((row) => {
if (!(row.id in tankDefinitions.tanks)) {
console.warn('Skipping unknown popular tank', row.id);
return false;
}
return row.id in tankDefinitions.tanks;
})
.slice(0, 8)
.map(({ id, views }) => ({ id, views }));
const popularTanks: PopularTanks = { tanks };
await commitAssets('popular tanks', [
{
path: 'definitions/popular-tanks.pb',
content: encodePBBuffer(PopularTanks, popularTanks),
},
]);
}
@@ -166,28 +166,9 @@ export async function extractModel(data: string, path: string) {
break; break;
case 'TransformComponent': { case 'TransformComponent': {
const translation = new Vector3(); node.setTranslation(component['tc.localTranslation']);
const rotation = new Quaternion(); node.setRotation(component['tc.localRotation']);
const scale = new Vector3(); node.setScale(component['tc.localScale']);
new Matrix4()
.multiplyMatrices(
new Matrix4().compose(
new Vector3().fromArray(component['tc.worldTranslation']),
new Quaternion().fromArray(component['tc.worldRotation']),
new Vector3().fromArray(component['tc.worldScale']),
),
new Matrix4().compose(
new Vector3().fromArray(component['tc.localTranslation']),
new Quaternion().fromArray(component['tc.localRotation']),
new Vector3().fromArray(component['tc.localScale']),
),
)
.decompose(translation, rotation, scale);
node.setTranslation(translation.toArray());
node.setRotation(rotation.toArray() as Vector4Tuple);
node.setScale(scale.toArray());
break; break;
} }
@@ -1,4 +1,5 @@
import { assertSecret } from '@blitzkit/core'; import { assertSecret } from '@blitzkit/core';
import { chunk } from 'lodash-es';
import { commitMultipleFiles, FileChange } from './commitMultipleFiles'; import { commitMultipleFiles, FileChange } from './commitMultipleFiles';
export async function commitAssets(message: string, changes: FileChange[]) { export async function commitAssets(message: string, changes: FileChange[]) {
@@ -6,10 +7,19 @@ export async function commitAssets(message: string, changes: FileChange[]) {
if (changes.length === 0) return; if (changes.length === 0) return;
await commitMultipleFiles( const chunks = chunk(changes, 64);
assertSecret(import.meta.env.PUBLIC_ASSET_REPO), let chunkIndex = 0;
assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
`${message} - ${new Date().toDateString()}`, for (const chunk of chunks) {
changes, console.log(`Committing chunk ${chunkIndex + 1}/${chunks.length}`);
);
await commitMultipleFiles(
assertSecret(import.meta.env.PUBLIC_ASSET_REPO),
assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
`${message} - ${new Date().toDateString()} (${chunkIndex + 1}/${chunks.length})`,
chunk,
);
chunkIndex++;
}
} }
@@ -7,6 +7,8 @@ export interface FileChange {
content: Buffer; content: Buffer;
} }
const heuristicFormats = ['glb', 'webp', 'jpg', 'png'];
export async function commitMultipleFiles( export async function commitMultipleFiles(
repoRaw: string, repoRaw: string,
branch: string, branch: string,
@@ -21,29 +23,69 @@ export async function commitMultipleFiles(
const response = await fetch( const response = await fetch(
`https://raw.githubusercontent.com/${blobPath}`, `https://raw.githubusercontent.com/${blobPath}`,
); );
let isNew: boolean;
let isDifferent: boolean;
let diff: number;
if (response.status === 404) { if (response.status === 404) {
console.log( isNew = true;
`🟢 (+${change.content.length.toLocaleString()}B) ${blobPath}`, diff = change.content.length;
); isDifferent = true;
changes.push(change); } else if (
response.headers.has('Content-Length') &&
parseInt(response.headers.get('Content-Length')!) !==
change.content.length
) {
isNew = false;
diff =
change.content.length -
parseInt(response.headers.get('Content-Length')!);
isDifferent = true;
} else if (response.status === 200) { } else if (response.status === 200) {
isNew = false;
const buffer = Buffer.from(await response.arrayBuffer()); const buffer = Buffer.from(await response.arrayBuffer());
if (!buffer.equals(change.content)) { if (buffer.equals(change.content)) {
const diff = change.content.length - buffer.length; diff = 0;
isDifferent = false;
console.log( } else {
`🟡 (${diff > 0 ? '+' : ''}${diff.toLocaleString()}B) ${blobPath}`, diff = change.content.length - buffer.length;
); isDifferent = true;
changes.push(change);
} }
} else { } else {
throw new Error( throw new Error(
`Unexpected status code ${response.status} for ${blobPath}`, `Unexpected status code ${response.status} for ${blobPath}`,
); );
} }
/**
* Heuristic: if it's a large blob like a .glb, a diff = 0 might end up
* being a false positive with isDifferent = true. So, it's okay to
* assume it's unchanged.
*/
if (
diff === 0 &&
heuristicFormats.some((format) => change.path.endsWith(`.${format}`))
) {
isDifferent = false;
isNew = false;
}
if (isNew) {
console.log(
`🟢 (+${change.content.length.toLocaleString()}B) ${blobPath}`,
);
changes.push(change);
} else if (isDifferent) {
console.log(
`🟡 (${diff > 0 ? '+' : ''}${diff.toLocaleString()}B) ${blobPath}`,
);
changes.push(change);
} else {
console.log(
`🔵 (${change.content.length.toLocaleString()}B) ${blobPath}`,
);
}
}), }),
); );
+1 -10
View File
@@ -4,8 +4,6 @@ import partytown from '@astrojs/partytown';
import react from '@astrojs/react'; import react from '@astrojs/react';
import sitemap from '@astrojs/sitemap'; import sitemap from '@astrojs/sitemap';
import { SUPPORTED_LOCALES } from '@blitzkit/i18n'; import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
import playformInline from '@playform/inline';
import compressor from 'astro-compressor';
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
export default defineConfig({ export default defineConfig({
@@ -23,14 +21,7 @@ export default defineConfig({
outDir: '../../dist/website', outDir: '../../dist/website',
prefetch: true, prefetch: true,
integrations: [ integrations: [react(), sitemap(), partytown()],
react(),
sitemap(),
partytown(),
// playformCompress(),
compressor(),
playformInline(),
],
vite: { vite: {
resolve: { resolve: {
alias: { alias: {
+2 -1
View File
@@ -14,6 +14,7 @@
"@astrolib/seo": "^1.0.0-beta.8", "@astrolib/seo": "^1.0.0-beta.8",
"@blitzkit/core": "workspace:*", "@blitzkit/core": "workspace:*",
"@blitzkit/i18n": "workspace:*", "@blitzkit/i18n": "workspace:*",
"@fontsource/inter": "^5.2.5",
"@nanostores/persistent": "^0.10.2", "@nanostores/persistent": "^0.10.2",
"@nanostores/react": "^0.7.3", "@nanostores/react": "^0.7.3",
"@nivo/bar": "^0.87.0", "@nivo/bar": "^0.87.0",
@@ -25,11 +26,11 @@
"@react-three/fiber": "^8.17.10", "@react-three/fiber": "^8.17.10",
"compare-versions": "^6.1.1", "compare-versions": "^6.1.1",
"echarts": "^5.5.1", "echarts": "^5.5.1",
"googleapis": "^144.0.0",
"immer": "^10.1.1", "immer": "^10.1.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"markdown-to-jsx": "^7.5.0", "markdown-to-jsx": "^7.5.0",
"nanostores": "^0.11.3", "nanostores": "^0.11.3",
"quicklime": "^1.2.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-device-detect": "^2.2.3", "react-device-detect": "^2.2.3",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 KiB

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 KiB

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

@@ -36,6 +36,7 @@ export function ArmorPlateDisplay() {
style={{ pointerEvents: highlightArmor.editingPlate ? 'auto' : 'none' }} style={{ pointerEvents: highlightArmor.editingPlate ? 'auto' : 'none' }}
> >
<Card <Card
variant="classic"
mb="4" mb="4"
style={{ style={{
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
@@ -72,6 +73,7 @@ export function ArmorPlateDisplay() {
{highlightArmor.editingPlate && ( {highlightArmor.editingPlate && (
<Flex gap="2" align="center"> <Flex gap="2" align="center">
<TextField.Root <TextField.Root
variant="classic"
style={{ width: 96 }} style={{ width: 96 }}
mt="2" mt="2"
ref={input} ref={input}
@@ -5,7 +5,14 @@ import {
} from '@blitzkit/core'; } from '@blitzkit/core';
import { invalidate, useFrame } from '@react-three/fiber'; import { invalidate, useFrame } from '@react-three/fiber';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { MeshBasicMaterial, Object3D, ShaderMaterial, Vector2 } from 'three'; import {
MeshBasicMaterial,
Object3D,
ShaderMaterial,
UniformsLib,
UniformsUtils,
Vector2,
} from 'three';
import { degToRad } from 'three/src/math/MathUtils.js'; import { degToRad } from 'three/src/math/MathUtils.js';
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment'; import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../core/blitzkit/jsxTree'; import { jsxTree } from '../../../../core/blitzkit/jsxTree';
@@ -36,27 +43,33 @@ export function PrimaryArmorSceneComponent({
fragmentShader, fragmentShader,
vertexShader, vertexShader,
fog: true,
transparent: true, transparent: true,
uniforms: { uniforms: UniformsUtils.merge([
thickness: { value: null }, UniformsLib.common,
penetration: { value: null }, UniformsLib.fog,
caliber: { value: null },
ricochet: { value: null },
normalization: { value: null },
isExplosive: { value: null },
canSplash: { value: null },
damage: { value: null },
explosionRadius: { value: null },
greenPenetration: { value: null },
advancedHighlighting: { value: null },
opaque: { value: null },
inverseProjectionMatrix: { value: null }, {
resolution: { value: new Vector2() }, thickness: { value: null },
spacedArmorBuffer: { value: null }, penetration: { value: null },
spacedArmorDepth: { value: null }, caliber: { value: null },
}, ricochet: { value: null },
normalization: { value: null },
isExplosive: { value: null },
canSplash: { value: null },
damage: { value: null },
explosionRadius: { value: null },
greenPenetration: { value: null },
advancedHighlighting: { value: null },
opaque: { value: null },
inverseProjectionMatrix: { value: null },
resolution: { value: new Vector2() },
spacedArmorBuffer: { value: null },
spacedArmorDepth: { value: null },
},
]),
}); });
useEffect(() => { useEffect(() => {
@@ -198,16 +211,30 @@ export function PrimaryArmorSceneComponent({
return ( return (
<> <>
{jsxTree( {jsxTree(node, {
node, mesh(_, props, key) {
{ mesh: { renderOrder: 0, material: excludeMaterial } }, return (
`${node.uuid}-exclude`, <mesh
)} {...props}
{jsxTree( key={`${key}-exclude`}
node, renderOrder={0}
{ mesh: { renderOrder: 1, material } }, material={excludeMaterial}
`${node.uuid}-include`, />
)} );
},
})}
{jsxTree(node, {
mesh(_, props, key) {
return (
<mesh
{...props}
key={`${key}-include`}
renderOrder={1}
material={material}
/>
);
},
})}
</> </>
); );
} }
@@ -1,3 +1,7 @@
#define USE_FOG
#include <fog_pars_fragment>
varying vec3 vNormal; varying vec3 vNormal;
varying vec3 vViewPosition; varying vec3 vViewPosition;
@@ -39,6 +43,8 @@ vec3 getPenetrationColor(bool isThreeCalibersRule, bool couldHaveRicochet) {
} }
void main() { void main() {
#include <clipping_planes_fragment>
vec2 screenCoord = gl_FragCoord.xy / resolution; vec2 screenCoord = gl_FragCoord.xy / resolution;
vec4 spacedArmorData = texture2D(spacedArmorBuffer, screenCoord); vec4 spacedArmorData = texture2D(spacedArmorBuffer, screenCoord);
bool isUnderSpacedArmor = spacedArmorData.a != 0.0; bool isUnderSpacedArmor = spacedArmorData.a != 0.0;
@@ -111,4 +117,6 @@ void main() {
} else { } else {
gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha); gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha);
} }
#include <fog_fragment>
} }
@@ -1,11 +1,17 @@
#define USE_FOG
#include <fog_pars_vertex>
varying vec3 vNormal; varying vec3 vNormal;
varying vec3 vViewPosition; varying vec3 vViewPosition;
void main() { void main() {
vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0); vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewPosition = vViewPosition4.xyz; vViewPosition = mvPosition.xyz;
vNormal = normalMatrix * normal; vNormal = normalMatrix * normal;
gl_Position = projectionMatrix * vViewPosition4; gl_Position = projectionMatrix * mvPosition;
}
#include <fog_vertex>
}
@@ -115,7 +115,7 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
const inTitleColor = shotStatusColors[shot.in.status]; const inTitleColor = shotStatusColors[shot.in.status];
return ( return (
<Card style={{ width: 300 }} {...props}> <Card variant="classic" style={{ width: 300 }} {...props}>
<Flex direction="column" gap="2"> <Flex direction="column" gap="2">
<Flex direction="column" gap="1"> <Flex direction="column" gap="1">
<Text color={inTitleColor} weight="bold"> <Text color={inTitleColor} weight="bold">
@@ -74,7 +74,7 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
return ( return (
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
scene={scene} scene={scene}
key={node.uuid} key={`${node.uuid}-hull`}
type={spaced ? ArmorType.Spaced : ArmorType.Primary} type={spaced ? ArmorType.Spaced : ArmorType.Primary}
thickness={thickness} thickness={thickness}
node={node} node={node}
@@ -93,7 +93,7 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
return ( return (
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
scene={scene} scene={scene}
key={node.uuid} key={`${node.uuid}-track`}
type={ArmorType.External} type={ArmorType.External}
thickness={trackModelDefinition.thickness} thickness={trackModelDefinition.thickness}
variant="track" variant="track"
@@ -127,7 +127,7 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
<group position={turretOrigin}> <group position={turretOrigin}>
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
scene={scene} scene={scene}
key={node.uuid} key={`${node.uuid}-turret`}
type={spaced ? ArmorType.Spaced : ArmorType.Primary} type={spaced ? ArmorType.Spaced : ArmorType.Primary}
thickness={thickness} thickness={thickness}
node={node} node={node}
@@ -158,7 +158,7 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
return null; return null;
return ( return (
<group position={hullOrigin} key={node.uuid}> <group position={hullOrigin} key={`${node.uuid}-gun-spaced}`}>
<group position={turretOrigin.clone().add(gunOrigin)}> <group position={turretOrigin.clone().add(gunOrigin)}>
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
scene={scene} scene={scene}
@@ -185,7 +185,7 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
return ( return (
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
scene={scene} scene={scene}
key={node.uuid} key={`${node.uuid}-gun-external`}
type={ArmorType.External} type={ArmorType.External}
thickness={gunModelDefinition.thickness} thickness={gunModelDefinition.thickness}
variant="gun" variant="gun"
@@ -114,33 +114,57 @@ export function SpacedArmorSubExternal({
return ( return (
<> <>
{jsxTree( {jsxTree(node, {
node, group(_, props, key) {
{ return (
mesh: { <group {...props} key={`${key}-spaced-sub-external-exclude`} />
renderOrder: 3, );
material: new MeshBasicMaterial({
colorWrite: false,
depthTest: true,
depthWrite: true,
...(clip ? { clippingPlanes: [clip] } : {}),
}),
onClick() {},
userData: {
type: ArmorType.External,
thickness,
variant,
} satisfies ArmorUserData,
},
}, },
`${node.uuid}-exclude`,
)}
{jsxTree( mesh(_, props, key) {
node, return (
{ mesh: { renderOrder: 4, material } }, <mesh
`${node.uuid}-include`, {...props}
)} key={`${key}-spaced-sub-external-exclude`}
renderOrder={3}
material={
new MeshBasicMaterial({
colorWrite: false,
depthTest: true,
depthWrite: true,
...(clip ? { clippingPlanes: [clip] } : {}),
})
}
userData={
{
type: ArmorType.External,
thickness,
variant,
} satisfies ArmorUserData
}
/>
);
},
})}
{jsxTree(node, {
group(_, props, key) {
return (
<group {...props} key={`${key}-spaced-sub-external-include`} />
);
},
mesh(_, props, key) {
return (
<mesh
{...props}
key={`${key}-spaced-sub-external-include`}
renderOrder={4}
material={material}
/>
);
},
})}
</> </>
); );
} }
@@ -124,32 +124,46 @@ export function SpacedArmorSubSpaced({
return ( return (
<> <>
{jsxTree( {jsxTree(node, {
node, group(_, props, key) {
{ return <group {...props} key={`${key}-spaced-sub-spaced-exclude`} />;
mesh: {
renderOrder: 2,
material,
onClick() {},
userData: {
type: ArmorType.Spaced,
thickness,
} satisfies ArmorUserData,
},
}, },
`${node.uuid}-exclude`,
)}
{jsxTree( mesh(_, props, key) {
node, return (
{ <mesh
mesh: { {...props}
renderOrder: 5, key={`${key}-spaced-sub-spaced-exclude`}
material: depthWriteMaterial, renderOrder={2}
}, material={material}
onClick={() => {}}
userData={
{
type: ArmorType.Spaced,
thickness,
} satisfies ArmorUserData
}
/>
);
}, },
`${node.uuid}-include`, })}
)}
{jsxTree(node, {
group(_, props, key) {
return <group {...props} key={`${key}-spaced-sub-spaced-include`} />;
},
mesh(_, props, key) {
return (
<mesh
{...props}
key={`${key}-spaced-sub-spaced-include`}
renderOrder={5}
material={depthWriteMaterial}
/>
);
},
})}
</> </>
); );
} }
@@ -372,29 +372,33 @@ export function SpacedArmorSceneComponent({
return ( return (
<> <>
{props.type === ArmorType.Primary && {props.type === ArmorType.Primary &&
jsxTree( jsxTree(node, {
node, mesh(_, props, key) {
{ return (
mesh: { <mesh
renderOrder: 0, {...props}
material: omitMaterial, key={`${key}-spaced-component`}
userData: { renderOrder={0}
type: ArmorType.Primary, material={omitMaterial}
thickness, userData={
} satisfies ArmorUserData, {
async onClick(event) { type: ArmorType.Primary,
event.stopPropagation(); thickness,
const shot = (await shoot( } satisfies ArmorUserData
event.point, }
event.intersections, onClick={async (event) => {
true, event.stopPropagation();
))!; const shot = (await shoot(
tankopediaEphemeralStore.setState({ shot }); event.point,
}, event.intersections,
}, true,
))!;
tankopediaEphemeralStore.setState({ shot });
}}
/>
);
}, },
node.uuid, })}
)}
{props.type === ArmorType.Spaced && ( {props.type === ArmorType.Spaced && (
<SpacedArmorSubSpaced node={node} thickness={thickness} /> <SpacedArmorSubSpaced node={node} thickness={thickness} />
@@ -215,50 +215,62 @@ export function StaticArmorSceneComponent({
return ( return (
<> <>
{jsxTree(node, { {jsxTree(node, {
mesh: { mesh(_, meshProps, key) {
material: surfaceMaterial, return (
onPointerDown, <mesh
userData: { {...meshProps}
type: props.type, key={key}
variant: props.type === ArmorType.External ? props.variant : 'gun', material={surfaceMaterial}
thickness, onPointerDown={onPointerDown}
} satisfies ArmorUserData, userData={
{
type: props.type,
variant:
props.type === ArmorType.External ? props.variant : 'gun',
thickness,
} satisfies ArmorUserData
}
onClick={(event) => {
if (discardClippingPlane(event.object, event.point)) return;
onClick(event) { event.stopPropagation();
if (discardClippingPlane(event.object, event.point)) return;
event.stopPropagation(); const { editStatic } = tankopediaEphemeralStore.getState();
const { editStatic } = tankopediaEphemeralStore.getState(); const bounds = new Box3().setFromObject(event.object);
const point = bounds.min
.clone()
.add(bounds.max)
.divideScalar(2)
.setY(bounds.max.y);
const cameraNormal = camera.position
.clone()
.sub(point)
.normalize();
const surfaceNormal = event
.normal!.clone()
.applyQuaternion(
event.object.getWorldQuaternion(new Quaternion()),
);
const angle = surfaceNormal.angleTo(cameraNormal);
const thicknessAngled =
thickness / Math.sin(Math.PI / 2 - angle);
const bounds = new Box3().setFromObject(event.object); mutateTankopediaEphemeralStore((draft) => {
const point = bounds.min draft.highlightArmor = {
.clone() type: props.type,
.add(bounds.max) name,
.divideScalar(2) point,
.setY(bounds.max.y); thickness,
const cameraNormal = camera.position.clone().sub(point).normalize(); thicknessAngled,
const surfaceNormal = event angle,
.normal!.clone() color: `#${color.getHexString()}`,
.applyQuaternion( editingPlate: editStatic,
event.object.getWorldQuaternion(new Quaternion()), };
); });
const angle = surfaceNormal.angleTo(cameraNormal); }}
const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle); />
);
mutateTankopediaEphemeralStore((draft) => {
draft.highlightArmor = {
type: props.type,
name,
point,
thickness,
thicknessAngled,
angle,
color: `#${color.getHexString()}`,
editingPlate: editStatic,
};
});
},
}, },
})} })}
@@ -1,4 +1,4 @@
import { createPortal, useFrame } from '@react-three/fiber'; import { createPortal, useFrame, useThree } from '@react-three/fiber';
import { memo, useState } from 'react'; import { memo, useState } from 'react';
import { DepthTexture, Scene, Vector2 } from 'three'; import { DepthTexture, Scene, Vector2 } from 'three';
import { PrimaryArmorScene } from './components/PrimaryArmorScene'; import { PrimaryArmorScene } from './components/PrimaryArmorScene';
@@ -6,8 +6,14 @@ import { spacedArmorRenderTarget } from './components/PrimaryArmorSceneComponent
import { SpacedArmorScene } from './components/SpacedArmorScene'; import { SpacedArmorScene } from './components/SpacedArmorScene';
export const Armor = memo(() => { export const Armor = memo(() => {
const rootScene = useThree((state) => state.scene);
const [spacedArmorScene] = useState(() => new Scene()); const [spacedArmorScene] = useState(() => new Scene());
const [primaryArmorScene] = useState(() => new Scene()); const [primaryArmorScene] = useState(() => {
const primaryArmorScene = new Scene();
primaryArmorScene.fog = rootScene.fog;
return primaryArmorScene;
});
const spacedArmorPortal = createPortal( const spacedArmorPortal = createPortal(
<SpacedArmorScene scene={spacedArmorScene} />, <SpacedArmorScene scene={spacedArmorScene} />,
spacedArmorScene, spacedArmorScene,
@@ -1,5 +1,6 @@
import { PlusIcon, TrashIcon } from '@radix-ui/react-icons'; import { PlusIcon, TrashIcon } from '@radix-ui/react-icons';
import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes'; import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes';
import { awaitableModelDefinitions } from '../../core/awaitables/modelDefinitions';
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions'; import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember'; import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
import { useLocale } from '../../hooks/useLocale'; import { useLocale } from '../../hooks/useLocale';
@@ -15,7 +16,10 @@ interface ControlsProps {
onAddTankDialogOpenChange: (open: boolean) => void; onAddTankDialogOpenChange: (open: boolean) => void;
} }
const provisionDefinitions = await awaitableProvisionDefinitions; const [provisionDefinitions, modelDefinitions] = await Promise.all([
awaitableProvisionDefinitions,
awaitableModelDefinitions,
]);
export function Controls({ export function Controls({
addTankDialogOpen, addTankDialogOpen,
@@ -56,9 +60,11 @@ export function Controls({
<TankSearch <TankSearch
compact compact
onSelect={(tank) => { onSelect={(tank) => {
const model = modelDefinitions.models[tank.id];
mutateCompareEphemeral((draft) => { mutateCompareEphemeral((draft) => {
draft.members.push( draft.members.push(
tankToCompareMember(tank, provisionDefinitions), tankToCompareMember(tank, model, provisionDefinitions),
); );
draft.sorting = undefined; draft.sorting = undefined;
}); });
@@ -67,9 +73,15 @@ export function Controls({
onSelectAll={(tanks) => { onSelectAll={(tanks) => {
mutateCompareEphemeral((draft) => { mutateCompareEphemeral((draft) => {
draft.members.push( draft.members.push(
...tanks.map((tank) => ...tanks.map((tank) => {
tankToCompareMember(tank, provisionDefinitions), const model = modelDefinitions.models[tank.id];
),
return tankToCompareMember(
tank,
model,
provisionDefinitions,
);
}),
); );
draft.sorting = undefined; draft.sorting = undefined;
}); });
@@ -95,6 +107,7 @@ export function Controls({
</Button> </Button>
<SegmentedControl.Root <SegmentedControl.Root
variant="classic"
value={deltaMode} value={deltaMode}
onValueChange={(value) => { onValueChange={(value) => {
mutateComparePersistent((draft) => { mutateComparePersistent((draft) => {
@@ -105,7 +105,7 @@ export function TankCard({ index, tank }: TankCardProps) {
return ( return (
<StickyColumnHeaderCell> <StickyColumnHeaderCell>
<Flex direction="column" align="center" justify="between" gap="2"> <Flex direction="column" align="center" justify="between" gap="2">
<TankControl index={index} key={tank.id} id={tank.id} /> <TankControl index={index} key={tank.id} slug={tank.slug} />
<Flex <Flex
ref={draggable} ref={draggable}
@@ -1,6 +1,7 @@
import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons'; import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons';
import { Dialog, Flex, IconButton } from '@radix-ui/themes'; import { Dialog, Flex, IconButton } from '@radix-ui/themes';
import { useState } from 'react'; import { useState } from 'react';
import { awaitableModelDefinitions } from '../../core/awaitables/modelDefinitions';
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions'; import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember'; import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
import { useLocale } from '../../hooks/useLocale'; import { useLocale } from '../../hooks/useLocale';
@@ -10,12 +11,15 @@ import { TankSearch } from '../TankSearch';
interface TankControlProps { interface TankControlProps {
index: number; index: number;
id: number; slug: string;
} }
const provisionDefinitions = await awaitableProvisionDefinitions; const [provisionDefinitions, modelDefinitions] = await Promise.all([
awaitableProvisionDefinitions,
awaitableModelDefinitions,
]);
export function TankControl({ index, id }: TankControlProps) { export function TankControl({ index, slug }: TankControlProps) {
const { locale } = useLocale(); const { locale } = useLocale();
const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false); const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false);
const mutateCompareEphemeral = CompareEphemeral.useMutation(); const mutateCompareEphemeral = CompareEphemeral.useMutation();
@@ -57,9 +61,12 @@ export function TankControl({ index, id }: TankControlProps) {
<TankSearch <TankSearch
compact compact
onSelect={(tank) => { onSelect={(tank) => {
const model = modelDefinitions.models[tank.id];
mutateCompareEphemeral((draft) => { mutateCompareEphemeral((draft) => {
draft.members[index] = tankToCompareMember( draft.members[index] = tankToCompareMember(
tank, tank,
model,
provisionDefinitions, provisionDefinitions,
); );
draft.sorting = undefined; draft.sorting = undefined;
@@ -72,11 +79,7 @@ export function TankControl({ index, id }: TankControlProps) {
</Dialog.Content> </Dialog.Content>
</Dialog.Root> </Dialog.Root>
<LinkI18n <LinkI18n locale={locale} href={`/tanks/${slug}`} target="_blank">
locale={locale}
href={`/tools/tankopedia/${id}`}
target="_blank"
>
<IconButton variant="ghost"> <IconButton variant="ghost">
<ExternalLinkIcon /> <ExternalLinkIcon />
</IconButton> </IconButton>
@@ -1,5 +1,5 @@
import { Switch } from '@radix-ui/themes'; import { Switch } from '@radix-ui/themes';
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/tools/embed/[embed]/_index'; import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
import { EmbedState } from '../../stores/embedState'; import { EmbedState } from '../../stores/embedState';
export function Boolean({ configKey }: EmbedPreviewControllerProps) { export function Boolean({ configKey }: EmbedPreviewControllerProps) {
@@ -7,7 +7,7 @@ export function Boolean({ configKey }: EmbedPreviewControllerProps) {
return ( return (
<Switch <Switch
variant="soft" variant="classic"
checked={EmbedState.use((state) => state[configKey] as boolean)} checked={EmbedState.use((state) => state[configKey] as boolean)}
onCheckedChange={() => { onCheckedChange={() => {
mutateEmbedState((draft) => { mutateEmbedState((draft) => {
@@ -1,4 +1,4 @@
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/tools/embed/[embed]/_index'; import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
import { EmbedState } from '../../stores/embedState'; import { EmbedState } from '../../stores/embedState';
import { ColorControllerRaw } from './ColorControllerRaw'; import { ColorControllerRaw } from './ColorControllerRaw';
@@ -25,7 +25,7 @@ export function ColorControllerRaw({
}} }}
/> />
<Slider <Slider
variant="soft" variant="classic"
min={0} min={0}
max={255} max={255}
value={[alpha]} value={[alpha]}
@@ -1,6 +1,6 @@
import { Select } from '@radix-ui/themes'; import { Select } from '@radix-ui/themes';
import { useLocale } from '../../hooks/useLocale'; import { useLocale } from '../../hooks/useLocale';
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/tools/embed/[embed]/_index'; import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/embed/[embed]/_index';
import { EmbedState } from '../../stores/embedState'; import { EmbedState } from '../../stores/embedState';
import type { import type {
EmbedConfigItemType, EmbedConfigItemType,
@@ -25,7 +25,8 @@ export function Enum({
}); });
}} }}
> >
<Select.Trigger /> <Select.Trigger variant="classic" />
<Select.Content> <Select.Content>
{config.options.map((option) => ( {config.options.map((option) => (
<Select.Item key={option.value} value={option.value}> <Select.Item key={option.value} value={option.value}>
Loaded 100 of 239 files, more files were not shown because too many files have changed in this diff. Show more