diff --git a/packages/app/package.json b/packages/app/package.json deleted file mode 100644 index 0001efb50..000000000 --- a/packages/app/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@blitzkit/app", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "tauri": "tauri" - }, - "dependencies": { - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-opener": "^2", - "@tauri-apps/cli": "^2", - "vite": "^6.0.3", - "typescript": "~5.6.2" - } -} diff --git a/packages/app/src-tauri/.gitignore b/packages/app/src-tauri/.gitignore deleted file mode 100644 index b21bd681d..000000000 --- a/packages/app/src-tauri/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Generated by Tauri -# will have schema files for capabilities auto-completion -/gen/schemas diff --git a/packages/app/src-tauri/Cargo.lock b/packages/app/src-tauri/Cargo.lock deleted file mode 100644 index a2c357c41..000000000 --- a/packages/app/src-tauri/Cargo.lock +++ /dev/null @@ -1,5286 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.1", -] - -[[package]] -name = "async-lock" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "async-signal" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.61.1", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link 0.2.0", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" -dependencies = [ - "serde", -] - -[[package]] -name = "blitzkit" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-opener", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "block2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" -dependencies = [ - "objc2 0.6.2", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "brotli" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "bytemuck" -version = "1.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.9.4", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.17", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.7", -] - -[[package]] -name = "cc" -version = "1.2.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link 0.2.0", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.9.4", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.9.4", - "core-foundation", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.106", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.106", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.106", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "deranged" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" -dependencies = [ - "powerfmt", - "serde_core", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.106", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.1", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "dlopen2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "embed-resource" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 0.9.7", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "endi" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.1", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" - -[[package]] -name = "flate2" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.7+wasi-0.2.4", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.9.4", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever", - "match_token", -] - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-util" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.1", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" -dependencies = [ - "equivalent", - "hashbrown 0.16.0", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", -] - -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "js-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.9.4", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 2.11.4", - "selectors", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.176" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags 2.9.4", - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "muda" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "once_cell", - "png", - "serde", - "thiserror 2.0.17", - "windows-sys 0.60.2", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.9.4", - "jni-sys", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "num-conv" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.1", - "libc", - "objc2 0.6.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-foundation 0.3.1", - "objc2-quartz-core 0.3.1", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" -dependencies = [ - "bitflags 2.9.4", - "dispatch2", - "objc2 0.6.2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" -dependencies = [ - "bitflags 2.9.4", - "dispatch2", - "objc2 0.6.2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" -dependencies = [ - "objc2 0.6.2", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.9.4", - "block2 0.5.1", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.1", - "libc", - "objc2 0.6.2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-javascript-core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9052cb1bb50a4c161d934befcf879526fb87ae9a68858f241e693ca46225cf5a" -dependencies = [ - "objc2 0.6.2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.9.4", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.9.4", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-security" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8e0ef3ab66b08c42644dcb34dba6ec0a574bbd8adbb8bdbdc7a2779731a44" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" -dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-core-foundation", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.1", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "objc2-javascript-core", - "objc2-security", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "open" -version = "5.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.1", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plist" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" -dependencies = [ - "base64 0.22.1", - "indexmap 2.11.4", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.1", -] - -[[package]] -name = "potential_utf" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit 0.23.6", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.38.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" -dependencies = [ - "bitflags 2.9.4", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 2.0.17", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "regex" -version = "1.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" - -[[package]] -name = "reqwest" -version = "0.12.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags 2.9.4", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.1", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.106", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_json" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.11.4", - "schemars 0.9.0", - "schemars 1.0.4", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "softbuffer" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" -dependencies = [ - "bytemuck", - "cfg_aliases", - "core-graphics", - "foreign-types", - "js-sys", - "log", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", - "raw-window-handle", - "redox_syscall", - "wasm-bindgen", - "web-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.34.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7" -dependencies = [ - "bitflags 2.9.4", - "block2 0.6.1", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dispatch", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "once_cell", - "parking_lot", - "raw-window-handle", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.3", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.17", - "tokio", - "tray-icon", - "url", - "urlpattern", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows", -] - -[[package]] -name = "tauri-build" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "toml 0.9.7", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.106", - "tauri-utils", - "thiserror 2.0.17", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.106", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "toml 0.9.7", - "walkdir", -] - -[[package]] -name = "tauri-plugin-opener" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5" -dependencies = [ - "dunce", - "glob", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "open", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.17", - "url", - "windows", - "zbus", -] - -[[package]] -name = "tauri-runtime" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2 0.6.2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.17", - "url", - "webkit2gtk", - "webview2-com", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-foundation 0.3.1", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dunce", - "glob", - "html5ever", - "http", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.3", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.17", - "toml 0.9.7", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" -dependencies = [ - "embed-resource", - "toml 0.9.7", -] - -[[package]] -name = "tempfile" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.61.1", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl 2.0.17", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.47.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" -dependencies = [ - "backtrace", - "bytes", - "io-uring", - "libc", - "mio", - "pin-project-lite", - "slab", - "socket2", - "windows-sys 0.59.0", -] - -[[package]] -name = "tokio-util" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" -dependencies = [ - "indexmap 2.11.4", - "serde_core", - "serde_spanned 1.0.2", - "toml_datetime 0.7.2", - "toml_parser", - "toml_writer", - "winnow 0.7.13", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.11.4", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.11.4", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" -dependencies = [ - "indexmap 2.11.4", - "toml_datetime 0.7.2", - "toml_parser", - "winnow 0.7.13", -] - -[[package]] -name = "toml_parser" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" -dependencies = [ - "winnow 0.7.13", -] - -[[package]] -name = "toml_writer" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags 2.9.4", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tray-icon" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.1", - "once_cell", - "png", - "serde", - "thiserror 2.0.17", - "windows-sys 0.59.0", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicode-ident" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" -dependencies = [ - "getrandom 0.3.3", - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webview2-com" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" -dependencies = [ - "thiserror 2.0.17", - "windows", - "windows-core 0.61.2", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.1", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.0", - "windows-result 0.4.0", - "windows-strings 0.5.0", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-interface" -version = "0.59.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.4", -] - -[[package]] -name = "windows-sys" -version = "0.61.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" -dependencies = [ - "windows-link 0.2.0", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-version" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "700dad7c058606087f6fdc1f88da5841e06da40334413c6cd4367b25ef26d24e" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "wry" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90" -dependencies = [ - "base64 0.22.1", - "block2 0.6.1", - "cookie", - "crossbeam-channel", - "dirs", - "dpi", - "dunce", - "gdkx11", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni", - "kuchikiki", - "libc", - "ndk", - "objc2 0.6.2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.1", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.17", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "nix", - "ordered-stream", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "windows-sys 0.60.2", - "winnow 0.7.13", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" -dependencies = [ - "serde", - "static_assertions", - "winnow 0.7.13", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "zvariant" -version = "5.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow 0.7.13", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.106", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.106", - "winnow 0.7.13", -] diff --git a/packages/app/src-tauri/Cargo.toml b/packages/app/src-tauri/Cargo.toml deleted file mode 100644 index 155fe8b1a..000000000 --- a/packages/app/src-tauri/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "blitzkit" -version = "0.1.0" -description = "A Tauri App" -authors = ["you"] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -# The `_lib` suffix may seem redundant but it is necessary -# to make the lib name unique and wouldn't conflict with the bin name. -# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "blitzkit_lib" -crate-type = ["staticlib", "cdylib", "rlib"] - -[build-dependencies] -tauri-build = { version = "2", features = [] } - -[dependencies] -tauri = { version = "2", features = [] } -tauri-plugin-opener = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" - diff --git a/packages/app/src-tauri/build.rs b/packages/app/src-tauri/build.rs deleted file mode 100644 index d860e1e6a..000000000 --- a/packages/app/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/packages/app/src-tauri/capabilities/default.json b/packages/app/src-tauri/capabilities/default.json deleted file mode 100644 index 4cdbf49a7..000000000 --- a/packages/app/src-tauri/capabilities/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Capability for the main window", - "windows": ["main"], - "permissions": [ - "core:default", - "opener:default" - ] -} diff --git a/packages/app/src-tauri/src/lib.rs b/packages/app/src-tauri/src/lib.rs deleted file mode 100644 index 4a277ef35..000000000 --- a/packages/app/src-tauri/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/packages/app/src-tauri/src/main.rs b/packages/app/src-tauri/src/main.rs deleted file mode 100644 index 986d1d794..000000000 --- a/packages/app/src-tauri/src/main.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -use std::process::{Command, Stdio}; -use tauri::Manager; - -fn main() { - tauri::Builder::default() - .setup(|app| { - let app_handle = app.handle(); - - let entry = app_handle - .path_resolver() - .resolve_resource("../../dist/website/server/entry.mjs") - .expect("failed to resolve server.ts"); - - Command::new("bun") - .arg("run") - .arg(entry) - .stderr(Stdio::null()) - .spawn() - .expect("failed to start Bun server"); - - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running Tauri app"); -} diff --git a/packages/app/src-tauri/tauri.conf.json b/packages/app/src-tauri/tauri.conf.json deleted file mode 100644 index 36d837238..000000000 --- a/packages/app/src-tauri/tauri.conf.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "blitzkit", - "version": "0.1.0", - "identifier": "com.tresabhi.blitzkit", - "build": { - "beforeDevCommand": "cd ../website && bun run dev", - "_beforeBuildCommand": "cd ../website && bun run build", - "frontendDist": "../../../dist/website/client", - "devUrl": "http://localhost:4321" - }, - "app": { - "withGlobalTauri": true, - "windows": [ - { - "url": "http://localhost:4321", - "title": "BlitzKit", - "fullscreen": false, - "resizable": true, - "maximized": true - } - ], - "security": { - "csp": null - } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - } -} diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json deleted file mode 100644 index b533f27f1..000000000 --- a/packages/app/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": ["../../tsconfig.json"], - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - } -} diff --git a/packages/bot/package.json b/packages/bot/package.json deleted file mode 100644 index 6e18e80ef..000000000 --- a/packages/bot/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@blitzkit/bot", - "dependencies": { - "@blitzkit/core": "workspace:*", - "@blitzkit/i18n": "workspace:*", - "@radix-ui/colors": "^3.0.0", - "@radix-ui/themes": "^3.3.0", - "@resvg/resvg-js": "^2.6.2", - "@types/adm-zip": "^0.5.7", - "@types/bun": "^1.3.9", - "@types/lodash-es": "^4.17.12", - "@types/markdown-escape": "^1.1.3", - "@types/react": "^19.2.14", - "adm-zip": "^0.5.16", - "bepaint": "^0.1.0-beta.3", - "discord.js": "^14.25.1", - "dotenv": "^17.3.1", - "fuzzysort": "^3.1.0", - "lodash-es": "^4.17.23", - "markdown-escape": "^2.0.0", - "pickleparser": "^0.2.1", - "prisma": "^6.19.2", - "protobufjs": "^7.5.4", - "react": "^19.2.4", - "satori": "^0.18.4", - "sharp": "^0.34.5" - }, - "private": true, - "scripts": { - "dev": "bun --watch src/main.ts", - "start": "bun src/main.ts" - }, - "type": "module", - "types": "src/types.d.ts" -} diff --git a/packages/bot/src/commands/about.ts b/packages/bot/src/commands/about.ts deleted file mode 100644 index 82552eca3..000000000 --- a/packages/bot/src/commands/about.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import { - commandsArray, - type CommandRegistry, -} from "../events/interactionCreate"; - -const subcommands = [ - "embeds", - "introduction", - "invite", - "timezones", - "credits", -] as const; -type Subcommand = (typeof subcommands)[number]; - -const DOCS: Record = { - embeds: "guide/embeds", - introduction: "guide/introduction", - invite: "guide/invite", - timezones: "guide/timezones", - credits: "about/credits", -}; - -export const aboutCommand = new Promise(async (resolve) => { - resolve({ - command: createLocalizedCommand("about", [ - { subcommand: "commands" }, - ...subcommands.map((subcommand) => ({ subcommand })), - ]), - - async handler(interaction) { - const subcommand = interaction.options.getSubcommand(); - const { strings } = translator(interaction.locale); - - if (subcommand === "commands") { - return `${strings.bot.commands.about.subcommands.commands.body}${( - await Promise.all(commandsArray) - ) - .sort((a, b) => - (a.command.name_localizations?.[interaction.locale] ?? - a.command.name) < - (b.command.name_localizations?.[interaction.locale] ?? - b.command.name) - ? -1 - : 1, - ) - .map( - (registry) => - `- \`/${ - registry.command.name_localizations?.[interaction.locale] ?? - registry.command.name - }\`: ${ - registry.command.description_localizations?.[ - interaction.locale - ] ?? registry.command.description - }`, - ) - .join("\n")}`; - } - - const url = `https://raw.githubusercontent.com/tresabhi/blitzkit/main/docs/${DOCS[subcommand as Subcommand]}.md`; - const content = fetch(url).then((response) => response.text()); - - return content; - }, - }); -}); diff --git a/packages/bot/src/commands/breakdown/index.tsx b/packages/bot/src/commands/breakdown/index.tsx deleted file mode 100644 index 958e8abd2..000000000 --- a/packages/bot/src/commands/breakdown/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { addPeriodicFilterOptions } from "../../core/discord/addPeriodicFilterOptions"; -import { addUsernameChoices } from "../../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../../core/discord/resolvePeriodFromButton"; -import { resolvePlayerFromButton } from "../../core/discord/resolvePlayerFromButton"; -import { translator } from "../../core/localization/translator"; -import type { CommandRegistry } from "../../events/interactionCreate"; -import { renderBreakdown } from "./render"; - -export const breakdownCommand = new Promise( - async (resolve) => { - resolve({ - command: await addPeriodicFilterOptions( - createLocalizedCommand("breakdown"), - (option) => option.addStringOption(addUsernameChoices), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // ...filters, - // }); - - // return [ - // ...(await renderBreakdown( - // player, - // period, - // filters, - // interaction.locale, - // )), - // buttonRefresh(interaction, path), - // await getBlitzStarsLinkButton(player.region, player.id), - // ]; - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); - - return await renderBreakdown( - player, - period, - filters, - interaction.locale, - ); - }, - }); - }, -); diff --git a/packages/bot/src/commands/breakdown/render.tsx b/packages/bot/src/commands/breakdown/render.tsx deleted file mode 100644 index 91fda4e2b..000000000 --- a/packages/bot/src/commands/breakdown/render.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import { - type BlitzStats, - calculateWN8, - getAccountInfo, - getClanAccountInfo, - getTankStats, - getWN8Percentile, - TankDefinition, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { chunk } from "lodash-es"; -import * as Breakdown from "../../components/Breakdown"; -import { CommandWrapper } from "../../components/CommandWrapper"; -import { NoData } from "../../components/NoData"; -import { TitleBar } from "../../components/TitleBar"; -import { filtersToDescription } from "../../core/blitzkit/filtersToDescription"; -import { - averageDefinitions, - tankDefinitions, -} from "../../core/blitzkit/nonBlockingPromises"; -import { UserError } from "../../core/blitzkit/userError"; -import { - filterStats, - type StatFilters, -} from "../../core/blitzstars/filterStats"; -import { getStatsInPeriod } from "../../core/blitzstars/getStatsInPeriod"; -import type { ResolvedPeriod } from "../../core/discord/resolvePeriodFromCommand"; -import type { ResolvedPlayer } from "../../core/discord/resolvePlayerFromCommand"; -import { translator } from "../../core/localization/translator"; - -const ROWS_PER_PAGE = 8; -const MAX_PAGES = 9; - -export async function renderBreakdown( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - filters: StatFilters, - locale: Locale, -) { - const { strings, unwrap } = translator(locale); - const awaitedTankDefinitions = await tankDefinitions; - const awaitedTankAverages = await averageDefinitions; - const statsInPeriod = await getStatsInPeriod(region, id, start, end, locale); - const { filteredOrder } = await filterStats(statsInPeriod, filters); - const accountInfo = await getAccountInfo(region, id); - const clanData = await getClanAccountInfo(region, id, ["clan"]); - const tankStats = await getTankStats(region, id); - const filterDescriptions = await filtersToDescription(filters, locale); - const orderedCurrentStats: BlitzStats[] = []; - const orderedCareerStats: BlitzStats[] = []; - const orderedCurrentWN8: (number | undefined)[] = []; - const orderedCareerWN8: (number | undefined)[] = []; - const orderedCareerWN8Full: (number | undefined)[] = []; - - if (tankStats === null) { - throw new UserError(strings.bot.common.errors.no_tank_stats); - } - - filteredOrder.forEach((id) => { - const tankAveragesEntry = awaitedTankAverages.averages[id]; - const current = statsInPeriod.diff[id]; - const career = tankStats.find((tank) => tank.tank_id === id)!.all; - const currentWN8 = tankAveragesEntry - ? calculateWN8(tankAveragesEntry.mu, current) - : undefined; - const careerWN8 = tankAveragesEntry - ? calculateWN8(tankAveragesEntry.mu, career) - : undefined; - - orderedCurrentStats.push(current); - orderedCareerStats.push(career); - orderedCurrentWN8.push(currentWN8); - orderedCareerWN8.push(careerWN8); - }); - tankStats.forEach((tank) => { - const tankAveragesEntry = awaitedTankAverages.averages[tank.tank_id]; - - orderedCareerWN8Full.push( - tankAveragesEntry - ? calculateWN8(tankAveragesEntry.mu, tank.all) - : undefined, - ); - }); - - const currentBattles = orderedCurrentStats.reduce( - (accumulator, current) => accumulator + current.battles, - 0, - ); - const careerBattles = tankStats.reduce( - (accumulator, current) => accumulator + current.all.battles, - 0, - ); - const currentWinrate = - orderedCurrentStats.reduce( - (accumulator, current) => accumulator + current.wins, - 0, - ) / currentBattles; - const careerWinrate = - tankStats.reduce( - (accumulator, current) => accumulator + current.all.wins, - 0, - ) / careerBattles; - const currentWN8 = - orderedCurrentWN8.reduce( - (accumulator, current, index) => - accumulator + (current ?? 0) * orderedCurrentStats[index].battles, - 0, - ) / - orderedCurrentStats.reduce( - (accumulator, current, index) => - accumulator + - (orderedCurrentWN8[index] === undefined ? 0 : current.battles), - 0, - ); - const careerWN8 = - orderedCareerWN8Full.reduce( - (accumulator, current) => accumulator + (current ?? 0), - 0, - ) / - tankStats.reduce( - (accumulator, current, index) => - accumulator + - (orderedCareerWN8Full[index] === undefined ? 0 : current.all.battles), - 0, - ); - const currentDamage = - orderedCurrentStats.reduce( - (accumulator, current) => accumulator + current.damage_dealt, - 0, - ) / currentBattles; - const careerDamage = - tankStats.reduce( - (accumulator, current) => accumulator + current.all.damage_dealt, - 0, - ) / careerBattles; - - const children: React.JSX.Element[] = []; - - if (filteredOrder.length > 0) { - children.push( - , - ); - - filteredOrder.forEach((id, index) => { - const tankDefinition = awaitedTankDefinitions.tanks[id] as - | TankDefinition - | undefined; - const current = orderedCurrentStats[index]; - const career = orderedCareerStats[index]; - const currentWN8 = orderedCurrentWN8[index]; - const careerWN8 = orderedCareerWN8[index]; - - children.push( - , - ); - }); - } - - const pages = chunk( - children, - Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE), - ); - - if (filteredOrder.length > 0) { - return pages - .map((page, index) => ( - - {index === 0 && ( - - )} - - {page} - - )) - .reverse(); - } else { - return [ - - - - - , - ]; - } -} diff --git a/packages/bot/src/commands/debug.ts b/packages/bot/src/commands/debug.ts deleted file mode 100644 index 08f43e8ba..000000000 --- a/packages/bot/src/commands/debug.ts +++ /dev/null @@ -1,37 +0,0 @@ -import packageJSON from "../../../../package.json"; -import { client } from "../core/discord/client"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const executionStart = new Date().getTime(); - -export const debugCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("debug"), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const currentTime = new Date().getTime(); - const uptime = currentTime - executionStart; - const list = [ - [strings.bot.commands.debug.body.version, packageJSON.version], - [ - strings.bot.commands.debug.body.shard.label, - client.shard?.ids[0] ?? strings.bot.commands.debug.body.shard.default, - ], - [ - strings.bot.commands.debug.body.uptime, - `${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor( - (uptime / 1000 / 60) % 60 - )}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor( - uptime % 1000 - )}ms`, - ], - ]; - return `${strings.bot.commands.debug.body.title}${list - .map(([key, value]) => `- ${key}: ${value}`) - .join("\n")}`; - }, - }); -}); diff --git a/packages/bot/src/commands/evolution.tsx b/packages/bot/src/commands/evolution.tsx deleted file mode 100644 index 9717938f9..000000000 --- a/packages/bot/src/commands/evolution.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { emblemURL, getAccountInfo, getClanAccountInfo } from "@blitzkit/core"; -import { Locale, SlashCommandSubcommandGroupBuilder } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import * as Graph from "../components/Graph"; -import { LineColor } from "../components/Graph/components/Line/constants"; -import { NoData } from "../components/NoData"; -import { TitleBar } from "../components/TitleBar"; -import { getPlayerHistories } from "../core/blitzstars/getPlayerHistories"; -import { getTankHistories } from "../core/blitzstars/getTankHistories"; -import { addPeriodSubCommands } from "../core/discord/addPeriodSubCommands"; -import { addTankChoices } from "../core/discord/addTankChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -type EvolutionStatType = "player" | "tank"; - -async function render( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - type: "player" | "tank", - tankId: number | null, - locale: Locale, -) { - const accountInfo = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const logo = clan ? emblemURL(clan.emblem_set_id) : undefined; - const histories = await (type === "player" - ? getPlayerHistories(region, id, { - start, - end, - includeLatestHistories: true, - includePreviousHistories: true, - }) - : getTankHistories(region, id, locale, { - tankId: tankId!, - start, - end, - includeLatestHistories: true, - includePreviousHistories: true, - })); - const plot = histories.map( - (history) => - [ - history.all.battles, - (history.all.wins / history.all.battles) * 100, - ] as Graph.PlotItem, - ); - const times = histories.map((history) => history.last_battle_time); - const minTime = Math.min(...times); - const maxTime = Math.max(...times); - const ys = plot.map(([, y]) => y); - const xs = plot.map(([x]) => x); - const maxY = Math.max(...ys); - const minY = Math.min(...ys); - const maxX = Math.max(...xs); - const minX = Math.min(...xs); - - return ( - - - - {plot.length > 0 && ( - - {Graph.Line({ - color: LineColor.Red, - plot: plot, - minY, - maxY, - })} - - )} - - {plot.length === 0 && } - - ); -} - -export const evolutionCommand = new Promise( - async (resolve) => { - resolve({ - command: createLocalizedCommand("evolution", [ - { - group: "player", - modify(option: SlashCommandSubcommandGroupBuilder) { - addPeriodSubCommands(option, (option) => - option.addStringOption(addUsernameChoices), - ); - }, - }, - { - group: "tank", - modify(option: SlashCommandSubcommandGroupBuilder) { - addPeriodSubCommands(option, (option) => - option - .addStringOption(addTankChoices) - .addStringOption(addUsernameChoices), - ); - }, - }, - ]), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const commandGroup = - // interaction.options.getSubcommandGroup() as EvolutionStatType; - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const tankIdRaw = interaction.options.getString("tank")!; - // const tankId = - // commandGroup === "tank" - // ? await resolveTankId(tankIdRaw, interaction.locale) - // : null; - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // tankId, - // }); - - // return [ - // await render( - // player, - // period, - // commandGroup, - // tankId, - // interaction.locale - // ), - // buttonRefresh(interaction, path), - // await getBlitzStarsLinkButton( - // player.region, - // player.id, - // tankId ?? undefined - // ), - // ]; - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const url = new URL(`https://example.com/${interaction.customId}`); - const path = url.pathname.split("/"); - const commandGroup = path[2] as EvolutionStatType; - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - - return await render( - player, - period, - commandGroup, - parseInt(url.searchParams.get("tankId") ?? "0") || null, - interaction.locale, - ); - }, - }); - }, -); diff --git a/packages/bot/src/commands/fullStats.tsx b/packages/bot/src/commands/fullStats.tsx deleted file mode 100644 index 3cc13ce3d..000000000 --- a/packages/bot/src/commands/fullStats.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { - emblemURL, - getAccountInfo, - getClanAccountInfo, - getTierWeights, -} from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { GenericAllStats } from "../components/GenericAllStats"; -import { NoData } from "../components/NoData"; -import { TierWeights } from "../components/TierWeights"; -import { TitleBar } from "../components/TitleBar"; -import { filtersToDescription } from "../core/blitzkit/filtersToDescription"; -import { filterStats, type StatFilters } from "../core/blitzstars/filterStats"; -import { getStatsInPeriod } from "../core/blitzstars/getStatsInPeriod"; -import { addPeriodicFilterOptions } from "../core/discord/addPeriodicFilterOptions"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -async function render( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - filters: StatFilters, - locale: Locale, -) { - const { nickname } = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - const diffedTankStats = await getStatsInPeriod( - region, - id, - start, - end, - locale, - ); - const { stats, supplementary, filteredOrder } = await filterStats( - diffedTankStats, - filters, - ); - const filterDescriptions = await filtersToDescription(filters, locale); - const tierWeights = await getTierWeights(diffedTankStats.diff, filteredOrder); - - return ( - - - - {stats.battles === 0 && ( - - )} - {stats.battles > 0 && } - {stats.battles > 0 && ( - - )} - - ); -} - -export const fullStatsCommand = new Promise( - async (resolve) => { - resolve({ - command: await addPeriodicFilterOptions( - createLocalizedCommand("full-stats"), - (option) => option.addStringOption(addUsernameChoices), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // ...filters, - // }); - - // return Promise.all([ - // render(player, period, filters, interaction.locale), - // buttonRefresh(interaction, path), - // getBlitzStarsLinkButton(player.region, player.id), - // ]); - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); - - return await render(player, period, filters, interaction.locale); - }, - }); - }, -); diff --git a/packages/bot/src/commands/inactive.tsx b/packages/bot/src/commands/inactive.tsx deleted file mode 100644 index 128384a9a..000000000 --- a/packages/bot/src/commands/inactive.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { getAccountInfo, getClanInfo } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { GenericStats } from "../components/GenericStats"; -import { NoData } from "../components/NoData"; -import { TitleBar } from "../components/TitleBar"; -import { addClanChoices } from "../core/discord/addClanChoices"; -import { autocompleteClan } from "../core/discord/autocompleteClan"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { resolveClanFromCommand } from "../core/discord/resolveClanFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const DEFAULT_THRESHOLD = 7; - -export const inactiveCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("inactive") - .addStringOption(addClanChoices) - .addNumberOption((option) => { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName("threshold") - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands.inactive.options.threshold.name, - undefined, - true - ) - ) - .setDescription( - strings.bot.commands.inactive.options.threshold.description - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.inactive.options.threshold.description - ) - ) - .setMinValue(0); - }), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const { region, id } = await resolveClanFromCommand(interaction); - const threshold = - interaction.options.getNumber("threshold")! ?? DEFAULT_THRESHOLD; - const time = new Date().getTime() / 1000; - const clanInfo = await getClanInfo(region, id); - const accountInfo = await getAccountInfo(region, clanInfo.members_ids); - const inactive = accountInfo - .map( - (account) => - [ - account.nickname, - (time - account.last_battle_time) / 60 / 60 / 24, - ] as [string, number] - ) - .filter(([, inactiveDays]) => inactiveDays >= threshold) - .sort((a, b) => b[1] - a[1]) - .map( - ([name, days]) => - [ - name, - literals(strings.bot.commands.inactive.body.listing, { - days: days.toFixed(0), - }), - ] as [string, string] - ); - const hasInactiveMembers = inactive.length > 0; - - return ( - - - - {!hasInactiveMembers && ( - - )} - {hasInactiveMembers && } - - ); - }, - - autocomplete: autocompleteClan, - }); -}); diff --git a/packages/bot/src/commands/link.tsx b/packages/bot/src/commands/link.tsx deleted file mode 100644 index 3961a6687..000000000 --- a/packages/bot/src/commands/link.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { getAccountInfo, getClanAccountInfo } from '@blitzkit/core'; -import { teal } from '@radix-ui/colors'; -import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow'; -import { CommandWrapper } from '../components/CommandWrapper'; -import { linkBlitzAndDiscord } from '../core/db/linkBlitzAndDiscord'; -import { addUsernameChoices } from '../core/discord/addUsernameChoices'; -import { autocompleteUsername } from '../core/discord/autocompleteUsername'; -import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; -import { embedInfo } from '../core/discord/embedInfo'; -import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand'; -import { translator } from '../core/localization/translator'; -import type { CommandRegistry } from '../events/interactionCreate'; -import { theme } from '../stitches.config'; - -export const verifyCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand('link').addStringOption((option) => - addUsernameChoices(option).setRequired(true), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const { id, region } = await resolvePlayerFromCommand(interaction); - const discordId = BigInt(interaction.user.id); - const accountInfo = await getAccountInfo(region, id); - const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']); - - await linkBlitzAndDiscord(discordId, id); - - return [ - -
- - -
-
- avatar - -
- - {interaction.user.displayName} - - - @{interaction.user.username} - -
-
- -
- accounts linked - - {strings.bot.commands.link.body.accounts_linked} - -
- -
- {clanAccountInfo?.clan ? ( - clan emblem - ) : null} -
- - {accountInfo.nickname} - - - {clanAccountInfo?.clan - ? `[${clanAccountInfo?.clan?.tag}] • ` - : ''} - {strings.common.regions.normal[region]} - -
-
-
- - -
-
, - - embedInfo( - strings.bot.commands.link.embed.title, - strings.bot.commands.link.embed.description, - ), - ]; - }, - - autocomplete: autocompleteUsername, - }); -}); diff --git a/packages/bot/src/commands/ownedTanks.tsx b/packages/bot/src/commands/ownedTanks.tsx deleted file mode 100644 index ba4b6e549..000000000 --- a/packages/bot/src/commands/ownedTanks.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { - TANK_CLASSES, - TIER_ROMAN_NUMERALS, - TankDefinition, - flags, - getAccountInfo, - getTankStats, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import markdownEscape from "markdown-escape"; -import { - gameDefinitions, - tankDefinitions, -} from "../core/blitzkit/nonBlockingPromises"; -import { addTierChoices } from "../core/discord/addTierChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { chunkLines } from "../core/discord/chunkLines"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; - -export const ownedTanksCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("owned-tanks") - .addStringOption(addTierChoices) - .addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings, unwrap } = translator(interaction.locale); - const tier = Number(interaction.options.getString("tier")); - const { id, region } = await resolvePlayerFromCommand(interaction); - const accountInfo = await getAccountInfo(region, id); - const tankStats = await getTankStats(region, id); - - if (tankStats === null) { - return strings.bot.common.errors.no_tank_stats; - } - - const filteredTanks = ( - await Promise.all( - tankStats.map(async (tankData) => ({ - tankDefinitions: (await tankDefinitions).tanks[tankData.tank_id]!, - id: tankData.tank_id, - })), - ) - ).filter((tank) => tank.tankDefinitions?.tier === tier); - const groupedTanks: Record = {}; - const nations: string[] = []; - - filteredTanks.forEach((tank) => { - if (groupedTanks[tank.tankDefinitions.nation] === undefined) { - groupedTanks[tank.tankDefinitions.nation] = [tank.tankDefinitions]; - nations.push(tank.tankDefinitions.nation); - } else { - groupedTanks[tank.tankDefinitions.nation].push(tank.tankDefinitions); - } - }); - - const awaitedTankDefinitions = await tankDefinitions; - const awaitedGameDefinitions = await gameDefinitions; - const title = literals(strings.bot.commands.owned_tanks.body.title, { - name: markdownEscape(accountInfo.nickname), - tier: TIER_ROMAN_NUMERALS[tier], - }); - const tankList = tankStats - .map(({ tank_id }) => awaitedTankDefinitions.tanks[tank_id]) - .filter((tank) => tank.tier === tier) - .sort( - (a, b) => - TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class), - ) - .sort( - (a, b) => - awaitedGameDefinitions.nations.indexOf(a.nation) - - awaitedGameDefinitions.nations.indexOf(b.nation), - ) - .map( - (tank) => - `[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name!))}]()`, - ); - const lines = [title, ...tankList]; - - return chunkLines(lines); - }, - - autocomplete: autocompleteUsername, - }); -}); diff --git a/packages/bot/src/commands/permissions.ts b/packages/bot/src/commands/permissions.ts deleted file mode 100644 index 1885c699e..000000000 --- a/packages/bot/src/commands/permissions.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import { CommandRegistry } from "../events/interactionCreate"; - -export const permissionsCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("permissions"), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - // friday the 13th permissions easter egg - const isFridayThe13th = - new Date().getDay() === 5 && new Date().getDate() === 13; - const permissions = [ - [ - interaction.appPermissions?.has("ViewChannel"), - strings.bot.commands.permissions.body.view_channels, - ], - [ - interaction.appPermissions?.has("ReadMessageHistory"), - strings.bot.commands.permissions.body.read_message_history, - ], - [ - interaction.appPermissions?.has("AttachFiles"), - strings.bot.commands.permissions.body.attach_files, - ], - [ - interaction.appPermissions?.has("UseExternalEmojis"), - strings.bot.commands.permissions.body.external_emojis, - ], - ]; - const body = permissions - .map(([hasPermission, description]) => { - if (hasPermission) { - return `${isFridayThe13th ? "😇" : "✅"} ${description}`; - } else { - return `${isFridayThe13th ? "💀" : "❌"} ${description}`; - } - }) - .join("\n"); - - return literals(strings.bot.commands.permissions.body.title, { body }); - }, - }); -}); diff --git a/packages/bot/src/commands/ping.ts b/packages/bot/src/commands/ping.ts deleted file mode 100644 index e73a09fef..000000000 --- a/packages/bot/src/commands/ping.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -export const pingCommand = new Promise((resolve) => { - resolve({ - handlesInteraction: true, - - command: createLocalizedCommand("ping", [ - { subcommand: "blitzkit" }, - { subcommand: "wotb" }, - ]), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const subcommand = interaction.options.getSubcommand(); - const executionStart = Date.now(); - - if (subcommand === "blitzkit") { - await interaction.editReply(strings.bot.commands.ping.body.pong); - } else { - await fetch("https://api.wotblitz.com/"); - } - - const executionTime = Date.now() - executionStart; - interaction.editReply( - literals(strings.bot.commands.ping.body.pong_with_time, { - time: executionTime.toFixed(0), - }) - ); - }, - }); -}); diff --git a/packages/bot/src/commands/playerInfo.ts b/packages/bot/src/commands/playerInfo.ts deleted file mode 100644 index 2b4c61268..000000000 --- a/packages/bot/src/commands/playerInfo.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { getAccountInfo } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import markdownEscape from "markdown-escape"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { embedInfo } from "../core/discord/embedInfo"; -import { markdownTable } from "../core/discord/markdownTable"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import { CommandRegistry } from "../events/interactionCreate"; - -export const playerInfoCommand = new Promise((resolve) => { - resolve({ - command: - createLocalizedCommand("player-info").addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const account = await resolvePlayerFromCommand(interaction); - const { id, region: region } = account; - const accountInfo = await getAccountInfo(region, id); - - return embedInfo( - literals(strings.bot.commands.player_info.body.title, { - name: markdownEscape(accountInfo.nickname), - }), - - markdownTable([ - [ - strings.bot.commands.player_info.body.nickname, - `${accountInfo.nickname}`, - ], - [ - strings.bot.commands.player_info.body.battles, - `${accountInfo.statistics.all.battles}`, - ], - [ - strings.bot.commands.player_info.body.winrate, - `${( - 100 * - (accountInfo.statistics.all.wins / - accountInfo.statistics.all.battles) - ).toFixed(2)}%`, - ], - [], - [ - strings.bot.commands.player_info.body.account_id, - `${accountInfo.account_id}`, - ], - [ - strings.bot.commands.player_info.body.created, - new Date(accountInfo.created_at * 1000).toLocaleDateString( - interaction.locale - ), - ], - [ - strings.bot.commands.player_info.body.last_battle, - new Date(accountInfo.last_battle_time * 1000).toLocaleDateString( - interaction.locale - ), - ], - ]) - ); - }, - - autocomplete: autocompleteUsername, - }); -}); diff --git a/packages/bot/src/commands/rating.tsx b/packages/bot/src/commands/rating.tsx deleted file mode 100644 index 16b1c7feb..000000000 --- a/packages/bot/src/commands/rating.tsx +++ /dev/null @@ -1,535 +0,0 @@ -import { - deltaBkrlBlitzStats, - emblemURL, - getAccountInfo, - getArchivedRatingLeaderboard, - getClanAccountInfo, - getLeagueFromScore, - getRatingInfo, - getRatingNeighbors, - imgur, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { SlashCommandSubcommandBuilder } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { Glow } from "../components/AllStatsOverview/components/HeroStat/components/Glow"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { DeltaCaret } from "../components/DeltaCaret"; -import { TitleBar } from "../components/TitleBar"; -import { normalizeLeagueIcon } from "../core/blitz/normalizeLeagueIcon"; -import { iconPng } from "../core/blitzkit/iconPng"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; -import { theme } from "../stitches.config"; - -const LEAGUE_ACCENT = [ - "_purple", - "_sand", - "_amber", - "_mauve", - "_brown", -] as const; - -export const ratingCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("rating", [ - { - subcommand: "today", - modify(option: SlashCommandSubcommandBuilder) { - option.addStringOption(addUsernameChoices); - }, - }, - { - subcommand: "season", - modify(option: SlashCommandSubcommandBuilder) { - option.addStringOption(addUsernameChoices); - }, - }, - ]), - - async handler(interaction) { - const subcommand = interaction.options.getSubcommand(true) as - | "season" - | "today"; - const { strings } = translator(interaction.locale); - const { id, region } = await resolvePlayerFromCommand(interaction); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - - const ratingInfo = await getRatingInfo(region); - - if (ratingInfo.detail) { - throw new Error(`Rating info not available for ${region}`); - } - - const leaderboard = await getArchivedRatingLeaderboard( - region, - ratingInfo.current_season - (subcommand === "today" ? 0 : 1) - ); - - if (leaderboard.version!.$case === "v1") { - throw new Error( - "Encountered bkrl minimal format in latest season. Wait till next cron?" - ); - } - - const accountInfo = await getAccountInfo(region, id, [ - "statistics.rating", - ]); - const statsIndex = leaderboard.version!.value.entries.findIndex( - (entry) => entry.id === id - ); - const statsA = leaderboard - ? leaderboard.version!.value.entries[statsIndex!] - : null; - const positionA = leaderboard ? statsIndex! + 1 : undefined; - const statsB1 = accountInfo.statistics.rating!; - const statsB2Array = (await getRatingNeighbors(region, id, 0)).neighbors; - - if (statsB2Array === undefined) { - return literals(strings.bot.commands.rating.errors.no_participation, { - name: markdownEscape(accountInfo.nickname), - }); - } - - const [statsB2] = statsB2Array; - const league = getLeagueFromScore(statsB2.score); - - if (!statsA) { - return strings.bot.commands.rating.errors.unrecorded_stats; - } - if (!statsB1) { - return literals(strings.bot.commands.rating.errors.no_participation, { - name: markdownEscape(accountInfo.nickname), - }); - } - - const reward = ratingInfo.rewards.find( - (reward) => - statsB2.number >= reward.from_position && - statsB2.number <= reward.to_position - ); - const positionDelta = leaderboard - ? statsB2.number - positionA! - : undefined; - const delta = deltaBkrlBlitzStats( - statsA, - accountInfo.statistics.rating!, - statsB2 - ); - const leagueTheme = LEAGUE_ACCENT[league.index]; - let rewardImage = reward - ? reward.type === "vehicle" - ? reward.vehicle.preview_image_url - : reward.stuff.image_url - : undefined; - - if (rewardImage) { - const { ok } = await fetch(rewardImage); - - if (!ok) { - rewardImage = imgur("kP88vqr"); - } - } - - return ( - - - -
-
- {reward === undefined && ( - - )} - -
- {`League - -
-
- - {statsB2.score.toLocaleString(interaction.locale)} - - {delta.score !== 0 && ( - <> - - 0 - ? theme.colors.textLowContrast_green - : theme.colors.textLowContrast_red, - fontSize: 16, - }} - > - {Math.abs(delta.score).toLocaleString( - interaction.locale - )} - - - )} -
- -
- - #{statsB2.number.toLocaleString(interaction.locale)} - - {positionDelta !== undefined && positionDelta !== 0 && ( - <> - - 0 - ? theme.colors.textLowContrast_red - : theme.colors.textLowContrast_green, - fontSize: 16, - }} - > - {Math.abs(positionDelta).toLocaleString( - interaction.locale - )} - - - )} -
-
-
- - {reward && ( -
-
-
- - {reward.type === "vehicle" - ? reward.vehicle.user_string - : reward.stuff.title} - -
- -
- - Reward - {reward.type === "stuff" ? ` x${reward.count}` : ""} - -
-
- - -
- )} - - {reward === undefined && ( - - )} -
- - {/* separated because fragment middle man invalidates relative parent: https://github.com/vercel/satori/issues/605 */} - {reward && ( - - )} - {reward && ( - - )} -
- - {delta.battles > 0 && ( -
-
- - {delta.battles.toLocaleString(interaction.locale)} - - - Battles - -
-
- - {(100 * (delta.wins / delta.battles)).toFixed(0)}% - - - Winrate - -
-
- - {Math.round(delta.damage / delta.battles).toLocaleString( - interaction.locale - )} - - - Damage - -
-
- - {(100 * (delta.survived / delta.battles)).toFixed(0)}% - - - Survival - -
-
- - {(delta.kills / delta.battles).toFixed(2)} - - - Kills - -
-
- )} -
- ); - }, - }); -}); diff --git a/packages/bot/src/commands/ratingLeaderboard.tsx b/packages/bot/src/commands/ratingLeaderboard.tsx deleted file mode 100644 index 458e70d52..000000000 --- a/packages/bot/src/commands/ratingLeaderboard.tsx +++ /dev/null @@ -1,468 +0,0 @@ -import { - FIRST_MINIMAL_ARCHIVED_RATING_SEASON, - getAccountInfo, - getArchivedLatestSeasonNumber, - getArchivedRatingLeaderboard, - getClanAccountInfo, - getRatingInfo, - getRatingLeague, - getRatingNeighbors, - imgur, - isOnGoingRatingSeason, - LEAGUES, - type RatingInfo, - RatingLeaderboard, - type Region, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { - type APIApplicationCommandOptionChoice, - Locale, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import { range } from "lodash-es"; -import markdownEscape from "markdown-escape"; -import { CommandWrapper } from "../components/CommandWrapper"; -import * as Leaderboard from "../components/Leaderboard"; -import { TitleBar } from "../components/TitleBar"; -import { UserError } from "../core/blitzkit/userError"; -import { addRegionChoices } from "../core/discord/addRegionChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -interface SimplifiedPlayer { - id: number; - score: number; - position: number; - clan: string | undefined; - nickname: string; -} - -const DEFAULT_LIMIT = 10; - -export const ratingLeaderboardCommand = new Promise( - async (resolve) => { - const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); - const onGoingSeason = await isOnGoingRatingSeason(); - const seasonNumbers = range( - FIRST_MINIMAL_ARCHIVED_RATING_SEASON, - latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1 - ).slice(-25); - - function addOptions(option: SlashCommandSubcommandBuilder) { - const { strings } = translator(Locale.EnglishUS); - - return option - .addIntegerOption((option) => - option - .setName(strings.bot.commands.rating_leaderboard.options.limit.name) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.limit.name, - undefined, - true - ) - ) - .setDescription( - literals( - strings.bot.commands.rating_leaderboard.options.limit - .description, - { count: DEFAULT_LIMIT } - ) - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.limit - .description, - { count: DEFAULT_LIMIT } - ) - ) - .setRequired(false) - .setMinValue(5) - .setMaxValue(30) - ) - .addStringOption((option) => - option - .setName( - strings.bot.commands.rating_leaderboard.options.season.name - ) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.season.name, - undefined, - true - ) - ) - .setDescription( - strings.bot.commands.rating_leaderboard.options.season.description - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.season - .description - ) - ) - .setRequired(false) - .addChoices( - ...seasonNumbers - .map((number, index) => { - const current = - index === seasonNumbers.length - 1 && onGoingSeason; - - return { - name: current - ? literals( - strings.bot.commands.rating_leaderboard.options.season - .choices.current, - { season: number } - ) - : `${number}`, - value: `${number}`, - name_localizations: current - ? localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options - .season.choices.current, - { season: number } - ) - : undefined, - } satisfies APIApplicationCommandOptionChoice; - }) - .reverse() - ) - ); - } - - resolve({ - command: createLocalizedCommand("rating-leaderboard", [ - { - subcommand: "neighbors", - modify(option: SlashCommandSubcommandBuilder) { - option.addStringOption(addUsernameChoices); - addOptions(option); - }, - }, - { - subcommand: "league", - modify(option: SlashCommandSubcommandBuilder) { - option - .addStringOption((option) => { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName( - strings.bot.commands.rating_leaderboard.options.league.name - ) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.league - .name, - undefined, - true - ) - ) - .setDescription( - strings.bot.commands.rating_leaderboard.options.league - .description - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.league - .description - ) - ) - .addChoices( - ...LEAGUES.map( - ({ name }, value) => - ({ - name: ( - strings.common.leagues as Record - )[name], - value: `${value}`, - name_localizations: localizationObject( - (strings) => - ( - strings.common.leagues as Record - )[name] - ), - } satisfies APIApplicationCommandOptionChoice) - ) - ) - .setRequired(true); - }) - .addStringOption(addRegionChoices); - addOptions(option); - }, - }, - ]), - - // BIG TODO: title seems to be in english - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const subcommand = interaction.options.getSubcommand(true) as - | "league" - | "neighbors"; - const limit = interaction.options.getInteger("limit") ?? DEFAULT_LIMIT; - const seasonOption = interaction.options.getString("season"); - const season = seasonOption - ? parseInt(seasonOption) - : onGoingSeason - ? latestArchivedSeasonNumber + 1 - : latestArchivedSeasonNumber; - const isCurrentSeason = season === latestArchivedSeasonNumber + 1; - let titleName: string; - let titleImage: string | undefined; - let titleDescription: string; - let playersAfter: number; - let players: SimplifiedPlayer[] | undefined; - let archivedLeaderboard: RatingLeaderboard | null; - let playerId: number | undefined; - let regionRatingInfo: RatingInfo | null; - - if (subcommand === "league") { - const leagueIndex = parseInt( - interaction.options.getString("league")! - ); - const region = interaction.options.getString("region") as Region; - regionRatingInfo = await (isCurrentSeason - ? getRatingInfo(region) - : null); - - if ( - regionRatingInfo !== null && - regionRatingInfo.detail !== undefined - ) { - return strings.bot.commands.rating_leaderboard.body - .no_ongoing_season; - } - - const result = isCurrentSeason - ? await getRatingLeague(region, leagueIndex).then(async (result) => - result.slice(0, limit).map( - (player) => - ({ - id: player.spa_id, - score: player.score, - position: player.number, - clan: player.clan_tag, - nickname: player.nickname, - } satisfies SimplifiedPlayer) - ) - ) - : await getArchivedRatingLeaderboard(region, season!).then( - async (players) => { - const firstPlayerIndex = - leagueIndex === 0 - ? 0 - : players.version!.value.entries.findIndex( - (player) => - player.score < LEAGUES[leagueIndex - 1].minScore - ); - const lastPlayerIndex = firstPlayerIndex + limit - 1; - const trimmed = players.version!.value.entries.slice( - firstPlayerIndex, - lastPlayerIndex + 1 - ); - const clanData = await getClanAccountInfo( - region, - trimmed.map(({ id }) => id), - ["clan"] - ); - - return trimmed.map( - (player, index) => - ({ - id: player.id, - score: player.score, - position: firstPlayerIndex + index + 1, - clan: clanData[player.id]?.clan?.tag, - nickname: - clanData[index]?.account_name ?? - literals( - strings.bot.commands.rating_leaderboard.body - .deleted_player, - { id: player.id } - ), - } satisfies SimplifiedPlayer) - ); - } - ); - const leagueInfo = LEAGUES[leagueIndex]; - archivedLeaderboard = await getArchivedRatingLeaderboard( - region, - season - ); - - players = result; - playersAfter = - (regionRatingInfo === null - ? archivedLeaderboard.version!.value.entries.length - : regionRatingInfo.count) - result[result.length - 1].position; - titleName = `${ - leagueInfo - ? (strings.common.leagues as Record)[ - LEAGUES[leagueInfo.index].name - ] - : "" - } - ${strings.common.regions.short[region]}`; - titleImage = imgur(leagueInfo.icon); - titleDescription = literals( - strings.bot.commands.rating_leaderboard.body.top_players, - { count: limit } - ); - } else { - const { region, id } = await resolvePlayerFromCommand(interaction); - regionRatingInfo = isCurrentSeason - ? await getRatingInfo(region) - : null; - - if ( - regionRatingInfo !== null && - regionRatingInfo.detail !== undefined - ) { - return strings.bot.commands.rating_leaderboard.body - .no_ongoing_season; - } - - const accountInfo = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const neighbors = isCurrentSeason - ? await getRatingNeighbors(region, id, Math.round(limit / 2)).then( - (players) => - players.neighbors.map( - (player) => - ({ - id: player.spa_id, - position: player.number, - score: player.score, - clan: player.clan_tag, - nickname: player.nickname, - } satisfies SimplifiedPlayer) - ) - ) - : await getArchivedRatingLeaderboard(region, season!).then( - async (players) => { - const playerIndex = players.version!.value.entries.findIndex( - (player) => player.id === id - ); - - if (playerIndex === -1) { - throw new UserError( - literals( - strings.bot.commands.rating_leaderboard.body - .no_participation, - { player: markdownEscape(accountInfo.nickname), season } - ) - ); - } - - const halfRange = Math.round(limit / 2); - const trimmed = players.version!.value.entries.slice( - playerIndex - halfRange, - playerIndex + halfRange + 1 - ); - const clanData = await getClanAccountInfo( - region, - trimmed.map((player) => player.id), - ["clan"] - ); - - return trimmed.map((player, index) => ({ - id: player.id, - score: player.score, - position: playerIndex + index + 1, - clan: clanData[index]?.clan?.tag, - nickname: clanData[index] - ? clanData[index]!.account_name - : `Deleted ${player.id}`, - })); - } - ); - archivedLeaderboard = await getArchivedRatingLeaderboard( - region, - season - ); - - players = neighbors; - titleImage = clan - ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png` - : undefined; - titleName = neighbors ? accountInfo.nickname : ""; - titleDescription = - strings.bot.commands.rating_leaderboard.body.neighbors; - playersAfter = - (regionRatingInfo === null - ? archivedLeaderboard.version!.value.entries.length - : regionRatingInfo.count) - - neighbors[neighbors.length - 1].position; - playerId = id; - } - - const items = players.map((player) => { - const midnightIndex = - archivedLeaderboard?.version!.value.entries.findIndex( - (item) => item.id === player.id - ); - const midnightScore = - midnightIndex === undefined - ? player.score - : archivedLeaderboard!.version!.value.entries[midnightIndex] - .score; - const midnightPosition = - midnightIndex === undefined ? player.position : midnightIndex + 1; - - return ( - - ); - }); - - return ( - - - - {items && ( - - {items} - {playersAfter > 0 && ( - - )} - - )} - - ); - }, - - autocomplete: autocompleteUsername, - }); - } -); diff --git a/packages/bot/src/commands/replay.tsx b/packages/bot/src/commands/replay.tsx deleted file mode 100644 index ed6da65fc..000000000 --- a/packages/bot/src/commands/replay.tsx +++ /dev/null @@ -1,592 +0,0 @@ -import { - calculateWN8, - emblemURL, - getClanAccountInfo, - idToRegion, - tankIcon, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { clamp } from "lodash-es"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { TitleBar } from "../components/TitleBar"; -import { iconPng } from "../core/blitzkit/iconPng"; -import { - averageDefinitions, - mapDefinitions, - tankDefinitions, -} from "../core/blitzkit/nonBlockingPromises"; -import { RenderConfiguration } from "../core/blitzkit/renderConfiguration"; -import { buttonLink } from "../core/discord/buttonLink"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; -import { theme } from "../stitches.config"; - -type WotInspectorReplaySubmission = - | { status: "error"; data: unknown; error: { message: string } } - | { - status: "ok"; - data: { - download_url: string; - view_url: string; - summary: { - battle_start_time: string; - map_name: string; - battle_start_timestamp: number; - battle_duration: number; - title: string; - arena_unique_id: string; - allies: number[]; - enemies: number[]; - protagonist: number; - player_name: string; - vehicle: string; - exp_base: number; - exp_total: number; - credits_base: number; - credits_total: number; - room_type: number; - battle_type: number; - mastery_badge: number; - battle_result: number; - vehicle_tier: number; - vehicle_type: number; - protagonist_team: number; - winner_team: number; - details: { - achievements: unknown[]; - base_capture_points: number; - base_defend_points: number; - chassis_id: unknown | null; - clan_tag: string; - clanid: number; - credits: number; - damage_assisted: number; - damage_assisted_track: number; - damage_blocked: unknown | null; - damage_made: number; - damage_received: number; - dbid: number; - death_reason: number; - distance_travelled: number; - enemies_damaged: number; - enemies_destroyed: number; - enemies_spotted: number; - entity_id: number; - exp: number; - exp_for_assist: number; - exp_for_damage: number; - exp_team_bonus: number; - gun_id: unknown | null; - hero_bonus_credits: number; - hero_bonus_exp: number; - hitpoints_left: number; - hits_bounced: number; - hits_pen: number; - hits_received: number; - hits_splash: number; - killed_by: number; - name: string; - shots_hit: number; - shots_made: number; - shots_pen: number; - shots_splash: number; - squad_index: number; - team: number; - time_alive: number; - turret_id: unknown | null; - vehicle_descr: number; - wp_points_earned: number; - wp_points_stolen: number; - }; - }; - }; - error: {}; - }; - -interface WotInspectorReplay { - id: string; - map_id: number; - battle_duration: number; - title: unknown | null; - player_name: string; - protagonist: number; - vehicle_descr: number; - mastery_badge: number; - exp_base: number; - enemies_spotted: number; - enemies_destroyed: number; - damage_assisted: number; - damage_made: number; - details_url: string; - download_url: string; - game_version: { - name: string; - package: string; - }; - arena_unique_id: string; - download_count: number; - data_version: number; - private: boolean; - private_clan: boolean; - battle_start_time: Date; - upload_time: Date; - allies: number[]; - enemies: number[]; - protagonist_clan: number; - protagonist_team: number; - battle_result: number; - credits_base: number; - tags: number[]; - battle_type: number; - room_type: number; - last_accessed_time: Date; - winner_team: number; - finish_reason: number; - players_data: WotInspectorReplayPlayerData[]; - exp_total: number; - credits_total: number; - repair_cost: number; - exp_free: number; - exp_free_base: number; - exp_penalty: number; - credits_penalty: number; - credits_contribution_in: number; - credits_contribution_out: number; - camouflage_id: number; -} - -export interface WotInspectorReplayPlayerData { - team: number; - name: string; - entity_id: number; - dbid: number; - clanid: number | null; - clan_tag: null | string; - hitpoints_left: number; - credits: number; - exp: number; - shots_made: number; - shots_hit: number; - shots_splash: number; - shots_pen: number; - damage_made: number; - damage_received: number; - damage_assisted: number; - damage_assisted_track: number; - hits_received: number; - hits_bounced: number; - hits_splash: number; - hits_pen: number; - enemies_spotted: number; - enemies_damaged: number; - enemies_destroyed: number; - time_alive: number; - distance_travelled: number; - killed_by: number; - base_capture_points: number; - base_defend_points: number; - exp_for_damage: number; - exp_for_assist: number; - exp_team_bonus: number; - wp_points_earned: number; - wp_points_stolen: number; - hero_bonus_credits: number; - hero_bonus_exp: number; - death_reason: number; - achievements: any[]; - vehicle_descr: number; - turret_id: number; - gun_id: number; - chassis_id: number; - squad_index: number; - damage_blocked: number; -} - -function stat(value: string | number, flex = 2) { - return ( -
- {value} -
- ); -} - -async function playerListing( - locale: Locale, - player: WotInspectorReplayPlayerData, - protagonistTeam: boolean, - winningTeam: number, - recordingPlayer = false, -) { - const { unwrap } = translator(locale); - const awaitedTankAverages = await averageDefinitions; - const awaitedTankDefinitions = await tankDefinitions; - const tank = awaitedTankDefinitions.tanks[player.vehicle_descr]; - const blockAccent = recordingPlayer ? "_amber" : ""; - const healthAccent = protagonistTeam - ? recordingPlayer - ? "_amber" - : "_green" - : "_red"; - - // our turret ids are always off by 1 or 2 lol so imma just grab the closest - // TODO: 💀 - const turret = tank.turrets.sort( - (a, b) => - Math.abs(a.id - player.turret_id) - Math.abs(b.id - player.turret_id), - )[0]; - const totalHealth = turret.health + tank.health; - const healthLeft = clamp(player.hitpoints_left / totalHealth, 0, 1); - const tankAveragesEntry = awaitedTankAverages.averages[player.vehicle_descr]; - const wn8 = - tankAveragesEntry === undefined - ? -1 - : calculateWN8(tankAveragesEntry.mu, { - battles: 1, - damage_dealt: player.damage_made, - dropped_capture_points: player.base_defend_points, - frags: player.enemies_destroyed, - spotted: player.enemies_spotted, - wins: winningTeam === player.team ? 1 : 0, - }); - - return ( -
-
- {unwrap(tank.name!)} - -
-
- - {TIER_ROMAN_NUMERALS[tank.tier]} - - - {unwrap(tank.name!)} - -
- - - {player.name} - {player.clan_tag ? ` [${player.clan_tag}]` : ""} - -
-
- -
- {stat(player.damage_made.toLocaleString())} - {stat(wn8 === -1 ? "--" : Math.round(wn8).toLocaleString())} - {stat(player.exp.toLocaleString())} - {stat(player.enemies_destroyed, 1)} -
- -
-
-
-
- ); -} - -function Header() { - return ( -
-
- -
-
- - Damage - -
-
- - WN8 - -
-
- - XP - -
-
- - Kills - -
-
-
- ); -} - -export const replayCommand = new Promise((resolve) => { - const { strings } = translator(Locale.EnglishUS); - - resolve({ - command: createLocalizedCommand("replay").addAttachmentOption((option) => - option - .setName(strings.bot.commands.replay.options.file.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands.replay.options.file.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.commands.replay.options.file.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.commands.replay.options.file.description, - ), - ) - .setRequired(true), - ), - - async handler(interaction) { - const awaitedMapDefinitions = await mapDefinitions; - const file = interaction.options.getAttachment("file")!; - const submissionData = (await fetch( - `https://wotinspector.com/api/replay/upload/?url=${encodeURIComponent( - file.url, - )}`, - ).then((response) => response.json())) as WotInspectorReplaySubmission; - - if (submissionData.status === "error") { - return `# Uh oh! WoT Inspector didn't like that.\n\nThere was an error processing your replay. Get help on [the official Discord server](https://discord.gg/nDt7AjGJQH).`; - } - - const replayId = submissionData.data.download_url.split("/").at(-1)!; - const replayData = (await fetch( - `https://api.wotinspector.com/v2/blitz/replays/${replayId}/`, - ).then((response) => response.json())) as WotInspectorReplay; - const antagonistTeamId = 3 - replayData.protagonist_team; - const players = replayData.players_data.sort( - (a, b) => b.damage_made - a.damage_made, - ); - const clan = ( - await getClanAccountInfo( - idToRegion(replayData.protagonist), - replayData.protagonist, - ["clan"], - ) - )?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - - return [ - new RenderConfiguration({ width: 960 }), - - - -
-
-
- - {await Promise.all( - players - .filter( - (player) => player.team === replayData.protagonist_team, - ) - .map((player) => - playerListing( - interaction.locale, - player, - true, - replayData.winner_team, - player.dbid === replayData.protagonist, - ), - ), - )} -
- -
-
- - {await Promise.all( - players - .filter((player) => player.team === antagonistTeamId) - .map((player) => - playerListing( - interaction.locale, - player, - false, - replayData.winner_team, - ), - ), - )} -
-
-
, - buttonLink(submissionData.data.view_url, "WoT Inspector"), - buttonLink(submissionData.data.download_url, "Download"), - ]; - }, - }); -}); diff --git a/packages/bot/src/commands/research.tsx b/packages/bot/src/commands/research.tsx deleted file mode 100644 index e0a987044..000000000 --- a/packages/bot/src/commands/research.tsx +++ /dev/null @@ -1,626 +0,0 @@ -import { - asset, - emblemURL, - equipmentPriceMatrix, - getAccountInfo, - getClanAccountInfo, - getTankStats, - TANK_ICONS, - TankType, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { escapeMarkdown, Locale } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { TitleBar } from "../components/TitleBar"; -import { resolveTankId } from "../core/blitz/resolveTankId"; -import { buildTechTreeLine } from "../core/blitzkit/buildTechTreeLine"; -import { iconPng } from "../core/blitzkit/iconPng"; -import { tankDefinitions } from "../core/blitzkit/nonBlockingPromises"; -import { resolveAncestry } from "../core/blitzkit/resolveAncestry"; -import { tankIconPng } from "../core/blitzkit/tankIconPng"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; -import { theme } from "../stitches.config"; - -export const researchCommand = new Promise((resolve) => { - const { strings } = translator(Locale.EnglishUS); - - resolve({ - command: createLocalizedCommand("research") - .addStringOption((option) => - option - .setName(strings.bot.commands.research.options.target_tank.name!) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.target_tank.name, - undefined, - true, - ), - ) - .setDescription( - strings.bot.commands.research.options.target_tank.description, - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.target_tank.description, - ), - ) - .setAutocomplete(true) - .setRequired(true), - ) - .addStringOption((option) => - option - .setName(strings.bot.commands.research.options.starting_tank.name!) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.starting_tank.name, - undefined, - true, - ), - ) - .setDescription( - strings.bot.commands.research.options.starting_tank.description, - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.starting_tank.description, - ), - ) - .setAutocomplete(true) - .setRequired(false), - ) - .addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings, unwrap } = translator(interaction.locale); - const awaitedTankDefinitions = await tankDefinitions; - const targetTankId = await resolveTankId( - interaction.options.getString("target-tank", true), - interaction.locale, - true, - ); - const startingTankRaw = interaction.options.getString("starting-tank"); - const targetTank = awaitedTankDefinitions.tanks[targetTankId]; - const { id, region } = await resolvePlayerFromCommand(interaction); - let startingTankId: number; - const targetTankAncestry = await resolveAncestry(targetTankId); - - if (startingTankRaw) { - startingTankId = await resolveTankId( - startingTankRaw, - interaction.locale, - true, - ); - - if (targetTankId === startingTankId) { - return literals( - strings.bot.commands.research.errors.start_end_equal, - { tank: escapeMarkdown(unwrap(targettank.name!)) }, - ); - } - - const startingTank = awaitedTankDefinitions.tanks[startingTankId]; - - if (startingTank.tier > targetTank.tier) { - return literals(strings.bot.commands.research.errors.unordered, { - tank_1: escapeMarkdown(unwrap(startingtank.name!)), - tank_2: escapeMarkdown(unwrap(targettank.name!)), - }); - } - - if (!targetTankAncestry.includes(startingTankId)) { - return literals( - strings.bot.commands.research.errors.tanks_not_on_line, - { - tank_1: escapeMarkdown(unwrap(targettank.name!)), - tank_2: escapeMarkdown(unwrap(startingtank.name!)), - }, - ); - } - } else { - if (targetTank.type !== TankType.TANK_TYPE_RESEARCHABLE) { - return literals(strings.bot.commands.research.errors.non_tech_tree, { - tank: escapeMarkdown(unwrap(targettank.name!)), - }); - } - - if (targetTank.tier === 1) { - return literals(strings.bot.commands.research.errors.tier_1, { - tank: escapeMarkdown(unwrap(targettank.name!)), - }); - } - - const tankStats = await getTankStats(region, id); - - if (tankStats === null) { - return strings.bot.common.errors.no_tank_stats; - } - - if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) { - return literals( - strings.bot.commands.research.errors.already_researched, - { tank: escapeMarkdown(unwrap(targettank.name!)) }, - ); - } - - const foundAncestor = targetTankAncestry.find( - (id) => - awaitedTankDefinitions.tanks[id].tier === 1 || - tankStats.some(({ tank_id }) => tank_id === id), - ); - - if (foundAncestor === undefined) { - throw new Error("No first owned tech tree found."); - } - - startingTankId = foundAncestor; - } - - const techTreeLine = await buildTechTreeLine( - startingTankId, - targetTankId, - ); - - const line = [...techTreeLine, startingTankId].reverse(); - const { nickname } = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - const costs = await Promise.all( - line.map(async (id) => { - const tank = awaitedTankDefinitions.tanks[id]; - const turretXps = new Map(); - const gunXps = new Map(); - const engineXps = new Map(); - const trackXps = new Map(); - - tank.turrets.forEach((turret) => { - if ( - turret.research_cost !== undefined && - typeof turret.research_cost.research_cost_type!.value === "number" - ) { - turretXps.set( - turret.id, - turret.research_cost.research_cost_type!.value, - ); - } - - turret.guns.forEach((gun) => { - if ( - gun.research_cost !== undefined && - typeof gun.research_cost.research_cost_type!.value === "number" - ) { - gunXps.set(gun.id, gun.research_cost.research_cost_type!.value); - } - }); - }); - - tank.engines.forEach((engine) => { - if ( - engine.research_cost !== undefined && - typeof engine.research_cost.research_cost_type!.value === "number" - ) { - engineXps.set( - engine.id, - engine.research_cost.research_cost_type!.value, - ); - } - }); - - tank.tracks.forEach((track) => { - if ( - track.research_cost !== undefined && - typeof track.research_cost.research_cost_type!.value === "number" - ) { - trackXps.set( - track.id, - track.research_cost.research_cost_type!.value, - ); - } - }); - - let moduleXp = 0; - turretXps.forEach((value) => { - moduleXp += value; - }); - gunXps.forEach((value) => { - moduleXp += value; - }); - engineXps.forEach((value) => { - moduleXp += value; - }); - trackXps.forEach((value) => { - moduleXp += value; - }); - - return { - research: tank.research_cost!.research_cost_type!.value as number, - upgrades: moduleXp, - purchase: tank.price!.value, - equipment: equipmentPriceMatrix[tank.tier].reduce( - (a, b) => a + 3 * b, - 0, - ), - }; - }), - ); - - const image = ( - - - -
-
-
- -
- - {strings.bot.commands.research.body.total} - -
- -
-
- XP icon - - Experience:{" "} - {costs - .reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0) - .toLocaleString(interaction.locale)} - -
- -
- Silver icon - - Purchase:{" "} - {costs - .reduce((a, b) => a + b.purchase, 0) - .toLocaleString(interaction.locale)} - -
- -
- Silver icon - - Equipment:{" "} - {costs - .reduce((a, b) => a + b.equipment, 0) - .toLocaleString(interaction.locale)} - -
-
-
- - {await Promise.all( - line.map(async (id, index) => { - const tank = awaitedTankDefinitions.tanks[id]; - - return ( -
-
- - {TIER_ROMAN_NUMERALS[tank.tier]} - -
- -
- Tank icon - -
- {unwrap(tank.name!)} - - - {unwrap(tank.name!)} - -
-
- -
-
- XP icon - - {literals( - strings.bot.commands.research.body.research, - { - cost: costs[index].research?.toLocaleString( - interaction.locale, - ), - }, - )} - -
- - {costs[index].upgrades > 0 && ( -
- Free XP icon - - {literals( - strings.bot.commands.research.body.upgrades, - { cost: costs[index].upgrades.toLocaleString() }, - )} - -
- )} - -
- Silver icon - - {literals( - strings.bot.commands.research.body.purchase, - { - cost: costs[index].purchase.toLocaleString( - interaction.locale, - ), - }, - )} - -
- -
- Silver icon - - {literals( - strings.bot.commands.research.body.equipment, - { - cost: costs[index].equipment.toLocaleString( - interaction.locale, - ), - }, - )} - -
-
-
- ); - }), - )} -
-
- ); - - return startingTankRaw - ? image - : [strings.bot.commands.research.body.estimation, image]; - }, - - autocomplete(interaction) { - autocompleteTanks(interaction, true, ["target-tank", "starting-tank"]); - }, - }); -}); diff --git a/packages/bot/src/commands/searchClans.ts b/packages/bot/src/commands/searchClans.ts deleted file mode 100644 index 45c0c6aee..000000000 --- a/packages/bot/src/commands/searchClans.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { type ClanList, fetchBlitz, type Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { addClanChoices } from "../core/discord/addClanChoices"; -import { addRegionChoices } from "../core/discord/addRegionChoices"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { embedInfo } from "../core/discord/embedInfo"; -import { localizationObject } from "../core/discord/localizationObject"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const DEFAULT_LIMIT = 25; - -export const searchClansCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("search-clans") - .addStringOption(addRegionChoices) - .addStringOption((option) => - addClanChoices(option).setAutocomplete(false) - ) - .addIntegerOption((option) => { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.commands.search_clans.options.limit.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands.search_clans.options.limit.name, - undefined, - true - ) - ) - .setDescription( - literals( - strings.bot.commands.search_clans.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.search_clans.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setMinValue(1) - .setMaxValue(100); - }), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const region = interaction.options.getString("region") as Region; - const clan = interaction.options.getString("clan")!; - const limit = interaction.options.getInteger("limit") ?? DEFAULT_LIMIT; - const clanList = await fetchBlitz(region, "clans/list", { - search: clan, - limit, - }); - - return embedInfo( - literals(strings.bot.commands.search_clans.body.title, { - search: markdownEscape(clan), - region: strings.common.regions.normal[region], - }), - clanList.length === 0 - ? strings.bot.commands.search_clans.body.no_results - : `\`\`\`\n${clanList - .map((clan) => `${clan.name} [${clan.tag}]`) - .join("\n")}\n\`\`\`` - ); - }, - }); -}); diff --git a/packages/bot/src/commands/searchPlayers.ts b/packages/bot/src/commands/searchPlayers.ts deleted file mode 100644 index 54bfa4dc1..000000000 --- a/packages/bot/src/commands/searchPlayers.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - type AccountList, - fetchBlitz, - type Region, - usernamePattern, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { addRegionChoices } from "../core/discord/addRegionChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { embedInfo } from "../core/discord/embedInfo"; -import { localizationObject } from "../core/discord/localizationObject"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const DEFAULT_LIMIT = 25; - -export const searchPlayersCommand = new Promise((resolve) => { - const { strings } = translator(Locale.EnglishUS); - - resolve({ - command: createLocalizedCommand("search-players") - .addStringOption(addRegionChoices) - .addStringOption((option) => - addUsernameChoices(option).setAutocomplete(false).setRequired(true) - ) - .addIntegerOption((option) => - option - .setName(strings.bot.commands.search_players.options.limit.name) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.search_players.options.limit.name, - undefined, - true - ) - ) - .setDescription( - literals( - strings.bot.commands.search_players.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.search_players.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setMinValue(1) - .setMaxValue(100) - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const region = interaction.options.getString("region") as Region; - const name = interaction.options.getString("username")!; - const limit = interaction.options.getInteger("limit") ?? 25; - const trimmedSearch = name.trim(); - const players = usernamePattern.test(trimmedSearch) - ? await fetchBlitz(region, "account/list", { - search: trimmedSearch, - limit, - }) - : []; - - return embedInfo( - literals(strings.bot.commands.search_players.body.title, { - search: markdownEscape(trimmedSearch), - region: strings.common.regions.normal[region], - }), - `\`\`\`${ - players.length === 0 - ? strings.bot.commands.search_players.body.no_results - : players.map((player) => player.nickname).join("\n") - }\`\`\`` - ); - }, - }); -}); diff --git a/packages/bot/src/commands/stats.tsx b/packages/bot/src/commands/stats.tsx deleted file mode 100644 index 38337cd09..000000000 --- a/packages/bot/src/commands/stats.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { emblemURL, getAccountInfo, getClanAccountInfo } from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { AllStatsOverview } from "../components/AllStatsOverview"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { NoData } from "../components/NoData"; -import { TitleBar } from "../components/TitleBar"; -import { filtersToDescription } from "../core/blitzkit/filtersToDescription"; -import { filterStats, type StatFilters } from "../core/blitzstars/filterStats"; -import { getStatsInPeriod } from "../core/blitzstars/getStatsInPeriod"; -import { addPeriodicFilterOptions } from "../core/discord/addPeriodicFilterOptions"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -async function render( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - filters: StatFilters, - locale: Locale, -) { - const { nickname } = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - const diffedTankStats = await getStatsInPeriod( - region, - id, - start, - end, - locale, - ); - const { stats, supplementary } = await filterStats(diffedTankStats, filters); - const filterDescriptions = await filtersToDescription(filters, locale); - - return ( - - - - {!stats.battles && } - {stats.battles > 0 && ( - - )} - - ); -} - -export const statsCommand = new Promise(async (resolve) => { - resolve({ - command: await addPeriodicFilterOptions( - createLocalizedCommand("stats"), - (option) => option.addStringOption(addUsernameChoices), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // ...filters, - // }); - - // return Promise.all([ - // render(player, period, filters, interaction.locale), - // buttonRefresh(interaction, path), - // getBlitzStarsLinkButton(player.region, player.id), - // ]); - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); - - return await render(player, period, filters, interaction.locale); - }, - }); -}); diff --git a/packages/bot/src/commands/today.ts b/packages/bot/src/commands/today.ts deleted file mode 100644 index 65256cad9..000000000 --- a/packages/bot/src/commands/today.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { gameDefinitions } from "../core/blitzkit/nonBlockingPromises"; -import { addFilterOptions } from "../core/discord/addFilterOptions"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; -import { renderBreakdown } from "./breakdown/render"; - -export const todayCommand = new Promise(async (resolve) => { - const awaitedGameDefinitions = await gameDefinitions; - - resolve({ - command: addFilterOptions( - createLocalizedCommand("today"), - awaitedGameDefinitions.nations, - ).addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand( - // player.region, - // interaction, - // 'today', - // ); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction, true), - // ...filters, - // }); - - // return [ - // ...(await renderBreakdown(player, period, filters, interaction.locale)), - // buttonRefresh(interaction, path), - // await getBlitzStarsLinkButton(player.region, player.id), - // ]; - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); - - return await renderBreakdown(player, period, filters, interaction.locale); - }, - }); -}); diff --git a/packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx b/packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx deleted file mode 100644 index 1a3a00277..000000000 --- a/packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { type ComponentProps } from "react"; - -export interface GlowProps extends ComponentProps<"div"> { - color: string; - direction?: "default" | "reverse"; - rotation?: number; - startOpacity?: number; - endOpacity?: number; -} - -const width = 16; - -export function Glow({ - color, - direction = "default", - rotation = 0, - style, - startOpacity = 0, - endOpacity = 1, - ...props -}: GlowProps) { - const color1 = `${color}${Math.round(endOpacity * 255) - .toString(16) - .padStart(2, "0")}`; - const color2 = `${color}${Math.round(startOpacity * 255) - .toString(16) - .padStart(2, "0")}`; - const background = `linear-gradient(${ - direction === "default" ? 0 : 180 - }deg, ${color1} 0%, ${color2} 100%)`; - - return ( -
-
-
-
-
-
-
- ); -} diff --git a/packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx b/packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx deleted file mode 100644 index be3b5a0f2..000000000 --- a/packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { theme } from '../../../../stitches.config'; -import { Glow } from './components/Glow'; - -export interface WN8DisplayProps { - subtitle: string; - color: string; - value: string | number; -} - -export function HeroStat({ color, subtitle, value }: WN8DisplayProps) { - return ( -
- - -
-
- - {value} - - - {subtitle} - -
-
- - -
- ); -} diff --git a/packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx b/packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx deleted file mode 100644 index 62db93c41..000000000 --- a/packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { theme } from '../../../stitches.config'; - -export enum WN8SurroundingStatAlign { - Left, - Right, -} - -export interface WN8SurroundingStatProps { - label: string; - value: string | number; - align?: WN8SurroundingStatAlign; - padded?: boolean; -} - -export function WN8SurroundingStat({ - label, - value, - align = WN8SurroundingStatAlign.Left, - padded = false, -}: WN8SurroundingStatProps) { - return ( -
- - {value} - - - {label} - -
- ); -} diff --git a/packages/bot/src/components/AllStatsOverview/index.tsx b/packages/bot/src/components/AllStatsOverview/index.tsx deleted file mode 100644 index 62073cb00..000000000 --- a/packages/bot/src/components/AllStatsOverview/index.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { - getWN8Percentile, - Percentile, - type SupplementaryStats, -} from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { translator } from '../../core/localization/translator'; -import { PERCENTILE_COLORS } from '../PercentileIndicator/constants'; -import { HeroStat } from './components/HeroStat'; -import { - WN8SurroundingStat, - WN8SurroundingStatAlign, -} from './components/WN8SurroundingStat'; - -export interface AllStatsOverviewProps { - stats: { - battles: number; - wins: number; - damage_dealt: number; - damage_received: number; - survived_battles: number; - hits: number; - shots: number; - frags: number; - }; - supplementaryStats: SupplementaryStats; - locale: Locale; -} - -export function AllStatsOverview({ - stats, - locale, - supplementaryStats, -}: AllStatsOverviewProps) { - const { strings } = translator(locale); - const percentile = - typeof supplementaryStats.WN8 === 'number' - ? getWN8Percentile(supplementaryStats.WN8) - : Percentile.VeryBad; - const color = PERCENTILE_COLORS[percentile]; - - const heroStat = - typeof supplementaryStats.WN8 === 'number' - ? supplementaryStats.WN8.toFixed(0) - : '--'; - - return ( -
-
- - - - -
- - - -
- - - - -
-
- ); -} diff --git a/packages/bot/src/components/Breakdown/components/Root.tsx b/packages/bot/src/components/Breakdown/components/Root.tsx deleted file mode 100644 index cbbe20573..000000000 --- a/packages/bot/src/components/Breakdown/components/Root.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { type ReactNode } from "react"; - -interface RootProps { - children: ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Breakdown/components/Row.tsx b/packages/bot/src/components/Breakdown/components/Row.tsx deleted file mode 100644 index a5af946d6..000000000 --- a/packages/bot/src/components/Breakdown/components/Row.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { - Percentile, - TankClass, - TankType, - TREE_TYPE_ICONS, -} from "@blitzkit/core"; -import { createColors } from "bepaint"; -import { - type AccentColor, - type GrayColor, - PALETTES, -} from "../../../core/radix/radixColors"; -import { RowStat } from "./RowStat"; - -export interface RowStatItem { - title: string; - current?: number | string; - career?: number | string; - delta?: number; - percentile?: Percentile; -} - -interface RowProps { - displayType?: "tank" | "summary"; - naked?: boolean; - title: string; - minimized?: boolean; - type?: TankType; - class?: TankClass; - color?: AccentColor | GrayColor; - stats: (RowStatItem | undefined)[]; -} - -export function Row({ - color = "slate", - stats, - title, - minimized, - class: tankClass, - type, - naked = false, - displayType, -}: RowProps) { - const palette = PALETTES[`${color}Dark`]; - const theme = createColors(palette); - - return ( -
-
- {displayType === "tank" && tankClass !== undefined && ( - {TankClass[tankClass]} - )} - - - {title} - -
- -
- {stats.map( - (row, index) => - row && ( - - ), - )} -
-
- ); -} diff --git a/packages/bot/src/components/Breakdown/components/RowStat.tsx b/packages/bot/src/components/Breakdown/components/RowStat.tsx deleted file mode 100644 index 430f8b6a6..000000000 --- a/packages/bot/src/components/Breakdown/components/RowStat.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Percentile } from "@blitzkit/core"; -import { createColors } from "bepaint"; -import { - type AccentColor, - type GrayColor, - PALETTES, -} from "../../../core/radix/radixColors"; -import { PercentileIndicator } from "../../PercentileIndicator"; - -interface RowStatProps { - name: string; - value?: string | number; - delta?: number; - percentile?: Percentile; - minimized?: boolean; - color?: AccentColor | GrayColor; -} - -export function RowStat({ - name, - value, - delta, - percentile, - minimized = false, - color = "slate", -}: RowStatProps) { - const theme = createColors(PALETTES[`${color}Dark`]); - - return ( -
-
- {percentile === undefined && delta !== undefined && delta !== 0 && ( - {delta 0 ? "Increase" : "Decrease"} - src={ - (delta ?? 0) > 0 - ? "https://i.imgur.com/qbjiXa1.png" - : "https://i.imgur.com/3uyNhun.png" - } - style={{ width: 12, height: 12 }} - /> - )} - {percentile !== undefined && ( - - )} - - - {value} - -
- - {!minimized && ( - - {name} - - )} -
- ); -} diff --git a/packages/bot/src/components/Breakdown/index.ts b/packages/bot/src/components/Breakdown/index.ts deleted file mode 100644 index bf85c5f20..000000000 --- a/packages/bot/src/components/Breakdown/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/Root'; -export * from './components/Row'; -export * from './components/RowStat'; diff --git a/packages/bot/src/components/CommandWrapper.tsx b/packages/bot/src/components/CommandWrapper.tsx deleted file mode 100644 index c4dd6b066..000000000 --- a/packages/bot/src/components/CommandWrapper.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { type ReactNode } from "react"; -import { theme } from "../stitches.config"; - -interface CommandWrapperProps { - children: ReactNode; - fat?: boolean; -} - -export function CommandWrapper({ children, fat }: CommandWrapperProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/DeltaCaret.tsx b/packages/bot/src/components/DeltaCaret.tsx deleted file mode 100644 index 15c1ae8aa..000000000 --- a/packages/bot/src/components/DeltaCaret.tsx +++ /dev/null @@ -1,21 +0,0 @@ -interface DeltaCaretProps { - delta: number; -} - -export function DeltaCaret({ delta }: DeltaCaretProps) { - return ( - {delta 0 ? 'Increase' : 'Decrease'} - style={{ - width: 16, - height: 16, - objectFit: 'contain', - }} - src={ - delta > 0 - ? 'https://i.imgur.com/gjI4a5f.png' - : 'https://i.imgur.com/lLGZygN.png' - } - /> - ); -} diff --git a/packages/bot/src/components/Flag.tsx b/packages/bot/src/components/Flag.tsx deleted file mode 100644 index 110b71f95..000000000 --- a/packages/bot/src/components/Flag.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { asset } from '@blitzkit/core'; - -interface FlagProps { - nation: string; -} - -export function Flag({ nation }: FlagProps) { - return ( -
- {nation} -
- ); -} diff --git a/packages/bot/src/components/GenericAllStats/components/Card.tsx b/packages/bot/src/components/GenericAllStats/components/Card.tsx deleted file mode 100644 index 2f0922c29..000000000 --- a/packages/bot/src/components/GenericAllStats/components/Card.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { theme } from '../../../stitches.config'; -import { PercentileIndicator } from '../../PercentileIndicator'; - -interface CardItem { - label: string; - value: string | number | undefined; - percentile?: Percentile; -} - -interface CardProps { - items: CardItem[]; - title: string; -} - -export function Card({ items, title }: CardProps) { - return ( -
- - {title} - - -
- {items.map((item) => ( -
- - {item.label} - - -
- -
- {item.percentile !== undefined && ( - - )} - - {item.value === undefined || Number.isNaN(item.value) - ? '--' - : item.value} - -
-
- ))} -
-
- ); -} diff --git a/packages/bot/src/components/GenericAllStats/components/Root.tsx b/packages/bot/src/components/GenericAllStats/components/Root.tsx deleted file mode 100644 index 5c362f44b..000000000 --- a/packages/bot/src/components/GenericAllStats/components/Root.tsx +++ /dev/null @@ -1,17 +0,0 @@ -interface RootProps { - children: React.ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/GenericAllStats/components/Row.tsx b/packages/bot/src/components/GenericAllStats/components/Row.tsx deleted file mode 100644 index bd5cfa17e..000000000 --- a/packages/bot/src/components/GenericAllStats/components/Row.tsx +++ /dev/null @@ -1,7 +0,0 @@ -interface RowProps { - children: React.ReactNode; -} - -export function Row({ children }: RowProps) { - return
{children}
; -} diff --git a/packages/bot/src/components/GenericAllStats/index.tsx b/packages/bot/src/components/GenericAllStats/index.tsx deleted file mode 100644 index 36b687132..000000000 --- a/packages/bot/src/components/GenericAllStats/index.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { - type BlitzStats, - getWN8Percentile, - isNumber, - type SupplementaryStats, -} from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { translator } from '../../core/localization/translator'; -import { Card } from './components/Card'; -import { Root } from './components/Root'; -import { Row } from './components/Row'; - -interface GenericAllStatsProps { - stats: BlitzStats; - supplementaryStats?: SupplementaryStats; - locale: Locale; -} - -export function GenericAllStats({ - stats, - supplementaryStats, - locale, -}: GenericAllStatsProps) { - const { strings } = translator(locale); - const damageRatio = stats.damage_dealt / stats.damage_received; - const killsToDeathRatio = - stats.frags / (stats.battles - stats.survived_battles); - - return ( - - - - - - - - - - - ); -} diff --git a/packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx b/packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx deleted file mode 100644 index 5346cde64..000000000 --- a/packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { type GenericStat } from ".."; -import { GenericStatsRow } from "./GenericStatsRow"; - -interface GenericStatsColumnProps { - stats: GenericStat[]; -} - -export function GenericStatsColumn({ stats }: GenericStatsColumnProps) { - return ( -
- {stats.map((stat) => ( - - ))} -
- ); -} diff --git a/packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx b/packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx deleted file mode 100644 index 1db1c6bda..000000000 --- a/packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { type GenericStat } from ".."; -import { theme } from "../../../stitches.config"; -import { PercentileIndicator } from "../../PercentileIndicator"; - -interface GenericStatsRowProps { - stat: GenericStat; -} - -export function GenericStatsRow({ stat }: GenericStatsRowProps) { - return ( -
- - {stat[0]} - - - - {stat[2] !== undefined && } - {stat[1]} - -
- ); -} diff --git a/packages/bot/src/components/GenericStats/index.tsx b/packages/bot/src/components/GenericStats/index.tsx deleted file mode 100644 index ae23b18e9..000000000 --- a/packages/bot/src/components/GenericStats/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { GenericStatsColumn } from './components/GenericStatsColumn'; - -export type GenericStat = - | [string, string | number | undefined] - | [string, string | number | undefined, Percentile | undefined]; - -export interface GenericStatsProps { - stats: GenericStat[]; - columnCount?: number; -} - -export function GenericStats({ stats, columnCount = 2 }: GenericStatsProps) { - const filteredStats = stats.filter(([, value]) => value !== undefined); - const itemsPerRow = Math.ceil(filteredStats.length / columnCount); - const columns: GenericStat[][] = []; - - filteredStats.forEach((stat, index) => { - const column = Math.floor(index / itemsPerRow); - if (!columns[column]) columns[column] = []; - columns[column].push(stat); - }); - - return ( -
- {columns.map((stats) => ( - stat[0]).join('-')} - stats={stats} - /> - ))} -
- ); -} diff --git a/packages/bot/src/components/Graph/components/Line/constants.ts b/packages/bot/src/components/Graph/components/Line/constants.ts deleted file mode 100644 index 74170a3a8..000000000 --- a/packages/bot/src/components/Graph/components/Line/constants.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum LineColor { - Red, - Green, - Blue, - White, -} -export enum LineStyle { - Solid, - Dashes, - DashesAndDots, -} diff --git a/packages/bot/src/components/Graph/components/Line/index.tsx b/packages/bot/src/components/Graph/components/Line/index.tsx deleted file mode 100644 index ad0beda82..000000000 --- a/packages/bot/src/components/Graph/components/Line/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { blue, green, red, slateDark } from '@radix-ui/colors'; -import { GRAPH_HEIGHT, GRAPH_WIDTH } from '../Root/constants'; -import { LineColor, LineStyle } from './constants'; - -const LINE_COLORS = { - [LineColor.Red]: red.red9, - [LineColor.Green]: green.green9, - [LineColor.Blue]: blue.blue9, - [LineColor.White]: slateDark.slate11, -}; - -const LINE_STYLES = { - [LineStyle.Solid]: undefined, - [LineStyle.Dashes]: '8, 8', - [LineStyle.DashesAndDots]: '1, 4, 8, 4', -}; - -export type PlotItem = [number, number]; -type Plot = PlotItem[]; - -interface LineProps { - plot: Plot; - minY?: number; - maxY?: number; - color?: LineColor; - style?: LineStyle; - lineWidth?: number; -} - -export function Line({ - plot, - minY: providedMinY, - maxY: providedMaxY, - color = LineColor.Red, - style = LineStyle.Solid, - lineWidth = 2, -}: LineProps) { - const xs = plot.map(([x]) => x); - const minX = Math.min(...xs); - const maxX = Math.max(...xs); - const ys = plot.map(([, y]) => y); - const minY = providedMinY ?? Math.min(...ys); - const maxY = providedMaxY ?? Math.max(...ys); - const rangeX = maxX - minX; - const rangeY = maxY - minY; - const points = plot - .map( - ([x, y]) => - `${(x - minX) * (GRAPH_WIDTH / rangeX)},${ - GRAPH_HEIGHT - (y - minY) * (GRAPH_HEIGHT / rangeY) - }`, - ) - .join(' '); - - return ( - - ); -} diff --git a/packages/bot/src/components/Graph/components/Margin/constants.ts b/packages/bot/src/components/Graph/components/Margin/constants.ts deleted file mode 100644 index fab153d6c..000000000 --- a/packages/bot/src/components/Graph/components/Margin/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum MarginOrientation { - Vertical, - Horizontal, -} diff --git a/packages/bot/src/components/Graph/components/Margin/index.tsx b/packages/bot/src/components/Graph/components/Margin/index.tsx deleted file mode 100644 index eb15c732c..000000000 --- a/packages/bot/src/components/Graph/components/Margin/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { times } from 'lodash-es'; -import { theme } from '../../../../stitches.config'; -import { type MarginInputProps } from '../Root'; -import { MarginOrientation } from './constants'; - -interface MarginProps extends MarginInputProps { - verticalSeparations: number; - orientation?: MarginOrientation; -} - -export function Margin({ - verticalSeparations, - max, - min, - suffix, - precision = 0, - orientation = MarginOrientation.Vertical, -}: MarginProps) { - const isVertical = orientation === MarginOrientation.Vertical; - - return ( -
- {times(verticalSeparations, (index) => ( - - {`${( - (max - min) * - ((verticalSeparations - (index + 1)) / - (verticalSeparations - 1)) + - min - ).toFixed(precision)}${suffix ?? ''}`} - - ))} -
- ); -} diff --git a/packages/bot/src/components/Graph/components/Root/constants.ts b/packages/bot/src/components/Graph/components/Root/constants.ts deleted file mode 100644 index 0ff4e7d6b..000000000 --- a/packages/bot/src/components/Graph/components/Root/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const GRAPH_WIDTH = 384; -export const GRAPH_HEIGHT = 320; diff --git a/packages/bot/src/components/Graph/components/Root/index.tsx b/packages/bot/src/components/Graph/components/Root/index.tsx deleted file mode 100644 index 86ac910f7..000000000 --- a/packages/bot/src/components/Graph/components/Root/index.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { times } from "lodash-es"; -import { type ReactNode } from "react"; -import { theme } from "../../../../stitches.config"; -import { Margin } from "../Margin"; -import { MarginOrientation } from "../Margin/constants"; -import { GRAPH_HEIGHT, GRAPH_WIDTH } from "./constants"; - -export interface MarginInputProps { - min: number; - max: number; - suffix?: string; - precision?: number; -} - -interface RootProps { - children: ReactNode; - verticalMargin?: MarginInputProps; - horizontalMargin?: MarginInputProps; - separations?: number; - xMinLabel: string; - xMaxLabel: string; - xTitle?: string; -} - -export function Root({ - children, - verticalMargin, - horizontalMargin, - separations = 5, - xMinLabel, - xMaxLabel, - xTitle, -}: RootProps) { - return ( -
-
- - {xMinLabel} - - - {xMaxLabel} - -
- -
- {verticalMargin && ( - - )} - - - {times(separations - 2, (index) => { - const y = GRAPH_HEIGHT * ((index + 1) / (separations - 1)); - - return ( - - ); - })} - - {children} - -
- - {horizontalMargin && ( - - )} - - {xTitle !== undefined && ( -
- - {xTitle} - -
- )} -
- ); -} diff --git a/packages/bot/src/components/Graph/index.ts b/packages/bot/src/components/Graph/index.ts deleted file mode 100644 index 5aaadb77e..000000000 --- a/packages/bot/src/components/Graph/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/Line'; -export * from './components/Margin'; -export * from './components/Root'; diff --git a/packages/bot/src/components/Leaderboard/components/Gap.tsx b/packages/bot/src/components/Leaderboard/components/Gap.tsx deleted file mode 100644 index f1fa3f16c..000000000 --- a/packages/bot/src/components/Leaderboard/components/Gap.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { theme } from '../../../stitches.config'; - -interface GapProps { - message: string; -} - -export function Gap({ message: number }: GapProps) { - return ( -
- - {number.toLocaleString()} - -
- ); -} diff --git a/packages/bot/src/components/Leaderboard/components/Item.tsx b/packages/bot/src/components/Leaderboard/components/Item.tsx deleted file mode 100644 index 249221e41..000000000 --- a/packages/bot/src/components/Leaderboard/components/Item.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { type ComponentProps } from "react"; -import { theme } from "../../../stitches.config"; - -interface ItemProps extends ComponentProps<"div"> { - position: number; - deltaPosition?: number; - score?: number; - deltaScore?: number; - nickname: string; - clan?: string; - highlight?: boolean; -} - -export function Item({ - position, - deltaPosition, - score, - deltaScore, - nickname, - clan, - highlight, - ...props -}: ItemProps) { - return ( -
- - {position.toLocaleString()}. - - -
- - {nickname} - - {clan && ( - - [{clan}] - - )} -
- - {deltaPosition !== undefined && deltaPosition !== 0 && ( -
- {deltaPosition 0 ? "Increase" : "Decrease"} - style={{ - width: 8, - height: 8, - }} - src={ - deltaPosition > 0 - ? "https://i.imgur.com/qbjiXa1.png" - : "https://i.imgur.com/3uyNhun.png" - } - /> - - {Math.abs(deltaPosition).toLocaleString()} - -
- )} - - {/* TODO: lol remove this spacer div for a better solution */} -
- -
- {deltaScore !== 0 && deltaScore !== undefined && ( - - {Math.abs(deltaScore).toLocaleString()} - - )} - {deltaScore !== 0 && deltaScore !== undefined && ( - {deltaScore 0 ? "Increase" : "Decrease"} - style={{ - width: 8, - height: 8, - }} - src={ - deltaScore > 0 - ? "https://i.imgur.com/qbjiXa1.png" - : "https://i.imgur.com/3uyNhun.png" - } - /> - )} - - {score !== undefined && ( - - {score.toLocaleString()} - - )} -
-
- ); -} diff --git a/packages/bot/src/components/Leaderboard/components/Root.tsx b/packages/bot/src/components/Leaderboard/components/Root.tsx deleted file mode 100644 index d812cf3c7..000000000 --- a/packages/bot/src/components/Leaderboard/components/Root.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { type ReactNode } from 'react'; - -interface RootProps { - children: ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Leaderboard/index.ts b/packages/bot/src/components/Leaderboard/index.ts deleted file mode 100644 index 4334194b1..000000000 --- a/packages/bot/src/components/Leaderboard/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/Gap'; -export * from './components/Item'; -export * from './components/Root'; diff --git a/packages/bot/src/components/NoData/constants.ts b/packages/bot/src/components/NoData/constants.ts deleted file mode 100644 index a01763279..000000000 --- a/packages/bot/src/components/NoData/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum NoDataType { - BattlesInPeriod, - PlayersInPeriod, - TanksFound, -} diff --git a/packages/bot/src/components/NoData/index.tsx b/packages/bot/src/components/NoData/index.tsx deleted file mode 100644 index 7fa7227aa..000000000 --- a/packages/bot/src/components/NoData/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Locale } from 'discord.js'; -import { translator } from '../../core/localization/translator'; -import { theme } from '../../stitches.config'; - -interface NoDataProps { - type: - | 'battles_in_period' - | 'players_in_period' - | 'tanks_found' - | 'battles_in_season'; - locale: Locale; -} - -export function NoData({ type, locale }: NoDataProps) { - const { strings } = translator(locale); - - return ( -
- - {strings.bot.common.no_data[type]} - -
- ); -} diff --git a/packages/bot/src/components/PercentileIndicator/constants.ts b/packages/bot/src/components/PercentileIndicator/constants.ts deleted file mode 100644 index b5cefb554..000000000 --- a/packages/bot/src/components/PercentileIndicator/constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { - amber, - blue, - green, - lime, - orange, - pink, - purple, - red, - slate, - teal, -} from '@radix-ui/colors'; - -export const PERCENTILE_COLORS = { - [Percentile.VeryBad]: slate.slate9, - [Percentile.Bad]: red.red9, - [Percentile.BelowAverage]: orange.orange9, - [Percentile.Average]: amber.amber10, - [Percentile.AboveAverage]: lime.lime8, - [Percentile.Good]: green.green9, - [Percentile.VeryGood]: teal.teal9, - [Percentile.Great]: blue.blue9, - [Percentile.Unicum]: pink.pink9, - [Percentile.SuperUnicum]: purple.purple9, -}; diff --git a/packages/bot/src/components/PercentileIndicator/index.tsx b/packages/bot/src/components/PercentileIndicator/index.tsx deleted file mode 100644 index f7c0f5b68..000000000 --- a/packages/bot/src/components/PercentileIndicator/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { PERCENTILE_COLORS } from './constants'; - -interface PercentileIndicatorProps { - percentile: Percentile; -} - -export function PercentileIndicator({ percentile }: PercentileIndicatorProps) { - return ( -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Column.tsx b/packages/bot/src/components/Tanks/components/Column.tsx deleted file mode 100644 index 6e3549c89..000000000 --- a/packages/bot/src/components/Tanks/components/Column.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { type ReactNode } from 'react'; - -interface ColumnProps { - children: ReactNode; -} - -export function Column({ children }: ColumnProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Item/index.tsx b/packages/bot/src/components/Tanks/components/Item/index.tsx deleted file mode 100644 index f33342c3a..000000000 --- a/packages/bot/src/components/Tanks/components/Item/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { TankClass, TankType, TREE_TYPE_ICONS } from "@blitzkit/core"; -import { theme } from "../../../../stitches.config"; - -export interface ItemProps { - tankClass?: TankClass; - name: string; - tankType: TankType; -} - -const TREE_TYPE_COLOR = { - [TankType.TANK_TYPE_RESEARCHABLE]: "", - [TankType.TANK_TYPE_PREMIUM]: "_amber", - [TankType.TANK_TYPE_COLLECTOR]: "_blue", -} as const; - -export function Item({ - tankClass: tankType, - name, - tankType: treeType, -}: ItemProps) { - return ( -
-
- {tankType && ( - {TankType[tankType]} - )} - - {name} - -
- - {/* {image && ( - // TODO: remove hardcoded dimensions when satori fixes non-width unloadable images - {name} - )} */} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Root.tsx b/packages/bot/src/components/Tanks/components/Root.tsx deleted file mode 100644 index a5d2e3107..000000000 --- a/packages/bot/src/components/Tanks/components/Root.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { type ReactNode } from "react"; - -interface RootProps { - children: ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Row.tsx b/packages/bot/src/components/Tanks/components/Row.tsx deleted file mode 100644 index f5520127d..000000000 --- a/packages/bot/src/components/Tanks/components/Row.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { type ReactNode } from "react"; - -interface RowProps { - children: ReactNode; -} - -export function Row({ children }: RowProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Title.tsx b/packages/bot/src/components/Tanks/components/Title.tsx deleted file mode 100644 index 10e7d6281..000000000 --- a/packages/bot/src/components/Tanks/components/Title.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { theme } from '../../../stitches.config'; - -interface TitleProps { - children: string; -} - -export function Title({ children }: TitleProps) { - return ( - - {children} - - ); -} diff --git a/packages/bot/src/components/Tanks/index.ts b/packages/bot/src/components/Tanks/index.ts deleted file mode 100644 index 7042919a3..000000000 --- a/packages/bot/src/components/Tanks/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './components/Column'; -export * from './components/Item'; -export * from './components/Root'; -export * from './components/Row'; -export * from './components/Title'; diff --git a/packages/bot/src/components/TierWeights.tsx b/packages/bot/src/components/TierWeights.tsx deleted file mode 100644 index 415c6b6bc..000000000 --- a/packages/bot/src/components/TierWeights.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { TierWeightsRecord } from "@blitzkit/core"; -import { theme } from "../stitches.config"; - -interface TierWeightsProps { - weights: TierWeightsRecord; -} - -export function TierWeights({ weights }: TierWeightsProps) { - return ( -
- {Object.entries(weights).map(([tier, weight]) => ( -
- {tier} -
- ))} -
- ); -} diff --git a/packages/bot/src/components/TitleBar.tsx b/packages/bot/src/components/TitleBar.tsx deleted file mode 100644 index e64d0db7b..000000000 --- a/packages/bot/src/components/TitleBar.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { theme } from '../stitches.config'; - -interface TitleBarProps { - title: string; - image?: string; - description?: string; -} - -export function TitleBar({ title, image, description }: TitleBarProps) { - return ( -
-
- {image && ( - {title} - )} - - - {title} - -
- - - {description} - -
- ); -} diff --git a/packages/bot/src/core/blitz/normalizeLeagueIcon.ts b/packages/bot/src/core/blitz/normalizeLeagueIcon.ts deleted file mode 100644 index 4c450ed19..000000000 --- a/packages/bot/src/core/blitz/normalizeLeagueIcon.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function normalizeLeagueIcon(url: string) { - return url.startsWith('http') ? url : `https:${url}`; -} diff --git a/packages/bot/src/core/blitz/resolveTankId.ts b/packages/bot/src/core/blitz/resolveTankId.ts deleted file mode 100644 index 4415a8362..000000000 --- a/packages/bot/src/core/blitz/resolveTankId.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { SEARCH_KEYS } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { go } from "fuzzysort"; -import markdownEscape from "markdown-escape"; -import { tankDefinitions, tankNames } from "../blitzkit/nonBlockingPromises"; -import { UserError } from "../blitzkit/userError"; -import { tankNamesTechTreeOnly } from "../discord/autocompleteTanks"; -import { translator } from "../localization/translator"; - -export async function resolveTankId( - tank: string | number, - locale: Locale, - techTreeOnly = false -) { - const { strings } = translator(locale); - const number = typeof tank === "string" ? Number(tank) : tank; - - if (Number.isNaN(number)) { - const searchResult = go( - `${tank}`, - await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), - { keys: SEARCH_KEYS, limit: 1 } - ); - - if (searchResult.length === 0) { - throw new UserError( - literals(strings.bot.common.errors.tank_name_not_found, { - search: markdownEscape(`${tank}`), - }) - ); - } else { - return searchResult[0].obj.id; - } - } else { - const awaitedTankDefinitions = await tankDefinitions; - - if (awaitedTankDefinitions.tanks[number]) { - return number; - } else { - throw new UserError( - literals(strings.bot.common.errors.tank_id_not_found, { - search: number, - }) - ); - } - } -} diff --git a/packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts b/packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts deleted file mode 100644 index c06e03b30..000000000 --- a/packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { searchClansAcrossRegions } from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { UserError } from '../blitzkit/userError'; -import { translator } from '../localization/translator'; - -export async function searchClansAcrossRegionsBotWrapper( - search: string, - locale: Locale, - limit?: number, -) { - const { strings } = translator(locale); - const trimmed = search.trim(); - - if (trimmed.length < 3) { - throw new UserError(strings.bot.common.errors.query_too_small); - } - - if (trimmed.length > 100) { - throw new UserError(strings.bot.common.errors.query_too_long); - } - - return await searchClansAcrossRegions(trimmed, limit); -} diff --git a/packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts b/packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts deleted file mode 100644 index 89e473a8d..000000000 --- a/packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { searchPlayersAcrossRegions } from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { UserError } from '../blitzkit/userError'; -import { translator } from '../localization/translator'; - -export async function searchPlayersAcrossRegionsBotWrapper( - search: string, - locale: Locale, - limit?: number, -) { - const { strings } = translator(locale); - const trimmed = search.trim(); - - if (trimmed.length < 3) { - throw new UserError(strings.bot.common.errors.query_too_small); - } - - if (trimmed.length > 100) { - throw new UserError(strings.bot.common.errors.query_too_long); - } - - return await searchPlayersAcrossRegions(trimmed, limit); -} diff --git a/packages/bot/src/core/blitzkit/buildTechTreeLine.ts b/packages/bot/src/core/blitzkit/buildTechTreeLine.ts deleted file mode 100644 index e8e1ed463..000000000 --- a/packages/bot/src/core/blitzkit/buildTechTreeLine.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { fetchTankDefinitions } from '@blitzkit/core'; -import { resolveAncestry } from './resolveAncestry'; - -export async function buildTechTreeLine(start: number, end: number) { - const tankDefinitions = await fetchTankDefinitions(); - const endTank = tankDefinitions.tanks[end]; - if (!endTank.ancestors) throw new Error('End tank has no ancestors'); - const line = [end]; - - if (start === end) return line; - for (const ancestorId of endTank.ancestors) { - if ((await resolveAncestry(ancestorId)).includes(start)) { - const ancestorTree = await buildTechTreeLine(start, ancestorId); - line.push(...ancestorTree); - break; - } - } - - return line; -} diff --git a/packages/bot/src/core/blitzkit/filtersToDescription.ts b/packages/bot/src/core/blitzkit/filtersToDescription.ts deleted file mode 100644 index a8c8377cd..000000000 --- a/packages/bot/src/core/blitzkit/filtersToDescription.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { fetchTankDefinitions, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { type StatFilters } from "../blitzstars/filterStats"; -import { translator } from "../localization/translator"; - -export async function filtersToDescription( - { nation, tier, type, class: tankClass, tank }: StatFilters, - locale: Locale, -) { - const { strings, bkLocale } = translator(locale); - const awaitedTankDefinitions = await fetchTankDefinitions(); - const info: string[] = []; - - // TODO: somehow localize tank names? - if (tank) return awaitedTankDefinitions.tanks[tank].name!.locales[bkLocale]; - if (nation) - info.push((strings.common.nations as Record)[nation]); - if (tier) { - info.push( - literals(strings.bot.common.filters.tier, { - tier: TIER_ROMAN_NUMERALS[tier], - }), - ); - } - if (tankClass) info.push(strings.common.tank_class_short[tankClass]); - if (type) info.push(strings.common.tree_type[type]); - - return info.length === 0 ? strings.bot.common.filters.none : info.join(", "); -} diff --git a/packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts b/packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts deleted file mode 100644 index 16088c64b..000000000 --- a/packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { idToRegion } from '@blitzkit/core'; -import { flagUserActivity } from '../db/flagUserActivity'; -import { usersDatabase } from '../db/users'; - -export async function getBlitzFromDiscord(discordId: bigint) { - const unique = await usersDatabase.discordUser.findUnique({ - where: { discord_id: discordId }, - }); - - if (unique === null) return null; - - const id = Number(unique.blitz_id); - - await flagUserActivity(id); - return { id, region: idToRegion(id) }; -} diff --git a/packages/bot/src/core/blitzkit/getPeriodNow.ts b/packages/bot/src/core/blitzkit/getPeriodNow.ts deleted file mode 100644 index 030f2aa94..000000000 --- a/packages/bot/src/core/blitzkit/getPeriodNow.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function getPeriodNow() { - return Date.now() / 1000; -} diff --git a/packages/bot/src/core/blitzkit/getPeriodStart.ts b/packages/bot/src/core/blitzkit/getPeriodStart.ts deleted file mode 100644 index aef8b4328..000000000 --- a/packages/bot/src/core/blitzkit/getPeriodStart.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getTimeDaysAgo, type Region } from '@blitzkit/core'; -import type { PeriodSize } from '../discord/addPeriodSubCommands'; - -export function getPeriodStart(region: Region, period: PeriodSize) { - if (period === 'career') return 0; - if (period === 'today') return getTimeDaysAgo(region, 1); - return getTimeDaysAgo(region, parseInt(period)); -} diff --git a/packages/bot/src/core/blitzkit/iconPng.ts b/packages/bot/src/core/blitzkit/iconPng.ts deleted file mode 100644 index c575b07b7..000000000 --- a/packages/bot/src/core/blitzkit/iconPng.ts +++ /dev/null @@ -1,16 +0,0 @@ -import sharp from "sharp"; - -const cache: Record = {}; - -export async function iconPng(url: string) { - if (!url.endsWith(".webp")) return url; - - if (!cache[url]) { - const imageResponse = await fetch(url); - const imageBufferWEBP = await imageResponse.arrayBuffer(); - const imageBufferPNG = await sharp(imageBufferWEBP).png().toBuffer(); - cache[url] = imageBufferPNG; - } - - return `data:image/png;base64,${cache[url].toString("base64")}`; -} diff --git a/packages/bot/src/core/blitzkit/jsxToPng.ts b/packages/bot/src/core/blitzkit/jsxToPng.ts deleted file mode 100644 index bc8ae24d0..000000000 --- a/packages/bot/src/core/blitzkit/jsxToPng.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { jsxToSvg } from "./jsxToSvg"; -import { svgToPng } from "./svgToPng"; - -export async function jsxToPng(jsx: React.JSX.Element) { - const svg = await jsxToSvg(jsx); - const png = svgToPng(svg); - - return png; -} diff --git a/packages/bot/src/core/blitzkit/jsxToPngThreaded.ts b/packages/bot/src/core/blitzkit/jsxToPngThreaded.ts deleted file mode 100644 index 16ee14401..000000000 --- a/packages/bot/src/core/blitzkit/jsxToPngThreaded.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { jsxToSvg } from "./jsxToSvg"; -import { RenderConfiguration } from "./renderConfiguration"; -import { svgToPngThreaded } from "./svgToPngThreaded"; - -export async function jsxToPngThreaded( - jsx: React.JSX.Element, - renderConfiguration: RenderConfiguration -) { - const svg = await jsxToSvg(jsx, renderConfiguration); - const png = await svgToPngThreaded(svg); - return png; -} diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf b/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf deleted file mode 100644 index 96ddf0003..000000000 Binary files a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf and /dev/null differ diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf b/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf deleted file mode 100644 index 7a876e28e..000000000 Binary files a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf and /dev/null differ diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf b/packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf deleted file mode 100644 index 3033308a6..000000000 Binary files a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf and /dev/null differ diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/index.ts b/packages/bot/src/core/blitzkit/jsxToSvg/index.ts deleted file mode 100644 index 40c0e7bb0..000000000 --- a/packages/bot/src/core/blitzkit/jsxToSvg/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { readFile } from "fs/promises"; -import satori from "satori"; -import { RenderConfiguration } from "../renderConfiguration"; -import robotoBlack from "./Roboto-Black.ttf"; -import robotoBold from "./Roboto-Bold.ttf"; -import roboto from "./Roboto.ttf"; - -const fontsPromise = [ - { file: roboto, weight: 400 as const }, - { file: robotoBold, weight: 700 as const }, - { file: robotoBlack, weight: 900 as const }, -].map(async ({ file, weight }) => { - const data = await readFile(`${__dirname}/${file}`); - return { name: "Roboto", weight, data }; -}); - -export async function jsxToSvg( - jsx: React.JSX.Element, - renderConfiguration = new RenderConfiguration() -) { - const fonts = await Promise.all(fontsPromise); - return await satori(jsx, { width: renderConfiguration.width, fonts }); -} diff --git a/packages/bot/src/core/blitzkit/nonBlockingPromises.ts b/packages/bot/src/core/blitzkit/nonBlockingPromises.ts deleted file mode 100644 index 647a955d0..000000000 --- a/packages/bot/src/core/blitzkit/nonBlockingPromises.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - fetchAverageDefinitions, - fetchDiscoveredIdsDefinitions, - fetchGameDefinitions, - fetchMapDefinitions, - fetchTankDefinitions, - fetchTankNames, -} from "@blitzkit/core"; - -export const averageDefinitions = fetchAverageDefinitions(); -export const tankDefinitions = fetchTankDefinitions(); -export const gameDefinitions = fetchGameDefinitions(); -export const tankNames = fetchTankNames(); -export const mapDefinitions = fetchMapDefinitions(); -export const discoveredIdsDefinitions = fetchDiscoveredIdsDefinitions(); diff --git a/packages/bot/src/core/blitzkit/processes.ts b/packages/bot/src/core/blitzkit/processes.ts deleted file mode 100644 index a639e25cc..000000000 --- a/packages/bot/src/core/blitzkit/processes.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { usersDatabase } from "../db/users"; -import { client } from "../discord/client"; - -const EXIT_EVENTS = ["SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2"]; - -let cleaningUp = false; - -async function cleanup(signal: string) { - if (cleaningUp) return; - cleaningUp = true; - - console.log(`Cleaning up... (${signal})`); - - try { - await Promise.all([usersDatabase.$disconnect(), client.destroy()]); - } catch (err) { - console.error("Cleanup error:", err); - } - - console.log("Gracefully exiting..."); - - process.exit(0); -} - -export function registerProcesses() { - process.on("uncaughtException", (err) => { - console.error("uncaughtException:", err); - }); - - process.on("unhandledRejection", (err) => { - console.error("unhandledRejection:", err); - }); - - client.on("error", console.error); - - EXIT_EVENTS.forEach((event) => process.on(event, () => cleanup(event))); -} diff --git a/packages/bot/src/core/blitzkit/renderConfiguration.ts b/packages/bot/src/core/blitzkit/renderConfiguration.ts deleted file mode 100644 index 508e20591..000000000 --- a/packages/bot/src/core/blitzkit/renderConfiguration.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface RenderConfiguration { - width: number; -} - -export class RenderConfiguration implements RenderConfiguration { - width = 480; - - constructor(configuration?: Partial) { - Object.assign(this, configuration); - } -} diff --git a/packages/bot/src/core/blitzkit/resolveAncestry.ts b/packages/bot/src/core/blitzkit/resolveAncestry.ts deleted file mode 100644 index e5ac8b5db..000000000 --- a/packages/bot/src/core/blitzkit/resolveAncestry.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { fetchTankDefinitions } from '@blitzkit/core'; - -export async function resolveAncestry(id: number) { - const tankDefinitions = await fetchTankDefinitions(); - const tank = tankDefinitions.tanks[id]; - const ancestors: number[] = []; - - if (tank.ancestors) { - for (const ancestor of tank.ancestors) { - if (!ancestors.includes(ancestor)) { - ancestors.push(ancestor); - - for (const ancestorLayer2 of await resolveAncestry(ancestor)) { - if (!ancestors.includes(ancestorLayer2)) { - ancestors.push(ancestorLayer2); - } - } - } - } - } - - return ancestors.sort( - (a, b) => tankDefinitions.tanks[b].tier - tankDefinitions.tanks[a].tier, - ); -} diff --git a/packages/bot/src/core/blitzkit/svgToPng.ts b/packages/bot/src/core/blitzkit/svgToPng.ts deleted file mode 100644 index 06ace7446..000000000 --- a/packages/bot/src/core/blitzkit/svgToPng.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Resvg } from '@resvg/resvg-js'; - -export function svgToPng(svg: string) { - return new Resvg(svg).render().asPng(); -} diff --git a/packages/bot/src/core/blitzkit/svgToPngThreaded.ts b/packages/bot/src/core/blitzkit/svgToPngThreaded.ts deleted file mode 100644 index 344fb4627..000000000 --- a/packages/bot/src/core/blitzkit/svgToPngThreaded.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Worker } from 'worker_threads'; - -const RENDER_WORKERS = 1; - -const workers = [...Array(RENDER_WORKERS)].map(() => { - const worker = new Worker('dist/bot/workers/render.js'); - console.log(`🟡 Launching render worker ${worker.threadId}`); - return worker; -}); -const queue: { - svg: string; - resolve: (png: Buffer) => void; -}[] = []; - -function manageQueue() { - if (queue.length > 0 && workers.length > 0) { - const { svg, resolve } = queue.shift()!; - const worker = workers.shift()!; - - function cleanup() { - workers.push(worker); - worker.off('message', handleMessage); - worker.off('error', handleError); - manageQueue(); - } - function handleMessage(png: Uint8Array) { - cleanup(); - resolve(Buffer.from(png)); - } - function handleError(error: Error) { - cleanup(); - throw error; - } - - worker.postMessage(svg); - worker.on('message', handleMessage); - worker.on('error', handleError); - } -} - -export function svgToPngThreaded(svg: string) { - return new Promise((resolve) => { - queue.push({ svg, resolve }); - manageQueue(); - }); -} diff --git a/packages/bot/src/core/blitzkit/tankIconPng.ts b/packages/bot/src/core/blitzkit/tankIconPng.ts deleted file mode 100644 index a15b09c8b..000000000 --- a/packages/bot/src/core/blitzkit/tankIconPng.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { tankIcon } from '@blitzkit/core'; -import { iconPng } from './iconPng'; - -export function tankIconPng(id: number) { - return iconPng(tankIcon(id)); -} diff --git a/packages/bot/src/core/blitzkit/userError.ts b/packages/bot/src/core/blitzkit/userError.ts deleted file mode 100644 index 44a647f15..000000000 --- a/packages/bot/src/core/blitzkit/userError.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Discriminator for user caused errors to not report in logs - */ -export class UserError { - constructor(public message: string) {} -} diff --git a/packages/bot/src/core/blitzstars/filterStats.ts b/packages/bot/src/core/blitzstars/filterStats.ts deleted file mode 100644 index 2936170a9..000000000 --- a/packages/bot/src/core/blitzstars/filterStats.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { TankClass, TankType } from "@blitzkit/core"; -import type { SupplementaryStats } from "@blitzkit/core/src/blitz/getAccountInfo"; -import { calculateWN8 } from "@blitzkit/core/src/statistics/calculateWN8"; -import type { BlitzStats } from "@blitzkit/core/src/statistics/compositeStats"; -import { sumAllStats } from "@blitzkit/core/src/statistics/sumAllStats"; -import { - averageDefinitions, - tankDefinitions, -} from "../blitzkit/nonBlockingPromises"; - -export interface StatFilters { - nation?: string; - tier?: number; - class?: TankClass; - type?: TankType; - tank?: number; -} - -export async function filterStats( - { diff, order }: DiffedTankStats, - filters: StatFilters, -) { - // tank, if provided, takes priority over all other filters - if (filters.tank) filters = { tank: filters.tank }; - - const awaitedTankDefinitions = await tankDefinitions; - const awaitedTankAverages = await averageDefinitions; - const filteredOrder = order.filter((id) => { - const entry = awaitedTankDefinitions.tanks[id]; - - return ( - entry && - (filters.nation === undefined || entry.nation === filters.nation) && - (filters.tier === undefined || entry.tier === filters.tier) && - (filters.class === undefined || entry.class === filters.class) && - (filters.type === undefined || - (filters.type === TankType.TANK_TYPE_COLLECTOR && - entry.type === TankType.TANK_TYPE_COLLECTOR) || - (filters.type === TankType.TANK_TYPE_PREMIUM && - entry.type === TankType.TANK_TYPE_PREMIUM) || - (filters.type === TankType.TANK_TYPE_RESEARCHABLE && - entry.type === TankType.TANK_TYPE_RESEARCHABLE)) && - (filters.tank === undefined || entry.id === filters.tank) - ); - }); - const stats = sumAllStats(filteredOrder.map((id) => diff[id])); - const battlesOfTanksWithAverages = filteredOrder.reduce( - (accumulator, id) => - awaitedTankAverages.averages[id] - ? accumulator + diff[id].battles - : accumulator, - 0, - ); - const battlesOfTanksWithTankDefinition = filteredOrder.reduce( - (accumulator, id) => - awaitedTankDefinitions.tanks[id] - ? accumulator + diff[id].battles - : accumulator, - 0, - ); - const supplementary = { - WN8: - filteredOrder.reduce( - (accumulator, id) => - awaitedTankAverages.averages[id] - ? accumulator + - calculateWN8(awaitedTankAverages.averages[id].mu, diff[id]) * - diff[id].battles - : accumulator, - 0, - ) / battlesOfTanksWithAverages, - tier: - filteredOrder.reduce( - (accumulator, id) => - awaitedTankDefinitions.tanks[id] - ? accumulator + - awaitedTankDefinitions.tanks[id]!.tier * diff[id].battles - : accumulator, - 0, - ) / battlesOfTanksWithTankDefinition, - } satisfies SupplementaryStats; - - return { stats, supplementary, filteredOrder }; -} - -export interface DiffedTankStats { - diff: Record; - order: number[]; -} diff --git a/packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts b/packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts deleted file mode 100644 index 9a17855e7..000000000 --- a/packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { getAccountInfo, type Region } from "@blitzkit/core"; -import { buttonLink } from "../discord/buttonLink"; - -export async function getBlitzStarsLinkButton( - region: Region, - id: number, - tankId?: number -) { - const { nickname } = await getAccountInfo(region, id); - - return buttonLink( - `https://www.blitzstars.com/player/${region}/${nickname}${ - tankId ? `/tank/${tankId!}` : "" - }`, - "BlitzStars" - ); -} diff --git a/packages/bot/src/core/blitzstars/getPlayerHistories.ts b/packages/bot/src/core/blitzstars/getPlayerHistories.ts deleted file mode 100644 index 0434b8116..000000000 --- a/packages/bot/src/core/blitzstars/getPlayerHistories.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { type Region, emptyAllStats, getAccountInfo } from '@blitzkit/core'; -import { - type GetHistoriesOptions, - type Histories, - type History, - getHistoriesDefaultOptions, -} from '../../types/histories'; -import type { PlayerStats } from './getPlayerStats'; - -export async function getPlayerHistories( - server: Region, - id: number, - options?: Partial, -) { - const mergedOptions = { ...getHistoriesDefaultOptions, ...options }; - const response = await fetch( - `https://www.blitzstars.com/api/playerstats/${id}`, - ); - const playerHistories = ((await response.json()) as PlayerStats).map( - (history) => - ({ - all: history.statistics.all, - last_battle_time: history.last_battle_time, - }) satisfies History, - ); - let lastNonCompliantIndex = -1; - let hasCompliedOnce = false; - const filtered = playerHistories.filter(({ last_battle_time }, index) => { - const complies = - last_battle_time >= mergedOptions.start && - last_battle_time <= mergedOptions.end; - - if (complies && !hasCompliedOnce) hasCompliedOnce = true; - if (!hasCompliedOnce) lastNonCompliantIndex = index; - - return complies; - }); - - const latestNodes: Histories = []; - - if (mergedOptions.includeLatestHistories) { - const { last_battle_time, statistics } = await getAccountInfo(server, id); - - latestNodes.push({ all: statistics.all, last_battle_time }); - } - - const previousNodes: Histories = []; - - if (mergedOptions.includePreviousHistories) { - if (lastNonCompliantIndex !== -1) { - previousNodes.push( - playerHistories[lastNonCompliantIndex] ?? - ({ - all: emptyAllStats, - last_battle_time: -Infinity, - } satisfies History), - ); - } - } - - return [...latestNodes, ...filtered, ...previousNodes] satisfies Histories; -} diff --git a/packages/bot/src/core/blitzstars/getPlayerStats.ts b/packages/bot/src/core/blitzstars/getPlayerStats.ts deleted file mode 100644 index 1555bc147..000000000 --- a/packages/bot/src/core/blitzstars/getPlayerStats.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { BlitzStats, Region } from "@blitzkit/core"; - -interface IndividualPlayerStats { - clan: { clan_id: number; name: string; tag: string }; - statistics: { - all: BlitzStats; - wn8: number; - wn7: number; - average_tier: number; - }; - last_battle_time: number; - nickname: string; - account_id: number; - achievements: { - account_id: number; - [key: string]: number; - }; - region: Region; -} - -export type PlayerStats = IndividualPlayerStats[]; - -export async function getPlayerStats< - Ids extends number | number[], - ReturnType = Ids extends number ? PlayerStats : PlayerStats[], ->(region: Region, ids: Ids) { - const stats = await Promise.all( - ((typeof ids === "number" ? [ids] : ids) as number[]).map((id) => - fetch(`https://www.blitzstars.com/api/playerstats/${id}`).then( - (response) => response.json() as Promise - ) - ) - ); - - if (typeof ids === "number") return stats[0] as ReturnType; - return stats as ReturnType; -} diff --git a/packages/bot/src/core/blitzstars/getStatsInPeriod.ts b/packages/bot/src/core/blitzstars/getStatsInPeriod.ts deleted file mode 100644 index 861f5c8a5..000000000 --- a/packages/bot/src/core/blitzstars/getStatsInPeriod.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { type BlitzStats, emptyAllStats, type Region } from "@blitzkit/core"; -import { Locale } from "discord.js"; -import type { DiffedTankStats } from "./filterStats"; -import { - getTankHistories, - type TankHistories, - type TankHistoryRaw, -} from "./getTankHistories"; - -const emptyTankHistoryNode: TankHistoryRaw = { - account_id: 0, - battle_life_time: 0, - last_battle_time: 0, - mark_of_mastery: 0, - tank_id: 0, - all: emptyAllStats, -}; - -// BIG TODO: merge this neatly with diffNormalizedTankStats -export async function getStatsInPeriod( - server: Region, - id: number, - start: number, - end: number, - locale: Locale -) { - const history = await getTankHistories(server, id, locale, { - includeLatestHistories: true, - }); - - // sort them even though most won't be used - const tankSortedHistory: Record = {}; - const tanks: number[] = []; - history.forEach((node) => { - if (!tankSortedHistory[node.tank_id]) { - tankSortedHistory[node.tank_id] = [node]; - tanks.push(node.tank_id); - } else { - tankSortedHistory[node.tank_id].push(node); - } - }); - - // find the ones played within the range - const inRangeTanks = tanks.filter((tankId) => - tankSortedHistory[tankId].some( - (node) => node.last_battle_time > start && node.last_battle_time < end - ) - ); - const playedTanks: number[] = []; - - // fetch history node right before "start" and right before "end" - const diff: Record = {}; - inRangeTanks.forEach((tankId) => { - const sortedHistory = tankSortedHistory[tankId]; - const previousIndex = - sortedHistory.findIndex((node) => node.last_battle_time > start) - 1; - const latestIndex = - sortedHistory.findIndex((node) => node.last_battle_time > end) - 1; - const previous = sortedHistory[previousIndex] ?? emptyTankHistoryNode; - const latest = - sortedHistory[latestIndex] ?? sortedHistory[sortedHistory.length - 1]; - - function d(get: (allStats: BlitzStats) => number) { - return get(latest.all) - get(previous.all); - } - - // check if there was a change in battles as games in rating do update last_battle_time - if (d((a) => a.battles) > 0) { - diff[tankId] = { - battles: d((a) => a.battles), - capture_points: d((a) => a.capture_points), - damage_dealt: d((a) => a.damage_dealt), - damage_received: d((a) => a.damage_received), - dropped_capture_points: d((a) => a.dropped_capture_points), - frags: d((a) => a.frags), - frags8p: d((a) => a.frags8p), - hits: d((a) => a.hits), - losses: d((a) => a.losses), - max_frags: latest.all.max_frags, - max_xp: latest.all.max_xp, - shots: d((a) => a.shots), - spotted: d((a) => a.spotted), - survived_battles: d((a) => a.survived_battles), - win_and_survived: d((a) => a.win_and_survived), - wins: d((a) => a.wins), - xp: d((a) => a.xp), - }; - - playedTanks.push(tankId); - } - }); - - const order = playedTanks.sort((a, b) => { - const aHistories = tankSortedHistory[a]; - const bHistories = tankSortedHistory[b]; - const aLatest = aHistories[aHistories.length - 1]; - const bLatest = bHistories[bHistories.length - 1]; - - return bLatest.last_battle_time - aLatest.last_battle_time; - }); - - return { diff, order } satisfies DiffedTankStats; -} diff --git a/packages/bot/src/core/blitzstars/getTankHistories.ts b/packages/bot/src/core/blitzstars/getTankHistories.ts deleted file mode 100644 index a8cbb9f95..000000000 --- a/packages/bot/src/core/blitzstars/getTankHistories.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { type Region, emptyAllStats, getTankStats } from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { - type GetHistoriesOptions, - type Histories, - type History, - getHistoriesDefaultOptions, -} from "../../types/histories"; -import { UserError } from "../blitzkit/userError"; -import { translator } from "../localization/translator"; - -export interface TankHistory extends History { - tank_id: number; -} - -export type TankHistories = TankHistory[]; - -export interface TankHistoryRaw extends TankHistory { - mark_of_mastery: number; - battle_life_time: number; - account_id: number; -} - -export type TankHistoriesRaw = TankHistoryRaw[]; - -export interface GetTankHistoriesOptions extends GetHistoriesOptions { - tankId?: number; -} - -const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = { - ...getHistoriesDefaultOptions, - tankId: undefined, -}; - -export async function getTankHistories( - server: Region, - id: number, - locale: Locale, - options?: Partial -) { - const { strings } = translator(locale); - const mergedOptions = { ...getTankHistoriesDefaultOptions, ...options }; - const tankHistoriesResponse = await fetch( - `https://www.blitzstars.com/api/tankhistories/for/${id}/` - ); - const tankHistories = ( - (await tankHistoriesResponse.json()) as TankHistoriesRaw - ).map( - (history) => - ({ - all: history.all, - last_battle_time: history.last_battle_time, - tank_id: history.tank_id, - }) satisfies TankHistory - ); - const compliantTanks: number[] = []; - const lastBattles: Record = {}; - const lastNonCompliantIndex: Record = {}; - const hasCompliedOnce: Record = {}; - const filtered = tankHistories.filter( - ({ last_battle_time, tank_id, all: { battles } }, index) => { - const complies = - last_battle_time >= mergedOptions.start && - last_battle_time <= mergedOptions.end && - (mergedOptions.tankId === undefined || - tank_id === mergedOptions.tankId); - - if (complies && !hasCompliedOnce[tank_id]) { - hasCompliedOnce[tank_id] = true; - compliantTanks.push(tank_id); - } - if (!hasCompliedOnce[tank_id]) lastNonCompliantIndex[tank_id] = index; - lastBattles[tank_id] = battles; - - return complies; - } - ); - - const latestNodes: TankHistories = []; - - if (mergedOptions.includeLatestHistories) { - const tankStats = await getTankStats(server, id); - - if (tankStats === null) { - throw new UserError(strings.bot.common.errors.no_tank_stats); - } - - tankStats.forEach((tankHistory) => { - if ( - tankHistory.all.battles > (lastBattles[tankHistory.tank_id] ?? 0) && - (mergedOptions.tankId === undefined || - tankHistory.tank_id === mergedOptions.tankId) - ) { - if (!hasCompliedOnce[tankHistory.tank_id]) { - // was played today for the first time... I think??? - hasCompliedOnce[tankHistory.tank_id] = true; - compliantTanks.push(tankHistory.tank_id); - } - latestNodes.push(tankHistory); - } - }); - } - - const previousNodes: TankHistories = []; - - if (mergedOptions.includePreviousHistories) { - compliantTanks.forEach((tankId) => { - if (tankId in lastNonCompliantIndex) { - previousNodes.push( - tankHistories[lastNonCompliantIndex[tankId]] ?? - ({ - all: emptyAllStats, - last_battle_time: -Infinity, - tank_id: tankId, - } satisfies TankHistory) - ); - } - }); - } - - return [...previousNodes, ...filtered, ...latestNodes] satisfies Histories; -} diff --git a/packages/bot/src/core/db/flagUserActivity.ts b/packages/bot/src/core/db/flagUserActivity.ts deleted file mode 100644 index 4fa958410..000000000 --- a/packages/bot/src/core/db/flagUserActivity.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { usersDatabase } from './users'; - -export async function flagUserActivity(blitzId: number) { - await usersDatabase.user.upsert({ - where: { blitz_id: blitzId }, - update: {}, - create: { blitz_id: blitzId }, - }); -} diff --git a/packages/bot/src/core/db/linkBlitzAndDiscord.ts b/packages/bot/src/core/db/linkBlitzAndDiscord.ts deleted file mode 100644 index 229aaeb1c..000000000 --- a/packages/bot/src/core/db/linkBlitzAndDiscord.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { flagUserActivity } from './flagUserActivity'; -import { usersDatabase } from './users'; - -export async function linkBlitzAndDiscord(discordId: bigint, blitzId: number) { - await usersDatabase.discordUser.upsert({ - where: { discord_id: discordId }, - update: { blitz_id: blitzId }, - create: { discord_id: discordId, blitz_id: blitzId }, - }); - await flagUserActivity(blitzId); -} diff --git a/packages/bot/src/core/db/users.ts b/packages/bot/src/core/db/users.ts deleted file mode 100644 index bfa600844..000000000 --- a/packages/bot/src/core/db/users.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PrismaClient } from '../../prisma'; - -export const usersDatabase = new PrismaClient(); diff --git a/packages/bot/src/core/discord/addClanChoices.ts b/packages/bot/src/core/discord/addClanChoices.ts deleted file mode 100644 index 328ee5c34..000000000 --- a/packages/bot/src/core/discord/addClanChoices.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Locale, SlashCommandStringOption } from 'discord.js'; -import { translator } from '../localization/translator'; -import { localizationObject } from './localizationObject'; - -export function addClanChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName('clan') - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.clan.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.clan.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.clan.description, - ), - ) - .setAutocomplete(true) - .setMinLength(2) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addFilterOptions.ts b/packages/bot/src/core/discord/addFilterOptions.ts deleted file mode 100644 index 7e1efd606..000000000 --- a/packages/bot/src/core/discord/addFilterOptions.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { - TANK_CLASSES, - TANK_TYPE_COMMAND_NAMES, - TANK_TYPES, -} from '@blitzkit/core'; -import { - type APIApplicationCommandOptionChoice, - Locale, - SlashCommandBuilder, - SlashCommandSubcommandBuilder, -} from 'discord.js'; -import { translator } from '../localization/translator'; -import { addTankChoices } from './addTankChoices'; -import { addTierChoices } from './addTierChoices'; -import { localizationObject } from './localizationObject'; - -export function addFilterOptions< - Option extends SlashCommandBuilder | SlashCommandSubcommandBuilder, ->(option: Option, nations: string[]) { - const { strings } = translator(Locale.EnglishUS); - - return ( - option - .addStringOption((option) => - option - .setName(strings.bot.common.options.filters.nation.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.nation.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.filters.nation.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.common.options.filters.nation.description, - ), - ) - .addChoices( - ...nations.map( - (nation) => - ({ - value: nation, - name: (strings.common.nations as Record)[ - nation - ], - name_localizations: localizationObject( - (strings) => - (strings.common.nations as Record)[ - nation - ], - ), - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(false), - ) - .addStringOption((option) => addTierChoices(option).setRequired(false)) - .addStringOption((option) => - option - .setName(strings.bot.common.options.filters.class.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.class.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.filters.class.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.class.description, - ), - ) - .addChoices( - ...TANK_CLASSES.map( - (tankType) => - ({ - value: `${tankType}`, - name: strings.common.tank_class_short[tankType], - name_localizations: localizationObject( - (strings) => strings.common.tank_class_short[tankType], - ), - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(false), - ) - .addStringOption((option) => - option - .setName(strings.bot.common.options.filters.type.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.type.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.filters.type.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.type.description, - ), - ) - .addChoices( - ...TANK_TYPES.map( - (treeType) => - ({ - value: TANK_TYPE_COMMAND_NAMES[treeType], - name: strings.common.tree_type[treeType], - name_localizations: localizationObject( - (strings) => strings.common.tree_type[treeType], - ), - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(false), - ) - // TODO: somehow localize this? - .addStringOption((option) => - addTankChoices(option).setRequired(false), - ) as Option - ); -} diff --git a/packages/bot/src/core/discord/addPeriodSubCommands.ts b/packages/bot/src/core/discord/addPeriodSubCommands.ts deleted file mode 100644 index 94cf335d4..000000000 --- a/packages/bot/src/core/discord/addPeriodSubCommands.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { - Locale, - SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from 'discord.js'; -import { translator } from '../localization/translator'; -import { localizationObject } from './localizationObject'; - -export type PeriodType = 'today' | '30' | '60' | '90' | 'career' | 'custom'; -export type PeriodSize = 'today' | `${number}` | 'career'; - -export function addPeriodSubCommands( - option: SlashCommandBuilder | SlashCommandSubcommandGroupBuilder, - extra: ( - option: SlashCommandSubcommandBuilder, - ) => SlashCommandSubcommandBuilder = (option) => option, -) { - const { strings } = translator(Locale.EnglishUS); - const subcommandNameLocalizations = { - today: localizationObject( - (strings) => strings.bot.common.subcommands.today.name, - undefined, - true, - ), - 30: localizationObject( - (strings) => strings.bot.common.subcommands[30].name, - undefined, - true, - ), - 60: localizationObject( - (strings) => strings.bot.common.subcommands[60].name, - undefined, - true, - ), - 90: localizationObject( - (strings) => strings.bot.common.subcommands[90].name, - undefined, - true, - ), - career: localizationObject( - (strings) => strings.bot.common.subcommands.career.name, - undefined, - true, - ), - custom: localizationObject( - (strings) => strings.bot.common.subcommands.custom.name, - undefined, - true, - ), - }; - const subcommandDescriptionLocalizations = { - today: localizationObject( - (strings) => strings.bot.common.subcommands.today.description, - ), - 30: localizationObject( - (strings) => strings.bot.common.subcommands[30].description, - ), - 60: localizationObject( - (strings) => strings.bot.common.subcommands[60].description, - ), - 90: localizationObject( - (strings) => strings.bot.common.subcommands[90].description, - ), - career: localizationObject( - (strings) => strings.bot.common.subcommands.career.description, - ), - custom: localizationObject( - (strings) => strings.bot.common.subcommands.custom.description, - ), - }; - - option - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands.today.name) - .setNameLocalizations(subcommandNameLocalizations.today) - .setDescription(strings.bot.common.subcommands.today.description) - .setDescriptionLocalizations( - subcommandDescriptionLocalizations.today, - ), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands[30].name) - .setNameLocalizations(subcommandNameLocalizations[30]) - .setDescription(strings.bot.common.subcommands[30].description) - .setDescriptionLocalizations(subcommandDescriptionLocalizations[30]), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands[60].name) - .setNameLocalizations(subcommandNameLocalizations[60]) - .setDescription(strings.bot.common.subcommands[60].description) - .setDescriptionLocalizations(subcommandDescriptionLocalizations[60]), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands[90].name) - .setNameLocalizations(subcommandNameLocalizations[90]) - .setDescription(strings.bot.common.subcommands[90].description) - .setDescriptionLocalizations(subcommandDescriptionLocalizations[90]), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands.career.name) - .setNameLocalizations(subcommandNameLocalizations.career) - .setDescription(strings.bot.common.subcommands.career.description) - .setDescriptionLocalizations( - subcommandDescriptionLocalizations.career, - ), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands.custom.name) - .setNameLocalizations(subcommandNameLocalizations.custom) - .setDescription(strings.bot.common.subcommands.custom.description) - .setDescriptionLocalizations( - subcommandDescriptionLocalizations.custom, - ) - .addIntegerOption((option) => - option - .setName(strings.bot.common.subcommands.custom.options.start.name) - .setDescription( - strings.bot.common.subcommands.custom.options.start.description, - ) - .setRequired(true), - ) - .addIntegerOption((option) => - option - .setName(strings.bot.common.subcommands.custom.options.end.name) - .setDescription( - strings.bot.common.subcommands.custom.options.end.description, - ) - .setRequired(true), - ), - ), - ); -} diff --git a/packages/bot/src/core/discord/addPeriodicFilterOptions.ts b/packages/bot/src/core/discord/addPeriodicFilterOptions.ts deleted file mode 100644 index 8e9d2f04b..000000000 --- a/packages/bot/src/core/discord/addPeriodicFilterOptions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from 'discord.js'; -import { gameDefinitions } from '../blitzkit/nonBlockingPromises'; -import { addFilterOptions } from './addFilterOptions'; -import { addPeriodSubCommands } from './addPeriodSubCommands'; - -export async function addPeriodicFilterOptions< - OptionType extends SlashCommandBuilder | SlashCommandSubcommandGroupBuilder, ->( - option: OptionType, - extra: ( - option: SlashCommandSubcommandBuilder, - ) => SlashCommandSubcommandBuilder = (option) => option, -): Promise { - const awaitedGameDefinitions = await gameDefinitions; - - addPeriodSubCommands(option, (option) => - addFilterOptions(extra(option), awaitedGameDefinitions.nations), - ); - - return option; -} diff --git a/packages/bot/src/core/discord/addRegionChoices.ts b/packages/bot/src/core/discord/addRegionChoices.ts deleted file mode 100644 index 38adf10c9..000000000 --- a/packages/bot/src/core/discord/addRegionChoices.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { REGIONS } from "@blitzkit/core"; -import { - type APIApplicationCommandOptionChoice, - Locale, - SlashCommandStringOption, -} from "discord.js"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -export function addRegionChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.region.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.region.name, - undefined, - true - ) - ) - .setDescription(strings.bot.common.options.region.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.region.description - ) - ) - .addChoices( - ...REGIONS.map( - (region) => - ({ - value: region, - name: strings.common.regions.normal[region], - name_localizations: localizationObject( - (strings) => strings.common.regions.normal[region] - ), - }) satisfies APIApplicationCommandOptionChoice - ) - ) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addTankChoices.ts b/packages/bot/src/core/discord/addTankChoices.ts deleted file mode 100644 index 3b38e220b..000000000 --- a/packages/bot/src/core/discord/addTankChoices.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Locale, SlashCommandStringOption } from "discord.js"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -export function addTankChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.tank.name!) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tank.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.tank.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tank.description, - ), - ) - .setAutocomplete(true) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addTierChoices.ts b/packages/bot/src/core/discord/addTierChoices.ts deleted file mode 100644 index 40ac51671..000000000 --- a/packages/bot/src/core/discord/addTierChoices.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { numberToRomanNumeral } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { - APIApplicationCommandOptionChoice, - Locale, - SlashCommandStringOption, -} from "discord.js"; -import { range } from "lodash-es"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -export function addTierChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.tier.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tier.name, - undefined, - true - ) - ) - .setDescription(strings.bot.common.options.tier.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tier.description - ) - ) - .setChoices( - ...range(10, 0).map( - (tier) => - ({ - name: literals(strings.bot.common.options.tier.choices.tier, { - tier_1: tier, - tier_2: numberToRomanNumeral(tier), - }), - value: `${tier}`, - name_localizations: localizationObject( - (strings) => strings.bot.common.options.tier.choices.tier, - { tier_1: tier, tier_2: numberToRomanNumeral(tier) } - ), - }) satisfies APIApplicationCommandOptionChoice - ) - ) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addUsernameChoices.ts b/packages/bot/src/core/discord/addUsernameChoices.ts deleted file mode 100644 index 35d851fdd..000000000 --- a/packages/bot/src/core/discord/addUsernameChoices.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Locale, SlashCommandStringOption } from 'discord.js'; -import { translator } from '../localization/translator'; -import { localizationObject } from './localizationObject'; - -export function addUsernameChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.username.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.username.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.username.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.username.description, - ), - ) - .setAutocomplete(true); -} diff --git a/packages/bot/src/core/discord/autocompleteClan/constants.ts b/packages/bot/src/core/discord/autocompleteClan/constants.ts deleted file mode 100644 index 26b7f01f6..000000000 --- a/packages/bot/src/core/discord/autocompleteClan/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const DISCORD_CHOICES_MAX_NAME_SIZE = 25; -export const OVERFLOW_SUFFIX = '…'; diff --git a/packages/bot/src/core/discord/autocompleteClan/index.ts b/packages/bot/src/core/discord/autocompleteClan/index.ts deleted file mode 100644 index 5ed5d8d3f..000000000 --- a/packages/bot/src/core/discord/autocompleteClan/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { searchClansAcrossRegions } from '@blitzkit/core'; -import { - ApplicationCommandOptionChoiceData, - AutocompleteInteraction, - CacheType, -} from 'discord.js'; -import { translator } from '../../localization/translator'; -import { DISCORD_CHOICES_MAX_NAME_SIZE, OVERFLOW_SUFFIX } from './constants'; - -export async function autocompleteClan( - interaction: AutocompleteInteraction, -) { - const { strings } = translator(interaction.locale); - const focusedOption = interaction.options.getFocused(true); - if (focusedOption.name !== 'clan') return; - if (focusedOption.value.length < 2) return interaction.respond([]); - const clans = await searchClansAcrossRegions(focusedOption.value); - - await interaction.respond( - clans - ? clans.map((clan) => { - let name = `${clan.name} (${strings.common.regions.short[clan.region]})`; - - if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { - const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; - - name = `${clan.name.slice( - 0, - clan.name.length - overSize - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX} (${strings.common.regions.short[clan.region]})`; - } - - return { - name, - value: `${clan.region}/${clan.clan_id}`, - } satisfies ApplicationCommandOptionChoiceData; - }) - : [], - ); -} diff --git a/packages/bot/src/core/discord/autocompleteTanks.ts b/packages/bot/src/core/discord/autocompleteTanks.ts deleted file mode 100644 index e43c975fb..000000000 --- a/packages/bot/src/core/discord/autocompleteTanks.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { fetchTankNames, SEARCH_KEYS, TankType } from "@blitzkit/core"; -import { - type ApplicationCommandOptionChoiceData, - AutocompleteInteraction, - type CacheType, -} from "discord.js"; -import { go } from "fuzzysort"; -import { tankNames } from "../blitzkit/nonBlockingPromises"; -import { translator } from "../localization/translator"; -import { - DISCORD_CHOICES_MAX_NAME_SIZE, - OVERFLOW_SUFFIX, -} from "./autocompleteClan/constants"; - -export const tankNamesTechTreeOnly = fetchTankNames().then((names) => - names.filter((tank) => tank.treeType === TankType.TANK_TYPE_RESEARCHABLE), -); - -export async function autocompleteTanks( - interaction: AutocompleteInteraction, - techTreeOnly = false, - fields = ["tank"], -) { - const { unwrap } = translator(interaction.locale); - const focusedOption = interaction.options.getFocused(true); - if (!fields.includes(focusedOption.name)) return; - - await interaction.respond( - focusedOption.value - ? await Promise.all( - go( - focusedOption.value, - await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), - { keys: SEARCH_KEYS, limit: 10 }, - ).map(async (item) => { - let name = unwrap(item.obj.searchableName); - - if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { - const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; - - name = `${unwrap(item.obj.searchableName).slice( - 0, - unwrap(item.obj.searchableName).length - - overSize - - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX}`; - } - - return { - name, - value: `${item.obj.id}`, - } satisfies ApplicationCommandOptionChoiceData; - }), - ) - : [], - ); -} diff --git a/packages/bot/src/core/discord/autocompleteUsername.ts b/packages/bot/src/core/discord/autocompleteUsername.ts deleted file mode 100644 index 522721c84..000000000 --- a/packages/bot/src/core/discord/autocompleteUsername.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { searchPlayersAcrossRegions } from '@blitzkit/core'; -import { - ApplicationCommandOptionChoiceData, - AutocompleteInteraction, - CacheType, -} from 'discord.js'; -import { translator } from '../localization/translator'; -import { - DISCORD_CHOICES_MAX_NAME_SIZE, - OVERFLOW_SUFFIX, -} from './autocompleteClan/constants'; - -export async function autocompleteUsername( - interaction: AutocompleteInteraction, -) { - const { strings } = translator(interaction.locale); - const focusedOption = interaction.options.getFocused(true); - if (focusedOption.name !== 'username') return; - const players = await searchPlayersAcrossRegions(focusedOption.value); - - try { - await interaction.respond( - players - ? players.map((player) => { - let name = `${player.nickname} (${ - strings.common.regions.short[player.region] - })`; - - if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { - const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; - - name = `${player.nickname.slice( - 0, - player.nickname.length - overSize - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX} (${ - strings.common.regions.short[player.region] - })`; - } - - return { - name, - value: `${player.account_id}`, - } satisfies ApplicationCommandOptionChoiceData; - }) - : [], - ); - } catch (error) {} -} diff --git a/packages/bot/src/core/discord/buttonLink.ts b/packages/bot/src/core/discord/buttonLink.ts deleted file mode 100644 index 65751dd57..000000000 --- a/packages/bot/src/core/discord/buttonLink.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - ButtonBuilder, - ButtonStyle, - type ComponentEmojiResolvable, -} from "discord.js"; - -export function buttonLink( - url: string, - label: string, - emoji?: ComponentEmojiResolvable -) { - const button = new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setURL(url) - .setLabel(label); - - if (emoji) button.setEmoji(emoji); - - return button; -} diff --git a/packages/bot/src/core/discord/buttonPrimary.ts b/packages/bot/src/core/discord/buttonPrimary.ts deleted file mode 100644 index 2fa4733a0..000000000 --- a/packages/bot/src/core/discord/buttonPrimary.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - ButtonBuilder, - ButtonStyle, - type ComponentEmojiResolvable, -} from "discord.js"; - -export function buttonPrimary( - id: string, - label: string, - emoji?: ComponentEmojiResolvable -) { - const button = new ButtonBuilder() - .setCustomId(id) - .setStyle(ButtonStyle.Primary) - .setLabel(label); - - if (emoji) button.setEmoji(emoji); - - return button; -} diff --git a/packages/bot/src/core/discord/buttonRefresh.ts b/packages/bot/src/core/discord/buttonRefresh.ts deleted file mode 100644 index fdf21e961..000000000 --- a/packages/bot/src/core/discord/buttonRefresh.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { translator } from '../localization/translator'; -import { buttonPrimary } from './buttonPrimary'; - -export function buttonRefresh( - interaction: ChatInputCommandInteraction, - path: string, -) { - const { strings } = translator(interaction.locale); - - if ( - interaction.appPermissions?.has('ReadMessageHistory') && - interaction.appPermissions?.has('ViewChannel') - ) { - return buttonPrimary(path, strings.bot.common.buttons.refresh); - } - - return null; -} diff --git a/packages/bot/src/core/discord/chunkLines.ts b/packages/bot/src/core/discord/chunkLines.ts deleted file mode 100644 index dfcdef6d0..000000000 --- a/packages/bot/src/core/discord/chunkLines.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function chunkLines(lines: string[], joiner = '\n', maxSize = 2000) { - const chunks = ['']; - - lines.map((line) => { - if (chunks.at(-1)!.length + line.length > maxSize) { - chunks.push(''); - } - - if (chunks.at(-1)!.length === 0) { - chunks[chunks.length - 1] += line; - } else { - chunks[chunks.length - 1] += joiner + line; - } - }); - - return chunks; -} diff --git a/packages/bot/src/core/discord/client.ts b/packages/bot/src/core/discord/client.ts deleted file mode 100644 index a83fb3822..000000000 --- a/packages/bot/src/core/discord/client.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Client, GatewayIntentBits } from "discord.js"; -import { interactionCreate } from "../../events/interactionCreate"; -import { ready } from "../../events/ready"; - -export const client = new Client({ - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers], -}) - .on("clientReady", ready) - .on("interactionCreate", interactionCreate); diff --git a/packages/bot/src/core/discord/colors.ts b/packages/bot/src/core/discord/colors.ts deleted file mode 100644 index bb2c7f51d..000000000 --- a/packages/bot/src/core/discord/colors.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const ACCENT_COLOR = '#ecedee'; -export const POSITIVE_COLOR = '#82ff29'; -export const NEGATIVE_COLOR = '#ff4747'; -export const WARNING_COLOR = '#ffab00'; diff --git a/packages/bot/src/core/discord/commandToURL.ts b/packages/bot/src/core/discord/commandToURL.ts deleted file mode 100644 index 91455adeb..000000000 --- a/packages/bot/src/core/discord/commandToURL.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { type CacheType, ChatInputCommandInteraction } from "discord.js"; - -export interface Parameters { - [key: string]: string | number | boolean | undefined | null; -} - -// TODO: automatically infer command options??? -// but how would that work with the region and id options? - -export function commandToURL( - interaction: ChatInputCommandInteraction, - parameters?: Parameters -) { - const pathname = [ - interaction.commandName, - interaction.options.getSubcommandGroup(false), - interaction.options.getSubcommand(false), - ] - .filter(Boolean) - .join("/"); - const params = (parameters ? Object.entries(parameters) : []) - .filter(([, value]) => value !== undefined && value !== null) - .map(([key, value]) => `${key}=${value}`) - .join("&"); - - return `${pathname}${params.length > 0 ? `?${params}` : ""}`; -} diff --git a/packages/bot/src/core/discord/createLocalizedCommand.ts b/packages/bot/src/core/discord/createLocalizedCommand.ts deleted file mode 100644 index acb00ca49..000000000 --- a/packages/bot/src/core/discord/createLocalizedCommand.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { type BlitzKitStrings } from "@blitzkit/i18n"; -import { - Locale, - SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from "discord.js"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -interface CreateLocalizedCommandSubcommand { - subcommand: string; - modify?: (option: SlashCommandSubcommandBuilder) => void; -} - -interface CreateLocalizedCommandSubcommandGroup { - group: string; - modify?: (option: SlashCommandSubcommandGroupBuilder) => void; -} - -export function createLocalizedCommand( - command: string, - sub?: - | CreateLocalizedCommandSubcommand[] - | CreateLocalizedCommandSubcommandGroup[], -) { - const commandPathItem = command.replaceAll( - "-", - "_", - ) as keyof BlitzKitStrings["bot"]["commands"]; - const { strings } = translator(Locale.EnglishUS); - const slashCommand = new SlashCommandBuilder() - .setName(command) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands[commandPathItem].name, - undefined, - true, - ), - ) - .setDescription(strings.bot.commands[commandPathItem].description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.commands[commandPathItem].description, - ), - ); - - if (sub) { - sub.map((subItem) => { - if ("subcommand" in subItem) { - const subcommandNameLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - subcommands: Record; - } - ).subcommands[subItem.subcommand].name, - undefined, - true, - ); - const subcommandDescriptionLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - subcommands: Record; - } - ).subcommands[subItem.subcommand].description, - ); - - slashCommand.addSubcommand((option) => { - if (subItem.modify) subItem.modify(option); - - return option - .setName(subItem.subcommand) - .setNameLocalizations(subcommandNameLocalizations) - .setDescription( - ( - strings.bot.commands[commandPathItem] as unknown as { - subcommands: Record; - } - ).subcommands[subItem.subcommand].description, - ) - .setDescriptionLocalizations(subcommandDescriptionLocalizations); - }); - } else { - const subcommandNameLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - groups: Record; - } - ).groups[subItem.group].name, - undefined, - true, - ); - const subcommandDescriptionLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - groups: Record; - } - ).groups[subItem.group].description, - ); - - slashCommand.addSubcommandGroup((option) => { - if (subItem.modify) subItem.modify(option); - - return option - .setName(subItem.group) - .setNameLocalizations(subcommandNameLocalizations) - .setDescription( - ( - strings.bot.commands[commandPathItem] as unknown as { - groups: Record; - } - ).groups[subItem.group].description, - ) - .setDescriptionLocalizations(subcommandDescriptionLocalizations); - }); - } - }); - } - - return slashCommand; -} diff --git a/packages/bot/src/core/discord/embedInfo.ts b/packages/bot/src/core/discord/embedInfo.ts deleted file mode 100644 index 5359e3212..000000000 --- a/packages/bot/src/core/discord/embedInfo.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { ACCENT_COLOR } from './colors'; - -export function embedInfo(title: string, description: string) { - const embed = new EmbedBuilder() - .setColor(ACCENT_COLOR) - .setDescription(description); - - if (title) embed.setTitle(title); - - return embed; -} diff --git a/packages/bot/src/core/discord/embedNegative.ts b/packages/bot/src/core/discord/embedNegative.ts deleted file mode 100644 index 1d520500a..000000000 --- a/packages/bot/src/core/discord/embedNegative.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { NEGATIVE_COLOR } from './colors'; - -export function embedNegative(title: string, description: string) { - return new EmbedBuilder() - .setColor(NEGATIVE_COLOR) - .setTitle(title) - .setDescription(description); -} diff --git a/packages/bot/src/core/discord/embedPositive.ts b/packages/bot/src/core/discord/embedPositive.ts deleted file mode 100644 index 5f157a851..000000000 --- a/packages/bot/src/core/discord/embedPositive.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { POSITIVE_COLOR } from './colors'; - -export function embedPositive(title: string, description: string) { - return new EmbedBuilder() - .setColor(POSITIVE_COLOR) - .setTitle(title) - .setDescription(description); -} diff --git a/packages/bot/src/core/discord/embedWarning.ts b/packages/bot/src/core/discord/embedWarning.ts deleted file mode 100644 index 0cb912d9e..000000000 --- a/packages/bot/src/core/discord/embedWarning.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { WARNING_COLOR } from './colors'; - -export function embedWarning(title: string, description: string) { - return new EmbedBuilder() - .setColor(WARNING_COLOR) - .setTitle(title) - .setDescription(description); -} diff --git a/packages/bot/src/core/discord/getCustomPeriodParams.ts b/packages/bot/src/core/discord/getCustomPeriodParams.ts deleted file mode 100644 index eaa19e7fa..000000000 --- a/packages/bot/src/core/discord/getCustomPeriodParams.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ChatInputCommandInteraction } from "discord.js"; -import type { PeriodType } from "./addPeriodSubCommands"; - -export function getCustomPeriodParams( - interaction: ChatInputCommandInteraction, - forcedNonCustomPeriod = false -) { - if ( - !forcedNonCustomPeriod && - (interaction.options.getSubcommand() as PeriodType) === "custom" - ) { - return { - start: interaction.options.getInteger("start", true), - end: interaction.options.getInteger("end", true), - }; - } - - return undefined; -} diff --git a/packages/bot/src/core/discord/getFiltersFromButton.ts b/packages/bot/src/core/discord/getFiltersFromButton.ts deleted file mode 100644 index 37689b7f0..000000000 --- a/packages/bot/src/core/discord/getFiltersFromButton.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { TankClass, TankType } from "@blitzkit/core"; -import { ButtonInteraction } from "discord.js"; -import type { StatFilters } from "../blitzstars/filterStats"; - -export function getFiltersFromButton(interaction: ButtonInteraction) { - const url = new URL(`https://example.com/${interaction.customId}`); - - return { - nation: url.searchParams.get("nation") ?? undefined, - tank: parseInt(url.searchParams.get("tank") ?? "0") || undefined, - class: - (Number(url.searchParams.get("class")) as TankClass | undefined) ?? - undefined, - type: - (Number(url.searchParams.get("type")) as TankType | undefined) ?? - undefined, - tier: parseInt(url.searchParams.get("tier") ?? "0") || undefined, - } satisfies StatFilters; -} diff --git a/packages/bot/src/core/discord/getFiltersFromCommand.ts b/packages/bot/src/core/discord/getFiltersFromCommand.ts deleted file mode 100644 index d4cac40cf..000000000 --- a/packages/bot/src/core/discord/getFiltersFromCommand.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { resolveTankId } from '../blitz/resolveTankId'; -import type { StatFilters } from '../blitzstars/filterStats'; - -export async function getFiltersFromCommand( - interaction: ChatInputCommandInteraction, -) { - const nationRaw = interaction.options.getString('nation'); - const tierRaw = interaction.options.getString('tier'); - const classRaw = interaction.options.getString('class'); - const typeRaw = interaction.options.getString('type'); - const tankRaw = interaction.options.getString('tank'); - - return { - nation: nationRaw ?? undefined, - tier: tierRaw === null ? undefined : Number(tierRaw), - class: classRaw === null ? undefined : Number(classRaw), - type: typeRaw === null ? undefined : Number(typeRaw), - tank: - tankRaw === null - ? undefined - : await resolveTankId(tankRaw, interaction.locale), - } satisfies StatFilters; -} diff --git a/packages/bot/src/core/discord/getPeriodOptionName.ts b/packages/bot/src/core/discord/getPeriodOptionName.ts deleted file mode 100644 index 1eaea0772..000000000 --- a/packages/bot/src/core/discord/getPeriodOptionName.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { translator } from "../localization/translator"; -import type { PeriodSize } from "./addPeriodSubCommands"; - -export function getPeriodOptionName(period: PeriodSize, locale: Locale) { - const { strings } = translator(locale); - if (period === "career") return strings.bot.common.periods.career; - if (period === "today") return strings.bot.common.periods.today; - return literals(strings.bot.common.periods.days, { days: period }); -} diff --git a/packages/bot/src/core/discord/localizationObject.ts b/packages/bot/src/core/discord/localizationObject.ts deleted file mode 100644 index 637a6d604..000000000 --- a/packages/bot/src/core/discord/localizationObject.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { type BlitzKitStrings, literals as literalsFunc } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { SUPPORTED_LOCALES_DISCORD } from "../localization/strings/constants"; -import { translator } from "../localization/translator"; - -const validNameRegex = - /^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u; - -export function localizationObject( - path: (strings: BlitzKitStrings) => string, - literals?: Record, - strict = false -) { - return SUPPORTED_LOCALES_DISCORD.reduce>>( - (localizations, locale) => { - const { strings } = translator(locale); - const translation = literalsFunc(path(strings), literals); - const isValid = - (!strict || validNameRegex.test(translation)) && translation.length > 0; - - if (!isValid) { - // console.warn( - // `Invalid localization for ${locale} (${path}): ${translation}; skipping...` - // ); - return localizations; - } - - return { - ...localizations, - [locale]: translation, - }; - }, - {} - ); -} diff --git a/packages/bot/src/core/discord/markdownTable.ts b/packages/bot/src/core/discord/markdownTable.ts deleted file mode 100644 index 6ad0b6f64..000000000 --- a/packages/bot/src/core/discord/markdownTable.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type TableInputEntry = [string, string | number] | []; -export type TableInput = TableInputEntry[]; - -export function markdownTable(input: TableInput) { - return input - .filter((array) => array[1] !== undefined) - .map((array) => (array.length === 0 ? '' : `**${array[0]}**: ${array[1]}`)) - .join('\n'); -} diff --git a/packages/bot/src/core/discord/normalizeInteractionReturnable.tsx b/packages/bot/src/core/discord/normalizeInteractionReturnable.tsx deleted file mode 100644 index 7e67b9767..000000000 --- a/packages/bot/src/core/discord/normalizeInteractionReturnable.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { - ActionRowBuilder, - AttachmentBuilder, - ButtonBuilder, - EmbedBuilder, - type InteractionEditReplyOptions, - type InteractionReplyOptions, - type MessageEditOptions, -} from "discord.js"; -import type { InteractionReturnable } from "../../events/interactionCreate"; -import type { Writeable } from "../../types/writable"; -import { jsxToPngThreaded } from "../blitzkit/jsxToPngThreaded"; -import { RenderConfiguration } from "../blitzkit/renderConfiguration"; - -export async function normalizeInteractionReturnable( - returnable: InteractionReturnable -) { - const images: [number, Buffer][] = []; - const reply: InteractionEditReplyOptions & - InteractionReplyOptions & - MessageEditOptions = {}; - const awaitedReturnable = await returnable; - const normalizedReturnable = Array.isArray(awaitedReturnable) - ? awaitedReturnable - : [awaitedReturnable]; - let renderConfiguration = new RenderConfiguration(); - - await Promise.all( - normalizedReturnable.map(async (item, index) => { - if (typeof item === "string") { - reply.content = item; - } else if (item instanceof EmbedBuilder) { - if (!reply.embeds) reply.embeds = []; - (reply.embeds as Writeable).push(item); - } else if (item instanceof ButtonBuilder) { - if (!reply.components) - reply.components = [new ActionRowBuilder()]; - (reply.components[0] as ActionRowBuilder).addComponents( - item - ); - } else if (item instanceof AttachmentBuilder) { - if (!reply.files) reply.files = []; - (reply.files as Writeable).push(item); - } else if (item instanceof RenderConfiguration) { - renderConfiguration = item; - } else if (item === null) { - return; - } else { - const image = await jsxToPngThreaded(item, renderConfiguration); - images.push([index, image]); - } - }) - ); - - if (images.length > 0) { - images.sort(([a], [b]) => b - a); - if (!reply.files) reply.files = []; - images.forEach(([, image]) => - (reply.files as Writeable)?.push(image) - ); - } - - return reply; -} diff --git a/packages/bot/src/core/discord/psa.ts b/packages/bot/src/core/discord/psa.ts deleted file mode 100644 index 42dfe93eb..000000000 --- a/packages/bot/src/core/discord/psa.ts +++ /dev/null @@ -1,36 +0,0 @@ -export type PSAData = { - secret?: boolean; - links?: { - title: string; - url: string; - }[]; - commands?: string[]; -} & ( - | { - type?: 'embed'; - title: string; - description: string; - image?: string; - } - | { - type: 'image'; - image: string; - title?: string; - } -); - -export interface PSA { - data: PSAData | null; -} - -export const psa: PSA = { - data: null, -}; - -fetch( - 'https://gist.githubusercontent.com/tresabhi/ed4b136f08856e615212e573aabe1968/raw', -) - .then((response) => response.json()) - .then((data) => { - psa.data = data as PSAData; - }); diff --git a/packages/bot/src/core/discord/resolveClanFromCommand.ts b/packages/bot/src/core/discord/resolveClanFromCommand.ts deleted file mode 100644 index 2a78793b6..000000000 --- a/packages/bot/src/core/discord/resolveClanFromCommand.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { ChatInputCommandInteraction } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { searchClansAcrossRegionsBotWrapper } from "../blitz/searchClansAcrossRegionsBotWrapper"; -import { UserError } from "../blitzkit/userError"; -import { translator } from "../localization/translator"; - -export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/; - -export async function resolveClanFromCommand( - interaction: ChatInputCommandInteraction -) { - const { strings } = translator(interaction.locale); - const clan = interaction.options.getString("clan", true); - - if (serverAndIdPattern.test(clan)) { - const [server, accountId] = clan.split("/"); - return { region: server as Region, id: Number(accountId) }; - } else { - const accounts = await searchClansAcrossRegionsBotWrapper( - clan, - interaction.locale - ); - - if (accounts[0]) { - return { region: accounts[0].region, id: accounts[0].clan_id }; - } else { - throw new UserError( - literals(strings.bot.common.errors.clan_not_found, { - search: markdownEscape(clan), - }) - ); - } - } -} diff --git a/packages/bot/src/core/discord/resolvePeriodFromButton.ts b/packages/bot/src/core/discord/resolvePeriodFromButton.ts deleted file mode 100644 index cb9388bdf..000000000 --- a/packages/bot/src/core/discord/resolvePeriodFromButton.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getTimeDaysAgo, type Region } from "@blitzkit/core"; -import { ButtonInteraction } from "discord.js"; -import { getPeriodNow } from "../blitzkit/getPeriodNow"; -import { getPeriodStart } from "../blitzkit/getPeriodStart"; -import type { PeriodType } from "./addPeriodSubCommands"; -import { getPeriodOptionName } from "./getPeriodOptionName"; -import type { ResolvedPeriod } from "./resolvePeriodFromCommand"; - -export function resolvePeriodFromButton( - region: Region, - interaction: ButtonInteraction -) { - let name: string; - let start: number; - let end: number; - const url = new URL(`https://example.com/${interaction.customId}`); - const path = url.pathname.split("/"); - const periodSubcommand = path.at(-1) as PeriodType; - - if (periodSubcommand === "custom") { - const startRaw = parseInt(url!.searchParams.get("start")!); - const endRaw = parseInt(url!.searchParams.get("end")!); - const startDaysAgoMin = Math.min(startRaw, endRaw); - const endDaysAgoMax = Math.max(startRaw, endRaw); - - name = `${startDaysAgoMin} - ${endDaysAgoMax} days`; - start = getTimeDaysAgo(region, endDaysAgoMax); - end = getTimeDaysAgo(region, startDaysAgoMin); - } else { - name = getPeriodOptionName(periodSubcommand, interaction.locale); - start = getPeriodStart(region, periodSubcommand); - end = getPeriodNow(); - } - - return { - name, - start, - end, - } satisfies ResolvedPeriod; -} diff --git a/packages/bot/src/core/discord/resolvePeriodFromCommand.ts b/packages/bot/src/core/discord/resolvePeriodFromCommand.ts deleted file mode 100644 index 55ad2af4e..000000000 --- a/packages/bot/src/core/discord/resolvePeriodFromCommand.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getTimeDaysAgo, type Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { type CacheType, ChatInputCommandInteraction } from "discord.js"; -import { getPeriodNow } from "../blitzkit/getPeriodNow"; -import { getPeriodStart } from "../blitzkit/getPeriodStart"; -import { translator } from "../localization/translator"; -import type { PeriodType } from "./addPeriodSubCommands"; -import { getPeriodOptionName } from "./getPeriodOptionName"; - -export interface ResolvedPeriod { - name: string; - start: number; - end: number; -} - -export function resolvePeriodFromCommand( - region: Region, - interaction: ChatInputCommandInteraction, - forcedPeriod?: PeriodType -) { - const { strings } = translator(interaction.locale); - let name: string; - let start: number; - let end: number; - const periodSubcommand = forcedPeriod - ? forcedPeriod - : (interaction.options.getSubcommand() as PeriodType); - - if (periodSubcommand === "custom") { - const startOption = interaction.options.getInteger("start", true); - const endOption = interaction.options.getInteger("end", true); - const startDaysAgoMin = Math.min(startOption, endOption); - const endDaysAgoMax = Math.max(startOption, endOption); - - name = literals(strings.bot.common.periods.custom, { - day_1: startDaysAgoMin, - day_2: endDaysAgoMax, - }); - start = getTimeDaysAgo(region, endDaysAgoMax); - end = getTimeDaysAgo(region, startDaysAgoMin); - } else { - name = getPeriodOptionName(periodSubcommand, interaction.locale); - start = getPeriodStart(region, periodSubcommand); - end = getPeriodNow(); - } - - return { - name, - start, - end, - } satisfies ResolvedPeriod; -} diff --git a/packages/bot/src/core/discord/resolvePlayerFromButton.ts b/packages/bot/src/core/discord/resolvePlayerFromButton.ts deleted file mode 100644 index 06b3e2895..000000000 --- a/packages/bot/src/core/discord/resolvePlayerFromButton.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Region } from "@blitzkit/core"; -import { ButtonInteraction } from "discord.js"; -import type { ResolvedPlayer } from "./resolvePlayerFromCommand"; - -export async function resolvePlayerFromButton(interaction: ButtonInteraction) { - const url = new URL(`https://exmaple.com/${interaction.customId}`); - - return { - id: parseInt(url.searchParams.get("id")!), - region: url.searchParams.get("region") as Region, - } satisfies ResolvedPlayer; -} diff --git a/packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts b/packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts deleted file mode 100644 index c76e6c70e..000000000 --- a/packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { idToRegion, type Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { type CacheType, ChatInputCommandInteraction } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { searchPlayersAcrossRegionsBotWrapper } from "../../blitz/searchPlayersAcrossRegionsBotWrapper"; -import { getBlitzFromDiscord } from "../../blitzkit/getBlitzFromDiscord"; -import { UserError } from "../../blitzkit/userError"; -import { translator } from "../../localization/translator"; - -export interface ResolvedPlayer { - region: Region; - id: number; -} - -export async function resolvePlayerFromCommand( - interaction: ChatInputCommandInteraction -) { - const { strings } = translator(interaction.locale); - const commandUsername = interaction.options.getString("username"); - - if (commandUsername !== null) { - const id = parseInt(commandUsername); - - if (!isNaN(id)) { - const region = idToRegion(id); - return { region, id } satisfies ResolvedPlayer; - } else { - const accounts = await searchPlayersAcrossRegionsBotWrapper( - commandUsername, - interaction.locale - ); - - if (accounts[0]) { - return { - region: accounts[0].region, - id: accounts[0].account_id, - } satisfies ResolvedPlayer; - } else { - throw new UserError( - literals(strings.bot.common.errors.player_not_found, { - search: markdownEscape(commandUsername), - }) - ); - } - } - } else { - const account = await getBlitzFromDiscord(BigInt(interaction.user.id)); - - if (account) return account; - - throw new UserError(strings.bot.common.errors.player_not_linked); - } -} diff --git a/packages/bot/src/core/localization/strings/constants.ts b/packages/bot/src/core/localization/strings/constants.ts deleted file mode 100644 index 4cb69a89c..000000000 --- a/packages/bot/src/core/localization/strings/constants.ts +++ /dev/null @@ -1,25 +0,0 @@ -import locales from "@blitzkit/i18n/locales.json"; -import { Locale } from "discord.js"; - -const SUPPORTED_LOCALES_DISCORD_MAP: Record = { - en: Locale.EnglishUS, - es: Locale.SpanishES, - pt: Locale.PortugueseBR, - ru: Locale.Russian, - uk: Locale.Ukrainian, - zh: Locale.ChineseCN, - fr: Locale.French, - ja: Locale.Japanese, - pl: Locale.Polish, -}; - -export const SUPPORTED_LOCALES_DISCORD_MAP_INVERSE = Object.fromEntries( - Object.entries(SUPPORTED_LOCALES_DISCORD_MAP).map(([k, v]) => [v, k]) -) as Record; - -export const SUPPORTED_LOCALES_DISCORD = Object.values( - SUPPORTED_LOCALES_DISCORD_MAP -); - -export const DEFAULT_LOCALE_DISCORD = - SUPPORTED_LOCALES_DISCORD_MAP[locales.default]; diff --git a/packages/bot/src/core/localization/translator.ts b/packages/bot/src/core/localization/translator.ts deleted file mode 100644 index 37168ac30..000000000 --- a/packages/bot/src/core/localization/translator.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { STRINGS, unwrapper } from '@blitzkit/i18n'; -import { Locale } from 'discord.js'; -import { - DEFAULT_LOCALE_DISCORD, - SUPPORTED_LOCALES_DISCORD, - SUPPORTED_LOCALES_DISCORD_MAP_INVERSE, -} from './strings/constants'; - -export function translator(localeRaw: Locale) { - const locale = - localeRaw !== DEFAULT_LOCALE_DISCORD && - SUPPORTED_LOCALES_DISCORD.includes(localeRaw) - ? localeRaw - : DEFAULT_LOCALE_DISCORD; - const bkLocale = SUPPORTED_LOCALES_DISCORD_MAP_INVERSE[locale]; - const strings = STRINGS[bkLocale]; - const unwrap = unwrapper(bkLocale); - - return { locale, strings, unwrap, bkLocale }; -} diff --git a/packages/bot/src/core/radix/radixColors.ts b/packages/bot/src/core/radix/radixColors.ts deleted file mode 100644 index dfe17a359..000000000 --- a/packages/bot/src/core/radix/radixColors.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as colors from "@radix-ui/colors"; -import { Theme } from "@radix-ui/themes"; -import type { ComponentProps } from "react"; - -export type AccentColor = Exclude< - ComponentProps["accentColor"], - "gray" | undefined ->; -export type AccentColorDark = `${AccentColor}Dark`; -export type GrayColor = Exclude< - ComponentProps["grayColor"], - "auto" | undefined ->; -export type GrayColorDark = `${GrayColor}Dark`; - -export const ACCENT_COLORS: AccentColor[] = [ - "tomato", - "red", - "ruby", - "crimson", - "pink", - "plum", - "purple", - "violet", - "iris", - "indigo", - "blue", - "cyan", - "teal", - "jade", - "green", - "grass", - "brown", - "orange", - "sky", - "mint", - "lime", - "yellow", - "amber", - "gold", - "bronze", -]; -export const GRAY_COLORS: GrayColor[] = [ - "gray", - "mauve", - "slate", - "sage", - "olive", - "sand", -]; - -export const PALETTES: Record< - AccentColor | AccentColorDark | GrayColor | GrayColorDark, - { [key: `${string}${number}`]: string } -> = colors; diff --git a/packages/bot/src/core/streams/wotbreplay.ts b/packages/bot/src/core/streams/wotbreplay.ts deleted file mode 100644 index 47d18d9d3..000000000 --- a/packages/bot/src/core/streams/wotbreplay.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ReadStream } from "@blitzkit/core"; -import AdmZip from "adm-zip"; -import { readFile } from "fs/promises"; -import { Parser } from "pickleparser"; -import protobuf from "protobufjs"; - -interface WotbReplayMetaJson { - version: string; - title: string; - dbid: string; - playerName: string; - battleStartTime: string; - playerVehicleName: string; - mapName: string; - arenaUniqueId: string; - battleDuration: number; - vehicleCompDescriptor: number; - camouflageId: number; - mapId: number; - arenaBonusType: number; -} - -export class WotbReplayReadStream extends ReadStream { - async wotbReplay() { - const pickleParser = new Parser(); - const zip = new AdmZip(Buffer.from(this.buffer)); - const metaJsonBuffer = zip.getEntry("meta.json")?.getData(); - const battleResultsDat = zip.getEntry("battle_results.dat")?.getData(); - - if (!metaJsonBuffer || !battleResultsDat) - throw new SyntaxError("meta.json not found"); - - const metaJson = JSON.parse( - metaJsonBuffer.toString() - ) as WotbReplayMetaJson; - const unpickledBattleResults = pickleParser.parse(battleResultsDat); - const [, battleResultsString] = unpickledBattleResults as [number, string]; - const root = await protobuf.load( - `${__dirname}/../../protos/BattleResultsClient.proto` - ); - const BattleResults = root.lookupType( - "BattleResultsGenerated.BattleResultsClient" - ); - - const reader = protobuf.Reader.create(Buffer.from(battleResultsString)); - - // const decodedBattleResults = BattleResults.decodeDelimited( - // Buffer.from(battleResultsString), - // ); - // console.log(decodedBattleResults.toJSON()); - } -} - -const replay = await readFile("temp/test3.wotbreplay"); -const stream = new WotbReplayReadStream(replay.buffer as ArrayBuffer); - -stream.wotbReplay(); diff --git a/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts b/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts deleted file mode 100644 index 59ec8f072..000000000 --- a/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AutocompleteInteraction, CacheType } from "discord.js"; -import { commands } from ".."; - -export async function handleAutocomplete( - interaction: AutocompleteInteraction, -) { - const command = commands[interaction.commandName]; - - if (!command) { - console.error(`No command matching ${interaction.commandName} was found.`); - return; - } - - if (command.autocomplete) command.autocomplete(interaction); -} diff --git a/packages/bot/src/events/interactionCreate/handlers/button.tsx b/packages/bot/src/events/interactionCreate/handlers/button.tsx deleted file mode 100644 index 8ac4b7269..000000000 --- a/packages/bot/src/events/interactionCreate/handlers/button.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ButtonInteraction, CacheType } from "discord.js"; -import { commands } from ".."; -import { normalizeInteractionReturnable } from "../../../core/discord/normalizeInteractionReturnable"; - -export async function handleButton(interaction: ButtonInteraction) { - await interaction.deferUpdate(); - - const url = new URL(`https://example.com/${interaction.customId}`); - const commandName = url.pathname.split("/")[1]; - const button = commands[commandName]?.button; - - if (!button) { - throw new Error(`Button handler not found for "${commandName}"`); - } - - if (interaction.message.interaction?.user.id !== interaction.user.id) { - interaction.followUp({ - ephemeral: true, - content: "You are not the author of this command.", - }); - return; - } - - await interaction.message.edit( - await normalizeInteractionReturnable(button(interaction)), - ); -} diff --git a/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts b/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts deleted file mode 100644 index d385a036a..000000000 --- a/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { - ActionRowBuilder, - AttachmentBuilder, - ButtonBuilder, - ButtonStyle, - type CacheType, - ChatInputCommandInteraction, - type InteractionReplyOptions, -} from "discord.js"; -import { type InteractionRawReturnable, commands } from ".."; -import { UserError } from "../../../core/blitzkit/userError"; -import { buttonLink } from "../../../core/discord/buttonLink"; -import { embedWarning } from "../../../core/discord/embedWarning"; -import { normalizeInteractionReturnable } from "../../../core/discord/normalizeInteractionReturnable"; -import { psa } from "../../../core/discord/psa"; -import { translator } from "../../../core/localization/translator"; -import type { Writeable } from "../../../types/writable"; - -export async function handleChatInputCommand( - interaction: ChatInputCommandInteraction, -) { - await interaction.deferReply(); - - const registry = commands[interaction.commandName]; - - try { - const returnable = await registry.handler(interaction); - - if (registry.handlesInteraction) return; - - const chunks: InteractionRawReturnable[][] = [[]]; - - (Array.isArray(returnable) ? returnable : [returnable]).forEach( - (object) => { - if (typeof object === "string") { - chunks.push([object]); - } else chunks.at(-1)?.push(object!); - }, - ); - - let index = 0; - for (const chunk of chunks.filter((chunk) => chunk.length > 0)) { - const reply = await normalizeInteractionReturnable(chunk); - - if ( - psa.data && - !psa.data.secret && - (psa.data.commands === undefined || - psa.data.commands.includes(interaction.commandName)) - ) { - if (psa.data.type === undefined || psa.data.type === "embed") { - if (!reply.embeds) reply.embeds = []; - - const embed = embedWarning(psa.data.title, psa.data.description); - - if (psa.data.image) embed.setImage(psa.data.image); - (reply.embeds as Writeable).push(embed); - } else if (psa.data.type === "image") { - if (!reply.files) reply.files = []; - (reply.files as Writeable).push( - new AttachmentBuilder(psa.data.image), - ); - reply.content = reply.content ?? psa.data.title; - } - - if (psa.data.links) { - if (!reply.components?.[0]) { - reply.components = [new ActionRowBuilder()]; - } - - ( - reply.components[0] as ActionRowBuilder - ).addComponents( - psa.data.links.map(({ title, url }) => buttonLink(url, title)), - ); - } - } - - if (index === 0) { - await interaction.editReply(reply); - } else { - await interaction.followUp(reply); - } - - if ( - psa.data && - psa.data.secret && - (psa.data.commands === undefined || - psa.data.commands.includes(interaction.commandName)) - ) { - const followUp: InteractionReplyOptions = { - ephemeral: true, - }; - - if (psa.data.type === undefined || psa.data.type === "embed") { - if (!followUp.embeds) followUp.embeds = []; - - const embed = embedWarning(psa.data.title, psa.data.description); - - if (psa.data.image) embed.setImage(psa.data.image); - (followUp.embeds as Writeable).push(embed); - } else if (psa.data.type === "image") { - if (!followUp.files) followUp.files = []; - (followUp.files as Writeable).push( - new AttachmentBuilder(psa.data.image), - ); - followUp.content = followUp.content ?? psa.data.title; - } - - if (psa.data.links) { - if (!followUp.components?.[0]) { - followUp.components = [new ActionRowBuilder()]; - } - - ( - followUp.components[0] as ActionRowBuilder - ).addComponents( - psa.data.links.map(({ title, url }) => buttonLink(url, title)), - ); - } - - interaction.followUp(followUp); - } - - index++; - } - } catch (error) { - const { strings } = translator(interaction.locale); - - const components = [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel(strings.bot.common.errors.get_help) - .setURL("https://discord.gg/nDt7AjGJQH") - .setStyle(ButtonStyle.Link), - ), - ]; - if (error instanceof UserError) { - interaction.editReply({ - content: error.message, - components, - }); - } else { - console.error(interaction.commandName, error); - interaction.editReply({ - content: strings.bot.common.errors.uncaught_error, - components, - }); - } - } -} diff --git a/packages/bot/src/events/interactionCreate/index.ts b/packages/bot/src/events/interactionCreate/index.ts deleted file mode 100644 index 9eef8b024..000000000 --- a/packages/bot/src/events/interactionCreate/index.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { - AttachmentBuilder, - AutocompleteInteraction, - ButtonBuilder, - ButtonInteraction, - type CacheType, - ChatInputCommandInteraction, - EmbedBuilder, - type Interaction, - REST, - Routes, - SlashCommandBuilder, - type SlashCommandOptionsOnlyBuilder, - type SlashCommandSubcommandsOnlyBuilder, -} from "discord.js"; -import { aboutCommand } from "../../commands/about"; -import { breakdownCommand } from "../../commands/breakdown"; -import { debugCommand } from "../../commands/debug"; -import { evolutionCommand } from "../../commands/evolution"; -import { fullStatsCommand } from "../../commands/fullStats"; -import { inactiveCommand } from "../../commands/inactive"; -import { verifyCommand } from "../../commands/link"; -import { ownedTanksCommand } from "../../commands/ownedTanks"; -import { permissionsCommand } from "../../commands/permissions"; -import { pingCommand } from "../../commands/ping"; -import { playerInfoCommand } from "../../commands/playerInfo"; -import { ratingCommand } from "../../commands/rating"; -import { ratingLeaderboardCommand } from "../../commands/ratingLeaderboard"; -import { replayCommand } from "../../commands/replay"; -import { researchCommand } from "../../commands/research"; -import { searchClansCommand } from "../../commands/searchClans"; -import { searchPlayersCommand } from "../../commands/searchPlayers"; -import { statsCommand } from "../../commands/stats"; -import { todayCommand } from "../../commands/today"; -import { RenderConfiguration } from "../../core/blitzkit/renderConfiguration"; -import { handleAutocomplete } from "./handlers/autocomplete"; -import { handleButton } from "./handlers/button"; -import { handleChatInputCommand } from "./handlers/chatInputCommand"; - -export type InteractionRawReturnable = - | string - | EmbedBuilder - | ButtonBuilder - | AttachmentBuilder - | RenderConfiguration - | React.JSX.Element - | null; -export type InteractionIterableReturnable = - | InteractionRawReturnable - | InteractionRawReturnable[]; -export type InteractionReturnable = - | InteractionIterableReturnable - | Promise; - -interface CommandRegistryBase { - command: - | SlashCommandBuilder - | SlashCommandSubcommandsOnlyBuilder - | SlashCommandOptionsOnlyBuilder - | Omit; - - autocomplete?: (interaction: AutocompleteInteraction) => void; - button?: (interaction: ButtonInteraction) => InteractionReturnable; -} - -interface CommandRegistryImplicit extends CommandRegistryBase { - handlesInteraction: true; - handler: (interaction: ChatInputCommandInteraction) => void; -} - -interface CommandRegistryExplicit extends CommandRegistryBase { - handlesInteraction?: false; - handler: ( - interaction: ChatInputCommandInteraction, - ) => InteractionReturnable; -} - -export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit; - -export const commandsArray: Promise[] = [ - // auctionCommand, - permissionsCommand, - debugCommand, - aboutCommand, - inactiveCommand, - ownedTanksCommand, - playerInfoCommand, - searchClansCommand, - searchPlayersCommand, - fullStatsCommand, - breakdownCommand, - verifyCommand, - pingCommand, - evolutionCommand, - statsCommand, - ratingLeaderboardCommand, - todayCommand, - researchCommand, - ratingCommand, - replayCommand, -]; - -export const commands: Record = {}; -const commandsFulfilled: CommandRegistry[] = []; - -let index = 0; -for (const registry of await Promise.allSettled(commandsArray)) { - if (registry.status === "rejected") { - console.warn(`Command ${index} rejected`, registry.reason); - continue; - } - - commandsFulfilled.push(registry.value); - commands[registry.value.command.name] = registry.value; - index++; -} - -const rest = new REST().setToken(assertSecret(import.meta.env.DISCORD_TOKEN)); -const body = commandsFulfilled.map((registry) => registry.command.toJSON()); -const applicationCommandsRoute = Routes.applicationCommands( - assertSecret(import.meta.env.DISCORD_CLIENT_ID), -); - -await rest.put(applicationCommandsRoute, { body }); - -export async function interactionCreate(interaction: Interaction) { - if (interaction.isAutocomplete()) { - handleAutocomplete(interaction); - } else if (interaction.isChatInputCommand()) { - handleChatInputCommand(interaction); - } else if (interaction.isButton()) { - handleButton(interaction); - } -} diff --git a/packages/bot/src/events/ready.ts b/packages/bot/src/events/ready.ts deleted file mode 100644 index 2612b1908..000000000 --- a/packages/bot/src/events/ready.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Client } from 'discord.js'; - -export function ready(client: Client) { - console.log(`🟢 Launched bot ${client.shard?.ids[0] ?? 'default'}`); -} diff --git a/packages/bot/src/main.ts b/packages/bot/src/main.ts deleted file mode 100644 index 8bac09c3a..000000000 --- a/packages/bot/src/main.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { ActivityType, ShardingManager } from "discord.js"; -import { - discoveredIdsDefinitions, - tankDefinitions, -} from "./core/blitzkit/nonBlockingPromises"; - -let manager: ShardingManager; -let iteration = 0; -const interval = setInterval(async () => { - console.log(`Attempting launch on iteration ${++iteration}`); - - manager?.respawnAll(); - manager = new ShardingManager("dist/bot/workers/bot.js", { - token: assertSecret(import.meta.env.DISCORD_TOKEN), - }); - - const shards = await manager - .on("shardCreate", (shard) => { - clearInterval(interval); - console.log(`🟡 Launching shard ${shard.id}`); - shard.on("ready", () => console.log(`🟢 Launched shard ${shard.id}`)); - }) - .spawn({ timeout: 60000 }); - - try { - let servers = 0; - let channels = 0; - let users = 0; - - for (const [, shard] of shards) { - const guilds = (await shard.fetchClientValue("guilds.cache")) as any; - - servers += guilds.length; - - for (const guild of guilds) { - channels += guild.channels.length; - users += guild.memberCount; - } - } - - const awaitedTankDefinitions = await tankDefinitions; - const tanks = Object.values(awaitedTankDefinitions.tanks).length; - const awaitedDiscoveredIdsDefinitions = await discoveredIdsDefinitions; - const usernames = awaitedDiscoveredIdsDefinitions.count; - - manager.broadcastEval( - async (client, { servers, users, channels, tanks, usernames }) => { - const bios = [ - `Living in ${servers.toLocaleString()} servers`, - `Serving ${users.toLocaleString()} users`, - `Observing ${channels.toLocaleString()} channels`, - `Tinkering with ${tanks.toLocaleString()} tanks`, - `Memorizing ${usernames.toLocaleString()} usernames`, - ]; - - client.user?.setPresence({ - activities: [ - { - type: ActivityType.Custom, - name: bios[Math.floor(Math.random() * bios.length)], - }, - ], - }); - - for (const guild of client.guilds.cache.values()) { - try { - const botMember = await guild.members.fetch(client.user.id); - await botMember.setNickname("BlitzKit"); - } catch {} - } - }, - { context: { servers, channels, users, tanks, usernames } }, - ); - } catch (error) { - console.warn( - "An error occurred when setting up presence, ignoring to keep the process alive", - error, - ); - } -}, 15000); diff --git a/packages/bot/src/stitches.config.ts b/packages/bot/src/stitches.config.ts deleted file mode 100644 index f50f1a5fa..000000000 --- a/packages/bot/src/stitches.config.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - amberDark, - amberDarkA, - blueDark, - brownDark, - crimsonDark, - greenDark, - mauveDark, - orangeDark, - purpleDark, - redDark, - rubyDark, - sandDark, - slate, - slateDark, - slateDarkA, - tealDark, -} from '@radix-ui/colors'; -import { createBorderStyles, createColors, createSpaces } from 'bepaint'; - -// this isn't really a stitches config, but the object is compliant -export const theme = { - spaces: createSpaces(), - - colors: { - ...createColors(mauveDark), - ...createColors(slateDarkA, 'alpha'), - ...createColors(slate, 'light'), - - ...createColors(amberDark, 'amber'), - ...createColors(amberDarkA, 'amberAlpha'), - ...createColors(orangeDark, 'orange'), - ...createColors(purpleDark, 'purple'), - ...createColors(blueDark, 'blue'), - ...createColors(tealDark, 'teal'), - ...createColors(greenDark, 'green'), - ...createColors(redDark, 'red'), - ...createColors(brownDark, 'brown'), - ...createColors(mauveDark, 'mauve'), - ...createColors(sandDark, 'sand'), - ...createColors(crimsonDark, 'crimson'), - ...createColors(rubyDark, 'ruby'), - }, - - borderStyles: { - subtle: `1px solid ${slateDark.slate2}`, - ...createBorderStyles(slateDark), - ...createBorderStyles(blueDark, undefined, 'blue'), - }, - - durations: { - regular: '0.2s', - }, -}; diff --git a/packages/bot/src/types.d.ts b/packages/bot/src/types.d.ts deleted file mode 100644 index 29102695c..000000000 --- a/packages/bot/src/types.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.ttf' { - const path: string; - export default path; -} diff --git a/packages/bot/src/types/histories.ts b/packages/bot/src/types/histories.ts deleted file mode 100644 index 297552339..000000000 --- a/packages/bot/src/types/histories.ts +++ /dev/null @@ -1,24 +0,0 @@ -// a cleaner simplification of Blitz Stars' histories because it's a mess there - -import { type BlitzStats } from '@blitzkit/core'; - -export interface History { - all: BlitzStats; - last_battle_time: number; -} - -export type Histories = History[]; - -export interface GetHistoriesOptions { - start: number; - end: number; - includeLatestHistories: boolean; - includePreviousHistories: boolean; -} - -export const getHistoriesDefaultOptions: GetHistoriesOptions = { - start: -Infinity, - end: Infinity, - includeLatestHistories: false, - includePreviousHistories: false, -}; diff --git a/packages/bot/src/types/possiblyPromise.ts b/packages/bot/src/types/possiblyPromise.ts deleted file mode 100644 index a4658c873..000000000 --- a/packages/bot/src/types/possiblyPromise.ts +++ /dev/null @@ -1 +0,0 @@ -export type PossiblyPromise = Promise | Type; diff --git a/packages/bot/src/types/ttf.d.ts b/packages/bot/src/types/ttf.d.ts deleted file mode 100644 index 29102695c..000000000 --- a/packages/bot/src/types/ttf.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.ttf' { - const path: string; - export default path; -} diff --git a/packages/bot/src/types/writable.ts b/packages/bot/src/types/writable.ts deleted file mode 100644 index 2f45b1129..000000000 --- a/packages/bot/src/types/writable.ts +++ /dev/null @@ -1 +0,0 @@ -export type Writeable = { -readonly [P in keyof T]: T[P] }; diff --git a/packages/bot/src/workers/bot.ts b/packages/bot/src/workers/bot.ts deleted file mode 100644 index b3af429ca..000000000 --- a/packages/bot/src/workers/bot.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { registerProcesses } from "../core/blitzkit/processes"; -import { client } from "../core/discord/client"; - -console.log(`🟡 Launching bot ${client.shard?.ids[0]}`); - -registerProcesses(); - -console.log(`post register processes on shard ${client.shard?.ids[0]}`); - -client.login(assertSecret(import.meta.env.DISCORD_TOKEN)); - -console.log(`post client login on shard ${client.shard?.ids[0]}`); diff --git a/packages/bot/src/workers/render.ts b/packages/bot/src/workers/render.ts deleted file mode 100644 index 1838e44ad..000000000 --- a/packages/bot/src/workers/render.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { parentPort, threadId } from 'worker_threads'; -import { svgToPng } from '../core/blitzkit/svgToPng'; - -console.log(`🟢 Launched render worker ${threadId}`); - -parentPort?.on('message', async (svg: string) => { - const png = svgToPng(svg); - parentPort?.postMessage(png); -}); - -console.log(`post render thread parent port on thread ${threadId}`); diff --git a/packages/bot/tsconfig.json b/packages/bot/tsconfig.json deleted file mode 100644 index d24722b0d..000000000 --- a/packages/bot/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"] -} diff --git a/packages/website/astro.config.ts b/packages/website/astro.config.ts deleted file mode 100644 index aaf08b1d2..000000000 --- a/packages/website/astro.config.ts +++ /dev/null @@ -1,114 +0,0 @@ -import node from "@astrojs/node"; -import react from "@astrojs/react"; -import { wrapper } from "@blitzkit/i18n"; -import locales from "@blitzkit/i18n/locales.json"; -import strings from "@blitzkit/i18n/strings/en.json"; -import { mauve, mauveDark } from "@radix-ui/colors"; -import AstroPWA from "@vite-pwa/astro"; -import favicons from "astro-favicons"; -import { defineConfig } from "astro/config"; -import packageJSON from "../../package.json"; -import { tools } from "./src/constants/tools"; - -const POSSIBLE_TARGETS = ["app", "web"]; - -const target = process.env.PUBLIC_TARGET; - -if (!target || !POSSIBLE_TARGETS.includes(target)) { - throw new Error("Invalid target"); -} - -export default defineConfig({ - devToolbar: { enabled: false }, - - vite: { server: { allowedHosts: [] } }, - output: target === "web" ? "static" : "server", - site: "https://blitzkit.app", - outDir: "../../dist/website", - prefetch: { defaultStrategy: "hover", prefetchAll: true }, - - build: { concurrency: 4 }, - adapter: target === "app" ? node({ mode: "standalone" }) : undefined, - - integrations: [ - favicons({ - name: strings.common.name, - short_name: strings.common.name, - - background: mauveDark.mauve1, - themes: [mauve.mauve1, mauveDark.mauve1], - appleStatusBarStyle: "black-translucent", - - version: packageJSON.version, - - shortcuts: Object.values(tools) - .filter((tool) => !tool.disabled && !tool.href) - .map((tool) => { - const stringId = (tool.strings ?? - tool.id) as keyof typeof strings.website.tools; - - return { - name: strings.website.tools[stringId].name, - name_localized: wrapper( - (strings) => strings.website.tools[stringId].name - ), - description: strings.website.tools[stringId].description, - description_localized: wrapper( - (strings) => strings.website.tools[stringId].description - ), - short_name: strings.website.tools[stringId].name, - short_name_localized: wrapper( - (strings) => strings.website.tools[stringId].name - ), - url: `/${tool.id}`, - icon: `public/assets/images/tools/${tool.id}.webp`, - }; - }), - - manifest: { - description: strings.website.home.seo_description, - description_localized: wrapper( - (strings) => strings.website.home.seo_description - ), - - dir: "ltr", - categories: ["utilities", "games", "developer", "developer tools"], - - id: "com.tresabhi.blitzkit", - start_url: "/", - launch_handler: { client_mode: "navigate-new" }, - display: "fullscreen", - display_override: ["fullscreen", "standalone", "minimal-ui"], - - lang: locales.default, - orientation: "any", - }, - - icons: { - windows: true, - android: true, - appleIcon: true, - appleStartup: true, - favicons: true, - yandex: true, - }, - }), - - AstroPWA({ - manifest: false, - devOptions: { enabled: true }, - workbox: { - globPatterns: ["**/*.{html,css,js,png,webp,svg,xml,json}"], - runtimeCaching: [ - { - urlPattern: ({ url }) => - url.href.startsWith(import.meta.env.PUBLIC_ASSET_BASE), - handler: "NetworkFirst", - }, - ], - }, - }), - - react(), - ], -}); diff --git a/packages/website/package.json b/packages/website/package.json deleted file mode 100644 index 359694ca1..000000000 --- a/packages/website/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@blitzkit/website", - "type": "module", - "scripts": { - "dev": "PUBLIC_TARGET=web astro dev", - "build": "PUBLIC_TARGET=web astro build", - "preview": "PUBLIC_TARGET=web astro preview", - "lint": "PUBLIC_TARGET=web astro check", - "astro": "astro" - }, - "dependencies": { - "@astrojs/check": "^0.9.4", - "@astrojs/node": "^9.4.4", - "@astrojs/react": "^4.4.0", - "@astrolib/seo": "^1.0.0-beta.8", - "@blitzkit/core": "workspace:*", - "@blitzkit/i18n": "workspace:*", - "@fontsource/inter": "^5.2.8", - "@radix-ui/colors": "^3.0.0", - "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/themes": "^3.2.1", - "@react-three/drei": "^10.7.6", - "@react-three/fiber": "^9.4.0", - "@types/lodash-es": "^4.17.12", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.2", - "@types/three": "^0.180.0", - "@vite-pwa/astro": "^1.1.0", - "astro": "^5.14.4", - "astro-favicons": "^3.1.5", - "cheerio": "^1.1.2", - "compare-versions": "^6.1.1", - "fuzzysort": "^3.1.0", - "immer": "^10.1.3", - "jimp": "^1.6.0", - "lodash-es": "^4.17.21", - "markdown-to-jsx": "^7.7.13", - "pwa-install-handler": "^2.6.3", - "quicklime": "^1.2.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-promise-suspense": "^0.3.4", - "three": "^0.180.0", - "three-stdlib": "^2.36.0", - "typescript": "^5.9.3", - "urlon": "^3.1.0", - "varuna": "workspace:*", - "vite": "^7.1.9" - } -} diff --git a/packages/website/public/.well-known/discord b/packages/website/public/.well-known/discord deleted file mode 100644 index d5e7a2f35..000000000 --- a/packages/website/public/.well-known/discord +++ /dev/null @@ -1 +0,0 @@ -dh=ecd11a058908f58ccea092ed17aa43ace59 4b9a5 diff --git a/packages/website/public/PROPOUND.WAV b/packages/website/public/PROPOUND.WAV deleted file mode 100644 index eb717aa86..000000000 Binary files a/packages/website/public/PROPOUND.WAV and /dev/null differ diff --git a/packages/website/public/assets/audio/lotta-damage.mp3 b/packages/website/public/assets/audio/lotta-damage.mp3 deleted file mode 100644 index f70b11d91..000000000 Binary files a/packages/website/public/assets/audio/lotta-damage.mp3 and /dev/null differ diff --git a/packages/website/public/assets/images/3d/grid.png b/packages/website/public/assets/images/3d/grid.png deleted file mode 100644 index 868507a97..000000000 Binary files a/packages/website/public/assets/images/3d/grid.png and /dev/null differ diff --git a/packages/website/public/assets/images/backgrounds/embed-default.webp b/packages/website/public/assets/images/backgrounds/embed-default.webp deleted file mode 100644 index 0069916ba..000000000 Binary files a/packages/website/public/assets/images/backgrounds/embed-default.webp and /dev/null differ diff --git a/packages/website/public/assets/images/halloween-blood.svg b/packages/website/public/assets/images/halloween-blood.svg deleted file mode 100644 index ea3b17bc9..000000000 --- a/packages/website/public/assets/images/halloween-blood.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/website/public/assets/images/icons/aim-caret.png b/packages/website/public/assets/images/icons/aim-caret.png deleted file mode 100644 index f0c286708..000000000 Binary files a/packages/website/public/assets/images/icons/aim-caret.png and /dev/null differ diff --git a/packages/website/public/assets/images/icons/custom-shell.png b/packages/website/public/assets/images/icons/custom-shell.png deleted file mode 100644 index ab13c566a..000000000 Binary files a/packages/website/public/assets/images/icons/custom-shell.png and /dev/null differ diff --git a/packages/website/public/assets/images/icons/tankopedia-dynamic-armor.png b/packages/website/public/assets/images/icons/tankopedia-dynamic-armor.png deleted file mode 100644 index 5dfd505ef..000000000 Binary files a/packages/website/public/assets/images/icons/tankopedia-dynamic-armor.png and /dev/null differ diff --git a/packages/website/public/assets/images/icons/tankopedia-model.png b/packages/website/public/assets/images/icons/tankopedia-model.png deleted file mode 100644 index 97f376d23..000000000 Binary files a/packages/website/public/assets/images/icons/tankopedia-model.png and /dev/null differ diff --git a/packages/website/public/assets/images/icons/tankopedia-modules.png b/packages/website/public/assets/images/icons/tankopedia-modules.png deleted file mode 100644 index bf79a66a4..000000000 Binary files a/packages/website/public/assets/images/icons/tankopedia-modules.png and /dev/null differ diff --git a/packages/website/public/assets/images/icons/tankopedia-static-armor.png b/packages/website/public/assets/images/icons/tankopedia-static-armor.png deleted file mode 100644 index 2c82de93e..000000000 Binary files a/packages/website/public/assets/images/icons/tankopedia-static-armor.png and /dev/null differ diff --git a/packages/website/public/assets/images/icons/xp-multiplier.png b/packages/website/public/assets/images/icons/xp-multiplier.png deleted file mode 100644 index 1155f2d71..000000000 Binary files a/packages/website/public/assets/images/icons/xp-multiplier.png and /dev/null differ diff --git a/packages/website/public/assets/images/national-wallpapers/china.jpg b/packages/website/public/assets/images/national-wallpapers/china.jpg deleted file mode 100644 index 2f7a414e9..000000000 Binary files a/packages/website/public/assets/images/national-wallpapers/china.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/national-wallpapers/european.jpg b/packages/website/public/assets/images/national-wallpapers/european.jpg deleted file mode 100644 index a5383d610..000000000 Binary files a/packages/website/public/assets/images/national-wallpapers/european.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/national-wallpapers/france.jpg b/packages/website/public/assets/images/national-wallpapers/france.jpg deleted file mode 100644 index c6a8e09d2..000000000 Binary files a/packages/website/public/assets/images/national-wallpapers/france.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/national-wallpapers/germany.jpg b/packages/website/public/assets/images/national-wallpapers/germany.jpg deleted file mode 100644 index 5a65449fb..000000000 Binary files a/packages/website/public/assets/images/national-wallpapers/germany.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/national-wallpapers/japan.jpg b/packages/website/public/assets/images/national-wallpapers/japan.jpg deleted file mode 100644 index 0e9964e71..000000000 Binary files a/packages/website/public/assets/images/national-wallpapers/japan.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/national-wallpapers/other.jpg b/packages/website/public/assets/images/national-wallpapers/other.jpg deleted file mode 100644 index da74cb4fa..000000000 Binary files a/packages/website/public/assets/images/national-wallpapers/other.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/national-wallpapers/uk.jpg b/packages/website/public/assets/images/national-wallpapers/uk.jpg deleted file mode 100644 index 1edd4ed05..000000000 Binary files a/packages/website/public/assets/images/national-wallpapers/uk.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/national-wallpapers/usa.jpg b/packages/website/public/assets/images/national-wallpapers/usa.jpg deleted file mode 100644 index 9dea35ce3..000000000 Binary files a/packages/website/public/assets/images/national-wallpapers/usa.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/national-wallpapers/ussr.jpg b/packages/website/public/assets/images/national-wallpapers/ussr.jpg deleted file mode 100644 index 46455845f..000000000 Binary files a/packages/website/public/assets/images/national-wallpapers/ussr.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/pwa-promo.png b/packages/website/public/assets/images/pwa-promo.png deleted file mode 100644 index 29d8f1b7e..000000000 Binary files a/packages/website/public/assets/images/pwa-promo.png and /dev/null differ diff --git a/packages/website/public/assets/images/tankopedia/visualizers/hull-traverse/hull-only.png b/packages/website/public/assets/images/tankopedia/visualizers/hull-traverse/hull-only.png deleted file mode 100644 index 8413f02df..000000000 Binary files a/packages/website/public/assets/images/tankopedia/visualizers/hull-traverse/hull-only.png and /dev/null differ diff --git a/packages/website/public/assets/images/tankopedia/visualizers/hull-traverse/hull.png b/packages/website/public/assets/images/tankopedia/visualizers/hull-traverse/hull.png deleted file mode 100644 index 35010d48a..000000000 Binary files a/packages/website/public/assets/images/tankopedia/visualizers/hull-traverse/hull.png and /dev/null differ diff --git a/packages/website/public/assets/images/tankopedia/visualizers/hull-traverse/turret.png b/packages/website/public/assets/images/tankopedia/visualizers/hull-traverse/turret.png deleted file mode 100644 index b1700a1d2..000000000 Binary files a/packages/website/public/assets/images/tankopedia/visualizers/hull-traverse/turret.png and /dev/null differ diff --git a/packages/website/public/assets/images/tankopedia/visualizers/range/dead-rail.png b/packages/website/public/assets/images/tankopedia/visualizers/range/dead-rail.png deleted file mode 100644 index 817a2b283..000000000 Binary files a/packages/website/public/assets/images/tankopedia/visualizers/range/dead-rail.png and /dev/null differ diff --git a/packages/website/public/assets/images/tankopedia/visualizers/range/ghost-factory.png b/packages/website/public/assets/images/tankopedia/visualizers/range/ghost-factory.png deleted file mode 100644 index 8c8b272c8..000000000 Binary files a/packages/website/public/assets/images/tankopedia/visualizers/range/ghost-factory.png and /dev/null differ diff --git a/packages/website/public/assets/images/tankopedia/visualizers/reload/background.jpg b/packages/website/public/assets/images/tankopedia/visualizers/reload/background.jpg deleted file mode 100644 index 82601a1e3..000000000 Binary files a/packages/website/public/assets/images/tankopedia/visualizers/reload/background.jpg and /dev/null differ diff --git a/packages/website/public/assets/images/tankopedia/visualizers/reload/caret.png b/packages/website/public/assets/images/tankopedia/visualizers/reload/caret.png deleted file mode 100644 index 660853971..000000000 Binary files a/packages/website/public/assets/images/tankopedia/visualizers/reload/caret.png and /dev/null differ diff --git a/packages/website/public/assets/images/tankopedia/visualizers/reload/shell.png b/packages/website/public/assets/images/tankopedia/visualizers/reload/shell.png deleted file mode 100644 index 57a555a2f..000000000 Binary files a/packages/website/public/assets/images/tankopedia/visualizers/reload/shell.png and /dev/null differ diff --git a/packages/website/public/assets/images/tankopedia/visualizers/ricochet/armor-hash.png b/packages/website/public/assets/images/tankopedia/visualizers/ricochet/armor-hash.png deleted file mode 100644 index e7728db67..000000000 Binary files a/packages/website/public/assets/images/tankopedia/visualizers/ricochet/armor-hash.png and /dev/null differ diff --git a/packages/website/public/assets/images/third-party/wotb-news.png b/packages/website/public/assets/images/third-party/wotb-news.png deleted file mode 100644 index 74c4e28fe..000000000 Binary files a/packages/website/public/assets/images/third-party/wotb-news.png and /dev/null differ diff --git a/packages/website/public/assets/images/tools/api.webp b/packages/website/public/assets/images/tools/api.webp deleted file mode 100644 index ba72cd100..000000000 Binary files a/packages/website/public/assets/images/tools/api.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/charts.webp b/packages/website/public/assets/images/tools/charts.webp deleted file mode 100644 index 1d9e0538a..000000000 Binary files a/packages/website/public/assets/images/tools/charts.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/compare.webp b/packages/website/public/assets/images/tools/compare.webp deleted file mode 100644 index b24df602f..000000000 Binary files a/packages/website/public/assets/images/tools/compare.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/discord.webp b/packages/website/public/assets/images/tools/discord.webp deleted file mode 100644 index 739ed8bc3..000000000 Binary files a/packages/website/public/assets/images/tools/discord.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/embed.webp b/packages/website/public/assets/images/tools/embed.webp deleted file mode 100644 index 5675f84a2..000000000 Binary files a/packages/website/public/assets/images/tools/embed.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/gallery.webp b/packages/website/public/assets/images/tools/gallery.webp deleted file mode 100644 index c8cdc346e..000000000 Binary files a/packages/website/public/assets/images/tools/gallery.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/guess.webp b/packages/website/public/assets/images/tools/guess.webp deleted file mode 100644 index 3453b25f9..000000000 Binary files a/packages/website/public/assets/images/tools/guess.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/mixer.webp b/packages/website/public/assets/images/tools/mixer.webp deleted file mode 100644 index 61f9c9596..000000000 Binary files a/packages/website/public/assets/images/tools/mixer.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/more.webp b/packages/website/public/assets/images/tools/more.webp deleted file mode 100644 index 2555eb689..000000000 Binary files a/packages/website/public/assets/images/tools/more.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/performance.webp b/packages/website/public/assets/images/tools/performance.webp deleted file mode 100644 index 917d281d4..000000000 Binary files a/packages/website/public/assets/images/tools/performance.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/players.webp b/packages/website/public/assets/images/tools/players.webp deleted file mode 100644 index a71fc9721..000000000 Binary files a/packages/website/public/assets/images/tools/players.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/playlist.webp b/packages/website/public/assets/images/tools/playlist.webp deleted file mode 100644 index 3504be291..000000000 Binary files a/packages/website/public/assets/images/tools/playlist.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/rating.webp b/packages/website/public/assets/images/tools/rating.webp deleted file mode 100644 index 8d3fa44d5..000000000 Binary files a/packages/website/public/assets/images/tools/rating.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/session.webp b/packages/website/public/assets/images/tools/session.webp deleted file mode 100644 index 64e7f5caf..000000000 Binary files a/packages/website/public/assets/images/tools/session.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/tanks.webp b/packages/website/public/assets/images/tools/tanks.webp deleted file mode 100644 index f5ffb5992..000000000 Binary files a/packages/website/public/assets/images/tools/tanks.webp and /dev/null differ diff --git a/packages/website/public/assets/images/tools/tier-list.webp b/packages/website/public/assets/images/tools/tier-list.webp deleted file mode 100644 index 4cc334352..000000000 Binary files a/packages/website/public/assets/images/tools/tier-list.webp and /dev/null differ diff --git a/packages/website/public/favicon.svg b/packages/website/public/favicon.svg deleted file mode 100644 index a89c21cb9..000000000 --- a/packages/website/public/favicon.svg +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/website/src/components/AesonPlug.tsx b/packages/website/src/components/AesonPlug.tsx deleted file mode 100644 index ad7f8bda9..000000000 --- a/packages/website/src/components/AesonPlug.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Flex, Heading, Link, Text } from '@radix-ui/themes'; -import { Var } from '../core/radix/var'; - -export function AesonPlug() { - return ( - - - - - - Preview brought to you by WoT Blitz News - - Join the Discord for the absolute latest news - - - - - ); -} diff --git a/packages/website/src/components/Analytics.astro b/packages/website/src/components/Analytics.astro deleted file mode 100644 index 415a4cd5d..000000000 --- a/packages/website/src/components/Analytics.astro +++ /dev/null @@ -1,25 +0,0 @@ ---- -import { assertSecret } from "@blitzkit/core"; - -const PUBLIC_GOOGLE_TAG_MANAGER_ID = assertSecret( - import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID, -); ---- - - - - diff --git a/packages/website/src/components/Armor/components/ArmorPlateDisplay.tsx b/packages/website/src/components/Armor/components/ArmorPlateDisplay.tsx deleted file mode 100644 index ec8b103e5..000000000 --- a/packages/website/src/components/Armor/components/ArmorPlateDisplay.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import { ReloadIcon } from "@radix-ui/react-icons"; -import { - Card, - Code, - Flex, - IconButton, - Text, - TextField, -} from "@radix-ui/themes"; -import { Html } from "@react-three/drei"; -import { useRef } from "react"; -import { radToDeg } from "three/src/math/MathUtils.js"; -import { resolveArmorIndex } from "../../../core/blitzkit/resolveArmorIndex"; -import { LocaleProvider, useLocale } from "../../../hooks/useLocale"; -import { App } from "../../../stores/app"; -import { Duel } from "../../../stores/duel"; -import { Tankopedia } from "../../../stores/tankopedia"; -import { layerTypeNames } from "./ShotDisplayCard"; -import { ArmorType } from "./SpacedArmorScene"; - -export function ArmorPlateDisplay() { - const highlightArmor = Tankopedia.use((state) => state.highlightArmor); - const developerMode = App.useDeferred((state) => state.developerMode, false); - const input = useRef(null); - const { locale } = useLocale(); - - if (highlightArmor === undefined) return null; - - return ( - - - - - - - {layerTypeNames[highlightArmor.type]}{" "} - {highlightArmor.thickness.toFixed(0)} - - mm - - - - {highlightArmor.type !== ArmorType.External && ( - - {highlightArmor.thicknessAngled.toFixed(0)} - - mm {radToDeg(highlightArmor.angle).toFixed(0)}° - - - )} - - {developerMode && ( - <> - - DEV:{" "} - - {highlightArmor.name} - - - - )} - - {highlightArmor.editingPlate && ( - - event.target.select()} - onKeyDown={(event) => { - if (!highlightArmor.editingPlate) return; - - const index = resolveArmorIndex(highlightArmor.name); - - if (index === undefined) return; - - const thickness = event.currentTarget.valueAsNumber; - - if (isNaN(thickness) || thickness < 0) return; - - if (event.key === "Enter") { - input.current?.blur(); - - Tankopedia.mutate((draft) => { - draft.highlightArmor = undefined; - const { protagonist } = Duel.state; - const tank = draft.model; - const turret = tank.turrets[protagonist.turret.id]; - const gun = turret.guns[protagonist.gun.id]; - const track = tank.tracks[protagonist.track.id]; - - if (highlightArmor.name.startsWith("hull_")) { - tank.armor!.thickness[index] = thickness; - } else if ( - highlightArmor.name.startsWith("turret_") - ) { - turret.armor!.thickness[index] = thickness; - } else if (highlightArmor.name.startsWith("gun_")) { - if (highlightArmor.name.includes("_armor_")) { - gun.armor!.thickness[index] = thickness; - } else { - gun.thickness = thickness; - } - } else if ( - highlightArmor.name.startsWith("chassis_") - ) { - track.thickness = thickness; - } - }); - } else if (event.key === "Escape") { - input.current?.blur(); - Tankopedia.mutate((draft) => { - draft.highlightArmor = undefined; - }); - } - }} - onClick={(event) => { - event.stopPropagation(); - event.preventDefault(); - }} - defaultValue={highlightArmor.thickness.toFixed(0)} - > - mm - - - { - Tankopedia.mutate((draft) => { - if (!highlightArmor.editingPlate) return; - - const index = resolveArmorIndex(highlightArmor.name); - - if (index === undefined) return; - - draft.highlightArmor = undefined; - const initialState = Tankopedia.initial; - const { protagonist } = Duel.state; - const tank = draft.model; - const initialTank = initialState.model; - const turret = tank.turrets[protagonist.turret.id]; - const initialTurret = - initialTank.turrets[protagonist.turret.id]; - const gun = turret.guns[protagonist.gun.id]; - const initialGun = - initialTurret.guns[protagonist.gun.id]; - const track = tank.tracks[protagonist.track.id]; - const initialTrack = - initialTank.tracks[protagonist.track.id]; - - if (highlightArmor.name.startsWith("hull_")) { - tank.armor!.thickness[index] = - initialTank.armor!.thickness[index]; - } else if (highlightArmor.name.startsWith("turret_")) { - turret.armor!.thickness[index] = - initialTurret.armor!.thickness[index]; - } else if (highlightArmor.name.startsWith("gun_")) { - if (highlightArmor.name.includes("_armor_")) { - gun.armor!.thickness[index] = - initialGun.armor!.thickness[index]; - } else { - gun.thickness = initialGun.thickness; - } - } else if (highlightArmor.name.startsWith("chassis_")) { - track.thickness = initialTrack.thickness; - } - }); - }} - > - - - - )} - - - - - - ); -} diff --git a/packages/website/src/components/Armor/components/ModelTankWrapper.tsx b/packages/website/src/components/Armor/components/ModelTankWrapper.tsx deleted file mode 100644 index dc3a7934c..000000000 --- a/packages/website/src/components/Armor/components/ModelTankWrapper.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { forwardRef, type ComponentProps } from "react"; -import { Group } from "three"; - -export const ModelTankWrapper = forwardRef>( - (props, ref) => { - return ; - } -); diff --git a/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx b/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx deleted file mode 100644 index 144240a11..000000000 --- a/packages/website/src/components/Armor/components/PrimaryArmorScene.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { memo, useRef } from "react"; -import { Group } from "three"; -import { correctZYTuple } from "../../../core/blitz/correctZYTuple"; -import { nameToArmorId } from "../../../core/blitzkit/nameToArmorId"; -import { resolveArmor } from "../../../core/blitzkit/resolveThickness"; -import { useArmor } from "../../../hooks/useArmor"; -import { useTankModelDefinition } from "../../../hooks/useTankModelDefinition"; -import { useTankTransform } from "../../../hooks/useTankTransform"; -import { Duel } from "../../../stores/duel"; -import { ModelTankWrapper } from "./ModelTankWrapper"; -import { PrimaryArmorSceneComponent } from "./PrimaryArmorSceneComponent"; - -export const PrimaryArmorScene = memo(() => { - const wrapper = useRef(null); - const turretContainer = useRef(null!); - const gunContainer = useRef(null!); - const tank = Duel.use((state) => state.protagonist.tank); - const track = Duel.use((state) => state.protagonist.track); - const turret = Duel.use((state) => state.protagonist.turret); - const gun = Duel.use((state) => state.protagonist.gun); - const armorGltf = useArmor(tank.id).gltf; - const armorNodes = Object.values(armorGltf.nodes); - const tankModelDefinition = useTankModelDefinition(); - const trackModelDefinition = tankModelDefinition.tracks[track.id]; - const turretModelDefinition = tankModelDefinition.turrets[turret.id]; - const gunModelDefinition = turretModelDefinition.guns[gun.id]; - const hullOrigin = correctZYTuple(trackModelDefinition.origin!); - const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!); - const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!); - const isDynamicArmorActive = Duel.use((state) => - state.protagonist.consumables.includes(73), - ); - - useTankTransform(track, turret, turretContainer, gunContainer); - - return ( - - - {armorNodes.map((node) => { - const isHull = node.name.startsWith("hull_"); - const isVisible = isHull; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - tankModelDefinition.armor!, - armorId, - ); - - if ( - !isVisible || - spaced || - thickness === undefined || - (isDynamicArmorActive && node.name.includes("state_01")) || - (!isDynamicArmorActive && node.name.includes("state_00")) - ) - return null; - - return ( - - ); - })} - - - - {armorNodes.map((node) => { - const isCurrentTurret = node.name.startsWith( - `turret_${turretModelDefinition.model_id - .toString() - .padStart(2, "0")}`, - ); - const isVisible = isCurrentTurret; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - turretModelDefinition.armor!, - armorId, - ); - - if ( - !isVisible || - spaced || - thickness === undefined || - (isDynamicArmorActive && node.name.includes("state_01")) || - (!isDynamicArmorActive && node.name.includes("state_00")) - ) - return null; - - return ( - - - - - - ); - })} - - - {armorNodes.map((node) => { - const isCurrentGun = node.name.startsWith( - `gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`, - ); - const isVisible = isCurrentGun; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - gunModelDefinition.armor!, - armorId, - ); - - if ( - !isVisible || - spaced || - thickness === undefined || - (isDynamicArmorActive && node.name.includes("state_01")) || - (!isDynamicArmorActive && node.name.includes("state_00")) - ) - return null; - - return ( - - - - - - ); - })} - - - - ); -}); diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx deleted file mode 100644 index 6f0e4282a..000000000 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import { - canSplash, - isExplosive, - resolvePenetrationCoefficient, -} from "@blitzkit/core"; -import { invalidate, useFrame } from "@react-three/fiber"; -import type { QuicklimeEvent } from "quicklime"; -import { useEffect } from "react"; -import { - MeshBasicMaterial, - NormalBlending, - Object3D, - ShaderMaterial, - SubtractiveBlending, - UniformsLib, - UniformsUtils, - Vector2, -} from "three"; -import { degToRad } from "three/src/math/MathUtils.js"; -import { hasEquipment } from "../../../../core/blitzkit/hasEquipment"; -import { jsxTree } from "../../../../core/blitzkit/jsxTree"; -import { Duel } from "../../../../stores/duel"; -import { Tankopedia } from "../../../../stores/tankopedia"; -import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent"; -import { transitionEvent } from "../../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting"; -import fragmentShader from "./shaders/fragment.glsl?raw"; -import vertexShader from "./shaders/vertex.glsl?raw"; -import { spacedArmorRenderTarget } from "./target"; - -interface PrimaryArmorSceneComponentProps { - thickness: number; - node: Object3D; -} - -const excludeMaterial = new MeshBasicMaterial({ - colorWrite: false, -}); - -const date = new Date(); -const isHalloween = date.getMonth() === 9 && date.getDate() === 31; - -export function PrimaryArmorSceneComponent({ - node, - thickness, -}: PrimaryArmorSceneComponentProps) { - const material = new ShaderMaterial({ - fragmentShader, - vertexShader, - - fog: true, - transparent: true, - - blending: isHalloween ? SubtractiveBlending : NormalBlending, - - uniforms: UniformsUtils.merge([ - UniformsLib.common, - UniformsLib.fog, - - { - thickness: { value: null }, - penetration: { 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 }, - - opacity: { value: 0 }, - }, - ]), - }); - - useEffect(() => { - function handleShellChange() { - const tankopediaEphemeral = Tankopedia.state; - const shell = - tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; - - material.uniforms.caliber.value = shell.caliber; - material.uniforms.ricochet.value = degToRad( - isExplosive(shell.type) ? 90 : shell.ricochet!, - ); - material.uniforms.normalization.value = degToRad( - shell.normalization ?? 0, - ); - material.uniforms.isExplosive.value = isExplosive(shell.type); - material.uniforms.canSplash.value = canSplash(shell.type); - material.uniforms.damage.value = shell.armor_damage; - material.uniforms.explosionRadius.value = shell.explosion_radius; - - handleProtagonistEquipmentChange(true); - handleAntagonistEquipmentChange(true); - - invalidate(); - } - function handleGreenPenetrationChange(greenPenetration: boolean) { - material.uniforms.greenPenetration.value = greenPenetration; - } - function handleAdvancedHighlightingChange(advancedHighlighting: boolean) { - material.uniforms.advancedHighlighting.value = advancedHighlighting; - invalidate(); - } - function handleOpaqueChange(opaque: boolean) { - material.uniforms.opaque.value = opaque; - } - function handleWireframeChange(wireframe: boolean) { - material.wireframe = wireframe; - } - function handleProtagonistEquipmentChange(noInvalidate = false) { - const equipment = Duel.state.protagonist.equipmentMatrix; - const hasEnhancedArmor = hasEquipment( - 110, - Duel.state.protagonist.tank.equipment_preset, - equipment, - ); - const equalizer = - (Duel.state.equalize - ? Duel.state.protagonist.tank.equalizer?.armor - : undefined) ?? 1; - - material.uniforms.thickness.value = - thickness * (hasEnhancedArmor ? 1.03 : 1) * equalizer; - - if (!noInvalidate) invalidate(); - } - function handleAntagonistEquipmentChange(noInvalidate = false) { - const equipment = Duel.state.antagonist.equipmentMatrix; - const tankopediaEphemeral = Tankopedia.state; - const shell = - tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; - const penetration = shell.penetration!.near; - const hasCalibratedShells = hasEquipment( - 103, - Duel.state.antagonist.tank.equipment_preset, - equipment, - ); - - material.uniforms.penetration.value = - penetration * - resolvePenetrationCoefficient( - hasCalibratedShells, - Duel.state.equalize, - shell.type, - Duel.state.antagonist.tank.equalizer, - ); - - if (!noInvalidate) invalidate(); - } - - function handleTransitionEvent(event: QuicklimeEvent) { - material.uniforms.opacity.value = event.data; - } - - handleShellChange(); - handleGreenPenetrationChange(TankopediaPersistent.state.greenPenetration); - handleAdvancedHighlightingChange( - TankopediaPersistent.state.advancedHighlighting, - ); - handleOpaqueChange(TankopediaPersistent.state.opaque); - handleWireframeChange(TankopediaPersistent.state.wireframe); - handleProtagonistEquipmentChange(); - handleAntagonistEquipmentChange(); - - transitionEvent.on(handleTransitionEvent); - - const unsubscribes = [ - Duel.on((state) => state.antagonist.shell, handleShellChange), - Tankopedia.on((state) => state.customShell, handleShellChange), - TankopediaPersistent.on( - (state) => state.greenPenetration, - handleGreenPenetrationChange, - ), - TankopediaPersistent.on( - (state) => state.advancedHighlighting, - handleAdvancedHighlightingChange, - ), - TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange), - Duel.on( - (state) => state.protagonist.equipmentMatrix, - () => handleProtagonistEquipmentChange(), - ), - Duel.on( - (state) => state.antagonist.equipmentMatrix, - () => handleAntagonistEquipmentChange(), - ), - Duel.on( - (state) => state.equalize, - () => { - handleProtagonistEquipmentChange(); - handleAntagonistEquipmentChange(); - }, - ), - () => transitionEvent.off(handleTransitionEvent), - ]; - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); - }; - }, []); - - useFrame(({ gl, camera }) => { - gl.getSize(material.uniforms.resolution.value).multiplyScalar( - gl.getPixelRatio(), - ); - material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture; - material.uniforms.spacedArmorDepth.value = - spacedArmorRenderTarget.depthTexture; - material.uniforms.inverseProjectionMatrix.value = - camera.projectionMatrixInverse; - }); - - return ( - <> - {jsxTree(node, { - mesh(_, props, key) { - return ( - - ); - }, - })} - {jsxTree(node, { - mesh(_, props, key) { - return ( - - ); - }, - })} - - ); -} diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl deleted file mode 100644 index eb5b86d1d..000000000 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/fragment.glsl +++ /dev/null @@ -1,120 +0,0 @@ -precision mediump float; - -varying vec3 vNormal; -varying vec3 vViewPosition; - -uniform float opacity; -uniform float thickness; -uniform float penetration; -uniform float caliber; -uniform float ricochet; -uniform float normalization; -uniform vec2 resolution; -uniform bool isExplosive; -uniform bool canSplash; -uniform float damage; -uniform float explosionRadius; -uniform bool greenPenetration; -uniform bool advancedHighlighting; -uniform bool opaque; -uniform mat4 inverseProjectionMatrix; - -uniform highp sampler2D spacedArmorBuffer; -uniform highp sampler2D spacedArmorDepth; - -const float EXPLOSION_DAMAGE_FACTOR = 1.1; -const float RANDOMIZATION_FACTOR = 0.05; -const float GREEN_VALUE = 0.392; - -float getDistance(vec2 coord, float depth) { - vec4 clipPos = vec4(coord * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); - vec4 eyePos = inverseProjectionMatrix * clipPos; - return length(eyePos.xyz / eyePos.w); -} - -vec3 getPenetrationColor(bool isThreeCalibersRule, bool couldHaveRicochet) { - if (advancedHighlighting && couldHaveRicochet) { - return vec3(0.0, 1.0, isThreeCalibersRule ? 1.0 : 0.0); - } - - return vec3(0.0, 1.0, 0.0); -} - -void main() { - #include - - vec2 screenCoord = gl_FragCoord.xy / resolution; - vec4 spacedArmorData = texture2D(spacedArmorBuffer, screenCoord); - bool isUnderSpacedArmor = spacedArmorData.a != 0.0; - - float viewDistance = length(vViewPosition); - float angle = acos(dot(vNormal, -vViewPosition) / viewDistance); - - bool threeCalibersRule = caliber > thickness * 3.0 || isUnderSpacedArmor; - bool couldHaveRicochet = angle >= ricochet; - float penetrationChance = -1.0; - float splashChance = 0.0; - bool didRicochet = false; - - if (!threeCalibersRule && couldHaveRicochet) { - penetrationChance = 0.0; - didRicochet = true; - } else { - float thicknessRatio = thickness > 0.0 ? caliber / thickness : 0.0; - bool twoCalibersRule = thicknessRatio > 2.0; - float finalNorm = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization; - float finalThickness = thickness / cos(max(0.0, angle - finalNorm)); - - float remainingPen = penetration; - - if (isUnderSpacedArmor) { - float spacedArmorThickness = spacedArmorData.r * penetration; - remainingPen -= spacedArmorThickness; - - if (isExplosive && remainingPen > 0.0) { - float spacedDist = getDistance(screenCoord, texture2D(spacedArmorDepth, screenCoord).r); - float primaryDist = getDistance(screenCoord, gl_FragCoord.z); - float distFromArmor = primaryDist - spacedDist; - - if (canSplash) { - float finalDamage = 0.5 * damage * (1.0 - distFromArmor / explosionRadius) - EXPLOSION_DAMAGE_FACTOR * (finalThickness + spacedArmorThickness); - splashChance = step(0.0, finalDamage); - penetrationChance = 0.0; - - } else { - remainingPen -= 0.5 * remainingPen * distFromArmor; - } - } - } - - if (penetrationChance < 0.0) { - remainingPen = max(0.0, remainingPen); - float delta = finalThickness - remainingPen; - float randomization = remainingPen * RANDOMIZATION_FACTOR; - penetrationChance = clamp(1.0 - (delta + randomization) / (2.0 * randomization), 0.0, 1.0); - - if (canSplash) { - float splashDamage = 0.5 * damage - EXPLOSION_DAMAGE_FACTOR * finalThickness; - splashChance = step(0.0, splashDamage); - } - } - } - - float alpha = opaque ? 1.0 : 0.5; - vec3 baseColor = vec3(1.0, splashChance * GREEN_VALUE, 0.0); - - if (advancedHighlighting && didRicochet) { - baseColor = vec3(1.0, baseColor.g, 1.0); - } - - if (greenPenetration || advancedHighlighting) { - float falling = -penetrationChance * penetrationChance + 1.0; - float gaining = -(penetrationChance - 1.0) * (penetrationChance - 1.0) + 1.0; - vec3 penColor = getPenetrationColor(threeCalibersRule, couldHaveRicochet); - gl_FragColor = vec4(falling * baseColor + gaining * penColor, alpha); - } else { - gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha); - } - - gl_FragColor.a *= opacity; -} \ No newline at end of file diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl deleted file mode 100644 index ebcd2593a..000000000 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/shaders/vertex.glsl +++ /dev/null @@ -1,13 +0,0 @@ -precision mediump float; - -varying vec3 vNormal; -varying vec3 vViewPosition; - -void main() { - vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); - - vViewPosition = mvPosition.xyz; - vNormal = normalMatrix * normal; - - gl_Position = projectionMatrix * mvPosition; -} diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/target.tsx b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/target.tsx deleted file mode 100644 index b61b76f7e..000000000 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/target.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { WebGLRenderTarget } from 'three'; - -export const spacedArmorRenderTarget = new WebGLRenderTarget(); diff --git a/packages/website/src/components/Armor/components/ShotDisplay.tsx b/packages/website/src/components/Armor/components/ShotDisplay.tsx deleted file mode 100644 index b7f8f9d2a..000000000 --- a/packages/website/src/components/Armor/components/ShotDisplay.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import { J_HAT, K_HAT } from "@blitzkit/core"; -import { Box, Flex, Text } from "@radix-ui/themes"; -import { Html } from "@react-three/drei"; -import { invalidate, useFrame } from "@react-three/fiber"; -import { clamp } from "lodash-es"; -import { useEffect, useRef, type ComponentProps } from "react"; -import { - BufferGeometry, - DoubleSide, - Euler, - Group, - Line, - LineDashedMaterial, - Mesh, - MeshBasicMaterial, - Path, - Quaternion, -} from "three"; -import { isHalloween } from "../../../core/blitzkit/isHalloween"; -import { LocaleProvider, useLocale } from "../../../hooks/useLocale"; -import { - Tankopedia, - type ShotLayerNonExternal, - type ShotStatus, -} from "../../../stores/tankopedia"; -import { ShotDisplayCard } from "./ShotDisplayCard"; - -export const shotStatusColors: Record< - ShotStatus, - ComponentProps["color"] -> = { - blocked: "red", - penetration: "green", - ricochet: "yellow", - splash: "orange", -}; - -const SURFACE_POINT_SIZE = 12; -export const SHOT_DISPLAY_LENGTH_INFINITY = 4; -const TRACER_THICK = 1 / 32; -const TRACER_THIN = TRACER_THICK / 2; - -export function ShotDisplay() { - const shot = Tankopedia.use((state) => state.shot); - const inTracer = useRef(null); - const outTracer = useRef(null); - const splashRadiusWrapper = useRef(null); - const splashRadiusMaterial = useRef(null); - let animationStartTime: number | null = null; - const { locale } = useLocale(); - - useEffect(() => { - if (shot?.splashRadius !== undefined) { - const outlineGeometry = new BufferGeometry().setFromPoints( - new Path().absarc(0, 0, 1, 0, Math.PI * 2).getSpacedPoints(50), - ); - const outlineMaterial = new LineDashedMaterial({ - color: "orange", - dashSize: 0.05, - gapSize: 0.025, - }); - const outline = new Line( - outlineGeometry, - outlineMaterial, - ).computeLineDistances(); - - splashRadiusWrapper.current?.add(outline); - - return () => { - splashRadiusWrapper.current?.remove(outline); - }; - } - }); - - useEffect(() => { - if ( - isHalloween() && - shot?.splashRadius !== undefined && - shot.in.status === "penetration" - ) { - const audio = new Audio("/assets/audio/lotta-damage.mp3"); - - audio.currentTime = 0.7; - audio.volume = 0.25; - audio.play(); - } - }, [shot]); - - useFrame(({ clock }) => { - if (!shot) return; - - const t = clock.elapsedTime; - - if (inTracer.current) { - const tracerT1 = t % 1; - const tracerT2 = (t + 0.5) % 1; - - inTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT1 - 0.5), 1); - inTracer.current.position.set(0, (1 - tracerT1) * inLength, 0); - - if (outTracer.current) { - outTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT2 - 0.5), 1); - outTracer.current.position.set(0, tracerT2 * outLength, 0); - } - - invalidate(); - } - - if ( - shot.splashRadius !== undefined && - splashRadiusWrapper.current && - splashRadiusMaterial.current - ) { - if (animationStartTime === null) animationStartTime = t; - - const scale = - clamp(4 * (t - animationStartTime), 0, 1) * shot.splashRadius; - const opacity = 1 - clamp(2 * (t - animationStartTime), 0, 1); - - splashRadiusWrapper.current.scale.set(scale, scale, scale); - splashRadiusMaterial.current.opacity = opacity; - - invalidate(); - } - }); - - if (!shot) return null; - - const inLength = - shot.in.layers.reduce((accumulator, layer) => { - if (layer.type === null) return accumulator + layer.distance; - return accumulator; - }, 0) + SHOT_DISPLAY_LENGTH_INFINITY; - const outLength = - shot.out && shot.out.layers.length > 0 - ? (shot.in.layers.at(-1) as ShotLayerNonExternal).point.distanceTo( - (shot.out.layers.at(-1) as ShotLayerNonExternal).point, - ) - : SHOT_DISPLAY_LENGTH_INFINITY; - const inLast = shot.in.layers.findLast( - (layer) => layer.type !== null, - ) as ShotLayerNonExternal; - const outTitleColor = shot.out - ? shotStatusColors[shot.out.status] - : undefined; - const inTitleColor = shotStatusColors[shot.in.status]; - const tracerGoingUp = - shot.in.surfaceNormal.y + (shot.out?.surfaceNormal.y ?? 0) > 0; - - return ( - - {shot.splashRadius && ( - - - - - - - )} - - - - - - - - - - {[...shot.in.layers, ...(shot.out?.layers ?? [])].map((layer, index) => { - if (layer.type === null) return null; - - const shotStatusColor = - shot.in.status === "splash" - ? shotStatusColors.splash - : shotStatusColors[layer.status]; - - return ( - - -
- - - ); - })} - - - - - - - - - - - - - - {shot.out && ( - - - - - - - - - - - - )} - - ); -} diff --git a/packages/website/src/components/Armor/components/ShotDisplayCard.tsx b/packages/website/src/components/Armor/components/ShotDisplayCard.tsx deleted file mode 100644 index e27c1de5c..000000000 --- a/packages/website/src/components/Armor/components/ShotDisplayCard.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import { literals } from "@blitzkit/i18n/src/literals"; -import { - Card, - Flex, - Inset, - Separator, - Text, - type CardProps, -} from "@radix-ui/themes"; -import type { ComponentProps } from "react"; -import { radToDeg } from "three/src/math/MathUtils.js"; -import { useLocale } from "../../../hooks/useLocale"; -import type { Shot, ShotLayer } from "../../../stores/tankopedia"; -import { shotStatusColors } from "./ShotDisplay"; -import { ArmorType } from "./SpacedArmorScene"; - -export const layerTypeNames = { - [ArmorType.Primary]: "primary", - [ArmorType.Spaced]: "spaced", - [ArmorType.External]: "external", - null: "gap", -} as const; - -function LayerEntry({ - layerIndex, - shotStatusColor, - layerName, - layer, - angle, -}: { - layerIndex?: string; - shotStatusColor: ComponentProps["color"]; - layerName: string; - layer: ShotLayer; - angle?: number; -}) { - const { locale, strings } = useLocale(); - - return ( - - - - {layerIndex} - - - - - - {layerName} - - - - - {layer.type === null && ( - - {literals(strings.common.units.mm, { - value: Math.round(layer.distance * 1000).toLocaleString(locale), - })} - - )} - - {layer.type === ArmorType.External && ( - - {literals(strings.common.units.mm, { - value: Math.round(layer.thickness).toLocaleString(locale), - })} - - )} - {(layer.type === ArmorType.Primary || - layer.type === ArmorType.Spaced) && ( - - {angle === undefined - ? literals(strings.common.units.mm, { - value: Math.round(layer.thicknessAngled).toLocaleString( - locale - ), - }) - : literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card - .stats.thickness_and_angle, - { - thickness: Math.round(layer.thicknessAngled).toLocaleString( - locale - ), - angle: radToDeg(angle ?? 0).toFixed(0), - } - )} - - )} - - - - {layer.type === null && ( - - {literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats - .ricochet_loss, - { loss: Math.max(-100, -50 * layer.distance).toFixed(0) } - )} - - )} - - {(layer.type === ArmorType.Primary || - layer.type === ArmorType.Spaced) && ( - - {literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats - .nominal, - { thickness: Math.round(layer.thickness).toLocaleString(locale) } - )} - - )} - - - ); -} - -interface ShotDisplayCardProps extends CardProps { - shot: Shot; -} - -export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) { - const { locale, strings } = useLocale(); - - if (!shot) return null; - - const outTitleColor = shot.out - ? shotStatusColors[shot.out.status] - : undefined; - const inTitleColor = shotStatusColors[shot.in.status]; - - return ( - - - - - {literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[ - shot.in.status - ], - { damage: Math.round(shot.damage).toLocaleString(locale) } - )} - - - - {shot.in.layers.map((layer, index) => { - const layerName = - strings.website.tools.tankopedia.sandbox.dynamic.shot_card - .element[ - layer.type === ArmorType.External - ? layer.variant - : layerTypeNames[`${layer.type}`] - ]; - const layerIndex = - layer.type === null - ? undefined - : `${(shot.containsGaps ? index / 2 : index) + 1}.`; - const shotStatusColor = - shot.in.status === "splash" - ? shotStatusColors.splash - : shotStatusColors[layer.status]; - - return ( - - ); - })} -
-
- - {shot.out && shot.out.layers.length > 0 && ( - <> - - - - - ricochet (-25% penetration) - - - - - - - {literals( - strings.website.tools.tankopedia.sandbox.dynamic.shot_card - .status[shot.out.status], - { damage: Math.round(shot.damage).toLocaleString(locale) } - )} - - - - {shot.out.layers.map((layer, index) => { - const layerName = - strings.website.tools.tankopedia.sandbox.dynamic.shot_card - .element[ - layer.type === ArmorType.External - ? layer.variant - : layerTypeNames[`${layer.type}`] - ]; - const shiftedIndex = index + shot.out!.layers.length; - const layerIndex = - layer.type === null - ? undefined - : `${ - (shot.containsGaps ? shiftedIndex / 2 : shiftedIndex) + - 1 - }.`; - const shotStatusColor = - shot.out!.status === "splash" - ? shotStatusColors.splash - : shotStatusColors[layer.status]; - - return ( - - ); - })} - - - )} -
-
- ); -} diff --git a/packages/website/src/components/Armor/components/SpacedArmorScene.tsx b/packages/website/src/components/Armor/components/SpacedArmorScene.tsx deleted file mode 100644 index 9b4110be7..000000000 --- a/packages/website/src/components/Armor/components/SpacedArmorScene.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { memo, useRef } from "react"; -import { Group, Plane, Scene, Vector3 } from "three"; -import { correctZYTuple } from "../../../core/blitz/correctZYTuple"; -import { nameToArmorId } from "../../../core/blitzkit/nameToArmorId"; -import { resolveArmor } from "../../../core/blitzkit/resolveThickness"; -import { useArmor } from "../../../hooks/useArmor"; -import { useModel } from "../../../hooks/useModel"; -import { useTankModelDefinition } from "../../../hooks/useTankModelDefinition"; -import { useTankTransform } from "../../../hooks/useTankTransform"; -import { Duel } from "../../../stores/duel"; -import { ModelTankWrapper } from "./ModelTankWrapper"; -import { SpacedArmorSceneComponent } from "./SpacedArmorSceneComponent"; - -export enum ArmorType { - Primary, - Spaced, - External, -} - -interface SpacedArmorSceneProps { - scene: Scene; -} - -export const SpacedArmorScene = memo(({ scene }) => { - const wrapper = useRef(null); - const turretContainer = useRef(null!); - const gunContainer = useRef(null!); - const tank = Duel.use((state) => state.protagonist.tank); - const track = Duel.use((state) => state.protagonist.track); - const turret = Duel.use((state) => state.protagonist.turret); - const gun = Duel.use((state) => state.protagonist.gun); - const { gltf: armorGltf } = useArmor(tank.id); - const { gltf: modelGltf } = useModel(tank.id); - const armorNodes = Object.values(armorGltf.nodes); - const modelNodes = Object.values(modelGltf.nodes); - const tankModelDefinition = useTankModelDefinition(); - const trackModelDefinition = tankModelDefinition.tracks[track.id]; - const turretModelDefinition = tankModelDefinition.turrets[turret.id]; - const gunModelDefinition = turretModelDefinition.guns[gun.id]; - const hullOrigin = correctZYTuple(trackModelDefinition.origin!); - const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!); - const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!); - const maskOrigin = - gunModelDefinition.mask === undefined - ? undefined - : gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y; - const isDynamicArmorActive = Duel.use((state) => - state.protagonist.consumables.includes(73), - ); - - useTankTransform(track, turret, turretContainer, gunContainer); - - return ( - - - {armorNodes.map((node) => { - const isHull = node.name.startsWith("hull_"); - const isVisible = isHull; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - tankModelDefinition.armor!, - armorId, - ); - - if ( - !isVisible || - thickness === undefined || - (isDynamicArmorActive && node.name.includes("state_00")) || - (!isDynamicArmorActive && node.name.includes("state_01")) - ) - return null; - - return ( - - ); - })} - - - {modelNodes.map((node) => { - const isWheel = node.name.startsWith("chassis_wheel_"); - const isTrack = node.name.startsWith("chassis_track_"); - const isVisible = isWheel || isTrack; - - if (!isVisible) return null; - - return ( - - ); - })} - - - {armorNodes.map((node) => { - const isCurrentTurret = node.name.startsWith( - `turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`, - ); - const isVisible = isCurrentTurret; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - turretModelDefinition.armor!, - armorId, - ); - - if ( - !isVisible || - thickness === undefined || - (isDynamicArmorActive && node.name.includes("state_00")) || - (!isDynamicArmorActive && node.name.includes("state_01")) - ) - return null; - - return ( - - - - - - ); - })} - - - {armorNodes.map((node) => { - const isCurrentGun = node.name.startsWith( - `gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`, - ); - const isVisible = isCurrentGun; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - gunModelDefinition.armor!, - armorId, - ); - - if ( - !isVisible || - thickness === undefined || - (isDynamicArmorActive && node.name.includes("state_00")) || - (!isDynamicArmorActive && node.name.includes("state_01")) - ) - return null; - - return ( - - - - - - ); - })} - - {modelNodes.map((node) => { - const gunString = `gun_${gunModelDefinition.model_id - .toString() - .padStart(2, "0")}`; - const isCurrentGun = gunModelDefinition.mask - ? node.name.startsWith(gunString) - : node.name === gunString; - const isVisible = isCurrentGun; - - if (!isVisible) return null; - - return ( - - ); - })} - - - - ); -}); diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx deleted file mode 100644 index 9adbf0cef..000000000 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { resolvePenetrationCoefficient } from "@blitzkit/core"; -import { useEffect } from "react"; -import { - AdditiveBlending, - MeshBasicMaterial, - Object3D, - Plane, - ShaderMaterial, -} from "three"; -import type { ArmorUserData, ExternalModuleVariant } from "../.."; -import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment"; -import { jsxTree } from "../../../../../../core/blitzkit/jsxTree"; -import { Duel } from "../../../../../../stores/duel"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { ArmorType } from "../../../SpacedArmorScene"; -import fragmentShader from "./shaders/fragment.glsl?raw"; -import vertexShader from "./shaders/vertex.glsl?raw"; - -interface SpacedArmorSubExternalProps { - node: Object3D; - thickness: number; - variant: ExternalModuleVariant; - clip?: Plane; -} - -export function SpacedArmorSubExternal({ - node, - thickness, - variant, - clip, -}: SpacedArmorSubExternalProps) { - const material = new ShaderMaterial({ - fragmentShader, - vertexShader, - - depthTest: true, - depthWrite: false, - blending: AdditiveBlending, - clipping: clip !== undefined, - ...(clip ? { clippingPlanes: [clip] } : {}), - - uniforms: { - thickness: { value: null }, - penetration: { value: null }, - }, - }); - - useEffect(() => { - function handleShellChange() { - const tankopediaEphemeral = Tankopedia.state; - const shell = - tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; - material.uniforms.penetration.value = shell.penetration!.near; - } - function handleProtagonistEquipmentChange() { - const equipment = Duel.state.protagonist.equipmentMatrix; - const hasEnhancedArmor = hasEquipment( - 110, - Duel.state.protagonist.tank.equipment_preset, - equipment, - ); - const equalizer = - (Duel.state.equalize - ? Duel.state.protagonist.tank.equalizer?.armor - : undefined) ?? 1; - - material.uniforms.thickness.value = - thickness * (hasEnhancedArmor ? 1.03 : 1) * equalizer; - } - function handleAntagonistEquipmentChange() { - const equipment = Duel.state.antagonist.equipmentMatrix; - const tankopediaEphemeral = Tankopedia.state; - const shell = - tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; - const penetration = shell.penetration!.near; - const hasCalibratedShells = hasEquipment( - 103, - Duel.state.antagonist.tank.equipment_preset, - equipment, - ); - const equalize = - (Duel.state.equalize - ? Duel.state.antagonist.tank.equalizer?.penetration - : undefined) ?? 1; - - material.uniforms.penetration.value = - penetration * - resolvePenetrationCoefficient( - hasCalibratedShells, - Duel.state.equalize, - shell.type, - Duel.state.antagonist.tank.equalizer, - ) * - equalize; - } - - handleShellChange(); - handleProtagonistEquipmentChange(); - handleAntagonistEquipmentChange(); - - const unsubscribes = [ - Duel.on((state) => state.antagonist.shell, handleShellChange), - Tankopedia.on((state) => state.customShell, handleShellChange), - Duel.on( - (state) => state.protagonist.equipmentMatrix, - handleProtagonistEquipmentChange, - ), - Duel.on( - (state) => state.antagonist.equipmentMatrix, - handleAntagonistEquipmentChange, - ), - Duel.on( - (state) => state.equalize, - () => { - handleProtagonistEquipmentChange(); - handleAntagonistEquipmentChange(); - }, - ), - ]; - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); - }; - }); - - return ( - <> - {jsxTree(node, { - group(_, props, key) { - return ( - - ); - }, - - mesh(_, props, key) { - return ( - {}} - 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 ( - - ); - }, - - mesh(_, props, key) { - return ( - - ); - }, - })} - - ); -} diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/shaders/fragment.glsl b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/shaders/fragment.glsl deleted file mode 100644 index 5d3ededb1..000000000 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/shaders/fragment.glsl +++ /dev/null @@ -1,12 +0,0 @@ -precision mediump float; - -uniform float thickness; -uniform float penetration; - -#include - -void main() { - #include - - gl_FragColor = vec4(thickness / penetration, 0.0, 0.0, 1.0); -} \ No newline at end of file diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/shaders/vertex.glsl b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/shaders/vertex.glsl deleted file mode 100644 index 0c9b82ba3..000000000 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/shaders/vertex.glsl +++ /dev/null @@ -1,9 +0,0 @@ -precision mediump float; - -#include - -void main() { - #include - #include - #include -} \ No newline at end of file diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx deleted file mode 100644 index d30e1074b..000000000 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { isExplosive, resolvePenetrationCoefficient } from "@blitzkit/core"; -import { useEffect } from "react"; -import { - AdditiveBlending, - MeshBasicMaterial, - ShaderMaterial, - type Object3D, -} from "three"; -import { degToRad } from "three/src/math/MathUtils.js"; -import type { ArmorUserData } from "../.."; -import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment"; -import { jsxTree } from "../../../../../../core/blitzkit/jsxTree"; -import { Duel } from "../../../../../../stores/duel"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { ArmorType } from "../../../SpacedArmorScene"; -import fragmentShader from "./shaders/fragment.glsl?raw"; -import vertexShader from "./shaders/vertex.glsl?raw"; - -interface SpacedArmorSubSpacedProps { - node: Object3D; - thickness: number; -} - -const depthWriteMaterial = new MeshBasicMaterial({ - depthWrite: true, - colorWrite: false, -}); - -export function SpacedArmorSubSpaced({ - node, - thickness, -}: SpacedArmorSubSpacedProps) { - const material = new ShaderMaterial({ - fragmentShader, - vertexShader, - - depthTest: true, - depthWrite: false, - blending: AdditiveBlending, - - uniforms: { - thickness: { value: null }, - penetration: { value: null }, - caliber: { value: null }, - ricochet: { value: null }, - normalization: { value: null }, - }, - }); - - useEffect(() => { - function handleShellChange() { - const tankopediaEphemeral = Tankopedia.state; - const shell = - tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; - - material.uniforms.penetration.value = shell.penetration!.near; - material.uniforms.caliber.value = shell.caliber; - material.uniforms.ricochet.value = degToRad( - isExplosive(shell.type) ? 90 : shell.ricochet!, - ); - material.uniforms.normalization.value = degToRad( - shell.normalization ?? 0, - ); - } - function handleProtagonistEquipmentChange() { - const equipment = Duel.state.protagonist.equipmentMatrix; - const hasEnhancedArmor = hasEquipment( - 110, - Duel.state.protagonist.tank.equipment_preset, - equipment, - ); - const equalizer = - (Duel.state.equalize - ? Duel.state.protagonist.tank.equalizer?.armor - : undefined) ?? 1; - - material.uniforms.thickness.value = - thickness * (hasEnhancedArmor ? 1.03 : 1) * equalizer; - } - function handleAntagonistEquipmentChange() { - const equipment = Duel.state.antagonist.equipmentMatrix; - const tankopediaEphemeral = Tankopedia.state; - const shell = - tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; - const penetration = shell.penetration!.near; - const hasCalibratedShells = hasEquipment( - 103, - Duel.state.antagonist.tank.equipment_preset, - equipment, - ); - const equalize = - (Duel.state.equalize - ? Duel.state.antagonist.tank.equalizer?.penetration - : undefined) ?? 1; - - material.uniforms.penetration.value = - penetration * - resolvePenetrationCoefficient( - hasCalibratedShells, - Duel.state.equalize, - shell.type, - Duel.state.antagonist.tank.equalizer, - ) * - equalize; - } - - handleShellChange(); - handleProtagonistEquipmentChange(); - handleAntagonistEquipmentChange(); - - const unsubscribes = [ - Duel.on((state) => state.antagonist.shell, handleShellChange), - Tankopedia.on((state) => state.customShell, handleShellChange), - Duel.on( - (state) => state.protagonist.equipmentMatrix, - handleProtagonistEquipmentChange, - ), - Duel.on( - (state) => state.antagonist.equipmentMatrix, - handleAntagonistEquipmentChange, - ), - Duel.on( - (state) => state.equalize, - () => { - handleProtagonistEquipmentChange(); - handleAntagonistEquipmentChange(); - }, - ), - ]; - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); - }; - }); - - return ( - <> - {jsxTree(node, { - group(_, props, key) { - return ; - }, - - mesh(_, props, key) { - return ( - {}} - userData={ - { - type: ArmorType.Spaced, - thickness, - } satisfies ArmorUserData - } - /> - ); - }, - })} - - {jsxTree(node, { - group(_, props, key) { - return ; - }, - - mesh(_, props, key) { - return ( - - ); - }, - })} - - ); -} diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/shaders/fragment.glsl b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/shaders/fragment.glsl deleted file mode 100644 index 6fafe53d1..000000000 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/shaders/fragment.glsl +++ /dev/null @@ -1,26 +0,0 @@ -precision mediump float; - -varying vec3 vNormal; -varying vec3 vViewPosition; - -uniform float thickness; -uniform float penetration; -uniform float caliber; -uniform float ricochet; -uniform float normalization; - -void main() { - float angle = acos(dot(vNormal, -vViewPosition) / length(vViewPosition)); - - bool threeCalibersRule = caliber > thickness * 3.0; - if (!threeCalibersRule && angle >= ricochet) { - gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); - return; - } - - bool twoCalibersRule = caliber > thickness * 2.0 && thickness > 0.0; - float finalNormalization = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization; - float finalThickness = thickness / cos(max(0.0, angle - finalNormalization)); - - gl_FragColor = vec4(finalThickness / penetration, 0.0, 0.0, 1.0); -} \ No newline at end of file diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/shaders/vertex.glsl b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/shaders/vertex.glsl deleted file mode 100644 index 459297160..000000000 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/shaders/vertex.glsl +++ /dev/null @@ -1,13 +0,0 @@ -precision mediump float; - -varying vec3 vNormal; -varying vec3 vViewPosition; - -void main() { - vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0); - - vViewPosition = vViewPosition4.xyz; - vNormal = normalMatrix * normal; - - gl_Position = projectionMatrix * vViewPosition4; -} \ No newline at end of file diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx deleted file mode 100644 index ffd6133b8..000000000 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx +++ /dev/null @@ -1,436 +0,0 @@ -import { - ShellType, - isExplosive, - resolvePenetrationCoefficient, -} from "@blitzkit/core"; -import { useThree } from "@react-three/fiber"; -import { useCallback } from "react"; -import { - type Intersection, - MeshBasicMaterial, - Object3D, - Plane, - Quaternion, - Raycaster, - Scene, - Vector3, -} from "three"; -import { degToRad } from "three/src/math/MathUtils.js"; -import { hasEquipment } from "../../../../core/blitzkit/hasEquipment"; -import { jsxTree } from "../../../../core/blitzkit/jsxTree"; -import { defaultEqualizer } from "../../../../core/blitzkit/tankToDuelMember"; -import { discardClippingPlane } from "../../../../core/three/discardClippingPlane"; -import { Duel } from "../../../../stores/duel"; -import { - type Shot, - type ShotLayerBase, - type ShotLayerNonExternal, - Tankopedia, -} from "../../../../stores/tankopedia"; -import { ArmorType } from "../SpacedArmorScene"; -import { SpacedArmorSubExternal } from "./components/SpacedArmorSubExternal"; -import { SpacedArmorSubSpaced } from "./components/SpacedArmorSubSpaced"; - -type SpacedArmorSceneComponentProps = { - node: Object3D; - thickness: number; - clip?: Plane; - scene: Scene; -} & ( - | { - type: Exclude; - } - | { - type: ArmorType.External; - variant: ExternalModuleVariant; - } -); - -export type ExternalModuleVariant = "gun" | "track"; -export type ArmorUserData = { - thickness: number; -} & ( - | { - type: Exclude; - } - | { - type: ArmorType.External; - variant: ExternalModuleVariant; - } -); - -const omitMaterial = new MeshBasicMaterial({ - colorWrite: false, - depthWrite: true, -}); - -/** - * Render orders allocations: - * - 0 : core omission - * - 1-2: spaced without depth buffer write but do test to allow additive blending - * - 3-4: external with depth buffer write in first pass and read in second to simplify geometry - * - 5 : spaced depth write - */ -export function SpacedArmorSceneComponent({ - node, - thickness, - clip, - ...props -}: SpacedArmorSceneComponentProps) { - const camera = useThree((state) => state.camera); - - const shoot = useCallback( - ( - point: Vector3, - intersections: Intersection[], - allowRicochet: boolean, - remainingPenetrationInput?: number, - ) => { - const { customShell } = Tankopedia.state; - const shell = customShell ?? Duel.state.antagonist.shell; - const { equalize } = Duel.state; - const protagonistEqualizer = - (equalize ? Duel.state.protagonist.tank.equalizer : undefined) ?? - defaultEqualizer; - const antagonistEqualizer = - (equalize ? Duel.state.antagonist.tank.equalizer : undefined) ?? - defaultEqualizer; - const cameraNormal = camera.position.clone().sub(point).normalize(); - const shot: Shot = { - splashRadius: - shell.type === ShellType.SHELL_TYPE_HE - ? shell.explosion_radius - : undefined, - damage: -1, - containsGaps: shell.type === ShellType.SHELL_TYPE_HEAT, - in: { - surfaceNormal: cameraNormal, - layers: [], - status: "blocked", - }, - }; - - const hasCalibratedShells = hasEquipment( - 103, - Duel.state.antagonist.tank.equipment_preset, - Duel.state.antagonist.equipmentMatrix, - ); - const hasEnhancedArmor = hasEquipment( - 110, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const penetration = - shell.penetration!.near * - resolvePenetrationCoefficient( - hasCalibratedShells, - equalize, - shell.type, - antagonistEqualizer, - ); - const thicknessCoefficient = - (hasEnhancedArmor ? 1.03 : 1) * protagonistEqualizer.armor; - const filteredIntersections = intersections.filter( - (intersection) => - "type" in intersection.object.userData && - !discardClippingPlane(intersection.object, intersection.point), - ) as unknown as Intersection[]; - const encounteredExternalModuleVariants: ExternalModuleVariant[] = []; - const noDuplicateIntersections: typeof filteredIntersections = []; - - for (const intersection of filteredIntersections) { - if (intersection.object.userData.type === ArmorType.External) { - if ( - !encounteredExternalModuleVariants.includes( - intersection.object.userData.variant, - ) - ) { - noDuplicateIntersections.push(intersection); - encounteredExternalModuleVariants.push( - intersection.object.userData.variant, - ); - } - } else { - noDuplicateIntersections.push(intersection); - - if (intersection.object.userData.type === ArmorType.Primary) { - break; - } - } - } - - if ( - !allowRicochet && - noDuplicateIntersections.every((intersection) => { - intersection.object.userData.type !== ArmorType.Primary; - }) - ) { - return null; - } - - let remainingPenetration = remainingPenetrationInput ?? penetration; - let index = 0; - for (const intersection of noDuplicateIntersections) { - const layer = intersection.object.userData; - const surfaceNormal = intersection - .normal!.clone() - .applyQuaternion( - intersection.object.getWorldQuaternion(new Quaternion()), - ); - - if (shell.type === ShellType.SHELL_TYPE_HEAT && index > 0) { - const lastLayer = shot.in.layers.at(-1) as ShotLayerBase; - const distance = lastLayer.point.distanceTo(intersection.point); - remainingPenetration -= 0.5 * remainingPenetration * distance; - const blocked = remainingPenetration <= 0; - - shot.in.layers.push({ - type: null, - distance, - status: blocked ? "blocked" : "penetration", - }); - - if (blocked) break; - } - - if (layer.type === ArmorType.External) { - const thickness = layer.thickness * thicknessCoefficient; - remainingPenetration -= thickness; - const blocked = - remainingPenetration <= 0 || shell.type === ShellType.SHELL_TYPE_HE; - - shot.in.layers.push({ - type: ArmorType.External, - index, - thickness, - point: intersection.point, - surfaceNormal, - status: - shell.type === ShellType.SHELL_TYPE_HE - ? "blocked" - : blocked - ? "blocked" - : "penetration", - variant: layer.variant, - }); - } else { - const thickness = layer.thickness * thicknessCoefficient; - const ricochet = degToRad( - isExplosive(shell.type) ? 90 : shell.ricochet!, - ); - const angle = surfaceNormal.angleTo(cameraNormal); - - const threeCalibersRule = - shell.caliber > thickness * 3 || index > 0 || !allowRicochet; - const twoCalibersRule = shell.caliber > thickness * 2; - const normalization = degToRad(shell.normalization ?? 0); - const finalNormalization = twoCalibersRule - ? (1.4 * normalization * shell.caliber) / (2.0 * thickness) - : normalization; - const finalThickness = - thickness / Math.cos(Math.max(0, angle - finalNormalization)); - - if (!threeCalibersRule && angle >= ricochet) { - remainingPenetration *= 0.75; - - shot.in.layers.push({ - type: layer.type, - index, - thickness, - thicknessAngled: finalThickness, - point: intersection.point, - surfaceNormal, - status: "ricochet", - angle, - }); - - break; - } else { - remainingPenetration -= finalThickness; - const blocked = remainingPenetration <= 0; - - shot.in.layers.push({ - type: layer.type, - index, - thickness, - thicknessAngled: finalThickness, - point: intersection.point, - surfaceNormal: intersection.face!.normal, - status: - shell.type === ShellType.SHELL_TYPE_HE && - layer.type !== ArmorType.Primary - ? "blocked" - : blocked - ? "blocked" - : "penetration", - angle, - }); - } - } - - if ( - shell.type !== ShellType.SHELL_TYPE_HE && - remainingPenetration <= 0 - ) { - break; - } - - index++; - } - - const lastLayer = shot.in.layers.at(-1) as - | ShotLayerNonExternal - | undefined; - const firstLayer = shot.in.layers[0] as ShotLayerBase | undefined; - - if (!lastLayer || !firstLayer) { - return null; - } else { - if (shell.type === ShellType.SHELL_TYPE_HE) { - const totalSpacedArmorThickness = shot.in.layers.reduce( - (acc, layer) => { - if (layer.type === null || layer.type === ArmorType.Primary) { - return acc; - } - - if (layer.type === ArmorType.External) { - return acc + layer.thickness; - } - - return acc + layer.thicknessAngled; - }, - 0, - ); - const distanceFromSpacedArmor = firstLayer.point.distanceTo( - lastLayer.point, - ); - const finalDamage = Math.max( - 0, - 0.5 * - shell.armor_damage * - (1 - distanceFromSpacedArmor / shell.explosion_radius!) - - 1.1 * - (lastLayer.thicknessAngled + - Math.min(penetration, totalSpacedArmorThickness)), - ); - - /** - * Above is some peculiar math. We're multiplying 1.1 by the - * thickness of the core plate plus the total spaced armor - * thickness capped to the penetration itself. This is because - * the shader is normalized by the penetration value; hence, - * the total spaced armor thickness cannot ever be thicker - * than the penetration. This is a precision error on the - * shader-side and I do not intend on fixing it. Instead, - * for parity's sake, I am capping the value here. - */ - - if (shot.in.layers.length > 1 || lastLayer.status === "blocked") { - shot.in.status = finalDamage > 0 ? "splash" : "blocked"; - shot.damage = finalDamage; - } else { - shot.in.status = "penetration"; - shot.damage = shell.armor_damage; - } - } else { - if (lastLayer.status === "blocked") { - shot.in.status = "blocked"; - shot.damage = 0; - } else if (lastLayer.status === "penetration") { - shot.in.status = "penetration"; - shot.damage = shell.armor_damage; - } else { - const caster = new Raycaster(); - // https://math.stackexchange.com/a/13263/1222875 - const shellNormal = cameraNormal.clone().multiplyScalar(-1); - const ricochetNormal = shellNormal - .clone() - .sub( - lastLayer.surfaceNormal - .clone() - .multiplyScalar(2 * shellNormal.dot(lastLayer.surfaceNormal)), - ); - - caster.set(lastLayer.point, ricochetNormal); - - const ricochetIntersections = caster.intersectObjects( - props.scene.children, - true, - ); - const outShot = shoot( - lastLayer.point, - ricochetIntersections, - false, - remainingPenetration, - ); - - shot.in.status = "ricochet"; - if (outShot === null) { - shot.damage = 0; - shot.out = { - surfaceNormal: ricochetNormal, - layers: [], - status: "ricochet", - }; - } else { - shot.damage = outShot.damage; - shot.out = outShot.in; - shot.out.surfaceNormal = ricochetNormal; - } - } - } - } - - shot.damage *= antagonistEqualizer.damage; - - return shot; - }, - [camera], - ); - - return ( - <> - {props.type === ArmorType.Primary && - jsxTree(node, { - mesh(_, props, key) { - return ( - { - event.stopPropagation(); - - const shot = shoot(event.point, event.intersections, true)!; - - Tankopedia.mutate((draft) => { - draft.shot = shot; - }); - }} - /> - ); - }, - })} - - {props.type === ArmorType.Spaced && ( - - )} - - {props.type === ArmorType.External && ( - - )} - - ); -} diff --git a/packages/website/src/components/Armor/components/StaticArmor.tsx b/packages/website/src/components/Armor/components/StaticArmor.tsx deleted file mode 100644 index 215220e35..000000000 --- a/packages/website/src/components/Armor/components/StaticArmor.tsx +++ /dev/null @@ -1,390 +0,0 @@ -import { useThree, type ThreeEvent } from "@react-three/fiber"; -import { memo, useRef } from "react"; -import { Group, Plane, Vector2, Vector3 } from "three"; -import { applyPitchYawLimits } from "../../../core/blitz/applyPitchYawLimits"; -import { correctZYTuple } from "../../../core/blitz/correctZYTuple"; -import { hasEquipment } from "../../../core/blitzkit/hasEquipment"; -import { modelTransformEvent } from "../../../core/blitzkit/modelTransform"; -import { nameToArmorId } from "../../../core/blitzkit/nameToArmorId"; -import { resolveArmor } from "../../../core/blitzkit/resolveThickness"; -import { controlsEnabledEvent } from "../../../core/controlsEnabled"; -import { useArmor } from "../../../hooks/useArmor"; -import { useModel } from "../../../hooks/useModel"; -import { useTankModelDefinition } from "../../../hooks/useTankModelDefinition"; -import { useTankTransform } from "../../../hooks/useTankTransform"; -import { Duel } from "../../../stores/duel"; -import { Tankopedia } from "../../../stores/tankopedia"; -import { TankopediaPersistent } from "../../../stores/tankopediaPersistent"; -import { ModelTankWrapper } from "./ModelTankWrapper"; -import { ArmorType } from "./SpacedArmorScene"; -import { StaticArmorSceneComponent } from "./StaticArmorSceneComponent"; - -interface ArmorSceneProps { - thicknessRange: ThicknessRange; -} - -export interface ThicknessRange { - value: number; -} - -export const StaticArmor = memo(({ thicknessRange }) => { - const wrapper = useRef(null); - const turretContainer = useRef(null!); - const gunContainer = useRef(null!); - const tank = Duel.use((state) => state.protagonist.tank); - const track = Duel.use((state) => state.protagonist.track); - const turret = Duel.use((state) => state.protagonist.turret); - const gun = Duel.use((state) => state.protagonist.gun); - const { gltf: armorGltf } = useArmor(tank.id); - const { gltf: modelGltf } = useModel(tank.id); - const armorNodes = Object.values(armorGltf.nodes); - const modelNodes = Object.values(modelGltf.nodes); - const tankModelDefinition = useTankModelDefinition(); - const trackModelDefinition = tankModelDefinition.tracks[track.id]; - const turretModelDefinition = tankModelDefinition.turrets[turret.id]; - const gunModelDefinition = turretModelDefinition.guns[gun.id]; - const hullOrigin = correctZYTuple(trackModelDefinition.origin!); - const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!); - const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!); - const canvas = useThree((state) => state.gl.domElement); - - const maskOrigin = - gunModelDefinition.mask === undefined - ? undefined - : gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y; - const showPrimaryArmor = TankopediaPersistent.use( - (state) => state.showPrimaryArmor, - ); - const showSpacedArmor = TankopediaPersistent.use( - (state) => state.showSpacedArmor, - ); - const showExternalModules = TankopediaPersistent.use( - (state) => state.showExternalModules, - ); - const isDynamicArmorActive = Duel.use((state) => - state.protagonist.consumables.includes(73), - ); - - useTankTransform(track, turret, turretContainer, gunContainer); - - return ( - - - {armorNodes.map((node) => { - const isHull = node.name.startsWith("hull_"); - const isVisible = isHull; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - tankModelDefinition.armor!, - armorId, - ); - - if ( - !isVisible || - thickness === undefined || - (!showPrimaryArmor && !spaced) || - (!showSpacedArmor && spaced) || - (isDynamicArmorActive && node.name.includes("state_00")) || - (!isDynamicArmorActive && node.name.includes("state_01")) - ) - return null; - - return ( - event.stopPropagation()} - /> - ); - })} - - - {modelNodes.map((node) => { - const isWheel = node.name.startsWith("chassis_wheel_"); - const isTrack = node.name.startsWith("chassis_track_"); - const isVisible = isWheel || isTrack; - - if (!isVisible || !showExternalModules) return null; - - return ( - event.stopPropagation()} - /> - ); - })} - - - {armorNodes.map((node) => { - const isCurrentTurret = node.name.startsWith( - `turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`, - ); - const isVisible = isCurrentTurret; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - turretModelDefinition.armor!, - armorId, - ); - - if ( - !isVisible || - thickness === undefined || - (!showPrimaryArmor && !spaced) || - (!showSpacedArmor && spaced) || - (isDynamicArmorActive && node.name.includes("state_00")) || - (!isDynamicArmorActive && node.name.includes("state_01")) - ) { - return null; - } - - const position = new Vector2(); - const delta = new Vector2(); - - function onPointerDown(event: ThreeEvent) { - event.stopPropagation(); - - position.set(event.clientX, event.clientY); - - controlsEnabledEvent.dispatch(false); - - Tankopedia.mutate((draft) => { - draft.shot = undefined; - draft.highlightArmor = undefined; - }); - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } - function handlePointerMove(event: PointerEvent) { - const hasImprovedVerticalStabilizer = hasEquipment( - 122, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const hasDownImprovedVerticalStabilizer = hasEquipment( - 124, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const boundingRect = canvas.getBoundingClientRect(); - - delta.set(event.clientX, event.clientY).sub(position); - position.set(event.clientX, event.clientY); - - const [pitch, yaw] = applyPitchYawLimits( - modelTransformEvent.last!.pitch, - modelTransformEvent.last!.yaw + - delta.x * (Math.PI / boundingRect.width), - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - modelTransformEvent.dispatch({ pitch, yaw }); - } - function handlePointerUp() { - controlsEnabledEvent.dispatch(true); - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - } - - return ( - - - - - - ); - })} - - - {armorNodes.map((node) => { - const isCurrentGun = node.name.startsWith( - `gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`, - ); - const isVisible = isCurrentGun; - const armorId = nameToArmorId(node.name); - const { spaced, thickness } = resolveArmor( - gunModelDefinition.armor!, - armorId, - ); - - if ( - !isVisible || - thickness === undefined || - (!showPrimaryArmor && !spaced) || - (!showSpacedArmor && spaced) || - (isDynamicArmorActive && node.name.includes("state_00")) || - (!isDynamicArmorActive && node.name.includes("state_01")) - ) - return null; - - const position = new Vector2(); - const delta = new Vector2(); - - function onPointerDown(event: ThreeEvent) { - event.stopPropagation(); - - controlsEnabledEvent.dispatch(false); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - draft.highlightArmor = undefined; - }); - - position.set(event.clientX, event.clientY); - - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } - function handlePointerMove(event: PointerEvent) { - const hasImprovedVerticalStabilizer = hasEquipment( - 122, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const hasDownImprovedVerticalStabilizer = hasEquipment( - 124, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const boundingRect = canvas.getBoundingClientRect(); - delta.set(event.clientX, event.clientY).sub(position); - position.set(event.clientX, event.clientY); - - const [pitch, yaw] = applyPitchYawLimits( - modelTransformEvent.last!.pitch - - delta.y * (Math.PI / boundingRect.height), - modelTransformEvent.last!.yaw + - delta.x * (Math.PI / boundingRect.width), - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - modelTransformEvent.dispatch({ pitch, yaw }); - } - function handlePointerUp() { - controlsEnabledEvent.dispatch(true); - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - } - - return ( - - - - - - ); - })} - - {modelNodes.map((node) => { - const gunString = `gun_${gunModelDefinition.model_id - .toString() - .padStart(2, "0")}`; - const isCurrentGun = gunModelDefinition.mask - ? node.name.startsWith(gunString) - : node.name === gunString; - const isVisible = isCurrentGun; - - if (!isVisible || !showExternalModules) return null; - - const position = new Vector2(); - const delta = new Vector2(); - - function onPointerDown(event: ThreeEvent) { - event.stopPropagation(); - - controlsEnabledEvent.dispatch(false); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - draft.highlightArmor = undefined; - }); - - position.set(event.clientX, event.clientY); - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } - function handlePointerMove(event: PointerEvent) { - const hasImprovedVerticalStabilizer = hasEquipment( - 122, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const hasDownImprovedVerticalStabilizer = hasEquipment( - 124, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const boundingRect = canvas.getBoundingClientRect(); - delta.set(event.clientX, event.clientY).sub(position); - position.set(event.clientX, event.clientY); - - const [pitch, yaw] = applyPitchYawLimits( - modelTransformEvent.last!.pitch - - delta.y * (Math.PI / boundingRect.height), - modelTransformEvent.last!.yaw + - delta.x * (Math.PI / boundingRect.width), - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - modelTransformEvent.dispatch({ pitch, yaw }); - } - function handlePointerUp() { - controlsEnabledEvent.dispatch(true); - - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - } - - return ( - - ); - })} - - - - ); -}); diff --git a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx b/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx deleted file mode 100644 index c3e97aca1..000000000 --- a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx +++ /dev/null @@ -1,328 +0,0 @@ -import { I_HAT, J_HAT } from "@blitzkit/core"; -import { invalidate, useThree } from "@react-three/fiber"; -import { clamp } from "lodash-es"; -import type { QuicklimeEvent } from "quicklime"; -import { useEffect, useMemo, type ComponentProps } from "react"; -import { - Box3, - Color, - DoubleSide, - FrontSide, - LineBasicMaterial, - Mesh, - MeshBasicMaterial, - Object3D, - Plane, - Quaternion, - Vector3, -} from "three"; -import { unrotateDavaVector } from "../../../core/blitz/unrotateDavaVector"; -import { jsxTree } from "../../../core/blitzkit/jsxTree"; -import { - modelTransformEvent, - type ModelTransformEventData, -} from "../../../core/blitzkit/modelTransform"; -import { defaultEqualizer } from "../../../core/blitzkit/tankToDuelMember"; -import { discardClippingPlane } from "../../../core/three/discardClippingPlane"; -import { useEquipment } from "../../../hooks/useEquipment"; -import { Duel } from "../../../stores/duel"; -import { Tankopedia } from "../../../stores/tankopedia"; -import { transitionEvent } from "../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting"; -import { ArmorType } from "./SpacedArmorScene"; -import type { - ArmorUserData, - ExternalModuleVariant, -} from "./SpacedArmorSceneComponent"; -import type { ThicknessRange } from "./StaticArmor"; - -type StaticArmorSceneComponentProps = { - name: string; - thickness: number; - thicknessRange: ThicknessRange; - node: Object3D; - onPointerDown?: ComponentProps<"mesh">["onPointerDown"]; -} & ( - | { - type: Exclude; - } - | { - type: ArmorType.External; - variant: ExternalModuleVariant; - } -) & - ( - | { - clip?: undefined; - } - | { - clip: Plane; - hullOrigin: Vector3; - turretOrigin: Vector3; - gunOrigin: Vector3; - } - ); - -const unselectedColor = new Color(0x404040); -const externalModuleColor = new Color(192 / 255, 192 / 255, 192 / 255); - -export function StaticArmorSceneComponent({ - name, - thickness: thicknessRaw, - thicknessRange, - node, - onPointerDown, - ...props -}: StaticArmorSceneComponentProps) { - const camera = useThree((state) => state.camera); - const hasEnhancedArmor = useEquipment(110); - const tank = Duel.use((state) => state.protagonist.tank); - const equalize = Duel.use((state) => state.equalize); - const equalizer = (equalize ? tank.equalizer : undefined) ?? defaultEqualizer; - const thicknessCoefficient = (hasEnhancedArmor ? 1.03 : 1) * equalizer.armor; - const thickness = thicknessRaw * thicknessCoefficient; - const x = thickness / thicknessRange.value; - const xClamped = clamp(x, 0, 1); - const color = useMemo(() => { - switch (props.type) { - case ArmorType.Primary: { - if (x > 1) { - return new Color(clamp(2 - x, 0.5, 1), 0, 0); - } else { - return new Color(-((1 - x) ** 2) + 1, -(x ** 2) + 1, 0); - } - } - - case ArmorType.Spaced: { - return new Color( - clamp(1 - (7 / 8) * xClamped, 0, 1), - 0, - clamp(1 - (1 / 8) * xClamped, 0, 1), - ); - } - - case ArmorType.External: { - return externalModuleColor; - } - } - }, [props.type, xClamped]); - const opacity = useMemo(() => { - switch (props.type) { - case ArmorType.Primary: - return 1; - - case ArmorType.Spaced: - return clamp(x + 1 / 2, 0, 1); - - case ArmorType.External: - return 1 / 8; - } - }, [props.type, x]); - const depthWrite = useMemo(() => { - switch (props.type) { - case ArmorType.Primary: - return true; - - case ArmorType.Spaced: - return true; - - case ArmorType.External: - return false; - } - }, [props.type]); - - const surfaceMaterial = useMemo( - () => - new MeshBasicMaterial({ - color, - transparent: true, - depthWrite, - ...(props.clip ? { clippingPlanes: [props.clip] } : {}), - opacity: 0, - userData: { opacity0: opacity }, - }), - [hasEnhancedArmor, equalize], - ); - const outlineMaterial = useMemo( - () => - new LineBasicMaterial({ - color: color - .clone() - .multiplyScalar(props.type === ArmorType.Spaced ? 2 ** 2 : 2 ** -1), - opacity: 0, - transparent: true, - }), - [hasEnhancedArmor, equalize], - ); - - useEffect(() => { - function handleHighlightArmor(selectedName?: string) { - if (selectedName === undefined) { - // nothing selected, go back to defaults - surfaceMaterial.opacity = opacity; - surfaceMaterial.transparent = opacity < 1; - surfaceMaterial.color = color; - surfaceMaterial.depthWrite = props.type !== ArmorType.External; - surfaceMaterial.side = FrontSide; - outlineMaterial.visible = true; - } else if ( - selectedName === name || - (name.startsWith("chassis_") && selectedName.startsWith("chassis_")) || - (name.startsWith("gun_") && - selectedName.startsWith("gun_") && - !name.includes("_armor_") && - !selectedName.includes("_armor_")) - ) { - // this selected, stand out! - surfaceMaterial.opacity = 1; - surfaceMaterial.transparent = false; - surfaceMaterial.color = color; - surfaceMaterial.depthWrite = true; - surfaceMaterial.side = DoubleSide; - outlineMaterial.visible = true; - } else { - // something else selected, become background - surfaceMaterial.opacity = 1 / 4; - surfaceMaterial.transparent = true; - surfaceMaterial.color = unselectedColor; - surfaceMaterial.depthWrite = props.type !== ArmorType.External; - surfaceMaterial.side = FrontSide; - outlineMaterial.visible = false; - } - - surfaceMaterial.needsUpdate = true; - invalidate(); - } - - handleHighlightArmor(); - - function handleTransitionEvent(event: QuicklimeEvent) { - surfaceMaterial.opacity = surfaceMaterial.userData.opacity0 * event.data; - outlineMaterial.opacity = event.data; - - surfaceMaterial.transparent = surfaceMaterial.opacity < 1; - outlineMaterial.transparent = outlineMaterial.opacity < 1; - } - - transitionEvent.on(handleTransitionEvent); - - const unsubscribes = [ - Tankopedia.on( - (state) => state.highlightArmor?.name, - handleHighlightArmor, - ), - () => transitionEvent.off(handleTransitionEvent), - ]; - - if (props.clip) { - /** - * hook inside an if statement?? don't panic! I assure you the clip prop - * never mutates :) - */ - - const { clip } = props; - const gunOrigin = unrotateDavaVector(props.gunOrigin.clone()); - const neckOrigin = unrotateDavaVector(props.hullOrigin.clone()).add( - unrotateDavaVector(props.turretOrigin.clone()), - ); - const barrelOrigin = neckOrigin.clone().add(gunOrigin); - const distanceToBarrel = clip.distanceToPoint(barrelOrigin); - const point = new Vector3(); - - function handleModelTransform( - event: QuicklimeEvent, - ) { - clip.normal - .set(0, 0, -1) - .applyAxisAngle(I_HAT, event.data.pitch) - .applyAxisAngle(J_HAT, event.data.yaw); - clip.constant = 0; - - point - .copy(gunOrigin) - .applyAxisAngle(J_HAT, event.data.yaw) - .add(clip.normal.multiplyScalar(-distanceToBarrel)) - .add(neckOrigin); - clip.normal.normalize(); - clip.constant -= clip.distanceToPoint(point); - } - - modelTransformEvent.on(handleModelTransform); - - unsubscribes.push(() => modelTransformEvent.off(handleModelTransform)); - } - - return () => { - unsubscribes.forEach((unsubscribe) => unsubscribe()); - }; - }, [hasEnhancedArmor, equalize]); - - return ( - <> - {jsxTree(node, { - mesh(_, meshProps, key) { - return ( - { - if (discardClippingPlane(event.object, event.point)) return; - - event.stopPropagation(); - - const { editStatic } = Tankopedia.state; - - 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); - - Tankopedia.mutate((draft) => { - draft.highlightArmor = { - type: props.type, - name, - point, - thickness, - thicknessAngled, - angle, - color: `#${color.getHexString()}`, - editingPlate: editStatic, - }; - }); - }} - /> - ); - }, - })} - - {node instanceof Mesh && ( - - - - )} - - ); -} diff --git a/packages/website/src/components/Armor/index.tsx b/packages/website/src/components/Armor/index.tsx deleted file mode 100644 index 580133ab8..000000000 --- a/packages/website/src/components/Armor/index.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { createPortal, useFrame, useThree } from '@react-three/fiber'; -import { memo, useState } from 'react'; -import { DepthTexture, Scene, Vector2 } from 'three'; -import { PrimaryArmorScene } from './components/PrimaryArmorScene'; -import { spacedArmorRenderTarget } from './components/PrimaryArmorSceneComponent/target'; -import { SpacedArmorScene } from './components/SpacedArmorScene'; - -export const Armor = memo(() => { - const rootScene = useThree((state) => state.scene); - const [spacedArmorScene] = useState(() => new Scene()); - const [primaryArmorScene] = useState(() => { - const primaryArmorScene = new Scene(); - primaryArmorScene.fog = rootScene.fog; - - return primaryArmorScene; - }); - const spacedArmorPortal = createPortal( - , - spacedArmorScene, - ); - const primaryArmorPortal = createPortal( - , - primaryArmorScene, - ); - - const renderSize = new Vector2(); - const newRenderSize = new Vector2(); - - useFrame(({ gl, camera, scene }) => { - gl.autoClear = true; - - gl.getSize(newRenderSize).multiplyScalar(gl.getPixelRatio()); - - if (!newRenderSize.equals(renderSize)) { - renderSize.copy(newRenderSize); - spacedArmorRenderTarget.depthTexture = new DepthTexture( - renderSize.x, - renderSize.y, - ); - } - - spacedArmorRenderTarget.setSize(renderSize.x, renderSize.y); - gl.setRenderTarget(spacedArmorRenderTarget); - gl.render(spacedArmorScene, camera); - - gl.autoClear = false; - - gl.setRenderTarget(null); - gl.render(scene, camera); - - gl.clearDepth(); - gl.render(primaryArmorScene, camera); - }, 1); - - return ( - <> - {spacedArmorPortal} - {primaryArmorPortal} - - ); -}); diff --git a/packages/website/src/components/ArticlePlug.astro b/packages/website/src/components/ArticlePlug.astro deleted file mode 100644 index 4ef838462..000000000 --- a/packages/website/src/components/ArticlePlug.astro +++ /dev/null @@ -1,38 +0,0 @@ ---- -import { tankIcon } from "@blitzkit/core"; -import { Flex, Heading, Link, Strong, Text } from "@radix-ui/themes"; ---- - - - - - - - - Christmas 2025 - - Win the GSOR the TANK and more! - - - - - diff --git a/packages/website/src/components/BlitzKitTheme/index.tsx b/packages/website/src/components/BlitzKitTheme/index.tsx deleted file mode 100644 index 1087417b7..000000000 --- a/packages/website/src/components/BlitzKitTheme/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Theme, type ThemeProps } from "@radix-ui/themes"; -import type { ReactNode } from "react"; - -interface BlitzKitThemeProps extends ThemeProps { - children: ReactNode; -} - -export function BlitzKitTheme({ - children, - className, - style, - ...props -}: BlitzKitThemeProps) { - return ( - - {children} - - ); -} diff --git a/packages/website/src/components/BlitzkitButtonGrayIcon.tsx b/packages/website/src/components/BlitzkitButtonGrayIcon.tsx deleted file mode 100644 index a9e7b07f3..000000000 --- a/packages/website/src/components/BlitzkitButtonGrayIcon.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function BlitzkitButtonGrayIcon(props: ComponentProps<'svg'>) { - return ( - - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/Checks/index.css b/packages/website/src/components/Checks/index.css deleted file mode 100644 index ba4682721..000000000 --- a/packages/website/src/components/Checks/index.css +++ /dev/null @@ -1,18 +0,0 @@ -.policies-agreement { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--color-overlay); - z-index: 2; -} - -.policies-agreement .content-wrapper { - width: 100%; - background: var(--color-panel-solid); -} - -.policies-agreement .content { - max-width: 60rem; -} diff --git a/packages/website/src/components/Checks/index.tsx b/packages/website/src/components/Checks/index.tsx deleted file mode 100644 index 539f99042..000000000 --- a/packages/website/src/components/Checks/index.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { assertSecret, idToRegion } from "@blitzkit/core"; -import { Button, Flex, Heading, Text } from "@radix-ui/themes"; -import { useEffect, useState } from "react"; -import { - LocaleProvider, - useLocale, - type LocaleAcceptorProps, -} from "../../hooks/useLocale"; -import { App } from "../../stores/app"; -import { CURRENT_POLICIES_AGREEMENT_INDEX } from "../../stores/app/constants"; -import { LinkI18n } from "../LinkI18n"; -import "./index.css"; - -type Extension = - | { - status: "ok"; - data: { - access_token: string; - account_id: number; - expires_at: number; - }; - } - | { - status: "error"; - }; - -export function Checks({ locale }: LocaleAcceptorProps) { - return ( - - - - ); -} - -/** - * Wargaming: 14 (refresh: 7) - */ -function Content() { - const { locale } = useLocale(); - const logins = App.use((state) => state.logins); - const policiesAgreementIndex = App.use( - (state) => state.policiesAgreementIndex - ); - const isLegal = window.location.pathname.startsWith("/legal"); - const [showPoliciesAgreement, setShowPoliciesAgreement] = useState(false); - - useEffect(() => { - function unsubscribe() { - window.removeEventListener("scroll", show); - window.removeEventListener("pointermove", show); - window.removeEventListener("pointerdown", show); - } - - function show() { - setShowPoliciesAgreement(true); - unsubscribe(); - } - - if ( - policiesAgreementIndex !== CURRENT_POLICIES_AGREEMENT_INDEX && - !isLegal - ) { - window.addEventListener("scroll", show); - window.addEventListener("pointermove", show); - window.addEventListener("pointerdown", show); - } - - return unsubscribe; - }, [policiesAgreementIndex, isLegal]); - - useEffect(() => { - if (logins.wargaming) { - const expiresIn = logins.wargaming.expires - Date.now(); - const expiresInDays = expiresIn / 1000 / 60 / 60 / 24; - - if (expiresInDays < 0) { - App.mutate((draft) => { - draft.logins.wargaming = undefined; - }); - } else if (expiresInDays < 7) { - const { wargaming } = App.state.logins; - - if (!wargaming) return; - - fetch( - `https://api.worldoftanks.${idToRegion( - wargaming.id - )}/wot/auth/prolongate/?application_id=${assertSecret( - import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID - )}&access_token=${wargaming.token}` - ) - .then((response) => response.json() as Promise) - .then((json) => { - App.mutate((draft) => { - if (json.status === "error") { - draft.logins.wargaming = undefined; - } else { - draft.logins.wargaming = { - id: wargaming.id, - token: json.data.access_token, - expires: json.data.expires_at * 1000, - }; - } - }); - }); - } - } - }); - - if (!showPoliciesAgreement) return null; - - return ( - - - - - - {policiesAgreementIndex === -1 - ? "BlitzKit's policies" - : "BlitzKit's policies have changed"} - - - - This website utilizes cookies to perform analytics and personalize - your experience. You can learn more through{" "} - - our privacy policy - - . By using BlitzKit, you also agree to our{" "} - - terms of service - - . - - - - - - - - ); -} diff --git a/packages/website/src/components/ClassIcon/components/ClassHeavy.tsx b/packages/website/src/components/ClassIcon/components/ClassHeavy.tsx deleted file mode 100644 index a21b5badd..000000000 --- a/packages/website/src/components/ClassIcon/components/ClassHeavy.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function ClassHeavy(props: ComponentProps<'svg'>) { - return ( - - - - - - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/ClassIcon/components/ClassLight.tsx b/packages/website/src/components/ClassIcon/components/ClassLight.tsx deleted file mode 100644 index 9db2a1e79..000000000 --- a/packages/website/src/components/ClassIcon/components/ClassLight.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function ClassLight(props: ComponentProps<'svg'>) { - return ( - - - - ); -} diff --git a/packages/website/src/components/ClassIcon/components/ClassMedium.tsx b/packages/website/src/components/ClassIcon/components/ClassMedium.tsx deleted file mode 100644 index a43521887..000000000 --- a/packages/website/src/components/ClassIcon/components/ClassMedium.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function ClassMedium(props: ComponentProps<'svg'>) { - return ( - - - - - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/ClassIcon/components/ClassTankDestroyer.tsx b/packages/website/src/components/ClassIcon/components/ClassTankDestroyer.tsx deleted file mode 100644 index 900c15e28..000000000 --- a/packages/website/src/components/ClassIcon/components/ClassTankDestroyer.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function ClassTankDestroyer(props: ComponentProps<'svg'>) { - return ( - - - - ); -} diff --git a/packages/website/src/components/ClassIcon/index.ts b/packages/website/src/components/ClassIcon/index.ts deleted file mode 100644 index d21bd0fd2..000000000 --- a/packages/website/src/components/ClassIcon/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TankClass } from "@blitzkit/core"; -import type { ComponentProps, ReactNode } from "react"; -import { ClassHeavy } from "./components/ClassHeavy"; -import { ClassLight } from "./components/ClassLight"; -import { ClassMedium } from "./components/ClassMedium"; -import { ClassTankDestroyer } from "./components/ClassTankDestroyer"; - -export const classIcons: Record< - TankClass, - (props: ComponentProps<"svg">) => ReactNode -> = { - [TankClass.TANK_CLASS_TANK_DESTROYER]: ClassTankDestroyer, - [TankClass.TANK_CLASS_HEAVY]: ClassHeavy, - [TankClass.TANK_CLASS_LIGHT]: ClassLight, - [TankClass.TANK_CLASS_MEDIUM]: ClassMedium, -}; diff --git a/packages/website/src/components/Compare/Body.tsx b/packages/website/src/components/Compare/Body.tsx deleted file mode 100644 index d6bdc5193..000000000 --- a/packages/website/src/components/Compare/Body.tsx +++ /dev/null @@ -1,319 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { Flex, Heading, IconButton, Table } from "@radix-ui/themes"; -import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics"; -import { useLocale } from "../../hooks/useLocale"; -import { CompareEphemeral } from "../../stores/compareEphemeral"; -import { CompareCellDirection } from "../CompareCell"; -import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell"; -import { CompareRow } from "./CompareRow"; -import { CompareSectionTitle } from "./CompareSectionTitle"; - -interface BodyProps { - stats: TankCharacteristics[]; -} - -export function Body({ stats }: BodyProps) { - const members = CompareEphemeral.use((state) => state.members); - const hasNonRegularGun = members.some( - ({ gun }) => gun.gun_type!.$case !== "regular", - ); - const { unwrap, strings } = useLocale(); - - return ( - <> - - - - - Firepower - - - - {members.map(({ gun, shell, key }, index) => ( - - - - {gun.shells.map((thisShell, shellIndex) => ( - { - CompareEphemeral.mutate((draft) => { - draft.members[index].shell = thisShell; - }); - }} - > - {unwrap(thisShell.name!)} - - ))} - - - - ))} - - - - - - - {hasNonRegularGun && ( - - )} - - - - stats.shellReload ?? - stats.shellReloads!.reduce((a, b) => a + b, 0) / - stats.shellReloads!.length - } - display={(stats) => - stats.shellReload?.toFixed(2) ?? - stats.shellReloads!.map((reload) => reload.toFixed(2)).join(", ") - } - deltaNominalDisplay={(delta) => delta.toFixed(2)} - /> - {hasNonRegularGun && ( - <> - - - - )} - - - - - - - - - - - - - - - - - - Maneuverability - - - - - - - - - - - - - - - - - - - - - Survivability - - - (stats.fireChance * 100).toFixed(0)} - deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)} - /> - - (stats.camouflageStill * 100).toFixed(2)} - deltaNominalDisplay={(delta) => (delta * 100).toFixed(2)} - /> - (stats.camouflageMoving * 100).toFixed(2)} - deltaNominalDisplay={(delta) => (delta * 100).toFixed(2)} - /> - (stats.camouflageShootingStill * 100).toFixed(2)} - deltaNominalDisplay={(delta) => (delta * 100).toFixed(2)} - /> - (stats.camouflageShootingMoving * 100).toFixed(2)} - deltaNominalDisplay={(delta) => (delta * 100).toFixed(2)} - /> - (stats.camouflageCaughtOnFire * 100).toFixed(2)} - deltaNominalDisplay={(delta) => (delta * 100).toFixed(2)} - /> - - - - - - - ); -} diff --git a/packages/website/src/components/Compare/CompareRow.tsx b/packages/website/src/components/Compare/CompareRow.tsx deleted file mode 100644 index fdb317b10..000000000 --- a/packages/website/src/components/Compare/CompareRow.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { - CaretLeftIcon, - CaretRightIcon, - CaretSortIcon, -} from "@radix-ui/react-icons"; -import { Flex, IconButton, Table, Text } from "@radix-ui/themes"; -import { useRef } from "react"; -import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics"; -import { useLocale } from "../../hooks/useLocale"; -import { CompareEphemeral } from "../../stores/compareEphemeral"; -import { ComparePersistent } from "../../stores/comparePersistent"; -import { CompareCell, CompareCellDirection } from "../CompareCell"; -import { StickyRowHeaderCell } from "../StickyRowHeaderCell"; - -type CompareRowProps = { - display?: ( - member: Awaited, - ) => number | string | undefined; - deltaType?: CompareCellDirection; - decimals?: number; - deltaNominalDisplay?: (delta: number) => string | number; - indent?: boolean; - stats: TankCharacteristics[]; -} & ( - | { - name: string; - value: (member: TankCharacteristics) => number | undefined; - } - | { - value: keyof TankCharacteristics; - } -); - -export function CompareRow({ - display, - deltaType = CompareCellDirection.HIGHER_IS_BETTER, - decimals, - deltaNominalDisplay, - indent, - stats, - ...props -}: CompareRowProps) { - const { strings } = useLocale(); - const sorting = CompareEphemeral.use((state) => state.sorting); - const deltaMode = ComparePersistent.use((state) => state.deltaMode); - const values = stats.map((stat) => - typeof props.value === "function" - ? props.value(stat)! - : (stat[props.value] as number), - ); - const id = useRef(Math.random()); - const name = - typeof props.value === "string" - ? strings.website.tools.tankopedia.characteristics.values[props.value] - : props.name; - - return ( - - - - { - CompareEphemeral.mutate((draft) => { - draft.members.sort((memberA, memberB) => { - const indexA = draft.members.indexOf(memberA); - const indexB = draft.members.indexOf(memberB); - const valueA = values[indexA]; - const valueB = values[indexB]; - - return draft.sorting?.direction === "ascending" && - draft.sorting.by === id.current - ? valueA - valueB - : valueB - valueA; - }); - - draft.sorting = { - by: id.current, - direction: - draft.sorting?.direction === "ascending" && - draft.sorting.by === id.current - ? "descending" - : "ascending", - }; - }); - }} - > - {(sorting === undefined || sorting.by !== id.current) && ( - - )} - {sorting?.by === id.current && ( - <> - {sorting.direction === "ascending" && } - {sorting.direction === "descending" && } - - )} - - - {name} - - - - {values.map((value, index) => { - const delta = value - values[0]; - const deltaPercentage = value / values[0] - 1; - const resolvedDisplayValue = display - ? display(stats[index]) - : decimals === undefined - ? value - : value?.toFixed(decimals); - - return ( - - - - {resolvedDisplayValue} - - - {delta !== 0 && - resolvedDisplayValue !== undefined && - values[0] !== undefined && ( - <> - {deltaMode === "absolute" && ( - - ( - {`${delta > 0 ? "+" : ""}${ - deltaNominalDisplay - ? deltaNominalDisplay(delta) - : decimals === undefined - ? delta - : delta.toFixed(decimals) - }`} - ) - - )} - - {deltaMode === "percentage" && ( - - ( - {`${deltaPercentage > 0 ? "+" : ""}${Math.round( - deltaPercentage * 100, - )}%`} - ) - - )} - - )} - - - ); - })} - - ); -} diff --git a/packages/website/src/components/Compare/CompareSectionTitle.tsx b/packages/website/src/components/Compare/CompareSectionTitle.tsx deleted file mode 100644 index 0c0f6cc68..000000000 --- a/packages/website/src/components/Compare/CompareSectionTitle.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Heading, Table } from "@radix-ui/themes"; -import { times } from "lodash-es"; -import { CompareEphemeral } from "../../stores/compareEphemeral"; -import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell"; - -export function CompareSectionTitle({ children }: { children: string }) { - const memberCount = CompareEphemeral.use((state) => state.members.length); - - return ( - - - - {children} - - - {times(memberCount, (index) => ( - - ))} - - - ); -} diff --git a/packages/website/src/components/Compare/CompareTable.tsx b/packages/website/src/components/Compare/CompareTable.tsx deleted file mode 100644 index d3ca23c23..000000000 --- a/packages/website/src/components/Compare/CompareTable.tsx +++ /dev/null @@ -1,647 +0,0 @@ -import { asset, availableProvisions, permanentSkills } from "@blitzkit/core"; -import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; -import { ComponentPlaceholderIcon } from "@radix-ui/react-icons"; -import { - Button, - Flex, - IconButton, - Popover, - Switch, - Table, - Text, -} from "@radix-ui/themes"; -import { times } from "lodash-es"; -import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions"; -import { awaitableEquipmentDefinitions } from "../../core/awaitables/equipmentDefinitions"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; -import { awaitableSkillDefinitions } from "../../core/awaitables/skillDefinitions"; -import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics"; -import { Var } from "../../core/radix/var"; -import { useLocale } from "../../hooks/useLocale"; -import { CompareEphemeral } from "../../stores/compareEphemeral"; -import type { EquipmentMatrix } from "../../stores/duel"; -import { BlitzkitButtonGrayIcon } from "../BlitzkitButtonGrayIcon"; -import { ConsumablesManager } from "../ConsumablesManager"; -import { CrewSkillManager } from "../CrewSkillManager"; -import { EquipmentManager } from "../EquipmentManager"; -import { ModuleManager } from "../ModuleManager"; -import { ProvisionsManager } from "../ProvisionsManager"; -import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell"; -import { StickyTableRoot } from "../StickyTableRoot"; -import { Body } from "./Body"; -import { InsertionMarker } from "./IntersectionMarker"; -import { TankCard } from "./TankCard"; - -interface CompareTableProps { - stats: TankCharacteristics[]; -} - -const [ - skillDefinitions, - equipmentDefinitions, - consumableDefinitions, - provisionDefinitions, -] = await Promise.all([ - awaitableSkillDefinitions, - awaitableEquipmentDefinitions, - awaitableConsumableDefinitions, - awaitableProvisionDefinitions, -]); - -export function CompareTable({ stats }: CompareTableProps) { - const crewSkills = CompareEphemeral.use((state) => state.crewSkills); - const members = CompareEphemeral.use((state) => state.members); - const { unwrap, strings } = useLocale(); - const equalize = CompareEphemeral.use((state) => state.equalize); - - return ( - - - - - - - - - - - {members.map(({ tank, key }, index) => { - return ; - })} - - - - - - - - - - - - - - { - CompareEphemeral.mutate((draft) => { - draft.crewSkills = skills; - }); - }} - /> - - - - - - - - - - - {strings.website.tools.compare.table.skills.equalize} - - { - CompareEphemeral.mutate((draft) => { - draft.equalize = checked; - }); - }} - /> - - - - - {members.map( - ( - { - equipmentMatrix, - tank, - key, - provisions, - gun, - shell, - turret, - engine, - track, - consumables, - camouflage, - }, - index, - ) => { - const equipmentPreset = - equipmentDefinitions.presets[tank.equipment_preset]; - const provisionsList = availableProvisions( - tank, - gun, - provisionDefinitions, - ); - const consumablesList = Object.values( - consumableDefinitions.consumables, - ).filter((consumable) => - checkConsumableProvisionInclusivity(consumable, tank, gun), - ); - - return ( - - - - - - - - - { - CompareEphemeral.mutate((draft) => { - draft.members[index] = { - ...draft.members[index], - ...modules, - }; - }); - }} - /> - - - - - - - - - - - - - - - - provision.id, - )} - selected={provisions} - disabled={tank.max_provisions === provisions.length} - onChange={(provisions) => { - CompareEphemeral.mutate((draft) => { - draft.members[index].provisions = provisions; - }); - }} - /> - - - - - - - - - - - - - - - - { - CompareEphemeral.mutate((draft) => { - draft.members[index].equipmentMatrix = matrix; - draft.sorting = undefined; - }); - }} - /> - - - - - - - - - - - - - - - { - CompareEphemeral.mutate((draft) => { - draft.members[index].consumables = consumables; - }); - }} - disabled={ - tank.max_consumables === consumables.length - } - /> - - - - - - - - - - - {!tank.fixed_camouflage && ( - { - CompareEphemeral.mutate((draft) => { - draft.members[index].camouflage = !camouflage; - }); - }} - variant={camouflage ? "solid" : "outline"} - > - - - )} - - - ); - }, - )} - - - - - - ); -} diff --git a/packages/website/src/components/Compare/Controls.tsx b/packages/website/src/components/Compare/Controls.tsx deleted file mode 100644 index ee86c027f..000000000 --- a/packages/website/src/components/Compare/Controls.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { PlusIcon, TrashIcon } from "@radix-ui/react-icons"; -import { Button, Dialog, Flex, SegmentedControl } from "@radix-ui/themes"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; -import { tankToCompareMember } from "../../core/blitzkit/tankToCompareMember"; -import { useLocale } from "../../hooks/useLocale"; -import { CompareEphemeral } from "../../stores/compareEphemeral"; -import { - ComparePersistent, - type DeltaMode, -} from "../../stores/comparePersistent"; -import { TankSearch } from "../TankSearch"; - -interface ControlsProps { - addTankDialogOpen: boolean; - onAddTankDialogOpenChange: (open: boolean) => void; -} - -const provisionDefinitions = await awaitableProvisionDefinitions; - -export function Controls({ - addTankDialogOpen, - onAddTankDialogOpenChange, -}: ControlsProps) { - const deltaMode = ComparePersistent.use((state) => state.deltaMode); - const { strings } = useLocale(); - - return ( - - - - - - - - - {strings.website.tools.compare.actions.add.title} - - - {strings.website.tools.compare.actions.add.description} - - - - - { - CompareEphemeral.mutate((draft) => { - draft.members.push( - tankToCompareMember(tank, provisionDefinitions) - ); - draft.sorting = undefined; - }); - onAddTankDialogOpenChange(false); - }} - onSelectAll={(tanks) => { - CompareEphemeral.mutate((draft) => { - draft.members.push( - ...tanks.map((tank) => { - return tankToCompareMember(tank, provisionDefinitions); - }) - ); - draft.sorting = undefined; - }); - onAddTankDialogOpenChange(false); - }} - /> - - - - - - - - { - ComparePersistent.mutate((draft) => { - draft.deltaMode = value as DeltaMode; - }); - }} - > - - {strings.website.tools.compare.actions.deltas.none} - - - {strings.website.tools.compare.actions.deltas.percentage} - - - {strings.website.tools.compare.actions.deltas.absolute} - - - - ); -} diff --git a/packages/website/src/components/Compare/IntersectionMarker/constants.ts b/packages/website/src/components/Compare/IntersectionMarker/constants.ts deleted file mode 100644 index 12b5899fe..000000000 --- a/packages/website/src/components/Compare/IntersectionMarker/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const insertionMarkers: { element: HTMLDivElement; index: number }[] = - []; diff --git a/packages/website/src/components/Compare/IntersectionMarker/index.tsx b/packages/website/src/components/Compare/IntersectionMarker/index.tsx deleted file mode 100644 index 03249f681..000000000 --- a/packages/website/src/components/Compare/IntersectionMarker/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useEffect, useRef } from 'react'; -import { Var } from '../../../core/radix/var'; -import { insertionMarkers } from './constants'; - -export function InsertionMarker({ index }: { index: number }) { - const marker = useRef(null); - - useEffect(() => { - insertionMarkers.push({ - element: marker.current!, - index, - }); - - return () => { - insertionMarkers.splice( - insertionMarkers.findIndex( - (insertionMarker) => insertionMarker.element === marker.current!, - ), - 1, - ); - }; - }); - - return ( -
- ); -} diff --git a/packages/website/src/components/Compare/TankCard.tsx b/packages/website/src/components/Compare/TankCard.tsx deleted file mode 100644 index bb6221719..000000000 --- a/packages/website/src/components/Compare/TankCard.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { type TankDefinition, tankIcon } from "@blitzkit/core"; -import { Flex, Text } from "@radix-ui/themes"; -import { useEffect, useRef } from "react"; -import { Vector2 } from "three"; -import { useLocale } from "../../hooks/useLocale"; -import { CompareEphemeral } from "../../stores/compareEphemeral"; -import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell"; -import { InsertionMarker } from "./IntersectionMarker"; -import { insertionMarkers } from "./IntersectionMarker/constants"; -import { TankControl } from "./TankControl"; - -interface TankCardProps { - index: number; - tank: TankDefinition; -} - -export function TankCard({ index, tank }: TankCardProps) { - const draggable = useRef(null); - const { unwrap } = useLocale(); - - useEffect(() => { - const initial = new Vector2(); - const current = new Vector2(); - const delta = new Vector2(); - const marker = new Vector2(); - let initialRect = draggable.current!.getBoundingClientRect(); - let dropIndex = -1; - - function handlePointerDown(event: PointerEvent) { - event.preventDefault(); - - initialRect = draggable.current!.getBoundingClientRect(); - - delta.set(0, 0); - current.copy(initial.set(event.clientX, event.clientY)); - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } - function handlePointerMove(event: PointerEvent) { - event.preventDefault(); - - current.set(event.clientX, event.clientY); - delta.copy(current).sub(initial); - insertionMarkers.forEach(({ element }) => { - element.style.opacity = "0"; - }); - - const distances = insertionMarkers.map((insertionMarker) => { - const markerBounds = insertionMarker.element.getBoundingClientRect(); - marker.set(markerBounds.left, markerBounds.top); - - return { - ...insertionMarker, - distance: marker.distanceTo(current), - }; - }); - const closest = distances.reduce((a, b) => - a.distance < b.distance ? a : b, - ); - - dropIndex = closest.index; - closest.element.style.opacity = "1"; - draggable.current!.style.position = "fixed"; - draggable.current!.style.cursor = "grabbing"; - draggable.current!.style.zIndex = "1"; - draggable.current!.style.left = `${initialRect.left + delta.x}px`; - draggable.current!.style.top = `${initialRect.top + delta.y}px`; - } - function handlePointerUp(event: PointerEvent) { - event.preventDefault(); - - draggable.current!.style.position = "static"; - draggable.current!.style.zIndex = "unset"; - draggable.current!.style.cursor = "grab"; - - insertionMarkers.forEach(({ element }) => { - element.style.opacity = "0"; - }); - - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - - if (dropIndex === -1) return; - - CompareEphemeral.mutate((draft) => { - const fromIndex = index; - const toIndex = dropIndex > index ? dropIndex - 1 : dropIndex; - const member = draft.members[fromIndex]; - - draft.members.splice(fromIndex, 1); - draft.members.splice(toIndex, 0, member); - }); - - dropIndex = -1; - } - - draggable.current?.addEventListener("pointerdown", handlePointerDown); - - return () => { - draggable.current?.removeEventListener("pointerdown", handlePointerDown); - }; - }, [index]); - - return ( - - - - - - {unwrap(tank.name!)} - - - {unwrap(tank.name!)} - - - - - - ); -} diff --git a/packages/website/src/components/Compare/TankControl.tsx b/packages/website/src/components/Compare/TankControl.tsx deleted file mode 100644 index a9ebe9a4a..000000000 --- a/packages/website/src/components/Compare/TankControl.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { ExternalLinkIcon, LoopIcon, TrashIcon } from "@radix-ui/react-icons"; -import { Dialog, Flex, IconButton } from "@radix-ui/themes"; -import { useState } from "react"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; -import { tankToCompareMember } from "../../core/blitzkit/tankToCompareMember"; -import { useLocale } from "../../hooks/useLocale"; -import { CompareEphemeral } from "../../stores/compareEphemeral"; -import { LinkI18n } from "../LinkI18n"; -import { TankSearch } from "../TankSearch"; - -interface TankControlProps { - index: number; - slug: string; -} - -const provisionDefinitions = await awaitableProvisionDefinitions; - -export function TankControl({ index, slug }: TankControlProps) { - const { locale, strings } = useLocale(); - const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false); - - return ( - - { - CompareEphemeral.mutate((draft) => { - draft.members.splice(index, 1); - draft.sorting = undefined; - }); - }} - > - - - - - - - - - - - - - {strings.website.common.tank_search.swap_dialog_title} - - - - - { - CompareEphemeral.mutate((draft) => { - draft.members[index] = tankToCompareMember( - tank, - provisionDefinitions - ); - draft.sorting = undefined; - }); - setSwitchTankDialogOpen(false); - }} - /> - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/CompareCell.tsx b/packages/website/src/components/CompareCell.tsx deleted file mode 100644 index 28f768a57..000000000 --- a/packages/website/src/components/CompareCell.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Table } from "@radix-ui/themes"; -import type { CellProps } from "@radix-ui/themes/components/table"; - -export enum CompareCellDirection { - HIGHER_IS_BETTER, - LOWER_IS_BETTER, -} - -interface CompareCellProps extends CellProps { - value: number; - truth: number; - direction?: CompareCellDirection; -} - -export function CompareCell({ - value, - truth, - style, - direction = CompareCellDirection.HIGHER_IS_BETTER, - ...props -}: CompareCellProps) { - const percentage = value / truth - 1; - const shiftedPercentage = Math.round( - Math.min(100, Math.abs(percentage) * 2 * 100 + 25) - ); - - return ( - truth - ? `color-mix(in srgb, var(--green-7) ${shiftedPercentage}%, var(--green-3))` - : `color-mix(in srgb, var(--red-7) ${shiftedPercentage}%, var(--red-3))`, - }} - {...props} - /> - ); -} diff --git a/packages/website/src/components/ConsumablesManager.tsx b/packages/website/src/components/ConsumablesManager.tsx deleted file mode 100644 index b71a44ecd..000000000 --- a/packages/website/src/components/ConsumablesManager.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import type { Consumable } from "@blitzkit/core"; -import { Flex, type FlexProps } from "@radix-ui/themes"; -import { ConsumableButton } from "./ModuleButtons/ConsumableButton"; - -type ConsumablesManagerProps = FlexProps & { - consumables: Consumable[]; - selected: number[]; - onConsumablesChange?: (consumables: number[]) => void; - disabled?: boolean; - timers?: boolean; - hasHighEndConsumables?: boolean; - hasConsumableDeliverySystem?: boolean; - cooldownBooster?: number; -}; - -export function ConsumablesManager({ - consumables, - selected, - onConsumablesChange, - disabled, - timers, - hasConsumableDeliverySystem, - cooldownBooster, - hasHighEndConsumables, - ...props -}: ConsumablesManagerProps) { - return ( - - {consumables.map((consumable) => { - if (consumable.game_mode_exclusive) return null; - - const isSelected = selected.some((id) => id === consumable.id); - - return ( - { - if (!onConsumablesChange) return; - - const draft = [...selected]; - - if (draft.some((id) => id === consumable.id)) { - draft.splice(draft.indexOf(consumable.id), 1); - } else { - draft.push(consumable.id); - } - - onConsumablesChange(draft); - }} - /> - ); - })} - - ); -} diff --git a/packages/website/src/components/CopyButton.tsx b/packages/website/src/components/CopyButton.tsx deleted file mode 100644 index 8fa174242..000000000 --- a/packages/website/src/components/CopyButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Button, type ButtonProps, Tooltip } from '@radix-ui/themes'; -import { useState } from 'react'; -import { useLocale } from '../hooks/useLocale'; - -interface CopyButtonProps extends ButtonProps { - copy: () => string | undefined; -} - -export function CopyButton({ copy, ...props }: CopyButtonProps) { - const [tooltipOpen, setTooltipOpen] = useState(false); - const { strings } = useLocale(); - - return ( - - - - ); - })} - - setYearScroll((state) => state - 1)} - mt="2" - > - - - - - - - - - { - if (monthOffset === 11) { - setYearOffset((state) => state + 1); - setMonthOffset(0); - } else { - setMonthOffset((state) => state + 1); - } - }} - mr="2" - > - - - - - - {times(7, (index) => ( - - {DAY_TITLES[index]} - - ))} - - - - {times(firstDayOfMonth, (index) => ( - - {daysInLastMonth - firstDayOfMonth + index + 1} - - ))} - - {times(daysInMonth, (index) => { - const selected = - index === day - 1 && monthOffset === 0 && yearOffset === 0; - - return ( - { - const newYear = - year + yearOffset + Math.floor((month + monthOffset) / 12); - const newMonth = (month + monthOffset) % 12; - const newDay = index + 1; - - setYear(newYear); - setMonth(newMonth); - setDay(newDay); - setYearOffset(0); - setMonthOffset(0); - - onDateChange?.(new Date(newYear, newMonth, newDay)); - }} - > - {index + 1} - - ); - })} - - {times(6 - lastDayOfMonth, (index) => ( - - {index + 1} - - ))} - - - ); -} diff --git a/packages/website/src/components/Discord/index.css b/packages/website/src/components/Discord/index.css deleted file mode 100644 index 59adf53a3..000000000 --- a/packages/website/src/components/Discord/index.css +++ /dev/null @@ -1,21 +0,0 @@ -:root { - --scroll-time: 80s; -} - -.scroller-0 { - animation: move-1 var(--scroll-time) linear infinite; -} - -.scroller-1 { - animation-delay: var(--scroll-time); - animation: move-1 var(--scroll-time) linear infinite; -} - -@keyframes move-1 { - from { - transform: translateY(0); - } - to { - transform: translateY(-100%); - } -} diff --git a/packages/website/src/components/Discord/index.tsx b/packages/website/src/components/Discord/index.tsx deleted file mode 100644 index 0b41b4732..000000000 --- a/packages/website/src/components/Discord/index.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import { assertSecret } from '@blitzkit/core'; -import { CaretRightIcon } from '@radix-ui/react-icons'; -import { Box, Button, Flex, Link, Text } from '@radix-ui/themes'; -import { times } from 'lodash-es'; -import { getStrings } from '../../core/i18n/getStrings'; -import { Var } from '../../core/radix/var'; -import type { LocaleAcceptorProps } from '../../hooks/useLocale'; -import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps'; -import { InlineSkeleton } from '../InlineSkeleton'; -import './index.css'; - -export interface DiscordWidget { - id: string; - name: string; - instant_invite: string; - channels: any[]; - members: Member[]; - presence_count: number; -} - -export interface Member { - id: string; - username: string; - discriminator: string; - avatar: null; - status: Status; - avatar_url: string; - game?: Game; -} - -export interface Game { - name: string; -} - -export enum Status { - DND = 'dnd', - Idle = 'idle', - Online = 'online', -} - -const COLUMN_COUNT = 3; - -const discordServerId = assertSecret(import.meta.env.PUBLIC_DISCORD_SERVER_ID); -const widget = await fetch( - `https://discord.com/api/guilds/${discordServerId}/widget.json`, -).then((response) => response.json() as Promise); -const filteredMembers = widget.members - .filter((member) => { - const isPrivateUsername = - member.username.length === 4 && member.username.endsWith('...'); - return !isPrivateUsername; - }) - .sort(() => Math.random() - 0.5); - -const columnSize = Math.floor(filteredMembers.length / COLUMN_COUNT); -const columns = times(COLUMN_COUNT, (index) => { - const start = index * columnSize; - const end = start + columnSize; - return filteredMembers.slice(start, end); -}); - -function Member({ - id, - avatar_url, - username, - status, - skeleton, -}: Member & MaybeSkeletonComponentProps) { - let statusColor: string; - - if (skeleton) { - statusColor = Var('gray-9'); - } else { - switch (status) { - case Status.DND: - statusColor = Var('red-9'); - break; - - case Status.Idle: - statusColor = Var('amber-9'); - break; - - case Status.Online: - statusColor = Var('jade-9'); - break; - } - } - - return ( - - - - {!skeleton && ( - {`${username} - )} - {skeleton && ( - - )} - - - - - {skeleton && } - {!skeleton && username} - - - ); -} - -function Scroller({ - index, - skeleton, -}: { index: number } & MaybeSkeletonComponentProps) { - return ( - - {columns.map((column, index) => ( - - {column.map((member) => ( - - ))} - - ))} - - ); -} - -export function DiscordPlug({ - locale, - skeleton, -}: LocaleAcceptorProps & MaybeSkeletonComponentProps) { - const strings = getStrings(locale); - - return ( - - - - - - - - - - - - - - {strings.website.home.discord.join_short} - - - - - {strings.website.home.discord.join_long} - - - - - - - - - ); -} diff --git a/packages/website/src/components/EasterEggs.astro b/packages/website/src/components/EasterEggs.astro deleted file mode 100644 index c4721a191..000000000 --- a/packages/website/src/components/EasterEggs.astro +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/packages/website/src/components/Embeds/Boolean.tsx b/packages/website/src/components/Embeds/Boolean.tsx deleted file mode 100644 index c098c9db8..000000000 --- a/packages/website/src/components/Embeds/Boolean.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Switch } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; -import { EmbedState } from "../../stores/embedState"; - -export function Boolean({ configKey }: EmbedPreviewControllerProps) { - return ( - state[configKey] as boolean)} - onCheckedChange={() => { - EmbedState.mutate((draft) => { - draft[configKey] = !draft[configKey]; - }); - }} - /> - ); -} diff --git a/packages/website/src/components/Embeds/Color.tsx b/packages/website/src/components/Embeds/Color.tsx deleted file mode 100644 index 16ebe28e6..000000000 --- a/packages/website/src/components/Embeds/Color.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; -import { EmbedState } from "../../stores/embedState"; -import { ColorControllerRaw } from "./ColorControllerRaw"; - -export function Color({ configKey }: EmbedPreviewControllerProps) { - const color = EmbedState.use((state) => state[configKey] as string); - - return ( - { - EmbedState.mutate((draft) => { - draft[configKey] = value; - }); - }} - /> - ); -} diff --git a/packages/website/src/components/Embeds/ColorControllerRaw.tsx b/packages/website/src/components/Embeds/ColorControllerRaw.tsx deleted file mode 100644 index b5c0fa8a5..000000000 --- a/packages/website/src/components/Embeds/ColorControllerRaw.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Slider } from '@radix-ui/themes'; - -interface ColorControllerRawProps { - value: string; - onValueChange: (value: string) => void; -} - -export function ColorControllerRaw({ - value, - onValueChange, -}: ColorControllerRawProps) { - const tintString = value.slice(0, 7); - const alphaString = value.slice(7, 9); - const alpha = alphaString.length === 0 ? 255 : parseInt(alphaString, 16); - - return ( - <> - { - let alpha = value.slice(7, 9); - if (alpha.length === 0) alpha = 'ff'; - onValueChange(`${event.target.value}${alpha}`); - }} - /> - { - onValueChange(`${tintString}${value.toString(16)}`); - }} - /> - - ); -} diff --git a/packages/website/src/components/Embeds/Enum.tsx b/packages/website/src/components/Embeds/Enum.tsx deleted file mode 100644 index 191e2ce33..000000000 --- a/packages/website/src/components/Embeds/Enum.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Select } from "@radix-ui/themes"; -import { useLocale } from "../../hooks/useLocale"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; -import { EmbedState } from "../../stores/embedState"; -import type { - EmbedConfigItemType, - EmbedItemType, -} from "../../stores/embedState/constants"; - -export function Enum({ - configKey, - config, -}: EmbedPreviewControllerProps & { - config: EmbedConfigItemType; -}) { - const { locale } = useLocale(); - - return ( - state[configKey] as string)} - onValueChange={(value) => { - EmbedState.mutate((draft) => { - draft[configKey] = value; - }); - }} - > - - - - {config.options.map((option) => ( - - {option.locales[locale]} - - ))} - - - ); -} diff --git a/packages/website/src/components/Embeds/GenerateURL.tsx b/packages/website/src/components/Embeds/GenerateURL.tsx deleted file mode 100644 index a02b010ee..000000000 --- a/packages/website/src/components/Embeds/GenerateURL.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import { - searchPlayersAcrossRegions, - type AccountListItem, - type AccountListWithServer, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { CaretLeftIcon, Link2Icon, PersonIcon } from "@radix-ui/react-icons"; -import { - Button, - Dialog, - Flex, - Heading, - Spinner, - Text, - TextField, -} from "@radix-ui/themes"; -import { debounce } from "lodash-es"; -import { useCallback, useRef, useState } from "react"; -import { stringify } from "urlon"; -import type { embedConfigurations } from "../../constants/embeds"; -import { useLocale } from "../../hooks/useLocale"; -import { EmbedState, type EmbedStateStore } from "../../stores/embedState"; -import { CopyButton } from "../CopyButton"; -import { SearchResults } from "../SearchResults"; - -interface GenerateURLProps { - embed: keyof typeof embedConfigurations; -} - -export function GenerateURL({ embed }: GenerateURLProps) { - const [searchDialogOpen, setSearchDialogOpen] = useState(false); - const [copyDialogOpen, setCopyDialogOpen] = useState(false); - const [copyUser, setCopyUser] = useState(null); - const input = useRef(null); - const [searching, setSearching] = useState(false); - const [results, setResults] = useState([]); - const { strings } = useLocale(); - const performSearch = useCallback( - debounce(async () => { - if (!input.current) return; - - const sanitized = input.current.value.trim(); - - if (sanitized.length === 0) { - setSearching(false); - return; - } - - const results = await searchPlayersAcrossRegions(sanitized, 12); - - setSearching(false); - setResults(results); - }, 500), - [] - ); - const handleChange = useCallback(() => { - if (!input.current) return; - - const sanitized = input.current.value.trim(); - - if (sanitized.length === 0) { - setSearching(false); - setResults([]); - } else { - setSearching(true); - performSearch(); - } - }, []); - - return ( - <> - - - - - - - - - {strings.website.tools.embed.configuration.export.generate.title} - - - { - strings.website.tools.embed.configuration.export.generate - .subtitle - } - - - - - - - - - - - {searching && ( - - - {strings.website.common.player_search.searching} - - - )} - {!searching && results.length === 0 && ( - - {strings.website.common.player_search.no_results} - - )} - - {!searching && results.length > 0 && ( - - {results.map((result) => ( - { - setCopyUser(result); - setCopyDialogOpen(true); - setSearchDialogOpen(false); - }} - /> - ))} - - )} - - - - - - - - - - - - - { - strings.website.tools.embed.configuration.export.generate - .success.title - } - - - {literals( - strings.website.tools.embed.configuration.export.generate - .success.info, - { name: copyUser?.nickname } - )} - - - - - - { - if (!copyUser) return; - - const shallowState: EmbedStateStore = {}; - - Object.entries(EmbedState.state).forEach(([key, value]) => { - if (value !== EmbedState.initial[key]) { - shallowState[key] = value; - } - }); - - const searchParams = new URLSearchParams({ - id: `${copyUser.account_id}`, - state: stringify(shallowState), - }); - - setSearchDialogOpen(false); - setCopyDialogOpen(false); - - return `${location.origin}/embed/${embed}/host?${searchParams.toString()}`; - }} - > - {" "} - { - strings.website.tools.embed.configuration.export.generate - .success.copy - } - - - - - - ); -} diff --git a/packages/website/src/components/Embeds/Import.tsx b/packages/website/src/components/Embeds/Import.tsx deleted file mode 100644 index 237128993..000000000 --- a/packages/website/src/components/Embeds/Import.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { - AlertDialog, - Button, - Flex, - Heading, - Text, - TextField, - Tooltip, -} from "@radix-ui/themes"; -import { useRef, useState } from "react"; -import { parse } from "urlon"; -import { useLocale } from "../../hooks/useLocale"; -import { EmbedState } from "../../stores/embedState"; - -export function Import() { - const importInput = useRef(null); - const [showInvalidURLPrompt, setShowInvalidURLPrompt] = useState(false); - const [showConfirmation, setConfirmation] = useState(false); - const { strings } = useLocale(); - - return ( - <> - - {strings.website.tools.embed.configuration.import.title} - - - {strings.website.tools.embed.configuration.import.description} - - - - - - {strings.website.tools.embed.configuration.import.idiot_title} - - - {strings.website.tools.embed.configuration.import.idiot_description} - - - - - - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/Embeds/PreviewWrapper.tsx b/packages/website/src/components/Embeds/PreviewWrapper.tsx deleted file mode 100644 index 1c0f02772..000000000 --- a/packages/website/src/components/Embeds/PreviewWrapper.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Box } from '@radix-ui/themes'; -import { useEffect, useRef } from 'react'; -import { embedPreviews } from '../../constants/embeds'; -import { useEmbedStateCurry } from '../../stores/embedState/utilities'; - -interface PreviewWrapperProps { - name: keyof typeof embedPreviews; - naked?: boolean; -} - -export function PreviewWrapper({ name, naked }: PreviewWrapperProps) { - const { useEmbedState } = useEmbedStateCurry(); - const width = useEmbedState('width'); - const height = useEmbedState('height'); - const Preview = embedPreviews[name]; - const wrapper = useRef(null); - - useEffect(() => { - if (!wrapper.current) return; - - wrapper.current.classList.remove('preview-container-animated'); - void wrapper.current.offsetWidth; - wrapper.current.classList.add('preview-container-animated'); - }, [width, height]); - - if (naked) { - return ( - - - - ); - } - - return ( - - - - ); -} diff --git a/packages/website/src/components/Embeds/Radius.tsx b/packages/website/src/components/Embeds/Radius.tsx deleted file mode 100644 index f7c76775c..000000000 --- a/packages/website/src/components/Embeds/Radius.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Slider } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; -import { EmbedState, type RadixRadius } from "../../stores/embedState"; - -export function Radius({ configKey }: EmbedPreviewControllerProps) { - return ( - { - const radius = state[configKey] as RadixRadius; - return radius === "full" ? 5 : parseInt(radius); - }), - ]} - onValueChange={([value]) => { - EmbedState.mutate((draft) => { - draft[configKey] = value === 5 ? "full" : `${value}`; - }); - }} - /> - ); -} diff --git a/packages/website/src/components/Embeds/RichText.tsx b/packages/website/src/components/Embeds/RichText.tsx deleted file mode 100644 index 469be74e6..000000000 --- a/packages/website/src/components/Embeds/RichText.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { FontBoldIcon, FontSizeIcon } from "@radix-ui/react-icons"; -import { Flex, Slider, Text } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; -import { EmbedState, type RadixSizeWithout0 } from "../../stores/embedState"; -import { - radixTextWeights, - type EmbedConfigItemType, - type EmbedItemType, -} from "../../stores/embedState/constants"; -import { ColorControllerRaw } from "./ColorControllerRaw"; - -export function RichText({ configKey }: EmbedPreviewControllerProps) { - const state = EmbedState.use( - (state) => - state[configKey] as EmbedConfigItemType["default"] - ); - - return ( - - - { - EmbedState.mutate((draft) => { - ( - draft[ - configKey - ] as EmbedConfigItemType["default"] - ).color = value; - }); - }} - /> - - - - - - - { - EmbedState.mutate((draft) => { - ( - draft[ - configKey - ] as EmbedConfigItemType["default"] - ).size = `${value}` as RadixSizeWithout0; - }); - }} - /> - - - - - - - { - EmbedState.mutate((draft) => { - ( - draft[ - configKey - ] as EmbedConfigItemType["default"] - ).weight = radixTextWeights[value]; - }); - }} - /> - - - ); -} diff --git a/packages/website/src/components/Embeds/Size.tsx b/packages/website/src/components/Embeds/Size.tsx deleted file mode 100644 index 8a28ecdf7..000000000 --- a/packages/website/src/components/Embeds/Size.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Slider } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; -import { EmbedState, type RadixSize } from "../../stores/embedState"; - -export function Size({ configKey }: EmbedPreviewControllerProps) { - return ( - parseInt(state[configKey] as RadixSize)), - ]} - onValueChange={([value]) => { - EmbedState.mutate((draft) => { - draft[configKey] = `${value}`; - }); - }} - /> - ); -} diff --git a/packages/website/src/components/Embeds/SizeWithout0.tsx b/packages/website/src/components/Embeds/SizeWithout0.tsx deleted file mode 100644 index b2ae6356e..000000000 --- a/packages/website/src/components/Embeds/SizeWithout0.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Slider } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; -import { EmbedState, type RadixSizeWithout0 } from "../../stores/embedState"; - -export function SizeWithout0({ configKey }: EmbedPreviewControllerProps) { - return ( - - parseInt(state[configKey] as RadixSizeWithout0) - ), - ]} - onValueChange={([value]) => { - EmbedState.mutate((draft) => { - draft[configKey] = `${value}`; - }); - }} - /> - ); -} diff --git a/packages/website/src/components/Embeds/Slider.tsx b/packages/website/src/components/Embeds/Slider.tsx deleted file mode 100644 index 0302fe82e..000000000 --- a/packages/website/src/components/Embeds/Slider.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Slider as RadixSlider } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; -import { EmbedState, type RadixSize } from "../../stores/embedState"; -import type { - EmbedConfigItemType, - EmbedItemType, -} from "../../stores/embedState/constants"; - -export function Slider({ - configKey, - config, -}: EmbedPreviewControllerProps & { - config: EmbedConfigItemType; -}) { - return ( - parseInt(state[configKey] as RadixSize)), - ]} - onValueChange={([value]) => { - EmbedState.mutate((draft) => { - draft[configKey] = value; - }); - }} - /> - ); -} diff --git a/packages/website/src/components/Embeds/TextController.tsx b/packages/website/src/components/Embeds/TextController.tsx deleted file mode 100644 index b7f6d4bc8..000000000 --- a/packages/website/src/components/Embeds/TextController.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { TextField } from "@radix-ui/themes"; -import type { EmbedPreviewControllerProps } from "../../pages/[...locale]/embed/[embed]/_index"; -import { EmbedState } from "../../stores/embedState"; - -export function TextController({ configKey }: EmbedPreviewControllerProps) { - return ( - state[configKey] as string)} - onChange={(event) => { - EmbedState.mutate((draft) => { - draft[configKey] = event.target.value; - }); - }} - /> - ); -} diff --git a/packages/website/src/components/Embeds/breakdown.tsx b/packages/website/src/components/Embeds/breakdown.tsx deleted file mode 100644 index 662762432..000000000 --- a/packages/website/src/components/Embeds/breakdown.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { - calculateCompositeStats, - compositeStats, - compositeStatsKeys, - deltaTankStats, - getTankStats, - idToRegion, - sumCompositeStats, - type IndividualTankStats, -} from "@blitzkit/core"; -import { STRINGS } from "@blitzkit/i18n"; -import { ReloadIcon } from "@radix-ui/react-icons"; -import { ContextMenu } from "@radix-ui/themes"; -import { useEffect, useMemo, useState } from "react"; -import { breakdownConfig } from "../../constants/embeds"; -import { awaitableAverageDefinitions } from "../../core/awaitables/averageDefinitions"; -import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; -import { EmbedBreakdown } from "../../stores/embedBreakdown"; -import { useEmbedStateCurry } from "../../stores/embedState/utilities"; -import { BreakdownEmbedCard, BreakdownEmbedWrapper } from "../TanksEmbed"; - -export const compositeStatsKeysOptions = compositeStatsKeys.map((value) => ({ - value, - locales: Object.entries(STRINGS).reduce((accumulator, [locale, strings]) => { - return { - ...accumulator, - [locale]: strings.common.composite_stats[value], - }; - }, {}), -})); - -const [tankDefinitions, averageDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableAverageDefinitions, -]); - -export const fakeCompositeStats = compositeStats( - { - battle_life_time: 5 * 50 * 60, - battles: 50, - capture_points: 5, - damage_dealt: 3201 * 50, - damage_received: 1093 * 50, - dropped_capture_points: 40, - frags: 99, - frags8p: 0, - hits: 50 * 13, - losses: 20, - max_frags: 7, - max_xp: 1203, - shots: 50 * 14, - spotted: 50 * 1.5, - survived_battles: 32, - win_and_survived: 28, - wins: 30, - xp: 1230 * 50, - }, - averageDefinitions.averages[7297].mu!, -); - -export function BreakdownPreview() { - const tanks = Object.values(tankDefinitions.tanks); - const { useEmbedState } = useEmbedStateCurry(); - - return ( - - {useEmbedState("showTotal") && ( - - )} - - {tanks.slice(0, useEmbedState("listMaxTanks")).map((tank) => ( - - ))} - - ); -} - -export function BreakdownRenderer() { - const { useEmbedState } = useEmbedStateCurry(); - const params = new URLSearchParams(window.location.search); - const id = Number(params.get("id")); - const tankStatsA = EmbedBreakdown.use((state) => state[id] ?? []); - const [tankStatsB, setTankStatsB] = useState([]); - const diff = useMemo( - () => - deltaTankStats(tankStatsA, tankStatsB).sort( - (a, b) => b.last_battle_time - a.last_battle_time, - ), - [tankStatsB], - ); - const compositeStats = diff - .map((diff) => { - const average = averageDefinitions.averages[diff.tank_id]; - - if (average === undefined) return null; - - const compositeStats = calculateCompositeStats( - { battle_life_time: diff.battle_life_time, ...diff.all }, - average.mu!, - ); - - return { id: diff.tank_id, compositeStats }; - }) - .filter((stats) => stats !== null); - const sum = sumCompositeStats( - compositeStats.map(({ compositeStats }) => compositeStats), - ); - - useEffect(() => { - const interval = setInterval(async () => { - const newB = await getTankStats(idToRegion(id), id); - - if (newB === null) return; - - setTankStatsB(newB); - }, 1000); - - return () => clearInterval(interval); - }, []); - - return ( - - - - {useEmbedState("showTotal") && ( - - )} - - {compositeStats - .slice(0, useEmbedState("listMaxTanks")) - .map(({ id, compositeStats }) => { - const tank = tankDefinitions.tanks[id]; - - return ( - - ); - })} - - - - - { - const newA = await getTankStats(idToRegion(id), id); - - if (newA === null) return; - - EmbedBreakdown.mutate((draft) => { - draft[id] = newA; - }); - }} - > - Reset - - - - ); -} diff --git a/packages/website/src/components/EqualIcon.tsx b/packages/website/src/components/EqualIcon.tsx deleted file mode 100644 index 316ec5542..000000000 --- a/packages/website/src/components/EqualIcon.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { ComponentProps } from "react"; - -export function EqualIcon(props: ComponentProps<"svg">) { - return ( - - - - ); -} diff --git a/packages/website/src/components/EquipmentManager/index.css b/packages/website/src/components/EquipmentManager/index.css deleted file mode 100644 index 64a8901f4..000000000 --- a/packages/website/src/components/EquipmentManager/index.css +++ /dev/null @@ -1,9 +0,0 @@ -.equipment-manager .left { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.equipment-manager .right { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} diff --git a/packages/website/src/components/EquipmentManager/index.tsx b/packages/website/src/components/EquipmentManager/index.tsx deleted file mode 100644 index 657572e1c..000000000 --- a/packages/website/src/components/EquipmentManager/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import type { EquipmentPreset } from '@blitzkit/core'; -import { Flex } from '@radix-ui/themes'; -import { chunk, cloneDeep } from 'lodash-es'; -import type { EquipmentMatrix } from '../../stores/duel'; -import { EquipmentButton } from '../ModuleButtons/EquipmentButton'; -import './index.css'; - -interface EquipmentManagerProps { - preset: EquipmentPreset; - matrix: EquipmentMatrix; - onChange: (matrix: EquipmentMatrix) => void; -} - -export function EquipmentManager({ - preset, - matrix, - onChange, -}: EquipmentManagerProps) { - return ( - - {chunk(preset.slots, 3).map((equipmentRow, rowIndex) => ( - - {equipmentRow.map((equipment, columnIndex) => ( - - { - const draft = cloneDeep(matrix); - - if (draft[rowIndex][columnIndex] === -1) { - draft[rowIndex][columnIndex] = 0; - } else { - draft[rowIndex][columnIndex] = -1; - } - - onChange(draft); - }} - /> - { - const draft = cloneDeep(matrix); - - if (draft[rowIndex][columnIndex] === 1) { - draft[rowIndex][columnIndex] = 0; - } else { - draft[rowIndex][columnIndex] = 1; - } - - onChange(draft); - }} - /> - - ))} - - ))} - - ); -} diff --git a/packages/website/src/components/ExperimentIcon.tsx b/packages/website/src/components/ExperimentIcon.tsx deleted file mode 100644 index 1af6844b8..000000000 --- a/packages/website/src/components/ExperimentIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function ExperimentIcon(props: ComponentProps<'svg'>) { - return ( - - - - ); -} diff --git a/packages/website/src/components/Favicons.astro b/packages/website/src/components/Favicons.astro deleted file mode 100644 index caf3a1824..000000000 --- a/packages/website/src/components/Favicons.astro +++ /dev/null @@ -1,7 +0,0 @@ ---- -import { localizedHTML as favicons } from "astro-favicons/middleware"; - -const { locale } = Astro.props; ---- - - diff --git a/packages/website/src/components/Footer.astro b/packages/website/src/components/Footer.astro deleted file mode 100644 index 196f62f34..000000000 --- a/packages/website/src/components/Footer.astro +++ /dev/null @@ -1,187 +0,0 @@ ---- -import { assertSecret, fetchGameDefinitions } from "@blitzkit/core"; -import { Box, Code, Flex, Heading, Link, Text } from "@radix-ui/themes"; -import packageJson from "../../../../package.json"; -import { getStrings } from "../core/i18n/getStrings"; -import { LinkI18n } from "./LinkI18n"; -import { LocaleSwitcherThemeWrapper } from "./LocaleSwitcher"; - -export interface Props { - locale: string; -} - -const gameDefinitions = await fetchGameDefinitions(); -const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_BRANCH); -const { locale } = Astro.props; -const strings = getStrings(locale); ---- - - - - - - - BlitzKit - {strings.website.footer.motto} - - {strings.website.footer.copyright} - - - - - - - - - - {strings.website.footer.about.title} - - - {strings.website.footer.about.credits} - - - {strings.website.footer.about.discord} - - - {strings.website.footer.about.github} - - - {strings.website.footer.about.patches} - - - - - - {strings.website.footer.legal.title} - - - {strings.website.footer.legal.privacy} - - - {strings.website.footer.legal.terms} - - - - - - - - - BlitzKit - { - PUBLIC_ASSET_BRANCH !== "preview" && ( - - WoTB - - ) - } - CDN - - - - - {packageJson.version} - - { - PUBLIC_ASSET_BRANCH !== "preview" && ( - - {gameDefinitions.version} - - ) - } - - {PUBLIC_ASSET_BRANCH} - - - - - - BlitzKit - - {packageJson.version} - - - { - PUBLIC_ASSET_BRANCH !== "preview" && ( - <> - - WoTB - - - {gameDefinitions.version} - - - ) - } - - CDN - - {PUBLIC_ASSET_BRANCH} - - - diff --git a/packages/website/src/components/Gallery/Card.tsx b/packages/website/src/components/Gallery/Card.tsx deleted file mode 100644 index 8345b5ded..000000000 --- a/packages/website/src/components/Gallery/Card.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { asset, Avatar } from "@blitzkit/core"; -import { Cross1Icon, DownloadIcon } from "@radix-ui/react-icons"; -import { - Dialog, - Flex, - IconButton, - Link, - Skeleton, - Text, -} from "@radix-ui/themes"; -import { useRef } from "react"; -import { useIntersection } from "../../hooks/useIntersection"; -import { useLocale } from "../../hooks/useLocale"; -import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; - -type GalleryCardProps = MaybeSkeletonComponentProps<{ - avatar: Avatar; -}>; - -export function GalleryCard(props: GalleryCardProps) { - const src = asset( - `gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`, - ); - const card = useRef(null!); - const { unwrap } = useLocale(); - - useIntersection(() => props.skeleton && props.onIntersection?.(), card, { - disabled: !props.skeleton || props.onIntersection === undefined, - }); - - const content = ( - - {props.skeleton && ( - <> - - - - )} - - {!props.skeleton && ( - <> - {unwrap(props.avatar.name!)} - - - {unwrap(props.avatar.name!)} - - - )} - - ); - - if (props.skeleton) return content; - - return ( - - {content} - - event.preventDefault()} - width="fit-content" - > - - - - - - - - - - - - - - - - {unwrap(props.avatar.name!)} - - - - - {unwrap(props.avatar.name!)} - - - - - ); -} diff --git a/packages/website/src/components/Gallery/List.tsx b/packages/website/src/components/Gallery/List.tsx deleted file mode 100644 index aa1ac26cf..000000000 --- a/packages/website/src/components/Gallery/List.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import locales from "@blitzkit/i18n/locales.json"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { Flex, Text } from "@radix-ui/themes"; -import fuzzysort from "fuzzysort"; -import { times } from "lodash-es"; -import { useMemo, useState } from "react"; -import { awaitableGallery } from "../../core/awaitables/gallery"; -import { useLocale } from "../../hooks/useLocale"; -import { Gallery } from "../../stores/gallery"; -import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; -import { InlineSkeleton } from "../InlineSkeleton"; -import { GalleryCard } from "./Card"; - -export interface Avatar { - name: string; - id: string; -} - -const gallery = await awaitableGallery; - -const DEFAULT_LOADED = 42; -const PREVIEW_COUNT = 28; - -export function GalleryList({ skeleton }: MaybeSkeletonComponentProps) { - const search = Gallery.use((state) => state.search); - const [loadedCards, setLoadedCards] = useState(DEFAULT_LOADED); - const { locale, strings } = useLocale(); - const filtered = useMemo(() => { - setLoadedCards(DEFAULT_LOADED); - - if (search === undefined) { - return gallery.avatars; - } else { - return fuzzysort - .go(search, gallery.avatars, { - keys: [ - ...locales.supported.map( - (supported) => `name.locales.${supported.locale}` - ), - "id", - ], - }) - .map((result) => result.obj); - } - }, [search]); - - return ( - <> - - {skeleton ? ( - - ) : ( - literals(strings.website.tools.gallery.search.results, { - count: filtered.length.toLocaleString(locale), - }) - )} - - - - {!skeleton && - filtered - .slice(0, loadedCards) - .map((avatar) => )} - - {times( - skeleton - ? DEFAULT_LOADED + PREVIEW_COUNT - : Math.min(PREVIEW_COUNT, filtered.length - loadedCards), - (index) => ( - setLoadedCards((state) => state + 2)} - /> - ) - )} - - - ); -} diff --git a/packages/website/src/components/Gallery/Search.tsx b/packages/website/src/components/Gallery/Search.tsx deleted file mode 100644 index 16307b2db..000000000 --- a/packages/website/src/components/Gallery/Search.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; -import { TextField } from "@radix-ui/themes"; -import { useRef } from "react"; -import { useLocale } from "../../hooks/useLocale"; -import { Gallery } from "../../stores/gallery"; -import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; - -export function GallerySearch({ skeleton }: MaybeSkeletonComponentProps) { - const input = useRef(null); - const { strings } = useLocale(); - - return ( - { - Gallery.mutate((draft) => { - const trimmed = event.target.value.trim(); - draft.search = trimmed.length === 0 ? undefined : trimmed; - }); - }} - > - - - - - ); -} diff --git a/packages/website/src/components/Giveaway.tsx b/packages/website/src/components/Giveaway.tsx deleted file mode 100644 index edd64c67a..000000000 --- a/packages/website/src/components/Giveaway.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Code, Flex, Heading, Link, Text } from "@radix-ui/themes"; -import { useEffect, useState, type ReactNode } from "react"; -import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponentProps"; -import { InlineSkeleton } from "./InlineSkeleton"; - -const TIME = 1764898200 * 1000; - -export function Giveaway({ skeleton }: MaybeSkeletonComponentProps) { - const [timeLeft, setTimeLeft] = useState(TIME - Date.now()); - - useEffect(() => { - const interval = setInterval(() => { - setTimeLeft(TIME - Date.now()); - }, 1000); - - return () => { - clearInterval(interval); - }; - }, []); - - let string: ReactNode; - - if (timeLeft > 0) { - const hours = Math.floor(timeLeft / (1000 * 60 * 60)); - const minutes = Math.floor((timeLeft / (1000 * 60)) % 60) - .toString() - .padStart(2, "0"); - const seconds = Math.floor((timeLeft / 1000) % 60) - .toString() - .padStart(2, "0"); - - string = ( - <> - in{" "} - - {hours}h {minutes}m {seconds}s - - - ); - } else { - string = "has ended. Come again later!"; - } - - return ( - - - - - - - - Music Box giveaway {!skeleton && string}{" "} - {skeleton && } - - - Join the BlitzKit Discord server event - - - - - - ); -} diff --git a/packages/website/src/components/GuessBackground/index.css b/packages/website/src/components/GuessBackground/index.css deleted file mode 100644 index 27600f511..000000000 --- a/packages/website/src/components/GuessBackground/index.css +++ /dev/null @@ -1,14 +0,0 @@ -.guess-background { - animation: moveBackground 180s linear infinite; -} - -@keyframes moveBackground { - from { - top: 0; - left: 0; - } - to { - top: -100%; - left: -100%; - } -} diff --git a/packages/website/src/components/GuessBackground/index.tsx b/packages/website/src/components/GuessBackground/index.tsx deleted file mode 100644 index 6923b9fbe..000000000 --- a/packages/website/src/components/GuessBackground/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Box } from "@radix-ui/themes"; -import { useEffect } from "react"; -import { awaitableModelDefinitions } from "../../core/awaitables/modelDefinitions"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; -import { tankToDuelMember } from "../../core/blitzkit/tankToDuelMember"; -import { Var } from "../../core/radix/var"; -import { Duel } from "../../stores/duel"; -import { Guess, GuessState } from "../../stores/guess"; -import { Tankopedia } from "../../stores/tankopedia"; -import "./index.css"; - -const [modelDefinitions, provisionDefinitions] = await Promise.all([ - awaitableModelDefinitions, - awaitableProvisionDefinitions, -]); - -export function GuessBackground() { - const guessState = Guess.use((state) => state.guessState); - const tank = Guess.use((state) => state.tank); - - useEffect(() => { - Duel.mutate((state) => { - state.protagonist = tankToDuelMember(tank, provisionDefinitions); - }); - Tankopedia.mutate((state) => { - state.model = modelDefinitions.models[tank.id]; - }); - }, [tank]); - - return ( - <> - - - - - - - ); -} diff --git a/packages/website/src/components/GuessRenderer.tsx b/packages/website/src/components/GuessRenderer.tsx deleted file mode 100644 index cc349d731..000000000 --- a/packages/website/src/components/GuessRenderer.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Guess, GuessState } from "../stores/guess"; -import { SmartCanvas } from "./SmartCanvas"; -import { Controls } from "./Tankopedia/HeroSection/components/TankSandbox/components/Control"; -import { Lighting } from "./Tankopedia/HeroSection/components/TankSandbox/components/Lighting"; -import { TankModel } from "./Tankopedia/HeroSection/components/TankSandbox/components/TankModel"; - -const BRIGHTNESS = 2 ** -5.5; -const CONTRAST = 2 ** 2; - -export function GuessRenderer() { - const guessState = Guess.use((state) => state.guessState); - const helpingReveal = Guess.use((state) => state.helpingReveal); - const isRevealed = helpingReveal || guessState !== GuessState.NotGuessed; - - return ( - - - - - - ); -} diff --git a/packages/website/src/components/GuessRendererLoader.tsx b/packages/website/src/components/GuessRendererLoader.tsx deleted file mode 100644 index da9479e91..000000000 --- a/packages/website/src/components/GuessRendererLoader.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Box, Spinner } from '@radix-ui/themes'; - -export function GuessRendererLoader() { - return ( - - - - ); -} diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx deleted file mode 100644 index 27c7915f0..000000000 --- a/packages/website/src/components/Guesser.tsx +++ /dev/null @@ -1,369 +0,0 @@ -import { - asset, - SEARCH_KEYS, - TankDefinition, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { - ArrowRightIcon, - EyeOpenIcon, - MagnifyingGlassIcon, - MixerVerticalIcon, - PaperPlaneIcon, - StarFilledIcon, - TrashIcon, -} from "@radix-ui/react-icons"; -import { - AlertDialog, - Box, - Button, - Card, - DropdownMenu, - Flex, - IconButton, - Spinner, - Text, - TextField, -} from "@radix-ui/themes"; -import fuzzysort from "fuzzysort"; -import { debounce, times } from "lodash-es"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; -import { awaitableTankNames } from "../core/awaitables/tankNames"; -import { useLocale } from "../hooks/useLocale"; -import { Guess, GuessState } from "../stores/guess"; -import { classIcons } from "./ClassIcon"; -import { SearchResults } from "./SearchResults"; - -const { go } = fuzzysort; - -const [tankNames, tankDefinitions] = await Promise.all([ - awaitableTankNames, - awaitableTankDefinitions, -]); - -const ids = Object.keys(tankDefinitions.tanks); - -export function Guesser() { - const tank = Guess.use((state) => state.tank); - const guessState = Guess.use((state) => state.guessState); - const correctGuesses = Guess.use((state) => state.correctGuesses); - const totalGuesses = Guess.use((state) => state.totalGuesses); - const helpingReveal = Guess.use((state) => state.helpingReveal); - const streak = Guess.use((state) => state.streak); - const { strings, unwrap } = useLocale(); - const input = useRef(null); - const [searching, setSearching] = useState(false); - const [results, setResults] = useState(null); - const [selected, setSelected] = useState(null); - const tiers = Guess.use((state) => state.tiers); - const status = useRef(null); - - const requestSearch = useCallback(() => { - setSelected(null); - setSearching(true); - search(); - }, []); - - const search = useCallback( - debounce(() => { - if (!input.current) return; - - setSearching(false); - const trimmed = input.current.value.trim(); - - if (trimmed.length === 0) { - setResults(null); - return; - } - - const searchResults = go(trimmed, tankNames, { - keys: SEARCH_KEYS, - limit: 6, - }); - - setResults( - searchResults.map((result) => tankDefinitions.tanks[result.obj.id]), - ); - }, 500), - [], - ); - - useEffect(() => { - if (guessState !== GuessState.NotGuessed) setSelected(null); - }, [guessState]); - - useEffect(() => { - const interval = setInterval(() => { - if (!status.current) return; - - status.current.innerText = literals(strings.website.tools.guess.stats, { - correct: correctGuesses, - total: totalGuesses, - streak: streak, - }); - }, 1000 / 200); - - return () => clearInterval(interval); - }, [correctGuesses, totalGuesses, streak]); - - return ( - - - {results !== null && ( - - - {results.length === 0 && ( - - - {strings.website.tools.guess.search.no_results} - - - )} - - - {results.map((result) => { - const Icon = classIcons[result.class]; - - return ( - { - if (!input.current) return; - - input.current.value = unwrap(result.name!); - setSelected(result); - setResults(null); - }} - text={unwrap(result.name!)} - prefix={ - - } - discriminator={ - - - {TIER_ROMAN_NUMERALS[result.tier]} - - } - /> - ); - })} - - - - )} - - - - - - - - - - - - - - - - {times(10, (index) => { - const tier = 10 - index; - const isSelected = tiers.includes(tier); - - return ( - { - event.preventDefault(); - - Guess.mutate((draft) => { - if (isSelected) { - if (draft.tiers.length === 1) return; - draft.tiers = draft.tiers.filter((t) => t !== tier); - } else { - draft.tiers = [...draft.tiers, tier]; - } - - draft.streak = 0; - }); - }} - > - {literals(strings.website.tools.guess.tier, { - tier: TIER_ROMAN_NUMERALS[tier], - })} - - ); - })} - - - - { - event.preventDefault(); - - Guess.mutate((draft) => { - draft.tiers = Guess.initial.tiers; - }); - }} - > - {strings.website.tools.guess.select_all} - - - { - event.preventDefault(); - - Guess.mutate((draft) => { - draft.tiers = []; - }); - }} - > - {strings.website.tools.guess.clear} - - - - - - - {searching ? : } - - - - - - - - - - - - - {strings.website.tools.guess.help.title} - - - {strings.website.tools.guess.help.description} - - - - - - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/GunAutoloaderIcon.tsx b/packages/website/src/components/GunAutoloaderIcon.tsx deleted file mode 100644 index 9c11a88f4..000000000 --- a/packages/website/src/components/GunAutoloaderIcon.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function GunAutoloaderIcon(props: ComponentProps<'svg'>) { - return ( - - - - - - ); -} diff --git a/packages/website/src/components/GunAutoreloaderIcon.tsx b/packages/website/src/components/GunAutoreloaderIcon.tsx deleted file mode 100644 index da0a94175..000000000 --- a/packages/website/src/components/GunAutoreloaderIcon.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function GunAutoreloaderIcon(props: ComponentProps<'svg'>) { - return ( - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/GunRegularIcon.tsx b/packages/website/src/components/GunRegularIcon.tsx deleted file mode 100644 index 904a91698..000000000 --- a/packages/website/src/components/GunRegularIcon.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function GunRegularIcon(props: ComponentProps<'svg'>) { - return ( - - - - ); -} diff --git a/packages/website/src/components/HelpingSpotLight.tsx b/packages/website/src/components/HelpingSpotLight.tsx deleted file mode 100644 index 85e16a212..000000000 --- a/packages/website/src/components/HelpingSpotLight.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Box, Text } from "@radix-ui/themes"; -import { Html } from "@react-three/drei"; -import { memo, type ComponentProps } from "react"; - -interface Props extends ComponentProps<"spotLight"> { - debug?: boolean; -} - -export const HelpingSpotLight = memo(({ debug, ...props }) => { - return ( - <> - {debug && ( - - - - - - - - - - {props.intensity} @ {JSON.stringify(props.userData)} - - - - - )} - - - - ); -}); diff --git a/packages/website/src/components/Home/HomeLocaleSwitcher.tsx b/packages/website/src/components/Home/HomeLocaleSwitcher.tsx deleted file mode 100644 index e17861a8d..000000000 --- a/packages/website/src/components/Home/HomeLocaleSwitcher.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import locales from "@blitzkit/i18n/locales.json"; -import { GlobeIcon } from "@radix-ui/react-icons"; -import { Flex, Text } from "@radix-ui/themes"; -import type { LocaleAcceptorProps } from "../../hooks/useLocale"; -import { BlitzKitTheme } from "../BlitzKitTheme"; -import { LocaleSwitcher } from "../LocaleSwitcher"; - -export function HomeLocaleSwitcher({ - locale = locales.default, -}: LocaleAcceptorProps) { - return ( - - - - - - - - - - ); -} diff --git a/packages/website/src/components/HomeHero.astro b/packages/website/src/components/HomeHero.astro deleted file mode 100644 index 9fe85224f..000000000 --- a/packages/website/src/components/HomeHero.astro +++ /dev/null @@ -1,153 +0,0 @@ ---- -import { Code, Flex, Heading, Text } from "@radix-ui/themes"; -import { resolveBranchName } from "../core/blitzkit/branches"; -import { getStrings } from "../core/i18n/getStrings"; -import { literals } from "@blitzkit/i18n/src/literals"; - -const { locale } = Astro.props; -const branchName = resolveBranchName(locale); -const isBranchNamed = branchName !== undefined; -const strings = getStrings(locale); ---- - - - - - - - - - - - BlitzKit - { - isBranchNamed && ( - - {branchName} - - ) - } - - - - - {literals(strings.website.home.hero.credit, { name: "TrèsAbhi" })} - - - - diff --git a/packages/website/src/components/HomePageHotTanks.tsx b/packages/website/src/components/HomePageHotTanks.tsx deleted file mode 100644 index f7c999185..000000000 --- a/packages/website/src/components/HomePageHotTanks.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { literals } from "@blitzkit/i18n/src/literals"; -import { EyeOpenIcon } from "@radix-ui/react-icons"; -import { Box, Flex, Heading, Text } from "@radix-ui/themes"; -import { awaitablePopularTanks } from "../core/awaitables/popularTanks"; -import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; -import { - LocaleProvider, - useLocale, - type LocaleAcceptorProps, -} from "../hooks/useLocale"; -import { TankCard } from "./TankCard"; - -const [tankDefinitions, popularTanks] = await Promise.all([ - awaitableTankDefinitions, - awaitablePopularTanks, -]); - -export function HomePageHotTanks({ locale }: LocaleAcceptorProps) { - return ( - - - - ); -} - -function Content() { - const { locale, strings } = useLocale(); - - return ( - - - {strings.website.home.hot_tanks.title} - - - {literals(strings.website.home.hot_tanks.subtitle, { days: 7 })} - - - - {popularTanks.tanks.map(({ id, views }) => { - const tank = tankDefinitions.tanks[id]; - - if (tank === undefined) return null; - - return ( - - - - {Math.round(views * (30 / 7)).toLocaleString(locale)} - - } - /> - - ); - })} - - - ); -} diff --git a/packages/website/src/components/InlineSkeleton/index.css b/packages/website/src/components/InlineSkeleton/index.css deleted file mode 100644 index 593636058..000000000 --- a/packages/website/src/components/InlineSkeleton/index.css +++ /dev/null @@ -1,3 +0,0 @@ -.inline-skeleton { - display: inline-block; -} diff --git a/packages/website/src/components/InlineSkeleton/index.tsx b/packages/website/src/components/InlineSkeleton/index.tsx deleted file mode 100644 index d825cff16..000000000 --- a/packages/website/src/components/InlineSkeleton/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Skeleton, type SkeletonProps } from '@radix-ui/themes'; -import './index.css'; - -export function InlineSkeleton({ className, ...props }: SkeletonProps) { - return ( - - ); -} diff --git a/packages/website/src/components/LinkI18n.tsx b/packages/website/src/components/LinkI18n.tsx deleted file mode 100644 index f076de7c9..000000000 --- a/packages/website/src/components/LinkI18n.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import locales from "@blitzkit/i18n/locales.json"; -import { Link, type LinkProps } from "@radix-ui/themes"; -import type { LocaleAcceptorProps } from "../hooks/useLocale"; - -type LinkI18nProps = LinkProps & LocaleAcceptorProps; - -export function LinkI18n({ locale, href, ...props }: LinkI18nProps) { - const prefix = - locale === undefined || locale === locales.default ? "" : `/${locale}`; - - return ( - - ); -} diff --git a/packages/website/src/components/LocaleRedirector.astro b/packages/website/src/components/LocaleRedirector.astro deleted file mode 100644 index e4ed25333..000000000 --- a/packages/website/src/components/LocaleRedirector.astro +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/packages/website/src/components/LocaleSwitcher.tsx b/packages/website/src/components/LocaleSwitcher.tsx deleted file mode 100644 index d53ed9e2f..000000000 --- a/packages/website/src/components/LocaleSwitcher.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { - STRINGS, - SUPPORTED_LOCALE_FLAGS, - type BlitzKitStrings, -} from "@blitzkit/i18n"; -import locales from "@blitzkit/i18n/locales.json"; -import { Select } from "@radix-ui/themes"; -import type { LocaleAcceptorProps } from "../hooks/useLocale"; -import { BlitzKitTheme } from "./BlitzKitTheme"; - -export function LocaleSwitcherThemeWrapper({ locale }: LocaleAcceptorProps) { - return ( - - - - ); -} - -export function LocaleSwitcher({ locale }: LocaleAcceptorProps) { - return ( - { - localStorage.setItem("preferred-locale", locale); - - let rawPath = window.location.pathname; - - for (const supported of locales.supported) { - if (window.location.pathname.startsWith(`/${supported.locale}`)) { - rawPath = rawPath.replace(`/${supported.locale}`, ""); - break; - } - } - - window.location.pathname = `${ - locale === locales.default ? "" : `/${locale}` - }${rawPath}`; - }} - > - - - - {locales.supported.map(({ locale }) => ( - - {SUPPORTED_LOCALE_FLAGS[locale]}{" "} - { - STRINGS[locale].common.locales[ - locale as keyof BlitzKitStrings["common"]["locales"] - ] - } - - ))} - - - ); -} diff --git a/packages/website/src/components/Mark/index.css b/packages/website/src/components/Mark/index.css deleted file mode 100644 index 50564f725..000000000 --- a/packages/website/src/components/Mark/index.css +++ /dev/null @@ -1,13 +0,0 @@ -.mark { - color: var(--gray-12); - background-color: var(--purple-a9); - outline: 4px solid var(--purple-a4); - box-shadow: 0 0 1rem var(--purple-9); -} - -.focused .mark { - color: var(--gray-1); - background-color: var(--amber-a9); - outline: 4px solid var(--amber-a4); - box-shadow: 0 0 1rem var(--amber-9); -} diff --git a/packages/website/src/components/Mark/index.tsx b/packages/website/src/components/Mark/index.tsx deleted file mode 100644 index c097675ec..000000000 --- a/packages/website/src/components/Mark/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentProps } from 'react'; -import { Var } from '../../core/radix/var'; -import './index.css'; - -export function Mark({ style, ...props }: ComponentProps<'mark'>) { - return ( - - ); -} diff --git a/packages/website/src/components/Markdown.tsx b/packages/website/src/components/Markdown.tsx deleted file mode 100644 index cc198c317..000000000 --- a/packages/website/src/components/Markdown.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Link, Table } from '@radix-ui/themes'; -import MarkdownToJSX from 'markdown-to-jsx'; -import type { ComponentProps } from 'react'; - -interface MarkdownProps { - children: string; -} - -function Image(props: ComponentProps<'img'>) { - return ; -} - -export function Markdown({ children }: MarkdownProps) { - return ( - - {children} - - ); -} diff --git a/packages/website/src/components/Meta.astro b/packages/website/src/components/Meta.astro deleted file mode 100644 index b8890591c..000000000 --- a/packages/website/src/components/Meta.astro +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/website/src/components/MissingShellIcon.tsx b/packages/website/src/components/MissingShellIcon.tsx deleted file mode 100644 index b4a93b57b..000000000 --- a/packages/website/src/components/MissingShellIcon.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function MissingShellIcon(props: ComponentProps<'svg'>) { - return ( - - - - ); -} diff --git a/packages/website/src/components/MixerScene/components/MixerFallback.tsx b/packages/website/src/components/MixerScene/components/MixerFallback.tsx deleted file mode 100644 index 707e37c6b..000000000 --- a/packages/website/src/components/MixerScene/components/MixerFallback.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Progress } from "@radix-ui/themes"; -import { Html } from "@react-three/drei"; -import { useStableProgress } from "../../../hooks/useStableProgress"; - -export function MixerFallback() { - const progress = useStableProgress(); - - return ( - - - - ); -} diff --git a/packages/website/src/components/MixerScene/components/Model.tsx b/packages/website/src/components/MixerScene/components/Model.tsx deleted file mode 100644 index 0bfb2e1cb..000000000 --- a/packages/website/src/components/MixerScene/components/Model.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import { useThree, type ThreeEvent } from "@react-three/fiber"; -import type { QuicklimeEvent } from "quicklime"; -import { useCallback, useEffect, useRef } from "react"; -import { Group, Vector2 } from "three"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; -import { jsxTree } from "../../../core/blitzkit/jsxTree"; -import { - modelTransformEvent, - type ModelTransformEventData, -} from "../../../core/blitzkit/modelTransform"; -import { controlsEnabledEvent } from "../../../core/controlsEnabled"; -import { useModel } from "../../../hooks/useModel"; -import { Mixer } from "../../../stores/mixer"; - -const modelDefinitions = await awaitableModelDefinitions; - -export function Model() { - const canvas = useThree((state) => state.gl.domElement); - - const turretGroup = useRef(null!); - const gunGroup = useRef(null!); - - const hull = Mixer.use((state) => state.hull); - const turret = Mixer.use((state) => state.turret); - const gun = Mixer.use((state) => state.gun); - - const hullGltf = useModel(hull.id); - const turretGltf = useModel(turret.tank.id); - const gunGltf = useModel(gun.tank.id); - - const hullNodes = Object.values(hullGltf.gltf.nodes); - const turretNodes = Object.values(turretGltf.gltf.nodes); - const gunNodes = Object.values(gunGltf.gltf.nodes); - - const tankModel = modelDefinitions.models[hull.id]; - const trackModel = tankModel.tracks[hull.tracks.at(-1)!.id]; - - const turretTankModel = modelDefinitions.models[turret.tank.id]; - const turretTrackModel = - turretTankModel.tracks[turret.tank.tracks.at(-1)!.id]; - const turretModel = turretTankModel.turrets[turret.turret.id]; - - const gunTankModel = modelDefinitions.models[gun.tank.id]; - const gunTrackModel = gunTankModel.tracks[gun.tank.tracks.at(-1)!.id]; - const gunTurretModel = gunTankModel.turrets[gun.turret.id]; - const gunModel = gunTurretModel.guns[gun.gun.id]; - - const pointer = useRef(new Vector2()); - const delta = useRef(new Vector2()); - const enablePitchRotation = useRef(true); - - const handlePointerDown = useCallback((event: ThreeEvent) => { - event.stopPropagation(); - - controlsEnabledEvent.dispatch(false); - pointer.current.set(event.clientX, event.clientY); - - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - }, []); - const handlePointerMove = useCallback((event: PointerEvent) => { - const bounds = canvas.getBoundingClientRect(); - - delta.current.set(event.clientX, event.clientY).sub(pointer.current); - pointer.current.set(event.clientX, event.clientY); - - modelTransformEvent.dispatch({ - pitch: - modelTransformEvent.last!.pitch - - (enablePitchRotation.current - ? delta.current.y * (Math.PI / bounds.height) - : 0), - yaw: - modelTransformEvent.last!.yaw + - delta.current.x * (Math.PI / bounds.width), - }); - }, []); - const handlePointerUp = useCallback(() => { - controlsEnabledEvent.dispatch(true); - - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - }, []); - - useEffect(() => { - function handleModelTransform( - event: QuicklimeEvent, - ) { - turretGroup.current.rotation.z = event.data.yaw; - gunGroup.current.rotation.x = event.data.pitch; - } - - modelTransformEvent.on(handleModelTransform); - - return () => { - modelTransformEvent.off(handleModelTransform); - }; - }, []); - - return ( - - {hullNodes.map((node) => { - const isHull = node.name === "hull"; - const isWheel = node.name.startsWith("chassis_wheel_"); - const isTrack = node.name.startsWith("chassis_track_"); - const isVisible = isHull || isWheel || isTrack; - - if (!isVisible) return null; - - return jsxTree(node, { - mesh(_, props, key) { - return ; - }, - }); - })} - - - - {turretNodes.map((node) => { - const isCurrentTurret = - node.name === - `turret_${turretModel.model_id.toString().padStart(2, "0")}`; - const isVisible = isCurrentTurret; - - if (!isVisible) return null; - - return jsxTree(node, { - mesh(_, props, key) { - return ( - { - enablePitchRotation.current = false; - handlePointerDown(event); - }} - /> - ); - }, - }); - })} - - - - {turretNodes.map((node) => { - const isCurrentMantlet = - node.name === - `gun_${turretModel.guns[turret.turret.guns.at(-1)!.id].model_id.toString().padStart(2, "0")}_mask`; - // const isCurrentGun = - // node.name === - // `gun_${gunModel.model_id.toString().padStart(2, "0")}`; - const isVisible = isCurrentMantlet; - - if (!isVisible) return null; - - return jsxTree(node, { - mesh(_, props, key) { - return ( - { - enablePitchRotation.current = true; - handlePointerDown(event); - }} - /> - ); - }, - }); - })} - - - - {gunNodes.map((node) => { - // const isCurrentMantlet = - // node.name === - // `gun_${gunModel.model_id.toString().padStart(2, "0")}_mask`; - const isCurrentGun = - node.name === - `gun_${gunModel.model_id.toString().padStart(2, "0")}`; - const isVisible = isCurrentGun; - - if (!isVisible) return null; - - return jsxTree(node, { - mesh(_, props, key) { - return ( - { - enablePitchRotation.current = true; - handlePointerDown(event); - }} - /> - ); - }, - }); - })} - - - - - - ); -} diff --git a/packages/website/src/components/MixerScene/components/Selector.tsx b/packages/website/src/components/MixerScene/components/Selector.tsx deleted file mode 100644 index d81bf194b..000000000 --- a/packages/website/src/components/MixerScene/components/Selector.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Box, Flex, Text } from "@radix-ui/themes"; -import type { ReactNode } from "react"; - -interface Props { - children: ReactNode; -} - -export function Selector({ children }: Props) { - return ( - - - - {children} - - - ); -} diff --git a/packages/website/src/components/MixerScene/index.tsx b/packages/website/src/components/MixerScene/index.tsx deleted file mode 100644 index c1b9291f5..000000000 --- a/packages/website/src/components/MixerScene/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { OrbitControls } from "@react-three/drei"; -import { Canvas } from "@react-three/fiber"; -import type { QuicklimeEvent } from "quicklime"; -import { forwardRef, Suspense, useEffect, useRef } from "react"; -import type { OrbitControls as OrbitControlsClass } from "three-stdlib"; -import { controlsEnabledEvent } from "../../core/controlsEnabled"; -import { Lighting } from "../Tankopedia/HeroSection/components/TankSandbox/components/Lighting"; -import { SceneProps } from "../Tankopedia/HeroSection/components/TankSandbox/components/SceneProps"; -import { MixerFallback } from "./components/MixerFallback"; -import { Model } from "./components/Model"; - -export const MixerScene = forwardRef((_, ref) => { - const controls = useRef(null!); - - useEffect(() => { - function handleControlsEnabled(event: QuicklimeEvent) { - if (!controls.current) return; - controls.current.enabled = event.data; - } - - controlsEnabledEvent.on(handleControlsEnabled); - - return () => { - controlsEnabledEvent.off(handleControlsEnabled); - }; - }, []); - - return ( - - - - - - - }> - - - - ); -}); diff --git a/packages/website/src/components/ModuleButtons/ConsumableButton.tsx b/packages/website/src/components/ModuleButtons/ConsumableButton.tsx deleted file mode 100644 index f6ddf6749..000000000 --- a/packages/website/src/components/ModuleButtons/ConsumableButton.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { ClockIcon, ReloadIcon } from "@radix-ui/react-icons"; -import { Flex, Text } from "@radix-ui/themes"; -import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions"; -import { useDelta } from "../../hooks/useDelta"; -import { useLocale } from "../../hooks/useLocale"; -import { GenericTankComponentButton } from "./GenericTankComponentButton"; -import type { TankComponentButtonProps } from "./TankComponentButton"; - -interface ConsumableButtonProps extends TankComponentButtonProps { - consumable: number; - duration?: number; - cooldown?: number; -} - -const consumableDefinitions = await awaitableConsumableDefinitions; - -export function ConsumableButton({ - consumable, - cooldown, - duration, - ...props -}: ConsumableButtonProps) { - const cooldownDelta = useDelta(cooldown ?? 0); - const durationDelta = useDelta(duration ?? 0); - const { unwrap } = useLocale(); - - return ( - - {(cooldown || duration) && ( - - {duration && ( - 0 - ? "green" - : durationDelta < 0 - ? "tomato" - : undefined - } - > - - - {Math.round(duration)}s - - - )} - {cooldown && ( - 0 - ? "tomato" - : undefined - } - > - - - {Math.round(cooldown)}s - - - )} - - )} - - ); -} diff --git a/packages/website/src/components/ModuleButtons/EquipmentButton.tsx b/packages/website/src/components/ModuleButtons/EquipmentButton.tsx deleted file mode 100644 index 337937832..000000000 --- a/packages/website/src/components/ModuleButtons/EquipmentButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { awaitableEquipmentDefinitions } from "../../core/awaitables/equipmentDefinitions"; -import { useLocale } from "../../hooks/useLocale"; -import { GenericTankComponentButton } from "./GenericTankComponentButton"; -import type { TankComponentButtonProps } from "./TankComponentButton"; - -interface EquipmentButtonProps extends TankComponentButtonProps { - equipment: number; -} - -const equipmentDefinitions = await awaitableEquipmentDefinitions; - -export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) { - const { unwrap } = useLocale(); - - return ( - - ); -} diff --git a/packages/website/src/components/ModuleButtons/GenericTankComponentButton.tsx b/packages/website/src/components/ModuleButtons/GenericTankComponentButton.tsx deleted file mode 100644 index 2ca36cd35..000000000 --- a/packages/website/src/components/ModuleButtons/GenericTankComponentButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { HTMLAttributes } from 'react'; -import { - TankComponentButton, - type TankComponentButtonProps, -} from './TankComponentButton'; - -interface GenericTankComponentButtonProps extends TankComponentButtonProps { - icon: string; - iconStyles?: HTMLAttributes['style']; -} - -export function GenericTankComponentButton({ - icon, - iconStyles, - children, - ...props -}: GenericTankComponentButtonProps) { - return ( - -
- {icon} -
- - {children} -
- ); -} diff --git a/packages/website/src/components/ModuleButtons/ModuleButton.tsx b/packages/website/src/components/ModuleButtons/ModuleButton.tsx deleted file mode 100644 index 663293425..000000000 --- a/packages/website/src/components/ModuleButtons/ModuleButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { asset } from '@blitzkit/core'; -import { GenericTankComponentButton } from './GenericTankComponentButton'; -import type { TankComponentButtonProps } from './TankComponentButton'; - -interface ModuleProps extends TankComponentButtonProps { - module: string; -} - -export function ModuleButton({ module, ...props }: ModuleProps) { - return ( - - ); -} diff --git a/packages/website/src/components/ModuleButtons/ProvisionButton.tsx b/packages/website/src/components/ModuleButtons/ProvisionButton.tsx deleted file mode 100644 index a3876df40..000000000 --- a/packages/website/src/components/ModuleButtons/ProvisionButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; -import { useLocale } from "../../hooks/useLocale"; -import { GenericTankComponentButton } from "./GenericTankComponentButton"; -import type { TankComponentButtonProps } from "./TankComponentButton"; - -interface ProvisionButtonProps extends TankComponentButtonProps { - provision: number; -} - -const provisionDefinitions = await awaitableProvisionDefinitions; - -export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) { - const { unwrap } = useLocale(); - - return ( - - ); -} diff --git a/packages/website/src/components/ModuleButtons/ShellButton.tsx b/packages/website/src/components/ModuleButtons/ShellButton.tsx deleted file mode 100644 index 08c3fcfd8..000000000 --- a/packages/website/src/components/ModuleButtons/ShellButton.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { asset } from '@blitzkit/core'; -import { GenericTankComponentButton } from './GenericTankComponentButton'; -import type { TankComponentButtonProps } from './TankComponentButton'; - -interface ShellButtonProps extends TankComponentButtonProps { - shell: string; -} - -export function ShellButton({ shell, ...props }: ShellButtonProps) { - return ( - - ); -} diff --git a/packages/website/src/components/ModuleButtons/TankComponentButton.tsx b/packages/website/src/components/ModuleButtons/TankComponentButton.tsx deleted file mode 100644 index 85d61101e..000000000 --- a/packages/website/src/components/ModuleButtons/TankComponentButton.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Button, Text, Tooltip } from '@radix-ui/themes'; -import type { ComponentProps, ReactNode } from 'react'; - -export interface TankComponentButtonProps - extends ComponentProps { - selected?: boolean; - children?: ReactNode; - discriminator?: ReactNode; - secondaryDiscriminator?: ReactNode; - disabled?: boolean; - banner?: string; - special?: boolean; - tooltip?: string; -} - -export function TankComponentButton({ - variant, - selected, - discriminator, - secondaryDiscriminator, - special, - children, - banner, - disabled, - style, - tooltip, - ...props -}: TankComponentButtonProps) { - const button = ( - - ); - - if (tooltip) return {button}; - return button; -} diff --git a/packages/website/src/components/ModuleManager.tsx b/packages/website/src/components/ModuleManager.tsx deleted file mode 100644 index 30d63a161..000000000 --- a/packages/website/src/components/ModuleManager.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { - TIER_ROMAN_NUMERALS, - type EngineDefinition, - type GunDefinition, - type ShellDefinition, - type TankDefinition, - type TrackDefinition, - type TurretDefinition, -} from "@blitzkit/core"; -import { DropdownMenu, Flex } from "@radix-ui/themes"; -import { useState } from "react"; -import { useLocale } from "../hooks/useLocale"; -import { ModuleButton } from "./ModuleButtons/ModuleButton"; - -interface ModuleManagerProps { - tank: TankDefinition; - turret: TurretDefinition; - gun: GunDefinition; - shell: ShellDefinition; - engine: EngineDefinition; - track: TrackDefinition; - onChange?: (modules: { - turret: TurretDefinition; - gun: GunDefinition; - engine: EngineDefinition; - track: TrackDefinition; - }) => void; -} - -export function ModuleManager({ - tank, - onChange, - ...modules -}: ModuleManagerProps) { - const [turretMenuOpen, setTurretMenuOpen] = useState(false); - const [gunMenuOpen, setGunMenuOpen] = useState(false); - const [engineMenuOpen, setEngineMenuOpen] = useState(false); - const [trackMenuOpen, setTrackMenuOpen] = useState(false); - const { unwrap } = useLocale(); - - return ( - - setTurretMenuOpen(true)} - /> - - - -
- - - - - {[...tank.turrets].reverse().map((turret) => { - return ( - { - const draft = { ...modules }; - draft.turret = turret; - draft.gun = draft.turret.guns.at(-1)!; - draft.shell = draft.gun.shells[0]; - - onChange?.(draft); - setTurretMenuOpen(false); - }} - > - {" "} - {unwrap(turret.name!)} - - ); - })} - - - - - setGunMenuOpen(true)} - /> - - - -
- - - - - {[...modules.turret.guns].reverse().map((gun) => { - return ( - { - const draft = { ...modules }; - draft.gun = gun; - draft.shell = draft.gun.shells[0]; - - onChange?.(draft); - setGunMenuOpen(false); - }} - > - {" "} - {unwrap(gun.name!)} - - ); - })} - - - - - setEngineMenuOpen(true)} - /> - - - -
- - - - - {[...tank.engines].reverse().map((engine) => { - return ( - { - const draft = { ...modules }; - draft.engine = engine; - - onChange?.(draft); - setEngineMenuOpen(false); - }} - > - {" "} - {unwrap(engine.name!)} - - ); - })} - - - - - setTrackMenuOpen(true)} - /> - - - -
- - - - - {[...tank.tracks].reverse().map((track) => { - return ( - { - const draft = { ...modules }; - draft.track = track; - - onChange?.(draft); - setTrackMenuOpen(false); - }} - > - {" "} - {unwrap(track.name!)} - - ); - })} - - - - - ); -} diff --git a/packages/website/src/components/Navbar.astro b/packages/website/src/components/Navbar.astro deleted file mode 100644 index b67d5d10a..000000000 --- a/packages/website/src/components/Navbar.astro +++ /dev/null @@ -1,264 +0,0 @@ ---- -import { - Cross1Icon, - DiscordLogoIcon, - GearIcon, - HamburgerMenuIcon, -} from "@radix-ui/react-icons"; -import { Box, Code, Flex, IconButton, Link } from "@radix-ui/themes"; -import { resolveBranchName } from "../core/blitzkit/branches"; -import { getStrings } from "../core/i18n/getStrings"; -import { Var } from "../core/radix/var"; -import { LinkI18n } from "./LinkI18n"; -import { tools } from "../constants/tools"; -import { NavbarAccount } from "./NavbarAccount"; - -export interface Props { - integrated?: boolean; - locale: string; -} - -const { integrated = false } = Astro.props; -const toolsFiltered = Object.values(tools).filter( - (tool) => !tool.href && !tool.disabled -); -const { locale } = Astro.props; -const branchName = resolveBranchName(locale); -const isBranchNamed = branchName !== undefined; -const strings = getStrings(locale); ---- - - - - - - - - - - - - BlitzKit - { - isBranchNamed && ( - - {branchName} - - ) - } - - - - - - - { - toolsFiltered.map((tool) => { - const selected = Astro.url.pathname.includes(`/${tool.id}`); - const toolStrings = - strings.website.tools[ - (tool.strings ?? - tool.id) as keyof typeof strings.website.tools - ]; - - return ( - - {toolStrings.name} - - ); - }) - } - - - - - - - - - - - - - - - - - - - - - - - - - - { - toolsFiltered.map((tool) => { - const toolStrings = - strings.website.tools[ - (tool.strings ?? tool.id) as keyof typeof strings.website.tools - ]; - - return ( - - - - {toolStrings.name} - - - - ); - }) - } - - - - - - diff --git a/packages/website/src/components/NavbarAccount/index.tsx b/packages/website/src/components/NavbarAccount/index.tsx deleted file mode 100644 index d649cdc4a..000000000 --- a/packages/website/src/components/NavbarAccount/index.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { PersonIcon } from "@radix-ui/react-icons"; -import { - Button, - Flex, - IconButton, - Popover, - Skeleton, - Text, -} from "@radix-ui/themes"; -import { Suspense } from "react"; -import { - LocaleProvider, - type LocaleAcceptorProps, -} from "../../hooks/useLocale"; -import { App } from "../../stores/app"; -import { BlitzKitTheme } from "../BlitzKitTheme"; -import { WargamingAccountName } from "../WargamingAccountName"; -import { WargamingIcon } from "../WargamingIcon"; -import { WargamingLoginButton } from "../WargamingLoginButton"; - -export function NavbarAccount({ locale }: LocaleAcceptorProps) { - return ( - - - - ); -} - -function Content() { - const logins = App.use((state) => state.logins); - - return ( - - - - - - - - - event.preventDefault()} - > - {logins.wargaming && ( - - - Logged in accounts - - - {logins.wargaming && ( - - - - }> - - - - - - )} - - )} - - {/* {!logins. !== !logins.wargaming && ( - - - - )} */} - - {!logins.wargaming && ( - - - Log in with... - - - {!logins.wargaming && } - - )} - - - - ); -} diff --git a/packages/website/src/components/PageWrapper.tsx b/packages/website/src/components/PageWrapper.tsx deleted file mode 100644 index 8933a7795..000000000 --- a/packages/website/src/components/PageWrapper.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Flex, type FlexProps, Theme } from "@radix-ui/themes"; -import type { ComponentProps } from "react"; -import { NAVBAR_HEIGHT } from "../constants/navbar"; -import type { RadixSize } from "../stores/embedState"; -import { BlitzKitTheme } from "./BlitzKitTheme"; - -type PageWrapperProps = FlexProps & { - color?: ComponentProps["accentColor"]; - containerProps?: ComponentProps; - noFlex1?: boolean; - padding?: RadixSize; - noMinHeight?: boolean; -}; - -export function PageWrapper({ - style, - color, - children, - noFlex1 = false, - containerProps, - noMinHeight = false, - padding = "4", - ...props -}: PageWrapperProps) { - return ( - - - {children} - - - ); -} diff --git a/packages/website/src/components/Performance/Header.tsx b/packages/website/src/components/Performance/Header.tsx deleted file mode 100644 index 4ab8a23bc..000000000 --- a/packages/website/src/components/Performance/Header.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { - CaretDownIcon, - CaretSortIcon, - CaretUpIcon, -} from "@radix-ui/react-icons"; -import { Button, Flex, Table } from "@radix-ui/themes"; -import { useLocale } from "../../hooks/useLocale"; -import { TankPerformanceSort } from "../../stores/tankPerformanceSort"; -import { TankPerformanceSortTypeNamesArray } from "../../stores/tankPerformanceSort/constants"; -import { StickyColumnHeaderCell } from "../StickyColumnHeaderCell"; - -export function Header() { - const sort = TankPerformanceSort.use(); - const { strings } = useLocale(); - - return ( - - - - {strings.website.tools.performance.table.tanks.header} - - - {TankPerformanceSortTypeNamesArray.map((type) => { - const isSelected = sort.type === type; - - return ( - - - - - - ); - })} - - - ); -} diff --git a/packages/website/src/components/Performance/PerformanceInfo.tsx b/packages/website/src/components/Performance/PerformanceInfo.tsx deleted file mode 100644 index 6c88d8aed..000000000 --- a/packages/website/src/components/Performance/PerformanceInfo.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { formatCompact } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { Callout, Flex } from "@radix-ui/themes"; -import { awaitableAverageDefinitions } from "../../core/awaitables/averageDefinitions"; -import { awaitableDiscoveredIdsDefinitions } from "../../core/awaitables/discoveredIdsDefinitions"; -import { useLocale } from "../../hooks/useLocale"; -import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; -import { InlineSkeleton } from "../InlineSkeleton"; - -const [discoveredIdsDefinitions, averageDefinitions] = await Promise.all([ - awaitableDiscoveredIdsDefinitions, - awaitableAverageDefinitions, -]); - -export function PerformanceInfo({ skeleton }: MaybeSkeletonComponentProps) { - const { strings, locale } = useLocale(); - const samples = formatCompact( - locale, - Math.round(discoveredIdsDefinitions.count) - ); - const minutesAgo = Math.floor( - (Date.now() - averageDefinitions.time) / (1000 * 60) - ); - const hoursAgo = Math.floor(minutesAgo / 60); - const timeAgo = literals( - strings.website.tools.performance.estimation[ - hoursAgo === 0 ? "minutes" : "hours" - ], - { count: hoursAgo === 0 ? minutesAgo : hoursAgo } - ); - - return ( - - - - - - - {skeleton && } - - {!skeleton && - literals(strings.website.tools.performance.estimation.body, { - count: samples, - age: timeAgo, - })} - - - - ); -} diff --git a/packages/website/src/components/Performance/PlayerCountControl.tsx b/packages/website/src/components/Performance/PlayerCountControl.tsx deleted file mode 100644 index 297bb2908..000000000 --- a/packages/website/src/components/Performance/PlayerCountControl.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Flex, Select, Text } from '@radix-ui/themes'; -import { useLocale } from '../../hooks/useLocale'; -import { Performance, type PlayerCountPeriod } from '../../stores/performance'; - -export function PlayerCountControl() { - const playerCountPeriod = Performance.use((state) => state.playerCountPeriod); - const { strings } = useLocale(); - - return ( - - {strings.website.tools.performance.player_count.label} - { - Performance.mutate((draft) => { - draft.playerCountPeriod = period as PlayerCountPeriod; - }); - }} - > - - - - {strings.common.player_count_period.total} - - - {strings.common.player_count_period.d_120} - - - {strings.common.player_count_period.d_90} - - - {strings.common.player_count_period.d_60} - - - {strings.common.player_count_period.d_30} - - - {strings.common.player_count_period.d_7} - - - {strings.common.player_count_period.d_1} - - - - - ); -} diff --git a/packages/website/src/components/Performance/RowLoader.tsx b/packages/website/src/components/Performance/RowLoader.tsx deleted file mode 100644 index 8beb2ee44..000000000 --- a/packages/website/src/components/Performance/RowLoader.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Skeleton, Table } from "@radix-ui/themes"; -import { times } from "lodash-es"; -import { memo, useRef } from "react"; -import { useIntersection } from "../../hooks/useIntersection"; -import { TankPerformanceSortTypeNamesArray } from "../../stores/tankPerformanceSort/constants"; - -interface RowLoaderProps { - onIntersection?: () => void; -} - -export const RowLoader = memo(({ onIntersection }) => { - const row = useRef(null!); - - useIntersection(() => onIntersection?.(), row, { - disabled: onIntersection === undefined, - }); - - return ( - - - - - - {times(TankPerformanceSortTypeNamesArray.length, (index) => ( - - - - ))} - - ); -}); diff --git a/packages/website/src/components/Performance/Table.tsx b/packages/website/src/components/Performance/Table.tsx deleted file mode 100644 index 24a38a468..000000000 --- a/packages/website/src/components/Performance/Table.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Flex } from "@radix-ui/themes"; -import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; -import { StickyTableRoot } from "../StickyTableRoot"; -import { Header } from "./Header"; -import { Tanks } from "./Tanks"; - -export function TankTable({ skeleton }: MaybeSkeletonComponentProps) { - return ( - - -
- - - - ); -} diff --git a/packages/website/src/components/Performance/TankRow.tsx b/packages/website/src/components/Performance/TankRow.tsx deleted file mode 100644 index 8bdf77fb6..000000000 --- a/packages/website/src/components/Performance/TankRow.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { TankDefinition, formatCompact } from "@blitzkit/core"; -import { Table } from "@radix-ui/themes"; -import { memo } from "react"; -import { awaitableAverageDefinitions } from "../../core/awaitables/averageDefinitions"; -import { useAveragesExclusionRatio } from "../../hooks/useAveragesExclusionRatio"; -import { useLocale } from "../../hooks/useLocale"; -import { Performance } from "../../stores/performance"; -import { CompareCell, CompareCellDirection } from "../CompareCell"; -import { TankRowHeaderCell } from "../TankRowHeaderCell"; - -interface TankRowProps { - tank: TankDefinition; - average: Record; -} - -const averageDefinitions = await awaitableAverageDefinitions; - -export const TankRow = memo( - ({ tank, average }) => { - const averages = averageDefinitions.averages[tank.id]; - const ratio = useAveragesExclusionRatio(); - const playerCountPeriod = Performance.use( - (state) => state.playerCountPeriod, - ); - const { locale } = useLocale(); - - return ( - - - - - {((averages.mu!.wins / averages.mu!.battles) * 100).toFixed(1)}% - - - {formatCompact( - locale, - Math.round(ratio * averages.samples![playerCountPeriod]), - )} - - - {Math.round( - averages.mu!.damage_dealt / averages.mu!.battles, - ).toLocaleString(locale)} - - - {Math.round( - (averages.mu!.survived_battles / averages.mu!.battles) * 100, - )} - % - - - {Math.round(averages.mu!.xp / averages.mu!.battles).toLocaleString( - locale, - )} - - - {(averages.mu!.frags / averages.mu!.battles).toFixed(2)} - - - {(averages.mu!.spotted / averages.mu!.battles).toFixed(2)} - - - {Math.round((averages.mu!.hits / averages.mu!.shots) * 100)}% - - - {(averages.mu!.shots / averages.mu!.battles).toFixed(1)} - - - {(averages.mu!.hits / averages.mu!.battles).toFixed(1)} - - - {(averages.mu!.damage_dealt / averages.mu!.damage_received).toFixed( - 2, - )} - - - {Math.round( - averages.mu!.damage_received / averages.mu!.battles, - ).toLocaleString(locale)} - - - {(averages.mu!.capture_points / averages.mu!.battles).toFixed(2)} - - - ); - }, - (prev, next) => prev.tank.id === next.tank.id, -); diff --git a/packages/website/src/components/Performance/Tanks.tsx b/packages/website/src/components/Performance/Tanks.tsx deleted file mode 100644 index 5496e1e5c..000000000 --- a/packages/website/src/components/Performance/Tanks.tsx +++ /dev/null @@ -1,269 +0,0 @@ -import { formatCompact, type BlitzkitStats } from "@blitzkit/core"; -import { Table } from "@radix-ui/themes"; -import { times } from "lodash-es"; -import { - Fragment, - Suspense, - useCallback, - useEffect, - useMemo, - useState, -} from "react"; -import usePromise from "react-promise-suspense"; -import { awaitableAverageDefinitions } from "../../core/awaitables/averageDefinitions"; -import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; -import { filterTanks } from "../../core/blitzkit/filterTanks"; -import { useAveragesExclusionRatio } from "../../hooks/useAveragesExclusionRatio"; -import { useLocale } from "../../hooks/useLocale"; -import { Performance } from "../../stores/performance"; -import { TankFilters } from "../../stores/tankFilters"; -import { TankPerformanceSort } from "../../stores/tankPerformanceSort"; -import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; -import { StickyRowHeaderCell } from "../StickyRowHeaderCell"; -import { RowLoader } from "./RowLoader"; -import { TankRow } from "./TankRow"; - -const PREVIEW_COUNT = 10; -const DEFAULT_LOADED_ROWS = 25; - -const [tankDefinitions, averageDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableAverageDefinitions, -]); - -const tankList = Object.values(tankDefinitions.tanks); - -export function Tanks({ skeleton }: MaybeSkeletonComponentProps) { - if (skeleton) { - return ( - - {times(16, (index) => ( - - ))} - - ); - } - - const sort = TankPerformanceSort.use(); - const filters = TankFilters.use(); - const filteredTanks = usePromise( - () => - filterTanks(filters, tankList).then((tanks) => - tanks.filter(({ id }) => id in averageDefinitions.averages), - ), - // react-promise-suspense has awful type annotations - [filters] as any, - ); - const tanksMapped = useMemo( - () => - filteredTanks.map(({ id }) => ({ - id, - ...averageDefinitions.averages[id], - })), - [filters], - ); - const tanksSorted = useMemo(() => { - const { playerCountPeriod } = Performance.state; - - switch (sort.type) { - case "accuracy": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.hits / a.mu!.shots - b.mu!.hits / b.mu!.shots), - ); - case "capture_points": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.capture_points / a.mu!.battles - - b.mu!.capture_points / b.mu!.battles), - ); - case "damage": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.damage_dealt / a.mu!.battles - - b.mu!.damage_dealt / b.mu!.battles), - ); - case "damage_ratio": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.damage_dealt / a.mu!.damage_received - - b.mu!.damage_dealt / b.mu!.damage_received), - ); - case "damage_taken": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.damage_received / a.mu!.battles - - b.mu!.damage_received / b.mu!.battles), - ); - case "hits": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.hits / a.mu!.battles - b.mu!.hits / b.mu!.battles), - ); - case "kills": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.frags / a.mu!.battles - b.mu!.frags / b.mu!.battles), - ); - case "shots": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.shots / a.mu!.battles - b.mu!.shots / b.mu!.battles), - ); - case "players": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.samples![playerCountPeriod] - b.samples![playerCountPeriod]), - ); - case "spots": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.spotted / a.mu!.battles - b.mu!.spotted / b.mu!.battles), - ); - case "survival": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.survived_battles / a.mu!.battles - - b.mu!.survived_battles / b.mu!.battles), - ); - case "winrate": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.wins / a.mu!.battles - b.mu!.wins / b.mu!.battles), - ); - case "xp": - return tanksMapped.sort( - (a, b) => - sort.direction * - (a.mu!.xp / a.mu!.battles - b.mu!.xp / b.mu!.battles), - ); - } - }, [filters, sort]); - const [loadedRows, setLoadedRows] = useState(DEFAULT_LOADED_ROWS); - - useEffect(() => { - setLoadedRows(DEFAULT_LOADED_ROWS); - }, [filters, sort]); - - const playerCountPeriod = Performance.use((state) => state.playerCountPeriod); - const { strings, locale } = useLocale(); - const ratio = useAveragesExclusionRatio(); - const sum = useCallback( - (slice: (tank: BlitzkitStats) => number) => { - return tanksSorted.reduce( - (acc, tank) => acc + tank.samples![playerCountPeriod] * slice(tank.mu!), - 0, - ); - }, - [tanksSorted], - ); - - const battles = sum(({ battles }) => battles); - const winrate = sum(({ wins }) => wins) / battles; - const damage = sum(({ damage_dealt }) => damage_dealt) / battles; - const survival = sum(({ survived_battles }) => survived_battles) / battles; - const xp = sum(({ xp }) => xp) / battles; - const kills = sum(({ frags }) => frags) / battles; - const spots = sum(({ spotted }) => spotted) / battles; - const hits = sum(({ hits }) => hits) / battles; - const shots = sum(({ shots }) => shots) / battles; - const accuracy = hits / shots; - const damageReceived = - sum(({ damage_received }) => damage_received) / battles; - const damageRatio = damage / damageReceived; - const capturePoints = sum(({ capture_points }) => capture_points) / battles; - - const average = { - battles, - winrate, - damage, - survival, - xp, - kills, - spots, - hits, - shots, - accuracy, - damageReceived, - damageRatio, - capturePoints, - }; - - return ( - - - - {strings.website.tools.performance.table.tanks.total} - - {(winrate * 100).toFixed(1)}% - - {formatCompact( - locale, - ratio * averageDefinitions.samples![playerCountPeriod], - )} - - - {Math.round(damage).toLocaleString(locale)} - - - {Math.round(survival * 100).toFixed(1)}% - - - {Math.round(xp).toLocaleString(locale)} - - {kills.toFixed(2)} - {spots.toFixed(2)} - {(accuracy * 100).toFixed(0)}% - {shots.toFixed(1)} - {hits.toFixed(1)} - {damageRatio.toFixed(2)} - - {Math.round(damageReceived).toLocaleString(locale)} - - {capturePoints.toFixed(2)} - - - {tanksSorted.slice(0, loadedRows).map((averages) => { - const tank = tankDefinitions.tanks[averages.id]; - - if (tank === undefined) return null; - - return ( - - }> - - - - ); - })} - - {times( - Math.min(PREVIEW_COUNT, tanksSorted.length - loadedRows), - (index) => { - return ( - { - setLoadedRows((state) => - Math.min(state + 2, tanksSorted.length), - ); - }} - /> - ); - }, - )} - - ); -} diff --git a/packages/website/src/components/PlaylistActions.tsx b/packages/website/src/components/PlaylistActions.tsx deleted file mode 100644 index 6839607e3..000000000 --- a/packages/website/src/components/PlaylistActions.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { fisherYates } from "@blitzkit/core"; -import { CaretDownIcon } from "@radix-ui/react-icons"; -import { Button, DropdownMenu, Flex, Spinner } from "@radix-ui/themes"; -import { useLocale } from "../hooks/useLocale"; -import { App } from "../stores/app"; -import { Playlist } from "../stores/playlist"; - -export function PlaylistActions() { - const { strings } = useLocale(); - const wargaming = App.use((state) => state.logins.wargaming); - - return ( - <> - - - - - - - - {wargaming && ( - <> - - - - - - - { - Playlist.mutate((draft) => { - draft.list = draft.list?.sort( - (a, b) => (a.then?.last ?? 0) - (b.then?.last ?? 0) - ); - }); - }} - > - {strings.website.tools.playlist.sort.last_played} - - { - Playlist.mutate((draft) => { - draft.list = draft.list?.sort( - (a, b) => - (a.then?.battles ?? 0) - (b.then?.battles ?? 0) - ); - }); - }} - > - {strings.website.tools.playlist.sort.least_played} - - - - - )} - - - {wargaming && ( - - - {strings.website.tools.playlist.tracking} - - )} - - ); -} diff --git a/packages/website/src/components/PlaylistGenerator.tsx b/packages/website/src/components/PlaylistGenerator.tsx deleted file mode 100644 index 85b9c5d0a..000000000 --- a/packages/website/src/components/PlaylistGenerator.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { fisherYates, tankIcon } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { ChevronRightIcon } from "@radix-ui/react-icons"; -import { - AlertDialog, - Box, - Button, - Flex, - Grid, - Skeleton, - Text, -} from "@radix-ui/themes"; -import { useMemo, useState } from "react"; -import usePromise from "react-promise-suspense"; -import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; -import { filterTanks } from "../core/blitzkit/filterTanks"; -import { generatePlaylist } from "../core/blitzkit/generatePlaylist"; -import { useLocale } from "../hooks/useLocale"; -import { App } from "../stores/app"; -import { TankFilters } from "../stores/tankFilters"; -import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponentProps"; -import { LinkI18n } from "./LinkI18n"; -import { FilterControl } from "./TankSearch/components/FilterControl"; - -const tankDefinitions = await awaitableTankDefinitions; - -const POOL_HEIGHT = "16rem"; - -export function PlaylistGenerator({ skeleton }: MaybeSkeletonComponentProps) { - const [largeWarningOpen, setLargeWarningOpen] = useState(false); - const { strings, locale } = useLocale(); - const wargaming = App.use((state) => state.logins.wargaming); - const filters = TankFilters.use(); - const randomTankList = useMemo( - () => fisherYates(Object.values(tankDefinitions.tanks)), - [filters] - ); - const tanks = usePromise( - () => filterTanks(filters, randomTankList, wargaming?.id), - // react-promise-suspense has awful type annotations - [filters] as any - ); - const tanksTrimmed = useMemo(() => tanks.slice(0, 65), [filters]); - - return ( - <> - - - - {strings.website.tools.playlist.large_warning.title} - - - {literals( - strings.website.tools.playlist.large_warning.description, - { - count: tanks.length, - time: Math.round(tanks.length * ((2 * 4) / 60)), - } - )} - - - - - - - - - - - - - - - - - - - - - - - - {tanksTrimmed.map((tank) => - skeleton ? ( - - - - ) : ( - - ) - )} - - - - - - {literals(strings.website.tools.playlist.pool_size, { - count: tanks.length, - })} - - - - - - ); -} diff --git a/packages/website/src/components/PlaylistOrder.tsx b/packages/website/src/components/PlaylistOrder.tsx deleted file mode 100644 index f9fa71ab9..000000000 --- a/packages/website/src/components/PlaylistOrder.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Box, Flex } from "@radix-ui/themes"; -import { CurrentlyPlaying } from "./CurrentlyPlaying"; -import { PlaylistActions } from "./PlaylistActions"; -import { PlaylistTable } from "./PlaylistTable"; -import { PlaylistTracking } from "./PlaylistTracking"; - -export function PlaylistOrder() { - return ( - - - - - - - - - - ); -} diff --git a/packages/website/src/components/PlaylistTable.tsx b/packages/website/src/components/PlaylistTable.tsx deleted file mode 100644 index bbf6abdc0..000000000 --- a/packages/website/src/components/PlaylistTable.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Table, Text } from "@radix-ui/themes"; -import { useLocale } from "../hooks/useLocale"; -import { Playlist } from "../stores/playlist"; -import { PlaylistTankEntry } from "./PlaylistTankEntry"; -import { StickyColumnHeaderCell } from "./StickyColumnHeaderCell"; -import { StickyTableRoot } from "./StickyTableRoot"; - -export function PlaylistTable() { - const { strings } = useLocale(); - const list = Playlist.use((state) => state.list); - - if (!list) return null; - - return ( - - - - - - {strings.website.tools.playlist.table.index} - - - - - - - - - {strings.website.tools.playlist.table.tank} - - - - {strings.website.tools.playlist.table.wins} - - - - {strings.website.tools.playlist.table.battles} - - - - {strings.website.tools.playlist.table.last_played} - - - - - - {list.map((props, index) => ( - - ))} - - - ); -} diff --git a/packages/website/src/components/PlaylistTankEntry.tsx b/packages/website/src/components/PlaylistTankEntry.tsx deleted file mode 100644 index 5a783d5f9..000000000 --- a/packages/website/src/components/PlaylistTankEntry.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { - MinusCircledIcon, - TriangleDownIcon, - TriangleUpIcon, -} from "@radix-ui/react-icons"; -import { Checkbox, Flex, IconButton, Table, Text } from "@radix-ui/themes"; -import { memo } from "react"; -import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; -import { useLocale } from "../hooks/useLocale"; -import { Playlist, type PlaylistEntry } from "../stores/playlist"; -import { TankRowHeaderCell } from "./TankRowHeaderCell"; - -interface Props extends PlaylistEntry { - index: number; -} - -const tankDefinitions = await awaitableTankDefinitions; - -export const PlaylistTankEntry = memo( - ({ id, checked, then, now, index }) => { - const { locale, strings } = useLocale(); - const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" }); - const dateNow = Date.now(); - const tank = tankDefinitions.tanks[id]; - - return ( - - {index + 1} - - { - Playlist.mutate((draft) => { - draft.list![index].checked = !checked; - }); - }} - > - then.battles) || - checked - } - /> - - - - { - Playlist.mutate((draft) => { - draft.list!.splice(index, 1); - }); - }} - > - - - - - - - - {now && then && now.battles !== then.battles ? ( - - - - - {now.wins - then.wins} - - - - - - - {now.battles - then.battles - now.wins + then.wins} - - - - ) : ( - strings.website.tools.playlist.table.unrecorded - )} - - - - {now?.battles ?? strings.website.tools.playlist.table.unrecorded} - - - - {now && now.last !== 0 - ? rtf.format( - Math.ceil((now.last - dateNow / 1000) / (60 * 60 * 24)), - "day" - ) - : strings.website.tools.playlist.table.unrecorded} - - - ); - }, - (a, b) => - a.checked === b.checked && - a.then?.battles === b.then?.battles && - a.index === b.index -); diff --git a/packages/website/src/components/PlaylistTracking.tsx b/packages/website/src/components/PlaylistTracking.tsx deleted file mode 100644 index dd8e3b34b..000000000 --- a/packages/website/src/components/PlaylistTracking.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useEffect } from "react"; -import { updatePlaylist } from "../core/blitzkit/updatePlaylist"; - -export function PlaylistTracking() { - useEffect(() => { - const interval = setInterval(updatePlaylist, 5000); - - return () => { - clearInterval(interval); - }; - }, []); - - return null; -} diff --git a/packages/website/src/components/Plugs.tsx b/packages/website/src/components/Plugs.tsx deleted file mode 100644 index f5c4d838b..000000000 --- a/packages/website/src/components/Plugs.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { ArrowRightIcon } from "@radix-ui/react-icons"; -import { Button, Code, Flex, Link, Text } from "@radix-ui/themes"; - -export function Plugs() { - const promoteOpentest = - assertSecret(import.meta.env.PUBLIC_PROMOTE_OPENTEST) === "true" && - assertSecret(import.meta.env.PUBLIC_BRANCH) !== "opentest"; - const promotions = [promoteOpentest]; - - if (promotions.every((promotion) => !promotion)) return null; - - return ( - - {promoteOpentest && ( - - - BlitzKit{" "} - - opentest - {" "} - is available now{" "} - - - - - - - )} - - ); -} diff --git a/packages/website/src/components/ProvisionsManager.tsx b/packages/website/src/components/ProvisionsManager.tsx deleted file mode 100644 index 7ede38889..000000000 --- a/packages/website/src/components/ProvisionsManager.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Flex } from "@radix-ui/themes"; -import { awaitableProvisionDefinitions } from "../core/awaitables/provisionDefinitions"; -import { ProvisionButton } from "./ModuleButtons/ProvisionButton"; - -interface ProvisionsManagerProps { - provisions: number[]; - selected: number[]; - disabled?: boolean; - onChange?: (provisions: number[]) => void; -} - -const provisionDefinitions = await awaitableProvisionDefinitions; - -export function ProvisionsManager({ - provisions, - selected, - onChange, - disabled, -}: ProvisionsManagerProps) { - return ( - - {provisions.map((id) => { - const isSelected = selected.includes(id); - const provision = provisionDefinitions.provisions[id]; - - if (provision.game_mode_exclusive) return null; - - return ( - { - if (!onChange) return; - - const draft = [...selected]; - - if (isSelected) { - draft.splice(draft.indexOf(id), 1); - } else { - draft.push(id); - } - - onChange(draft); - }} - /> - ); - })} - - ); -} diff --git a/packages/website/src/components/ResearchedIcon.tsx b/packages/website/src/components/ResearchedIcon.tsx deleted file mode 100644 index b7f985eb0..000000000 --- a/packages/website/src/components/ResearchedIcon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function ResearchedIcon(props: ComponentProps<'svg'>) { - return ( - - - - - - - ); -} diff --git a/packages/website/src/components/ScienceIcon.tsx b/packages/website/src/components/ScienceIcon.tsx deleted file mode 100644 index 21cdaeea3..000000000 --- a/packages/website/src/components/ScienceIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function ScienceIcon(props: ComponentProps<'svg'>) { - return ( - - - - ); -} diff --git a/packages/website/src/components/ScienceOffIcon.tsx b/packages/website/src/components/ScienceOffIcon.tsx deleted file mode 100644 index 0dfc616fc..000000000 --- a/packages/website/src/components/ScienceOffIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function ScienceOffIcon(props: ComponentProps<'svg'>) { - return ( - - - - ); -} diff --git a/packages/website/src/components/ScreenshotButton.tsx b/packages/website/src/components/ScreenshotButton.tsx deleted file mode 100644 index b86be58b9..000000000 --- a/packages/website/src/components/ScreenshotButton.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { CameraIcon, CopyIcon, DownloadIcon } from "@radix-ui/react-icons"; -import { - Button, - Flex, - IconButton, - Popover, - type ButtonProps, -} from "@radix-ui/themes"; -import { Jimp } from "jimp"; -import { useCallback, type RefObject } from "react"; -import { useLocale } from "../hooks/useLocale"; -import { screenshotReadyEvent } from "./Tankopedia/HeroSection/components/TankSandbox/components/SceneProps"; - -interface Props extends ButtonProps { - canvas: RefObject; - name: string; -} - -export function ScreenshotButton({ canvas, name, children, ...props }: Props) { - const { strings } = useLocale(); - - const screenshot = useCallback(async () => { - if (!canvas.current) { - throw new Error("Canvas not found"); - } - - screenshotReadyEvent.dispatch(true); - - const blob = await new Promise((resolve, reject) => { - requestAnimationFrame(() => { - canvas.current.toBlob((blob) => { - screenshotReadyEvent.dispatch(false); - - if (!blob) { - reject(new Error("Failed to capture screenshot")); - } else { - resolve(blob); - } - }); - }); - }); - - const buffer = await blob.arrayBuffer(); - const image = await Jimp.fromBuffer(buffer); - - image.autocrop(); - - return image; - }, [canvas]); - - return ( - - - {children ? ( - - ) : ( - - - - )} - - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/SearchResults.tsx b/packages/website/src/components/SearchResults.tsx deleted file mode 100644 index f0c41662c..000000000 --- a/packages/website/src/components/SearchResults.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { - Badge, - Button, - Flex, - Text, - type ButtonProps, - type FlexProps, -} from "@radix-ui/themes"; -import type { ReactNode } from "react"; - -function Root(props: FlexProps) { - return ; -} - -interface ItemProps extends Omit { - prefix?: React.ReactNode; - text: string; - discriminator?: ReactNode; -} - -function Item({ text, discriminator, prefix, ...props }: ItemProps) { - return ( - - ); -} - -export const SearchResults = { Root, Item }; diff --git a/packages/website/src/components/Servicing.astro b/packages/website/src/components/Servicing.astro deleted file mode 100644 index 03b49c172..000000000 --- a/packages/website/src/components/Servicing.astro +++ /dev/null @@ -1,14 +0,0 @@ ---- -import { Heading, Link, Text } from '@radix-ui/themes'; -import { PageWrapper } from './PageWrapper'; ---- - - - BlitzKit down for servicing - - - Join the official Discord server - - for updates. - - diff --git a/packages/website/src/components/SmallTankIcon.tsx b/packages/website/src/components/SmallTankIcon.tsx deleted file mode 100644 index 9bf36d757..000000000 --- a/packages/website/src/components/SmallTankIcon.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { asset } from '@blitzkit/core'; - -interface SmallTankIconProps { - id: number; - size?: number; -} - -const WIDTH = 70; -const HEIGHT = 24; - -export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) { - return ( -
- {`Tank -
- ); -} diff --git a/packages/website/src/components/SmartCanvas.tsx b/packages/website/src/components/SmartCanvas.tsx deleted file mode 100644 index 6dd23e46b..000000000 --- a/packages/website/src/components/SmartCanvas.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Canvas, type CanvasProps } from "@react-three/fiber"; -import { forwardRef, useImperativeHandle, useRef } from "react"; -import { useOnScreen } from "../hooks/useOnScreen"; - -export const SmartCanvas = forwardRef( - (props, ref) => { - const canvas = useRef(null!); - const onScreen = useOnScreen(canvas); - - useImperativeHandle(ref, () => canvas.current!, []); - - return ( - - ); - } -); diff --git a/packages/website/src/components/Speedometer.tsx b/packages/website/src/components/Speedometer.tsx deleted file mode 100644 index 02680ca7d..000000000 --- a/packages/website/src/components/Speedometer.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Flex, type FlexProps, Text } from '@radix-ui/themes'; -import type { ReactNode } from 'react'; - -type SpeedometerProps = FlexProps & { - value: ReactNode; - color: string; - fill: number; - label: ReactNode; -}; - -export function Speedometer({ - value, - color, - fill, - label, - ...props -}: SpeedometerProps) { - return ( - - - - - - - - - {value} - - - {label} - - - ); -} diff --git a/packages/website/src/components/StickyColumnHeaderCell.tsx b/packages/website/src/components/StickyColumnHeaderCell.tsx deleted file mode 100644 index 462739fdc..000000000 --- a/packages/website/src/components/StickyColumnHeaderCell.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Table } from '@radix-ui/themes'; -import type { ColumnHeaderCellProps } from '@radix-ui/themes/src/components/table.js'; - -interface StickyColumnHeaderCellProps extends ColumnHeaderCellProps { - top?: number | string; -} - -export function StickyColumnHeaderCell({ - style, - top = 0, - ...props -}: StickyColumnHeaderCellProps) { - return ( - - ); -} diff --git a/packages/website/src/components/StickyRowHeaderCell.tsx b/packages/website/src/components/StickyRowHeaderCell.tsx deleted file mode 100644 index d7f6a300c..000000000 --- a/packages/website/src/components/StickyRowHeaderCell.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Table } from "@radix-ui/themes"; -import type { RowHeaderCellProps } from "@radix-ui/themes/src/components/table.js"; - -interface Props extends RowHeaderCellProps { - left?: string | number; -} - -export function StickyRowHeaderCell({ style, left = 0, ...props }: Props) { - return ( - - ); -} diff --git a/packages/website/src/components/StickyTableRoot.tsx b/packages/website/src/components/StickyTableRoot.tsx deleted file mode 100644 index d0b8e2cdc..000000000 --- a/packages/website/src/components/StickyTableRoot.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Table } from '@radix-ui/themes'; -import type { RootProps } from '@radix-ui/themes/src/components/table.js'; -import { useEffect, useRef } from 'react'; - -export function StickyTableRoot({ style, ...props }: RootProps) { - const table = useRef(null); - - useEffect(() => { - table.current - ?.querySelector('table') - ?.style.setProperty('overflow', 'auto'); - }, []); - - return ( - - ); -} diff --git a/packages/website/src/components/StupidCat.tsx b/packages/website/src/components/StupidCat.tsx deleted file mode 100644 index 86c5be34d..000000000 --- a/packages/website/src/components/StupidCat.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function StupidCat() { - return ; -} diff --git a/packages/website/src/components/TankCard/index.css b/packages/website/src/components/TankCard/index.css deleted file mode 100644 index 6cc92e4a2..000000000 --- a/packages/website/src/components/TankCard/index.css +++ /dev/null @@ -1,43 +0,0 @@ -.tank-search-card { - user-select: none; - display: flex; - flex-direction: column; - align-items: center; - gap: var(--space-2); - background-position: top left; - background-size: contain; - background-repeat: no-repeat; - overflow: hidden; -} - -.tank-search-card[data-provide-link='true'] { - cursor: pointer; -} - -.tank-search-card .link { - width: 100%; - display: flex; - flex-direction: column; - color: inherit; -} - -.tank-search-card .image { - width: 100%; - height: 8rem; - object-position: center right; - object-fit: contain; - filter: drop-shadow(-1px -1px 4px black); -} - -.tank-search-card .class-icon { - width: 1em; - height: 1em; - min-width: 1em; - min-height: 1em; -} - -.tank-search-card .name { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} diff --git a/packages/website/src/components/TankCard/index.tsx b/packages/website/src/components/TankCard/index.tsx deleted file mode 100644 index 64eece530..000000000 --- a/packages/website/src/components/TankCard/index.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { - asset, - fetchTankDefinitions, - TankType, - type TankDefinition, -} from "@blitzkit/core"; -import { Flex, Text, type TextProps } from "@radix-ui/themes"; -import { uniq } from "lodash-es"; -import { forwardRef, type ReactNode } from "react"; -import { useLocale } from "../../hooks/useLocale"; -import { TankopediaPersistent } from "../../stores/tankopediaPersistent"; -import { classIcons } from "../ClassIcon"; -import { LinkI18n } from "../LinkI18n"; -import { MAX_RECENTLY_VIEWED } from "../TankSearch/constants"; -import "./index.css"; - -type TankCardProps = TextProps & { - tank: TankDefinition; - onTankSelect?: (tank: TankDefinition) => void; - discriminator?: ReactNode; - noLink?: boolean; -}; - -const tankDefinitions = await fetchTankDefinitions(); - -export const TankCard = forwardRef( - ( - { - tank, - discriminator, - onTankSelect: onSelect, - noLink, - style, - ...props - }: TankCardProps, - ref, - ) => { - const { unwrap, locale } = useLocale(); - const provideLink = !noLink && onSelect === undefined; - - const Icon = classIcons[tank.class]; - - return ( - { - onSelect?.(tank); - TankopediaPersistent.mutate((draft) => { - draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed]) - .filter((id) => id in tankDefinitions.tanks) - .slice(0, MAX_RECENTLY_VIEWED); - }); - }} - className="tank-search-card" - data-provide-link={provideLink} - style={{ - backgroundImage: `url(${asset(`flags/scratched/${tank.nation}.webp`)})`, - ...style, - }} - {...props} - > - { - if (!provideLink) event.preventDefault(); - }} - > - {unwrap(tank.name!)} - - - - - {unwrap(tank.name!)} - - - - {discriminator && ( - - {discriminator} - - )} - - - ); - }, -); diff --git a/packages/website/src/components/TankRowHeaderCell.tsx b/packages/website/src/components/TankRowHeaderCell.tsx deleted file mode 100644 index 75cf846de..000000000 --- a/packages/website/src/components/TankRowHeaderCell.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { type TankDefinition, tankIcon, TankType } from "@blitzkit/core"; -import { Box, Flex } from "@radix-ui/themes"; -import { useLocale } from "../hooks/useLocale"; -import { classIcons } from "./ClassIcon"; -import { ExperimentIcon } from "./ExperimentIcon"; -import { LinkI18n } from "./LinkI18n"; -import { StickyRowHeaderCell } from "./StickyRowHeaderCell"; - -interface TankRowHeaderCellProps { - tank: TankDefinition; -} - -export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) { - const { locale, unwrap } = useLocale(); - const Icon = classIcons[tank.class]; - - return ( - - - - - - - - {tank.testing && ( - - )} - - {unwrap(tank.name!)} - - - - ); -} diff --git a/packages/website/src/components/TankSearch/components/Card.tsx b/packages/website/src/components/TankSearch/components/Card.tsx deleted file mode 100644 index 8eb75f918..000000000 --- a/packages/website/src/components/TankSearch/components/Card.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { - normalizeBoundingBox, - resolveDpm, - unionBoundingBox, - type TankDefinition, -} from "@blitzkit/core"; -import { useMemo } from "react"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; -import { resolveReload } from "../../../core/blitzkit/resolveReload"; -import { TankSort } from "../../../stores/tankopediaSort"; -import { TankCard } from "../../TankCard"; - -interface TankSearchCardProps { - tank: TankDefinition; - onSelect?: (tank: TankDefinition) => void; -} - -const modelDefinitions = await awaitableModelDefinitions; - -export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) { - const by = TankSort.use((state) => state.by); - const discriminator = useMemo(() => { - if (by.startsWith("meta")) return undefined; - - const turret = tank.turrets.at(-1)!; - const gun = turret.guns.at(-1)!; - const shell0 = gun.shells[0]; - const shell1 = gun.shells[1]; - const shell2 = gun.shells[2]; - const tracks = tank.tracks.at(-1)!; - const engine = tank.engines.at(-1)!; - const tankModelDefinition = modelDefinitions.models[tank.id]; - const turretModelDefinition = tankModelDefinition.turrets[turret.id]; - const gunModelDefinition = turretModelDefinition.guns[gun.id]; - - switch (by) { - case "fire.aimTime": - return gun.aim_time.toFixed(2); - case "fire.caliber": - return shell0.caliber.toFixed(0); - case "fire.damage": - return shell0.armor_damage.toFixed(0); - case "fire.moduleDamage": - return shell0.module_damage.toFixed(0); - case "fire.dispersionMoving": - return tracks.dispersion_move.toFixed(3); - case "fire.dispersionStill": - return gun.dispersion_base.toFixed(3); - case "fire.dpm": - return resolveDpm(gun, shell0).toFixed(0); - case "fire.dpmPremium": - return shell1 ? resolveDpm(gun, shell1).toFixed(0) : "--"; - case "fire.reload": - return resolveReload(gun).toFixed(2); - case "fire.standardPenetration": - return shell0?.penetration!.near.toFixed(0); - case "fire.premiumPenetration": - return shell1 ? shell1?.penetration!.near.toFixed(0) : "--"; - case "fire.tertiaryPenetration": - return shell1 ? shell2?.penetration!.near.toFixed(0) : "--"; - case "fire.shellVelocity": - return shell0.velocity.toFixed(0); - case "fire.shellCapacity": - return gun.shell_capacity.toFixed(0); - case "fire.shellRange": - return shell0.range.toFixed(0); - case "fire.gunDepression": - return ( - gunModelDefinition.pitch!.max + - (tankModelDefinition.initial_turret_rotation?.pitch ?? 0) - ).toFixed(1); - case "fire.gunElevation": - return ( - -gunModelDefinition.pitch!.min - - (tankModelDefinition.initial_turret_rotation?.pitch ?? 0) - ).toFixed(1); - case "maneuverability.forwardsSpeed": - return tank.speed_forwards.toFixed(0); - case "maneuverability.backwardsSpeed": - return tank.speed_backwards.toFixed(0); - case "maneuverability.power": - return engine.power.toFixed(0); - case "maneuverability.powerToWeight": - return ( - (1000 * engine.power) / - (tank.weight + - engine.weight + - tracks.weight + - turret.weight + - gun.weight) - ).toFixed(1); - case "maneuverability.weight": - return ( - (tank.weight + - engine.weight + - tracks.weight + - turret.weight + - gun.weight) / - 1000 - ).toFixed(1); - case "maneuverability.traverseSpeed": - return tracks.traverse_speed.toFixed(1); - case "survivability.health": - return (tank.health + turret.health).toFixed(0); - case "survivability.viewRange": - return turret.view_range.toFixed(0); - case "survivability.camouflageStill": - return (tank.camouflage_still * 100).toFixed(0); - case "survivability.camouflageMoving": - return (tank.camouflage_moving * 100).toFixed(0); - case "survivability.camouflageShooting": - return (tank.camouflage_still * gun.camouflage_loss * 100).toFixed(0); - case "survivability.volume": { - const dimensions = normalizeBoundingBox( - unionBoundingBox( - tankModelDefinition.bounding_box!, - turretModelDefinition.bounding_box!, - ), - ); - - return (dimensions.x * dimensions.y * dimensions.z).toFixed(0); - } - - case "survivability.length": { - const bounds = normalizeBoundingBox( - unionBoundingBox( - tankModelDefinition.bounding_box!, - turretModelDefinition.bounding_box!, - ), - ); - - return Math.max(bounds.x, bounds.y, bounds.z).toFixed(0); - } - } - }, [by]); - - return ( - - ); -} diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx deleted file mode 100644 index 645e8f73c..000000000 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ /dev/null @@ -1,1244 +0,0 @@ -import { - asset, - GunDefinition, - ShellType, - TANK_CLASSES, - TANK_TYPES, - TankType, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import locales from "@blitzkit/i18n/locales.json"; -import { - LockClosedIcon, - LockOpen2Icon, - ResetIcon, - TrashIcon, -} from "@radix-ui/react-icons"; -import { - Box, - Button, - DropdownMenu, - Flex, - IconButton, - Text, - Tooltip, - type FlexProps, -} from "@radix-ui/themes"; -import { times } from "lodash-es"; -import { Fragment, type ComponentProps, type ReactNode } from "react"; -import { awaitableConsumableDefinitions } from "../../../core/awaitables/consumableDefinitions"; -import { awaitableGameDefinitions } from "../../../core/awaitables/gameDefinitions"; -import { awaitableProvisionDefinitions } from "../../../core/awaitables/provisionDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { useLocale } from "../../../hooks/useLocale"; -import { App } from "../../../stores/app"; -import { TankFilters, type CaseType } from "../../../stores/tankFilters"; -import { classIcons } from "../../ClassIcon"; -import { GunAutoloaderIcon } from "../../GunAutoloaderIcon"; -import { GunAutoreloaderIcon } from "../../GunAutoreloaderIcon"; -import { GunRegularIcon } from "../../GunRegularIcon"; -import { MissingShellIcon } from "../../MissingShellIcon"; -import { ResearchedIcon } from "../../ResearchedIcon"; -import { ScienceIcon } from "../../ScienceIcon"; -import { ScienceOffIcon } from "../../ScienceOffIcon"; - -const gameDefinitions = await awaitableGameDefinitions; -const consumableDefinitions = await awaitableConsumableDefinitions; -const provisionDefinitions = await awaitableProvisionDefinitions; -const tankDefinitions = await awaitableTankDefinitions; - -const gameModeRoleSets: Record> = {}; - -for (const tankIdString in tankDefinitions.tanks) { - const gameMode = tankDefinitions.tanks[tankIdString]; - - for (const gameModeIdString in gameMode.roles) { - const role = gameMode.roles[gameModeIdString]; - const gameModeId = Number(gameModeIdString); - - if (gameModeId in gameModeRoleSets) { - gameModeRoleSets[gameModeId].add(role); - } else { - gameModeRoleSets[gameModeId] = new Set([role]); - } - } -} - -const gameModeRoles: { - gameModeId: number; - consumables: number[]; - provisions: number[]; -}[] = []; - -const allGameModeConsumables = new Set(); -const allGameModeProvisions = new Set(); - -for (const gameModeIdString in gameModeRoleSets) { - const gameModeId = Number(gameModeIdString); - const roles = gameModeRoleSets[gameModeId]; - const consumables = new Set(); - const provisions = new Set(); - - for (const roleId of roles) { - const role = gameDefinitions.roles[roleId]; - - outerLoop: for (const id of role.consumables) { - const name = - consumableDefinitions.consumables[id].name!.locales[locales.default]; - - for (const otherConsumable of consumables.values()) { - const otherName = - consumableDefinitions.consumables[otherConsumable].name!.locales[ - locales.default - ]; - - if (name === otherName) continue outerLoop; - } - - consumables.add(id); - allGameModeConsumables.add(id); - } - - outerLoop: for (const id of role.provisions) { - const name = - provisionDefinitions.provisions[id].name!.locales[locales.default]; - - for (const otherProvision of provisions.values()) { - const otherName = - provisionDefinitions.provisions[otherProvision].name!.locales[ - locales.default - ]; - - if (name === otherName) continue outerLoop; - } - - provisions.add(id); - allGameModeProvisions.add(id); - } - } - - gameModeRoles.push({ - gameModeId: gameModeId, - consumables: Array.from(consumables.values()), - provisions: Array.from(provisions.values()), - }); -} - -const consumablesArray = Object.values(consumableDefinitions.consumables) - .filter( - (consumable) => - !consumable.game_mode_exclusive && - consumable.name!.locales[locales.default], - ) - .map((consumable) => consumable.id); -const provisionsArray = Array.from( - Object.values(provisionDefinitions.provisions) - .filter( - (provision) => - !provision.game_mode_exclusive && - provision.name!.locales[locales.default], - ) - .reduce((uniqueMap, consumable) => { - if (!uniqueMap.has(consumable.name!.locales[locales.default])) { - uniqueMap.set(consumable.name!.locales[locales.default], consumable.id); - } - - return uniqueMap; - }, new Map()) - .values(), -); -const shellTypeIcons: Record = { - [ShellType.SHELL_TYPE_AP]: "ap", - [ShellType.SHELL_TYPE_APCR]: "ap_cr", - [ShellType.SHELL_TYPE_HE]: "he", - [ShellType.SHELL_TYPE_HEAT]: "hc", -}; - -const GUN_TYPE_ICONS: Record< - CaseType, - (props: ComponentProps<"svg">) => ReactNode -> = { - regular: GunRegularIcon, - auto_loader: GunAutoloaderIcon, - auto_reloader: GunAutoreloaderIcon, -}; - -const TANK_TYPE_COLORS: Record = { - [TankType.TANK_TYPE_RESEARCHABLE]: "gray", - [TankType.TANK_TYPE_PREMIUM]: "amber", - [TankType.TANK_TYPE_COLLECTOR]: "blue", -}; - -const GUN_TYPES = Object.keys( - GUN_TYPE_ICONS, -) as (keyof typeof GUN_TYPE_ICONS)[]; - -const MAX_ICONS = 4; - -const TIERS = times(10, (i) => 10 - i); - -export function FilterControl() { - return ( - - - - - - - - - - - - - - - - - - ); -} - -function ResetButton() { - return ( - { - TankFilters.set(TankFilters.initial); - }} - > - - - ); -} - -function TiersFilter() { - const { strings } = useLocale(); - const tiersRaw = TankFilters.use((state) => state.tiers); - const tiers = tiersRaw.length === 0 ? TIERS : tiersRaw; - - return ( - - - - - - - {TIERS.map((tier) => { - const selected = tiersRaw.includes(tier); - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.tiers = draft.tiers.filter((t) => t !== tier); - } else { - draft.tiers = [...draft.tiers, tier].sort((a, b) => b - a); - } - }); - }} - checked={selected} - key={tier} - > - {literals(strings.website.common.tank_search.tier, { - tier: TIER_ROMAN_NUMERALS[tier], - })} - - ); - })} - - - - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - draft.tiers = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} - -function NationsFilter() { - const rawNations = TankFilters.use((state) => state.nations); - const nations = - rawNations.length === 0 ? gameDefinitions.nations : rawNations; - const { strings } = useLocale(); - - return ( - - - - - - - {gameDefinitions.nations.map((nation) => { - const selected = rawNations.includes(nation); - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.nations = draft.nations.filter((n) => n !== nation); - } else { - draft.nations = [...draft.nations, nation]; - } - }); - }} - checked={selected} - key={nation} - style={{ - position: "relative", - }} - > - - - - - - { - strings.common.nations[ - nation as keyof typeof strings.common.nations - ] - } - - - ); - })} - - - - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - draft.nations = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} - -function ClassFilter() { - const { strings } = useLocale(); - const rawClasses = TankFilters.use((state) => state.classes); - const classes = rawClasses.length === 0 ? TANK_CLASSES : rawClasses; - - return ( - - - - - - - {TANK_CLASSES.map((tankClass) => { - const selected = rawClasses.includes(tankClass); - const Icon = classIcons[tankClass]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.classes = draft.classes.filter( - (c) => c !== tankClass, - ); - } else { - draft.classes = [...draft.classes, tankClass]; - } - }); - }} - checked={selected} - > - - - {strings.common.tank_class_medium[tankClass]} - - ); - })} - - - - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - draft.classes = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} - -function GunTypeFilter() { - const rawGunTypes = TankFilters.use((state) => state.gunType); - const gunTypes = rawGunTypes.length === 0 ? GUN_TYPES : rawGunTypes; - const { strings } = useLocale(); - - return ( - - - - - - - {GUN_TYPES.map((gunType) => { - const selected = rawGunTypes.includes(gunType); - const Icon = GUN_TYPE_ICONS[gunType]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.gunType = draft.gunType.filter((c) => c !== gunType); - } else { - draft.gunType = [...draft.gunType, gunType]; - } - }); - }} - checked={selected} - key={gunType} - > - - - {strings.common.gun_types[gunType]} - - ); - })} - - - - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - draft.gunType = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} - -function TypeFilter() { - const { strings } = useLocale(); - const rawTypes = TankFilters.use((state) => state.types); - const types = rawTypes.length === 0 ? TANK_TYPES : rawTypes; - - return ( - - - - - - - {TANK_TYPES.map((tankType) => { - const selected = rawTypes.includes(tankType); - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.types = draft.types.filter((c) => c !== tankType); - } else { - draft.types = [...draft.types, tankType]; - } - }); - }} - checked={selected} - key={tankType} - > - - - {strings.common.tree_type[tankType]} - - ); - })} - - - - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - draft.types = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} - -function ShellFilter() { - return ( - - - - - - ); -} - -function IndividualShellFilter({ - index, - premium, -}: { - index: number; - premium?: boolean; -}) { - const { strings } = useLocale(); - const shells = TankFilters.use((state) => state.shells); - - return ( - - - - {shells[index] === null && ( - - - - )} - {shells[index] !== null && ( - - )} - - - - - - {Object.values(ShellType).map((shellType) => { - if (typeof shellType === "string") return null; - - const selected = shells[index] === shellType; - - return ( - { - event.preventDefault(); - - const mutated = [...shells] as TankFilters["shells"]; - - mutated[index] = selected ? null : shellType; - - TankFilters.mutate((draft) => { - draft.shells = mutated; - }); - }} - color={selected ? undefined : "gray"} - > - - - { - strings.common.shells[ - shellTypeIcons[ - shellType - ] as keyof typeof strings.common.shells - ] - } - - ); - })} - - - - { - event.preventDefault(); - - const mutated = [...shells] as TankFilters["shells"]; - - mutated[index] = null; - - TankFilters.mutate((draft) => { - draft.shells = mutated; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - - ); -} - -function OwnershipFilter() { - const wargaming = App.use((state) => state.logins.wargaming); - const { strings } = useLocale(); - - return wargaming ? ( - - ) : ( - - - - ); -} - -function OwnershipFilterInternal(props: FlexProps) { - const wargaming = App.use((state) => state.logins.wargaming); - const showOwned = TankFilters.use((state) => state.showOwned); - const showUnowned = TankFilters.use((state) => state.showUnowned); - - return ( - - { - TankFilters.mutate((draft) => { - draft.showOwned = !draft.showOwned; - }); - }} - > - - - - { - TankFilters.mutate((draft) => { - draft.showUnowned = !draft.showUnowned; - }); - }} - > - - - - ); -} - -function TestFilter() { - const showTesting = TankFilters.use((state) => state.showTesting); - const showNonTesting = TankFilters.use((state) => state.showNonTesting); - - return ( - - { - TankFilters.mutate((draft) => { - draft.showNonTesting = !draft.showNonTesting; - }); - }} - > - - - - { - TankFilters.mutate((draft) => { - draft.showTesting = !draft.showTesting; - }); - }} - > - - - - ); -} - -function ConsumablesFilter() { - const { unwrap, strings } = useLocale(); - const rawConsumables = TankFilters.use((state) => state.consumables); - const consumables = - rawConsumables.length === 0 ? consumablesArray : rawConsumables; - - return ( - - - - - - - {consumablesArray.map((consumable) => { - const selected = rawConsumables.includes(consumable); - const consumableDefinition = - consumableDefinitions.consumables[consumable]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.consumables = draft.consumables.filter( - (n) => n !== consumable, - ); - } else { - draft.consumables = [...draft.consumables, consumable]; - } - }); - }} - checked={selected} - key={consumable} - > - - - {unwrap(consumableDefinition.name!)} - - ); - })} - - - - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - draft.consumables = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} - -function ProvisionsFilter() { - const { unwrap, strings } = useLocale(); - const rawProvisions = TankFilters.use((state) => state.provisions); - const provisions = - rawProvisions.length === 0 ? provisionsArray : rawProvisions; - - return ( - - - - - - - {provisionsArray.map((provision) => { - const selected = rawProvisions.includes(provision); - const provisionDefinition = - provisionDefinitions.provisions[provision]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.provisions = draft.provisions.filter( - (n) => n !== provision, - ); - } else { - draft.provisions = [...draft.provisions, provision]; - } - }); - }} - checked={selected} - key={provision} - > - - - {unwrap(provisionDefinition.name!)} - - ); - })} - - - - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - draft.provisions = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} - -function GameModeAbilitiesFilter() { - const { unwrap, strings } = useLocale(); - const rawAbilities = TankFilters.use((state) => state.abilities); - const rawPowers = TankFilters.use((state) => state.powers); - - const abilities = - rawAbilities.length === 0 && rawPowers.length === 0 - ? Array.from(allGameModeConsumables.values()) - : rawAbilities; - const powers = - rawPowers.length === 0 && rawAbilities.length === 0 - ? Array.from(allGameModeProvisions.values()) - : rawPowers; - - const icons = [ - ...abilities.map((ability) => `icons/consumables/${ability}.webp`), - ...powers.map((power) => `icons/provisions/${power}.webp`), - ]; - - return ( - - - - - - - {gameModeRoles.map(({ gameModeId, consumables, provisions }) => { - const gameMode = gameDefinitions.gameModes[gameModeId]; - const hasNeither = - consumables.length === 0 && provisions.length === 0; - - if (hasNeither) return null; - - const hasBoth = consumables.length > 0 && provisions.length > 0; - - return ( - - {consumables.length > 0 && ( - - {hasBoth - ? literals(strings.website.common.tank_search.active, { - name: unwrap(gameMode.name!), - }) - : unwrap(gameMode.name!)} - - )} - - {consumables.map((consumable) => { - { - const selected = rawAbilities.includes(consumable); - const abilityDefinition = - consumableDefinitions.consumables[consumable]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.abilities = draft.abilities.filter( - (n) => n !== consumable, - ); - } else { - draft.abilities = [...draft.abilities, consumable]; - } - }); - }} - checked={selected} - key={consumable} - > - - - {unwrap(abilityDefinition.name!)} - - ); - } - })} - - {provisions.length > 0 && ( - - {hasBoth - ? literals(strings.website.common.tank_search.passive, { - name: unwrap(gameMode.name!), - }) - : unwrap(gameMode.name!)} - - )} - - {provisions.map((provision) => { - { - const selected = rawPowers.includes(provision); - const powerDefinition = - provisionDefinitions.provisions[provision]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.powers = draft.powers.filter( - (n) => n !== provision, - ); - } else { - draft.powers = [...draft.powers, provision]; - } - }); - }} - checked={selected} - key={provision} - > - - - {unwrap(powerDefinition.name!)} - - ); - } - })} - - ); - })} - - - - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - draft.abilities = []; - draft.powers = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} diff --git a/packages/website/src/components/TankSearch/components/NoResults.tsx b/packages/website/src/components/TankSearch/components/NoResults.tsx deleted file mode 100644 index 2d5cc6d91..000000000 --- a/packages/website/src/components/TankSearch/components/NoResults.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Flex, Link, Text } from "@radix-ui/themes"; -import { useLocale } from "../../../hooks/useLocale"; -import { TankFilters } from "../../../stores/tankFilters"; - -interface NoResultsProps { - type?: "filters" | "search"; -} - -export function NoResults({ type = "filters" }: NoResultsProps) { - const { strings } = useLocale(); - - return ( - - - {strings.website.common.tank_search.no_tanks_found.body}{" "} - { - TankFilters.set(TankFilters.initial); - }} - > - {type === "filters" - ? strings.website.common.tank_search.no_tanks_found.clear_filters - : strings.website.common.tank_search.no_tanks_found.search_again} - - - - ); -} diff --git a/packages/website/src/components/TankSearch/components/QuickLink.tsx b/packages/website/src/components/TankSearch/components/QuickLink.tsx deleted file mode 100644 index 014990cb6..000000000 --- a/packages/website/src/components/TankSearch/components/QuickLink.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { type TankDefinition } from "@blitzkit/core"; -import { CaretRightIcon } from "@radix-ui/react-icons"; -import { Button, TextField } from "@radix-ui/themes"; -import { useLocale } from "../../../hooks/useLocale"; -import { TankFilters } from "../../../stores/tankFilters"; -import { LinkI18n } from "../../LinkI18n"; - -interface QuickLinkProps { - topResult?: TankDefinition; -} - -export function QuickLink({ topResult }: QuickLinkProps) { - const { locale, unwrap } = useLocale(); - const search = TankFilters.use((state) => state.search); - const searching = TankFilters.use((state) => state.searching); - - if (!search || !topResult || searching) return null; - - return ( - - - - - - ); -} diff --git a/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx b/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx deleted file mode 100644 index da71d5966..000000000 --- a/packages/website/src/components/TankSearch/components/RecentlyViewed.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Flex, Separator, Text } from "@radix-ui/themes"; -import { isEqual } from "lodash-es"; -import { useMemo } from "react"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { useLocale } from "../../../hooks/useLocale"; -import { TankFilters } from "../../../stores/tankFilters"; -import { TankopediaPersistent } from "../../../stores/tankopediaPersistent"; -import { TankSort } from "../../../stores/tankopediaSort"; -import { TankCard } from "../../TankCard"; -import { TankCardWrapper } from "./TankCardWrapper"; - -const tankDefinitions = await awaitableTankDefinitions; - -export function RecentlyViewed() { - const filters = TankFilters.use(); - // non-reactive because it is a little weird that it updates instantly even before the page loads - const recentlyViewed = TankopediaPersistent.state.recentlyViewed.filter( - (id) => id in tankDefinitions.tanks - ); - const hasFilters = useMemo( - () => - Object.entries(filters).some(([key, value]) => { - return !isEqual( - value, - TankFilters.initial[key as keyof typeof TankFilters.initial] - ); - }), - [filters] - ); - const { strings } = useLocale(); - const by = TankSort.use((state) => state.by); - - if (recentlyViewed.length === 0 || by !== "meta.none" || hasFilters) { - return null; - } - - return ( - - - {strings.website.common.tank_search.recent} - - - {recentlyViewed.map((id) => ( - - ))} - - - - - ); -} diff --git a/packages/website/src/components/TankSearch/components/SearchBar.tsx b/packages/website/src/components/TankSearch/components/SearchBar.tsx deleted file mode 100644 index 32eba599f..000000000 --- a/packages/website/src/components/TankSearch/components/SearchBar.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import type { TankDefinition } from "@blitzkit/core"; -import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; -import { Flex, Spinner, TextField } from "@radix-ui/themes"; -import { debounce } from "lodash-es"; -import { type KeyboardEventHandler, useCallback, useRef } from "react"; -import { useLocale } from "../../../hooks/useLocale"; -import { TankFilters } from "../../../stores/tankFilters"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; -import { QuickLink } from "./QuickLink"; -import { Sort } from "./Sort"; - -type SearchBarProps = MaybeSkeletonComponentProps & { - topResult?: TankDefinition; - onSelect?: (tank: TankDefinition) => void; -}; - -export function SearchBar({ topResult, skeleton, onSelect }: SearchBarProps) { - const { strings } = useLocale(); - const search = TankFilters.use((state) => state.search); - const searching = TankFilters.use((state) => state.searching); - const input = useRef(null); - const performSearch = useCallback( - debounce(() => { - TankFilters.mutate((draft) => { - draft.searching = false; - }); - - if (!input.current) return; - - const sanitized = input.current.value.trim(); - - TankFilters.mutate((draft) => { - draft.search = sanitized.length === 0 ? null : sanitized; - }); - }, 500), - [] - ); - const handleChange = useCallback(() => { - if (!searching) { - TankFilters.mutate((draft) => { - draft.searching = true; - }); - } - - performSearch(); - }, [searching]); - const handleKeyDown = useCallback( - (event) => { - if (event.key !== "Enter" || !topResult || searching) return; - - event.preventDefault(); - - if (onSelect) { - onSelect(topResult); - } else { - window.location.href = `/tanks/${topResult.slug}`; - } - }, - [topResult] - ); - - return ( - - - - - {searching ? : } - - - - - - - - - ); -} diff --git a/packages/website/src/components/TankSearch/components/SkeletonTankCard.tsx b/packages/website/src/components/TankSearch/components/SkeletonTankCard.tsx deleted file mode 100644 index 69f6804ed..000000000 --- a/packages/website/src/components/TankSearch/components/SkeletonTankCard.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Flex, Skeleton } from "@radix-ui/themes"; -import { memo, useRef } from "react"; -import { useIntersection } from "../../../hooks/useIntersection"; - -interface SkeletonTankCardProps { - onIntersection?: () => void; -} - -export const SkeletonTankCard = memo( - ({ onIntersection }: SkeletonTankCardProps) => { - const card = useRef(null!); - - useIntersection(() => onIntersection?.(), card, { - disabled: onIntersection === undefined, - }); - - return ( - - - - - ); - }, - () => true -); diff --git a/packages/website/src/components/TankSearch/components/Sort.tsx b/packages/website/src/components/TankSearch/components/Sort.tsx deleted file mode 100644 index 71898443d..000000000 --- a/packages/website/src/components/TankSearch/components/Sort.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { CaretDownIcon } from '@radix-ui/react-icons'; -import { Button, DropdownMenu } from '@radix-ui/themes'; -import { useLocale } from '../../../hooks/useLocale'; -import type { - TankopediaSortBy, - TankopediaSortDirection, -} from '../../../stores/tankopediaPersistent'; -import { TankSort } from '../../../stores/tankopediaSort'; - -interface ItemProps { - by: TankopediaSortBy; -} - -interface ItemProps { - by: TankopediaSortBy; -} - -function Item({ by }: ItemProps) { - const { strings } = useLocale(); - const _by = TankSort.use((state) => state.by); - - return ( - { - TankSort.mutate((draft) => { - draft.by = by; - }); - }} - > - {strings.website.common.tank_search.sort[by]} - - ); -} - -export function Sort() { - const direction = TankSort.use((state) => state.direction); - const { strings } = useLocale(); - - return ( - - - - - - - - {strings.website.common.tank_search.sort_dropdown.groups.sort.label} - - - - - - - {strings.website.common.tank_search.sort_dropdown.groups.sort.fire} - - - - - - - - - - - - - - - - - - - - - - - - - { - strings.website.common.tank_search.sort_dropdown.groups.sort - .maneuverability - } - - - - - - - - - - - - - - { - strings.website.common.tank_search.sort_dropdown.groups.sort - .survivability - } - - - - - - - - - - - - - - { - strings.website.common.tank_search.sort_dropdown.groups.direction - .label - } - - { - TankSort.mutate((draft) => { - draft.direction = value as TankopediaSortDirection; - }); - }} - > - - { - strings.website.common.tank_search.sort_dropdown.groups.direction - .ascending - } - - - { - strings.website.common.tank_search.sort_dropdown.groups.direction - .descending - } - - - - - ); -} diff --git a/packages/website/src/components/TankSearch/components/TankCardWrapper.tsx b/packages/website/src/components/TankSearch/components/TankCardWrapper.tsx deleted file mode 100644 index 462c55995..000000000 --- a/packages/website/src/components/TankSearch/components/TankCardWrapper.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Grid, type GridProps } from "@radix-ui/themes"; - -export function TankCardWrapper(props: GridProps) { - return ( - - ); -} diff --git a/packages/website/src/components/TankSearch/components/TierFilter.tsx b/packages/website/src/components/TankSearch/components/TierFilter.tsx deleted file mode 100644 index 735399ea9..000000000 --- a/packages/website/src/components/TankSearch/components/TierFilter.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; -import { Flex, IconButton, Text } from "@radix-ui/themes"; -import { times } from "lodash-es"; -import { memo } from "react"; -import { TankFilters } from "../../../stores/tankFilters"; -import { TankSort } from "../../../stores/tankopediaSort"; - -export const TierFilter = memo(() => { - const by = TankSort.use((state) => state.by); - const search = TankFilters.use((state) => state.search); - const tiers = TankFilters.use((state) => state.tiers); - - if (by !== "meta.none" || search) return null; - - return ( - - - {times(10, (index) => { - const tier = 10 - index; - const selected = tiers.includes(tier); - - return ( - { - TankFilters.mutate((draft) => { - if (tiers.includes(tier)) { - draft.tiers = tiers.filter((t) => t !== tier); - } else { - draft.tiers = [...tiers, tier]; - } - }); - }} - > - {TIER_ROMAN_NUMERALS[tier]} - - ); - })} - - - ); -}); diff --git a/packages/website/src/components/TankSearch/constants.tsx b/packages/website/src/components/TankSearch/constants.tsx deleted file mode 100644 index 8d51acff4..000000000 --- a/packages/website/src/components/TankSearch/constants.tsx +++ /dev/null @@ -1 +0,0 @@ -export const MAX_RECENTLY_VIEWED = 14; diff --git a/packages/website/src/components/TankSearch/index.tsx b/packages/website/src/components/TankSearch/index.tsx deleted file mode 100644 index 6a6513145..000000000 --- a/packages/website/src/components/TankSearch/index.tsx +++ /dev/null @@ -1,552 +0,0 @@ -import { - metaSortTank, - normalizeBoundingBox, - resolveDpm, - SEARCH_KEYS, - unionBoundingBox, - type TankDefinition, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { Callout, Flex, Link, Text, type FlexProps } from "@radix-ui/themes"; -import fuzzysort from "fuzzysort"; -import { times, uniq } from "lodash-es"; -import { memo, useEffect, useMemo, useState } from "react"; -import usePromise from "react-promise-suspense"; -import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions"; -import { awaitableModelDefinitions } from "../../core/awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; -import { awaitableTankNames } from "../../core/awaitables/tankNames"; -import { filterTanks } from "../../core/blitzkit/filterTanks"; -import { resolveReload } from "../../core/blitzkit/resolveReload"; -import { useLocale } from "../../hooks/useLocale"; -import { App } from "../../stores/app"; -import { TankFilters } from "../../stores/tankFilters"; -import { TankopediaPersistent } from "../../stores/tankopediaPersistent"; -import { SORT_UNITS } from "../../stores/tankopediaPersistent/constants"; -import { TankSort } from "../../stores/tankopediaSort"; -import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; -import { ExperimentIcon } from "../ExperimentIcon"; -import { TankSearchCard } from "./components/Card"; -import { FilterControl } from "./components/FilterControl"; -import { NoResults } from "./components/NoResults"; -import { RecentlyViewed } from "./components/RecentlyViewed"; -import { SearchBar } from "./components/SearchBar"; -import { SkeletonTankCard } from "./components/SkeletonTankCard"; -import { TankCardWrapper } from "./components/TankCardWrapper"; -import { MAX_RECENTLY_VIEWED } from "./constants"; - -export type TankSearchProps = MaybeSkeletonComponentProps & - Omit & { - compact?: boolean; - onSelect?: (tank: TankDefinition) => void; - onSelectAll?: (tanks: TankDefinition[]) => void; - }; - -const PREVIEW_COUNT = 20; -const DEFAULT_LOADED_CARDS = 64; - -const [gameDefinitions, modelDefinitions, tankDefinitions, tankNames] = - await Promise.all([ - awaitableGameDefinitions, - awaitableModelDefinitions, - awaitableTankDefinitions, - awaitableTankNames, - ]); - -export const TankSearch = memo( - ({ compact, onSelect, onSelectAll, skeleton, ...props }) => { - const { strings, locale } = useLocale(); - const wargaming = App.use((state) => state.logins.wargaming); - const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks); - const tankopediaSort = TankSort.use(); - const tankFilters = TankFilters.use(); - - const filtered = usePromise( - () => { - if (tankFilters.search === null) { - return filterTanks( - tankFilters, - awaitedTanksDefinitionsArray, - wargaming?.id, - ); - } else { - return Promise.resolve( - fuzzysort - .go(tankFilters.search, tankNames, { - keys: SEARCH_KEYS, - }) - .map((result) => tankDefinitions.tanks[result.obj.id]), - ); - } - }, - // react-promise-suspense has awful type annotations - [tankFilters] as any, - ); - const sorted = useMemo(() => { - if (tankFilters.search === null) { - let sorted: TankDefinition[]; - - switch (tankopediaSort.by) { - case "meta.none": - sorted = metaSortTank(filtered, gameDefinitions); - break; - - case "survivability.health": { - sorted = filtered.sort((a, b) => { - const aHealth = a.health + a.turrets.at(-1)!.health; - const bHealth = b.health + b.turrets.at(-1)!.health; - - return aHealth - bHealth; - }); - break; - } - - case "survivability.viewRange": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.view_range - b.turrets.at(-1)!.view_range, - ); - break; - - case "survivability.camouflageStill": - sorted = filtered.sort( - (a, b) => a.camouflage_still - b.camouflage_still, - ); - break; - - case "survivability.camouflageMoving": - sorted = filtered.sort( - (a, b) => a.camouflage_moving - b.camouflage_moving, - ); - break; - - case "survivability.camouflageShooting": - sorted = filtered.sort( - (a, b) => - a.camouflage_still * - a.turrets.at(-1)!.guns.at(-1)!.camouflage_loss - - b.camouflage_still * - b.turrets.at(-1)!.guns.at(-1)!.camouflage_loss, - ); - break; - - case "survivability.volume": - sorted = filtered.sort((a, b) => { - const aTankModelDefinition = modelDefinitions.models[a.id]; - const bTankModelDefinition = modelDefinitions.models[b.id]; - const aTurretModelDefinition = - aTankModelDefinition.turrets[a.turrets.at(-1)!.id]; - const bTurretModelDefinition = - bTankModelDefinition.turrets[b.turrets.at(-1)!.id]; - const aSize = normalizeBoundingBox( - unionBoundingBox( - aTankModelDefinition.bounding_box!, - aTurretModelDefinition.bounding_box!, - ), - ); - const bSize = normalizeBoundingBox( - unionBoundingBox( - bTankModelDefinition.bounding_box!, - bTurretModelDefinition.bounding_box!, - ), - ); - const aVolume = aSize.x * aSize.y * aSize.z; - const bVolume = bSize.x * bSize.y * bSize.z; - - return aVolume - bVolume; - }); - break; - - case "survivability.length": - sorted = filtered.sort((a, b) => { - const aTankModelDefinition = modelDefinitions.models[a.id]; - const bTankModelDefinition = modelDefinitions.models[b.id]; - const aTurretModelDefinition = - aTankModelDefinition.turrets[a.turrets.at(-1)!.id]; - const bTurretModelDefinition = - bTankModelDefinition.turrets[b.turrets.at(-1)!.id]; - const aSize = normalizeBoundingBox( - unionBoundingBox( - aTankModelDefinition.bounding_box!, - aTurretModelDefinition.bounding_box!, - ), - ); - const bSize = normalizeBoundingBox( - unionBoundingBox( - bTankModelDefinition.bounding_box!, - bTurretModelDefinition.bounding_box!, - ), - ); - const aLength = Math.max(aSize.x, aSize.y, aSize.z); - const bLength = Math.max(bSize.x, bSize.y, bSize.z); - - return aLength - bLength; - }); - break; - - case "fire.dpm": - sorted = filtered.sort( - (a, b) => - resolveDpm( - a.turrets.at(-1)!.guns.at(-1)!, - a.turrets.at(-1)!.guns.at(-1)!.shells[0], - ) - - resolveDpm( - b.turrets.at(-1)!.guns.at(-1)!, - b.turrets.at(-1)!.guns.at(-1)!.shells[0], - ), - ); - break; - - case "fire.dpmPremium": - sorted = filtered.sort( - (a, b) => - (a.turrets.at(-1)!.guns.at(-1)!.shells[1] - ? resolveDpm( - a.turrets.at(-1)!.guns.at(-1)!, - a.turrets.at(-1)!.guns.at(-1)!.shells[1], - ) - : 0) - - (b.turrets.at(-1)!.guns.at(-1)!.shells[1] - ? resolveDpm( - b.turrets.at(-1)!.guns.at(-1)!, - b.turrets.at(-1)!.guns.at(-1)!.shells[1], - ) - : 0), - ); - break; - - case "fire.reload": - sorted = filtered.sort( - (a, b) => - resolveReload(a.turrets.at(-1)!.guns.at(-1)!) - - resolveReload(b.turrets.at(-1)!.guns.at(-1)!), - ); - break; - - case "fire.caliber": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber, - ); - break; - - case "fire.standardPenetration": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near, - ); - break; - - case "fire.premiumPenetration": - sorted = filtered.sort( - (a, b) => - (a.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration!.near ?? - 0) - - (b.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration!.near ?? - 0), - ); - break; - - case "fire.tertiaryPenetration": - sorted = filtered.sort( - (a, b) => - (a.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration!.near ?? - 0) - - (b.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration!.near ?? - 0), - ); - break; - - case "fire.damage": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage, - ); - break; - - case "fire.moduleDamage": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage, - ); - break; - - case "fire.shellVelocity": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity, - ); - break; - - case "fire.shellRange": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.shells[0].range - - b.turrets.at(-1)!.guns.at(-1)!.shells[0].range, - ); - break; - - case "fire.shellCapacity": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.shell_capacity - - b.turrets.at(-1)!.guns.at(-1)!.shell_capacity, - ); - break; - - case "fire.aimTime": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.aim_time - - b.turrets.at(-1)!.guns.at(-1)!.aim_time, - ); - break; - - case "fire.dispersionStill": - sorted = filtered.sort( - (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.dispersion_base - - b.turrets.at(-1)!.guns.at(-1)!.dispersion_base, - ); - break; - - case "fire.dispersionMoving": - sorted = filtered.sort( - (a, b) => - a.tracks.at(-1)!.dispersion_move - - b.tracks.at(-1)!.dispersion_move, - ); - break; - - case "fire.gunDepression": - sorted = filtered.sort( - (a, b) => - modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id] - .guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch!.max + - (modelDefinitions.models[a.id].initial_turret_rotation?.pitch ?? - 0) - - modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id] - .guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch!.max - - (modelDefinitions.models[b.id].initial_turret_rotation?.pitch ?? - 0), - ); - break; - - case "fire.gunElevation": - sorted = filtered.sort( - (a, b) => - modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id] - .guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch!.min + - (modelDefinitions.models[b.id].initial_turret_rotation?.pitch ?? - 0) - - modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id] - .guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch!.min - - (modelDefinitions.models[a.id].initial_turret_rotation?.pitch ?? - 0), - ); - break; - - case "maneuverability.forwardsSpeed": - sorted = filtered.sort( - (a, b) => a.speed_forwards - b.speed_forwards, - ); - break; - - case "maneuverability.backwardsSpeed": - sorted = filtered.sort( - (a, b) => a.speed_backwards - b.speed_backwards, - ); - break; - - case "maneuverability.power": - sorted = filtered.sort( - (a, b) => a.engines.at(-1)!.power - b.engines.at(-1)!.power, - ); - break; - - case "maneuverability.powerToWeight": - sorted = filtered.sort( - (a, b) => - a.engines.at(-1)!.power / - (a.weight + - a.engines.at(-1)!.weight + - a.tracks.at(-1)!.weight + - a.turrets.at(-1)!.weight + - a.turrets.at(-1)!.guns.at(-1)!.weight) - - b.engines.at(-1)!.power / - (b.weight + - b.engines.at(-1)!.weight + - b.tracks.at(-1)!.weight + - b.turrets.at(-1)!.weight + - b.turrets.at(-1)!.guns.at(-1)!.weight), - ); - break; - - case "maneuverability.weight": - sorted = filtered.sort( - (a, b) => - a.weight + - a.engines.at(-1)!.weight + - a.tracks.at(-1)!.weight + - a.turrets.at(-1)!.weight + - a.turrets.at(-1)!.guns.at(-1)!.weight - - (b.weight + - b.engines.at(-1)!.weight + - b.tracks.at(-1)!.weight + - b.turrets.at(-1)!.weight + - b.turrets.at(-1)!.guns.at(-1)!.weight), - ); - break; - - case "maneuverability.traverseSpeed": - sorted = filtered.sort( - (a, b) => - a.tracks.at(-1)!.traverse_speed - - b.tracks.at(-1)!.traverse_speed, - ); - break; - } - - return tankopediaSort.direction === "ascending" - ? sorted - : sorted.reverse(); - } else { - return filtered; - } - }, [tankFilters, tankopediaSort]); - - const [loadedCards, setLoadedCards] = useState(DEFAULT_LOADED_CARDS); - const tanks = sorted.slice(0, loadedCards); - - useEffect(() => { - setLoadedCards(DEFAULT_LOADED_CARDS); - }, [tankFilters, tankopediaSort]); - - return ( - - - - {!tankFilters.search && !tankFilters.searching && } - - {!skeleton && !compact && } - - - - - {sorted.length === 1 - ? strings.website.common.tank_search.count_singular - : literals(strings.website.common.tank_search.count_plural, { - count: sorted.length.toLocaleString(locale), - })} - - - {onSelectAll && ( - { - event.preventDefault(); - onSelectAll(sorted); - TankopediaPersistent.mutate((draft) => { - draft.recentlyViewed = uniq([ - ...sorted.map(({ id }) => id), - ...draft.recentlyViewed, - ]) - .filter((id) => id in tankDefinitions.tanks) - .slice(0, MAX_RECENTLY_VIEWED); - }); - }} - > - {strings.website.common.tank_search.select_all} - - )} - - - {tankopediaSort.by !== "meta.none" && ( - - {literals(strings.website.common.tank_search.sorting_by, { - name: strings.website.common.tank_search.sort[ - tankopediaSort.by - ], - })} - {SORT_UNITS[tankopediaSort.by] === undefined - ? "" - : ` (${SORT_UNITS[tankopediaSort.by]})`} - , {tankopediaSort.direction} - - )} - - - {tankFilters.showTesting && !tankFilters.showNonTesting && ( - - - - - - - {strings.website.common.warnings.test} - - - - )} - - {!skeleton && !tankFilters.searching && ( - <> - {tanks.length > 0 && ( - - {tanks.map((tank) => ( - - ))} - - {times( - Math.min(PREVIEW_COUNT, sorted.length - loadedCards), - (index) => { - return ( - { - setLoadedCards((state) => - Math.min(state + 2, sorted.length), - ); - }} - /> - ); - }, - )} - - )} - - {tanks.length === 0 && } - - )} - - {(skeleton || tankFilters.searching) && ( - - {times( - Math.round( - skeleton ? DEFAULT_LOADED_CARDS : 10 + 10 * Math.random(), - ), - (index) => ( - - ), - )} - - )} - - ); - }, -); diff --git a/packages/website/src/components/Tankopedia/CalloutsSection.tsx b/packages/website/src/components/Tankopedia/CalloutsSection.tsx deleted file mode 100644 index 4bc97c35c..000000000 --- a/packages/website/src/components/Tankopedia/CalloutsSection.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { assertSecret, fetchTankDefinitions } from "@blitzkit/core"; -import { MinusCircledIcon } from "@radix-ui/react-icons"; -import { Callout, Flex } from "@radix-ui/themes"; -import { uniq } from "lodash-es"; -import { useEffect } from "react"; -import { Duel } from "../../stores/duel"; -import { TankopediaPersistent } from "../../stores/tankopediaPersistent"; -import { AesonPlug } from "../AesonPlug"; -import { ExperimentIcon } from "../ExperimentIcon"; -import { MAX_RECENTLY_VIEWED } from "../TankSearch/constants"; - -const tankDefinitions = await fetchTankDefinitions(); - -export function CalloutsSection() { - const tank = Duel.use((state) => state.protagonist.tank); - const promoteAeson = - assertSecret(import.meta.env.PUBLIC_BRANCH) === "preview"; - - useEffect(() => { - TankopediaPersistent.mutate((draft) => { - draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed]) - .filter((id) => id in tankDefinitions.tanks) - .slice(0, MAX_RECENTLY_VIEWED); - }); - }, [tank]); - - if (!tank.testing && !tank.deprecated && !promoteAeson) return null; - - return ( - - {promoteAeson && } - - {tank.deprecated && ( - - - - - - Deprecated tanks may display undefined behavior. - - - )} - - {tank.testing && ( - - - - - - Tanks in testing are subject to change and may not represent the - final product. - - - )} - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/AssaultRangesVisualizer/components/Cursor/index.css b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/AssaultRangesVisualizer/components/Cursor/index.css deleted file mode 100644 index bf7e4950c..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/AssaultRangesVisualizer/components/Cursor/index.css +++ /dev/null @@ -1,7 +0,0 @@ -#assault-cursor { - cursor: grab; -} - -#assault-cursor:active { - cursor: grabbing; -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/AssaultRangesVisualizer/components/Cursor/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/AssaultRangesVisualizer/components/Cursor/index.tsx deleted file mode 100644 index 88079a188..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/AssaultRangesVisualizer/components/Cursor/index.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { DragHandleDots2Icon } from "@radix-ui/react-icons"; -import { Box, Flex, Text } from "@radix-ui/themes"; -import { useEffect, useRef, useState } from "react"; -import { clamp } from "three/src/math/MathUtils.js"; -import { - fakeLogSquishyHigh, - fakeLogSquishyHighInverse, -} from "../../../../../../../../../core/blitzkit/fakeLog"; -import { Var } from "../../../../../../../../../core/radix/var"; -import { Duel } from "../../../../../../../../../stores/duel"; -import "./index.css"; - -interface Props { - maxDistance: number; - maxFakeDistance: number; -} - -export function AssaultCursor({ maxDistance, maxFakeDistance }: Props) { - const assaultDistance = Duel.use( - (state) => state.protagonist.assaultDistance - ); - const [x, setX] = useState(fakeLogSquishyHigh(assaultDistance / maxDistance)); - const cursor = useRef(null); - - useEffect(() => { - let localX = x; - let lastX = 0; - - function handlePointerDown(event: PointerEvent) { - event.preventDefault(); - - lastX = event.clientX; - - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } - - function handlePointerMove(event: PointerEvent) { - event.preventDefault(); - - const parent = cursor.current?.parentElement; - - if (!parent) return; - - const width = parent.clientWidth; - const movementX = event.clientX - lastX; - lastX = event.clientX; - - setX((state) => { - localX = clamp(state + movementX / width, 0, 1); - return localX; - }); - } - - function handlePointerUp() { - Duel.mutate((draft) => { - draft.protagonist.assaultDistance = - fakeLogSquishyHighInverse(localX * maxFakeDistance) * maxDistance; - }); - - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - } - - cursor.current?.addEventListener("pointerdown", handlePointerDown); - - return () => { - cursor.current?.removeEventListener("pointerdown", handlePointerDown); - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - }; - }, []); - - return ( - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/AssaultRangesVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/AssaultRangesVisualizer/index.tsx deleted file mode 100644 index 5fb137305..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/AssaultRangesVisualizer/index.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import type { AssaultRanges } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Box, Flex, Text } from "@radix-ui/themes"; -import { clamp } from "three/src/math/MathUtils.js"; -import { - fakeLogSquishyHigh, - fakeLogSquishyLow, -} from "../../../../../../../core/blitzkit/fakeLog"; -import type { TankCharacteristics } from "../../../../../../../core/blitzkit/tankCharacteristics"; -import { Var } from "../../../../../../../core/radix/var"; -import { useLocale } from "../../../../../../../hooks/useLocale"; -import { VisualizerCard } from "../VisualizerCard"; -import { AssaultCursor } from "./components/Cursor"; - -interface Props { - ranges: AssaultRanges; - stats: TankCharacteristics; -} - -export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) { - const maxDistance = _ranges.ranges.at(-1)!.distance; - const ranges = _ranges.ranges.map((range) => ({ - factor: range.factor, - distance: range.distance, - fakeDistance: fakeLogSquishyHigh(range.distance / maxDistance), - })); - const maxFakeDistance = ranges.at(-1)!.fakeDistance; - const maxFactor = ranges[0].factor; - - const { strings } = useLocale(); - - return ( - - - - {strings.website.tools.tankopedia.visualizers.assault.title} - - - - - - {ranges.map((range, index) => ( - - {literals(strings.common.units.hp, { - value: Math.round( - stats.damageWithoutAssault * range.factor - ), - })} - - ))} - - - - {ranges.map((range, index) => { - const mix = - clamp(2 * (range.factor / maxFactor - 1) + 1, 0, 1) * 100; - const color0 = `color-mix(in hsl, ${Var("tomato-9")}, ${Var("jade-9")} ${mix}%)`; - const color1 = `color-mix(in hsl, ${Var("tomato-7")}, ${Var("jade-7")} ${mix}%)`; - const last = ranges[index - 1]; - const lastFakeDistance = last?.fakeDistance ?? 0; - const fakeWidth = range.fakeDistance - lastFakeDistance; - const fraction = fakeWidth / ranges.at(-1)!.fakeDistance; - - return ( - - ); - })} - - {ranges.map((range, index) => ( - - ))} - - - - - - - {ranges.map((range, index) => { - const last = ranges[index - 1]; - const lastFakeDistance = last?.fakeDistance ?? 0; - const fakeWidth = range.fakeDistance - lastFakeDistance; - const fraction = fakeWidth / ranges.at(-1)!.fakeDistance; - - return ( - - - {literals(strings.common.units.m, { - value: Math.round(range.distance), - })} - - - ); - })} - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/CompareOptions.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/CompareOptions.tsx deleted file mode 100644 index 283884b7f..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/CompareOptions.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { Flex, SegmentedControl, Text, type FlexProps } from "@radix-ui/themes"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { Duel } from "../../../../../../stores/duel"; -import { - Tankopedia, - TankopediaRelativeAgainst, -} from "../../../../../../stores/tankopedia"; - -export function CompareOptions(props: FlexProps) { - const relativeAgainst = Tankopedia.use((state) => state.relativeAgainst); - const tank = Duel.use((state) => state.protagonist.tank); - const { strings } = useLocale(); - - return ( - - - {strings.website.tools.tankopedia.characteristics.compare.label} - - - { - Tankopedia.mutate((draft) => { - draft.relativeAgainst = Number(value) as TankopediaRelativeAgainst; - }); - }} - > - - {literals( - strings.website.tools.tankopedia.characteristics.compare.class, - { - tier: TIER_ROMAN_NUMERALS[tank.tier], - class: - strings.common.tank_class_short_plural_lowercase[tank.class], - } - )} - - - {literals( - strings.website.tools.tankopedia.characteristics.compare.tier, - { tier: TIER_ROMAN_NUMERALS[tank.tier] } - )} - - - {strings.website.tools.tankopedia.characteristics.compare.all} - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx deleted file mode 100644 index 471b27da0..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { CREW_MEMBER_NAMES, CrewType } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { AccessibilityIcon, InfoCircledIcon } from "@radix-ui/react-icons"; -import { Flex, Heading, IconButton, Popover, Text } from "@radix-ui/themes"; -import { Fragment } from "react/jsx-runtime"; -import { awaitableProvisionDefinitions } from "../../../../../../core/awaitables/provisionDefinitions"; -import { useEquipment } from "../../../../../../hooks/useEquipment"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { Duel } from "../../../../../../stores/duel"; -import { InfoWithDelta } from "./InfoWithDelta"; -import { StatsTableWrapper } from "./StatsTableWrapper"; -import type { StatsAcceptorProps } from "./TraverseVisualizer"; - -const provisionDefinitions = await awaitableProvisionDefinitions; - -export function Crew({ stats }: StatsAcceptorProps) { - const tank = Duel.use((state) => state.protagonist.tank); - const { strings } = useLocale(); - const provisions = Duel.use((state) => state.protagonist.provisions); - const hasImprovedVentilation = useEquipment(102); - const provisionCrewBonus = - provisions.reduce( - (total, provision) => - provisionDefinitions.provisions[provision].crew - ? total + provisionDefinitions.provisions[provision].crew! / 100 - : total, - 0, - ) + (hasImprovedVentilation ? 0.08 : 0); - const commanderMastery = 1 + provisionCrewBonus; - - return ( - - - - {strings.website.tools.tankopedia.crew.title} - - - - - - - - - - - - - {strings.website.tools.tankopedia.crew.info} - - - - - - - - {tank.crew.map((member) => { - return ( - - 1 - ? ` ${literals(strings.common.units.x, { value: member.count })}` - : "" - }`} - decimals={0} - noRanking - value={() => - (member.type === CrewType.CREW_TYPE_COMMANDER - ? commanderMastery - : commanderMastery * 1.1) * 100 - } - /> - - {member.substitute.length > 0 && ( - - - - {member.substitute - .map((sub, index) => - index === 0 - ? strings.website.tools.tankopedia.crew[ - CREW_MEMBER_NAMES[sub] - ] - : sub, - ) - .join(", ")} - - - } - noRanking - value={() => commanderMastery * 1.05 * 100} - /> - )} - - ); - })} - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx deleted file mode 100644 index 4fc066c22..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Firepower.tsx +++ /dev/null @@ -1,303 +0,0 @@ -import { asset, isExplosive } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { useTankModelDefinition } from "../../../../../../hooks/useTankModelDefinition"; -import { Duel } from "../../../../../../stores/duel"; -import type { MaybeSkeletonComponentProps } from "../../../../../../types/maybeSkeletonComponentProps"; -import { AssaultRangesVisualizer } from "./AssaultRangesVisualizer"; -import { GunFlexibilityVisualizer } from "./GunFlexibilityVisualizer"; -import { Info } from "./Info"; -import { InfoWithDelta } from "./InfoWithDelta"; -import { ReloadVisualizer } from "./ReloadVisualizer"; -import { RicochetVisualizer } from "./RicochetVisualizer"; -import { StatsTableWrapper } from "./StatsTableWrapper"; -import type { StatsAcceptorProps } from "./TraverseVisualizer"; - -export function Firepower({ - stats, - skeleton, -}: StatsAcceptorProps & MaybeSkeletonComponentProps) { - const { strings, unwrap } = useLocale(); - const turret = Duel.use((state) => state.protagonist.turret); - const gun = Duel.use((state) => state.protagonist.gun); - const shell = Duel.use((state) => state.protagonist.shell); - const tankModelDefinition = useTankModelDefinition(); - const turretModelDefinition = tankModelDefinition.turrets[turret.id]; - const gunModelDefinition = turretModelDefinition.guns[gun.id]; - - return ( - - - - {strings.website.tools.tankopedia.firepower.title} - - - - {gun.shells.map((thisShell, shellIndex) => { - const selected = thisShell.id === shell.id; - - return ( - - { - Duel.mutate((draft) => { - draft.protagonist.shell = thisShell; - }); - }} - > - {unwrap(thisShell.name!)} - - - ); - })} - - - - - {strings.common.gun_types[gun.gun_type!.$case]} - - - - {gun.gun_type!.$case !== "regular" && ( - - )} - - - - {gun.assault_ranges?.types.includes(shell.type) && ( - - )} - - {gun.gun_type!.$case === "auto_reloader" && ( - - )} - {gun.gun_type!.$case !== "regular" && ( - - )} - {gun.gun_type!.$case === "auto_reloader" ? ( - <> - - {stats.mostOptimalShellIndex! + 1} - - - {stats.shellReloads!.map((reload, index) => ( - reload} - /> - ))} - - ) : ( - - )} - {(gun.gun_type!.$case === "auto_loader" || - gun.gun_type!.$case === "auto_reloader") && ( - - )} - - - {isExplosive(shell.type) && ( - - )} - - - {!isExplosive(shell.type) && ( - <> - - - - )} - - - - - - - - - - - - - - - - - - {gunModelDefinition.pitch!.front && ( - <> - - - - )} - {gunModelDefinition.pitch!.back && ( - <> - - - - )} - {turretModelDefinition.yaw && ( - <> - - - - )} - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CameraPositioner.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CameraPositioner.tsx deleted file mode 100644 index 3243f1d7a..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CameraPositioner.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useFrame } from '@react-three/fiber'; - -export const zoom = 60; - -export function CameraPositioner() { - useFrame(({ camera, gl: { domElement } }) => { - const { width } = domElement.getBoundingClientRect(); - const physicalWidth = width / zoom; - - camera.position.set(10, 0, -physicalWidth / 6); - }); - - return null; -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx deleted file mode 100644 index b062628bc..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/CanvasContent.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type { QuicklimeEvent } from "quicklime"; -import { useEffect, useRef } from "react"; -import type { Group } from "three"; -import { awaitableModelDefinitions } from "../../../../../../../../core/awaitables/modelDefinitions"; -import { - modelTransformEvent, - type ModelTransformEventData, -} from "../../../../../../../../core/blitzkit/modelTransform"; -import { Duel } from "../../../../../../../../stores/duel"; -import { ModelChunk } from "./ModelChunk"; - -const modelDefinitions = await awaitableModelDefinitions; - -export function CanvasContent() { - const hullWrapper = useRef(null); - const gunWrapper = useRef(null); - - const tank = Duel.use((state) => state.protagonist.tank); - const turret = Duel.use((state) => state.protagonist.turret); - - const tankModel = modelDefinitions.models[tank.id]; - const turretModel = tankModel.turrets[turret.id]; - - useEffect(() => { - function handleModelTransformEvent( - event: QuicklimeEvent, - ) { - if (!gunWrapper.current || !hullWrapper.current) return; - - gunWrapper.current.rotation.x = event.data.pitch; - if (event.data.yaw) hullWrapper.current.rotation.y = event.data.yaw; - } - - modelTransformEvent.on(handleModelTransformEvent); - - return () => { - modelTransformEvent.off(handleModelTransformEvent); - }; - }, []); - return ( - <> - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/FlexibilityCanvas.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/FlexibilityCanvas.tsx deleted file mode 100644 index 43ce564b2..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/FlexibilityCanvas.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { SmartCanvas } from '../../../../../../../SmartCanvas'; -import { CameraPositioner, zoom } from './CameraPositioner'; -import { CanvasContent } from './CanvasContent'; - -export function FlexibilityCanvas() { - return ( - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/ModelChunk.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/ModelChunk.tsx deleted file mode 100644 index 366971625..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/components/ModelChunk.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { mauveDark } from "@radix-ui/colors"; -import { LineBasicMaterial, MeshBasicMaterial } from "three"; -import { awaitableModelDefinitions } from "../../../../../../../../core/awaitables/modelDefinitions"; -import { jsxTree } from "../../../../../../../../core/blitzkit/jsxTree"; -import { useModel } from "../../../../../../../../hooks/useModel"; -import { Duel } from "../../../../../../../../stores/duel"; - -const modelDefinitions = await awaitableModelDefinitions; - -const surfaceMaterial: Record = { - gun: new MeshBasicMaterial({ - color: mauveDark.mauve8, - toneMapped: false, - }), - turret: new MeshBasicMaterial({ - color: mauveDark.mauve6, - toneMapped: false, - }), - body: new MeshBasicMaterial({ - color: mauveDark.mauve6, - toneMapped: false, - transparent: true, - opacity: 0.5, - }), -}; -const outlineMaterial: Record = { - gun: new LineBasicMaterial({ - color: mauveDark.mauve11, - toneMapped: false, - }), - turret: new LineBasicMaterial({ - color: mauveDark.mauve8, - toneMapped: false, - }), - body: new LineBasicMaterial({ - color: mauveDark.mauve8, - toneMapped: false, - transparent: true, - opacity: 0.5, - }), -}; - -interface Props { - only: "gun" | "turret" | "body"; -} - -export function ModelChunk({ only }: Props) { - const tank = Duel.use((state) => state.protagonist.tank); - const turret = Duel.use((state) => state.protagonist.turret); - const gun = Duel.use((state) => state.protagonist.gun); - - const tankModel = modelDefinitions.models[tank.id]; - const turretModel = tankModel.turrets[turret.id]; - const gunModel = turretModel.guns[gun.id]; - - const originZ = tankModel.turret_origin!.z + turretModel.gun_origin!.z; - const originY = tankModel.turret_origin!.y + turretModel.gun_origin!.y; - - const { gltf } = useModel(tank.id); - const nodes = Object.values(gltf.nodes); - - return ( - - {nodes.map((node) => { - const isCurrentMantlet = - node.name === - `gun_${gunModel.model_id.toString().padStart(2, "0")}_mask`; - const isCurrentGun = - node.name === `gun_${gunModel.model_id.toString().padStart(2, "0")}`; - const isGun = isCurrentGun || isCurrentMantlet; - - const isCurrentTurret = - node.name === - `turret_${turretModel.model_id.toString().padStart(2, "0")}`; - const isTurret = isCurrentTurret; - - const isHull = node.name === "hull"; - const isWheel = node.name.startsWith("chassis_wheel_"); - const isTrack = node.name.startsWith("chassis_track_"); - const isBody = isHull || isWheel || isTrack; - - if ( - (only === "gun" && !isGun) || - (only === "turret" && !isTurret) || - (only === "body" && !isBody) - ) { - return null; - } - - return jsxTree(node, { - mesh(mesh, props, key) { - return ( - <> - - - - - - ); - }, - }); - })} - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx deleted file mode 100644 index a278a230a..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/GunFlexibilityVisualizer/index.tsx +++ /dev/null @@ -1,394 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { Box, Card, Flex } from "@radix-ui/themes"; -import { clamp } from "lodash-es"; -import { useRef, useState } from "react"; -import { degToRad, radToDeg } from "three/src/math/MathUtils.js"; -import { awaitableModelDefinitions } from "../../../../../../../core/awaitables/modelDefinitions"; -import { - DEFAULT_PITCH_TRANSITION, - applyPitchYawLimits, -} from "../../../../../../../core/blitz/applyPitchYawLimits"; -import { hasEquipment } from "../../../../../../../core/blitzkit/hasEquipment"; -import { modelTransformEvent } from "../../../../../../../core/blitzkit/modelTransform"; -import { Var } from "../../../../../../../core/radix/var"; -import { useLocale } from "../../../../../../../hooks/useLocale"; -import { Duel } from "../../../../../../../stores/duel"; -import type { MaybeSkeletonComponentProps } from "../../../../../../../types/maybeSkeletonComponentProps"; -import { VisualizerCard } from "../VisualizerCard"; -import { VisualizerCornerStat } from "../VisualizerCornerStat"; -import { FlexibilityCanvas } from "./components/FlexibilityCanvas"; - -const ANGLE_COEFFICIENT = 1 / 10; - -const modelDefinition = await awaitableModelDefinitions; - -function mag(x: number) { - return (1 / Math.PI) * Math.atan(ANGLE_COEFFICIENT * x) + 1 / 2; -} - -function magInverse(y: number): number { - return (1 / ANGLE_COEFFICIENT) * Math.tan(Math.PI * y - Math.PI / 2); -} - -function c(thetaDeg: number, m = 1) { - const theta = thetaDeg * (Math.PI / 180); - return `${m * Math.cos(theta)} ${-m * Math.sin(theta)}`; -} - -export function GunFlexibilityVisualizer({ - skeleton, -}: MaybeSkeletonComponentProps) { - const { strings } = useLocale(); - - const tank = Duel.use((state) => state.protagonist.tank); - const turret = Duel.use((state) => state.protagonist.turret); - const gun = Duel.use((state) => state.protagonist.gun); - - const tankModel = modelDefinition.models[tank.id]; - const turretModel = tankModel.turrets[turret.id]; - const gunModel = turretModel.guns[gun.id]; - - const [yaw, setYaw] = useState(0); - const [pitch, setPitch] = useState(0); - const [minPitch, setMinPitch] = useState( - -degToRad(gunModel.pitch!.front?.max ?? gunModel.pitch!.max), - ); - const [maxPitch, setMaxPitch] = useState( - -degToRad(gunModel.pitch!.front?.min ?? gunModel.pitch!.min), - ); - - const container = useRef(null); - const highlighter = useRef(null); - const pointer = useRef(null); - - const p = gunModel.pitch!; - const y = turretModel.yaw; - const f = p.front; - const b = p.back; - const t = gunModel.pitch!.transition ?? DEFAULT_PITCH_TRANSITION; - - let d = ""; - - // right - const mo = mag(p.max); - if (y) { - d += `M 0 0`; - d += `L ${c(90 - y.max, mo)}`; - } else if (b) { - d += `M ${c(-90 + b.range / 2 + t, mo)}`; - } else { - d += `M ${c(-90, mo)}`; - } - if (f) { - d += `A ${mo} ${mo} 0 0 0 ${c(90 - f.range / 2 - t, mo)}`; - } else { - d += `A ${mo} ${mo} 0 0 0 ${c(90, mo)}`; - } - - // front - if (f) { - const m = mag(f.max); - d += `L ${c(90 - f.range / 2, m)}`; - d += `A ${m} ${m} 0 0 0 ${c(90 + f.range / 2, m)}`; - } - - // left - if (f) { - d += `L ${c(90 + f.range / 2 + t, mo)}`; - } else { - d += `L ${c(90, mo)}`; - } - if (y) { - d += `A ${mo} ${mo} 0 0 0 ${c(90 - y.min, mo)}`; - } else if (b) { - d += `A ${mo} ${mo} 0 0 0 ${c(-90 - b.range / 2 - t, mo)}`; - } else { - d += `A ${mo} ${mo} 0 0 0 ${c(-90, mo)}`; - } - - // back - if (!y && b) { - const m = mag(b.max); - d += `L ${c(-90 - b.range / 2, m)}`; - d += `A ${m} ${m} 0 0 0 ${c(-90 + b.range / 2, m)}`; - } - - d += `Z`; - - // right - const mi = mag(p.min); - if (y) { - d += `M 0 0`; - d += `L ${c(90 - y.max, mi)}`; - } else if (b) { - d += `M ${c(-90 + b.range / 2 + t, mi)}`; - } else { - d += `M ${c(-90, mi)}`; - } - if (f) { - d += `A ${mi} ${mi} 0 0 0 ${c(90 - f.range / 2 - t, mi)}`; - } else { - d += `A ${mi} ${mi} 0 0 0 ${c(90, mi)}`; - } - - // front - if (f) { - const m = mag(f.min); - d += `L ${c(90 - f.range / 2, m)}`; - d += `A ${m} ${m} 0 0 0 ${c(90 + f.range / 2, m)}`; - } - - // left - if (f) { - d += `L ${c(90 + f.range / 2 + t, mi)}`; - } else { - d += `L ${c(90, mi)}`; - } - if (y) { - d += `A ${mi} ${mi} 0 0 0 ${c(90 - y.min, mi)}`; - } else if (b) { - d += `A ${mi} ${mi} 0 0 0 ${c(-90 - b.range / 2 - t, mi)}`; - } else { - d += `A ${mi} ${mi} 0 0 0 ${c(-90, mi)}`; - } - - // back - if (!y && b) { - const m = mag(b.min); - d += `L ${c(-90 - b.range / 2, m)}`; - d += `A ${m} ${m} 0 0 0 ${c(-90 + b.range / 2, m)}`; - } - - d += `Z`; - - const r = mag(radToDeg(-pitch)); - const dotX = r * Math.sin(yaw); - const dotY = r * Math.cos(yaw); - - return ( - - - - - {!skeleton && } - - - - - - - { - event.preventDefault(); - - if (!container.current || !highlighter.current || !pointer.current) - return; - - const rect = container.current.getBoundingClientRect(); - const u = event.clientX - rect.left - rect.width / 2; - const v = event.clientY - rect.top - rect.height / 2; - let yaw = Math.atan2(u, -v); - - const hasImprovedVerticalStabilizer = hasEquipment( - 122, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const hasDownImprovedVerticalStabilizer = hasEquipment( - 124, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - - const min = applyPitchYawLimits( - -Math.PI, - yaw, - p, - y, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - const max = applyPitchYawLimits( - Math.PI, - yaw, - p, - y, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - - yaw = min[1]; - let t = 2 * Math.sqrt((u / rect.width) ** 2 + (v / rect.height) ** 2); - t = clamp(t, 0, 1); - let pitch = degToRad(magInverse(-t)); - pitch = clamp(pitch, min[0], max[0]); - - setPitch(pitch); - setYaw(yaw); - setMinPitch(min[0]); - setMaxPitch(max[0]); - - modelTransformEvent.dispatch({ pitch, yaw }); - }} - mb="0" - > - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Info.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Info.tsx deleted file mode 100644 index 64f1c49c6..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Info.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { CaretDownIcon, CaretUpIcon } from "@radix-ui/react-icons"; -import { Box, Flex, Text } from "@radix-ui/themes"; -import fuzzysort from "fuzzysort"; -import { Quicklime } from "quicklime"; -import { useEffect, useRef, type ReactNode } from "react"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { Mark } from "../../../../../Mark"; - -export const highlightedRows = new Set(); -export const highlightedRowsUpdate = new Quicklime(); - -export interface InfoProps { - name: ReactNode; - children?: ReactNode; - indent?: boolean; - delta?: number; - decimals?: number; - prefix?: string; - deltaType?: "higherIsBetter" | "lowerIsBetter"; -} - -export function Info({ - name, - children, - indent = false, - delta, - decimals, - prefix, - deltaType = "higherIsBetter", -}: InfoProps) { - const statSearch = Tankopedia.use((state) => state.statSearch); - const container = useRef(null); - - let label = name; - let highlighted = false; - - if (statSearch !== undefined && typeof name === "string") { - const result = fuzzysort.single(statSearch, name); - - if (result) { - label = result.highlight((match, index) => ( - {match} - )); - highlighted = true; - } - } - - useEffect(() => { - if (!container.current) return; - - if (highlighted) { - highlightedRows.add(container.current); - highlightedRowsUpdate.dispatch(); - } - - return () => { - highlightedRows.delete(container.current!); - highlightedRowsUpdate.dispatch(); - }; - }); - - return ( - - {label} - - - - {children !== undefined && ( - - {delta !== undefined && delta !== 0 && !isNaN(delta) && ( - <> - 0 - ? "green" - : "tomato" - } - > - {decimals !== undefined - ? Math.abs(delta).toFixed(decimals) - : Math.abs(delta)} - - 0 - ? "green" - : "tomato" - } - > - {delta > 0 ? : } - - - )} - - - {prefix !== undefined && ( - - {prefix} - - )} - - {decimals !== undefined && typeof children === "number" - ? children.toFixed(decimals) - : children} - - - )} - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx deleted file mode 100644 index f29d6993a..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { createDefaultSkills } from "@blitzkit/core"; -import { Flex, Progress, Text } from "@radix-ui/themes"; -import { clamp } from "lodash-es"; -import { memo, useMemo, type ComponentProps, type ReactNode } from "react"; -import { awaitableEquipmentDefinitions } from "../../../../../../core/awaitables/equipmentDefinitions"; -import { awaitableModelDefinitions } from "../../../../../../core/awaitables/modelDefinitions"; -import { awaitableProvisionDefinitions } from "../../../../../../core/awaitables/provisionDefinitions"; -import { awaitableSkillDefinitions } from "../../../../../../core/awaitables/skillDefinitions"; -import { awaitableTankDefinitions } from "../../../../../../core/awaitables/tankDefinitions"; -import { - tankCharacteristics, - type TankCharacteristics, -} from "../../../../../../core/blitzkit/tankCharacteristics"; -import { tankToDuelMember } from "../../../../../../core/blitzkit/tankToDuelMember"; -import { useDelta } from "../../../../../../hooks/useDelta"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { Duel } from "../../../../../../stores/duel"; -import { - Tankopedia, - TankopediaRelativeAgainst, -} from "../../../../../../stores/tankopedia"; -import { Info, type InfoProps } from "./Info"; - -type InfoWithDeltaProps = Omit & { - stats: TankCharacteristics; - noRanking?: boolean; -} & ( - | { - value: keyof TankCharacteristics; - } - | { - value: (tank: TankCharacteristics) => number | undefined; - name: ReactNode; - } - ); - -const [ - tankDefinitions, - provisionDefinitions, - equipmentDefinitions, - modelDefinitions, - skillDefinitions, -] = await Promise.all([ - awaitableTankDefinitions, - awaitableProvisionDefinitions, - awaitableEquipmentDefinitions, - awaitableModelDefinitions, - awaitableSkillDefinitions, -]); - -export const InfoWithDelta = memo( - ({ stats, indent, noRanking, deltaType, ...props }) => { - const { strings } = useLocale(); - const relativeAgainst = Tankopedia.use((state) => state.relativeAgainst); - const uhWhatDoICallThisVariable = - typeof props.value === "function" - ? props.value(stats)! - : (stats[props.value] as number); - const delta = useDelta(uhWhatDoICallThisVariable); - const protagonistTank = Duel.use((state) => state.protagonist.tank); - const shellIndex = Duel.use((state) => - state.protagonist.gun.shells.indexOf(state.protagonist.shell), - ); - const equipmentMatrix = Duel.use( - (state) => state.protagonist.equipmentMatrix, - ); - const equalize = Duel.use((state) => state.equalize); - const others = useMemo(() => { - const defaultSkills = createDefaultSkills(skillDefinitions); - - return Object.values(tankDefinitions.tanks) - .filter( - (tank) => - (relativeAgainst === TankopediaRelativeAgainst.Class && - tank.tier === protagonistTank.tier && - tank.class === protagonistTank.class) || - (relativeAgainst === TankopediaRelativeAgainst.Tier && - tank.tier === protagonistTank.tier) || - relativeAgainst === TankopediaRelativeAgainst.All, - ) - .map((tank) => { - const member = tankToDuelMember(tank, provisionDefinitions); - - if (member.gun.shells[shellIndex] === undefined) { - return undefined; - } - - return tankCharacteristics( - { - applyDynamicArmor: false, - applyReactiveArmor: false, - applySpallLiner: false, - camouflage: member.camouflage, - consumables: member.consumables, - crewSkills: defaultSkills, - provisions: member.provisions, - engine: member.engine, - gun: member.gun, - equipmentMatrix: equipmentMatrix, - shell: member.gun.shells[shellIndex], - stockEngine: tank.engines[0], - stockGun: tank.turrets[0].guns[0], - stockTrack: tank.tracks[0], - stockTurret: tank.turrets[0], - tank, - track: member.track, - turret: member.turret, - assaultDistance: member.assaultDistance, - equalize, - }, - { - equipmentDefinitions, - provisionDefinitions, - tankModelDefinition: modelDefinitions.models[tank.id], - }, - ); - }) - .filter((tank) => { - if (tank === undefined) return false; - - const othersValue = - typeof props.value === "function" - ? props.value(tank)! - : (tank[props.value] as number); - - return othersValue !== undefined; - }) as TankCharacteristics[]; - }, [relativeAgainst, shellIndex, equipmentMatrix, equalize]); - const betterTanks = others.filter((tank) => { - const othersValue = - typeof props.value === "function" - ? props.value(tank)! - : (tank[props.value] as number); - - if (othersValue === undefined) return false; - return deltaType === "lowerIsBetter" - ? othersValue < uhWhatDoICallThisVariable - : othersValue > uhWhatDoICallThisVariable; - }); - const goodness = (others.length - betterTanks.length) / others.length; - let color: ComponentProps["color"]; - - if (goodness <= 0.25) color = "red"; - else if (goodness <= 0.5) color = "orange"; - else if (goodness <= 0.75) color = "yellow"; - else color = "green"; - - return ( - - - {uhWhatDoICallThisVariable} - - - {!noRanking && ( - - - - - {clamp(betterTanks.length + 1, 1, others.length)} /{" "} - {others.length} - - - )} - - ); - }, - - (a, b) => - (typeof a.value === "function" ? a.value(a.stats) : a.stats[a.value]) === - (typeof b.value === "function" ? b.value(b.stats) : b.stats[b.value]), -); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Maneuverability.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Maneuverability.tsx deleted file mode 100644 index 858479216..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Maneuverability.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { Heading } from '@radix-ui/themes'; -import { useLocale } from '../../../../../../hooks/useLocale'; -import { Info } from './Info'; -import { InfoWithDelta } from './InfoWithDelta'; -import { StatsTableWrapper } from './StatsTableWrapper'; -import { - TraverseVisualizer, - type StatsAcceptorProps, -} from './TraverseVisualizer'; - -export function Maneuverability({ stats }: StatsAcceptorProps) { - const { strings } = useLocale(); - - return ( - - - {strings.website.tools.tankopedia.maneuverability.title} - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/Shell.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/Shell.tsx deleted file mode 100644 index c06e38136..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/Shell.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Box, Flex } from '@radix-ui/themes'; -import type { QuicklimeCallback } from 'quicklime'; -import { useEffect, useRef } from 'react'; -import { reloadUpdate, type ReloadUpdateData } from '..'; -import { Var } from '../../../../../../../../core/radix/var'; -import type { StatsAcceptorProps } from '../../TraverseVisualizer'; -import { ShellBorder } from './ShellBorder'; - -interface ShellProps { - index: number; -} - -export function Shell({ index, stats }: ShellProps & StatsAcceptorProps) { - const bar = useRef(null!); - - useEffect(() => { - const handleReloadUpdate: QuicklimeCallback = ({ - data, - }) => { - bar.current.style.width = `${data.shells[index] * 100}%`; - }; - - reloadUpdate.on(handleReloadUpdate); - - return () => { - reloadUpdate.off(handleReloadUpdate); - }; - }, []); - - return ( - - - - - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/ShellBorder.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/ShellBorder.tsx deleted file mode 100644 index 58030407f..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/ShellBorder.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function ShellBorder(props: ComponentProps<'svg'>) { - return ( - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/Target.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/Target.tsx deleted file mode 100644 index 8e3b76920..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/components/Target.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Box } from '@radix-ui/themes'; - -export function Target() { - return ( - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/index.css b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/index.css deleted file mode 100644 index 11cf949b6..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/index.css +++ /dev/null @@ -1,21 +0,0 @@ -#reload-visualizer-flash { - background: transparent; - transition-duration: 200ms; -} - -#reload-visualizer-flash.go { - background: var(--white-a3); - transition: none; -} - -.reload { - width: 65%; - height: 65%; - transition-duration: 200ms; -} - -.reload.go { - width: 70%; - height: 70%; - transition: none; -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/index.tsx deleted file mode 100644 index 1ce46ab09..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ReloadVisualizer/index.tsx +++ /dev/null @@ -1,318 +0,0 @@ -import { DoubleArrowRightIcon } from '@radix-ui/react-icons'; -import { Box, Button, Code } from '@radix-ui/themes'; -import { times } from 'lodash-es'; -import { Quicklime, type QuicklimeCallback } from 'quicklime'; -import { useCallback, useEffect, useRef } from 'react'; -import { Var } from '../../../../../../../core/radix/var'; -import { useLocale } from '../../../../../../../hooks/useLocale'; -import type { StatsAcceptorProps } from '../TraverseVisualizer'; -import { VisualizerCard } from '../VisualizerCard'; -import { Shell } from './components/Shell'; -import { Target } from './components/Target'; -import './index.css'; - -const PRECISION = 2; - -export interface ReloadUpdateData { - reload: number; - shells: number[]; -} - -export const reloadUpdate = new Quicklime(); - -export function ReloadVisualizer({ stats }: StatsAcceptorProps) { - const { strings } = useLocale(); - - const reloadCircle = useRef(null); - const reloadCore = useRef(null); - const reloadGlow = useRef(null); - const totalTime = useRef(null); - const progressTime = useRef(null); - const flash = useRef(null); - - const state = useRef(0); - - const shoot = useCallback(() => { - flash.current?.classList.add('go'); - reloadCircle.current?.classList.add('go'); - reloadGlow.current?.classList.add('go'); - - void flash.current?.offsetWidth; - void reloadCircle.current?.offsetWidth; - void reloadGlow.current?.offsetWidth; - - flash.current?.classList.remove('go'); - reloadCircle.current?.classList.remove('go'); - reloadGlow.current?.classList.remove('go'); - }, []); - - useEffect(() => { - let cancel = false; - let lastT = Date.now() / 1000; - let lastState = state.current; - const shellArray = new Array(stats.shells); - - function frame() { - if (!totalTime.current || !progressTime.current) return; - - const reloadThreshold = stats.shellReloads - ? stats.shellReloads.length - : 1; - const now = Date.now() / 1000; - const dt = now - lastT; - let reload; - let shells; - - if (state.current < reloadThreshold) { - const shellReload = - reloadThreshold === 1 - ? stats.shellReload! - : stats.shellReloads![Math.floor(state.current)]; - - state.current = Math.min( - reloadThreshold, - state.current + dt / shellReload, - ); - reload = state.current % 1; - shells = shellArray.fill(reload); - - if (reloadThreshold > 1) { - shells = shells.map((_, index) => - Math.max(0, state.current - shellArray.length + index + 1), - ); - } - - totalTime.current.innerHTML = shellReload.toFixed(PRECISION); - progressTime.current.innerHTML = ((1 - reload) * shellReload).toFixed( - PRECISION, - ); - - if (state.current >= reloadThreshold) shoot(); - } else if (state.current < stats.shells + reloadThreshold - 1) { - let interval; - - if ( - stats.burstShells === 1 || - Math.floor(state.current) % stats.burstShells === 0 - ) { - interval = stats.intraClip!; - } else { - interval = stats.burstInterShell!; - } - - state.current = Math.min( - stats.shells + reloadThreshold, - state.current + dt / interval, - ); - reload = state.current % 1; - - if (reloadThreshold > 1) { - shells = shellArray.map((_, index) => - Math.max( - 0, - Math.floor(index + 1 + reloadThreshold - state.current), - ), - ); - } else { - shells = times(stats.shells, (index) => index > state.current - 1); - } - - totalTime.current.innerHTML = interval.toFixed(PRECISION); - progressTime.current.innerHTML = ((1 - reload) * interval).toFixed( - PRECISION, - ); - - if (lastState % 1 > state.current % 1) shoot(); - } else { - state.current = 0; - reload = 0; - shells = shellArray.fill(0); - } - - reloadUpdate.dispatch({ reload, shells }); - - if (!cancel) requestAnimationFrame(frame); - - lastT = now; - lastState = state.current; - } - - frame(); - - return () => { - cancel = true; - }; - }, [stats]); - - useEffect(() => { - const handleReloadUpdate: QuicklimeCallback = ({ - data, - }) => { - if (!reloadCircle.current || !reloadCore.current || !reloadGlow.current) - return; - - const reloadAngle = data.reload * 2 * Math.PI; - - reloadCircle.current.style.background = `conic-gradient(${Var('amber-9')} 0 ${reloadAngle}rad, ${Var('orange-7')} ${reloadAngle}rad)`; - reloadCore.current.style.background = `conic-gradient(${Var('amber-6')} 0 ${reloadAngle}rad, ${Var('orange-3')} ${reloadAngle}rad)`; - reloadGlow.current.style.background = `conic-gradient(${Var('amber-9')} 0 ${reloadAngle}rad, transparent ${reloadAngle}rad)`; - }; - - reloadUpdate.on(handleReloadUpdate); - - return () => { - reloadUpdate.off(handleReloadUpdate); - }; - }, []); - - return ( - - - - - - - - - - - - - - - - - - - - - 0.00 - - - 0.00 - - - {stats.shells > 1 && - times(stats.shells, (index) => ( - - ))} - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx deleted file mode 100644 index 52d3b431d..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/components/ShellSwitcher.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { Flex, IconButton, Tooltip } from "@radix-ui/themes"; -import { useLocale } from "../../../../../../../../hooks/useLocale"; -import { Duel } from "../../../../../../../../stores/duel"; - -export function ShellSwitcher() { - const gun = Duel.use((state) => state.protagonist.gun); - const shell = Duel.use((state) => state.protagonist.shell); - - const { unwrap } = useLocale(); - - return ( - - {gun.shells.map((thisShell, shellIndex) => { - const selected = thisShell.id === shell.id; - - return ( - - { - Duel.mutate((draft) => { - draft.protagonist.shell = thisShell; - }); - }} - > - {unwrap(thisShell.name!)} - - - ); - })} - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx deleted file mode 100644 index a688de0d7..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/RicochetVisualizer/index.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import { asset, isExplosive } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Box, Flex, Text } from "@radix-ui/themes"; -import { useRef, useState } from "react"; -import { clamp, degToRad, radToDeg } from "three/src/math/MathUtils.js"; -import { Var } from "../../../../../../../core/radix/var"; -import { useLocale } from "../../../../../../../hooks/useLocale"; -import { Duel } from "../../../../../../../stores/duel"; -import type { StatsAcceptorProps } from "../TraverseVisualizer"; -import { VisualizerCard } from "../VisualizerCard"; -import { VisualizerCornerStat } from "../VisualizerCornerStat"; -import { ShellSwitcher } from "./components/ShellSwitcher"; - -export function RicochetVisualizer({ stats }: StatsAcceptorProps) { - const shell = Duel.use((state) => state.protagonist.shell); - const container = useRef(null); - const normalization = degToRad(stats.shellNormalization); - const ricochet = degToRad(stats.shellRicochet ?? 90); - const [rawAngle, setAngle] = useState(degToRad(-25)); - const explodes = isExplosive(shell.type); - const angle = explodes ? degToRad(-70) : rawAngle; - const doesRicochet = Math.abs(angle) >= ricochet; - const effectiveAngle = doesRicochet - ? -angle + Math.PI - : Math.sign(angle) * Math.max(0, Math.abs(angle) - normalization); - const { strings } = useLocale(); - const armorPercentage = Math.min(999, 100 / Math.cos(effectiveAngle)); - - return ( - - { - event.preventDefault(); - - if (!container.current || explodes) return; - - const rect = container.current.getBoundingClientRect(); - const x = event.clientX - rect.left - rect.width / 3; - const y = event.clientY - rect.top - rect.height / 2; - const angle = clamp(Math.atan2(-y, x), -Math.PI / 2, Math.PI / 2); - - setAngle(angle); - }} - > - - - - - - - - - - - - - - - - - - - - - - - {explodes && ( - - - { - strings.website.tools.tankopedia.visualizers.ricochet - .no_ricochet - } - - - )} - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/StatSearch.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/StatSearch.tsx deleted file mode 100644 index 77cde4dda..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/StatSearch.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; -import { Flex, Spinner, TextField, type FlexProps } from "@radix-ui/themes"; -import { debounce } from "lodash-es"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { highlightedRows, highlightedRowsUpdate } from "./Info"; - -export function StatSearch(props: FlexProps) { - const { strings } = useLocale(); - const [searching, setSearching] = useState(false); - const input = useRef(null); - const count = useRef(null); - const [index, setIndex] = useState(0); - const search = useCallback(() => { - if (!input.current || !count.current) return; - - if (input.current.value.length > 0) { - setSearching(true); - } else { - setSearching(false); - count.current.textContent = ""; - } - - requestUpdate(); - setIndex(0); - }, []); - const requestUpdate = useCallback( - debounce(() => { - if (!input.current || !count.current) return; - - if (input.current.value.length === 0) { - count.current.textContent = ""; - - setSearching(false); - Tankopedia.mutate((draft) => { - draft.statSearch = undefined; - }); - return; - } - - setSearching(false); - Tankopedia.mutate((draft) => { - draft.statSearch = input.current!.value; - }); - }, 500), - [] - ); - - useEffect(() => { - const handleHighlightedRowsUpdate = () => { - if (!count.current || !input.current) return; - - count.current.textContent = - input.current.value.length === 0 ? "" : `0 / ${highlightedRows.size}`; - }; - - highlightedRowsUpdate.on(handleHighlightedRowsUpdate); - - return () => { - highlightedRowsUpdate.off(handleHighlightedRowsUpdate); - }; - }, []); - - return ( - - { - if (!count.current) return; - - if (event.key === "Enter") { - const newIndex = (index + 1) % highlightedRows.size; - count.current.textContent = `${newIndex + 1} / ${highlightedRows.size}`; - const array = [...highlightedRows]; - - array.forEach((element) => element.classList.remove("focused")); - - if (array[newIndex]) { - array[newIndex].classList.add("focused"); - array[newIndex].scrollIntoView({ - block: "center", - inline: "center", - }); - } - - setIndex((index) => index + 1); - } - }} - > - - {searching ? : } - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/StatsTableWrapper.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/StatsTableWrapper.tsx deleted file mode 100644 index 731619029..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/StatsTableWrapper.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Flex, type FlexProps } from '@radix-ui/themes'; - -export function StatsTableWrapper(props: FlexProps) { - return ( - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Survivability.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Survivability.tsx deleted file mode 100644 index edd9c96f7..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Survivability.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { Heading } from '@radix-ui/themes'; -import { useLocale } from '../../../../../../hooks/useLocale'; -import { Info } from './Info'; -import { InfoWithDelta } from './InfoWithDelta'; -import { StatsTableWrapper } from './StatsTableWrapper'; -import type { StatsAcceptorProps } from './TraverseVisualizer'; -import { ViewRangeVisualizer } from './ViewRangeVisualizer'; - -export function Survivability({ stats }: StatsAcceptorProps) { - const { strings } = useLocale(); - - return ( - - - {strings.website.tools.tankopedia.survivability.title} - - - stats.fireChance * 100} - /> - - - - stats.camouflageStill * 100} - stats={stats} - indent - name={ - strings.website.tools.tankopedia.characteristics.values - .camouflageStill - } - decimals={2} - /> - stats.camouflageMoving * 100} - stats={stats} - indent - name={ - strings.website.tools.tankopedia.characteristics.values - .camouflageMoving - } - decimals={2} - /> - stats.camouflageShootingStill * 100} - /> - stats.camouflageShootingMoving * 100} - /> - stats.camouflageCaughtOnFire * 100} - /> - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/TraverseVisualizer/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/TraverseVisualizer/index.tsx deleted file mode 100644 index 0015defe3..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/TraverseVisualizer/index.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { Checkbox, Code, Flex, Tabs, Text } from "@radix-ui/themes"; -import { useEffect, useRef, useState } from "react"; -import type { TankCharacteristics } from "../../../../../../../core/blitzkit/tankCharacteristics"; -import { useLocale } from "../../../../../../../hooks/useLocale"; -import { VisualizerCard } from "../VisualizerCard"; - -export interface StatsAcceptorProps { - stats: TankCharacteristics; -} - -type Terrain = "Hard" | "Medium" | "Soft"; - -export function TraverseVisualizer({ stats }: StatsAcceptorProps) { - const hull = useRef(null); - const turret = useRef(null); - const [terrain, setTerrain] = useState("Hard"); - const [rotateHull, setRotateHull] = useState(true); - const [rotateTurret, setRotateTurret] = useState(true); - const { strings } = useLocale(); - const isTurretless = - stats.azimuthLeft !== undefined || stats.azimuthRight !== undefined; - const turretAngle = useRef(0); - const hullAngle = useRef(0); - const turretSpeedDeg = rotateTurret ? stats.turretTraverseSpeed : 0; - const hullSpeedDeg = rotateHull ? stats[`hullTraverse${terrain}Terrain`] : 0; - - useEffect(() => { - let cancel = false; - const turretSpeedRad = turretSpeedDeg * (Math.PI / 180); - const hullTraverseRad = hullSpeedDeg * (Math.PI / 180); - let lastT = Date.now() / 1000; - - function frame() { - const t = Date.now() / 1000; - const dt = t - lastT; - lastT = t; - - if (turret.current) { - turretAngle.current = - (turretAngle.current + turretSpeedRad * dt) % (2 * Math.PI); - turret.current.style.transform = `translate(-50%, -50%) rotate(${-turretAngle.current}rad)`; - } - - if (hull.current) { - hullAngle.current = - (hullAngle.current + hullTraverseRad * dt) % (2 * Math.PI); - hull.current.style.transform = `translate(-50%, -50%) rotate(${-hullAngle.current}rad)`; - } - - if (!cancel) requestAnimationFrame(frame); - } - - frame(); - - return () => { - cancel = true; - }; - }, [terrain, turretSpeedDeg, hullSpeedDeg]); - - return ( - -
- - - {!isTurretless && ( - - )} -
- - - setTerrain(terrain as Terrain)} - > - - - {strings.website.tools.tankopedia.visualizers.rotator.hard} - - - {strings.website.tools.tankopedia.visualizers.rotator.medium} - - - {strings.website.tools.tankopedia.visualizers.rotator.soft} - - - - - - - - {strings.website.tools.tankopedia.visualizers.rotator.effective} - - - {literals(strings.common.units.deg_s, { - value: (turretSpeedDeg + hullSpeedDeg).toFixed(1), - })} - - - - {!isTurretless && ( - - setRotateHull((state) => !state)} - style={{ cursor: "pointer" }} - > - - - {strings.website.tools.tankopedia.visualizers.rotator.hull} - - - - setRotateTurret((state) => !state)} - style={{ cursor: "pointer" }} - > - - - {strings.website.tools.tankopedia.visualizers.rotator.turret} - - - - )} -
- ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ViewRangeVisualizer.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ViewRangeVisualizer.tsx deleted file mode 100644 index 34c9c9b82..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/ViewRangeVisualizer.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { EyeOpenIcon } from "@radix-ui/react-icons"; -import { Box, Card, Code, Flex, Slider, Tabs, Text } from "@radix-ui/themes"; -import { useRef, useState } from "react"; -import { Var } from "../../../../../../core/radix/var"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import type { StatsAcceptorProps } from "./TraverseVisualizer"; - -const maps = [ - { - id: "ghost-factory", - string: "ghost_factory", - size: 512, - }, - { - id: "dead-rail", - string: "dead_rail", - size: 512, - }, -] as const; - -export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) { - const [mapIndex, setMapIndex] = useState(0); - const [camouflage, setCamouflage] = useState(0); - const map = maps[mapIndex]; - const resolvedViewRange = stats.viewRange * (1 - camouflage); - const fraction = resolvedViewRange / map.size; - const fractionPercentage = `${fraction * 100}%`; - const { strings } = useLocale(); - const container = useRef(null); - const range = useRef(null); - - return ( - - { - if (!container.current || !range.current) return; - - const rect = container.current.getBoundingClientRect(); - range.current.style.left = `${event.clientX - rect.left}px`; - range.current.style.top = `${event.clientY - rect.top - 16}px`; - }} - flexGrow="1" - position="absolute" - top="0" - left="0" - width="100%" - height="100%" - > - - - - - - - {literals(strings.common.units.m, { - value: resolvedViewRange.toFixed(0), - })} - - - - - - - - {strings.website.tools.tankopedia.visualizers.view_range.camo} - - - setCamouflage(value)} - /> - - - - {literals(strings.common.units.percentage, { - value: `${Math.round(camouflage * 100)}`.padStart(3, " "), - })} - - - - - - setMapIndex(Number(index))} - > - - {maps.map((map) => ( - - { - strings.website.tools.tankopedia.visualizers.view_range[ - map.string - ] - } - - ))} - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/VisualizerCard.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/VisualizerCard.tsx deleted file mode 100644 index 5b09b8623..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/VisualizerCard.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Card, type CardProps } from '@radix-ui/themes'; -import { forwardRef } from 'react'; - -export const VisualizerCard = forwardRef( - ({ style, ...props }, ref) => { - return ( - - ); - }, -); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/VisualizerCornerStat.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/VisualizerCornerStat.tsx deleted file mode 100644 index 33c21a88b..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/VisualizerCornerStat.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Code, Flex, Text } from '@radix-ui/themes'; - -interface Props { - label: string; - value: string; - side: `${'top' | 'bottom'}-${'left' | 'right'}`; -} - -export function VisualizerCornerStat({ label, value, side }: Props) { - return ( - - - {label} - - - {value} - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx deleted file mode 100644 index cebcf0521..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Flex } from "@radix-ui/themes"; -import { useEffect } from "react"; -import { awaitableEquipmentDefinitions } from "../../../../../core/awaitables/equipmentDefinitions"; -import { awaitableProvisionDefinitions } from "../../../../../core/awaitables/provisionDefinitions"; -import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits"; -import { modelTransformEvent } from "../../../../../core/blitzkit/modelTransform"; -import { tankCharacteristics } from "../../../../../core/blitzkit/tankCharacteristics"; -import { useEquipment } from "../../../../../hooks/useEquipment"; -import { useTankModelDefinition } from "../../../../../hooks/useTankModelDefinition"; -import { Duel } from "../../../../../stores/duel"; -import { Tankopedia } from "../../../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps"; -import { Crew } from "./components/Crew"; -import { Firepower } from "./components/Firepower"; -import { Maneuverability } from "./components/Maneuverability"; -import { Survivability } from "./components/Survivability"; - -const [equipmentDefinitions, provisionDefinitions] = await Promise.all([ - awaitableEquipmentDefinitions, - awaitableProvisionDefinitions, -]); - -export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) { - const crewSkills = Tankopedia.use((state) => state.skills); - const provisions = Duel.use((state) => state.protagonist.provisions); - const consumables = Duel.use((state) => state.protagonist.consumables); - const camouflage = Duel.use((state) => state.protagonist.camouflage); - const assaultDistance = Duel.use( - (state) => state.protagonist.assaultDistance, - ); - const equipmentMatrix = Duel.use( - (state) => state.protagonist.equipmentMatrix, - ); - const equalize = Duel.use((state) => state.equalize); - const { tank, turret, gun, engine, track, shell } = Duel.use( - (state) => state.protagonist, - ); - const stockEngine = tank.engines[0]; - const stockTrack = tank.tracks[0]; - const stockTurret = tank.turrets[0]; - const stockGun = stockTurret.guns[0]; - const tankModelDefinition = useTankModelDefinition(); - const turretModelDefinition = tankModelDefinition.turrets[turret.id]; - const gunModelDefinition = turretModelDefinition.guns[gun.id]; - const stats = tankCharacteristics( - { - tank, - camouflage, - consumables, - crewSkills, - engine, - equipmentMatrix, - gun, - provisions, - shell, - turret, - track, - stockEngine, - stockGun, - stockTrack, - stockTurret, - applyReactiveArmor: false, - applyDynamicArmor: false, - applySpallLiner: false, - assaultDistance, - equalize, - }, - { - tankModelDefinition, - equipmentDefinitions: equipmentDefinitions, - provisionDefinitions: provisionDefinitions, - }, - ); - - const hasImprovedVerticalStabilizer = useEquipment(122); - const hasDownImprovedVerticalStabilizer = useEquipment(124); - - useEffect(() => { - const [pitch, yaw] = applyPitchYawLimits( - modelTransformEvent.last!.pitch, - modelTransformEvent.last!.yaw, - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - - modelTransformEvent.dispatch({ pitch, yaw }); - }, [hasImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer]); - - return ( - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/ConfigurationChildWrapper.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/ConfigurationChildWrapper.tsx deleted file mode 100644 index 5b161ebb7..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/ConfigurationChildWrapper.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { type FlexProps, Flex } from "@radix-ui/themes"; -import type { ReactNode } from "react"; - -type ConfigurationChildWrapperProps = FlexProps & { - children: ReactNode; -}; - -export function ConfigurationChildWrapper({ - children, - style, - ...props -}: ConfigurationChildWrapperProps) { - return ( - - {children} - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx deleted file mode 100644 index 386a62ddc..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { - Button, - Flex, - Heading, - IconButton, - Popover, - Text, -} from "@radix-ui/themes"; -import { awaitableConsumableDefinitions } from "../../../../core/awaitables/consumableDefinitions"; -import { useEquipment } from "../../../../hooks/useEquipment"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { ConsumablesManager } from "../../../ConsumablesManager"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -const consumableDefinitions = await awaitableConsumableDefinitions; - -export function Consumables() { - const protagonist = Duel.use((state) => state.protagonist); - const consumables = Duel.use((state) => state.protagonist.consumables); - const consumablesList = Object.values( - consumableDefinitions.consumables - ).filter((consumable) => - checkConsumableProvisionInclusivity( - consumable, - protagonist.tank, - protagonist.gun - ) - ); - const cooldownBooster = Duel.use( - (state) => state.protagonist.cooldownBooster - ); - const hasConsumableDeliverySystem = useEquipment(118); - const hasHighEndConsumables = useEquipment(101); - const { strings } = useLocale(); - - return ( - - - - - {strings.website.tools.tankopedia.configuration.consumables.title} - - - - - - - - - - - - { - strings.website.tools.tankopedia.configuration.consumables - .info - } - - - - - - - - - { - Duel.mutate((draft) => { - draft.protagonist.consumables = consumables; - }); - }} - justify={{ initial: "center", sm: "start" }} - /> - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx deleted file mode 100644 index 1ec26568a..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { - Badge, - Box, - Flex, - Heading, - Link, - Popover, - Switch, - Text, -} from "@radix-ui/themes"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { Tankopedia } from "../../../../stores/tankopedia"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -export function Equalizer() { - const { strings } = useLocale(); - const equalize = Duel.use((state) => state.equalize); - - return ( - - - - {strings.website.tools.tankopedia.configuration.equalizer.title}{" "} - - {strings.website.tools.tankopedia.configuration.equalizer.beta} - - - - - { - Duel.mutate((draft) => { - draft.equalize = !draft.equalize; - }); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - }); - }} - width="100%" - style={{ - cursor: "pointer", - borderRadius: "var(--radius-3)", - backgroundImage: `url(${asset("icons/game_mode_banners/45.webp")})`, - backgroundSize: "cover", - backgroundPosition: "center", - overflow: "hidden", - }} - > - - - {strings.website.tools.tankopedia.configuration.equalizer.equalize} - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx deleted file mode 100644 index 69a428b3f..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equipment.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Button, Flex, Heading } from "@radix-ui/themes"; -import { awaitableEquipmentDefinitions } from "../../../../core/awaitables/equipmentDefinitions"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { EquipmentManager } from "../../../EquipmentManager"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -const equipmentDefinitions = await awaitableEquipmentDefinitions; - -export function Equipment() { - const protagonist = Duel.use((state) => state.protagonist); - const equipmentPreset = - equipmentDefinitions.presets[protagonist.tank.equipment_preset]; - const equipmentMatrix = Duel.use( - (state) => state.protagonist.equipmentMatrix - ); - const { strings } = useLocale(); - - return ( - - - - {strings.website.tools.tankopedia.configuration.equipment.title} - - - - - { - Duel.mutate((draft) => { - draft.protagonist.equipmentMatrix = matrix; - }); - }} - /> - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx deleted file mode 100644 index 16423a884..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Miscellaneous.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { Flex, Heading } from "@radix-ui/themes"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { GenericTankComponentButton } from "../../../ModuleButtons/GenericTankComponentButton"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -export function Miscellaneous() { - const camouflage = Duel.use((state) => state.protagonist.camouflage); - const cooldownBooster = Duel.use( - (state) => state.protagonist.cooldownBooster - ); - const { tank } = Duel.use((state) => state.protagonist); - const { strings } = useLocale(); - - return ( - - - {strings.website.tools.tankopedia.configuration.miscellaneous.title} - - - - {!tank.fixed_camouflage && ( - { - Duel.mutate((draft) => { - draft.protagonist.camouflage = !camouflage; - }); - }} - /> - )} - 0} - banner={ - cooldownBooster === 0 - ? undefined - : cooldownBooster === 1 - ? "#afb0abc0" - : cooldownBooster === 2 - ? "#5f72cbc0" - : "#9b3cc0c0" - } - iconStyles={{ - transform: "translate(-50%, -50%) scale(0.8)", - }} - onClick={() => { - Duel.mutate((draft) => { - draft.protagonist.cooldownBooster++; - if (draft.protagonist.cooldownBooster === 4) - draft.protagonist.cooldownBooster = 0; - }); - }} - /> - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx deleted file mode 100644 index a290cf245..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx +++ /dev/null @@ -1,441 +0,0 @@ -import { - asset, - EngineDefinition, - formatCompact, - GunDefinition, - ModuleType, - tankIcon, - TIER_ROMAN_NUMERALS, - TrackDefinition, - TurretDefinition, - type TankDefinition, - type Unlock, -} from "@blitzkit/core"; -import { - Button, - ChevronDownIcon, - Flex, - Heading, - IconButton, - Text, - Tooltip, -} from "@radix-ui/themes"; -import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { LinkI18n } from "../../../LinkI18n"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -type ModuleDefinition = - | TurretDefinition - | GunDefinition - | TrackDefinition - | EngineDefinition; - -function extractModule(raw: ModuleDefinition | TankDefinition) { - if ("gun_type" in raw) { - return raw; - } else - return raw as Exclude; -} - -interface ModuleWithUnlocks { - unlocks: Unlock[]; -} - -const moduleTypeTypeIconName: Record = { - [ModuleType.MODULE_TYPE_VEHICLE]: "", - [ModuleType.MODULE_TYPE_ENGINE]: "engine", - [ModuleType.MODULE_TYPE_TRACKS]: "chassis", - [ModuleType.MODULE_TYPE_TURRET]: "turret", - [ModuleType.MODULE_TYPE_GUN]: "gun", -}; - -function ModuleButton({ - tier, - selected, - unlock, - top, - tooltip, - onClick, -}: { - unlock: Unlock; - tier: number; - selected: boolean; - top: boolean; - tooltip?: string; - onClick: () => void; -}) { - const isTank = unlock.type === ModuleType.MODULE_TYPE_VEHICLE; - const { locale } = useLocale(); - - const button = ( - - - {TIER_ROMAN_NUMERALS[tier]} - - - {unlock.cost!.value > 0 && ( - - - {formatCompact(locale, unlock.cost!.value)} - - - {unlock.cost!.type} - - )} - - {ModuleType[unlock.type]} - - ); - - const node = isTank ? ( - - {button} - - ) : ( - button - ); - - if (!tooltip) return node; - - return {node}; -} - -const tankDefinitions = await awaitableTankDefinitions; - -export function Modules() { - const tank = Duel.use((state) => state.protagonist.tank); - const hasUpgrades = - tank.turrets.length > 1 || - tank.turrets[0].guns.length > 1 || - tank.engines.length > 1 || - tank.tracks.length > 1; - const turret = Duel.use((state) => state.protagonist.turret); - const gun = Duel.use((state) => state.protagonist.gun); - const engine = Duel.use((state) => state.protagonist.engine); - const track = Duel.use((state) => state.protagonist.track); - const turret0 = tank.turrets[0]; - const gun0 = turret0.guns[0]; - const engine0 = tank.engines[0]; - const track0 = tank.tracks[0]; - const topTurret = tank.turrets.at(-1)!; - const topGun = topTurret.guns.at(-1)!; - const topEngine = tank.engines.at(-1)!; - const topTrack = tank.tracks.at(-1)!; - const { strings, unwrap } = useLocale(); - - function setByUnlock(unlock: Unlock) { - Duel.mutate((draft) => { - if (unlock.type === ModuleType.MODULE_TYPE_TURRET) { - draft.protagonist.turret = draft.protagonist.tank.turrets.find( - (turret) => turret.id === unlock.id, - )!; - - // Check if current gun exists in new turret - const gunInNewTurret = draft.protagonist.turret.guns.find( - (gun) => gun.id === draft.protagonist.gun.id, - ); - - if (gunInNewTurret) { - // Gun exists - get it FROM this turret - draft.protagonist.gun = gunInNewTurret; - draft.protagonist.shell = gunInNewTurret.shells[0]; - } else { - // Gun doesn't exist - select top gun from new turret - draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!; - draft.protagonist.shell = draft.protagonist.gun.shells[0]; - } - } else if (unlock.type === ModuleType.MODULE_TYPE_GUN) { - const gunInTurret = draft.protagonist.turret.guns.find( - (gun) => gun.id === unlock.id, - ); - if (gunInTurret) { - draft.protagonist.gun = gunInTurret; - draft.protagonist.shell = gunInTurret.shells[0]; - } else { - // TODO: warn somehow? - const suitableTurret = draft.protagonist.tank.turrets.find((turret) => - turret.guns.some((gun) => gun.id === unlock.id), - )!; - const gunInSuitableTurret = suitableTurret.guns.find( - (gun) => gun.id === unlock.id, - )!; - - draft.protagonist.turret = suitableTurret; - draft.protagonist.gun = gunInSuitableTurret; - draft.protagonist.shell = gunInSuitableTurret.shells[0]; - } - } else if (unlock.type === ModuleType.MODULE_TYPE_ENGINE) { - draft.protagonist.engine = draft.protagonist.tank.engines.find( - (engine) => engine.id === unlock.id, - )!; - } else if (unlock.type === ModuleType.MODULE_TYPE_TRACKS) { - draft.protagonist.track = draft.protagonist.tank.tracks.find( - (track) => track.id === unlock.id, - )!; - } - }); - } - - function tree(type: ModuleType, unlocks: Unlock[]) { - return ( - - {unlocks.map((unlock, index) => { - const first = index === 0; - const last = index === unlocks.length - 1; - const central = !first && !last; - let module: ModuleDefinition | TankDefinition | undefined = undefined; - - if (unlock.type === ModuleType.MODULE_TYPE_TRACKS) { - module = tank.tracks.find((track) => track.id === unlock.id)!; - } else if (unlock.type === ModuleType.MODULE_TYPE_ENGINE) { - module = tank.engines.find((engine) => engine.id === unlock.id)!; - } else if (unlock.type === ModuleType.MODULE_TYPE_TURRET) { - module = tank.turrets.find((turret) => turret.id === unlock.id)!; - } else if (unlock.type === ModuleType.MODULE_TYPE_GUN) { - module = tank.turrets - .find((turret) => - turret.guns.some((gun) => gun.id === unlock.id), - )! - .guns.find((gun) => gun.id === unlock.id)!; - } else if (unlock.type === ModuleType.MODULE_TYPE_VEHICLE) { - module = tankDefinitions.tanks[unlock.id]; - } - - if (module === undefined) return null; - - const tooltip = unwrap(module.name!); - - return ( - - {unlock.cost!.value > 0 && ( - <> - {unlocks.length > 1 && ( -
- )} - - -
- - - - )} - - setByUnlock(unlock)} - /> - - {(extractModule(module) as ModuleWithUnlocks).unlocks && - (extractModule(module) as ModuleWithUnlocks).unlocks!.length > - 1 && ( -
- )} - - {(extractModule(module) as ModuleWithUnlocks).unlocks && - tree( - type, - (extractModule(module) as ModuleWithUnlocks).unlocks!, - )} - - ); - })} - - ); - } - - return ( - - - - {strings.website.tools.tankopedia.configuration.modules.title} - - - {hasUpgrades && ( - <> - - - - )} - - - - {tree(ModuleType.MODULE_TYPE_ENGINE, [ - { - cost: { type: "xp", value: 0 }, - id: engine0.id, - type: ModuleType.MODULE_TYPE_ENGINE, - }, - ])} - {tree(ModuleType.MODULE_TYPE_TRACKS, [ - { - cost: { type: "xp", value: 0 }, - id: track0.id, - type: ModuleType.MODULE_TYPE_TRACKS, - }, - ])} - {tree(ModuleType.MODULE_TYPE_TURRET, [ - { - cost: { type: "xp", value: 0 }, - id: turret0.id, - type: ModuleType.MODULE_TYPE_TURRET, - }, - ])} - {tree(ModuleType.MODULE_TYPE_GUN, [ - { - cost: { type: "xp", value: 0 }, - id: gun0.id, - type: ModuleType.MODULE_TYPE_GUN, - }, - ])} - - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx deleted file mode 100644 index 258b23141..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Provisions.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { availableProvisions } from "@blitzkit/core"; -import { Button, Flex, Heading } from "@radix-ui/themes"; -import { awaitableProvisionDefinitions } from "../../../../core/awaitables/provisionDefinitions"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { ProvisionsManager } from "../../../ProvisionsManager"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -const provisionDefinitions = await awaitableProvisionDefinitions; - -export function Provisions() { - const { tank, gun } = Duel.use((state) => state.protagonist); - const provisions = Duel.use((state) => state.protagonist.provisions); - const provisionsList = availableProvisions(tank, gun, provisionDefinitions); - const { strings } = useLocale(); - - return ( - - - - {strings.website.tools.tankopedia.configuration.provisions.title} - - - - - provision.id)} - selected={provisions} - disabled={tank.max_provisions === provisions.length} - onChange={(provisions) => { - Duel.mutate((draft) => { - draft.protagonist.provisions = provisions; - }); - }} - /> - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx deleted file mode 100644 index 0e16fd790..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { InfoCircledIcon } from "@radix-ui/react-icons"; -import { - Button, - Flex, - Heading, - IconButton, - Popover, - Text, -} from "@radix-ui/themes"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Tankopedia } from "../../../../stores/tankopedia"; -import { CrewSkillManager } from "../../../CrewSkillManager"; -import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper"; - -export function Skills() { - const skillLevels = Tankopedia.use((state) => state.skills); - const { strings } = useLocale(); - - return ( - - - - - {strings.website.tools.tankopedia.configuration.skills.title} - - - - - - - - - - - - - {strings.website.tools.tankopedia.configuration.skills.info} - - - - - - - - - - - { - Tankopedia.mutate((draft) => { - draft.skills = skills; - }); - }} - /> - - ); -} diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/index.tsx deleted file mode 100644 index c304e4522..000000000 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Flex } from "@radix-ui/themes"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; -import { Characteristics } from "./components/Characteristics"; -import { CompareOptions } from "./components/Characteristics/components/CompareOptions"; -import { StatSearch } from "./components/Characteristics/components/StatSearch"; -import { Consumables } from "./components/Consumables"; -import { Equalizer } from "./components/Equalizer"; -import { Equipment } from "./components/Equipment"; -import { Miscellaneous } from "./components/Miscellaneous"; -import { Modules } from "./components/Modules"; -import { Provisions } from "./components/Provisions"; -import { Skills } from "./components/Skills"; - -export function CharacteristicsSection({ - skeleton, -}: MaybeSkeletonComponentProps) { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/GameModeSection.tsx b/packages/website/src/components/Tankopedia/GameModeSection.tsx deleted file mode 100644 index a43a3fb94..000000000 --- a/packages/website/src/components/Tankopedia/GameModeSection.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { Box, Flex, Heading, Text } from "@radix-ui/themes"; -import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions"; -import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; -import { useLocale } from "../../hooks/useLocale"; -import { Duel } from "../../stores/duel"; - -const [gameDefinitions, consumableDefinitions, provisionDefinitions] = - await Promise.all([ - awaitableGameDefinitions, - awaitableConsumableDefinitions, - awaitableProvisionDefinitions, - ]); - -export function GameModeSection() { - const { unwrap, strings } = useLocale(); - const tank = Duel.use((state) => state.protagonist.tank); - const roles = Object.entries(tank.roles); - - if (roles.length === 0) return null; - - return ( - - - {strings.website.tools.tankopedia.game_modes.title} - - - - {roles.map(([key, value]) => { - const id = Number(key); - const gameMode = gameDefinitions.gameModes[id]; - - return ( - - - - - - {unwrap(gameMode.name!)} - - - - {gameDefinitions.roles[value].provisions.map((id) => { - const provisions = provisionDefinitions.provisions[id]; - - return ( - - - {unwrap(provisions.name!)} - {unwrap(provisions.name!)} - - - ); - })} - - {gameDefinitions.roles[value].consumables.map((id) => { - const consumable = consumableDefinitions.consumables[id]; - - return ( - - - {unwrap(consumable.name!)} - {unwrap(consumable.name!)} - - - ); - })} - - - - ); - })} - - - ); -} diff --git a/packages/website/src/components/Tankopedia/GuideSection/index.tsx b/packages/website/src/components/Tankopedia/GuideSection/index.tsx deleted file mode 100644 index ab6e61896..000000000 --- a/packages/website/src/components/Tankopedia/GuideSection/index.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { CaretRightIcon } from "@radix-ui/react-icons"; -import { Box, Flex, Link, Text } from "@radix-ui/themes"; -import { Var } from "../../../core/radix/var"; -import type { TankGuide } from "../../../types/tankGuide"; - -interface Props { - guide: TankGuide; -} - -export function GuideSection({ guide }: Props) { - return ( - - - - - - - - GuidesBlitz - - - - {guide.description} - - - - - - - - Read the full guide - - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/CustomShellButton.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/CustomShellButton.tsx deleted file mode 100644 index 95a04c441..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/CustomShellButton.tsx +++ /dev/null @@ -1,328 +0,0 @@ -import { ShellType } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { - Button, - Flex, - IconButton, - Popover, - Select, - Slider, - Text, - TextField, -} from "@radix-ui/themes"; -import { throttle } from "lodash-es"; -import { type ChangeEvent } from "react"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { Duel } from "../../../../../../stores/duel"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; - -export function CustomShellButton() { - const customShell = Tankopedia.use((state) => state.customShell); - const gun = Duel.use((state) => state.protagonist.gun); - const antagonistShell = Duel.use((state) => state.antagonist.shell); - const { strings } = useLocale(); - - return ( - { - if (!open || customShell) return; - - Tankopedia.mutate((draft) => { - draft.customShell = antagonistShell; - }); - }} - > - - - custom shell - - - - - {customShell && ( - <> - - - - { - strings.website.tools.tankopedia.sandbox.dynamic - .custom_shell.type - } - - { - const type = Number(value) as ShellType; - const modified = { ...customShell, type }; - - switch (type) { - case ShellType.SHELL_TYPE_AP: { - modified.normalization = 5; - modified.ricochet = 70; - modified.explosion_radius = undefined; - break; - } - case ShellType.SHELL_TYPE_APCR: { - modified.normalization = 2; - modified.ricochet = 70; - modified.explosion_radius = undefined; - break; - } - case ShellType.SHELL_TYPE_HEAT: { - modified.normalization = undefined; - modified.ricochet = undefined; - modified.explosion_radius = undefined; - } - case ShellType.SHELL_TYPE_HE: { - modified.normalization = undefined; - modified.ricochet = undefined; - modified.explosion_radius = - gun.shells.find( - (shell) => shell.type === ShellType.SHELL_TYPE_HE, - )?.explosion_radius ?? 1; - break; - } - } - - Tankopedia.mutate((draft) => { - draft.customShell = modified; - }); - }} - > - - - - - {strings.common.shells.ap} - - - {strings.common.shells.ap_cr} - - - {strings.common.shells.hc} - - - {strings.common.shells.he} - - - - - - - - { - strings.website.tools.tankopedia.sandbox.dynamic - .custom_shell.caliber - } - - { - Tankopedia.mutate((draft) => { - draft.customShell!.caliber = event.target.valueAsNumber; - }); - }} - onFocus={(event) => event.target.select()} - onKeyDown={(event) => { - event.stopPropagation(); - - if (event.key === "Enter") { - (event.target as HTMLInputElement).blur(); - } else if (event.key === "Escape") { - (event.target as HTMLInputElement).valueAsNumber = - customShell.caliber; - (event.target as HTMLInputElement).blur(); - } - }} - > - - {literals(strings.common.units.mm, { value: "" })} - - - - - - - { - strings.website.tools.tankopedia.sandbox.dynamic - .custom_shell.penetration - } - - ) => { - Tankopedia.mutate((draft) => { - draft.customShell!.penetration!.near = - event.target.valueAsNumber; - }); - }} - onFocus={(event) => event.target.select()} - onKeyDown={(event) => { - event.stopPropagation(); - - if (event.key === "Enter") { - (event.target as HTMLInputElement).blur(); - } else if (event.key === "Escape") { - (event.target as HTMLInputElement).valueAsNumber = - customShell.penetration!.near; - (event.target as HTMLInputElement).blur(); - } - }} - > - - {literals(strings.common.units.mm, { value: "" })} - - - - - {(customShell.type === ShellType.SHELL_TYPE_AP || - customShell.type === ShellType.SHELL_TYPE_APCR) && ( - <> - - - { - strings.website.tools.tankopedia.sandbox.dynamic - .custom_shell.normalization - } - - - - - {literals(strings.common.units.deg, { - value: (customShell.normalization ?? 0).toFixed(0), - })} - - { - console.log("asd"); - - Tankopedia.mutate((draft) => { - draft.customShell!.normalization = value; - }); - }} - /> - - - - - - { - strings.website.tools.tankopedia.sandbox.dynamic - .custom_shell.ricochet - } - - - - - {literals(strings.common.units.deg, { - value: (customShell.ricochet ?? 90).toFixed(0), - })} - - { - Tankopedia.mutate((draft) => { - draft.customShell!.ricochet = value; - }); - }, 1000)} - /> - - - - )} - - {customShell.type === ShellType.SHELL_TYPE_HE && ( - - Explosion radius - ) => { - Tankopedia.mutate((draft) => { - draft.customShell!.explosion_radius = - event.target.valueAsNumber; - }); - }} - onFocus={(event) => event.target.select()} - onKeyDown={(event) => { - event.stopPropagation(); - - if (event.key === "Enter") { - (event.target as HTMLInputElement).blur(); - } else if (event.key === "Escape") { - (event.target as HTMLInputElement).valueAsNumber = - customShell.explosion_radius ?? 1; - (event.target as HTMLInputElement).blur(); - } - }} - > - m - - - )} - - - - - - - - - - )} - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx deleted file mode 100644 index 6da4bfb18..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/DynamicArmorSwitcher.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { IconButton } from "@radix-ui/themes"; -import { useArmor } from "../../../../../../hooks/useArmor"; -import { Duel } from "../../../../../../stores/duel"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; - -export function DynamicArmorSwitcher() { - const tank = Duel.use((state) => state.protagonist.tank); - const isDynamicArmorActive = Duel.use((state) => - state.protagonist.consumables.includes(73) - ); - const { hasDynamicArmor } = useArmor(tank.id); - - if (!hasDynamicArmor) return null; - - return ( - { - Duel.mutate((draft) => { - if (draft.protagonist.consumables.includes(73)) { - draft.protagonist.consumables = - draft.protagonist.consumables.filter((id) => id !== 73); - } else { - if (draft.protagonist.consumables.length === tank.max_consumables) { - draft.protagonist.consumables[tank.max_consumables - 1] = 73; - } else { - draft.protagonist.consumables.push(73); - } - } - }); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - }); - }} - > - Dynamic Armor - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.css b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.css deleted file mode 100644 index 81a69216d..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.css +++ /dev/null @@ -1,8 +0,0 @@ -.quick-inputs { - left: -5rem; -} - -.quick-inputs:focus-within, -.tank-sandbox-container:hover .quick-inputs { - left: 1rem; -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.tsx deleted file mode 100644 index d4fd3d5b6..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { HeightIcon, WidthIcon } from "@radix-ui/react-icons"; -import { Flex, TextField } from "@radix-ui/themes"; -import type { QuicklimeEvent } from "quicklime"; -import { useEffect, useRef } from "react"; -import { degToRad, radToDeg } from "three/src/math/MathUtils.js"; -import { applyPitchYawLimits } from "../../../../../../../core/blitz/applyPitchYawLimits"; -import { - type ModelTransformEventData, - modelTransformEvent, -} from "../../../../../../../core/blitzkit/modelTransform"; -import { useEquipment } from "../../../../../../../hooks/useEquipment"; -import { useTankModelDefinition } from "../../../../../../../hooks/useTankModelDefinition"; -import { Duel } from "../../../../../../../stores/duel"; -import "./index.css"; - -export function QuickInputs() { - const protagonist = Duel.use((state) => state.protagonist); - const yawInput = useRef(null); - const pitchInput = useRef(null); - const tankModelDefinition = useTankModelDefinition(); - const turretModelDefinition = - tankModelDefinition.turrets[protagonist.turret.id]; - const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id]; - const initialTurretPitch = - tankModelDefinition.initial_turret_rotation?.pitch ?? 0; - const hasImprovedVerticalStabilizer = useEquipment(122); - const hasDownImprovedVerticalStabilizer = useEquipment(124); - - function displayPitch(pitch: number) { - return radToDeg(pitch) - initialTurretPitch; - } - - useEffect(() => { - function syncInputValues(data: ModelTransformEventData) { - if (!pitchInput.current || !yawInput.current) return; - - pitchInput.current.value = displayPitch(data.pitch).toFixed(1); - yawInput.current.value = radToDeg(data.yaw).toFixed(1); - } - - function handleTransformEvent( - event: QuicklimeEvent, - ) { - syncInputValues(event.data); - } - - modelTransformEvent.on(handleTransformEvent); - syncInputValues(modelTransformEvent.last!); - - return () => { - modelTransformEvent.off(handleTransformEvent); - }; - }, [initialTurretPitch]); - - return ( - - { - const value = Number(yawInput.current!.value); - - if (isNaN(value)) { - yawInput.current!.value = radToDeg( - modelTransformEvent.last!.yaw, - ).toFixed(1); - return; - } - - const [pitch, yaw] = applyPitchYawLimits( - modelTransformEvent.last!.pitch, - degToRad(value), - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - - modelTransformEvent.dispatch({ pitch, yaw }); - yawInput.current!.value = radToDeg( - modelTransformEvent.last!.yaw, - ).toFixed(1); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - yawInput.current?.blur(); - } - }} - onFocus={() => yawInput.current?.focus()} - ref={yawInput} - > - - - - - - - { - const value = Number(pitchInput.current!.value); - if (isNaN(value)) { - pitchInput.current!.value = displayPitch( - modelTransformEvent.last!.pitch, - ).toFixed(1); - return; - } - const [pitch, yaw] = applyPitchYawLimits( - degToRad(value + initialTurretPitch), - modelTransformEvent.last!.yaw, - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - modelTransformEvent.dispatch({ pitch, yaw }); - pitchInput.current!.value = displayPitch( - modelTransformEvent.last!.pitch, - ).toFixed(1); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - pitchInput.current?.blur(); - } - }} - onFocus={() => pitchInput.current?.focus()} - ref={pitchInput} - > - - - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx deleted file mode 100644 index 7fdb09d73..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { literals } from "@blitzkit/i18n/src/literals"; -import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from "@radix-ui/react-icons"; -import { Button, Checkbox, Flex, Text } from "@radix-ui/themes"; -import { Suspense } from "react"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent"; -import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants"; -import type { MaybeSkeletonComponentProps } from "../../../../../../types/maybeSkeletonComponentProps"; -import type { ThicknessRange } from "../../../../../Armor/components/StaticArmor"; -import { DynamicArmorSwitcher } from "./DynamicArmorSwitcher"; - -type ThicknessesProps = MaybeSkeletonComponentProps & { - thicknessRange: ThicknessRange; -}; - -export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) { - const display = Tankopedia.use((state) => state.display); - const showExternalModules = TankopediaPersistent.use( - (state) => state.showExternalModules - ); - const showSpacedArmor = TankopediaPersistent.use( - (state) => state.showSpacedArmor - ); - const showPrimaryArmor = TankopediaPersistent.use( - (state) => state.showPrimaryArmor - ); - const editStatic = Tankopedia.use((state) => state.editStatic); - const { strings } = useLocale(); - - return ( - - - { - TankopediaPersistent.mutate((draft) => { - draft.showPrimaryArmor = !draft.showPrimaryArmor; - }); - Tankopedia.mutate((draft) => { - draft.highlightArmor = undefined; - }); - }} - > - - - {literals(strings.common.units.mm, { - value: (thicknessRange.value * 1.5).toFixed(0), - })} - - - {strings.website.tools.tankopedia.sandbox.static.primary} - - - {literals(strings.common.units.mm, { value: 0 })} - - - - - {showPrimaryArmor ? : } - - - - { - TankopediaPersistent.mutate((draft) => { - draft.showSpacedArmor = !draft.showSpacedArmor; - }); - Tankopedia.mutate((draft) => { - draft.highlightArmor = undefined; - }); - }} - > - - - {literals(strings.common.units.mm, { - value: thicknessRange.value.toFixed(0), - })} - - - {strings.website.tools.tankopedia.sandbox.static.spaced} - - - {literals(strings.common.units.mm, { value: 0 })} - - - - - {showSpacedArmor ? : } - - - - { - TankopediaPersistent.mutate((draft) => { - draft.showExternalModules = !draft.showExternalModules; - }); - Tankopedia.mutate((draft) => { - draft.highlightArmor = undefined; - }); - }} - > - - - {strings.website.tools.tankopedia.sandbox.static.external} - - - - - {showExternalModules ? : } - - - - - - - { - Tankopedia.mutate((draft) => { - draft.editStatic = !draft.editStatic; - draft.highlightArmor = undefined; - }); - }} - > - - {strings.website.tools.tankopedia.sandbox.static.edit.label} - - - - - - - - - {!skeleton && ( - - - - )} - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx deleted file mode 100644 index 10841eeda..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ /dev/null @@ -1,517 +0,0 @@ -import { - asset, - resolvePenetrationCoefficient, - TIER_ROMAN_NUMERALS, - uniqueGuns, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { - Crosshair1Icon, - EnterFullScreenIcon, - ExitFullScreenIcon, - EyeOpenIcon, - ShadowInnerIcon, - ShadowNoneIcon, - SunIcon, -} from "@radix-ui/react-icons"; -import { - Button, - Dialog, - DropdownMenu, - Flex, - IconButton, - Popover, - SegmentedControl, - Tabs, - Text, - Tooltip, -} from "@radix-ui/themes"; -import { Suspense, useState, type RefObject } from "react"; -import { Pose, poseEvent } from "../../../../../core/blitzkit/pose"; -import { useEquipment } from "../../../../../hooks/useEquipment"; -import { useFullScreen } from "../../../../../hooks/useFullScreen"; -import { useFullscreenAvailability } from "../../../../../hooks/useFullscreenAvailability"; -import { useLocale } from "../../../../../hooks/useLocale"; -import { Duel } from "../../../../../stores/duel"; -import { Tankopedia } from "../../../../../stores/tankopedia"; -import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent"; -import { TankopediaDisplay } from "../../../../../stores/tankopediaPersistent/constants"; -import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps"; -import type { ThicknessRange } from "../../../../Armor/components/StaticArmor"; -import { EqualIcon } from "../../../../EqualIcon"; -import { ModuleButton } from "../../../../ModuleButtons/ModuleButton"; -import { ScreenshotButton } from "../../../../ScreenshotButton"; -import { SmallTankIcon } from "../../../../SmallTankIcon"; -import { TankSearch } from "../../../../TankSearch"; -import { CustomShellButton } from "./components/CustomShellButton"; -import { DynamicArmorSwitcher } from "./components/DynamicArmorSwitcher"; -import { QuickInputs } from "./components/QuickInputs"; -import { Thicknesses } from "./components/Thicknesses"; - -type OptionsProps = MaybeSkeletonComponentProps & { - thicknessRange: ThicknessRange; - canvas: RefObject; -}; - -export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { - const hasCustomShell = Tankopedia.use( - (state) => state.customShell !== undefined, - ); - const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); - const isFullScreen = useFullScreen(); - const advancedHighlighting = TankopediaPersistent.use( - (state) => state.advancedHighlighting, - ); - const fullScreenAvailable = useFullscreenAvailability(true); - const protagonistTank = Duel.use((state) => state.protagonist.tank); - const antagonistGun = Duel.use((state) => state.antagonist.gun); - const antagonistShell = Duel.use((state) => state.antagonist.shell); - const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false); - const antagonistTank = Duel.use((state) => state.antagonist.tank); - const hasCalibratedShells = useEquipment(103, true); - const [tab, setTab] = useState("search"); - const hasEnhancedArmor = useEquipment(110); - const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets); - const { strings, unwrap } = useLocale(); - const revealed = Tankopedia.use((state) => state.revealed); - const disturbed = Tankopedia.use((state) => state.disturbed); - const highGraphics = TankopediaPersistent.use((state) => state.highGraphics); - const equalize = Duel.use((state) => state.equalize); - const equalizer = Duel.use((state) => state.antagonist.tank.equalizer); - - return ( - <> - {disturbed && } - - - - - {!hasCustomShell && ( - - {literals(strings.common.units.mm, { - value: ( - resolvePenetrationCoefficient( - hasCalibratedShells, - equalize, - antagonistShell.type, - equalizer, - ) * antagonistShell.penetration!.near - ).toFixed(0), - })} - - )} - - {antagonistUniqueGuns.length > 1 && ( - - - - - {TIER_ROMAN_NUMERALS[antagonistGun.tier]} - - - Antagonist Gun - - - - - - {[...antagonistUniqueGuns].reverse().map(({ gun, turret }) => ( - - Duel.mutate((draft) => { - draft.antagonist.turret = turret; - draft.antagonist.gun = gun; - draft.antagonist.shell = gun.shells[0]; - }) - } - selected={gun.id === antagonistGun.id} - discriminator={TIER_ROMAN_NUMERALS[gun.tier]} - secondaryDiscriminator={ - - {literals(strings.common.units.mm, { - value: gun.shells[0].caliber.toFixed(0), - })} - - } - /> - ))} - - - - )} - - - {antagonistGun.shells.map((thisShell) => ( - { - Duel.mutate((draft) => { - draft.antagonist.shell = thisShell; - }); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - draft.customShell = undefined; - }); - }} - > - {unwrap(thisShell.name!)} - - ))} - - - - - { - Duel.mutate((draft) => { - draft.antagonist.equipmentMatrix[0][0] = hasCalibratedShells - ? 0 - : 1; - }); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - }); - }} - > - Calibrated Shells - - { - Duel.mutate((draft) => { - draft.protagonist.equipmentMatrix[1][1] = hasEnhancedArmor - ? 0 - : -1; - }); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - }); - }} - > - Enhanced Armor - - - - {!skeleton && ( - - - - )} - - { - Duel.mutate((draft) => { - draft.equalize = !draft.equalize; - }); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - }); - }} - > - - - - - - - {disturbed && ( - <> - {requestedDisplay === TankopediaDisplay.DynamicArmor && ( - { - TankopediaPersistent.mutate((draft) => { - draft.advancedHighlighting = !draft.advancedHighlighting; - }); - }} - color={advancedHighlighting ? undefined : "gray"} - variant={advancedHighlighting ? "solid" : "surface"} - > - {advancedHighlighting ? ( - - ) : ( - - )} - - )} - - - - - - - - - - poseEvent.emit(Pose.HullDown)} - > - {strings.website.tools.tankopedia.sandbox.poses.hull_down} - - - poseEvent.emit(Pose.FaceHug)} - > - {strings.website.tools.tankopedia.sandbox.poses.face_hug} - - - poseEvent.emit(Pose.Default)} - > - {strings.website.tools.tankopedia.sandbox.poses.default} - - - - - { - TankopediaPersistent.mutate((draft) => { - draft.highGraphics = !draft.highGraphics; - }); - }} - color={highGraphics ? undefined : "gray"} - variant={highGraphics ? "solid" : "surface"} - > - - - - - - {fullScreenAvailable && ( - { - if (isFullScreen) { - document.exitFullscreen(); - } else document.body.requestFullscreen(); - }} - color={isFullScreen ? undefined : "gray"} - variant={isFullScreen ? "solid" : "surface"} - > - {isFullScreen ? ( - - ) : ( - - )} - - )} - - )} - - - - { - Tankopedia.mutate((draft) => { - draft.disturbed = true; - draft.requestedDisplay = Number(value); - }); - }} - > - - - - - - - - - - - - - - - - - - - - - - - - {requestedDisplay === TankopediaDisplay.DynamicArmor && ( - - - - - - - - {strings.website.common.tank_search.select_dialog_title} - - - - { - Duel.mutate((draft) => { - draft.antagonist.tank = tank; - draft.antagonist.engine = tank.engines.at(-1)!; - draft.antagonist.track = tank.tracks.at(-1)!; - draft.antagonist.turret = tank.turrets.at(-1)!; - draft.antagonist.gun = - draft.antagonist.turret.guns.at(-1)!; - draft.antagonist.shell = draft.antagonist.gun.shells[0]; - }); - setAntagonistSelectorOpen(false); - }} - /> - - - - )} - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/ScrollHint/index.css b/packages/website/src/components/Tankopedia/HeroSection/components/ScrollHint/index.css deleted file mode 100644 index cca875423..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/ScrollHint/index.css +++ /dev/null @@ -1,27 +0,0 @@ -.scroll-hint { - position: absolute; - animation: dropFade 1s ease-in-out infinite; -} - -@keyframes dropFade { - 0% { - top: 0; - opacity: 1; - } - 20% { - top: 0; - opacity: 1; - } - 80% { - top: 50%; - opacity: 1; - } - 99% { - top: 50%; - opacity: 0; - } - 100% { - top: 0; - opacity: 1; - } -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/ScrollHint/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/ScrollHint/index.tsx deleted file mode 100644 index 062dee4f2..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/ScrollHint/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Box } from "@radix-ui/themes"; -import { Var } from "../../../../../core/radix/var"; -import { Tankopedia } from "../../../../../stores/tankopedia"; -import "./index.css"; - -export function ScrollHint() { - const disturbed = Tankopedia.use((state) => state.disturbed); - - if (disturbed) return null; - - return ( - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/components/Tracker.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/components/Tracker.tsx deleted file mode 100644 index 2b6d7071d..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/components/Tracker.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Box, Progress } from "@radix-ui/themes"; -import { useStableProgress } from "../../../../../../../hooks/useStableProgress"; - -interface Props { - fontSize?: string; -} - -export function Tracker({ fontSize }: Props) { - const progress = useStableProgress(); - - return ( - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx deleted file mode 100644 index 694d24f12..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/Title/index.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Flex, Heading } from "@radix-ui/themes"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { Duel } from "../../../../../../stores/duel"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { Tracker } from "./components/Tracker"; - -export function Title() { - const { unwrap } = useLocale(); - const protagonist = Duel.use((state) => state.protagonist.tank); - const revealed = Tankopedia.use((state) => state.revealed); - const disturbed = Tankopedia.use((state) => state.disturbed); - const name = unwrap(protagonist.name!); - const fontSize = revealed - ? disturbed - ? "1.5rem" - : "2rem" - : `min(12vh, ${75 / name.length}vw)`; - - return ( - { - event.preventDefault(); - - Tankopedia.mutate((draft) => { - draft.revealed = true; - }); - }} - direction="column" - position="absolute" - align="center" - justify="center" - top={revealed ? (disturbed ? "3rem" : "6rem") : "50%"} - left="50%" - width="100%" - height={revealed ? fontSize : "100%"} - style={{ - pointerEvents: revealed ? "none" : undefined, - transitionDuration: "1s", - transform: "translate(-50%, -50%)", - }} - > - - {name} - - - {!revealed && } - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/AutoClear.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/AutoClear.tsx deleted file mode 100644 index 7c095b7cd..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/AutoClear.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useFrame } from '@react-three/fiber'; - -export function AutoClear() { - useFrame(({ gl }) => { - gl.clear(); - }, 0); - - return null; -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx deleted file mode 100644 index 191745600..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx +++ /dev/null @@ -1,263 +0,0 @@ -import { I_HAT, J_HAT } from "@blitzkit/core"; -import { OrbitControls } from "@react-three/drei"; -import { invalidate, useFrame, useThree } from "@react-three/fiber"; -import type { QuicklimeEvent } from "quicklime"; -import { useEffect, useRef } from "react"; -import { PerspectiveCamera, Vector3 } from "three"; -import { OrbitControls as OrbitControlsClass } from "three-stdlib"; -import { awaitableModelDefinitions } from "../../../../../../core/awaitables/modelDefinitions"; -import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits"; -import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment"; -import { Pose, poseEvent } from "../../../../../../core/blitzkit/pose"; -import { controlsEnabledEvent } from "../../../../../../core/controlsEnabled"; -import { Duel } from "../../../../../../stores/duel"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; - -const poseDistances: Record = { - [Pose.HullDown]: 15, - [Pose.FaceHug]: 5, - [Pose.Default]: -1, -}; - -const modelDefinitions = await awaitableModelDefinitions; - -interface ControlsProps { - autoRotate?: boolean; - zoomable?: boolean; - enableRotate?: boolean; -} - -export function Controls({ - autoRotate = true, - zoomable = true, - enableRotate = true, -}: ControlsProps) { - const camera = useThree((state) => state.camera as PerspectiveCamera); - const canvas = useThree((state) => state.gl.domElement); - const orbitControls = useRef(null); - const protagonistTurret = Duel.use((state) => state.protagonist.turret); - const antagonistTurret = Duel.use((state) => state.antagonist.turret); - const protagonistTrack = Duel.use((state) => state.protagonist.track); - const protagonistTank = Duel.use((state) => state.protagonist.tank); - const antagonistTank = Duel.use((state) => state.antagonist.tank); - const protagonistGun = Duel.use((state) => state.protagonist.gun); - const protagonistModelDefinition = - modelDefinitions.models[protagonistTank.id]; - const protagonistTrackModelDefinition = - modelDefinitions.models[protagonistTank.id].tracks[protagonistTrack.id]; - const antagonistModelDefinition = modelDefinitions.models[antagonistTank.id]; - const protagonistTurretModelDefinition = - protagonistModelDefinition.turrets[protagonistTurret.id]; - const antagonistTurretModelDefinition = - antagonistModelDefinition.turrets[antagonistTurret.id]; - const protagonistGunModelDefinition = - protagonistTurretModelDefinition.guns[protagonistGun.id]; - const protagonistHullOrigin = new Vector3( - protagonistTrackModelDefinition.origin!.x, - protagonistTrackModelDefinition.origin!.y, - -protagonistTrackModelDefinition.origin!.z, - ); - const protagonistTurretOrigin = new Vector3( - protagonistModelDefinition.turret_origin!.x, - protagonistModelDefinition.turret_origin!.y, - -protagonistModelDefinition.turret_origin!.z, - ); - const protagonistGunOrigin = new Vector3( - protagonistTurretModelDefinition.gun_origin!.x, - protagonistTurretModelDefinition.gun_origin!.y, - -protagonistTurretModelDefinition.gun_origin!.z, - ); - const antagonistGunHeight = - protagonistTrackModelDefinition.origin!.y + - antagonistModelDefinition.turret_origin!.y + - antagonistTurretModelDefinition.gun_origin!.y; - const disturbed = Tankopedia.use((state) => state.disturbed); - const doAutoRotate = autoRotate && !disturbed; - const gunHeight = - protagonistHullOrigin.y + - protagonistTurretOrigin.y + - protagonistGunOrigin.y; - const inspectModeInitialPosition = new Vector3(-8, 2, -13); - - const t0 = useRef(Date.now() / 1000); - useFrame(() => { - if (!doAutoRotate) return; - - const now = Date.now() / 1000; - const t = now - t0.current; - - camera.position - .copy(inspectModeInitialPosition) - .applyAxisAngle(I_HAT, (Math.PI / 32) * Math.sin(t / 9)) - .applyAxisAngle(J_HAT, (-Math.PI / 16) * Math.sin(t / 7)); - - invalidate(); - }); - - useEffect(() => { - function handleControlsEnabled(event: QuicklimeEvent) { - if (!orbitControls.current) return; - orbitControls.current.enabled = event.data; - } - - controlsEnabledEvent.on(handleControlsEnabled); - - function handlePoseEvent(event: Pose) { - const hasImprovedVerticalStabilizer = hasEquipment( - 122, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const hasDownImprovedVerticalStabilizer = hasEquipment( - 124, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - - switch (event) { - case Pose.HullDown: { - const [pitch] = applyPitchYawLimits( - -Infinity, - 0, - protagonistGunModelDefinition.pitch!, - protagonistTurretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - - camera.position - .set(0, 0, 0) - .add(protagonistHullOrigin) - .add(protagonistTurretOrigin) - .add(protagonistGunOrigin) - .add( - new Vector3( - 0, - poseDistances[event] * Math.sin(pitch), - poseDistances[event] * -Math.cos(pitch), - ), - ); - camera.lookAt( - protagonistHullOrigin - .clone() - .add(protagonistTurretOrigin) - .add(protagonistGunOrigin), - ); - orbitControls.current?.target.set(0, antagonistGunHeight, 0); - - break; - } - - case Pose.FaceHug: { - const [pitch] = applyPitchYawLimits( - 0, - 0, - protagonistGunModelDefinition.pitch!, - protagonistTurretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - - camera.position - .set(0, 0, 0) - .add(protagonistHullOrigin) - .add(protagonistTurretOrigin) - .add(protagonistGunOrigin) - .add( - new Vector3( - 0, - poseDistances[event] * Math.sin(pitch), - poseDistances[event] * -Math.cos(pitch), - ), - ); - orbitControls.current?.target - .set(0, 0, 0) - .add(protagonistHullOrigin) - .add(protagonistTurretOrigin) - .add(protagonistGunOrigin) - .add( - new Vector3( - 0, - 0.5 * poseDistances[event] * Math.sin(pitch), - 0.5 * poseDistances[event] * -Math.cos(pitch), - ), - ); - - break; - } - - case Pose.Default: { - camera.position.copy(inspectModeInitialPosition); - orbitControls.current?.target.set(0, 1.25, 0); - break; - } - } - - invalidate(); - } - - poseEvent.on(handlePoseEvent); - - return () => { - controlsEnabledEvent.off(handleControlsEnabled); - poseEvent.off(handlePoseEvent); - }; - }, [camera, protagonistTank.id, antagonistTank.id]); - - useEffect(() => { - function handleWheel(event: WheelEvent) { - event.preventDefault(); - } - - function handleScroll(event: Event) { - event.preventDefault(); - } - - function disturb() { - Tankopedia.mutate((draft) => { - draft.disturbed = true; - }); - } - - poseEvent.on(disturb); - canvas.addEventListener("pointerdown", disturb); - canvas.addEventListener("wheel", handleWheel); - document.body.addEventListener("scroll", handleScroll); - - function updateCamera() { - if (!orbitControls.current) return; - - orbitControls.current.rotateSpeed = 0.25; - - camera.position.copy(inspectModeInitialPosition); - orbitControls.current.target.set(0, gunHeight * 0.6, 0); - orbitControls.current.enablePan = true; - orbitControls.current.enableZoom = true; - camera.fov = 25; - - camera.updateProjectionMatrix(); - } - - updateCamera(); - - return () => { - canvas.removeEventListener("pointerdown", disturb); - poseEvent.off(disturb); - canvas.removeEventListener("wheel", handleWheel); - document.body.removeEventListener("scroll", handleScroll); - }; - }, []); - - return ( - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/InitialAligner.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/InitialAligner.tsx deleted file mode 100644 index 26eb65286..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/InitialAligner.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect } from "react"; -import { degToRad } from "three/src/math/MathUtils.js"; -import { awaitableModelDefinitions } from "../../../../../../core/awaitables/modelDefinitions"; -import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits"; -import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform"; -import { Duel } from "../../../../../../stores/duel"; - -const modelDefinitions = await awaitableModelDefinitions; - -export function InitialAligner() { - const tank = Duel.use((state) => state.protagonist.tank); - const turret = Duel.use((state) => state.protagonist.turret); - const gun = Duel.use((state) => state.protagonist.gun); - - const tankModel = modelDefinitions.models[tank.id]; - const turretModel = tankModel.turrets[turret.id]; - const gunModel = turretModel.guns[gun.id]; - - useEffect(() => { - const [pitch, yaw] = applyPitchYawLimits( - degToRad(8), - degToRad(25), - gunModel.pitch!, - turretModel.yaw, - ); - - modelTransformEvent.dispatch({ pitch, yaw }); - }, []); - - return null; -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx deleted file mode 100644 index a5d6fa550..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Lighting.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { useFrame, useThree } from "@react-three/fiber"; -import { times } from "lodash-es"; -import { Quicklime } from "quicklime"; -import { useEffect, useRef } from "react"; -import { HemisphereLight, SpotLight, type Group } from "three"; -import { clamp, degToRad, lerp } from "three/src/math/MathUtils.js"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent"; -import { HelpingSpotLight } from "../../../../../HelpingSpotLight"; - -const ANGLE = Math.PI * 2 ** -2; -const REVEAL_ANIMATION_TIME = 3; -const TRANSITION_ANIMATION_TIME = 0.5; - -const LIGHTS_COUNT = 5; -const THETA_OFFSET = degToRad(-152); -const LIGHT_DISTANCE = 13; -const LIGHT_HEIGHT_0 = 4; -const LIGHT_HEIGHT_1 = 2; -const INTENSITY_0 = 3.5; -const INTENSITY_1 = 1.5; -const HEMISPHERE_INTENSITY = 2; - -export const transitionEvent = new Quicklime(0); - -export function Lighting() { - const wrapper = useRef(null!); - const t0 = useRef(0); - const transition = useRef(false); - const clock = useThree((state) => state.clock); - const display = Tankopedia.use((state) => state.display); - const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay); - const animationTime = useRef(REVEAL_ANIMATION_TIME); - const highGraphics = TankopediaPersistent.use((state) => state.highGraphics); - - useEffect(() => { - Tankopedia.mutate((draft) => { - draft.revealed = true; - }); - - t0.current = performance.now() / 1000 - REVEAL_ANIMATION_TIME; - }, []); - - useFrame(({ invalidate }) => { - if (transition.current) { - transition.current = false; - t0.current = performance.now() / 1000; - } - - const x = clamp( - (performance.now() / 1000 - t0.current) / animationTime.current, - 0, - 2, - ); - const t = (0.5 * Math.sin(Math.PI * (x + 0.5)) + 0.5) ** 2; - - if (t !== transitionEvent.last) { - transitionEvent.dispatch(t); - } - - for (const child of wrapper.current.children) { - if (child instanceof SpotLight) { - child.angle = ANGLE * t; - } else if (child instanceof HemisphereLight) { - child.intensity = HEMISPHERE_INTENSITY * t; - } - } - - if (x < 2) invalidate(); - - if ( - x > 1 && - Tankopedia.state.display !== Tankopedia.state.requestedDisplay - ) { - Tankopedia.mutate((draft) => { - draft.display = requestedDisplay; - }); - } - }); - - useEffect(() => { - if (requestedDisplay === display) return; - - transition.current = true; - animationTime.current = TRANSITION_ANIMATION_TIME; - }, [requestedDisplay]); - - return ( - - - - {times(LIGHTS_COUNT, (index) => { - const x = index / (LIGHTS_COUNT - 1); - const theta = 2 * Math.PI * (index / LIGHTS_COUNT) + THETA_OFFSET; - const position = [ - LIGHT_DISTANCE * Math.sin(theta), - lerp(LIGHT_HEIGHT_0, LIGHT_HEIGHT_1, x), - LIGHT_DISTANCE * Math.cos(theta), - ] as const; - const intensity = lerp(INTENSITY_0, INTENSITY_1, x); - - return ( - - ); - })} - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/ModelLoader.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/ModelLoader.tsx deleted file mode 100644 index 0eea6c02f..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/ModelLoader.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Spinner } from '@radix-ui/themes'; -import { Html } from '@react-three/drei'; - -export function ModelLoader() { - return ( - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/components/TargetCircle.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/components/TargetCircle.tsx deleted file mode 100644 index bfc750666..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/components/TargetCircle.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Box, type BoxProps } from '@radix-ui/themes'; -import { forwardRef } from 'react'; -import { Var } from '../../../../../../../../core/radix/var'; - -// const DOTS = 32; - -type TargetCircleProps = BoxProps & { - variant: 'client' | 'server'; -}; - -export const TargetCircle = forwardRef( - ({ style, variant, ...props }, ref) => { - return ( - - {/* {variant === 'client' && - times(DOTS, (index) => { - const angle = 2 * Math.PI * (index / DOTS); - - return ( - - ); - })} */} - - - - ); - }, -); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/constants.ts b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/constants.ts deleted file mode 100644 index d696148df..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Vector3 } from 'three'; - -export const aimTarget = new Vector3(); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx deleted file mode 100644 index be871c5ef..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/SceneProps/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { invalidate, useFrame } from "@react-three/fiber"; -import { clamp } from "lodash-es"; -import { Quicklime, type QuicklimeEvent } from "quicklime"; -import { useEffect, useRef } from "react"; -import { Mesh, MeshStandardMaterial } from "three"; - -const SIZE = 2 ** 6; - -export const screenshotReadyEvent = new Quicklime(false); - -export function SceneProps() { - const mesh = useRef(null!); - const material = useRef(null); - - useFrame(({ camera }) => { - if (!material.current) return; - material.current.opacity = clamp(0.5 * camera.position.y, 0, 0.5); - }); - - useEffect(() => { - function handleScreenshotReady(event: QuicklimeEvent) { - mesh.current.visible = !event.data; - invalidate(); - } - - screenshotReadyEvent.on(handleScreenshotReady); - - return () => { - screenshotReadyEvent.off(handleScreenshotReady); - }; - }, []); - - return ( - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx deleted file mode 100644 index ded24bba1..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import { invalidate, useThree, type ThreeEvent } from "@react-three/fiber"; -import { useRef } from "react"; -import { Group, Mesh, MeshStandardMaterial, Vector2 } from "three"; -import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits"; -import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment"; -import { jsxTree } from "../../../../../../core/blitzkit/jsxTree"; -import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform"; -import { controlsEnabledEvent } from "../../../../../../core/controlsEnabled"; -import { useModel } from "../../../../../../hooks/useModel"; -import { useTankModelDefinition } from "../../../../../../hooks/useTankModelDefinition"; -import { useTankTransform } from "../../../../../../hooks/useTankTransform"; -import { Duel } from "../../../../../../stores/duel"; -import { Tankopedia } from "../../../../../../stores/tankopedia"; -import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants"; -import { ModelTankWrapper } from "../../../../../Armor/components/ModelTankWrapper"; - -export function TankModel() { - const protagonist = Duel.use((draft) => draft.protagonist); - const track = Duel.use((state) => state.protagonist.track); - const turret = Duel.use((state) => state.protagonist.turret); - const canvas = useThree((state) => state.gl.domElement); - const hullContainer = useRef(null); - const turretContainer = useRef(null!); - const gunContainer = useRef(null!); - const tankModelDefinition = useTankModelDefinition(); - const turretModelDefinition = - tankModelDefinition.turrets[protagonist.turret.id]; - const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id]; - const { gltf } = useModel(protagonist.tank.id); - const nodes = Object.values(gltf.nodes); - - useTankTransform(track, turret, turretContainer, gunContainer); - - return ( - - {nodes.map((node) => { - const isHull = node.name === "hull"; - const isWheel = node.name.startsWith("chassis_wheel_"); - const isTrack = node.name.startsWith("chassis_track_"); - const isVisible = isHull || isWheel || isTrack; - const position = new Vector2(); - const delta = new Vector2(); - - if (!isVisible) return null; - - function translateTexture(offset: number) { - const mesh = node.children[0] as Mesh; - const material = mesh?.material as MeshStandardMaterial; - - if (!material) return; - - invalidate(); - - if (material.map) material.map.offset.y += offset; - if (material.aoMap) material.aoMap.offset.y += offset; - if (material.normalMap) material.normalMap.offset.y += offset; - if (material.roughnessMap) material.roughnessMap.offset.y += offset; - if (material.metalnessMap) material.metalnessMap.offset.y += offset; - } - - function onPointerDown(event: ThreeEvent) { - if (isTrack && Tankopedia.state.display === TankopediaDisplay.Model) { - position.set(event.clientX, event.clientY); - event.stopPropagation(); - - controlsEnabledEvent.dispatch(false); - - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } else { - event.stopPropagation(); - } - } - function handlePointerMove(event: PointerEvent) { - delta.set(event.clientX, event.clientY).sub(position); - position.set(event.clientX, event.clientY); - const deltaX = delta.x / window.innerWidth; - const deltaY = delta.y / window.innerHeight; - - translateTexture(deltaX + deltaY); - } - function handlePointerUp() { - controlsEnabledEvent.dispatch(true); - - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - } - - return jsxTree(node, { - mesh(_, props, key) { - return ( - - ); - }, - }); - })} - - - {nodes.map((node) => { - const isTurret = node.name.startsWith("turret_"); - const isCurrentTurret = - node.name === - `turret_${turretModelDefinition.model_id - .toString() - .padStart(2, "0")}`; - const isVisible = isCurrentTurret; - const position = new Vector2(); - const delta = new Vector2(); - - if (!isVisible) return null; - - function onPointerDown(event: ThreeEvent) { - event.stopPropagation(); - - if (!isTurret) return; - - position.set(event.clientX, event.clientY); - - controlsEnabledEvent.dispatch(false); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - draft.highlightArmor = undefined; - }); - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } - function handlePointerMove(event: PointerEvent) { - const hasImprovedVerticalStabilizer = hasEquipment( - 122, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const hasDownImprovedVerticalStabilizer = hasEquipment( - 124, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const boundingRect = canvas.getBoundingClientRect(); - - delta.set(event.clientX, event.clientY).sub(position); - position.set(event.clientX, event.clientY); - - const [pitch, yaw] = applyPitchYawLimits( - modelTransformEvent.last!.pitch, - modelTransformEvent.last!.yaw + - delta.x * (Math.PI / boundingRect.width), - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - modelTransformEvent.dispatch({ pitch, yaw }); - } - function handlePointerUp() { - controlsEnabledEvent.dispatch(true); - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - } - - return jsxTree(node, { - mesh(_, props, key) { - return ( - - ); - }, - }); - })} - - - {nodes.map((node) => { - const isCurrentMantlet = - node.name === - `gun_${gunModelDefinition.model_id - .toString() - .padStart(2, "0")}_mask`; - const isCurrentGun = - node.name === - `gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`; - const isVisible = isCurrentGun || isCurrentMantlet; - const position = new Vector2(); - const delta = new Vector2(); - - if (!isVisible) return null; - - let pitch = 0; - let yaw = 0; - - function onPointerDown(event: ThreeEvent) { - event.stopPropagation(); - - controlsEnabledEvent.dispatch(false); - Tankopedia.mutate((draft) => { - draft.shot = undefined; - draft.highlightArmor = undefined; - }); - - position.set(event.clientX, event.clientY); - pitch = modelTransformEvent.last!.pitch; - yaw = modelTransformEvent.last!.yaw; - - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } - function handlePointerMove(event: PointerEvent) { - const hasImprovedVerticalStabilizer = hasEquipment( - 122, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const hasDownImprovedVerticalStabilizer = hasEquipment( - 124, - Duel.state.protagonist.tank.equipment_preset, - Duel.state.protagonist.equipmentMatrix, - ); - const boundingRect = canvas.getBoundingClientRect(); - delta.set(event.clientX, event.clientY).sub(position); - position.set(event.clientX, event.clientY); - - [pitch, yaw] = applyPitchYawLimits( - pitch - delta.y * (Math.PI / boundingRect.height), - yaw + delta.x * (Math.PI / boundingRect.width), - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - modelTransformEvent.dispatch({ pitch, yaw }); - } - function handlePointerUp() { - controlsEnabledEvent.dispatch(true); - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - } - - return jsxTree(node, { - mesh(_, props, key) { - return ( - - ); - }, - }); - })} - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TransitionSkeleton/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TransitionSkeleton/index.tsx deleted file mode 100644 index 5171f6258..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TransitionSkeleton/index.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { useRef } from "react"; -import { Group, ShaderMaterial, UniformsLib, UniformsUtils } from "three"; -import { correctZYTuple } from "../../../../../../../core/blitz/correctZYTuple"; -import { jsxTree } from "../../../../../../../core/blitzkit/jsxTree"; -import { useArmor } from "../../../../../../../hooks/useArmor"; -import { useModel } from "../../../../../../../hooks/useModel"; -import { useTankModelDefinition } from "../../../../../../../hooks/useTankModelDefinition"; -import { useTankTransform } from "../../../../../../../hooks/useTankTransform"; -import { Duel } from "../../../../../../../stores/duel"; -import { ModelTankWrapper } from "../../../../../../Armor/components/ModelTankWrapper"; -import fragmentShader from "./shaders/fragment.glsl?raw"; -import vertexShader from "./shaders/vertex.glsl?raw"; - -const skeletonMaterial = new ShaderMaterial({ - vertexShader, - fragmentShader, - wireframe: true, - fog: true, - transparent: true, - uniforms: UniformsUtils.merge([UniformsLib.common, UniformsLib.fog]), - depthWrite: false, -}); - -export function TransitionSkeleton() { - const tank = Duel.use((state) => state.protagonist.tank); - const gun = Duel.use((state) => state.protagonist.gun); - const track = Duel.use((state) => state.protagonist.track); - const turret = Duel.use((state) => state.protagonist.turret); - const turretContainer = useRef(null!); - const gunContainer = useRef(null!); - const { gltf: armorGltf } = useArmor(tank.id); - const { gltf: modelGltf } = useModel(tank.id); - const modelNodes = Object.values(modelGltf.nodes); - const armorNodes = Object.values(armorGltf.nodes); - const tankModelDefinition = useTankModelDefinition(); - const turretModelDefinition = tankModelDefinition.turrets[turret.id]; - const gunModelDefinition = turretModelDefinition.guns[gun.id]; - const trackModelDefinition = tankModelDefinition.tracks[track.id]; - const wrapper = useRef(null); - const hullOrigin = correctZYTuple(trackModelDefinition.origin!); - const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!); - const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!); - - useTankTransform(track, turret, turretContainer, gunContainer); - - return ( - - - {armorNodes.map((node) => { - const isHull = node.name.startsWith("hull_"); - const isVisible = isHull; - - if (!isVisible) return null; - - return jsxTree(node, { - mesh(_, props, key) { - return ; - }, - }); - })} - - - {modelNodes.map((node) => { - const isWheel = node.name.startsWith("chassis_wheel_"); - const isTrack = node.name.startsWith("chassis_track_"); - const isVisible = isWheel || isTrack; - - if (!isVisible) return null; - - return jsxTree(node, { - mesh(_, props, key) { - return ; - }, - }); - })} - - - {armorNodes.map((node) => { - const isCurrentTurret = node.name.startsWith( - `turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`, - ); - const isVisible = isCurrentTurret; - if (!isVisible) { - return null; - } - - return ( - - - {jsxTree(node, { - mesh(_, props, key) { - return ( - - ); - }, - })} - - - ); - })} - - - {armorNodes.map((node) => { - const isCurrentGun = node.name.startsWith( - `gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`, - ); - const isVisible = isCurrentGun; - - if (!isVisible) return null; - - return ( - - - {jsxTree(node, { - mesh(_, props, key) { - return ( - - ); - }, - })} - - - ); - })} - - {modelNodes.map((node) => { - const gunString = `gun_${gunModelDefinition.model_id - .toString() - .padStart(2, "0")}`; - const isCurrentGun = gunModelDefinition.mask - ? node.name.startsWith(gunString) - : node.name === gunString; - const isVisible = isCurrentGun; - - if (!isVisible) return null; - - return jsxTree(node, { - mesh(_, props, key) { - return ( - - ); - }, - }); - })} - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TransitionSkeleton/shaders/fragment.glsl b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TransitionSkeleton/shaders/fragment.glsl deleted file mode 100644 index 5bbbb707b..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TransitionSkeleton/shaders/fragment.glsl +++ /dev/null @@ -1,11 +0,0 @@ -precision mediump float; - -#define USE_FOG - -#include - -void main() { - float fogFactor = smoothstep(fogNear, fogFar, vFogDepth); - gl_FragColor = vec4(0.5, 0.5, 0.5, fogFactor); - gl_FragColor.rgb = mix(fogColor, gl_FragColor.rgb, fogFactor); -} \ No newline at end of file diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TransitionSkeleton/shaders/vertex.glsl b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TransitionSkeleton/shaders/vertex.glsl deleted file mode 100644 index 2c0140061..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TransitionSkeleton/shaders/vertex.glsl +++ /dev/null @@ -1,19 +0,0 @@ -precision mediump float; - -#define USE_FOG - -#include - -varying vec3 vNormal; -varying vec3 vViewPosition; - -void main() { - vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); - - vViewPosition = mvPosition.xyz; - vNormal = normalMatrix * normal; - - gl_Position = projectionMatrix * mvPosition; - - #include -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx deleted file mode 100644 index b8b7dcdfa..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import { - forwardRef, - Suspense, - useEffect, - useImperativeHandle, - useRef, -} from "react"; -import { Fog } from "three"; -import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits"; -import { modelTransformEvent } from "../../../../../core/blitzkit/modelTransform"; -import { Pose, poseEvent } from "../../../../../core/blitzkit/pose"; -import { useEquipment } from "../../../../../hooks/useEquipment"; -import { useTankModelDefinition } from "../../../../../hooks/useTankModelDefinition"; -import { Duel } from "../../../../../stores/duel"; -import { Tankopedia } from "../../../../../stores/tankopedia"; -import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent"; -import { TankopediaDisplay } from "../../../../../stores/tankopediaPersistent/constants"; -import { Armor } from "../../../../Armor"; -import { ArmorPlateDisplay } from "../../../../Armor/components/ArmorPlateDisplay"; -import { ShotDisplay } from "../../../../Armor/components/ShotDisplay"; -import { - StaticArmor, - type ThicknessRange, -} from "../../../../Armor/components/StaticArmor"; -import { SmartCanvas } from "../../../../SmartCanvas"; -import { AutoClear } from "./components/AutoClear"; -import { Controls } from "./components/Control"; -import { InitialAligner } from "./components/InitialAligner"; -import { Lighting } from "./components/Lighting"; -import { SceneProps } from "./components/SceneProps"; -import { TankModel } from "./components/TankModel"; - -interface TankSandboxProps { - thicknessRange: ThicknessRange; -} - -export const TankSandbox = forwardRef( - ({ thicknessRange }, ref) => { - const canvas = useRef(null); - const hasImprovedVerticalStabilizer = useEquipment(122); - const hasDownImprovedVerticalStabilizer = useEquipment(124); - const protagonistGun = Duel.use((state) => state.protagonist.gun); - const protagonistTurret = Duel.use((state) => state.protagonist.turret); - const tankModelDefinition = useTankModelDefinition(); - const turretModelDefinition = - tankModelDefinition.turrets[protagonistTurret.id]; - const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id]; - const display = Tankopedia.use((state) => state.display); - const hideTankModelUnderArmor = TankopediaPersistent.use( - (state) => state.hideTankModelUnderArmor, - ); - - useImperativeHandle(ref, () => canvas.current!, []); - - function handlePointerDown() { - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerUp); - } - function handlePointerMove(event: PointerEvent) { - event.preventDefault(); - } - function handlePointerUp(event: PointerEvent) { - event.preventDefault(); - - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerUp); - } - - useEffect(() => { - function handlePoseEvent(pose: Pose) { - switch (pose) { - case Pose.HullDown: { - const [pitch, yaw] = applyPitchYawLimits( - -Infinity, - 0, - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - hasDownImprovedVerticalStabilizer, - ); - - modelTransformEvent.dispatch({ pitch, yaw }); - - break; - } - - case Pose.FaceHug: { - const [pitch, yaw] = applyPitchYawLimits( - Infinity, - 0, - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - ); - - modelTransformEvent.dispatch({ pitch, yaw }); - - break; - } - - case Pose.Default: - const [pitch, yaw] = applyPitchYawLimits( - 0, - 0, - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - ); - - modelTransformEvent.dispatch({ pitch, yaw }); - - break; - } - } - - poseEvent.on(handlePoseEvent); - - return () => { - poseEvent.off(handlePoseEvent); - }; - }); - - useEffect(() => { - const [pitch, yaw] = applyPitchYawLimits( - modelTransformEvent.last!.pitch, - modelTransformEvent.last!.yaw, - gunModelDefinition.pitch!, - turretModelDefinition.yaw, - hasImprovedVerticalStabilizer, - ); - - modelTransformEvent.dispatch({ pitch, yaw }); - }, [protagonistGun, protagonistTurret]); - - useEffect(() => { - if (display !== TankopediaDisplay.DynamicArmor) { - Tankopedia.mutate((draft) => { - draft.shot = undefined; - }); - } - - if (display !== TankopediaDisplay.StaticArmor) { - Tankopedia.mutate((draft) => { - draft.highlightArmor = undefined; - }); - } - }, [display]); - - return ( - { - Tankopedia.mutate((draft) => { - draft.shot = undefined; - draft.highlightArmor = undefined; - }); - }} - style={{ - userSelect: "none", - width: "100%", - height: "100%", - outline: undefined, - }} - > - - - - {(display === TankopediaDisplay.Model || - (display === TankopediaDisplay.DynamicArmor && - !hideTankModelUnderArmor)) && } - - - - - - {/* Controls within Suspense to allow for frame-perfect start of camera auto-rotate */} - - - {display === TankopediaDisplay.DynamicArmor && } - {display === TankopediaDisplay.StaticArmor && ( - - )} - - - - - ); - }, -); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandboxLoader.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandboxLoader.tsx deleted file mode 100644 index 0c4c33338..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandboxLoader.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { Flex, type FlexProps, Progress } from "@radix-ui/themes"; -import { useProgress } from "@react-three/drei"; - -type TankSandboxLoaderProps = Omit & { - id: number; -}; - -export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) { - const data = useProgress(); - - return ( - - - - - ); -} diff --git a/packages/website/src/components/Tankopedia/HeroSection/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/index.tsx deleted file mode 100644 index af403e531..000000000 --- a/packages/website/src/components/Tankopedia/HeroSection/index.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { Box, Flex } from "@radix-ui/themes"; -import { times } from "lodash-es"; -import { useEffect, useMemo, useRef } from "react"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { defaultEqualizer } from "../../../core/blitzkit/tankToDuelMember"; -import { useFullScreen } from "../../../hooks/useFullScreen"; -import { Duel } from "../../../stores/duel"; -import { Tankopedia } from "../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; -import type { ThicknessRange } from "../../Armor/components/StaticArmor"; -import { Options } from "./components/Options"; -import { TankSandbox } from "./components/TankSandbox"; -import { Title } from "./components/TankSandbox/Title"; - -const tankDefinitions = await awaitableTankDefinitions; - -export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { - const equalize = Duel.use((state) => state.equalize); - const disturbed = Tankopedia.use((state) => state.disturbed); - const canvas = useRef(null!); - const isFullScreen = useFullScreen(); - const protagonist = Duel.use((state) => state.protagonist.tank); - const thicknessRange = useMemo(() => { - const entries = Object.values(tankDefinitions.tanks); - const filtered = entries.filter( - (thisTank) => thisTank.tier === protagonist.tier, - ); - const value = - (filtered.reduce((accumulator, thisTank) => { - return ( - accumulator + - thisTank.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near * - ((equalize ? thisTank.equalizer : undefined) ?? defaultEqualizer) - .penetration - ); - }, 0) / - filtered.length) * - (3 / 4); - - return { value } satisfies ThicknessRange; - }, [protagonist, equalize]); - - useEffect(() => { - if (disturbed) { - document.body.classList.remove("no-navbar"); - } - }, [disturbed]); - - useEffect(() => { - function handleKeyDown(event: KeyboardEvent) { - times(3, (index) => { - if ( - event.key === `${index + 1}` && - Duel.state.antagonist.gun.shells.length > index - ) { - Duel.mutate((draft) => { - draft.antagonist.shell = Duel.state.antagonist.gun.shells[index]; - }); - Tankopedia.mutate((draft) => { - draft.customShell = undefined; - }); - } - }); - } - - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, []); - - return ( - - - - {/* */} - - - - - - - {!skeleton && ( - - )} - - - - - - <Options - skeleton={skeleton} - canvas={canvas} - thicknessRange={thicknessRange} - /> - </Box> - </Box> - </Flex> - </Flex> - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Listing.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Listing.tsx deleted file mode 100644 index 79becdf1c..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Listing.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Flex, Text, type TextProps } from '@radix-ui/themes'; -import { Var } from '../../../../core/radix/var'; - -type ListingProps = TextProps & { - label: string; -}; - -export function Listing({ label, ...props }: ListingProps) { - return ( - <Flex - minWidth="17rem" - justify="between" - gap="4" - style={{ borderBottom: `1px solid ${Var('gray-a9')}` }} - > - <Text size="4">{label}</Text> - <Text align="right" size="4" weight="medium" {...props} /> - </Flex> - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Stars/components/PartialStar.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Stars/components/PartialStar.tsx deleted file mode 100644 index d2ee2bb22..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Stars/components/PartialStar.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { StarFilledIcon, StarIcon } from '@radix-ui/react-icons'; -import { Box, type BoxProps } from '@radix-ui/themes'; - -type PartialStarProps = BoxProps & { - fill: number; -}; - -export function PartialStar({ fill, ...props }: PartialStarProps) { - return ( - <Box height="1em" width="1em" position="relative" {...props}> - <StarIcon width="1em" height="1em" /> - {fill > 0 && ( - <Box - top="0" - left="0" - width={`${fill * 100}%`} - position="absolute" - overflow="hidden" - > - <StarFilledIcon width="1em" height="1em" /> - </Box> - )} - </Box> - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Stars/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Stars/index.tsx deleted file mode 100644 index 06f4187d0..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Stars/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { StarsInt } from '@blitzkit/core'; -import { type TextProps, Flex, Text } from '@radix-ui/themes'; -import { times } from 'lodash-es'; -import { PartialStar } from './components/PartialStar'; - -type StarsProps = TextProps & { - stars: number | null; - onCast?: (value: StarsInt) => void; -}; - -export function Stars({ stars, onCast, ...props }: StarsProps) { - let color: TextProps['color'] = 'green'; - - if (stars === null) color = 'gray'; - else if (stars < 1.5) color = 'tomato'; - else if (stars < 2.5) color = 'orange'; - else if (stars < 3.5) color = 'amber'; - else if (stars < 4.5) color = 'blue'; - - return ( - <Text color={color} {...props}> - <Flex style={{ cursor: onCast ? 'pointer' : 'unset' }}> - {stars !== null && - times(Math.floor(stars), (index) => ( - <PartialStar - fill={1} - key={index} - onClick={() => onCast?.((index + 1) as StarsInt)} - /> - ))} - {stars !== null && stars % 1 !== 0 && <PartialStar fill={stars % 1} />} - {times(5 - Math.ceil(stars ?? 0), (index) => ( - <PartialStar - fill={0} - key={index} - onClick={() => - onCast?.((Math.floor(stars ?? 0) + index + 1) as StarsInt) - } - /> - ))} - </Flex> - </Text> - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/StarRow.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/StarRow.tsx deleted file mode 100644 index 199e734b9..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/StarRow.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Flex, Text } from '@radix-ui/themes'; -import { Stars } from '../../Stars'; - -interface StarRowProps { - stars: number | null; - children: string; -} - -export function StarRow({ stars, children }: StarRowProps) { - return ( - <Text color={stars === null ? 'gray' : undefined}> - <Flex align="center" justify="between" width="15rem"> - {children} - <Stars stars={stars} /> - </Flex> - </Text> - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx deleted file mode 100644 index a036259cb..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import type { BlitzkitResponse, StarsInt } from "@blitzkit/core"; -import { PaperPlaneIcon } from "@radix-ui/react-icons"; -import { - AlertDialog, - Button, - Dialog, - Flex, - Spinner, - Text, -} from "@radix-ui/themes"; -import { useState } from "react"; -import { useLocale } from "../../../../../../hooks/useLocale"; -import { App } from "../../../../../../stores/app"; -import { Duel } from "../../../../../../stores/duel"; -import { Stars } from "../../Stars"; - -export function VoteCaster() { - const tank = Duel.use((state) => state.protagonist.tank); - const [easiness, setEasiness] = useState<StarsInt | null>(null); - const [firepower, setFirepower] = useState<StarsInt | null>(null); - const [maneuverability, setManeuverability] = useState<StarsInt | null>(null); - const [survivability, setSurvivability] = useState<StarsInt | null>(null); - // true = vote successful, false = vote failed, null = hide alert - const [postVote, setPostVote] = useState<boolean | null>(null); - // true = open, false = closed, null = casting - const [showDialogue, setShowDialogue] = useState<boolean | null>(false); - const { unwrap } = useLocale(); - - return ( - <> - <Dialog.Root open={showDialogue !== false} onOpenChange={setShowDialogue}> - <Dialog.Trigger> - <Button variant="surface"> - <PaperPlaneIcon /> Vote - </Button> - </Dialog.Trigger> - - <Dialog.Content maxWidth="23rem"> - <Dialog.Title>Voting for {unwrap(tank.name!)}</Dialog.Title> - - <Flex direction="column" mt="4"> - <Flex align="center" justify="between"> - <Text> - Easiness{" "} - <Text color="gray" size="2"> - {easiness === null ? "(empty)" : `(${easiness})`} - </Text> - </Text> - <Stars stars={easiness} size="5" onCast={setEasiness} /> - </Flex> - <Flex align="center" justify="between"> - <Text> - Firepower{" "} - <Text color="gray" size="2"> - {firepower === null ? "(empty)" : `(${firepower})`} - </Text> - </Text> - <Stars stars={firepower} size="5" onCast={setFirepower} /> - </Flex> - <Flex align="center" justify="between"> - <Text> - Maneuverability{" "} - <Text color="gray" size="2"> - {maneuverability === null - ? "(empty)" - : `(${maneuverability})`} - </Text> - </Text> - <Stars - stars={maneuverability} - size="5" - onCast={setManeuverability} - /> - </Flex> - <Flex align="center" justify="between"> - <Text> - Survivability{" "} - <Text color="gray" size="2"> - {survivability === null ? "(empty)" : `(${survivability})`} - </Text> - </Text> - <Stars stars={survivability} size="5" onCast={setSurvivability} /> - </Flex> - </Flex> - - <Flex justify="end" mt="4" gap="2"> - <Dialog.Close> - <Button variant="outline" color="red"> - Cancel - </Button> - </Dialog.Close> - - <Button - disabled={ - showDialogue === null || - (easiness === null && - firepower === null && - maneuverability === null && - survivability === null) - } - onClick={() => { - const { wargaming } = App.state.logins; - - if (!wargaming) { - return setShowDialogue(false); - } - - setShowDialogue(null); - - fetch( - `/api/tank-voting/${tank.id}/cast?${ - easiness === null ? "" : `easiness=${easiness}&` - }${firepower === null ? "" : `firepower=${firepower}&`}${ - maneuverability === null - ? "" - : `maneuverability=${maneuverability}&` - }${ - survivability === null - ? "" - : `survivability=${survivability}&` - }player=${wargaming.id}&token=${wargaming.token}`, - ) - .then( - (response) => response.json() as Promise<BlitzkitResponse>, - ) - .then((json) => { - setShowDialogue(false); - setPostVote(json.status === "ok"); - - if (json.status === "error") { - console.error("vote failed", json); - } - }); - }} - > - {showDialogue === null ? <Spinner /> : <PaperPlaneIcon />} Vote - </Button> - </Flex> - </Dialog.Content> - </Dialog.Root> - - <AlertDialog.Root - open={postVote !== null} - onOpenChange={() => setPostVote(null)} - > - {postVote === true && ( - <AlertDialog.Content maxWidth="20rem"> - <AlertDialog.Title>Vote casted</AlertDialog.Title> - <AlertDialog.Description> - It may take a few minutes for it to show up. - </AlertDialog.Description> - - <Flex justify="end" mt="4"> - <AlertDialog.Cancel> - <Button>Okay</Button> - </AlertDialog.Cancel> - </Flex> - </AlertDialog.Content> - )} - - {postVote === false && ( - <AlertDialog.Content maxWidth="20rem"> - <AlertDialog.Title>Voting failed</AlertDialog.Title> - <AlertDialog.Description> - So sorry about this! If you're a nerd, feel free to check out the - console. - </AlertDialog.Description> - - <Flex justify="end" mt="4"> - <AlertDialog.Cancel> - <Button>Gotcha</Button> - </AlertDialog.Cancel> - </Flex> - </AlertDialog.Content> - )} - </AlertDialog.Root> - </> - ); -} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx deleted file mode 100644 index 5455a4327..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { formatCompact } from '@blitzkit/core'; -import { Flex, Heading, Skeleton, Text } from '@radix-ui/themes'; -import { memo } from 'react'; -import { useLocale } from '../../../../../hooks/useLocale'; -import { useTankVotes } from '../../../../../hooks/useTankVotes'; -import { App } from '../../../../../stores/app'; -import { Duel } from '../../../../../stores/duel'; -import { WargamingLoginButton } from '../../../../WargamingLoginButton'; -import { StarRow } from './components/StarRow'; -import { VoteCaster } from './components/VoteCaster'; - -export const Votes = memo( - () => { - const id = Duel.use((state) => state.protagonist.tank.id); - const votes = useTankVotes(id); - const wargaming = App.use((state) => state.logins.wargaming); - const { locale } = useLocale(); - - return ( - <Flex direction="column" gap="3"> - <Flex justify="between" align="center"> - <Heading size="4"> - {!votes && <Skeleton height="1em" width="4rem" />} - {votes && formatCompact(locale, votes.votes)}{' '} - {votes && (votes.votes > 1 ? 'votes' : 'vote')} - </Heading> - - {!wargaming && ( - <WargamingLoginButton variant="surface"> - Login to vote - </WargamingLoginButton> - )} - {wargaming && <VoteCaster />} - </Flex> - - <Flex gap="0" direction="column"> - <Flex - gap={{ initial: '0', sm: '6' }} - direction={{ initial: 'column', sm: 'row' }} - > - <StarRow - stars={votes?.categories.easiness ?? null} - children="Easiness" - /> - <StarRow - stars={votes?.categories.firepower ?? null} - children="Firepower" - /> - </Flex> - - <Flex - gap={{ initial: '0', sm: '6' }} - direction={{ initial: 'column', sm: 'row' }} - > - <StarRow - stars={votes?.categories.maneuverability ?? null} - children="Maneuverability" - /> - <StarRow - stars={votes?.categories.survivability ?? null} - children="Survivability" - /> - </Flex> - - <Flex justify="center"> - <Text size="2" color="gray" mt="2"> - <> - {votes ? ( - wargaming ? ( - votes.last_updated ? ( - `You voted on ${new Date( - votes.last_updated, - ).toLocaleDateString()}` - ) : ( - `You haven't voted yet` - ) - ) : ( - 'Login to vote' - ) - ) : ( - <Skeleton height="1em" width="5rem" /> - )} - </> - </Text> - </Flex> - </Flex> - </Flex> - ); - }, - () => true, -); diff --git a/packages/website/src/components/Tankopedia/MetaSection/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/index.tsx deleted file mode 100644 index d795d1926..000000000 --- a/packages/website/src/components/Tankopedia/MetaSection/index.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import { - asset, - TankPriceType, - TankType, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { Box, Flex } from "@radix-ui/themes"; -import { Var } from "../../../core/radix/var"; -import { useLocale } from "../../../hooks/useLocale"; -import { App } from "../../../stores/app"; -import { Duel } from "../../../stores/duel"; -import { classIcons } from "../../ClassIcon"; -import { CopyableCode } from "../../CopyableCode"; -import { Listing } from "./components/Listing"; - -const NATIONAL_BANNER_POSITION_OVERRIDES: Record<string, string> = { - germany: "50% 35%", - france: "50% 35%", - other: "50% 35%", -}; - -export function MetaSection() { - const developerMode = App.useDeferred((state) => state.developerMode, false); - const protagonist = Duel.use((state) => state.protagonist.tank); - const ClassIcon = classIcons[protagonist.class]; - const { locale, strings } = useLocale(); - - return ( - <Flex justify="center" mt="-9"> - <Box - style={{ - background: `url(/assets/images/national-wallpapers/${protagonist.nation}.jpg)`, - backgroundRepeat: "no-repeat", - backgroundSize: "cover", - backgroundPosition: - NATIONAL_BANNER_POSITION_OVERRIDES[protagonist.nation] ?? "center", - }} - flexGrow="1" - maxWidth="120rem" - > - <Box - style={{ - background: `url(${asset(`flags/scratched/${protagonist.nation}.webp`)})`, - backgroundRepeat: "no-repeat", - backgroundSize: "min(53rem, 80vw)", - backgroundPosition: "-8rem 50%", - }} - > - <Flex - justify="center" - align="center" - py="6rem" - style={{ - background: `linear-gradient(${Var("black-a8")}, ${Var("black-a8")}, ${Var("gray-1")})`, - }} - > - <Flex direction="column" align="center" gap="6"> - <Flex - gap={{ initial: "2", sm: "8" }} - direction={{ initial: "column", sm: "row" }} - > - <Flex direction="column" width="100%" gap="2"> - <Listing label={strings.website.tools.tankopedia.meta.nation}> - <Flex align="center" gap="1"> - <img - style={{ width: "1em", height: "1em" }} - alt={protagonist.nation} - src={asset(`flags/circle/${protagonist.nation}.webp`)} - /> - { - strings.common.nations[ - protagonist.nation as keyof typeof strings.common.nations - ] - } - </Flex> - </Listing> - <Listing label={strings.website.tools.tankopedia.meta.class}> - <Flex align="center" gap="1"> - <ClassIcon width="1em" height="1em" /> - {strings.common.tank_class_short[protagonist.class]} - </Flex> - </Listing> - <Listing label={strings.website.tools.tankopedia.meta.tier}> - {TIER_ROMAN_NUMERALS[protagonist.tier]} - </Listing> - <Listing - label={strings.website.tools.tankopedia.meta.type} - color={ - protagonist.type === TankType.TANK_TYPE_COLLECTOR - ? "blue" - : protagonist.type === TankType.TANK_TYPE_PREMIUM - ? "amber" - : undefined - } - > - {strings.common.tree_type[protagonist.type]} - </Listing> - </Flex> - - <Flex direction="column" width="100%" gap="2"> - {developerMode && ( - <Listing - label={strings.website.tools.tankopedia.meta.dev_id} - > - <CopyableCode - copy={`${protagonist.id}`} - variant="soft" - highContrast - color="gray" - > - {protagonist.id} - </CopyableCode> - </Listing> - )} - {developerMode && ( - <Listing - label={strings.website.tools.tankopedia.meta.dev_name} - > - <CopyableCode - copy={protagonist.dev_name} - variant="soft" - highContrast - color="gray" - > - {protagonist.dev_name} - </CopyableCode> - </Listing> - )} - {protagonist.type === TankType.TANK_TYPE_PREMIUM && ( - <Listing - label={strings.website.tools.tankopedia.meta.purchase} - > - <Flex align="center" gap="1"> - {protagonist.price!.value / 400} - <img - style={{ width: "1em", height: "1em" }} - alt="gold" - src={asset("icons/currencies/gold.webp")} - /> - </Flex> - </Listing> - )} - <Listing - label={ - protagonist.type === TankType.TANK_TYPE_RESEARCHABLE - ? strings.website.tools.tankopedia.meta.purchase - : strings.website.tools.tankopedia.meta.restoration - } - > - <Flex align="center" gap="1"> - {protagonist.price!.value.toLocaleString(locale)} - <img - style={{ width: "1em", height: "1em" }} - alt={TankPriceType[protagonist.price!.type]} - src={asset( - `icons/currencies/${ - protagonist.price!.type === - TankPriceType.TANK_PRICE_TYPE_GOLD - ? "gold" - : "silver" - }.webp`, - )} - /> - </Flex> - </Listing> - <Listing label={strings.website.tools.tankopedia.meta.sale}> - <Flex align="center" gap="1"> - {(protagonist.price!.value / 2).toLocaleString(locale)} - <img - style={{ width: "1em", height: "1em" }} - alt={TankPriceType[protagonist.price!.type]} - src={asset( - `icons/currencies/${ - protagonist.price!.type === - TankPriceType.TANK_PRICE_TYPE_GOLD - ? "gold" - : "silver" - }.webp`, - )} - /> - </Flex> - </Listing> - {protagonist.research_cost && ( - <Listing - label={strings.website.tools.tankopedia.meta.research} - > - <Flex align="center" gap="1"> - {( - protagonist.research_cost.research_cost_type! - .value as number - ).toLocaleString(locale)} - <img - style={{ width: "1em", height: "1em" }} - alt="xp" - src={asset("icons/currencies/xp.webp")} - /> - </Flex> - </Listing> - )} - </Flex> - </Flex> - </Flex> - </Flex> - </Box> - </Box> - </Flex> - ); -} diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/components/Arrow.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/components/Arrow.tsx deleted file mode 100644 index 79acdf3f3..000000000 --- a/packages/website/src/components/Tankopedia/TechTreeSection/components/Arrow.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { CaretRightIcon } from '@radix-ui/react-icons'; -import { Text } from '@radix-ui/themes'; - -export function Arrow() { - return ( - <Text color="gray"> - <CaretRightIcon /> - </Text> - ); -} diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx deleted file mode 100644 index 9a6ceb7f7..000000000 --- a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { asset, formatCompact, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { Box, Flex, Skeleton, Text } from "@radix-ui/themes"; -import { awaitableAverageDefinitions } from "../../../../core/awaitables/averageDefinitions"; -import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions"; -import { useLocale } from "../../../../hooks/useLocale"; -import { Tankopedia } from "../../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; -import { LinkI18n } from "../../../LinkI18n"; - -type NodeProps = MaybeSkeletonComponentProps & { - id: number; - nextIds?: number[]; - highlight?: boolean; -}; - -const [tankDefinitions, averageDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableAverageDefinitions, -]); - -export function Node({ id, highlight, nextIds, skeleton }: NodeProps) { - const { locale, strings, unwrap } = useLocale(); - const xpMultiplier = Tankopedia.use((state) => state.xpMultiplier); - const tank = tankDefinitions.tanks[id]; - const nextTanks = nextIds?.map((id) => tankDefinitions.tanks[id]); - const thisTankXp = - tank.research_cost === undefined || tank.tier === 1 - ? 0 - : (tank.research_cost.research_cost_type!.value as number)! / - xpMultiplier; - const nextTanksXp = nextTanks?.reduce( - (xp, tank) => - xp + - (tank.research_cost === undefined || tank.tier === 1 - ? 0 - : (tank.research_cost.research_cost_type!.value as number) / - xpMultiplier), - 0, - ); - const averages = averageDefinitions.averages[tank.id]?.mu; - const averageXp = averages ? averages.xp / averages.battles : undefined; - const games = - averages && nextTanks ? Math.round(nextTanksXp! / averageXp!) : undefined; - - return ( - <LinkI18n - locale={locale} - href={`/tanks/${tankDefinitions.tanks[id].slug}`} - key={id} - style={{ - textDecoration: "none", - color: "inherit", - position: "relative", - }} - > - <Box - mx={highlight ? "2" : "0"} - style={{ - background: highlight - ? `url(${asset(`flags/fade_small/${tank.nation}.webp`)})` - : undefined, - backgroundPosition: "center", - backgroundSize: "cover", - boxShadow: highlight ? "var(--shadow-4)" : undefined, - borderRadius: "var(--radius-4)", - overflow: "hidden", - }} - > - <Flex - direction="column" - align="center" - py="4" - px={highlight ? "4" : "2"} - style={{ - backdropFilter: highlight ? "brightness(0.25)" : undefined, - WebkitBackdropFilter: highlight ? "brightness(0.25)" : undefined, - }} - > - <img - alt={unwrap(tank.name!)} - src={asset(`icons/tanks/big/${id}.webp`)} - width={64} - height={64} - style={{ - objectFit: "contain", - }} - /> - - <Flex direction="column" align="center"> - <Flex gap="2" align="center"> - <Text color="gray" size="1"> - {TIER_ROMAN_NUMERALS[tank.tier]} - </Text> - <Text wrap="nowrap">{unwrap(tank.name!)}</Text> - </Flex> - <Flex gap="2" align="center"> - <Text color="gray" size="1" wrap="nowrap"> - <Flex gap="1" align="center"> - <img - alt="XP" - src={asset("icons/currencies/xp.webp")} - style={{ - width: "1em", - height: "1em", - objectFit: "contain", - objectPosition: "center", - }} - /> - {formatCompact(locale, thisTankXp)} - </Flex> - </Text> - <Text color="gray" size="1" wrap="nowrap"> - <Flex gap="1" align="center"> - <img - alt="Silver" - src={asset("icons/currencies/silver.webp")} - style={{ - width: "1em", - height: "1em", - objectFit: "contain", - objectPosition: "center", - }} - /> - {formatCompact(locale, tank.price!.value)} - </Flex> - </Text> - </Flex> - - {averages && nextTanks && tank.tier !== 10 && ( - <Text color="gray" size="1" mt="2"> - {skeleton && <Skeleton height="1em" width="4em" />} - {!skeleton && - (games === 1 - ? strings.website.tools.tankopedia.tech_tree.battle - : literals( - strings.website.tools.tankopedia.tech_tree.battles, - { count: games }, - ))} - </Text> - )} - </Flex> - </Flex> - </Box> - </LinkI18n> - ); -} diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx deleted file mode 100644 index 8c7a906f2..000000000 --- a/packages/website/src/components/Tankopedia/TechTreeSection/index.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import { asset, TankType } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { CaretLeftIcon, CaretRightIcon, PlusIcon } from "@radix-ui/react-icons"; -import { Flex, Heading, IconButton, ScrollArea, Text } from "@radix-ui/themes"; -import { Fragment, useEffect, useMemo, useRef, useState } from "react"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { useLocale } from "../../../hooks/useLocale"; -import { Duel } from "../../../stores/duel"; -import { Tankopedia } from "../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; -import { Arrow } from "./components/Arrow"; -import { Node } from "./components/Node"; - -type Line = number[]; - -const tankDefinitions = await awaitableTankDefinitions; - -export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10]; - -export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) { - const { locale, strings } = useLocale(); - const master = Duel.use((state) => state.protagonist.tank); - const xpMultiplier = Tankopedia.use((state) => state.xpMultiplier); - const container = useRef<HTMLDivElement>(null); - const lines = useMemo(() => { - function extend(line: Line): Line[] { - const root = tankDefinitions.tanks[line.at(-1)!]; - - if ( - root.ancestors === undefined || - root.ancestors.length === 0 || - root.tier === 1 - ) { - return [line]; - } else { - if (root.ancestors.length === 1 || root.tier === 2) { - line.push( - root.ancestors.find( - (ancestor) => !tankDefinitions.tanks[ancestor].deprecated, - ) ?? root.ancestors[0], - ); - return extend(line); - } else { - return root.ancestors - .map((ancestor) => { - const newLine = [...line, ancestor]; - return extend(newLine); - }) - .flat(); - } - } - } - - return extend([master.id]); - }, [master]); - const [lineIndex, setLineIndex] = useState(0); - const line = useMemo( - () => (lines.length === 0 ? [master.id] : [...lines[lineIndex]].reverse()), - [master, lineIndex], - ); - const totalXp = line.reduce( - (xp, id) => - xp + - (tankDefinitions.tanks[id].research_cost === undefined || - tankDefinitions.tanks[id].tier === 1 - ? 0 - : (tankDefinitions.tanks[id].research_cost.research_cost_type! - .value as number)), - 0, - ); - const totalCredits = line.reduce( - (credits, id) => credits + tankDefinitions.tanks[id].price!.value, - 0, - ); - - useEffect(() => { - if (!container.current) return; - container.current.scrollLeft = container.current.scrollWidth; - }); - - if ( - master.type !== TankType.TANK_TYPE_RESEARCHABLE || - master.ancestors === undefined || - master.ancestors.length === 0 - ) { - return null; - } - - return ( - <Flex - direction="column" - align="center" - gap="0" - style={{ backgroundColor: "var(--color-surface)" }} - py="6" - > - <Flex direction="column" align="center"> - <Heading size="6"> - {strings.website.tools.tankopedia.tech_tree.title} - </Heading> - - {master.tier > 1 && ( - <Flex gap="4" align="center"> - <Text color="gray" wrap="nowrap"> - <Flex gap="1" align="center"> - <img - alt="XP" - src={asset("icons/currencies/xp.webp")} - style={{ - width: "1em", - height: "1em", - objectFit: "contain", - objectPosition: "center", - }} - /> - {Math.round(totalXp / xpMultiplier).toLocaleString(locale)} - </Flex> - </Text> - - <Text color="gray" wrap="nowrap"> - <Flex gap="1" align="center"> - <img - alt="Silver" - src={asset("icons/currencies/silver.webp")} - style={{ - width: "1em", - height: "1em", - objectFit: "contain", - objectPosition: "center", - }} - /> - {totalCredits.toLocaleString(locale)} - </Flex> - </Text> - </Flex> - )} - - <Flex gap="1" mt="2"> - {XP_MULTIPLIERS.map((multiplier) => { - const selected = xpMultiplier === multiplier; - - return ( - <Flex - key={multiplier} - style={{ - background: "url(/assets/images/icons/xp-multiplier.png)", - backgroundSize: "contain", - backgroundRepeat: "no-repeat", - backgroundPosition: "center", - opacity: selected ? 1 : 0.5, - cursor: "pointer", - }} - onClick={() => { - Tankopedia.mutate((draft) => { - draft.xpMultiplier = multiplier; - }); - }} - width="2.5rem" - justify="center" - align="center" - pt="1" - pl="1" - > - <Text size="2"> - {literals(strings.common.units.x, { value: multiplier })} - </Text> - </Flex> - ); - })} - </Flex> - </Flex> - - <ScrollArea type="hover" scrollbars="horizontal" ref={container}> - <Flex align="center" gap="2" justify="center" p="4"> - {line.map((id, index) => { - const last = index === line.length - 1; - const tank = tankDefinitions.tanks[id]; - - return ( - <Fragment key={id}> - {index > 0 && <Arrow key={`${id}-arrow`} />} - - <Node - key={`${id}-node`} - id={id} - nextIds={last ? tank.successors : [line[index + 1]]} - highlight={last} - skeleton={skeleton} - /> - </Fragment> - ); - })} - - {master.tier < 10 && ( - <> - <Arrow /> - {master.successors!.map((id, index) => ( - <Fragment key={id}> - {index > 0 && ( - <Text color="gray" key={`${id}-plus`}> - <PlusIcon /> - </Text> - )} - <Node key={`${id}-node}`} id={id} skeleton={skeleton} /> - </Fragment> - ))} - </> - )} - </Flex> - </ScrollArea> - - <Flex> - {lines.length > 1 && ( - <Flex align="center" gap="2"> - <IconButton - size="2" - variant="soft" - onClick={() => - setLineIndex((lines.length + lineIndex - 1) % lines.length) - } - > - <CaretLeftIcon /> - </IconButton> - <Text> - {literals(strings.website.tools.tankopedia.tech_tree.route, { - index: lineIndex + 1, - count: lines.length, - })} - </Text> - <IconButton - size="2" - variant="soft" - onClick={() => setLineIndex((lineIndex + 1) % lines.length)} - > - <CaretRightIcon /> - </IconButton> - </Flex> - )} - </Flex> - </Flex> - ); -} diff --git a/packages/website/src/components/Tankopedia/VideoSection.tsx b/packages/website/src/components/Tankopedia/VideoSection.tsx deleted file mode 100644 index 0c879538b..000000000 --- a/packages/website/src/components/Tankopedia/VideoSection.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { youtubers } from "@blitzkit/core"; -import { Box, Flex, Heading, Link, Skeleton, Text } from "@radix-ui/themes"; -import { awaitableReviews } from "../../core/awaitables/reviews"; -import { Var } from "../../core/radix/var"; -import { useLocale } from "../../hooks/useLocale"; -import { Duel } from "../../stores/duel"; -import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps"; -import { PageWrapper } from "../PageWrapper"; -import { VerifiedIcon } from "../VerifiedIcon"; - -const reviews = await awaitableReviews; - -export function VideoSection({ skeleton }: MaybeSkeletonComponentProps) { - const tank = Duel.use((state) => state.protagonist.tank); - const videos = reviews.reviews[tank.id]?.videos; - const { strings } = useLocale(); - - if (videos === undefined || videos.length === 0) return null; - - return ( - <PageWrapper noFlex1 noMinHeight> - <Flex direction="column" gap="6" align="center"> - <Flex direction="column" align="center"> - <Heading size="6"> - {strings.website.tools.tankopedia.review.title} - </Heading> - </Flex> - - <Flex gap="3" wrap="wrap" justify="center"> - {videos.map((video) => { - const image = `https://i.ytimg.com/vi/${video.id}/hqdefault.jpg`; - const youtuber = youtubers.find( - (youtuber) => youtuber.id === video.author, - ); - - if (!youtuber) return null; - - return ( - <Link - color="gray" - highContrast - underline="hover" - key={video.id} - href={`https://www.youtube.com/watch?v=${video.id}`} - target="_blank" - > - <Flex - direction="column" - style={{ - borderRadius: "var(--radius-3)", - overflow: "hidden", - background: skeleton - ? Var("color-panel-solid") - : `url(${image})`, - backgroundPosition: "0 18px", - backgroundSize: "cover", - }} - > - <Box - style={{ - background: skeleton ? "transparent" : `url(${image})`, - backgroundSize: "cover", - backgroundPosition: "center", - height: 128, - aspectRatio: "16 / 9", - }} - /> - <Flex - p="2" - width="100%" - justify="center" - style={{ - backdropFilter: "blur(4rem)", - WebkitBackdropFilter: "blur(4rem)", - }} - align="center" - gap="1" - > - <Text> - {!skeleton && youtuber.name} - {skeleton && <Skeleton height="1em" width="7rem" />} - </Text> - {!skeleton && <VerifiedIcon width="1em" height="1em" />} - </Flex> - </Flex> - </Link> - ); - })} - </Flex> - </Flex> - </PageWrapper> - ); -} diff --git a/packages/website/src/components/TanksEmbed.tsx b/packages/website/src/components/TanksEmbed.tsx deleted file mode 100644 index a68d4b57e..000000000 --- a/packages/website/src/components/TanksEmbed.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { - formatCompositeStat, - TankClass, - TankDefinition, - TankType, - type CompositeStats, - type CompositeStatsKey, -} from "@blitzkit/core"; -import strings from "@blitzkit/i18n/strings/en.json"; -import { amberDark, blueDark } from "@radix-ui/colors"; -import { Flex, Text, type FlexProps } from "@radix-ui/themes"; -import { times } from "lodash-es"; -import type { ReactNode } from "react"; -import { breakdownConfig } from "../constants/embeds"; -import { toRadiusVar } from "../core/radix/utils"; -import { useLocale } from "../hooks/useLocale"; -import { useEmbedStateCurry } from "../stores/embedState/utilities"; -import { ClassHeavy } from "./ClassIcon/components/ClassHeavy"; -import { ClassLight } from "./ClassIcon/components/ClassLight"; -import { ClassMedium } from "./ClassIcon/components/ClassMedium"; -import { ClassTankDestroyer } from "./ClassIcon/components/ClassTankDestroyer"; - -type BreakdownEmbedWrapperProps = FlexProps & { - children: ReactNode; -}; - -export function BreakdownEmbedWrapper({ - children, - ...props -}: BreakdownEmbedWrapperProps) { - const { useEmbedState } = useEmbedStateCurry<typeof breakdownConfig>(); - - return ( - <Flex - direction="column" - gap={useEmbedState("listGap")} - height="100%" - {...props} - > - {children} - </Flex> - ); -} - -interface BreakdownEmbedCardProps { - tank: TankDefinition | null | undefined; - composite: CompositeStats; -} - -export function BreakdownEmbedCard({ - tank, - composite, -}: BreakdownEmbedCardProps) { - const { useEmbedState, useRichText } = - useEmbedStateCurry<typeof breakdownConfig>(); - const { unwrap } = useLocale(); - - return ( - <Flex - flexShrink="0" - direction="column" - style={{ - borderRadius: toRadiusVar(useEmbedState("cardRadius")), - overflow: "hidden", - backgroundColor: useEmbedState("cardBodyBackgroundColor"), - }} - > - <Flex - justify="center" - style={{ - backgroundColor: useEmbedState("cardHeaderBackgroundColor"), - }} - p={useEmbedState("cardHeaderPadding")} - > - <Text - {...{ - ...useRichText("cardTitle"), - style: - tank === null - ? undefined - : tank === undefined || - tank.type === TankType.TANK_TYPE_RESEARCHABLE || - !useEmbedState("cardTitleTypeColor") - ? undefined - : { - color: - tank.type === TankType.TANK_TYPE_PREMIUM - ? amberDark.amber11 - : blueDark.blue11, - }, - }} - > - <Flex align="center" gap="1"> - {useEmbedState("cardTitleClassIcon") && - tank !== null && - tank !== undefined && ( - <> - {tank.class === TankClass.TANK_CLASS_LIGHT && ( - <ClassLight width="1em" height="1em" /> - )} - {tank.class === TankClass.TANK_CLASS_MEDIUM && ( - <ClassMedium width="1em" height="1em" /> - )} - {tank.class === TankClass.TANK_CLASS_HEAVY && ( - <ClassHeavy width="1em" height="1em" /> - )} - {tank.class === TankClass.TANK_CLASS_TANK_DESTROYER && ( - <ClassTankDestroyer width="1em" height="1em" /> - )} - </> - )}{" "} - {tank === null - ? "Total" - : tank === undefined - ? `Unknown tank` - : unwrap(tank.name!)} - </Flex> - </Text> - </Flex> - - <Flex p={useEmbedState("cardBodyPadding")}> - {times(4, (index) => { - const column = (index + 1) as 1 | 2 | 3 | 4; - const statKey = useEmbedState(`column${column}`) as CompositeStatsKey; - const customLabel = useEmbedState(`column${column}CustomLabel`); - - return ( - <Flex - key={column} - direction="column" - align="center" - gap={useEmbedState("columnGap")} - flexGrow="1" - > - <Text {...useRichText("columnValue")}> - {formatCompositeStat(composite[statKey], statKey, composite)} - </Text> - <Text {...useRichText("columnLabel")}> - {customLabel.length === 0 - ? strings.common.composite_stats[statKey] - : customLabel} - </Text> - </Flex> - ); - })} - </Flex> - </Flex> - ); -} diff --git a/packages/website/src/components/TierList/Controls/components/Share.tsx b/packages/website/src/components/TierList/Controls/components/Share.tsx deleted file mode 100644 index d1ab64a44..000000000 --- a/packages/website/src/components/TierList/Controls/components/Share.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Share2Icon } from '@radix-ui/react-icons'; -import { generateTierListParams } from '../../../../core/blitzkit/generateTierListParams'; -import { useLocale } from '../../../../hooks/useLocale'; -import { TierList } from '../../../../stores/tierList'; -import { CopyButton } from '../../../CopyButton'; - -export function Share() { - const rows = TierList.use((state) => state.rows); - const { strings } = useLocale(); - - return ( - <CopyButton - variant="outline" - copy={() => - `${location.origin}/tier-list?${generateTierListParams(rows)}` - } - > - <Share2Icon /> {strings.website.tools.tier_list.buttons.share} - </CopyButton> - ); -} diff --git a/packages/website/src/components/TierList/Controls/index.tsx b/packages/website/src/components/TierList/Controls/index.tsx deleted file mode 100644 index fd1cd56e3..000000000 --- a/packages/website/src/components/TierList/Controls/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { ReloadIcon } from '@radix-ui/react-icons'; -import { Button, Flex } from '@radix-ui/themes'; -import { useLocale } from '../../../hooks/useLocale'; -import { TierList } from '../../../stores/tierList'; -import { Share } from './components/Share'; - -export function TierListControls() { - const { strings } = useLocale(); - - return ( - <Flex justify="center" gap="2"> - <Button - color="red" - onClick={() => { - TierList.mutate((draft) => { - Object.assign(draft, TierList.initial); - }); - }} - > - <ReloadIcon /> {strings.website.tools.tier_list.buttons.reset} - </Button> - - <Share /> - </Flex> - ); -} diff --git a/packages/website/src/components/TierList/Row/components/Header.tsx b/packages/website/src/components/TierList/Row/components/Header.tsx deleted file mode 100644 index c41632323..000000000 --- a/packages/website/src/components/TierList/Row/components/Header.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { - CheckIcon, - Cross1Icon, - Pencil1Icon, - Pencil2Icon, -} from '@radix-ui/react-icons'; -import { - Button, - Flex, - Heading, - IconButton, - Popover, - Table, - TextField, -} from '@radix-ui/themes'; -import { useRef, useState } from 'react'; -import { Var } from '../../../../core/radix/var'; -import { TierList } from '../../../../stores/tierList'; -import { tierListRows } from '../../Table/constants'; - -interface HeaderProps { - index: number; -} - -export function Header({ index }: HeaderProps) { - const rowStyle = tierListRows[index]; - const name = TierList.use((state) => state.rows[index].name); - const input = useRef<HTMLInputElement>(null); - const [open, setOpen] = useState(false); - - return ( - <Table.RowHeaderCell - width="0" - style={{ backgroundColor: Var(rowStyle.color) }} - > - <Popover.Root open={open} onOpenChange={setOpen}> - <Popover.Trigger> - <Heading> - <Flex direction="column" align="center" gap="2"> - {name} - - <IconButton variant="solid" color="gray" highContrast> - <Pencil2Icon /> - </IconButton> - </Flex> - </Heading> - </Popover.Trigger> - - <Popover.Content> - <Flex direction="column" gap="2"> - <TextField.Root - variant="classic" - placeholder="Tier name" - ref={input} - defaultValue={name} - onKeyDown={(event) => { - if (!input.current) return; - - if (event.key === 'Enter') { - input.current.blur(); - } else if (event.key === 'Escape') { - input.current.value = name; - input.current.blur(); - } - }} - onBlur={() => { - if (!input.current) return; - - TierList.mutate((draft) => { - if (!input.current) return; - draft.rows[index].name = input.current.value; - }); - - setOpen(false); - }} - > - <TextField.Slot> - <Pencil1Icon /> - </TextField.Slot> - </TextField.Root> - - <Flex gap="1" width="100%"> - <Popover.Close - onClick={() => { - if (!input.current) return; - - input.current.value = name; - }} - > - <Button variant="outline" color="red" style={{ flex: 1 }}> - <Cross1Icon /> Cancel - </Button> - </Popover.Close> - - <Button - style={{ flex: 1 }} - onClick={() => { - if (!input.current) return; - - input.current.blur(); - }} - > - <CheckIcon /> Apply - </Button> - </Flex> - </Flex> - </Popover.Content> - </Popover.Root> - </Table.RowHeaderCell> - ); -} diff --git a/packages/website/src/components/TierList/Row/index.tsx b/packages/website/src/components/TierList/Row/index.tsx deleted file mode 100644 index 17244ee36..000000000 --- a/packages/website/src/components/TierList/Row/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Flex, Table } from '@radix-ui/themes'; -import { useEffect, useRef } from 'react'; -import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions'; -import { TierList } from '../../../stores/tierList'; -import { tierListRowElements } from '../Table/constants'; -import { TierListTile } from '../Tile'; -import { Header } from './components/Header'; - -interface TierListRowProps { - index: number; -} - -const tankDefinitions = await awaitableTankDefinitions; - -export function TierListRow({ index }: TierListRowProps) { - const tanks = TierList.use((state) => state.rows[index].tanks); - const row = useRef<HTMLTableRowElement>(null); - - useEffect(() => { - if (!row.current) return; - - tierListRowElements.add(row.current); - - return () => { - if (!row.current) return; - - tierListRowElements.delete(row.current); - }; - }); - - return ( - <Table.Row ref={row} data-index={index}> - <Header index={index} /> - - <Table.Cell> - <Flex wrap="wrap" height="100%"> - {tanks.map((id, tankIndex) => { - const tank = tankDefinitions.tanks[id]; - return ( - <TierListTile - isPlaced - key={id} - tank={tank} - rowIndex={index} - tileIndex={tankIndex} - /> - ); - })} - </Flex> - </Table.Cell> - </Table.Row> - ); -} diff --git a/packages/website/src/components/TierList/Table/constants.ts b/packages/website/src/components/TierList/Table/constants.ts deleted file mode 100644 index c90423b36..000000000 --- a/packages/website/src/components/TierList/Table/constants.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { VarName } from '../../../core/radix/var'; - -export const tierListRows: { - name: string; - color: VarName; -}[] = [ - { name: 'S', color: 'tomato-9' }, - { name: 'A', color: 'orange-9' }, - { name: 'B', color: 'amber-9' }, - { name: 'C', color: 'green-9' }, - { name: 'D', color: 'blue-9' }, -]; - -export const tierListRowElements = new Set<HTMLTableRowElement>(); -export const tierListCardElements = tierListRows.map( - () => new Set<HTMLDivElement>(), -); diff --git a/packages/website/src/components/TierList/Table/index.tsx b/packages/website/src/components/TierList/Table/index.tsx deleted file mode 100644 index 7c5b74c55..000000000 --- a/packages/website/src/components/TierList/Table/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Table } from '@radix-ui/themes'; -import { TierListRow } from '../Row'; -import { tierListRows } from './constants'; - -export function TierListTable() { - return ( - <Table.Root variant="surface" mb="6"> - <Table.Body> - {tierListRows.map((_, index) => ( - <TierListRow index={index} key={index} /> - ))} - </Table.Body> - </Table.Root> - ); -} diff --git a/packages/website/src/components/TierList/Tile.tsx b/packages/website/src/components/TierList/Tile.tsx deleted file mode 100644 index a5fc0b7f7..000000000 --- a/packages/website/src/components/TierList/Tile.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import type { TankDefinition } from '@blitzkit/core'; -import { Box } from '@radix-ui/themes'; -import { - useCallback, - useEffect, - useRef, - type PointerEvent as ReactPointerEvent, -} from 'react'; -import { TierList } from '../../stores/tierList'; -import { TankCard } from '../TankCard'; -import { tierListCardElements, tierListRowElements } from './Table/constants'; - -type TierListTileProps = { - tank: TankDefinition; -} & ( - | { - isPlaced?: false; - } - | { - isPlaced: true; - rowIndex: number; - tileIndex: number; - } -); - -export function TierListTile(props: TierListTileProps) { - const card = useRef<HTMLDivElement>(null); - const lastPosition = useRef({ x: 0, y: 0 }); - const handlePointerDown = useCallback( - (event: ReactPointerEvent<HTMLSpanElement>) => { - if (!card.current) return; - - event.preventDefault(); - - const rect = card.current.getBoundingClientRect(); - - card.current.style.position = 'fixed'; - card.current.style.cursor = 'grabbing'; - card.current.style.zIndex = '1'; - card.current.style.left = `${rect.left}px`; - card.current.style.top = `${rect.top}px`; - - lastPosition.current = { x: event.clientX, y: event.clientY }; - - window.scrollTo({ behavior: 'smooth', top: 124 }); - - window.addEventListener('pointermove', handlePointerMove); - window.addEventListener('pointerup', handlePointerUp); - - TierList.mutate((draft) => { - draft.dragging = true; - }); - }, - [], - ); - const handlePointerMove = useCallback((event: PointerEvent) => { - if (!card.current) return; - - event.preventDefault(); - - const rect = card.current.getBoundingClientRect(); - const dx = event.clientX - lastPosition.current.x; - const dy = event.clientY - lastPosition.current.y; - - card.current.style.left = `${rect.left + dx}px`; - card.current.style.top = `${rect.top + dy}px`; - - lastPosition.current = { x: event.clientX, y: event.clientY }; - }, []); - const handlePointerUp = useCallback(() => { - if (!card.current) return; - - const cardRect = card.current.getBoundingClientRect(); - const cardX = (cardRect.left + cardRect.right) / 2; - const cardY = (cardRect.top + cardRect.bottom) / 2; - let wasPlaced = false; - - rowLoop: for (const row of tierListRowElements) { - const rowRect = row.getBoundingClientRect(); - const isWithinRow = - cardX >= rowRect.left && - cardX <= rowRect.right && - cardY >= rowRect.top && - cardY <= rowRect.bottom; - - if (!isWithinRow) continue; - - const rowIndex = Number(row.dataset.index); - - for (const tile of tierListCardElements[rowIndex]) { - const tileId = Number(tile.dataset.tileId); - - if (tileId === props.tank.id) continue; - - const tileRect = tile.getBoundingClientRect(); - const isWithinTile = - cardX >= tileRect.left && - cardX <= tileRect.right && - cardY >= tileRect.top && - cardY <= tileRect.bottom; - - if (!isWithinTile) continue; - - const tileIndex = Number(tile.dataset.tileIndex); - - TierList.mutate((draft) => { - draft.dragging = false; - draft.rows.forEach((row) => { - row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id); - }); - draft.rows[rowIndex].tanks.splice(tileIndex, 0, props.tank.id); - draft.placedTanks.add(props.tank.id); - }); - - wasPlaced = true; - - break rowLoop; - } - - TierList.mutate((draft) => { - draft.dragging = false; - draft.rows.forEach((row) => { - row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id); - }); - draft.rows[rowIndex].tanks.push(props.tank.id); - draft.placedTanks.add(props.tank.id); - }); - - wasPlaced = true; - - break; - } - - if (!wasPlaced) { - TierList.mutate((draft) => { - draft.dragging = false; - draft.rows.forEach((row) => { - row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id); - }); - draft.placedTanks.delete(props.tank.id); - }); - } - - card.current.style.position = 'static'; - card.current.style.zIndex = 'unset'; - card.current.style.cursor = 'grab'; - - window.removeEventListener('pointermove', handlePointerMove); - window.removeEventListener('pointerup', handlePointerUp); - }, []); - - useEffect(() => { - if (!props.isPlaced || !card.current) return; - - tierListCardElements[props.rowIndex].add(card.current); - - return () => { - if (!card.current) return; - - tierListCardElements[props.rowIndex].delete(card.current); - }; - }, []); - - return ( - <Box - p="2" - ref={card} - data-tile-index={props.isPlaced ? props.tileIndex : undefined} - data-tile-id={props.tank.id} - onPointerDown={handlePointerDown} - style={{ cursor: 'grab', touchAction: 'none' }} - > - <TankCard noLink tank={props.tank} /> - </Box> - ); -} diff --git a/packages/website/src/components/TierList/Tiles.tsx b/packages/website/src/components/TierList/Tiles.tsx deleted file mode 100644 index 45a7d16a8..000000000 --- a/packages/website/src/components/TierList/Tiles.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { metaSortTank } from "@blitzkit/core"; -import { times } from "lodash-es"; -import { useMemo, useState } from "react"; -import usePromise from "react-promise-suspense"; -import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions"; -import { awaitableTankDefinitions } from "../../core/awaitables/tankDefinitions"; -import { filterTanks } from "../../core/blitzkit/filterTanks"; -import { TankFilters } from "../../stores/tankFilters"; -import { TierList } from "../../stores/tierList"; -import { SkeletonTankCard } from "../TankSearch/components/SkeletonTankCard"; -import { TankCardWrapper } from "../TankSearch/components/TankCardWrapper"; -import { TierListTile } from "./Tile"; - -const [tankDefinitions, gameDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableGameDefinitions, -]); - -const tanks = Object.values(tankDefinitions.tanks); - -const PREVIEW_COUNT = 32; -const DEFAULT_LOADED_CARDS = 75; - -export function TierListTiles() { - const filters = TankFilters.use(); - const placedTanks = TierList.use((state) => state.placedTanks); - const filteredTanks = usePromise( - () => - filterTanks(filters, tanks).then((tanks) => - tanks.filter(({ id }) => !placedTanks.has(id)) - ), - // react-promise-suspense has awful type annotations - [filters, placedTanks] as any - ); - const sorted = useMemo( - () => metaSortTank(filteredTanks, gameDefinitions).reverse(), - [filters, placedTanks] - ); - const [loadedTiles, setLoadedTiles] = useState(DEFAULT_LOADED_CARDS); - - return ( - <TankCardWrapper> - {sorted.slice(0, loadedTiles).map((tank) => ( - <TierListTile key={tank.id} tank={tank} /> - ))} - - {times(Math.min(PREVIEW_COUNT, sorted.length - loadedTiles), (index) => { - return ( - <SkeletonTankCard - key={index} - onIntersection={() => { - setLoadedTiles((state) => Math.min(state + 2, sorted.length)); - }} - /> - ); - })} - </TankCardWrapper> - ); -} diff --git a/packages/website/src/components/TierList/URLManager.tsx b/packages/website/src/components/TierList/URLManager.tsx deleted file mode 100644 index f861b535c..000000000 --- a/packages/website/src/components/TierList/URLManager.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useEffect } from 'react'; -import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions'; -import { generateTierListParams } from '../../core/blitzkit/generateTierListParams'; -import { TierList } from '../../stores/tierList'; - -const tankDefinitions = await awaitableTankDefinitions; - -export function URLManager() { - const rows = TierList.use((state) => state.rows); - - useEffect(() => { - const params = new URLSearchParams(window.location.search); - - params.forEach((value, key) => { - if (key.startsWith('row-')) { - const index = Number.parseInt(key.slice(4), 10); - const values = value - .split(',') - .map(Number) - .filter((id) => id in tankDefinitions.tanks); - - TierList.mutate((draft) => { - draft.rows[index].tanks = values; - }); - } - }); - }, []); - - useEffect(() => { - window.history.replaceState(null, '', `?${generateTierListParams(rows)}`); - }, [rows]); - - return null; -} diff --git a/packages/website/src/components/ToolCard.astro b/packages/website/src/components/ToolCard.astro deleted file mode 100644 index 6d54d7f52..000000000 --- a/packages/website/src/components/ToolCard.astro +++ /dev/null @@ -1,129 +0,0 @@ ---- -import { CaretRightIcon } from "@radix-ui/react-icons"; -import { - Box, - Button, - Flex, - Heading, - Text, - type ButtonProps, -} from "@radix-ui/themes"; -import type { Tool } from "../constants/tools"; -import { getStrings } from "../core/i18n/getStrings"; -import { LinkI18n } from "./LinkI18n"; - -interface Props { - tool: Tool; - locale: string; -} - -const { locale, tool } = Astro.props; -const strings = getStrings(locale); -const toolStrings = - strings.website.tools[ - (tool.strings ?? tool.id) as keyof typeof strings.website.tools - ]; ---- - -<style> - .container { - border-radius: var(--radius-2); - background-size: cover; - background-position: center; - height: 16rem; - - &[data-disabled="true"] { - opacity: 0.25; - } - - &[data-significant="true"] { - width: 100%; - height: min(20rem, 50vh); - } - } - - .link { - display: flex; - width: 100%; - height: 100%; - flex-direction: column; - color: inherit; - } -</style> - -<Flex - style={{ - backgroundImage: `url(/assets/images/tools/${tool.id}.webp)`, - }} - data-significant={tool.significant} - className="container" - position="relative" - overflow="hidden" - flexGrow={tool.significant ? undefined : "1"} - flexBasis={tool.significant ? undefined : { initial: undefined, sm: "0" }} -> - <LinkI18n - {locale} - className="link" - underline="none" - href={tool.href ?? `/${tool.id}`} - target={tool.href ? "_blank" : undefined} - > - <Box - flexGrow="1" - style={{ - backgroundImage: `url(/assets/images/tools/${tool.id}.webp)`, - backgroundSize: "cover", - backgroundPosition: "center", - }} - /> - - <Flex - px={{ initial: "6", md: tool.significant ? "8" : "6" }} - py={{ initial: "4", sm: tool.significant ? "6" : "4" }} - gap="4" - align="center" - justify="between" - width="100%" - direction={{ initial: "column", xs: "row" }} - style={{ - backgroundColor: "var(--color-panel-translucent)", - backdropFilter: "blur(64px)", - WebkitBackdropFilter: "blur(64px)", - }} - > - <Flex - direction="column" - justify="center" - align={{ initial: "center", xs: "start" }} - > - <Heading - align={{ initial: "center", sm: "left" }} - size={{ initial: "6", sm: tool.significant ? "7" : "5" }} - weight="medium" - > - {toolStrings.name} - </Heading> - <Text - align={{ initial: "center", sm: "left" }} - size={{ initial: "3", sm: tool.significant ? "4" : "3" }} - color="gray" - > - {toolStrings.description} - </Text> - </Flex> - - <Button - size={{ - initial: undefined, - sm: tool.significant ? "3" : undefined, - }} - color={tool.button as ButtonProps["color"]} - style={{ cursor: "inherit" }} - > - {toolStrings.button} - <CaretRightIcon /> - </Button> - </Flex> - </LinkI18n> -</Flex> diff --git a/packages/website/src/components/ToolOpenGraph.astro b/packages/website/src/components/ToolOpenGraph.astro deleted file mode 100644 index 6e025cbf7..000000000 --- a/packages/website/src/components/ToolOpenGraph.astro +++ /dev/null @@ -1,35 +0,0 @@ ---- -import { AstroSeo } from "@astrolib/seo"; -import type { Tool } from "../constants/tools"; -import { getStrings } from "../core/i18n/getStrings"; - -interface Props { - tool: Tool; - locale: string; -} - -const { locale, tool } = Astro.props; -const strings = getStrings(locale); -const toolStrings = - strings.website.tools[ - (tool.strings ?? tool.id) as keyof typeof strings.website.tools - ]; ---- - -<AstroSeo - title={toolStrings.name} - description={toolStrings.seo_description} - canonical={Astro.url.href} - openGraph={{ - url: Astro.url.href, - images: [ - { - url: `${Astro.url.origin}/assets/images/tools/${tool.id}.webp`, - }, - ], - title: toolStrings.name, - description: toolStrings.seo_description, - site_name: "BlitzKit", - }} - twitter={{ cardType: "summary_large_image" }} -/> diff --git a/packages/website/src/components/VerifiedIcon.tsx b/packages/website/src/components/VerifiedIcon.tsx deleted file mode 100644 index 363ca475d..000000000 --- a/packages/website/src/components/VerifiedIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function VerifiedIcon(props: ComponentProps<'svg'>) { - return ( - <svg - xmlns="http://www.w3.org/2000/svg" - height="24px" - viewBox="0 -960 960 960" - width="24px" - fill="currentColor" - {...props} - > - <path d="m344-60-76-128-144-32 14-148-98-112 98-112-14-148 144-32 76-128 136 58 136-58 76 128 144 32-14 148 98 112-98 112 14 148-144 32-76 128-136-58-136 58Zm34-102 102-44 104 44 56-96 110-26-10-112 74-84-74-86 10-112-110-24-58-96-102 44-104-44-56 96-110 24 10 112-74 86 74 84-10 114 110 24 58 96Zm102-318Zm-42 142 226-226-56-58-170 170-86-84-56 56 142 142Z" /> - </svg> - ); -} diff --git a/packages/website/src/components/WargamingAccountName.tsx b/packages/website/src/components/WargamingAccountName.tsx deleted file mode 100644 index df9107d3c..000000000 --- a/packages/website/src/components/WargamingAccountName.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { getAccountInfo, idToRegion } from "@blitzkit/core"; -import { Text } from "@radix-ui/themes"; -import { useMemo } from "react"; -import usePromise from "react-promise-suspense"; -import { App } from "../stores/app"; - -export function WargamingAccountName() { - const wargaming = App.use((state) => state.logins.wargaming); - const promise = useMemo(async () => { - if (!wargaming) return null; - - const data = await getAccountInfo( - idToRegion(wargaming.id), - wargaming.id, - [], - { access_token: wargaming.token } - ); - - return data?.nickname as string | undefined; - }, [wargaming]); - const nickname = usePromise(() => promise, []); - - return nickname ?? <Text color="gray">Unknown Account</Text>; -} diff --git a/packages/website/src/components/WargamingIcon.tsx b/packages/website/src/components/WargamingIcon.tsx deleted file mode 100644 index 3822e770d..000000000 --- a/packages/website/src/components/WargamingIcon.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { ComponentProps } from 'react'; - -export function WargamingIcon(props: ComponentProps<'svg'>) { - return ( - <svg - width="15" - height="15" - viewBox="0 0 15 15" - fill="none" - xmlns="http://www.w3.org/2000/svg" - {...props} - > - <g clipPath="url(#clip0_574_231)"> - <path - fillRule="evenodd" - clipRule="evenodd" - d="M7.46409 0.156338C8.91652 0.149247 10.3384 0.57301 11.55 1.37404C12.7616 2.17507 13.7085 3.31739 14.2709 4.65654C14.8332 5.9957 14.9859 7.47154 14.7095 8.89745C14.4331 10.3234 13.7401 11.6353 12.7181 12.6673C11.6961 13.6994 10.391 14.4052 8.96789 14.6955C7.54475 14.9859 6.06748 14.8477 4.72288 14.2984C3.37829 13.7492 2.22676 12.8136 1.41391 11.6099C0.601062 10.4062 0.163403 8.98846 0.156278 7.53603V7.50009C0.150882 5.5586 0.916644 3.69442 2.28523 2.31733C3.65382 0.940243 5.51323 0.162958 7.45472 0.156338H7.46409ZM7.46409 1.56259C8.76962 1.56799 10.0372 2.00218 11.0719 2.79837C12.1065 3.59456 12.8509 4.70863 13.1906 5.96922C13.5302 7.2298 13.4463 8.56706 12.9517 9.77529C12.4571 10.9835 11.5792 11.9958 10.4532 12.6563C10.8305 11.3103 10.9857 9.91172 10.9125 8.51571C11.2938 8.62353 11.5094 8.68446 11.8922 8.8079C11.47 7.70829 10.9518 6.64795 10.3438 5.63915C9.79373 6.67527 9.10279 7.63018 8.29065 8.47665L9.51565 8.40009C8.46097 10.2938 6.97347 11.2126 5.08909 11.4798C4.6594 9.33759 4.98128 5.85478 7.38597 4.21728L7.62972 5.38603C8.09847 4.63134 9.02503 3.6329 9.71409 2.93759C8.57965 2.49502 7.34402 2.37866 6.1469 2.60165C6.34534 2.77196 6.59065 2.98759 6.78909 3.15634C4.07815 4.3079 2.58909 6.20009 1.6094 8.40009C1.48639 7.55586 1.54531 6.69509 1.78221 5.87549C2.0191 5.0559 2.4285 4.29643 2.98295 3.64802C3.5374 2.99961 4.22409 2.47724 4.99695 2.11595C5.76982 1.75465 6.611 1.56279 7.46409 1.55321V1.56259Z" - fill="currentColor" - /> - </g> - <defs> - <clipPath id="clip0_574_231"> - <rect width="15" height="15" fill="currentColor" /> - </clipPath> - </defs> - </svg> - ); -} diff --git a/packages/website/src/components/WargamingLoginButton.tsx b/packages/website/src/components/WargamingLoginButton.tsx deleted file mode 100644 index 6ae1fbf07..000000000 --- a/packages/website/src/components/WargamingLoginButton.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { assertSecret, REGIONS } from '@blitzkit/core'; -import { Button, type ButtonProps, Dialog, Flex, Link } from '@radix-ui/themes'; -import { useLocale } from '../hooks/useLocale'; -import { WargamingIcon } from './WargamingIcon'; - -export function WargamingLoginButton({ children, ...props }: ButtonProps) { - const { strings } = useLocale(); - - return ( - <Dialog.Root> - <Dialog.Trigger> - <Button - color="red" - {...props} - children={ - <> - <WargamingIcon width={15} height={15} /> {children ?? 'Wargaming'} - </> - } - /> - </Dialog.Trigger> - - <Dialog.Content width="fit-content"> - <Dialog.Title align="center">Choose your region</Dialog.Title> - - <Flex gap="2" wrap="wrap" justify="center" align="center"> - {REGIONS.map((region) => ( - <Dialog.Close key={region}> - <Link - href={ - typeof window !== 'undefined' - ? `https://api.worldoftanks.${region}/wot/auth/login/?application_id=${assertSecret( - import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID, - )}&redirect_uri=${encodeURIComponent( - `${location.origin}/auth/wargaming?return=${location.origin}${location.pathname}`, - )}` - : undefined - } - > - <Button color="red"> - {strings.common.regions.normal[region]} - </Button> - </Link> - </Dialog.Close> - ))} - </Flex> - </Dialog.Content> - </Dialog.Root> - ); -} diff --git a/packages/website/src/constants/embeds.ts b/packages/website/src/constants/embeds.ts deleted file mode 100644 index 8284acea7..000000000 --- a/packages/website/src/constants/embeds.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { type CompositeStatsKey } from '@blitzkit/core'; -import { grayA, grayDark, grayDarkA } from '@radix-ui/colors'; -import type { ReactNode } from 'react'; -import { - BreakdownPreview, - BreakdownRenderer, - compositeStatsKeysOptions, -} from '../components/Embeds/breakdown'; -import type { - EmbedConfig, - ExtractEmbedConfigTypes, -} from '../stores/embedState'; -import { EmbedItemType } from '../stores/embedState/constants'; - -export const breakdownConfig = { - width: { type: EmbedItemType.Slider, default: 320, min: 128, max: 640 }, - height: { type: EmbedItemType.Slider, default: 492, min: 128, max: 1024 }, - - showTotal: { type: EmbedItemType.Boolean, default: true, pad: true }, - - listGap: { type: EmbedItemType.Size, default: '2' }, - listMaxTanks: { - type: EmbedItemType.Slider, - default: 4, - min: 0, - max: 8, - pad: true, - }, - - cardRadius: { type: EmbedItemType.Radius, default: '2' }, - cardHeaderBackgroundColor: { - type: EmbedItemType.Color, - default: grayDarkA.grayA3, - }, - cardHeaderPadding: { type: EmbedItemType.Size, default: '1' }, - cardTitle: { - type: EmbedItemType.RichText, - default: { color: grayDark.gray12, weight: 'bold', size: '3' }, - }, - cardTitleTypeColor: { type: EmbedItemType.Boolean, default: true }, - cardTitleClassIcon: { type: EmbedItemType.Boolean, default: true }, - cardBodyBackgroundColor: { - type: EmbedItemType.Color, - default: grayA.grayA11, - }, - cardBodyPadding: { - type: EmbedItemType.Size, - default: '2', - pad: true, - }, - - columnGap: { type: EmbedItemType.Size, default: '0' }, - columnValue: { - type: EmbedItemType.RichText, - default: { color: grayDark.gray12, size: '3', weight: 'regular' }, - }, - columnLabel: { - type: EmbedItemType.RichText, - default: { color: grayDark.gray11, size: '2', weight: 'regular' }, - pad: true, - }, - - column1: { - type: EmbedItemType.Enum, - default: 'cumulative_battles' satisfies CompositeStatsKey, - options: compositeStatsKeysOptions, - }, - column2: { - type: EmbedItemType.Enum, - default: 'normalized_wins' satisfies CompositeStatsKey, - options: compositeStatsKeysOptions, - }, - column3: { - type: EmbedItemType.Enum, - default: 'cumulative_wn8' satisfies CompositeStatsKey, - options: compositeStatsKeysOptions, - }, - column4: { - type: EmbedItemType.Enum, - default: 'normalized_damage_dealt' satisfies CompositeStatsKey, - options: compositeStatsKeysOptions, - }, - column1CustomLabel: { type: EmbedItemType.String, default: '' }, - column2CustomLabel: { type: EmbedItemType.String, default: '' }, - column3CustomLabel: { type: EmbedItemType.String, default: '' }, - column4CustomLabel: { type: EmbedItemType.String, default: '' }, -} satisfies EmbedConfig; - -export const embedConfigurations = { - breakdown: breakdownConfig, -} satisfies Record<string, EmbedConfig>; - -export const embedPreviews = { - breakdown: BreakdownPreview, -} satisfies Record<string, () => ReactNode>; -export function extractEmbedConfigDefaults<Config extends EmbedConfig>( - config: Config, -) { - return Object.fromEntries( - Object.entries(config).map(([key, value]) => [key, value.default]), - ) as ExtractEmbedConfigTypes<Config>; -} - -export const embedRenderers = { - breakdown: BreakdownRenderer, -} satisfies Record<string, () => ReactNode>; diff --git a/packages/website/src/constants/lightingEnvironments.ts b/packages/website/src/constants/lightingEnvironments.ts deleted file mode 100644 index 4abf6a32d..000000000 --- a/packages/website/src/constants/lightingEnvironments.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const ENVIRONMENTS = [ - 'lobby', - 'apartment', - 'warehouse', - 'dawn', - 'sunset', -] as const; diff --git a/packages/website/src/constants/navbar.ts b/packages/website/src/constants/navbar.ts deleted file mode 100644 index ca8abaef7..000000000 --- a/packages/website/src/constants/navbar.ts +++ /dev/null @@ -1 +0,0 @@ -export const NAVBAR_HEIGHT = 48; diff --git a/packages/website/src/constants/tools.ts b/packages/website/src/constants/tools.ts deleted file mode 100644 index 100979a14..000000000 --- a/packages/website/src/constants/tools.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { ButtonProps } from "@radix-ui/themes"; - -export interface Tool { - id: string; - strings?: string; - - button: ButtonProps["color"]; - significant?: boolean; - disabled?: boolean; - href?: string; -} - -export const tools: Record<string, Tool> = { - tanks: { - id: "tanks", - strings: "tankopedia", - button: "purple", - significant: true, - }, - players: { - id: "players", - disabled: true, - button: "blue", - }, - compare: { - id: "compare", - button: "crimson", - }, - performance: { - id: "performance", - button: "jade", - }, - charts: { - id: "charts", - disabled: true, - button: "bronze", - }, - playlist: { - id: "playlist", - button: "tomato", - }, - mixer: { - id: "mixer", - button: "gray", - }, - guess: { - id: "guess", - button: "cyan", - }, - gallery: { - id: "gallery", - button: "gold", - }, - session: { - id: "session", - button: "blue", - }, - tier_list: { - id: "tier-list", - strings: "tier_list", - button: "orange", - }, - embed: { - id: "embed", - button: "red", - }, - api: { - id: "api", - disabled: true, - button: "bronze", - }, - more: { - id: "more", - href: "https://discord.gg/nDt7AjGJQH", - button: "plum", - }, -}; diff --git a/packages/website/src/core/awaitables/averageDefinitions.ts b/packages/website/src/core/awaitables/averageDefinitions.ts deleted file mode 100644 index 9d21ebe9a..000000000 --- a/packages/website/src/core/awaitables/averageDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchAverageDefinitions } from '@blitzkit/core'; - -export const awaitableAverageDefinitions = fetchAverageDefinitions(); diff --git a/packages/website/src/core/awaitables/consumableDefinitions.ts b/packages/website/src/core/awaitables/consumableDefinitions.ts deleted file mode 100644 index be0b8efb0..000000000 --- a/packages/website/src/core/awaitables/consumableDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchConsumableDefinitions } from '@blitzkit/core'; - -export const awaitableConsumableDefinitions = fetchConsumableDefinitions(); diff --git a/packages/website/src/core/awaitables/discoveredIdsDefinitions.ts b/packages/website/src/core/awaitables/discoveredIdsDefinitions.ts deleted file mode 100644 index 56f046d74..000000000 --- a/packages/website/src/core/awaitables/discoveredIdsDefinitions.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { fetchDiscoveredIdsDefinitions } from '@blitzkit/core'; - -export const awaitableDiscoveredIdsDefinitions = - fetchDiscoveredIdsDefinitions(); diff --git a/packages/website/src/core/awaitables/equipmentDefinitions.ts b/packages/website/src/core/awaitables/equipmentDefinitions.ts deleted file mode 100644 index 82f2ae8aa..000000000 --- a/packages/website/src/core/awaitables/equipmentDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchEquipmentDefinitions } from '@blitzkit/core'; - -export const awaitableEquipmentDefinitions = fetchEquipmentDefinitions(); diff --git a/packages/website/src/core/awaitables/gallery.ts b/packages/website/src/core/awaitables/gallery.ts deleted file mode 100644 index 3571a940d..000000000 --- a/packages/website/src/core/awaitables/gallery.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchGallery } from '@blitzkit/core'; - -export const awaitableGallery = fetchGallery(); diff --git a/packages/website/src/core/awaitables/gameDefinitions.ts b/packages/website/src/core/awaitables/gameDefinitions.ts deleted file mode 100644 index 84db30fce..000000000 --- a/packages/website/src/core/awaitables/gameDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchGameDefinitions } from '@blitzkit/core'; - -export const awaitableGameDefinitions = fetchGameDefinitions(); diff --git a/packages/website/src/core/awaitables/modelDefinitions.ts b/packages/website/src/core/awaitables/modelDefinitions.ts deleted file mode 100644 index 6f1cc2fd0..000000000 --- a/packages/website/src/core/awaitables/modelDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchModelDefinitions } from '@blitzkit/core'; - -export const awaitableModelDefinitions = fetchModelDefinitions(); diff --git a/packages/website/src/core/awaitables/popularTanks.ts b/packages/website/src/core/awaitables/popularTanks.ts deleted file mode 100644 index 0c5d31348..000000000 --- a/packages/website/src/core/awaitables/popularTanks.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchPopularTanks } from '@blitzkit/core'; - -export const awaitablePopularTanks = fetchPopularTanks(); diff --git a/packages/website/src/core/awaitables/provisionDefinitions.ts b/packages/website/src/core/awaitables/provisionDefinitions.ts deleted file mode 100644 index 1f21a1459..000000000 --- a/packages/website/src/core/awaitables/provisionDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchProvisionDefinitions } from '@blitzkit/core'; - -export const awaitableProvisionDefinitions = fetchProvisionDefinitions(); diff --git a/packages/website/src/core/awaitables/reviews.ts b/packages/website/src/core/awaitables/reviews.ts deleted file mode 100644 index 21ff0c095..000000000 --- a/packages/website/src/core/awaitables/reviews.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchReviews } from '@blitzkit/core'; - -export const awaitableReviews = fetchReviews(); diff --git a/packages/website/src/core/awaitables/skillDefinitions.ts b/packages/website/src/core/awaitables/skillDefinitions.ts deleted file mode 100644 index 0b7dde41d..000000000 --- a/packages/website/src/core/awaitables/skillDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchSkillDefinitions } from '@blitzkit/core'; - -export const awaitableSkillDefinitions = fetchSkillDefinitions(); diff --git a/packages/website/src/core/awaitables/tankDefinitions.ts b/packages/website/src/core/awaitables/tankDefinitions.ts deleted file mode 100644 index fe76877f3..000000000 --- a/packages/website/src/core/awaitables/tankDefinitions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchTankDefinitions } from '@blitzkit/core'; - -export const awaitableTankDefinitions = fetchTankDefinitions(); diff --git a/packages/website/src/core/awaitables/tankNames.ts b/packages/website/src/core/awaitables/tankNames.ts deleted file mode 100644 index d043c9705..000000000 --- a/packages/website/src/core/awaitables/tankNames.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fetchTankNames } from '@blitzkit/core'; - -export const awaitableTankNames = fetchTankNames(); diff --git a/packages/website/src/core/blitz/applyPitchYawLimits.ts b/packages/website/src/core/blitz/applyPitchYawLimits.ts deleted file mode 100644 index 5176c1ff9..000000000 --- a/packages/website/src/core/blitz/applyPitchYawLimits.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - normalizeAngleRad, - type PitchLimits, - type YawLimits, -} from "@blitzkit/core"; -import { clamp } from "lodash-es"; -import { degToRad } from "three/src/math/MathUtils.js"; - -export const DEFAULT_PITCH_TRANSITION = 15; - -export function applyPitchYawLimits( - pitch: number, // rad - yaw: number, // rad - pitchLimits: PitchLimits, // deg - yawLimits?: YawLimits, // deg - verticalStabilizer = false, - downVerticalStabilizer = false, -) { - let newPitch = pitch; - let newYaw = yaw; - - if (yawLimits) { - newYaw = normalizeAngleRad( - clamp(newYaw, -degToRad(yawLimits.max), -degToRad(yawLimits.min)), - ); - } - - const depressionBuff = - verticalStabilizer || downVerticalStabilizer ? degToRad(3) : 0; - const elevationBuff = verticalStabilizer ? degToRad(4) : 0; - let lowerPitch = -degToRad(pitchLimits.max) - depressionBuff; - let upperPitch = -degToRad(pitchLimits.min) + elevationBuff; - const transition = degToRad( - pitchLimits.transition ?? DEFAULT_PITCH_TRANSITION, - ); - - if (pitchLimits.back) { - const range = degToRad(pitchLimits.back.range); - const yawRotatedAbs = Math.abs(normalizeAngleRad(newYaw - Math.PI)); - - if (yawRotatedAbs <= range / 2 + transition) { - if (yawRotatedAbs <= range / 2) { - // inside range - lowerPitch = -degToRad(pitchLimits.back.max); - upperPitch = -degToRad(pitchLimits.back.min); - } else { - // inside transition - const transitionProgress = (yawRotatedAbs - range / 2) / transition; - // console.log(transitionProgress); - const maxInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.back.max + - transitionProgress * pitchLimits.max; - const minInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.back.min + - transitionProgress * pitchLimits.min; - - lowerPitch = -degToRad(maxInterpolatedPitch); - upperPitch = -degToRad(minInterpolatedPitch); - } - } - } - - if (pitchLimits.front) { - const range = degToRad(pitchLimits.front.range); - const yawAbs = Math.abs(normalizeAngleRad(newYaw)); - - if (yawAbs <= range / 2 + transition) { - if (yawAbs <= range / 2) { - // inside range - lowerPitch = -degToRad(pitchLimits.front.max); - upperPitch = -degToRad(pitchLimits.front.min); - } else { - // inside transition - const transitionProgress = (yawAbs - range / 2) / transition; - const maxInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.front.max + - transitionProgress * pitchLimits.max; - const minInterpolatedPitch = - (1 - transitionProgress) * pitchLimits.front.min + - transitionProgress * pitchLimits.min; - - lowerPitch = -degToRad(maxInterpolatedPitch); - upperPitch = -degToRad(minInterpolatedPitch); - } - } - } - - newPitch = clamp(newPitch, lowerPitch, upperPitch); - - return [newPitch, normalizeAngleRad(newYaw)] as const; -} diff --git a/packages/website/src/core/blitz/correctZYTuple.ts b/packages/website/src/core/blitz/correctZYTuple.ts deleted file mode 100644 index 572f3439d..000000000 --- a/packages/website/src/core/blitz/correctZYTuple.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Vector3 as BlitzkitVector3 } from '@blitzkit/core'; -import { Vector3 as THREEVector3 } from 'three'; -import { rotateDavaVector } from './rotateDavaVector'; - -export function correctZYTuple(tuple: BlitzkitVector3) { - return rotateDavaVector(new THREEVector3(tuple.x, tuple.y, -tuple.z)); -} diff --git a/packages/website/src/core/blitz/rotateDavaVector.ts b/packages/website/src/core/blitz/rotateDavaVector.ts deleted file mode 100644 index 14bd49613..000000000 --- a/packages/website/src/core/blitz/rotateDavaVector.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { I_HAT } from '@blitzkit/core'; -import { Vector3 } from 'three'; - -export function rotateDavaVector(vector: Vector3) { - return vector.applyAxisAngle(I_HAT, Math.PI / 2); -} diff --git a/packages/website/src/core/blitz/unrotateDavaVector.ts b/packages/website/src/core/blitz/unrotateDavaVector.ts deleted file mode 100644 index 509e0724b..000000000 --- a/packages/website/src/core/blitz/unrotateDavaVector.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { I_HAT } from '@blitzkit/core'; -import { Vector3 } from 'three'; - -export function unrotateDavaVector(vector: Vector3) { - return vector.applyAxisAngle(I_HAT, -Math.PI / 2); -} diff --git a/packages/website/src/core/blitzkit/branches.ts b/packages/website/src/core/blitzkit/branches.ts deleted file mode 100644 index 1861f11c8..000000000 --- a/packages/website/src/core/blitzkit/branches.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { STRINGS } from "@blitzkit/i18n"; -import locales from "@blitzkit/i18n/locales.json"; - -export function resolveBranchName(locale: string = locales.default) { - if (!(locale in STRINGS)) throw new Error(`Unsupported locale: ${locale}`); - - const strings = STRINGS[locale]; - const secret = assertSecret(import.meta.env.PUBLIC_BRANCH); - - if ( - assertSecret(import.meta.env.MODE) === "development" && - secret === "dev" - ) { - return strings.common.branches.local; - } else { - return (strings.common.branches as Record<string, string>)[secret] as - | string - | undefined; - } -} diff --git a/packages/website/src/core/blitzkit/curateMixer.ts b/packages/website/src/core/blitzkit/curateMixer.ts deleted file mode 100644 index 021c296a3..000000000 --- a/packages/website/src/core/blitzkit/curateMixer.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { awaitableModelDefinitions } from "../awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../awaitables/tankDefinitions"; - -const tankDefinitions = await awaitableTankDefinitions; -const modelDefinitions = await awaitableModelDefinitions; -const tanks = Object.values(tankDefinitions.tanks).filter((tank) => { - const tankModel = modelDefinitions.models[tank.id]; - const turret = tank.turrets.at(-1)!; - const turretModel = tankModel.turrets[turret.id]; - - return !turretModel.yaw; -}); - -export function curateMixer() { - const hull = tanks[Math.floor(Math.random() * tanks.length)]; - const turretTank = tanks[Math.floor(Math.random() * tanks.length)]; - const turret = - turretTank.turrets[Math.floor(Math.random() * turretTank.turrets.length)]; - const gunTank = tanks[Math.floor(Math.random() * tanks.length)]; - const gunTurret = - gunTank.turrets[Math.floor(Math.random() * gunTank.turrets.length)]; - const gun = gunTurret.guns[Math.floor(Math.random() * gunTurret.guns.length)]; - - return { - hull: hull, - turret: { tank: turretTank, turret: turret }, - gun: { - tank: gunTank, - turret: gunTurret, - gun: gun, - }, - }; -} diff --git a/packages/website/src/core/blitzkit/fakeLog.ts b/packages/website/src/core/blitzkit/fakeLog.ts deleted file mode 100644 index 1f40ef3e8..000000000 --- a/packages/website/src/core/blitzkit/fakeLog.ts +++ /dev/null @@ -1,17 +0,0 @@ -const GAMMA = 1.2; - -export function fakeLogSquishyHigh(x: number) { - return Math.sin((Math.PI / 2) * x) ** (1 / GAMMA); -} - -export function fakeLogSquishyHighInverse(y: number) { - return (2 / Math.PI) * Math.asin(y ** GAMMA); -} - -export function fakeLogSquishyLow(x: number) { - return x ** 2; -} - -export function fakeLogSquishyLowInverse(y: number) { - return y ** (1 / 2); -} diff --git a/packages/website/src/core/blitzkit/filterTank.ts b/packages/website/src/core/blitzkit/filterTank.ts deleted file mode 100644 index 02bea5262..000000000 --- a/packages/website/src/core/blitzkit/filterTank.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { TankDefinition } from "@blitzkit/core"; -import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; -import { times } from "lodash-es"; -import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions"; -import type { TankFilters } from "../../stores/tankFilters"; -import { awaitableGameDefinitions } from "../awaitables/gameDefinitions"; -import { awaitableProvisionDefinitions } from "../awaitables/provisionDefinitions"; - -const SHELLS = times(3, (index) => index); - -const consumableDefinitions = await awaitableConsumableDefinitions; -const provisionDefinitions = await awaitableProvisionDefinitions; -const gameDefinitions = await awaitableGameDefinitions; - -export async function filterTank( - filters: TankFilters, - tank: TankDefinition, - owned: number[] = [] -) { - return ( - ((filters.showOwned && owned.includes(tank.id)) || - (filters.showUnowned && !owned.includes(tank.id))) && - (filters.tiers.length === 0 || filters.tiers.includes(tank.tier)) && - (filters.nations.length === 0 || filters.nations.includes(tank.nation)) && - (filters.classes.length === 0 || filters.classes.includes(tank.class)) && - (filters.types.length === 0 || filters.types.includes(tank.type)) && - ((filters.showTesting && tank.testing) || - (filters.showNonTesting && !tank.testing)) && - (filters.gunType.length === 0 || - (filters.gunType.includes("regular") && - tank.turrets.some((turret) => - turret.guns.some((gun) => gun.gun_type!.$case === "regular") - )) || - (filters.gunType.includes("auto_loader") && - tank.turrets.some((turret) => - turret.guns.some((gun) => gun.gun_type!.$case === "auto_loader") - )) || - (filters.gunType.includes("auto_reloader") && - tank.turrets.some((turret) => - turret.guns.some((gun) => gun.gun_type!.$case === "auto_reloader") - ))) && - tank.turrets.some((turret) => - turret.guns.some((gun) => - SHELLS.every( - (index) => - filters.shells[index] === null || - gun.shells[index] === undefined || - gun.shells[index].type === filters.shells[index] - ) - ) - ) && - (filters.consumables.length === 0 || - filters.consumables.every((consumable) => - tank.turrets.some((turret) => - turret.guns.some((gun) => - checkConsumableProvisionInclusivity( - consumableDefinitions.consumables[consumable], - tank, - gun - ) - ) - ) - )) && - (filters.provisions.length === 0 || - filters.provisions.every((provision) => - tank.turrets.some((turret) => - turret.guns.some((gun) => - checkConsumableProvisionInclusivity( - provisionDefinitions.provisions[provision], - tank, - gun - ) - ) - ) - )) && - (filters.abilities.length === 0 || - Object.values(tank.roles).some((id) => - gameDefinitions.roles[id as unknown as number].consumables.some( - (consumable) => filters.abilities.includes(consumable) - ) - )) && - (filters.powers.length === 0 || - Object.values(tank.roles).some((id) => - gameDefinitions.roles[id as unknown as number].provisions.some( - (consumable) => filters.powers.includes(consumable) - ) - )) - ); -} diff --git a/packages/website/src/core/blitzkit/filterTanks.ts b/packages/website/src/core/blitzkit/filterTanks.ts deleted file mode 100644 index 2ae934349..000000000 --- a/packages/website/src/core/blitzkit/filterTanks.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { getTankStats, idToRegion, TankDefinition } from "@blitzkit/core"; -import { TankFilters } from "../../stores/tankFilters"; -import { filterTank } from "./filterTank"; - -export async function filterTanks( - filters: TankFilters, - tanks: TankDefinition[], - player?: number -) { - const filtered: TankDefinition[] = []; - let owned: number[] | undefined; - - if (player) { - const tankStats = await getTankStats(idToRegion(player), player, { - fields: "tank_id", - }); - - owned = tankStats?.map(({ tank_id }) => tank_id); - } - - for (const tank of tanks) { - if (await filterTank(filters, tank, owned)) filtered.push(tank); - } - - return filtered; -} diff --git a/packages/website/src/core/blitzkit/generatePlaylist.ts b/packages/website/src/core/blitzkit/generatePlaylist.ts deleted file mode 100644 index ccd8a13c7..000000000 --- a/packages/website/src/core/blitzkit/generatePlaylist.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - fisherYates, - getTankStats, - idToRegion, - type IndividualTankStats, -} from "@blitzkit/core"; -import { App } from "../../stores/app"; -import { Playlist } from "../../stores/playlist"; -import { TankFilters } from "../../stores/tankFilters"; -import { awaitableTankDefinitions } from "../awaitables/tankDefinitions"; -import { filterTanks } from "./filterTanks"; - -const tankDefinitions = await awaitableTankDefinitions; -const tanks = Object.values(tankDefinitions.tanks); - -export async function generatePlaylist() { - const id = App.state.logins.wargaming?.id; - const filtered = await filterTanks(TankFilters.state, tanks, id); - const statsList = id ? await getTankStats(idToRegion(id), id) : undefined; - const stats: Record<number, IndividualTankStats> = {}; - - if (statsList) { - for (const tankStat of statsList) { - stats[tankStat.tank_id] = tankStat; - } - } - - fisherYates(filtered); - - Playlist.mutate((draft) => { - draft.list = filtered.map((tank) => { - const tankStats = stats[tank.id]; - const then = tankStats - ? { - battles: tankStats.all.battles, - wins: tankStats.all.wins, - last: tankStats.last_battle_time, - } - : undefined; - - return { id: tank.id, then, now: then }; - }); - }); -} diff --git a/packages/website/src/core/blitzkit/generateTierListParams.ts b/packages/website/src/core/blitzkit/generateTierListParams.ts deleted file mode 100644 index 7a870e4bc..000000000 --- a/packages/website/src/core/blitzkit/generateTierListParams.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { TierList } from '../../stores/tierList'; - -export function generateTierListParams(rows: TierList['rows']) { - const params = new URLSearchParams(); - - rows.forEach((row, index) => { - params.set(`row-${index}`, row.tanks.join(',')); - }); - - return params; -} diff --git a/packages/website/src/core/blitzkit/hasEquipment.ts b/packages/website/src/core/blitzkit/hasEquipment.ts deleted file mode 100644 index 54cbfbcd0..000000000 --- a/packages/website/src/core/blitzkit/hasEquipment.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { EquipmentMatrix } from '../../stores/duel'; -import { awaitableEquipmentDefinitions } from '../awaitables/equipmentDefinitions'; - -const equipmentDefinitions = await awaitableEquipmentDefinitions; - -export function hasEquipment( - id: number, - equipmentPreset: string, - equipmentMatrix: EquipmentMatrix, -) { - const preset = equipmentDefinitions.presets[equipmentPreset]; - return preset.slots.some((slot, index) => { - const row = Math.floor(index / 3); - const column = index % 3; - const choice = equipmentMatrix[row][column]; - - if (choice === 0) return false; - - const equipped = slot[choice === -1 ? 'left' : 'right']; - - return equipped === id; - }); -} diff --git a/packages/website/src/core/blitzkit/isHalloween.ts b/packages/website/src/core/blitzkit/isHalloween.ts deleted file mode 100644 index d08592f25..000000000 --- a/packages/website/src/core/blitzkit/isHalloween.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function isHalloween() { - const data = new Date(); - return false; - return data.getMonth() === 9 && data.getDate() === 31; -} diff --git a/packages/website/src/core/blitzkit/jsxTree.tsx b/packages/website/src/core/blitzkit/jsxTree.tsx deleted file mode 100644 index cac9d9385..000000000 --- a/packages/website/src/core/blitzkit/jsxTree.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type { ComponentProps, ReactNode } from "react"; -import { Mesh, MeshStandardMaterial, Object3D } from "three"; - -export function jsxTree( - node: Object3D, - mergers?: { - mesh?: ( - mesh: Mesh, - props: ComponentProps<"mesh">, - key: string, - ) => ReactNode; - group?: ( - object3d: Object3D, - props: ComponentProps<"group">, - key: string, - ) => ReactNode; - }, -): ReactNode { - if (node instanceof Mesh) { - const props = { - geometry: node.geometry, - material: node.material, - position: node.position, - rotation: node.rotation, - scale: node.scale, - }; - - if (props.material instanceof MeshStandardMaterial) { - props.material.depthWrite = true; - } - - return mergers?.mesh ? ( - mergers.mesh(node, props, node.uuid) - ) : ( - <mesh key={node.uuid} {...props} /> - ); - } else if (node instanceof Object3D) { - const props = { - position: node.position, - rotation: node.rotation, - scale: node.scale, - children: node.children.map((child) => jsxTree(child, mergers)), - }; - - return mergers?.group ? ( - mergers.group(node, props, node.uuid) - ) : ( - <group key={node.uuid} {...props} /> - ); - } - - return null; -} diff --git a/packages/website/src/core/blitzkit/modelTransform.ts b/packages/website/src/core/blitzkit/modelTransform.ts deleted file mode 100644 index 0bbc370cf..000000000 --- a/packages/website/src/core/blitzkit/modelTransform.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Quicklime } from 'quicklime'; - -export interface ModelTransformEventData { - yaw: number; - pitch: number; -} - -export const modelTransformEvent = new Quicklime<ModelTransformEventData>({ - pitch: 0, - yaw: 0, -}); diff --git a/packages/website/src/core/blitzkit/nameToArmorId.ts b/packages/website/src/core/blitzkit/nameToArmorId.ts deleted file mode 100644 index 6ebb6bb15..000000000 --- a/packages/website/src/core/blitzkit/nameToArmorId.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function nameToArmorId(name: string) { - const match = name.match(/.+_armor_(-?\d+)/); - const group1 = match![1]; - - return parseInt(group1); -} diff --git a/packages/website/src/core/blitzkit/pose.ts b/packages/website/src/core/blitzkit/pose.ts deleted file mode 100644 index f05156844..000000000 --- a/packages/website/src/core/blitzkit/pose.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EventManager } from '@blitzkit/core'; - -export enum Pose { - HullDown, - FaceHug, - Default, -} - -export const poseEvent = new EventManager<Pose>(); diff --git a/packages/website/src/core/blitzkit/resolveArmorIndex.ts b/packages/website/src/core/blitzkit/resolveArmorIndex.ts deleted file mode 100644 index 816d15fe5..000000000 --- a/packages/website/src/core/blitzkit/resolveArmorIndex.ts +++ /dev/null @@ -1,6 +0,0 @@ -const pattern = /\d+$/; - -export function resolveArmorIndex(name: string) { - const match = name.match(pattern)?.[0]; - return match ? parseInt(match) : undefined; -} diff --git a/packages/website/src/core/blitzkit/resolveReload.ts b/packages/website/src/core/blitzkit/resolveReload.ts deleted file mode 100644 index dd5fd0b00..000000000 --- a/packages/website/src/core/blitzkit/resolveReload.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { GunDefinition } from '@blitzkit/core'; - -export function resolveReload(gun: GunDefinition) { - if (gun.gun_type!.$case === 'regular') { - return gun.gun_type!.value.reload; - } else if (gun.gun_type!.$case === 'auto_loader') { - return ( - gun.gun_type!.value.clip_reload + - (gun.gun_type!.value.shell_count - 1) * gun.gun_type!.value.intra_clip - ); - } else { - return ( - gun.gun_type!.value.shell_reloads.reduce((a, b) => a + b, 0) + - (gun.gun_type!.value.shell_count - 1) * gun.gun_type!.value.intra_clip - ); - } -} diff --git a/packages/website/src/core/blitzkit/resolveThickness.ts b/packages/website/src/core/blitzkit/resolveThickness.ts deleted file mode 100644 index 032f5aaf1..000000000 --- a/packages/website/src/core/blitzkit/resolveThickness.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Armor } from '@blitzkit/core'; - -export function resolveArmor(armor: Armor, index: number) { - const spaced = armor.spaced?.includes(index) ?? false; - const thickness = index === -1 ? 0 : (armor.thickness[index] ?? 0); - - return { spaced, thickness }; -} diff --git a/packages/website/src/core/blitzkit/tankCharacteristics.ts b/packages/website/src/core/blitzkit/tankCharacteristics.ts deleted file mode 100644 index 2f75f8930..000000000 --- a/packages/website/src/core/blitzkit/tankCharacteristics.ts +++ /dev/null @@ -1,672 +0,0 @@ -import { - CrewType, - degressiveStat, - isExplosive, - normalizeBoundingBox, - progressiveStat, - resolveDpm, - resolvePenetrationCoefficient, - ShellType, - sum, - TankClass, - unionBoundingBox, - type EngineDefinition, - type EquipmentDefinitions, - type GunDefinition, - type ModelDefinition, - type ProvisionDefinitions, - type ShellDefinition, - type TankDefinition, - type TrackDefinition, - type TurretDefinition, -} from "@blitzkit/core"; -import { coefficient } from "@blitzkit/core/src/blitzkit/coefficient"; -import type { EquipmentMatrix } from "../../stores/duel"; -import { defaultEqualizer } from "./tankToDuelMember"; - -export type TankCharacteristics = ReturnType<typeof tankCharacteristics>; -export type TankCharacteristicsKey = keyof TankCharacteristics; - -const EQUALIZER_HEALTH_EPSILON = 50; - -export function tankCharacteristics( - { - tank, - turret, - gun, - shell, - consumables, - equipmentMatrix, - provisions, - engine, - crewSkills, - camouflage, - track, - stockEngine, - stockTrack, - stockTurret, - stockGun, - applyReactiveArmor, - applyDynamicArmor, - applySpallLiner, - assaultDistance, - equalize, - }: { - tank: TankDefinition; - turret: TurretDefinition; - gun: GunDefinition; - shell: ShellDefinition; - track: TrackDefinition; - engine: EngineDefinition; - stockEngine: EngineDefinition; - stockTrack: TrackDefinition; - stockTurret: TurretDefinition; - stockGun: GunDefinition; - consumables: number[]; - equipmentMatrix: EquipmentMatrix; - provisions: number[]; - crewSkills: Record<string, number>; - camouflage: boolean; - applyReactiveArmor: boolean; - applyDynamicArmor: boolean; - applySpallLiner: boolean; - assaultDistance: number; - equalize: boolean; - }, - { - tankModelDefinition, - equipmentDefinitions, - provisionDefinitions, - }: { - tankModelDefinition: ModelDefinition; - equipmentDefinitions: EquipmentDefinitions; - provisionDefinitions: ProvisionDefinitions; - }, -) { - const preset = equipmentDefinitions.presets[tank.equipment_preset]; - const turretModelDefinition = tankModelDefinition.turrets[turret.id]; - const gunModelDefinition = turretModelDefinition.guns[gun.id]; - const equalizer = (equalize ? tank.equalizer : undefined) ?? defaultEqualizer; - - function equipment(id: number) { - return preset.slots.some((slot, index) => { - const choice = equipmentMatrix[Math.floor(index / 3)][index % 3]; - if (choice === 0) return false; - const equipped = slot[choice === -1 ? "left" : "right"]; - return equipped === id; - }); - } - function consumable(id: number) { - return consumables.includes(id); - } - function provision(id: number) { - return provisions.includes(id); - } - - const hasGunRammer = equipment(100); - const hasImprovedVentilation = equipment(102); - const hasCalibratedShells = equipment(103); - const hasEnhancedGunLayingDrive = equipment(104); - const hasSupercharger = equipment(107); - const hasVerticalStabilizer = equipment(105); - const hasRefinedGun = equipment(106); - const hasImprovedAssembly = equipment(111); - const hasImprovedOptics = equipment(114); - const hasImprovedControl = equipment(116); - const hasEngineAccelerator = equipment(117); - const hasCamouflageNet = equipment(115); - const hasImprovedVerticalStabilizer = equipment(122); - const hasDownImprovedVerticalStabilizer = equipment(124); - const hasImprovedSuspension = equipment(123); - - const hasTungsten = consumable(45); - const hasAdrenaline = consumable(18); - const hasShellReloadBoost = consumable(29); - const hasEnginePowerBoost = consumable(14); - const hasImprovedEnginePowerBoost = consumable(27); - const hasReticleCalibration = consumable(28); - const hasReducedEnginePowerBoost = consumable(80); - - const hasStandardFuel = provision(18); - const hasImprovedFuel = provision(19); - const hasProtectiveKit = provision(22); - const hasSandbagArmor = provision(28); - const hasEnhancedSandbagArmor = provision(29); - const hasImprovedGunPowder = provision(46); - const hasGearOil = provision(44); - const hasImprovedGearOil = provision(45); - - const gunDefaultPitch = - tankModelDefinition.initial_turret_rotation?.pitch ?? 0; - const provisionCrewBonus = - provisions.reduce( - (total, provision) => - provisionDefinitions.provisions[provision].crew - ? total + provisionDefinitions.provisions[provision].crew! / 100 - : total, - 0, - ) + (hasImprovedVentilation ? 0.08 : 0); - const commanderMastery = 1 + provisionCrewBonus; - const loaderMastery = - commanderMastery * - (tank.crew.some(({ type }) => type === CrewType.CREW_TYPE_LOADER) - ? 1.1 - : 1.05); - const gunnerMastery = - commanderMastery * - (tank.crew.some(({ type }) => type === CrewType.CREW_TYPE_GUNNER) - ? 1.1 - : 1.05); - const driverMastery = - commanderMastery * - (tank.crew.some(({ type }) => type === CrewType.CREW_TYPE_DRIVER) - ? 1.1 - : 1.05); - const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]); - const burstShells = gun.burst?.count ?? 1; - const burstInterShell = gun.burst?.interval; - const armorDamageCoefficient = - coefficient([hasTungsten, 0.15]) * - coefficient( - [applyReactiveArmor && shell.type !== ShellType.SHELL_TYPE_HE, -0.27], - [applyDynamicArmor, -0.1], - [applySpallLiner && shell.type === ShellType.SHELL_TYPE_HE, -0.2], - ); - const assaultDamageCoefficient = - gun.assault_ranges && gun.assault_ranges.types.includes(shell.type) - ? (gun.assault_ranges.ranges.find( - ({ distance }) => distance >= assaultDistance, - )?.factor ?? 0) - : 1; - const moduleDamageCoefficient = coefficient([hasTungsten, 0.15]); - const reloadCoefficient = - (coefficient([hasGunRammer, -0.05]) * - coefficient([true, degressiveStat(loaderMastery)])) / - coefficient([hasAdrenaline && gun.gun_type!.$case === "regular", 0.17]); - const penetrationCoefficient = resolvePenetrationCoefficient( - hasCalibratedShells, - equalize, - shell.type, - equalizer, - ); - const healthCoefficient = - coefficient( - [hasSandbagArmor, 0.03], - [hasEnhancedSandbagArmor, 0.06], - [hasImprovedAssembly, 0.04], - ) * equalizer.health; - const shellVelocityCoefficient = coefficient( - [hasSupercharger, 0.35], - [hasImprovedGunPowder, 0.3], - ); - const aimTimeCoefficient = - coefficient( - [hasEnhancedGunLayingDrive, -0.15], - [hasReticleCalibration, -0.3], - ) * coefficient([true, degressiveStat(gunnerMastery)]); - const dispersionStillCoefficient = - coefficient([hasRefinedGun, -0.15], [hasReticleCalibration, -0.3]) * - coefficient([true, degressiveStat(gunnerMastery)]); - const dispersionDamagedCoefficient = coefficient([ - hasVerticalStabilizer, - -0.12, - ]); - const dispersionHullTraverseCoefficient = coefficient( - [hasVerticalStabilizer, -0.12], - [true, -crewSkills.smooth_turn / 100], - ); - const dispersionAfterShotCoefficient = coefficient( - [hasVerticalStabilizer, -0.12], - [true, -crewSkills.soft_recoil / 100], - ); - const dispersionMovingCoefficient = coefficient( - [hasVerticalStabilizer, -0.12], - [true, -crewSkills.smooth_driving / 100], - ); - const dispersionTurretTraverseCoefficient = coefficient( - [hasVerticalStabilizer, -0.12], - [true, -crewSkills.smooth_turret * (2 / 100)], - ); - const enginePowerCoefficient = coefficient( - [ - hasEngineAccelerator, - tank.class === TankClass.TANK_CLASS_HEAVY || - tank.class === TankClass.TANK_CLASS_TANK_DESTROYER - ? 0.05 - : 0.04, - ], - [hasReducedEnginePowerBoost, 0.1], - [hasEnginePowerBoost, 0.2], - [hasImprovedEnginePowerBoost, 0.35], - [hasStandardFuel, 0.03], - [hasImprovedFuel, 0.1], - [hasGearOil, 0.03], - [hasImprovedGearOil, 0.06], - ); - const turretTraverseCoefficient = - coefficient([hasStandardFuel, 0.03], [hasImprovedFuel, 0.1]) * - coefficient([true, progressiveStat(gunnerMastery)]); - const hullTraverseCoefficient = - coefficient( - [hasImprovedControl, 0.11], - [hasImprovedEnginePowerBoost, 0.01], - [true, crewSkills.virtuoso / 100], - ) * coefficient([true, progressiveStat(driverMastery)]); - - const resistanceHardCoefficient = - coefficient([hasImprovedSuspension, -0.2]) * - coefficient([true, degressiveStat(driverMastery)]); - const resistanceMediumCoefficient = - coefficient([hasImprovedSuspension, -0.15]) * - coefficient([true, degressiveStat(driverMastery)]); - const resistanceSoftCoefficient = - coefficient([hasImprovedSuspension, -0.3]) * - coefficient([true, degressiveStat(driverMastery)]); - - const viewRangeCoefficient = - coefficient([ - hasImprovedOptics, - tank.class === TankClass.TANK_CLASS_HEAVY - ? 0.06 - : tank.class === TankClass.TANK_CLASS_MEDIUM - ? 0.08 - : tank.class === TankClass.TANK_CLASS_LIGHT - ? 0.11 - : 0.04, - ]) * coefficient([true, progressiveStat(commanderMastery)]); - const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]); - - const speedForwardsSum = sum( - [hasImprovedEnginePowerBoost, 4], - [hasGearOil, 2], - [hasImprovedGearOil, 4], - ); - const speedBackwardsSum = sum( - [hasImprovedEnginePowerBoost, 8], - [hasGearOil, 2], - [hasImprovedGearOil, 4], - ); - const camouflageCoefficientMoving = sum( - [ - true, - coefficient([ - camouflage, - tank.class === TankClass.TANK_CLASS_TANK_DESTROYER - ? 0.04 - : tank.class === TankClass.TANK_CLASS_HEAVY - ? 0.03 - : 0.02, - ]), - ], - [ - hasCamouflageNet, - tank.class === TankClass.TANK_CLASS_HEAVY - ? 0.02 - : tank.class === TankClass.TANK_CLASS_MEDIUM - ? 0.04 - : tank.class === TankClass.TANK_CLASS_LIGHT - ? 0.07 - : 0.1, - ], - ); - const camouflageCoefficientStill = sum( - [ - true, - coefficient( - [ - camouflage, - tank.class === TankClass.TANK_CLASS_TANK_DESTROYER - ? 0.04 - : tank.class === TankClass.TANK_CLASS_HEAVY - ? 0.03 - : 0.02, - ], - [true, crewSkills.camouflage * (3 / 100)], - ), - ], - [ - hasCamouflageNet, - tank.class === TankClass.TANK_CLASS_HEAVY - ? 0.04 - : tank.class === TankClass.TANK_CLASS_MEDIUM - ? 0.08 - : tank.class === TankClass.TANK_CLASS_LIGHT - ? 0.14 - : 0.2, - ], - ); - - const size = normalizeBoundingBox( - unionBoundingBox( - tankModelDefinition.bounding_box!, - turretModelDefinition.bounding_box!, - ), - ); - const weightKg = - tank.weight + engine.weight + track.weight + turret.weight + gun.weight; - const weightTons = weightKg / 1000; - const stockWeight = - tank.weight + - stockEngine.weight + - stockTrack.weight + - stockTurret.weight + - stockGun.weight; - const resolvedEnginePower = engine.power * enginePowerCoefficient; - const damageCoefficientWithoutAssault = - armorDamageCoefficient * equalizer.damage; - const damageCoefficient = - damageCoefficientWithoutAssault * assaultDamageCoefficient; - const dpm = resolveDpm( - gun, - shell, - damageCoefficient, - reloadCoefficient, - intraClipCoefficient, - ); - const intraClip = - gun.gun_type?.$case === "regular" - ? undefined - : gun.gun_type!.value.intra_clip * intraClipCoefficient; - const mostOptimalShellIndex = - gun.gun_type!.$case === "auto_reloader" - ? gun.gun_type!.value.shell_reloads.reduce<null | { - index: number; - reload: number; - }>((current, reloadRaw, index) => { - const reload = - reloadRaw * reloadCoefficient + (index > 0 ? intraClip! : 0); - - if (current === null || reload < current.reload) { - return { index, reload }; - } - return current; - }, null)!.index - : undefined; - const damageWithoutAssault = - shell.armor_damage * damageCoefficientWithoutAssault; - const damage = shell.armor_damage * damageCoefficient; - const dpmEffective = - gun.gun_type!.$case === "auto_reloader" - ? gun.gun_type!.value.shell_reloads[0] > - gun.gun_type!.value.shell_reloads[1] + gun.gun_type!.value.intra_clip - ? ((armorDamageCoefficient * shell.armor_damage) / - (gun.gun_type!.value.shell_reloads.at(-1)! * reloadCoefficient + - gun.gun_type!.value.intra_clip * intraClipCoefficient)) * - (60 - - (gun.gun_type!.value.shell_reloads.slice(0, -1).length - 1) * - gun.gun_type!.value.intra_clip * - intraClipCoefficient) + - armorDamageCoefficient * - shell.armor_damage * - gun.gun_type!.value.shell_reloads.slice(0, -1).length - : ((armorDamageCoefficient * shell.armor_damage) / - (gun.gun_type!.value.shell_reloads[0] * reloadCoefficient + - gun.gun_type!.value.intra_clip * intraClipCoefficient)) * - (60 - - (gun.gun_type!.value.shell_reloads.slice(1).length - 1) * - gun.gun_type!.value.intra_clip * - intraClipCoefficient) + - armorDamageCoefficient * - shell.armor_damage * - gun.gun_type!.value.shell_reloads.slice(1).length - : undefined; - const shells = - gun.gun_type!.$case === "regular" ? 1 : gun.gun_type!.value.shell_count; - const shellReloads = - gun.gun_type!.$case === "auto_reloader" - ? gun.gun_type!.value.shell_reloads.map( - (reload) => reload * reloadCoefficient, - ) - : undefined; - const shellReload = - gun.gun_type!.$case === "auto_reloader" - ? undefined - : (gun.gun_type!.$case === "regular" - ? gun.gun_type!.value.reload - : gun.gun_type!.value.clip_reload) * reloadCoefficient; - const caliber = shell.caliber; - const penetration = shell.penetration!.near * penetrationCoefficient; - const clipDamage = - gun.gun_type!.$case === "regular" - ? undefined - : gun.gun_type!.value.shell_count * damage; - const moduleDamage = shell.module_damage * moduleDamageCoefficient; - const explosionRadius = shell.explosion_radius; - const shellVelocity = shell.velocity * shellVelocityCoefficient; - const aimTime = gun.aim_time * aimTimeCoefficient; - const dispersion = gun.dispersion_base * dispersionStillCoefficient; - const dispersionMoving = track.dispersion_move * dispersionMovingCoefficient; - const dispersionHullTraversing = - track.dispersion_traverse * dispersionHullTraverseCoefficient; - const dispersionTurretTraversing = - gun.dispersion_traverse * dispersionTurretTraverseCoefficient; - const dispersionShooting = - gun.dispersion_shot * dispersionAfterShotCoefficient; - const dispersionGunDamaged = - gun.dispersion_damaged * dispersionDamagedCoefficient; - const gunDepression = - gunModelDefinition.pitch!.max + - gunDefaultPitch + - (hasImprovedVerticalStabilizer ? 3 : 0) + - (hasDownImprovedVerticalStabilizer ? 3 : 0); - const gunElevation = - -gunModelDefinition.pitch!.min - - gunDefaultPitch + - (hasImprovedVerticalStabilizer ? 4 : 0); - const gunFrontalDepression = gunModelDefinition.pitch!.front?.max; - const gunFrontalElevation = gunModelDefinition.pitch!.front - ? -gunModelDefinition.pitch!.front.min - : undefined; - const gunRearDepression = gunModelDefinition.pitch!.back?.max; - const gunRearElevation = gunModelDefinition.pitch!.back - ? -gunModelDefinition.pitch!.back.min - : undefined; - const azimuthLeft = turretModelDefinition.yaw - ? -turretModelDefinition.yaw.min - : undefined; - const azimuthRight = turretModelDefinition.yaw?.max; - const speedForwards = tank.speed_forwards + speedForwardsSum; - const speedBackwards = tank.speed_backwards + speedBackwardsSum; - const enginePower = resolvedEnginePower; - const hardTerrainCoefficientRaw = - track.resistance_hard * resistanceHardCoefficient; - const hardTerrainCoefficient = 100 / hardTerrainCoefficientRaw; - const mediumTerrainCoefficientRaw = - track.resistance_medium * resistanceMediumCoefficient; - const mediumTerrainCoefficient = 100 / mediumTerrainCoefficientRaw; - const softTerrainCoefficientRaw = - track.resistance_soft * resistanceSoftCoefficient; - const softTerrainCoefficient = 100 / softTerrainCoefficientRaw; - const powerToWeightRatioHardTerrain = - resolvedEnginePower / weightTons / hardTerrainCoefficientRaw; - const powerToWeightRatioMediumTerrain = - resolvedEnginePower / weightTons / mediumTerrainCoefficientRaw; - const powerToWeightRatioSoftTerrain = - resolvedEnginePower / weightTons / softTerrainCoefficientRaw; - const weight = weightTons; - const turretTraverseSpeed = turret.traverse_speed * turretTraverseCoefficient; - const gunTraverseSpeed = gun.rotation_speed * turretTraverseCoefficient; - /** - * resistance_hard is used in all cases as this is the coefficient between - * the absolute best coefficient and the current - */ - const hullTraverseHardTerrain = - (resolvedEnginePower / stockEngine.power) * - track.traverse_speed * - hullTraverseCoefficient * - (track.resistance_hard / hardTerrainCoefficientRaw) * - (stockWeight / weightKg); - const hullTraverseMediumTerrain = - (resolvedEnginePower / stockEngine.power) * - track.traverse_speed * - hullTraverseCoefficient * - (track.resistance_hard / mediumTerrainCoefficientRaw) * - (stockWeight / weightKg); - const hullTraverseSoftTerrain = - (resolvedEnginePower / stockEngine.power) * - track.traverse_speed * - hullTraverseCoefficient * - (track.resistance_hard / softTerrainCoefficientRaw) * - (stockWeight / weightKg); - let health = (tank.health + turret.health) * healthCoefficient; - - if (equalize) { - health = - EQUALIZER_HEALTH_EPSILON * Math.round(health / EQUALIZER_HEALTH_EPSILON); - } - - const fireChance = engine.fire_chance * fireChanceCoefficient; - const viewRange = turret.view_range * viewRangeCoefficient; - const camouflageStill = tank.camouflage_still * camouflageCoefficientStill; - const camouflageMoving = - tank.class === TankClass.TANK_CLASS_LIGHT - ? tank.camouflage_still * camouflageCoefficientStill - : tank.camouflage_moving * camouflageCoefficientMoving; - const camouflageShootingStill = - tank.camouflage_still * gun.camouflage_loss * camouflageCoefficientStill; - const camouflageShootingMoving = - (tank.class === TankClass.TANK_CLASS_LIGHT - ? tank.camouflage_still * camouflageCoefficientStill - : tank.camouflage_moving * camouflageCoefficientMoving) * - gun.camouflage_loss; - const camouflageCaughtOnFire = - tank.camouflage_on_fire * - tank.camouflage_still * - camouflageCoefficientStill; - const width = size.z; - const height = size.x; - const length = size.y; - const volume = width * height * length; - const shellNormalization = shell.normalization ?? 0; - const shellRicochet = isExplosive(shell.type) ? undefined : shell.ricochet; - const crewCount = tank.crew.length; - const shellRange = shell.range; - const shellCapacity = gun.shell_capacity; - const gunType = gun.gun_type!.$case; - const penetrationAt250m = shell.penetration!.far * penetrationCoefficient; - - return { - crewCount, - shellNormalization, - shellRicochet, - dpm, - dpmEffective, - shells, - mostOptimalShellIndex, - shellReloads, - shellReload, - intraClip, - caliber, - penetration, - damage, - damageWithoutAssault, - clipDamage, - moduleDamage, - explosionRadius, - shellVelocity, - aimTime, - dispersion, - dispersionMoving, - dispersionHullTraversing, - dispersionTurretTraversing, - dispersionShooting, - dispersionGunDamaged, - gunDepression, - gunElevation, - gunFrontalDepression, - gunFrontalElevation, - gunRearDepression, - gunRearElevation, - azimuthLeft, - azimuthRight, - speedForwards, - speedBackwards, - hardTerrainCoefficient, - hardTerrainCoefficientRaw, - mediumTerrainCoefficient, - mediumTerrainCoefficientRaw, - softTerrainCoefficient, - softTerrainCoefficientRaw, - enginePower, - powerToWeightRatioHardTerrain, - powerToWeightRatioMediumTerrain, - powerToWeightRatioSoftTerrain, - weight, - turretTraverseSpeed, - gunTraverseSpeed, // TODO: display this - hullTraverseHardTerrain, - hullTraverseMediumTerrain, - hullTraverseSoftTerrain, - health, - fireChance, - viewRange, - camouflageStill, - camouflageMoving, - camouflageShootingStill, - camouflageShootingMoving, - camouflageCaughtOnFire, - width, - height, - length, - volume, - shellRange, - shellCapacity, - gunType, - penetrationAt250m, - burstShells, - burstInterShell, - }; -} - -export enum TankCharacteristicsCondition { - Regular, - AutoLoader, - AutoReloader, -} - -export const characteristicsOrder: { - name: string; - items: TankCharacteristicsKey[]; -}[] = [ - { - name: "Firepower", - items: [ - "gunType", - "dpm", - "shells", - "shellReload", - "shellReloads", - "intraClip", - "penetration", - "penetrationAt250m", - "damage", - "clipDamage", - "moduleDamage", - "caliber", - "shellNormalization", - "shellRicochet", - "shellVelocity", - "shellRange", - "shellCapacity", - "aimTime", - "dispersion", - "dispersionMoving", - "dispersionHullTraversing", - "dispersionTurretTraversing", - "dispersionShooting", - "dispersionGunDamaged", - "gunDepression", - "gunElevation", - "gunFrontalDepression", - "gunFrontalElevation", - "gunRearDepression", - "gunRearElevation", - "azimuthLeft", - "azimuthRight", - ], - }, - { - name: "Crew", - items: ["crewCount"], - }, -]; diff --git a/packages/website/src/core/blitzkit/tankToCompareMember.ts b/packages/website/src/core/blitzkit/tankToCompareMember.ts deleted file mode 100644 index 2930293d3..000000000 --- a/packages/website/src/core/blitzkit/tankToCompareMember.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ProvisionDefinitions, TankDefinition } from '@blitzkit/core'; -import { uniqueId } from 'lodash-es'; -import type { CompareMember } from '../../stores/compareEphemeral'; -import { tankToDuelMember } from './tankToDuelMember'; - -export function tankToCompareMember( - tank: TankDefinition, - provisionDefinitions: ProvisionDefinitions, -) { - return { - ...tankToDuelMember(tank, provisionDefinitions), - key: uniqueId(), - } satisfies CompareMember; -} diff --git a/packages/website/src/core/blitzkit/tankToDuelMember.ts b/packages/website/src/core/blitzkit/tankToDuelMember.ts deleted file mode 100644 index 34f0aec50..000000000 --- a/packages/website/src/core/blitzkit/tankToDuelMember.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - createDefaultProvisions, - Equalizer, - type ProvisionDefinitions, - type TankDefinition, -} from "@blitzkit/core"; -import type { DuelMember } from "../../stores/duel"; -import { genericDefaultEquipmentMatrix } from "../../stores/duel/constants"; - -export function tankToDuelMember( - tank: TankDefinition, - provisionDefinitions: ProvisionDefinitions, -) { - const turret = tank.turrets.at(-1)!; - const gun = turret.guns.at(-1)!; - - return { - camouflage: true, - consumables: [], - equipmentMatrix: genericDefaultEquipmentMatrix, - cooldownBooster: 0, - provisions: createDefaultProvisions(tank, gun, provisionDefinitions), - engine: tank.engines.at(-1)!, - gun, - shell: tank.turrets.at(-1)!.guns.at(-1)!.shells[0], - tank, - track: tank.tracks.at(-1)!, - turret, - assaultDistance: (gun.assault_ranges?.ranges[0].distance ?? 0) / 2, - } satisfies DuelMember; -} - -export const defaultEqualizer: Equalizer = { - armor: 1, - damage: 1, - health: 1, - module_health: 1, - penetration: 1, -}; diff --git a/packages/website/src/core/blitzkit/updatePlaylist.ts b/packages/website/src/core/blitzkit/updatePlaylist.ts deleted file mode 100644 index d49435766..000000000 --- a/packages/website/src/core/blitzkit/updatePlaylist.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - getTankStats, - idToRegion, - type IndividualTankStats, -} from "@blitzkit/core"; -import { App } from "../../stores/app"; -import { Playlist } from "../../stores/playlist"; - -export async function updatePlaylist() { - const id = App.state.logins.wargaming?.id; - const statsList = id ? await getTankStats(idToRegion(id), id) : undefined; - const stats: Record<number, IndividualTankStats> = {}; - - if (statsList) { - for (const tankStat of statsList) { - stats[tankStat.tank_id] = tankStat; - } - } - - Playlist.mutate((draft) => { - draft.list?.map((item) => { - const tankStats = stats[item.id]; - item.now = tankStats - ? { - battles: tankStats.all.battles, - wins: tankStats.all.wins, - last: tankStats.last_battle_time, - } - : undefined; - }); - }); -} diff --git a/packages/website/src/core/controlsEnabled.ts b/packages/website/src/core/controlsEnabled.ts deleted file mode 100644 index 46ca66e73..000000000 --- a/packages/website/src/core/controlsEnabled.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Quicklime } from "quicklime"; - -export const controlsEnabledEvent = new Quicklime(true); diff --git a/packages/website/src/core/i18n/getBestLocale.ts b/packages/website/src/core/i18n/getBestLocale.ts deleted file mode 100644 index 67a5edf85..000000000 --- a/packages/website/src/core/i18n/getBestLocale.ts +++ /dev/null @@ -1,15 +0,0 @@ -import locales from "@blitzkit/i18n/locales.json"; - -export function getBestLocale() { - const desiredLocale = - localStorage.getItem("preferred-locale") ?? - navigator.language.split("-")[0]; - const isSupported = locales.supported.some( - ({ locale }) => locale === desiredLocale, - ); - const isDefault = desiredLocale === locales.default; - - if (!isSupported || isDefault) return locales.default; - - return desiredLocale; -} diff --git a/packages/website/src/core/i18n/getStrings.ts b/packages/website/src/core/i18n/getStrings.ts deleted file mode 100644 index 85f30f873..000000000 --- a/packages/website/src/core/i18n/getStrings.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { STRINGS } from "@blitzkit/i18n"; -import locales from "@blitzkit/i18n/locales.json"; - -export function getStrings(locale: string = locales.default) { - if (locale in STRINGS) return STRINGS[locale]; - throw new Error(`Unsupported locale: ${locale}`); -} diff --git a/packages/website/src/core/radix/utils.ts b/packages/website/src/core/radix/utils.ts deleted file mode 100644 index a93749655..000000000 --- a/packages/website/src/core/radix/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { RadixColorCompound, RadixRadius } from '../../stores/embedState'; - -export function toWidthVar(state: number) { - return `${state}px`; -} -export function toRadiusVar(state: RadixRadius) { - return `var(--radius-${state})`; -} -export function toColorVar(state: RadixColorCompound) { - return `var(--${state.base}-${state.variant})`; -} diff --git a/packages/website/src/core/radix/var.ts b/packages/website/src/core/radix/var.ts deleted file mode 100644 index 29b986ba8..000000000 --- a/packages/website/src/core/radix/var.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as ColorsRaw from '@radix-ui/colors'; - -type ExcludeSuffix<T> = - T extends `${string}${'A' | `${'P3' | 'Dark'}${'' | 'A'}`}` ? never : T; -export type RadixColor = ExcludeSuffix<keyof typeof ColorsRaw>; - -export type VarName = - | `color-${'background' | `panel-${'solid' | 'translucent'}` | 'surface' | 'overlay'}` - | `radius-${1 | 2 | 3 | 4 | 5 | 6 | 'full'}` - | `space-${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}` - | `shadow-${1 | 2 | 3 | 4 | 5 | 6}` - | `${RadixColor | 'accent' | 'gray' | 'black' | 'white'}-${ - | `${'' | 'a'}${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12}` - | 'surface' - | 'indicator' - | 'track' - | 'contrast'}`; - -export function Var(name: VarName) { - return `var(--${name})`; -} diff --git a/packages/website/src/core/three/discardClippingPlane.ts b/packages/website/src/core/three/discardClippingPlane.ts deleted file mode 100644 index a6a62dd4a..000000000 --- a/packages/website/src/core/three/discardClippingPlane.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Material, Mesh, Object3D, Vector3 } from 'three'; - -export function discardClippingPlane(object: Object3D, point: Vector3) { - if ( - object instanceof Mesh && - object.material instanceof Material && - object.material.clippingPlanes !== null - ) { - return object.material.clippingPlanes.every( - (plane) => plane.distanceToPoint(point) < 0, - ); - } - - return false; -} diff --git a/packages/website/src/env.d.ts b/packages/website/src/env.d.ts deleted file mode 100644 index e16c13c69..000000000 --- a/packages/website/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// <reference path="../.astro/types.d.ts" /> diff --git a/packages/website/src/hooks/useAppliedEquipments.ts b/packages/website/src/hooks/useAppliedEquipments.ts deleted file mode 100644 index 15e9e1c86..000000000 --- a/packages/website/src/hooks/useAppliedEquipments.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EquipmentPreset } from '@blitzkit/core'; -import type { EquipmentMatrix } from '../stores/duel'; - -export function useAppliedEquipments( - matrix: EquipmentMatrix, - preset: EquipmentPreset, -) { - const flatMatrix = matrix.flat(); - return preset.slots.map( - (options, index) => options[flatMatrix[index] == 1 ? 'right' : 'left'], - ); -} diff --git a/packages/website/src/hooks/useArmor.ts b/packages/website/src/hooks/useArmor.ts deleted file mode 100644 index d697bd0ba..000000000 --- a/packages/website/src/hooks/useArmor.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { useLoader } from "@react-three/fiber"; -import { GLTFLoader } from "three-stdlib"; -import { useDispose } from "./useDispose"; - -export function useArmor(id: number) { - const path = asset(`3d/tanks/armor/${id}.glb`); - const gltf = useLoader(GLTFLoader, path); - - useDispose(gltf, path); - - return { - gltf, - hasDynamicArmor: Object.values(gltf.nodes).some( - (node) => - node.name.includes("state_00") || node.name.includes("state_01"), - ), - }; -} diff --git a/packages/website/src/hooks/useAveragesExclusionRatio.ts b/packages/website/src/hooks/useAveragesExclusionRatio.ts deleted file mode 100644 index e5fd5ba84..000000000 --- a/packages/website/src/hooks/useAveragesExclusionRatio.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { awaitableAverageDefinitions } from "../core/awaitables/averageDefinitions"; -import { awaitableDiscoveredIdsDefinitions } from "../core/awaitables/discoveredIdsDefinitions"; - -const [discoveredIdsDefinitions, averageDefinitions] = await Promise.all([ - awaitableDiscoveredIdsDefinitions, - awaitableAverageDefinitions, -]); - -export function useAveragesExclusionRatio() { - return discoveredIdsDefinitions.count / averageDefinitions.samples!.total; -} diff --git a/packages/website/src/hooks/useDelta.ts b/packages/website/src/hooks/useDelta.ts deleted file mode 100644 index 5dc606824..000000000 --- a/packages/website/src/hooks/useDelta.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useEffect, useRef } from 'react'; - -export function useDelta(value: number) { - const lastValue = useRef(value); - - useEffect(() => { - lastValue.current = value; - }); - - return value - lastValue.current; -} diff --git a/packages/website/src/hooks/useDispose.ts b/packages/website/src/hooks/useDispose.ts deleted file mode 100644 index 2ff4e3685..000000000 --- a/packages/website/src/hooks/useDispose.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useLoader, type ObjectMap } from "@react-three/fiber"; -import { useEffect } from "react"; -import { Cache, Group, Mesh, MeshStandardMaterial, Object3D } from "three"; -import { GLTFLoader, type GLTF } from "three-stdlib"; - -export function useDispose(gltf: GLTF & ObjectMap, path: string) { - useEffect(() => { - return () => { - gltf.scene.traverse((object) => { - if (object instanceof Mesh) { - if (object.material instanceof MeshStandardMaterial) { - const maps = [ - object.material.map, - object.material.aoMap, - object.material.envMap, - object.material.bumpMap, - object.material.alphaMap, - object.material.lightMap, - object.material.normalMap, - object.material.emissiveMap, - object.material.roughnessMap, - object.material.metalnessMap, - object.material.displacementMap, - ]; - - for (const map of maps) { - if (!map) continue; - - map.source.data.close(); - map.dispose(); - } - - object.material.dispose(); - } else { - throw new TypeError( - `Unexpected material type: ${object.material.type}`, - ); - } - } else if (object instanceof Group || object instanceof Object3D) { - // traverse will get children - } else { - throw new TypeError(`Unexpected object type: ${object}`); - } - }); - - useLoader.clear(GLTFLoader, path); - Cache.remove(path); - }; - }, [gltf, path]); -} diff --git a/packages/website/src/hooks/useEccentricity.ts b/packages/website/src/hooks/useEccentricity.ts deleted file mode 100644 index 4565e6099..000000000 --- a/packages/website/src/hooks/useEccentricity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { TankDefinition } from '@blitzkit/core'; -import { sumBy } from 'lodash-es'; -import { useMemo } from 'react'; -import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions'; -import { Duel } from '../stores/duel'; - -export enum UseEccentricityMode { - Tier, - Class, -} - -const tankDefinitions = await awaitableTankDefinitions; - -export function useEccentricity(mode: UseEccentricityMode) { - const tank = Duel.use((state) => state.protagonist.tank); - const similarTanks = useMemo( - () => - Object.values(tankDefinitions).filter( - (tankB) => - tank.tier === tankB.tier && - (mode === UseEccentricityMode.Class - ? tank.class === tankB.class - : true), - ), - [tank.id], - ); - - function eccentricity(property: (tank: TankDefinition) => number) { - const average = sumBy(similarTanks, property) / similarTanks.length; - return property(tank) / average; - } - - return eccentricity; -} diff --git a/packages/website/src/hooks/useEquipment.ts b/packages/website/src/hooks/useEquipment.ts deleted file mode 100644 index 4c24f9828..000000000 --- a/packages/website/src/hooks/useEquipment.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useMemo } from 'react'; -import { awaitableEquipmentDefinitions } from '../core/awaitables/equipmentDefinitions'; -import { Duel } from '../stores/duel'; - -const equipmentDefinitions = await awaitableEquipmentDefinitions; - -export function useEquipment(id: number, antagonist = false) { - const member = Duel.use( - (state) => state[antagonist ? 'antagonist' : 'protagonist']!, - ); - const preset = equipmentDefinitions.presets[member.tank.equipment_preset]; - const equipmentMatrix = Duel.use( - (state) => - state[antagonist ? 'antagonist' : 'protagonist']!.equipmentMatrix, - ); - const value = useMemo(() => { - return preset.slots.some((slot, index) => { - const row = Math.floor(index / 3); - const column = index % 3; - const choice = equipmentMatrix[row][column]; - - if (choice === 0) return false; - - const equipped = slot[choice === -1 ? 'left' : 'right']; - - return equipped === id; - }); - }, [equipmentMatrix, member.tank]); - - return value; -} diff --git a/packages/website/src/hooks/useEquipmentPreset.ts b/packages/website/src/hooks/useEquipmentPreset.ts deleted file mode 100644 index c69f75da5..000000000 --- a/packages/website/src/hooks/useEquipmentPreset.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { awaitableEquipmentDefinitions } from "../core/awaitables/equipmentDefinitions"; - -const equipmentDefinitions = await awaitableEquipmentDefinitions; - -export function useEquipmentPreset(preset: string) { - return equipmentDefinitions.presets[preset]; -} diff --git a/packages/website/src/hooks/useFullScreen.ts b/packages/website/src/hooks/useFullScreen.ts deleted file mode 100644 index 32dae2e9c..000000000 --- a/packages/website/src/hooks/useFullScreen.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect, useState } from 'react'; - -export function useFullScreen() { - const [isFullScreen, setIsFullScreen] = useState( - typeof document !== 'undefined' && document.fullscreenElement !== null, - ); - - useEffect(() => { - function handleFullScreenChange() { - setIsFullScreen(document.fullscreenElement !== null); - } - - document.addEventListener('fullscreenchange', handleFullScreenChange); - - return () => { - document.removeEventListener('fullscreenchange', handleFullScreenChange); - }; - }, []); - - return isFullScreen; -} diff --git a/packages/website/src/hooks/useFullscreenAvailability.ts b/packages/website/src/hooks/useFullscreenAvailability.ts deleted file mode 100644 index d31d80728..000000000 --- a/packages/website/src/hooks/useFullscreenAvailability.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useEffect, useState } from 'react'; - -export function useFullscreenAvailability(assumption = false) { - const [available, setAvailable] = useState(assumption); - - useEffect(() => { - setAvailable(document.fullscreenEnabled); - }, []); - - return available; -} diff --git a/packages/website/src/hooks/useIntersection.ts b/packages/website/src/hooks/useIntersection.ts deleted file mode 100644 index 0cca4d739..000000000 --- a/packages/website/src/hooks/useIntersection.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { type RefObject, useEffect, useRef } from "react"; - -interface UseIntersectionOptions extends IntersectionObserverInit { - disabled: boolean; -} - -export function useIntersection( - callback: () => void, - ref: RefObject<HTMLElement>, - options?: UseIntersectionOptions -) { - const observer = useRef<IntersectionObserver>(null!); - - useEffect(() => { - if (options?.disabled || !ref.current) return; - - const rect = ref.current.getBoundingClientRect(); - const visible = !(rect.bottom < 0 || rect.top - window.innerHeight >= 0); - - if (visible) callback(); - - observer.current = new IntersectionObserver(callback, options); - observer.current.observe(ref.current); - - return () => observer.current!.disconnect(); - }, []); - - return observer; -} diff --git a/packages/website/src/hooks/useLocale.tsx b/packages/website/src/hooks/useLocale.tsx deleted file mode 100644 index 86e274cb8..000000000 --- a/packages/website/src/hooks/useLocale.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { unwrapper } from "@blitzkit/i18n"; -import locales from "@blitzkit/i18n/locales.json"; -import { createContext, useContext, type ReactNode } from "react"; -import { getStrings } from "../core/i18n/getStrings"; - -const LocaleContext = createContext<{ - locale: string; - localeRaw: string | undefined; -} | null>(null); - -interface LocaleProviderProps extends LocaleAcceptorProps { - children: ReactNode; -} - -export function LocaleProvider({ locale, children }: LocaleProviderProps) { - return ( - <LocaleContext.Provider - value={{ locale: locale ?? locales.default, localeRaw: locale }} - > - {children} - </LocaleContext.Provider> - ); -} - -export function useLocale() { - const context = useContext(LocaleContext); - - if (!context) { - throw new Error("useLocale must be used within a LocaleProvider"); - } - - const strings = getStrings(context.locale); - const unwrap = unwrapper(context.locale); - - return { locale: context.locale, strings, unwrap }; -} - -export interface LocaleAcceptorProps { - locale: string | undefined; -} diff --git a/packages/website/src/hooks/useModel.ts b/packages/website/src/hooks/useModel.ts deleted file mode 100644 index c8d532bd5..000000000 --- a/packages/website/src/hooks/useModel.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { asset } from "@blitzkit/core"; -import { useLoader } from "@react-three/fiber"; -import { Mesh, MeshStandardMaterial } from "three"; -import { GLTFLoader } from "three-stdlib"; -import { useDispose } from "./useDispose"; - -export function useModel(id: number) { - const path = asset(`3d/tanks/models/${id}.glb`); - const gltf = useLoader(GLTFLoader, path); - - useDispose(gltf, path); - - return { - gltf, - hasPbr: Object.values(gltf.nodes).some( - (node) => - node instanceof Mesh && - node.material instanceof MeshStandardMaterial && - node.material.roughnessMap !== null, - ), - }; -} diff --git a/packages/website/src/hooks/useOnScreen.ts b/packages/website/src/hooks/useOnScreen.ts deleted file mode 100644 index b4d75afae..000000000 --- a/packages/website/src/hooks/useOnScreen.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { type RefObject, useEffect, useRef, useState } from "react"; - -/** - * Thanks GuCire! - * - * https://stackoverflow.com/a/65008608/12294756 - */ -export function useOnScreen(ref: RefObject<HTMLElement>) { - const [isIntersecting, setIntersecting] = useState(false); - const observer = useRef<IntersectionObserver>(null!); - - useEffect(() => { - observer.current = new IntersectionObserver(([entry]) => - setIntersecting(entry.isIntersecting) - ); - }, [ref]); - - useEffect(() => { - if (!ref.current) return; - - observer.current?.observe(ref.current); - return () => observer.current?.disconnect(); - }, [ref]); - - return isIntersecting; -} diff --git a/packages/website/src/hooks/useProvision.ts b/packages/website/src/hooks/useProvision.ts deleted file mode 100644 index 054664f7e..000000000 --- a/packages/website/src/hooks/useProvision.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Duel } from '../stores/duel'; - -export function useProvision(id: number) { - const provisions = Duel.use((state) => state.protagonist.provisions); - return provisions.includes(id); -} diff --git a/packages/website/src/hooks/useStableProgress.ts b/packages/website/src/hooks/useStableProgress.ts deleted file mode 100644 index 332ded1d0..000000000 --- a/packages/website/src/hooks/useStableProgress.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useProgress } from "@react-three/drei"; -import { useRef } from "react"; - -export function useStableProgress() { - const { loaded, total } = useProgress(); - const progress = useRef(0); - - progress.current = Math.max( - progress.current, - total === 0 ? 0 : (loaded / total) * 100 - ); - - return progress.current; -} diff --git a/packages/website/src/hooks/useTankModelDefinition.ts b/packages/website/src/hooks/useTankModelDefinition.ts deleted file mode 100644 index 242713e4b..000000000 --- a/packages/website/src/hooks/useTankModelDefinition.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Tankopedia } from "../stores/tankopedia"; - -export function useTankModelDefinition() { - return Tankopedia.use((state) => state.model); -} diff --git a/packages/website/src/hooks/useTankTransform.ts b/packages/website/src/hooks/useTankTransform.ts deleted file mode 100644 index be26cb6be..000000000 --- a/packages/website/src/hooks/useTankTransform.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { - I_HAT, - J_HAT, - K_HAT, - TrackDefinition, - TurretDefinition, -} from "@blitzkit/core"; -import { invalidate } from "@react-three/fiber"; -import type { QuicklimeEvent } from "quicklime"; -import { type RefObject, useEffect } from "react"; -import { Euler, Group, Vector3 } from "three"; -import { degToRad } from "three/src/math/MathUtils.js"; -import { correctZYTuple } from "../core/blitz/correctZYTuple"; -import { - modelTransformEvent, - type ModelTransformEventData, -} from "../core/blitzkit/modelTransform"; -import { useTankModelDefinition } from "./useTankModelDefinition"; - -export function useTankTransform( - track: TrackDefinition, - turret: TurretDefinition, - turretContainer: RefObject<Group>, - gunContainer: RefObject<Group>, -) { - const tankModelDefinition = useTankModelDefinition(); - - useEffect(() => { - const trackModelDefinition = tankModelDefinition.tracks[track.id]; - const turretModelDefinition = tankModelDefinition.turrets[turret.id]; - const hullOrigin = correctZYTuple(trackModelDefinition.origin!); - const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!); - const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!); - const turretPosition = new Vector3(); - const turretRotation = new Euler(); - const gunPosition = new Vector3(); - const gunRotation = new Euler(); - - function handleModelTransform( - event: QuicklimeEvent<ModelTransformEventData>, - ) { - handleModelTransformInner(event.data); - } - - function handleModelTransformInner(data?: ModelTransformEventData) { - const yaw = data?.yaw ?? modelTransformEvent.last!.yaw; - const pitch = data?.pitch ?? modelTransformEvent.last!.pitch; - - gunPosition - .set(0, 0, 0) - .sub(hullOrigin) - .sub(turretOrigin) - .sub(gunOrigin) - .applyAxisAngle(I_HAT, pitch) - .add(gunOrigin) - .add(turretOrigin) - .add(hullOrigin); - gunRotation.set(pitch, 0, 0); - gunContainer.current?.position.copy(gunPosition); - gunContainer.current?.rotation.copy(gunRotation); - - if (yaw === undefined) return; - - turretPosition - .set(0, 0, 0) - .sub(hullOrigin) - .sub(turretOrigin) - .applyAxisAngle(new Vector3(0, 0, 1), yaw); - turretRotation.set(0, 0, yaw); - - if (tankModelDefinition.initial_turret_rotation) { - const initialPitch = -degToRad( - tankModelDefinition.initial_turret_rotation.pitch, - ); - const initialYaw = -degToRad( - tankModelDefinition.initial_turret_rotation.yaw, - ); - const initialRoll = -degToRad( - tankModelDefinition.initial_turret_rotation.roll, - ); - - turretPosition - .applyAxisAngle(I_HAT, initialPitch) - .applyAxisAngle(J_HAT, initialRoll) - .applyAxisAngle(K_HAT, initialYaw); - turretRotation.x += initialPitch; - turretRotation.y += initialRoll; - turretRotation.z += initialYaw; - } - - turretPosition.add(turretOrigin).add(hullOrigin); - turretContainer.current?.position.copy(turretPosition); - turretContainer.current?.rotation.copy(turretRotation); - - invalidate(); - } - - handleModelTransformInner(); - - modelTransformEvent.on(handleModelTransform); - - return () => { - modelTransformEvent.off(handleModelTransform); - }; - }, [track, turret]); -} diff --git a/packages/website/src/hooks/useTankVotes.ts b/packages/website/src/hooks/useTankVotes.ts deleted file mode 100644 index 58a8b435e..000000000 --- a/packages/website/src/hooks/useTankVotes.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - unwrapBlitzkitResponse, - type BlitzkitResponse, - type TankVotes, -} from '@blitzkit/core'; -import { useEffect, useState } from 'react'; -import { App } from '../stores/app'; - -const cache: Record<string, Promise<TankVotes> | TankVotes> = {}; - -export function useTankVotes(id: number) { - const [votes, setVotes] = useState<TankVotes | null>(null); - const wargaming = App.use((state) => state.logins.wargaming); - - useEffect(() => { - if (!wargaming) return; - - if (id in cache) { - Promise.all([cache[id]]).then(([votes]) => setVotes(votes)); - } else { - const cacheable = fetch( - `/api/tank-voting/${id}/average${ - wargaming ? `?player=${wargaming.id}&token=${wargaming.token}` : '' - }`, - ) - .then( - (response) => response.json() as Promise<BlitzkitResponse<TankVotes>>, - ) - .then(unwrapBlitzkitResponse); - cacheable.then(setVotes); - cache[id] = cacheable; - } - }, [id, wargaming]); - - return votes; -} diff --git a/packages/website/src/layouts/BaseLayout.astro b/packages/website/src/layouts/BaseLayout.astro deleted file mode 100644 index 4b7f7dc09..000000000 --- a/packages/website/src/layouts/BaseLayout.astro +++ /dev/null @@ -1,51 +0,0 @@ ---- -import strings from "@blitzkit/i18n/strings/en.json"; -import "@fontsource/inter"; -import { Flex } from "@radix-ui/themes"; -import Analytics from "../components/Analytics.astro"; -import { BlitzKitTheme } from "../components/BlitzKitTheme"; -import { Checks } from "../components/Checks"; -import EasterEggs from "../components/EasterEggs.astro"; -import Footer from "../components/Footer.astro"; -import LocaleRedirector from "../components/LocaleRedirector.astro"; -import Meta from "../components/Meta.astro"; -import Navbar from "../components/Navbar.astro"; -import Servicing from "../components/Servicing.astro"; - -export interface Props { - noFooter?: boolean; - noNavbar?: boolean; - integrated?: boolean; - locale: string; -} - -const servicing = import.meta.env.SERVICING; -const { noFooter, noNavbar, integrated, locale } = Astro.props; ---- - -<html lang={locale}> - <head> - <meta property="og:site_name" content={strings.common.name} /> - - <Meta /> - <Analytics /> - <LocaleRedirector /> - <EasterEggs /> - - <slot name="head" /> - </head> - - <body class={noNavbar ? "no-navbar" : ""}> - <BlitzKitTheme> - <Flex direction="column" minHeight="100vh"> - <Navbar {locale} {integrated} /> - - <Checks {locale} client:only="react" /> - - {servicing ? <Servicing /> : <slot />} - - {!noFooter && <Footer {locale} />} - </Flex> - </BlitzKitTheme> - </body> -</html> diff --git a/packages/website/src/layouts/EmbedLayout.astro b/packages/website/src/layouts/EmbedLayout.astro deleted file mode 100644 index e77d3bbb1..000000000 --- a/packages/website/src/layouts/EmbedLayout.astro +++ /dev/null @@ -1,13 +0,0 @@ ---- -import './base.css'; ---- - -<html> - <head> - <meta charset="UTF-8" /> - </head> - - <body> - <slot /> - </body> -</html> diff --git a/packages/website/src/layouts/LegacyRedirect.astro b/packages/website/src/layouts/LegacyRedirect.astro deleted file mode 100644 index d9fb5f038..000000000 --- a/packages/website/src/layouts/LegacyRedirect.astro +++ /dev/null @@ -1,41 +0,0 @@ ---- -import locales from "@blitzkit/i18n/locales.json"; -import { getStrings } from "../core/i18n/getStrings"; - -export interface Props { - path: string; - locale: string; -} - -const { path, locale } = Astro.props; - -const strings = getStrings(locale); -const resolvedPath = `/${locale === locales.default ? "" : `${locale}/`}${path}`; ---- - -<html lang={locale}> - <head> - <meta charset="utf-8" /> - <link rel="canonical" href={`${Astro.url.origin}${resolvedPath}`} /> - <meta http-equiv="refresh" content={`0; url=${resolvedPath}`} /> - </head> - - <body> - <div - style={{ - display: "flex", - flexDirection: "column", - alignItems: "center", - justifyContent: "center", - width: "100vw", - height: "100vh", - backgroundColor: "black", - color: "white", - fontFamily: "sans-serif", - }} - > - <h2>{strings.website.redirect.title}</h2> - <span>{strings.website.redirect.body}</span> - </div> - </body> -</html> diff --git a/packages/website/src/layouts/ToolLayout.astro b/packages/website/src/layouts/ToolLayout.astro deleted file mode 100644 index 2fe92d378..000000000 --- a/packages/website/src/layouts/ToolLayout.astro +++ /dev/null @@ -1,47 +0,0 @@ ---- -import { Box, Flex, Heading, Text } from "@radix-ui/themes"; -import ToolOpenGraph from "../components/ToolOpenGraph.astro"; -import type { Tool } from "../constants/tools"; -import { getStrings } from "../core/i18n/getStrings"; -import BaseLayout from "./BaseLayout.astro"; - -interface Props { - tool: Tool; - locale: string; - noNavbar?: boolean; - noFooter?: boolean; -} - -const { tool, locale, noNavbar, noFooter } = Astro.props; -const strings = getStrings(locale); -const toolStrings = - strings.website.tools[ - (tool.strings ?? tool.id) as keyof typeof strings.website.tools - ]; ---- - -<ToolOpenGraph {locale} {tool} /> - -<BaseLayout {locale} {noNavbar} {noFooter}> - <Box - style={{ - backgroundImage: `url(/assets/images/tools/${tool.id}.webp)`, - backgroundSize: "cover", - }} - > - <Flex - direction="column" - align="center" - py="6" - style={{ - backdropFilter: "blur(4rem)", - WebkitBackdropFilter: "blur(4rem)", - }} - > - <Heading size="7">{toolStrings.name}</Heading> - <Text>{toolStrings.description}</Text> - </Flex> - </Box> - - <slot /> -</BaseLayout> diff --git a/packages/website/src/layouts/base.css b/packages/website/src/layouts/base.css deleted file mode 100644 index 30210660b..000000000 --- a/packages/website/src/layouts/base.css +++ /dev/null @@ -1,9 +0,0 @@ -@import "@radix-ui/themes/styles.css"; - -.radix-themes { - --default-font-family: "Inter"; -} - -body { - margin: 0; -} diff --git a/packages/website/src/pages/404.astro b/packages/website/src/pages/404.astro deleted file mode 100644 index 05529036f..000000000 --- a/packages/website/src/pages/404.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import { DiscordLogoIcon, HomeIcon } from "@radix-ui/react-icons"; -import { Button, Flex, Heading, Link, Text } from "@radix-ui/themes"; -import { LinkI18n } from "../components/LinkI18n"; -import { PageWrapper } from "../components/PageWrapper"; -import BaseLayout from "../layouts/BaseLayout.astro"; - -const { locale } = Astro.props; ---- - -<BaseLayout {locale}> - <PageWrapper align="center" justify="center" gap="0"> - <Heading>404: page not found</Heading> - <Text color="gray">Where would you like to go?</Text> - - <Flex mt="4" gap="2"> - <LinkI18n {locale} href="/"> - <Button> - <HomeIcon /> Home - </Button> - </LinkI18n> - <Link href="https://discord.gg/nDt7AjGJQH"> - <Button variant="outline"> - <DiscordLogoIcon /> Discord - </Button> - </Link> - </Flex> - </PageWrapper> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/auth/[provider]/_index.tsx b/packages/website/src/pages/[...locale]/auth/[provider]/_index.tsx deleted file mode 100644 index c1bb1f1a7..000000000 --- a/packages/website/src/pages/[...locale]/auth/[provider]/_index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useEffect } from "react"; -import { App } from "../../../../stores/app"; - -export const AUTH_PROVIDERS = ["wargaming"] as const; - -export type AuthProvider = (typeof AUTH_PROVIDERS)[number]; - -export const AUTH_PROVIDER_NAMES: Record<AuthProvider, string> = { - wargaming: "Wargaming", -}; - -interface AuthorizeProps { - provider: AuthProvider; -} - -export function Authorize({ provider }: AuthorizeProps) { - useEffect(() => { - (async () => { - const searchParams = new URLSearchParams(window.location.search); - - switch (provider) { - case "wargaming": { - if ( - !searchParams.has("expires_at") || - !searchParams.has("access_token") || - !searchParams.has("account_id") - ) { - break; - } - - App.mutate((draft) => { - draft.logins.wargaming = { - expires: Number(searchParams.get("expires_at")) * 1000, - token: searchParams.get("access_token")!, - id: Number(searchParams.get("account_id"))!, - }; - }); - - break; - } - - default: - throw new Error(`Unknown provider: ${provider}`); - } - - window.location.href = searchParams.get("return") ?? "/"; - })(); - }, []); - - return null; -} diff --git a/packages/website/src/pages/[...locale]/auth/[provider]/index.astro b/packages/website/src/pages/[...locale]/auth/[provider]/index.astro deleted file mode 100644 index b38157251..000000000 --- a/packages/website/src/pages/[...locale]/auth/[provider]/index.astro +++ /dev/null @@ -1,38 +0,0 @@ ---- -import { Flex, Heading, Text } from "@radix-ui/themes"; -import type { GetStaticPaths } from "astro"; -import { PageWrapper } from "../../../../components/PageWrapper"; -import BaseLayout from "../../../../layouts/BaseLayout.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../../index.astro"; -import { AUTH_PROVIDER_NAMES, AUTH_PROVIDERS, Authorize } from "./_index"; - -export const getStaticPaths = (() => { - const parentPaths = parentGetStaticPaths(); - - return AUTH_PROVIDERS.map((provider) => - parentPaths.map((parent) => ({ params: { ...parent.params, provider } })) - ).flat(); -}) satisfies GetStaticPaths; - -const { provider } = Astro.params; -const { locale } = Astro.props; ---- - -<BaseLayout {locale}> - <PageWrapper> - <Flex - style={{ flex: 1 }} - align="center" - justify="center" - direction="column" - gap="2" - > - <Heading align="center" - >Authorizing you with {AUTH_PROVIDER_NAMES[provider]}...</Heading - > - <Text color="gray" align="center">You'll be on your way shortly.</Text> - </Flex> - </PageWrapper> - - <Authorize client:only="react" {provider} /> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/compare/_index.tsx b/packages/website/src/pages/[...locale]/compare/_index.tsx deleted file mode 100644 index b658aa085..000000000 --- a/packages/website/src/pages/[...locale]/compare/_index.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { createDefaultSkills } from "@blitzkit/core"; -import { Flex, Heading, Text } from "@radix-ui/themes"; -import { useEffect, useMemo, useState } from "react"; -import { CompareTable } from "../../../components/Compare/CompareTable"; -import { Controls } from "../../../components/Compare/Controls"; -import { DamageWarning } from "../../../components/DamageWarning"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { awaitableEquipmentDefinitions } from "../../../core/awaitables/equipmentDefinitions"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; -import { awaitableProvisionDefinitions } from "../../../core/awaitables/provisionDefinitions"; -import { awaitableSkillDefinitions } from "../../../core/awaitables/skillDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { tankCharacteristics } from "../../../core/blitzkit/tankCharacteristics"; -import { tankToCompareMember } from "../../../core/blitzkit/tankToCompareMember"; -import { - type LocaleAcceptorProps, - LocaleProvider, - useLocale, -} from "../../../hooks/useLocale"; -import { CompareEphemeral } from "../../../stores/compareEphemeral"; - -const [ - modelDefinitions, - equipmentDefinitions, - provisionDefinitions, - skillDefinitions, - tankDefinitions, -] = await Promise.all([ - awaitableModelDefinitions, - awaitableEquipmentDefinitions, - awaitableProvisionDefinitions, - awaitableSkillDefinitions, - awaitableTankDefinitions, -]); - -export function Page({ locale }: LocaleAcceptorProps) { - CompareEphemeral.useInitialization(createDefaultSkills(skillDefinitions)); - - return ( - <LocaleProvider locale={locale}> - <Content /> - </LocaleProvider> - ); -} - -function Content() { - const members = CompareEphemeral.use((state) => state.members); - const equalize = CompareEphemeral.use((state) => state.equalize); - const [addTankDialogOpen, setAddTankDialogOpen] = useState(false); - const crewSkills = CompareEphemeral.use((state) => state.crewSkills); - const { strings } = useLocale(); - const stats = useMemo<ReturnType<typeof tankCharacteristics>[]>( - () => - members.map((thisMember) => - tankCharacteristics( - { - ...thisMember, - crewSkills, - stockEngine: thisMember.tank.engines[0], - stockGun: thisMember.tank.turrets[0].guns[0], - stockTrack: thisMember.tank.tracks[0], - stockTurret: thisMember.tank.turrets[0], - applyReactiveArmor: members.some( - (member) => - member.key !== thisMember.key && - member.consumables.includes(33), - ), - applyDynamicArmor: members.some( - (member) => - member.key !== thisMember.key && - member.consumables.includes(73), - ), - applySpallLiner: members.some( - (member) => - member.key !== thisMember.key && - member.provisions.includes(101), - ), - assaultDistance: thisMember.assaultDistance, - equalize, - }, - { - equipmentDefinitions: equipmentDefinitions, - provisionDefinitions: provisionDefinitions, - tankModelDefinition: modelDefinitions.models[thisMember.tank.id], - }, - ), - ), - [members, crewSkills, equalize], - ); - - useEffect(() => { - const search = new URLSearchParams(window.location.search); - const tanksQuery = search.get("tanks"); - - if (tanksQuery === null) return; - - const tanks = tanksQuery.split(",").map(Number); - - CompareEphemeral.mutate((draft) => { - draft.members = []; - - tanks.forEach((id) => { - const tank = tankDefinitions.tanks[id]; - - if (tank === undefined) return; - - draft.members.push(tankToCompareMember(tank, provisionDefinitions)); - }); - }); - }, []); - - useEffect(() => { - const search = new URLSearchParams(window.location.search); - - search.set("tanks", members.map((member) => member.tank.id).join(",")); - - window.history.replaceState( - {}, - "", - `${window.location.pathname}?${search.toString()}`, - ); - }, [members]); - - return ( - <PageWrapper color="crimson" maxWidth="100%" px="0" pb="0"> - <Flex justify="center" gap="4" align="center" direction="column"> - <DamageWarning /> - <Controls - addTankDialogOpen={addTankDialogOpen} - onAddTankDialogOpenChange={setAddTankDialogOpen} - /> - </Flex> - - {members.length > 0 && ( - <Flex justify="center" flexGrow="1" position="relative"> - <CompareTable stats={stats} /> - </Flex> - )} - - {members.length === 0 && ( - <Flex - align="center" - justify="center" - direction="column" - style={{ flex: 1 }} - > - <Heading color="gray"> - {strings.website.tools.compare.no_tanks.title} - </Heading> - <Text color="gray"> - {strings.website.tools.compare.no_tanks.body} - </Text> - </Flex> - )} - </PageWrapper> - ); -} diff --git a/packages/website/src/pages/[...locale]/compare/index.astro b/packages/website/src/pages/[...locale]/compare/index.astro deleted file mode 100644 index fe6f043cb..000000000 --- a/packages/website/src/pages/[...locale]/compare/index.astro +++ /dev/null @@ -1,16 +0,0 @@ ---- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolOpenGraph {locale} tool={tools.compare} /> - -<BaseLayout {locale} noFooter> - <Page {locale} client:load /> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/docs/[directory]/[document]/_index.tsx b/packages/website/src/pages/[...locale]/docs/[directory]/[document]/_index.tsx deleted file mode 100644 index 5c1415000..000000000 --- a/packages/website/src/pages/[...locale]/docs/[directory]/[document]/_index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Markdown } from "../../../../../components/Markdown"; -import { PageWrapper } from "../../../../../components/PageWrapper"; - -interface PageProps { - content: string; -} - -export function Page({ content }: PageProps) { - return ( - <PageWrapper maxWidth="50rem"> - <Markdown>{content}</Markdown> - </PageWrapper> - ); -} diff --git a/packages/website/src/pages/[...locale]/docs/[directory]/[document]/index.astro b/packages/website/src/pages/[...locale]/docs/[directory]/[document]/index.astro deleted file mode 100644 index d3811142b..000000000 --- a/packages/website/src/pages/[...locale]/docs/[directory]/[document]/index.astro +++ /dev/null @@ -1,58 +0,0 @@ ---- -import { AstroSeo } from "@astrolib/seo"; -import type { GetStaticPaths } from "astro"; -import { readFile } from "fs/promises"; -import BaseLayout from "../../../../../layouts/BaseLayout.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../../../index.astro"; -import { Page } from "./_index"; - -export const getStaticPaths = (async () => { - const parentPaths = parentGetStaticPaths(); - const ROOT = "../../../../../../../../docs/"; - const files = import.meta.glob("../../../../../../../../docs/**/*.md", { - eager: true, - }); - - return ( - await Promise.all( - Object.keys(files).map(async (file) => { - const couple = file.replace(ROOT, "").split("/"); - couple[1] = couple[1].replace(".md", ""); - const content = await readFile( - `../../docs/${couple[0]}/${couple[1]}.md` - ).then((buffer) => buffer.toString()); - - return parentPaths.map((parent) => ({ - params: { - ...parent.params, - directory: couple[0], - document: couple[1], - }, - props: { ...parent.props, content }, - })); - }) - ) - ).flat(); -}) satisfies GetStaticPaths; - -const { content, locale } = Astro.props; - -const title = content.split("\n")[0].replace("# ", ""); ---- - -<AstroSeo - title={title} - description={title} - canonical={Astro.url.href} - openGraph={{ - url: Astro.url.href, - title, - description: title, - site_name: "BlitzKit", - }} - twitter={{ cardType: "summary_large_image" }} -/> - -<BaseLayout {locale}> - <Page content={content} /> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/docs/patches/index.astro b/packages/website/src/pages/[...locale]/docs/patches/index.astro deleted file mode 100644 index 5fd4da06b..000000000 --- a/packages/website/src/pages/[...locale]/docs/patches/index.astro +++ /dev/null @@ -1,67 +0,0 @@ ---- -import { AstroSeo } from "@astrolib/seo"; -import { MagicWandIcon, StarFilledIcon } from "@radix-ui/react-icons"; -import { Flex, Heading, Link } from "@radix-ui/themes"; -import { compareVersions } from "compare-versions"; -import { PageWrapper } from "../../../../components/PageWrapper"; -import { getStrings } from "../../../../core/i18n/getStrings"; -import BaseLayout from "../../../../layouts/BaseLayout.astro"; - -export { getStaticPaths } from "../../index.astro"; - -// this can't be used in template literal :( -const ROOT = "../../../../../../../docs/changelogs/"; - -const { locale } = Astro.props; -const strings = getStrings(locale); -const versions = Object.keys( - import.meta.glob("../../../../../../../docs/changelogs/*.md", { - eager: true, - }) -) - .map((file) => file.replace(ROOT, "").replace(".md", "")) - .sort(compareVersions) - .reverse(); ---- - -<AstroSeo - title={strings.website.docs.patches.name} - description={strings.website.docs.patches.seo_description} - canonical={Astro.url.href} - openGraph={{ - url: Astro.url.href, - title: strings.website.docs.patches.name, - description: strings.website.docs.patches.seo_description, - site_name: "BlitzKit", - }} - twitter={{ cardType: "summary_large_image" }} -/> - -<BaseLayout {locale}> - <PageWrapper align="center"> - <Heading mt="6" mb="4">BlitzKit patches</Heading> - - <Flex direction="column" gap="2" align="center"> - { - versions.map((version) => { - const split = version.split(".").map(Number); - const isMajor = split[2] === 0; - - return ( - <Link - size="5" - href={`/docs/changelogs/${version}`} - mb={isMajor ? "6" : "0"} - > - <Flex align="center" gap="2"> - {!isMajor && <MagicWandIcon />} - {isMajor && <StarFilledIcon />} - {version} - </Flex> - </Link> - ); - }) - } - </Flex> - </PageWrapper> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/_index.tsx b/packages/website/src/pages/[...locale]/embed/[embed]/_index.tsx deleted file mode 100644 index d1f6ca7c7..000000000 --- a/packages/website/src/pages/[...locale]/embed/[embed]/_index.tsx +++ /dev/null @@ -1,288 +0,0 @@ -import { - CodeIcon, - HeightIcon, - ImageIcon, - ResetIcon, - TimerIcon, - WidthIcon, -} from "@radix-ui/react-icons"; -import { Box, Button, Flex, Heading, ScrollArea, Text } from "@radix-ui/themes"; -import { useEffect, useRef, useState, type ReactNode } from "react"; -import { CopyButton } from "../../../../components/CopyButton"; -import { Boolean } from "../../../../components/Embeds/Boolean"; -import { Color } from "../../../../components/Embeds/Color"; -import { Enum } from "../../../../components/Embeds/Enum"; -import { GenerateURL } from "../../../../components/Embeds/GenerateURL"; -import { Import } from "../../../../components/Embeds/Import"; -import { PreviewWrapper } from "../../../../components/Embeds/PreviewWrapper"; -import { Radius } from "../../../../components/Embeds/Radius"; -import { RichText } from "../../../../components/Embeds/RichText"; -import { Size } from "../../../../components/Embeds/Size"; -import { SizeWithout0 } from "../../../../components/Embeds/SizeWithout0"; -import { Slider } from "../../../../components/Embeds/Slider"; -import { TextController } from "../../../../components/Embeds/TextController"; -import { PageWrapper } from "../../../../components/PageWrapper"; -import { - embedConfigurations, - extractEmbedConfigDefaults, -} from "../../../../constants/embeds"; -import { NAVBAR_HEIGHT } from "../../../../constants/navbar"; -import { - LocaleProvider, - useLocale, - type LocaleAcceptorProps, -} from "../../../../hooks/useLocale"; -import { EmbedState, type EmbedConfig } from "../../../../stores/embedState"; -import { EmbedItemType } from "../../../../stores/embedState/constants"; - -export interface EmbedPreviewControllerProps { - configKey: string; -} - -interface PageProps { - embed: keyof typeof embedConfigurations; -} - -export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) { - const config = embedConfigurations[embed]; - - EmbedState.useInitialization(extractEmbedConfigDefaults(config)); - - return ( - <LocaleProvider locale={locale}> - <Content embed={embed} /> - </LocaleProvider> - ); -} - -function Content({ embed }: PageProps) { - const config = embedConfigurations[embed] as EmbedConfig; - const [backgroundImage, setBackgroundImage] = useState( - "/assets/images/backgrounds/embed-default.webp" - ); - const fileInput = useRef<HTMLInputElement>(null!); - const { strings } = useLocale(); - - useEffect(() => { - fileInput.current = document.createElement("input"); - fileInput.current.type = "file"; - fileInput.current.style.display = "none"; - fileInput.current.accept = "image/*"; - fileInput.current.onchange = (event) => { - const file = (event.target as HTMLInputElement).files?.[0]; - if (!file) return; - setBackgroundImage(URL.createObjectURL(file)); - }; - }, []); - - return ( - <PageWrapper padding="0" maxWidth="104rem" color="red"> - <Flex> - <ScrollArea - scrollbars="vertical" - style={{ height: "100%", maxWidth: 320 }} - > - <Flex direction="column" gap="2" p="4"> - <Import /> - - <Heading> - {strings.website.tools.embed.configuration.export.title} - </Heading> - <Text size="2" color="gray" mb="2"> - {strings.website.tools.embed.configuration.export.description} - </Text> - - <Flex mb="6" gap="2" wrap="wrap"> - <GenerateURL embed={embed} /> - - <CopyButton - variant="outline" - copy={() => - "body { margin: 0; background-color: transparent; overflow: hidden; }" - } - > - <CodeIcon /> - {strings.website.tools.embed.configuration.export.css} - </CopyButton> - <CopyButton - variant="outline" - copy={() => `${EmbedState.state.width}`} - > - <WidthIcon /> - {strings.website.tools.embed.configuration.export.width} - </CopyButton> - <CopyButton - variant="outline" - copy={() => `${EmbedState.state.height}`} - > - <HeightIcon /> - {strings.website.tools.embed.configuration.export.height} - </CopyButton> - <CopyButton variant="outline" copy={() => "1"}> - <TimerIcon /> - {strings.website.tools.embed.configuration.export.framerate} - </CopyButton> - </Flex> - - <Flex justify="between" align="center"> - <Heading> - {strings.website.tools.embed.configuration.customize.title} - </Heading> - <Button - color="red" - variant="ghost" - onClick={() => { - EmbedState.set(EmbedState.initial); - }} - > - <ResetIcon />{" "} - {strings.website.tools.embed.configuration.customize.reset} - </Button> - </Flex> - <Text size="2" color="gray" mb="2"> - {strings.website.tools.embed.configuration.customize.description} - </Text> - - {Object.keys(config).map((configKey) => { - const item = config[configKey]; - let control: ReactNode; - let oneLiner = false; - - switch (item.type) { - case EmbedItemType.Boolean: { - control = <Boolean configKey={configKey} />; - oneLiner = true; - break; - } - - case EmbedItemType.Color: { - control = <Color configKey={configKey} />; - break; - } - - case EmbedItemType.Radius: { - control = <Radius configKey={configKey} />; - oneLiner = true; - break; - } - - case EmbedItemType.Size: { - control = <Size configKey={configKey} />; - oneLiner = true; - break; - } - - case EmbedItemType.SizeWithout0: { - control = <SizeWithout0 configKey={configKey} />; - oneLiner = true; - break; - } - - case EmbedItemType.Slider: { - control = <Slider configKey={configKey} config={item} />; - oneLiner = true; - break; - } - - case EmbedItemType.String: { - control = <TextController configKey={configKey} />; - break; - } - - case EmbedItemType.RichText: { - control = <RichText configKey={configKey} />; - break; - } - - case EmbedItemType.Enum: { - control = <Enum configKey={configKey} config={item} />; - oneLiner = true; - break; - } - } - - return ( - <Flex - key={configKey} - direction={oneLiner ? undefined : "column"} - gap={oneLiner ? "2" : "1"} - justify={oneLiner ? "between" : undefined} - mb={oneLiner ? "2" : "4"} - pb={item.pad ? "6" : undefined} - > - <Text> - { - ( - strings.website.tools.embed.types[embed] - .options as Record<string, string> - )[configKey] - } - </Text> - <Flex - gap="2" - align="center" - flexGrow="1" - justify={oneLiner ? "end" : undefined} - > - {control} - </Flex> - </Flex> - ); - })} - </Flex> - </ScrollArea> - - <Box - p="4" - flexGrow="1" - position="sticky" - top={`${NAVBAR_HEIGHT}px`} - height={`calc(100vh - ${NAVBAR_HEIGHT}px)`} - > - <Flex - direction="column" - height="100%" - style={{ - borderRadius: "var(--radius-3)", - overflow: "hidden", - boxShadow: "var(--shadow-3)", - }} - > - <Flex - justify="between" - align="center" - p="2" - pl="3" - style={{ - backgroundColor: "var(--color-panel-solid)", - }} - > - <Text color="gray"> - {strings.website.tools.embed.preview.title} - </Text> - - <Button - variant="outline" - color="gray" - onClick={() => fileInput.current?.click()} - > - <ImageIcon /> {strings.website.tools.embed.preview.upload} - </Button> - </Flex> - - <Box - flexGrow="1" - style={{ - background: `url(${backgroundImage}) center / cover no-repeat`, - }} - position="relative" - overflow="hidden" - > - <PreviewWrapper name={embed} /> - </Box> - </Flex> - </Box> - </Flex> - </PageWrapper> - ); -} diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/host/_index.tsx b/packages/website/src/pages/[...locale]/embed/[embed]/host/_index.tsx deleted file mode 100644 index 17691f545..000000000 --- a/packages/website/src/pages/[...locale]/embed/[embed]/host/_index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Box } from "@radix-ui/themes"; -import { useMemo } from "react"; -import { parse } from "urlon"; -import { BlitzKitTheme } from "../../../../../components/BlitzKitTheme"; -import { - embedConfigurations, - embedRenderers, - extractEmbedConfigDefaults, -} from "../../../../../constants/embeds"; -import { - LocaleProvider, - type LocaleAcceptorProps, -} from "../../../../../hooks/useLocale"; -import { - EmbedState, - type EmbedConfig, - type ExtractEmbedConfigTypes, -} from "../../../../../stores/embedState"; -import { useEmbedStateCurry } from "../../../../../stores/embedState/utilities"; - -interface PageProps { - embed: keyof typeof embedConfigurations; -} - -export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) { - const state = useMemo(() => { - const params = new URLSearchParams(window.location.search); - const stateRaw = params.get("state"); - const partialState = stateRaw === null ? {} : parse(stateRaw); - const state = { - ...extractEmbedConfigDefaults(embedConfigurations[embed]), - ...partialState, - } as ExtractEmbedConfigTypes<EmbedConfig>; - - return state; - }, [embed]); - - EmbedState.useInitialization(state); - - return ( - <LocaleProvider locale={locale}> - <Content embed={embed} /> - </LocaleProvider> - ); -} - -function Content({ embed }: PageProps) { - const Renderer = embedRenderers[embed]; - const { useEmbedState } = useEmbedStateCurry(); - const width = useEmbedState("width"); - const height = useEmbedState("height"); - - return ( - <BlitzKitTheme style={{ background: "transparent" }}> - <Box width={`${width}px`} height={`${height}px`} overflow="hidden"> - <Renderer /> - </Box> - </BlitzKitTheme> - ); -} diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/host/index.astro b/packages/website/src/pages/[...locale]/embed/[embed]/host/index.astro deleted file mode 100644 index 00fb8f464..000000000 --- a/packages/website/src/pages/[...locale]/embed/[embed]/host/index.astro +++ /dev/null @@ -1,13 +0,0 @@ ---- -import EmbedLayout from "../../../../../layouts/EmbedLayout.astro"; -import type { getStaticPaths } from "../index.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<EmbedLayout> - <Page {locale} client:only="react" embed={Astro.params.embed} /> -</EmbedLayout> diff --git a/packages/website/src/pages/[...locale]/embed/[embed]/index.astro b/packages/website/src/pages/[...locale]/embed/[embed]/index.astro deleted file mode 100644 index 85c9e5c26..000000000 --- a/packages/website/src/pages/[...locale]/embed/[embed]/index.astro +++ /dev/null @@ -1,65 +0,0 @@ ---- -import { AstroSeo } from "@astrolib/seo"; -import type { GetStaticPaths } from "astro"; -import { capitalize } from "lodash-es"; -import { embedConfigurations } from "../../../../constants/embeds"; -import BaseLayout from "../../../../layouts/BaseLayout.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; -import { Page } from "./_index"; - -export const getStaticPaths = (() => { - const parentPaths = parentGetStaticPaths(); - - return Object.keys(embedConfigurations) - .map((embed) => - parentPaths.map((parent) => ({ - params: { - ...parent.params, - embed: embed as keyof typeof embedConfigurations, - }, - })) - ) - .flat(); -}) satisfies GetStaticPaths; - -const name = capitalize(Astro.params.embed); -const title = `BlitzKit - ${name} embed`; -const description = `BlitzKit ${name} embeds for streaming World of Tanks Blitz with extreme customization`; -const { locale } = Astro.props; ---- - -<AstroSeo - title={title} - description={description} - canonical={Astro.url.href} - openGraph={{ - url: Astro.url.href, - title, - description, - site_name: "BlitzKit", - }} - twitter={{ cardType: "summary_large_image" }} -/> - -<style is:global> - @keyframes fade-out-border { - from { - outline: 1rem solid #ff0000ff; - } - to { - outline: 0.75rem solid #ff000000; - } - } - - .preview-container-animated { - animation: fade-out-border 0.5s linear; - } - - .preview-container-animated:hover { - outline: 1rem solid #ff000080; - } -</style> - -<BaseLayout {locale}> - <Page {locale} client:load embed={Astro.params.embed} /> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/embed/_index.tsx b/packages/website/src/pages/[...locale]/embed/_index.tsx deleted file mode 100644 index 73f504434..000000000 --- a/packages/website/src/pages/[...locale]/embed/_index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Box, Flex, Link, Text } from "@radix-ui/themes"; -import { PreviewWrapper } from "../../../components/Embeds/PreviewWrapper"; -import { LinkI18n } from "../../../components/LinkI18n"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { embedConfigurations } from "../../../constants/embeds"; -import { Var } from "../../../core/radix/var"; -import { - type LocaleAcceptorProps, - LocaleProvider, - useLocale, -} from "../../../hooks/useLocale"; - -export function Page({ locale }: LocaleAcceptorProps) { - return ( - <LocaleProvider locale={locale}> - <Content /> - </LocaleProvider> - ); -} - -function Content() { - const { locale, strings } = useLocale(); - - return ( - <PageWrapper color="red"> - <Text align="center" mt="3"> - {strings.website.tools.embed.coming_soon}{" "} - <Link - underline="always" - href="https://discord.gg/nDt7AjGJQH" - target="_blank" - > - {strings.website.tools.embed.suggest} - </Link> - </Text> - - <Flex justify="center"> - {Object.keys(embedConfigurations).map((embed) => ( - <LinkI18n - locale={locale} - href={`/embed/${embed}`} - color="gray" - highContrast - style={{ - width: "fit-content", - height: "fit-content", - }} - underline="hover" - > - <Flex - direction="column" - overflow="hidden" - style={{ - borderRadius: Var("radius-4"), - backgroundColor: Var("color-panel-solid"), - boxShadow: Var("shadow-3"), - }} - > - <Flex justify="center" p="2"> - <Text> - { - strings.website.tools.embed.types[ - embed as keyof typeof strings.website.tools.embed.types - ].name - } - </Text> - </Flex> - - <Box - width="20rem" - height="20rem" - overflow="hidden" - position="relative" - style={{ - backgroundImage: - "url(/assets/images/backgrounds/embed-default.webp)", - backgroundSize: "cover", - backgroundPosition: "center", - }} - > - <PreviewWrapper - naked - name={embed as keyof typeof embedConfigurations} - /> - </Box> - </Flex> - </LinkI18n> - ))} - </Flex> - </PageWrapper> - ); -} diff --git a/packages/website/src/pages/[...locale]/embed/index.astro b/packages/website/src/pages/[...locale]/embed/index.astro deleted file mode 100644 index 13ff94238..000000000 --- a/packages/website/src/pages/[...locale]/embed/index.astro +++ /dev/null @@ -1,13 +0,0 @@ ---- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolLayout {locale} tool={tools.embed}> - <Page {locale} /> -</ToolLayout> diff --git a/packages/website/src/pages/[...locale]/gallery/_index.tsx b/packages/website/src/pages/[...locale]/gallery/_index.tsx deleted file mode 100644 index e18e63bfc..000000000 --- a/packages/website/src/pages/[...locale]/gallery/_index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { GalleryList } from "../../../components/Gallery/List"; -import { GallerySearch } from "../../../components/Gallery/Search"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { - LocaleProvider, - type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; - -export function Page({ - locale, - ...props -}: LocaleAcceptorProps & MaybeSkeletonComponentProps) { - return ( - <LocaleProvider locale={locale}> - <Content {...props} /> - </LocaleProvider> - ); -} - -function Content({ skeleton }: MaybeSkeletonComponentProps) { - return ( - <PageWrapper maxWidth="80rem" color="gold"> - <GallerySearch skeleton={skeleton} /> - <GalleryList skeleton={skeleton} /> - </PageWrapper> - ); -} diff --git a/packages/website/src/pages/[...locale]/gallery/index.astro b/packages/website/src/pages/[...locale]/gallery/index.astro deleted file mode 100644 index b0ccfcfd1..000000000 --- a/packages/website/src/pages/[...locale]/gallery/index.astro +++ /dev/null @@ -1,16 +0,0 @@ ---- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; -import type { getStaticPaths } from "../index.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolLayout {locale} tool={tools.gallery}> - <Page {locale} client:only="react"> - <Page {locale} skeleton slot="fallback" /> - </Page> -</ToolLayout> diff --git a/packages/website/src/pages/[...locale]/guess/_index.tsx b/packages/website/src/pages/[...locale]/guess/_index.tsx deleted file mode 100644 index ac9c88c3b..000000000 --- a/packages/website/src/pages/[...locale]/guess/_index.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { Box, Flex, Heading } from "@radix-ui/themes"; -import { Suspense, useEffect } from "react"; -import { GuessBackground } from "../../../components/GuessBackground"; -import { Guesser } from "../../../components/Guesser"; -import { GuessRenderer } from "../../../components/GuessRenderer"; -import { GuessRendererLoader } from "../../../components/GuessRendererLoader"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { - type LocaleAcceptorProps, - LocaleProvider, - useLocale, -} from "../../../hooks/useLocale"; -import { Duel } from "../../../stores/duel"; -import { Guess, GuessState } from "../../../stores/guess"; -import { Tankopedia } from "../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; - -const [tankDefinitions, modelDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableModelDefinitions, -]); - -const ids = Object.keys(tankDefinitions.tanks); - -export function Page({ - locale, - skeleton, -}: LocaleAcceptorProps & MaybeSkeletonComponentProps) { - const id = Number(ids[Math.floor(Math.random() * ids.length)]); - const tank = tankDefinitions.tanks[id]; - - Guess.useInitialization(tank); - - return ( - <LocaleProvider locale={locale}> - <Container skeleton={skeleton} /> - </LocaleProvider> - ); -} - -function Container({ skeleton }: MaybeSkeletonComponentProps) { - const tank = Guess.use((state) => state.tank); - const model = modelDefinitions.models[tank.id]; - - Tankopedia.useInitialization(model); - Duel.useInitialization({ tank, model }); - - return ( - <PageWrapper p="0" maxWidth="unset" color="cyan"> - <Content skeleton={skeleton} /> - </PageWrapper> - ); -} - -function Content({ skeleton }: MaybeSkeletonComponentProps) { - const { unwrap } = useLocale(); - const tank = Guess.use((state) => state.tank); - const guessState = Guess.use((state) => state.guessState); - const isRevealed = guessState !== GuessState.NotGuessed; - const name = unwrap(tank.name!); - const fontSize = `min(48vh, ${55 / name.length}vw)`; - const transitionDuration = isRevealed ? "2s" : undefined; - const tiers = Guess.use((state) => state.tiers); - - useEffect(() => { - const isTankAlreadyCompliant = tiers.includes(tank.tier); - - if (isTankAlreadyCompliant) return; - - const filteredIds = ids.filter((id) => { - const tank = tankDefinitions.tanks[Number(id)]; - return tiers.includes(tank.tier); - }); - - if (filteredIds.length === 0) return; - - const id = Number( - filteredIds[Math.floor(Math.random() * filteredIds.length)], - ); - const newTank = tankDefinitions.tanks[id]; - - Guess.mutate((draft) => { - draft.tank = newTank; - draft.guessState = GuessState.NotGuessed; - draft.helpingReveal = false; - }); - }, [tiers]); - - return ( - <Flex flexGrow="1" position="relative" overflow="hidden"> - <GuessBackground /> - - <Heading - style={{ - transitionDuration, - opacity: isRevealed ? 1 : 0, - fontWeight: 900, - position: "absolute", - left: "50%", - top: "50%", - transform: "translate(-50%, -50%)", - fontSize, - userSelect: "none", - whiteSpace: "nowrap", - pointerEvents: "none", - }} - > - {name} - </Heading> - - <Box - position="absolute" - top="0" - left="0" - width="100%" - height="100%" - style={{ transitionDuration }} - > - <Suspense fallback={<GuessRendererLoader />}> - {!skeleton && <GuessRenderer />} - </Suspense> - </Box> - - <Heading - style={{ - transitionDuration, - opacity: isRevealed ? 0.5 : 0, - fontWeight: 900, - position: "absolute", - left: "50%", - top: "50%", - transform: "translate(-50%, -50%)", - fontSize, - userSelect: "none", - whiteSpace: "nowrap", - pointerEvents: "none", - }} - > - {name} - </Heading> - - <Guesser /> - </Flex> - ); -} diff --git a/packages/website/src/pages/[...locale]/guess/index.astro b/packages/website/src/pages/[...locale]/guess/index.astro deleted file mode 100644 index 4e457b286..000000000 --- a/packages/website/src/pages/[...locale]/guess/index.astro +++ /dev/null @@ -1,18 +0,0 @@ ---- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolOpenGraph {locale} tool={tools.guess} /> - -<BaseLayout {locale} noFooter> - <Page {locale} client:only="react"> - <Page {locale} skeleton slot="fallback" /> - </Page> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/index.astro b/packages/website/src/pages/[...locale]/index.astro deleted file mode 100644 index 4495f1d92..000000000 --- a/packages/website/src/pages/[...locale]/index.astro +++ /dev/null @@ -1,132 +0,0 @@ ---- -import { AstroSeo } from "@astrolib/seo"; -import { assertSecret } from "@blitzkit/core"; -import locales from "@blitzkit/i18n/locales.json"; -import { literals } from "@blitzkit/i18n/src/literals"; -import { Box, Flex, Link } from "@radix-ui/themes"; -import type { GetStaticPaths } from "astro"; -import { AesonPlug } from "../../components/AesonPlug"; -import HomeHero from "../../components/HomeHero.astro"; -import { Plugs } from "../../components/Plugs"; -import ToolCard from "../../components/ToolCard.astro"; -import { tools, type Tool } from "../../constants/tools"; -import { getStrings } from "../../core/i18n/getStrings"; -import BaseLayout from "../../layouts/BaseLayout.astro"; - -const MAX_TOOLS_PER_ROW = 2; - -const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_BRANCH); -const { locale } = Astro.props; -const strings = getStrings(locale); -const rows: Tool[][] = []; - -for (const id in tools) { - const tool = tools[id]; - - if ("disabled" in tool && tool.disabled) continue; - - const compatibleRow = rows.find( - (row) => - row.length < MAX_TOOLS_PER_ROW && - !("significant" in row[0] && row[0].significant), - ); - - if (compatibleRow) { - compatibleRow.push(tool); - } else { - rows.push([tool]); - } -} - -export const getStaticPaths = (() => { - return [ - ...locales.supported - .filter(({ locale }) => locale !== locales.default) - .map(({ locale }) => ({ params: { locale }, props: { locale } })), - { params: { locale: undefined }, props: { locale: locales.default } }, - ]; -}) satisfies GetStaticPaths; ---- - -<AstroSeo - title="BlitzKit" - description={strings.website.home.seo_description} - canonical={Astro.url.href} - openGraph={{ - url: Astro.url.href, - title: "BlitzKit", - description: strings.website.home.seo_description, - site_name: "BlitzKit", - }} - twitter={{ cardType: "summary_large_image" }} -/> - -<BaseLayout {locale}> - <HomeHero /> - - <!-- <Flex justify="center" py="6" px="4" justify="center"> - <Flex - wrap="wrap" - justify="between" - maxWidth="64rem" - gap="6" - align="center" - flexGrow="1" - > - <HomeLocaleSwitcher {locale} client:load /> - <DiscordPlug {locale} client:only="react"> - <DiscordPlug {locale} skeleton slot="fallback" /> - </DiscordPlug> - </Flex> - </Flex> --> - - { - locale !== locales.default && ( - <Flex justify="center" mb="4"> - <Link - href="https://crowdin.com/project/blitzkrieg" - target="_blank" - underline="always" - > - {literals(strings.website.home.help_translate, { - name: "BlitzKit", - language: - strings.common.locales[ - locale as keyof typeof strings.common.locales - ], - })} - </Link> - </Flex> - ) - } - - <Plugs client:idle /> - - { - PUBLIC_ASSET_BRANCH === "preview" && ( - <Flex justify="center" my="8" px="4"> - <AesonPlug /> - </Flex> - ) - } - - <Flex p="4" justify="center" gap="4" mt="3"> - <Flex maxWidth="80rem" flexGrow="1" gap="4" direction="column"> - { - rows.map((row) => ( - <Flex gap="4" direction={{ initial: "column", sm: "row" }}> - {row.map((tool) => ( - <ToolCard {locale} {tool} /> - ))} - {row.length < MAX_TOOLS_PER_ROW && - !("significant" in row[0] && row[0].significant) && ( - <Box flexGrow="1" /> - )} - </Flex> - )) - } - </Flex> - </Flex> - - <div style={{ flex: 1 }}></div> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/mixer/_index.tsx b/packages/website/src/pages/[...locale]/mixer/_index.tsx deleted file mode 100644 index a0d414198..000000000 --- a/packages/website/src/pages/[...locale]/mixer/_index.tsx +++ /dev/null @@ -1,311 +0,0 @@ -import { - asset, - GunDefinition, - TankDefinition, - TIER_ROMAN_NUMERALS, - TurretDefinition, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { CaretRightIcon, CaretUpIcon, UpdateIcon } from "@radix-ui/react-icons"; -import { - Box, - Button, - Dialog, - Flex, - IconButton, - Popover, - Skeleton, - Text, - type ButtonProps, -} from "@radix-ui/themes"; -import { useRef, useState } from "react"; -import { MixerScene } from "../../../components/MixerScene"; -import { ModuleButton } from "../../../components/ModuleButtons/ModuleButton"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { ScreenshotButton } from "../../../components/ScreenshotButton"; -import { TankSearch } from "../../../components/TankSearch"; -import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { curateMixer } from "../../../core/blitzkit/curateMixer"; -import { - LocaleProvider, - useLocale, - type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import { Mixer } from "../../../stores/mixer"; -import { Tankopedia } from "../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; - -const modelDefinition = await awaitableModelDefinitions.then( - ({ models }) => models[1] -); -const tankDefinitions = await awaitableTankDefinitions; - -export function Page({ - locale, - skeleton, -}: LocaleAcceptorProps & MaybeSkeletonComponentProps) { - const initial = useRef(curateMixer()); - - Mixer.useInitialization(initial.current); - Tankopedia.useInitialization(modelDefinition); - - return ( - <PageWrapper color="gray" p="0" maxWidth="unset"> - <LocaleProvider locale={locale}> - <Content skeleton={skeleton} /> - </LocaleProvider> - </PageWrapper> - ); -} - -type ModuleButtonProps = Omit<ButtonProps, "onSelect"> & - MaybeSkeletonComponentProps & { - tank: number; - onSelect: ( - tank: TankDefinition, - turret?: TurretDefinition, - gun?: GunDefinition - ) => void; - turret?: number; - gun?: number; - }; - -function ModuleChooser({ - skeleton, - tank, - onSelect, - turret, - gun, - ...props -}: ModuleButtonProps) { - const { strings } = useLocale(); - const [open, setOpen] = useState(false); - const tankDefinition = tankDefinitions.tanks[tank]; - const turretDefinition = tankDefinition.turrets.find((t) => t.id === turret); - const gunDefinition = turretDefinition?.guns.find((g) => g.id === gun); - const subSelector = - turret !== undefined && - (tankDefinition.turrets.length > 1 || - (gun !== undefined && turretDefinition!.guns.length > 1)); - - return ( - <Flex> - <Dialog.Root open={open} onOpenChange={setOpen}> - <Dialog.Trigger> - <Button - variant="surface" - size={{ initial: "3", md: "4" }} - style={{ - overflow: "clip", - borderTopRightRadius: subSelector ? 0 : undefined, - borderBottomRightRadius: subSelector ? 0 : undefined, - }} - {...props} - > - {skeleton ? ( - <Skeleton width="4em" height="2em" /> - ) : ( - <img - style={{ - width: "4em", - height: "4em", - objectFit: "contain", - }} - src={asset(`icons/tanks/big/${tank}.webp`)} - /> - )} - </Button> - </Dialog.Trigger> - - <Dialog.Content> - <Dialog.Title> - {strings.website.common.tank_search.select_dialog_title} - </Dialog.Title> - - <TankSearch - compact - onSelect={(tank) => { - onSelect?.(tank); - setOpen(false); - }} - /> - </Dialog.Content> - </Dialog.Root> - - {subSelector && ( - <Popover.Root> - <Popover.Trigger> - <IconButton - variant="surface" - size={{ initial: "3", md: "4" }} - style={{ - overflow: "clip", - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, - marginLeft: -1, - }} - {...props} - > - <CaretUpIcon /> - </IconButton> - </Popover.Trigger> - - <Popover.Content> - <Flex direction="column" gap="2"> - {tankDefinition.turrets.length > 1 && ( - <Flex gap="2"> - {tankDefinition.turrets.map((turret) => ( - <ModuleButton - variant={ - turret.id === turretDefinition?.id ? "solid" : undefined - } - key={turret.id} - module="turret" - discriminator={TIER_ROMAN_NUMERALS[turret.tier]} - onClick={() => { - onSelect?.(tankDefinition, turret); - }} - /> - ))} - </Flex> - )} - - {turretDefinition && turretDefinition.guns.length > 1 && ( - <Flex gap="2"> - {turretDefinition.guns.map((gun) => ( - <ModuleButton - variant={ - gun.id === gunDefinition?.id ? "solid" : undefined - } - key={gun.id} - module="gun" - discriminator={TIER_ROMAN_NUMERALS[gun.tier]} - secondaryDiscriminator={ - <Text style={{ fontSize: "0.75em" }}> - {literals(strings.common.units.mm, { - value: gun.shells[0].caliber.toFixed(0), - })} - </Text> - } - onClick={() => { - onSelect?.(tankDefinition, turretDefinition, gun); - }} - /> - ))} - </Flex> - )} - </Flex> - </Popover.Content> - </Popover.Root> - )} - </Flex> - ); -} - -function ModuleSeparator() { - return ( - <Text size="5" color="gray" trim="end"> - <CaretRightIcon width="1em" height="1em" /> - </Text> - ); -} - -function Content({ skeleton }: MaybeSkeletonComponentProps) { - const hullTankId = Mixer.use((state) => state.hull); - const turretTankId = Mixer.use((state) => state.turret.tank.id); - const turretTurretId = Mixer.use((state) => state.turret.turret.id); - const gunTankId = Mixer.use((state) => state.gun.tank.id); - const gunTurretId = Mixer.use((state) => state.gun.turret.id); - const gunGunId = Mixer.use((state) => state.gun.gun.id); - const canvas = useRef<HTMLCanvasElement>(null!); - const { strings } = useLocale(); - - return ( - <Box flexGrow="1" position="relative"> - <Box position="absolute" width="100%" height="100%" top="0" left="0"> - {!skeleton && <MixerScene ref={canvas} />} - </Box> - - <Flex - align="center" - direction="column" - position="absolute" - bottom="4" - left="50%" - style={{ transform: "translateX(-50%)" }} - gap="4" - > - <Flex gap="2"> - <ScreenshotButton - name={hullTankId.slug} - canvas={canvas} - highContrast - variant="surface" - > - {strings.website.tools.mixer.screenshot} - </ScreenshotButton> - <Button - highContrast - variant="surface" - onClick={() => { - const mixed = curateMixer(); - - Mixer.mutate((draft) => { - Object.assign(draft, mixed); - }); - }} - > - <UpdateIcon /> - {strings.website.tools.mixer.randomize} - </Button> - </Flex> - - <Flex gap="2" align="center"> - <ModuleChooser - skeleton={skeleton} - tank={hullTankId.id} - onSelect={(tank) => { - Mixer.mutate((draft) => { - draft.hull = tank; - }); - }} - /> - <ModuleSeparator /> - <ModuleChooser - skeleton={skeleton} - tank={turretTankId} - turret={turretTurretId} - onSelect={(tank, turret) => { - Mixer.mutate((draft) => { - draft.turret = { - tank, - turret: turret ?? tank.turrets.at(-1)!, - }; - }); - }} - /> - <ModuleSeparator /> - <ModuleChooser - skeleton={skeleton} - tank={gunTankId} - turret={gunTurretId} - gun={gunGunId} - onSelect={(tank, turret, gun) => { - Mixer.mutate((draft) => { - draft.gun = { - tank, - turret: turret ?? tank.turrets.at(-1)!, - gun: - gun ?? - turret?.guns.at(-1) ?? - tank.turrets.at(-1)!.guns.at(-1)!, - }; - }); - }} - /> - </Flex> - </Flex> - </Box> - ); -} diff --git a/packages/website/src/pages/[...locale]/mixer/index.astro b/packages/website/src/pages/[...locale]/mixer/index.astro deleted file mode 100644 index 70e118121..000000000 --- a/packages/website/src/pages/[...locale]/mixer/index.astro +++ /dev/null @@ -1,18 +0,0 @@ ---- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolOpenGraph {locale} tool={tools.mixer} /> - -<BaseLayout {locale} integrated> - <Page {locale} client:only="react"> - <Page {locale} skeleton slot="fallback" /> - </Page> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/performance/_index.tsx b/packages/website/src/pages/[...locale]/performance/_index.tsx deleted file mode 100644 index e0ce43b16..000000000 --- a/packages/website/src/pages/[...locale]/performance/_index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { PageWrapper } from "../../../components/PageWrapper"; -import { PlayerCountControl } from "../../../components/Performance/PlayerCountControl"; -import { TankTable } from "../../../components/Performance/Table"; -import { FilterControl } from "../../../components/TankSearch/components/FilterControl"; -import { - LocaleProvider, - type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; - -type PageProps = MaybeSkeletonComponentProps & LocaleAcceptorProps; - -export function Page({ skeleton, locale }: PageProps) { - return ( - <LocaleProvider locale={locale}> - <Content skeleton={skeleton} /> - </LocaleProvider> - ); -} - -function Content({ skeleton }: MaybeSkeletonComponentProps) { - return ( - <PageWrapper pb="0" px="0" color="jade" maxWidth="100%"> - <FilterControl /> - <PlayerCountControl /> - - <TankTable skeleton={skeleton} /> - </PageWrapper> - ); -} diff --git a/packages/website/src/pages/[...locale]/performance/index.astro b/packages/website/src/pages/[...locale]/performance/index.astro deleted file mode 100644 index fc34d8b9b..000000000 --- a/packages/website/src/pages/[...locale]/performance/index.astro +++ /dev/null @@ -1,19 +0,0 @@ ---- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; -import type { getStaticPaths } from "../index.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolOpenGraph {locale} tool={tools.performance} /> - -<BaseLayout {locale} noFooter> - <Page client:only="react" {locale}> - <Page skeleton slot="fallback" {locale} /> - </Page> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/playlist/_index.tsx b/packages/website/src/pages/[...locale]/playlist/_index.tsx deleted file mode 100644 index 0fe0c6f44..000000000 --- a/packages/website/src/pages/[...locale]/playlist/_index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { PageWrapper } from "../../../components/PageWrapper"; -import { PlaylistGenerator } from "../../../components/PlaylistGenerator"; -import { PlaylistOrder } from "../../../components/PlaylistOrder"; -import { LocaleProvider } from "../../../hooks/useLocale"; -import { App } from "../../../stores/app"; -import { Playlist } from "../../../stores/playlist"; -import { TankFilters } from "../../../stores/tankFilters"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; - -interface Props { - locale: string; -} - -export function Page({ - locale, - skeleton, -}: Props & MaybeSkeletonComponentProps) { - const generated = Playlist.use((state) => state.list !== undefined); - - if (App.state.logins.wargaming) TankFilters.state.showUnowned = false; - - return ( - <LocaleProvider locale={locale}> - <PageWrapper color="tomato" direction="row" maxWidth="unset" p="0"> - {!generated && <PlaylistGenerator skeleton={skeleton} />} - {generated && <PlaylistOrder />} - </PageWrapper> - </LocaleProvider> - ); -} diff --git a/packages/website/src/pages/[...locale]/playlist/index.astro b/packages/website/src/pages/[...locale]/playlist/index.astro deleted file mode 100644 index 5d5989351..000000000 --- a/packages/website/src/pages/[...locale]/playlist/index.astro +++ /dev/null @@ -1,18 +0,0 @@ ---- -import ToolOpenGraph from "../../../components/ToolOpenGraph.astro"; -import { tools } from "../../../constants/tools"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; -import { Page } from "./_index.tsx"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolOpenGraph {locale} tool={tools.playlist} /> - -<BaseLayout {locale} noFooter> - <Page {locale} client:only="react"> - <Page {locale} skeleton slot="fallback" /> - </Page> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/session/_index.tsx b/packages/website/src/pages/[...locale]/session/_index.tsx deleted file mode 100644 index 05ca5f1b3..000000000 --- a/packages/website/src/pages/[...locale]/session/_index.tsx +++ /dev/null @@ -1,487 +0,0 @@ -import { - type AccountListWithServer, - type CompositeStatsKey, - type IndividualAccountInfo, - type IndividualTankStats, - compositeStats, - compositeStatsKeys, - deltaTankStats, - formatCompositeStat, - getAccountInfo, - getTankStats, - idToRegion, - searchPlayersAcrossRegions, - sumCompositeStats, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { - ArrowDownIcon, - MagnifyingGlassIcon, - PlusIcon, -} from "@radix-ui/react-icons"; -import { - AlertDialog, - Button, - DropdownMenu, - Flex, - Heading, - IconButton, - Select, - Table, - Text, - TextField, -} from "@radix-ui/themes"; -import { debounce } from "lodash-es"; -import { useEffect, useMemo, useRef, useState } from "react"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { StickyRowHeaderCell } from "../../../components/StickyRowHeaderCell"; -import { StickyTableRoot } from "../../../components/StickyTableRoot"; -import { TankRowHeaderCell } from "../../../components/TankRowHeaderCell"; -import { awaitableAverageDefinitions } from "../../../core/awaitables/averageDefinitions"; -import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; -import { - type LocaleAcceptorProps, - LocaleProvider, - useLocale, -} from "../../../hooks/useLocale"; -import { Session, type SessionTracking } from "../../../stores/session"; - -const [tankDefinitions, averageDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableAverageDefinitions, -]); - -export function Page({ locale }: LocaleAcceptorProps) { - return ( - <LocaleProvider locale={locale}> - <Content /> - </LocaleProvider> - ); -} - -function Content() { - const { locale, strings } = useLocale(); - const [showSearch, setShowSearch] = useState(false); - const [searching, setSearching] = useState(false); - const [searchResults, setSearchResults] = useState<AccountListWithServer>([]); - const [showCCInaccessibilityPrompt, setShowCCInaccessibilityPrompt] = - useState(false); - const input = useRef<HTMLInputElement>(null); - const session = Session.use(); - const [tankStatsB, setTankStatsB] = useState<IndividualTankStats[] | null>( - null, - ); - const [accountInfo, setAccountInfo] = useState< - IndividualAccountInfo | null | undefined - >(undefined); - - useEffect(() => { - (async () => { - const id = session.tracking ? session.player.id : undefined; - if (!id) return setAccountInfo(null); - - setAccountInfo( - session.tracking ? await getAccountInfo(idToRegion(id), id) : null, - ); - })(); - }, [session.tracking && session.player.id]); - const delta = useMemo( - () => - session.tracking && tankStatsB - ? deltaTankStats(session.player.stats, tankStatsB) - .sort((a, b) => b.last_battle_time - a.last_battle_time) - .map((entry) => { - const tank = tankDefinitions.tanks[entry.tank_id]; - const average = averageDefinitions.averages[tank.id]; - const composite = compositeStats( - { ...entry.all, battle_life_time: entry.battle_life_time }, - average?.mu!, - ); - - return { tank, composite }; - }) - : undefined, - [session.tracking && session.player, tankStatsB], - ); - const total = useMemo( - () => - delta - ? sumCompositeStats(delta.map(({ composite }) => composite)) - : undefined, - [delta], - ); - const search = debounce(async () => { - if (!input.current) return; - - setSearchResults(await searchPlayersAcrossRegions(input.current.value, 15)); - setSearching(false); - }, 500); - - async function track(id: number) { - const region = idToRegion(id); - const player = await getAccountInfo(region, id); - const stats = await getTankStats(region, id); - - if (stats === null) { - setShowCCInaccessibilityPrompt(true); - return; - } - - setShowSearch(false); - - Session.mutate((draft) => { - if (!input.current) return; - - draft.tracking = true; - (draft as SessionTracking).player = { - id, - region, - since: Date.now(), - stats, - }; - - input.current.value = player.nickname; - }); - } - - useEffect(() => { - async function update() { - if (!session.tracking) return; - - const tankStats = await getTankStats( - idToRegion(session.player.id), - session.player.id, - ); - setTankStatsB(tankStats); - } - - update(); - const interval = setInterval(update, 5 * 1000); - - return () => clearInterval(interval); - }, [session.tracking && session.player.id]); - - return ( - <PageWrapper color="blue"> - <AlertDialog.Root - open={showCCInaccessibilityPrompt} - onOpenChange={setShowCCInaccessibilityPrompt} - > - <AlertDialog.Content> - <AlertDialog.Title> - {strings.website.tools.session.no_cc.title} - </AlertDialog.Title> - <AlertDialog.Description> - {strings.website.tools.session.no_cc.body} - </AlertDialog.Description> - - <Flex mt="4" justify="end"> - <AlertDialog.Action> - <Button variant="solid"> - {strings.website.tools.session.no_cc.alright} - </Button> - </AlertDialog.Action> - </Flex> - </AlertDialog.Content> - </AlertDialog.Root> - - {!session.tracking && ( - <Flex - style={{ flex: 1 }} - align="center" - justify="center" - direction="column" - gap="3" - > - <Flex gap="2" align="center"> - <Text color="gray"> - {strings.website.tools.session.search.lookup} - </Text> - <ArrowDownIcon /> - </Flex> - - <TextField.Root - variant="classic" - size="3" - style={{ - position: "relative", - width: "75vw", - maxWidth: 480, - }} - ref={input} - placeholder={strings.website.tools.session.search.hint} - onChange={() => { - if (!input.current) return; - - const sanitized = input.current.value.trim(); - - if (sanitized.length > 0) { - setShowSearch(true); - setSearching(true); - search(); - } else { - setShowSearch(false); - } - }} - > - <TextField.Slot> - <MagnifyingGlassIcon /> - </TextField.Slot> - </TextField.Root> - - {showSearch && ( - <Flex mt="2"> - {searching && ( - <Flex justify="center"> - <Text color="gray"> - {strings.website.tools.session.search.searching} - </Text> - </Flex> - )} - - {!searching && searchResults.length === 0 && ( - <Flex justify="center"> - <Text color="gray"> - {strings.website.tools.session.search.no_results} - </Text> - </Flex> - )} - - {!searching && searchResults.length > 0 && ( - <Flex direction="column" gap="2"> - {searchResults.map((player) => ( - <Button - key={player.account_id} - variant="ghost" - onClick={() => track(player.account_id)} - > - {player.nickname} ( - {strings.common.regions.short[player.region]}) - </Button> - ))} - </Flex> - )} - </Flex> - )} - </Flex> - )} - - {session.tracking && ( - <Flex - mt="2" - mb="2" - direction={{ initial: "column", sm: "row" }} - align="center" - justify="between" - gap="2" - > - <Flex mt="2" mb="2" direction="column" gap="2"> - <Heading size="5"> - {literals(strings.website.tools.session.controls.title, { - name: accountInfo?.nickname, - })} - </Heading> - <Text color="gray"> - {literals(strings.website.tools.session.controls.subtitle, { - time: new Date(session.player.since).toLocaleString(locale), - })} - </Text> - </Flex> - - <Flex gap="2" align="center" justify="center"> - <Button - color="red" - onClick={async () => { - const stats = await getTankStats( - session.player.region, - session.player.id, - ); - - if (stats === null) { - setShowCCInaccessibilityPrompt(true); - return; - } - - Session.mutate((draft) => { - (draft as SessionTracking).player.stats = stats; - }); - }} - > - {strings.website.tools.session.controls.reset} - </Button> - <Button - onClick={() => { - Session.mutate((draft) => { - draft.tracking = false; - }); - }} - > - {strings.website.tools.session.controls.change} - </Button> - </Flex> - </Flex> - )} - - {session.tracking && delta && delta.length > 0 && ( - <Flex justify="center"> - <StickyTableRoot variant="surface"> - <Table.Header> - <Table.Row> - <Table.ColumnHeaderCell> - {strings.website.tools.session.table.tank} - </Table.ColumnHeaderCell> - - {session.columns.map((column, index) => ( - <Table.ColumnHeaderCell - align="center" - width="0" - key={column} - style={{ position: "relative" }} - > - {index === 0 && session.columns.length < 4 && ( - <DropdownMenu.Root> - <DropdownMenu.Trigger> - <IconButton - size="1" - variant="surface" - style={{ - position: "absolute", - left: 0, - top: "50%", - transform: "translate(-100%, -50%)", - zIndex: 1, - }} - > - <PlusIcon /> - </IconButton> - </DropdownMenu.Trigger> - - <DropdownMenu.Content> - {compositeStatsKeys - .filter((item) => !session.columns.includes(item)) - .map((key) => ( - <DropdownMenu.Item - key={key} - onClick={() => { - Session.mutate((draft) => { - (draft as SessionTracking).columns.unshift( - key, - ); - }); - }} - > - {strings.common.composite_stats[key]} - </DropdownMenu.Item> - ))} - </DropdownMenu.Content> - </DropdownMenu.Root> - )} - - <Select.Root - value={column} - onValueChange={(value) => { - Session.mutate((draft) => { - if (value === "remove") { - (draft as SessionTracking).columns.splice(index, 1); - } else { - (draft as SessionTracking).columns[index] = - value as CompositeStatsKey; - } - }); - }} - > - <Select.Trigger variant="ghost" /> - - <Select.Content> - <Select.Item value="remove"> - {strings.website.tools.session.table.remove} - </Select.Item> - - <Select.Separator /> - - {compositeStatsKeys - .filter( - (item) => - !session.columns.includes(item) || - item === column, - ) - .map((key) => ( - <Select.Item key={key} value={key}> - {strings.common.composite_stats[key]} - </Select.Item> - ))} - </Select.Content> - </Select.Root> - </Table.ColumnHeaderCell> - ))} - </Table.Row> - </Table.Header> - - <Table.Body> - <Table.Row - style={{ - overflow: "hidden", - }} - > - <StickyRowHeaderCell - style={{ - paddingLeft: 32, - position: "relative", - overflowY: "hidden", - }} - > - {strings.website.tools.session.table.total} - </StickyRowHeaderCell> - {session.columns.map((column) => ( - <Table.Cell align="center" key={column}> - {formatCompositeStat(total![column], column, total!)} - </Table.Cell> - ))} - </Table.Row> - - {delta.map(({ tank, composite }) => { - return ( - <Table.Row - key={tank.id} - style={{ - overflow: "hidden", - }} - > - <TankRowHeaderCell tank={tank} /> - {session.columns.map((column) => ( - <Table.Cell align="center" key={column}> - {formatCompositeStat( - composite[column], - column, - composite, - )} - </Table.Cell> - ))} - </Table.Row> - ); - })} - </Table.Body> - </StickyTableRoot> - </Flex> - )} - - {session.tracking && delta && delta.length === 0 && ( - <Flex - align="center" - justify="center" - direction="column" - style={{ - flex: 1, - }} - > - <Heading color="gray"> - {strings.website.tools.session.no_battles.title} - </Heading> - <Text color="gray"> - {strings.website.tools.session.no_battles.hint} - </Text> - </Flex> - )} - </PageWrapper> - ); -} diff --git a/packages/website/src/pages/[...locale]/session/index.astro b/packages/website/src/pages/[...locale]/session/index.astro deleted file mode 100644 index ff8b3df9f..000000000 --- a/packages/website/src/pages/[...locale]/session/index.astro +++ /dev/null @@ -1,14 +0,0 @@ ---- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; -import type { getStaticPaths } from "../index.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolLayout {locale} tool={tools.session}> - <Page client:load {locale} /> -</ToolLayout> diff --git a/packages/website/src/pages/[...locale]/settings/_index.tsx b/packages/website/src/pages/[...locale]/settings/_index.tsx deleted file mode 100644 index 2279cf3af..000000000 --- a/packages/website/src/pages/[...locale]/settings/_index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Flex, Switch, Text } from "@radix-ui/themes"; -import { LocaleSwitcher } from "../../../components/LocaleSwitcher"; -import { PageWrapper } from "../../../components/PageWrapper"; -import { - LocaleProvider, - useLocale, - type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import { App } from "../../../stores/app"; - -export function Page({ locale }: LocaleAcceptorProps) { - return ( - <LocaleProvider locale={locale}> - <Content locale={locale} /> - </LocaleProvider> - ); -} - -function Content({ locale }: LocaleAcceptorProps) { - const developerMode = App.useDeferred((state) => state.developerMode, false); - const { strings } = useLocale(); - - return ( - <PageWrapper justify="center" align="center"> - <Flex gap="3" direction="column" width="100%" maxWidth="20rem"> - <Flex align="center" gap="2" justify="between"> - <Text>{strings.website.settings.dev_mode}</Text> - <Switch - variant="classic" - checked={developerMode} - onCheckedChange={(checked) => - App.mutate((draft) => { - draft.developerMode = checked; - }) - } - /> - </Flex> - - <Flex align="center" gap="2" justify="between"> - <Text>{strings.website.settings.language}</Text> - <LocaleSwitcher locale={locale} /> - </Flex> - </Flex> - </PageWrapper> - ); -} diff --git a/packages/website/src/pages/[...locale]/settings/index.astro b/packages/website/src/pages/[...locale]/settings/index.astro deleted file mode 100644 index 48671bfdb..000000000 --- a/packages/website/src/pages/[...locale]/settings/index.astro +++ /dev/null @@ -1,21 +0,0 @@ ---- -import { AstroSeo } from "@astrolib/seo"; -import { getStrings } from "../../../core/i18n/getStrings"; -import BaseLayout from "../../../layouts/BaseLayout.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; - -const strings = getStrings(locale); ---- - -<AstroSeo - title={strings.website.settings.title} - description={strings.website.settings.description} -/> - -<BaseLayout {locale}> - <Page {locale} client:load /> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx b/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx deleted file mode 100644 index 78ed0623c..000000000 --- a/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { TankType } from "@blitzkit/core"; -import { PageWrapper } from "../../../../components/PageWrapper"; -import { CalloutsSection } from "../../../../components/Tankopedia/CalloutsSection"; -import { CharacteristicsSection } from "../../../../components/Tankopedia/CharacteristicsSection"; -import { GameModeSection } from "../../../../components/Tankopedia/GameModeSection"; -import { GuideSection } from "../../../../components/Tankopedia/GuideSection"; -import { HeroSection } from "../../../../components/Tankopedia/HeroSection"; -import { MetaSection } from "../../../../components/Tankopedia/MetaSection"; -import { TechTreeSection } from "../../../../components/Tankopedia/TechTreeSection"; -import { VideoSection } from "../../../../components/Tankopedia/VideoSection"; -import { awaitableModelDefinitions } from "../../../../core/awaitables/modelDefinitions"; -import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions"; -import { - LocaleProvider, - type LocaleAcceptorProps, -} from "../../../../hooks/useLocale"; -import { Duel } from "../../../../stores/duel"; -import { Tankopedia } from "../../../../stores/tankopedia"; -import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps"; -import type { TankGuide } from "../../../../types/tankGuide"; - -type PageProps = MaybeSkeletonComponentProps & - LocaleAcceptorProps & { - id: number; - guide?: TankGuide; - }; - -const [tankDefinitions, modelDefinitions] = await Promise.all([ - awaitableTankDefinitions, - awaitableModelDefinitions, -]); - -export function Page({ id, skeleton, locale, guide }: PageProps) { - const tank = tankDefinitions.tanks[id]; - const model = modelDefinitions.models[id]; - - Tankopedia.useInitialization(model); - Duel.useInitialization({ tank, model }); - - return ( - <LocaleProvider locale={locale}> - <PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9"> - <HeroSection skeleton={skeleton} /> - <MetaSection /> - <CalloutsSection /> - {tank.type === TankType.TANK_TYPE_RESEARCHABLE && !tank.deprecated && ( - <TechTreeSection skeleton={skeleton} /> - )} - <CharacteristicsSection skeleton={skeleton} /> - <GameModeSection /> - {guide && <GuideSection guide={guide} />} - <VideoSection skeleton={skeleton} /> - </PageWrapper> - </LocaleProvider> - ); -} diff --git a/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro deleted file mode 100644 index 4933da71d..000000000 --- a/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro +++ /dev/null @@ -1,139 +0,0 @@ ---- -import { AstroSeo } from "@astrolib/seo"; -import { - assertSecret, - asset, - createDefaultProvisions, - createDefaultSkills, - fetchEquipmentDefinitions, - fetchModelDefinitions, - fetchProvisionDefinitions, - fetchSkillDefinitions, - fetchTankDefinitions, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { unwrapper } from "@blitzkit/i18n"; -import { literals } from "@blitzkit/i18n/src/literals"; -import type { GetStaticPaths } from "astro"; -import { load } from "cheerio"; -import { tankCharacteristics } from "../../../../core/blitzkit/tankCharacteristics"; -import { getStrings } from "../../../../core/i18n/getStrings"; -import BaseLayout from "../../../../layouts/BaseLayout.astro"; -import { genericDefaultEquipmentMatrix } from "../../../../stores/duel/constants"; -import type { TankGuide } from "../../../../types/tankGuide"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; -import { Page } from "./_index"; - -export const getStaticPaths = (async () => { - const tankDefinitions = await fetchTankDefinitions(); - const parentPaths = parentGetStaticPaths(); - - return Object.values(tankDefinitions.tanks) - .map((tank) => - parentPaths.map((parent) => ({ - params: { ...parent.params, slug: tank.slug }, - props: { ...parent.props, id: tank.id }, - })), - ) - .flat(); -}) satisfies GetStaticPaths; - -const { id, locale } = Astro.props; -const strings = getStrings(locale); -const tankDefinitions = await fetchTankDefinitions(); -const skillDefinitions = await fetchSkillDefinitions(); -const provisionDefinitions = await fetchProvisionDefinitions(); -const equipmentDefinitions = await fetchEquipmentDefinitions(); -const tankModelDefinitions = await fetchModelDefinitions(); - -const tank = tankDefinitions.tanks[id]; -const tankModelDefinition = tankModelDefinitions.models[tank.id]; -const unwrap = unwrapper(locale); -const title = literals(strings.website.tools.tankopedia.tank_seo_title, { - name: unwrap(tank.name!), - tier: TIER_ROMAN_NUMERALS[tank.tier], - nationality: - strings.common.nations_adjectives[ - tank.nation as keyof typeof strings.common.nations_adjectives - ], - class: strings.common.tank_class_short[tank.class], - website: "BlitzKit", -}); -const url = import.meta.env.SITE + Astro.url.pathname; - -const icon = asset(`icons/tanks/big/${id}.webp`); -const turret = tank.turrets.at(-1)!; -const gun = turret.guns.at(-1)!; -const shell = gun.shells[0]; -const characteristics = tankCharacteristics( - { - tank, - applyDynamicArmor: false, - applyReactiveArmor: false, - applySpallLiner: false, - camouflage: true, - consumables: [], - crewSkills: createDefaultSkills(skillDefinitions), - engine: tank.engines.at(-1)!, - equipmentMatrix: genericDefaultEquipmentMatrix, - turret, - gun, - provisions: createDefaultProvisions(tank, gun, provisionDefinitions), - shell, - stockEngine: tank.engines[0], - stockGun: tank.turrets[0].guns[0], - stockTurret: tank.turrets[0], - stockTrack: tank.tracks[0], - track: tank.tracks.at(-1)!, - assaultDistance: 0, - equalize: false, - }, - { equipmentDefinitions, provisionDefinitions, tankModelDefinition }, -); -const description = literals( - strings.website.tools.tankopedia.tank_seo_description, - { - name: unwrap(tank.name!), - dpm: Math.round(characteristics.dpm).toLocaleString(locale), - alpha: Math.round(characteristics.damage).toLocaleString(locale), - penetration: Math.round(characteristics.penetration).toLocaleString(locale), - speed: Math.round(characteristics.speedForwards).toLocaleString(locale), - health: Math.round(characteristics.health).toLocaleString(locale), - }, -); - -const guideUrl = `${assertSecret(import.meta.env.PUBLIC_GUIDE_WEBSITE)}/${tank.slug}`; -let guide: TankGuide | undefined = undefined; -const guideResponse = await fetch(guideUrl); - -if (guideResponse.ok) { - const $ = await guideResponse.text().then(load); - const description = $(".entry-content p").first().text(); - const image = $(".entry-content img").first().attr("src")?.toString(); - - if (description !== undefined || image !== undefined) { - guide = { - url: guideUrl, - description, - image, - }; - } -} ---- - -<AstroSeo - {title} - {description} - openGraph={{ title, description, images: [{ url: icon }], url }} -/> - -<meta name="twitter:title" content={title} /> -<meta name="twitter:description" content={description} /> -<meta name="twitter:card" content="summary" /> -<meta name="twitter:image:src" content={icon} /> - -<BaseLayout {locale} integrated> - <Page {locale} id={id} {guide} client:only="react"> - <Page {locale} id={id} {guide} skeleton slot="fallback" /> - </Page> -</BaseLayout> diff --git a/packages/website/src/pages/[...locale]/tanks/_index.tsx b/packages/website/src/pages/[...locale]/tanks/_index.tsx deleted file mode 100644 index f5b22a310..000000000 --- a/packages/website/src/pages/[...locale]/tanks/_index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { PageWrapper } from "../../../components/PageWrapper"; -import { TankSearch } from "../../../components/TankSearch"; -import { - LocaleProvider, - type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; -import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps"; - -export function Page({ - locale, - skeleton, -}: LocaleAcceptorProps & MaybeSkeletonComponentProps) { - return ( - <LocaleProvider locale={locale}> - <PageWrapper color="purple" maxWidth="80rem"> - <TankSearch skeleton={skeleton} /> - </PageWrapper> - </LocaleProvider> - ); -} diff --git a/packages/website/src/pages/[...locale]/tanks/index.astro b/packages/website/src/pages/[...locale]/tanks/index.astro deleted file mode 100644 index b4dcbd295..000000000 --- a/packages/website/src/pages/[...locale]/tanks/index.astro +++ /dev/null @@ -1,15 +0,0 @@ ---- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolLayout {locale} tool={tools.tanks}> - <Page {locale} client:only="react"> - <Page {locale} skeleton slot="fallback" /> - </Page> -</ToolLayout> diff --git a/packages/website/src/pages/[...locale]/tier-list/_index.tsx b/packages/website/src/pages/[...locale]/tier-list/_index.tsx deleted file mode 100644 index 37429bd3c..000000000 --- a/packages/website/src/pages/[...locale]/tier-list/_index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { PageWrapper } from "../../../components/PageWrapper"; -import { FilterControl } from "../../../components/TankSearch/components/FilterControl"; -import { TierListControls } from "../../../components/TierList/Controls"; -import { TierListTable } from "../../../components/TierList/Table"; -import { TierListTiles } from "../../../components/TierList/Tiles"; -import { URLManager } from "../../../components/TierList/URLManager"; -import { - LocaleProvider, - type LocaleAcceptorProps, -} from "../../../hooks/useLocale"; - -export function Page({ locale }: LocaleAcceptorProps) { - return ( - <LocaleProvider locale={locale}> - <Content /> - </LocaleProvider> - ); -} - -function Content() { - return ( - <PageWrapper color="orange" maxWidth="100rem"> - <URLManager /> - <TierListControls /> - <TierListTable /> - <FilterControl /> - <TierListTiles /> - </PageWrapper> - ); -} diff --git a/packages/website/src/pages/[...locale]/tier-list/index.astro b/packages/website/src/pages/[...locale]/tier-list/index.astro deleted file mode 100644 index cc9598883..000000000 --- a/packages/website/src/pages/[...locale]/tier-list/index.astro +++ /dev/null @@ -1,13 +0,0 @@ ---- -import { tools } from "../../../constants/tools"; -import ToolLayout from "../../../layouts/ToolLayout.astro"; -import { Page } from "./_index"; - -export { getStaticPaths } from "../index.astro"; - -const { locale } = Astro.props; ---- - -<ToolLayout {locale} tool={tools.tier_list}> - <Page {locale} client:load /> -</ToolLayout> diff --git a/packages/website/src/pages/[...locale]/tools/compare/index.astro b/packages/website/src/pages/[...locale]/tools/compare/index.astro deleted file mode 100644 index cf0884483..000000000 --- a/packages/website/src/pages/[...locale]/tools/compare/index.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; - -export { getStaticPaths } from "../../index.astro"; - -const { locale } = Astro.props; ---- - -<LegacyRedirect {locale} path="compare" /> diff --git a/packages/website/src/pages/[...locale]/tools/embed/[embed]/index.astro b/packages/website/src/pages/[...locale]/tools/embed/[embed]/index.astro deleted file mode 100644 index 4e8240656..000000000 --- a/packages/website/src/pages/[...locale]/tools/embed/[embed]/index.astro +++ /dev/null @@ -1,26 +0,0 @@ ---- -import type { GetStaticPaths } from "astro"; -import { embedConfigurations } from "../../../../../constants/embeds"; -import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; - -export const getStaticPaths = (() => { - const parentPaths = parentGetStaticPaths(); - - return Object.keys(embedConfigurations) - .map((embed) => - parentPaths.map((parent) => ({ - params: { - ...parent.params, - embed: embed as keyof typeof embedConfigurations, - }, - })) - ) - .flat(); -}) satisfies GetStaticPaths; - -const { embed } = Astro.params; -const { locale } = Astro.props; ---- - -<LegacyRedirect {locale} path={`embed/${embed}`} /> diff --git a/packages/website/src/pages/[...locale]/tools/embed/index.astro b/packages/website/src/pages/[...locale]/tools/embed/index.astro deleted file mode 100644 index 8096fbdd8..000000000 --- a/packages/website/src/pages/[...locale]/tools/embed/index.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; - -export { getStaticPaths } from "../../index.astro"; - -const { locale } = Astro.props; ---- - -<LegacyRedirect {locale} path="embed" /> diff --git a/packages/website/src/pages/[...locale]/tools/gallery/index.astro b/packages/website/src/pages/[...locale]/tools/gallery/index.astro deleted file mode 100644 index 13a26f486..000000000 --- a/packages/website/src/pages/[...locale]/tools/gallery/index.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; - -export { getStaticPaths } from "../../index.astro"; - -const { locale } = Astro.props; ---- - -<LegacyRedirect {locale} path="gallery" /> diff --git a/packages/website/src/pages/[...locale]/tools/performance/index.astro b/packages/website/src/pages/[...locale]/tools/performance/index.astro deleted file mode 100644 index 37a006ac9..000000000 --- a/packages/website/src/pages/[...locale]/tools/performance/index.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; - -export { getStaticPaths } from "../../index.astro"; - -const { locale } = Astro.props; ---- - -<LegacyRedirect {locale} path="performance" /> diff --git a/packages/website/src/pages/[...locale]/tools/session/index.astro b/packages/website/src/pages/[...locale]/tools/session/index.astro deleted file mode 100644 index f0777caef..000000000 --- a/packages/website/src/pages/[...locale]/tools/session/index.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; - -export { getStaticPaths } from "../../index.astro"; - -const { locale } = Astro.props; ---- - -<LegacyRedirect {locale} path="session" /> diff --git a/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/index.astro b/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/index.astro deleted file mode 100644 index d5a571f74..000000000 --- a/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/index.astro +++ /dev/null @@ -1,27 +0,0 @@ ---- -import { fetchTankDefinitions } from "@blitzkit/core"; -import type { GetStaticPaths } from "astro"; -import LegacyRedirect from "../../../../../layouts/LegacyRedirect.astro"; -import { getStaticPaths as parentGetStaticPaths } from "../index.astro"; - -export const getStaticPaths = (async () => { - const tankDefinitions = await fetchTankDefinitions(); - const parentPaths = parentGetStaticPaths(); - - return Object.values(tankDefinitions.tanks) - .map((tank) => - parentPaths.map((parent) => ({ - params: { ...parent.params, id: tank.id }, - props: parent.props, - })), - ) - .flat(); -}) satisfies GetStaticPaths; - -const { id } = Astro.params; -const { locale } = Astro.props; -const tankDefinitions = await fetchTankDefinitions(); -const tank = tankDefinitions.tanks[id]; ---- - -<LegacyRedirect {locale} path={`tanks/${tank.slug}`} /> diff --git a/packages/website/src/pages/[...locale]/tools/tankopedia/index.astro b/packages/website/src/pages/[...locale]/tools/tankopedia/index.astro deleted file mode 100644 index ce837a8b7..000000000 --- a/packages/website/src/pages/[...locale]/tools/tankopedia/index.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; - -export { getStaticPaths } from "../../index.astro"; - -const { locale } = Astro.props; ---- - -<LegacyRedirect {locale} path="tanks" /> diff --git a/packages/website/src/pages/[...locale]/tools/tier-list/index.astro b/packages/website/src/pages/[...locale]/tools/tier-list/index.astro deleted file mode 100644 index 32f3c7180..000000000 --- a/packages/website/src/pages/[...locale]/tools/tier-list/index.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import LegacyRedirect from "../../../../layouts/LegacyRedirect.astro"; - -export { getStaticPaths } from "../../index.astro"; - -const { locale } = Astro.props; ---- - -<LegacyRedirect {locale} path="tier-list" /> diff --git a/packages/website/src/pages/api/index.astro b/packages/website/src/pages/api/index.astro deleted file mode 100644 index b6ceca726..000000000 --- a/packages/website/src/pages/api/index.astro +++ /dev/null @@ -1,7 +0,0 @@ ---- -import locales from "@blitzkit/i18n/locales.json"; -import { tools } from "../../constants/tools"; -import ToolLayout from "../../layouts/ToolLayout.astro"; ---- - -<ToolLayout locale={locales.default} tool={tools.api} /> diff --git a/packages/website/src/pages/debug.astro b/packages/website/src/pages/debug.astro deleted file mode 100644 index d287cd3ed..000000000 --- a/packages/website/src/pages/debug.astro +++ /dev/null @@ -1 +0,0 @@ -debug diff --git a/packages/website/src/pages/robots.txt.ts b/packages/website/src/pages/robots.txt.ts deleted file mode 100644 index e056aa4a8..000000000 --- a/packages/website/src/pages/robots.txt.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { APIContext } from 'astro'; - -const ALLOW_ROBOTS = import.meta.env.ALLOW_ROBOTS; - -export async function GET({ request }: APIContext) { - const flags: Record<string, string | null> = { - 'User-agent': '*', - Allow: ALLOW_ROBOTS ? '/' : null, - Disallow: ALLOW_ROBOTS ? null : '/', - - space: null, - Sitemap: `${new URL(request.url).origin}/sitemap-index.xml`, - }; - - return new Response( - Object.entries(flags) - .map(([key, value]) => (value === null ? '' : `${key}: ${value}`)) - .join('\n'), - ); -} diff --git a/packages/website/src/stores/app/constants.ts b/packages/website/src/stores/app/constants.ts deleted file mode 100644 index 97c26f664..000000000 --- a/packages/website/src/stores/app/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const CURRENT_POLICIES_AGREEMENT_INDEX = 0; diff --git a/packages/website/src/stores/app/index.ts b/packages/website/src/stores/app/index.ts deleted file mode 100644 index 6b15394f2..000000000 --- a/packages/website/src/stores/app/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Varuna } from "varuna"; - -export interface WargamingLogin { - token: string; - id: number; - expires: number; -} - -export interface App { - developerMode: boolean; - policiesAgreementIndex: number; - logins: { - wargaming?: WargamingLogin; - }; -} - -export const App = new Varuna<App>( - { - developerMode: false, - policiesAgreementIndex: -1, - logins: {}, - }, - "app-2" -); diff --git a/packages/website/src/stores/compareEphemeral.ts b/packages/website/src/stores/compareEphemeral.ts deleted file mode 100644 index 2c5a2a6e3..000000000 --- a/packages/website/src/stores/compareEphemeral.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Varuna } from "varuna"; -import type { DuelMember } from "./duel"; - -export interface CompareMember extends DuelMember { - key: string; -} - -export interface CompareEphemeral { - crewSkills: Record<string, number>; - equalize: boolean; - members: CompareMember[]; - sorting?: { - direction: "ascending" | "descending"; - by: number; - }; -} - -export const CompareEphemeral = new Varuna< - CompareEphemeral, - Record<string, number> ->((crewSkills) => ({ - crewSkills, - equalize: false, - members: [], -})); diff --git a/packages/website/src/stores/comparePersistent.ts b/packages/website/src/stores/comparePersistent.ts deleted file mode 100644 index 3f6b0db96..000000000 --- a/packages/website/src/stores/comparePersistent.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Varuna } from "varuna"; - -export type DeltaMode = "none" | "percentage" | "absolute"; - -export interface ComparePersistent { - deltaMode: DeltaMode; -} - -export const ComparePersistent = new Varuna<ComparePersistent>( - { deltaMode: "none" }, - "compare-2" -); diff --git a/packages/website/src/stores/duel/constants.ts b/packages/website/src/stores/duel/constants.ts deleted file mode 100644 index 40355db2e..000000000 --- a/packages/website/src/stores/duel/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { EquipmentMatrix } from '.'; - -export const genericDefaultEquipmentMatrix: EquipmentMatrix = [ - [-1, 1, -1], - [0, 0, 0], - [0, 0, 0], -]; diff --git a/packages/website/src/stores/duel/index.ts b/packages/website/src/stores/duel/index.ts deleted file mode 100644 index b8a904161..000000000 --- a/packages/website/src/stores/duel/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - ModelDefinition, - type EngineDefinition, - type GunDefinition, - type ShellDefinition, - type TankDefinition, - type TrackDefinition, - type TurretDefinition, -} from "@blitzkit/core"; -import { Varuna } from "varuna"; -import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions"; -import { tankToDuelMember } from "../../core/blitzkit/tankToDuelMember"; - -type EquipmentMatrixItem = -1 | 0 | 1; -type EquipmentMatrixRow = [ - EquipmentMatrixItem, - EquipmentMatrixItem, - EquipmentMatrixItem, -]; -export type EquipmentMatrix = [ - EquipmentMatrixRow, - EquipmentMatrixRow, - EquipmentMatrixRow, -]; - -export interface DuelMember { - tank: TankDefinition; - engine: EngineDefinition; - turret: TurretDefinition; - gun: GunDefinition; - shell: ShellDefinition; - track: TrackDefinition; - equipmentMatrix: EquipmentMatrix; - consumables: number[]; - provisions: number[]; - camouflage: boolean; - cooldownBooster: number; - assaultDistance: number; -} - -export interface DuelStore { - protagonist: DuelMember; - antagonist: DuelMember; - equalize: boolean; -} - -const provisionDefinitions = await awaitableProvisionDefinitions; - -export const Duel = new Varuna< - DuelStore, - { tank: TankDefinition; model: ModelDefinition } ->(({ tank, model }) => { - const protagonist = tankToDuelMember(tank, provisionDefinitions); - - return { protagonist, antagonist: protagonist, model, equalize: false }; -}); diff --git a/packages/website/src/stores/embedBreakdown.ts b/packages/website/src/stores/embedBreakdown.ts deleted file mode 100644 index ecdb13481..000000000 --- a/packages/website/src/stores/embedBreakdown.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type IndividualTankStats } from "@blitzkit/core"; -import { Varuna } from "varuna"; - -type EmbedBreakdown = Record<number, IndividualTankStats[]>; - -export const EmbedBreakdown = new Varuna<EmbedBreakdown>( - {}, - "embed-breakdown-2" -); diff --git a/packages/website/src/stores/embedState/constants.ts b/packages/website/src/stores/embedState/constants.ts deleted file mode 100644 index 95ea7b555..000000000 --- a/packages/website/src/stores/embedState/constants.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { EmbedConfigItem } from '.'; - -export enum EmbedItemType { - Radius, - Size, - SizeWithout0, - Color, - String, - RichText, - Boolean, - Slider, - Enum, -} - -export const radixGrays = [ - 'gray', - 'mauve', - 'slate', - 'sage', - 'olive', - 'sand', -] as const; - -export const radixTextWeights = ['light', 'regular', 'medium', 'bold'] as const; - -export type EmbedConfigItemType<Type extends EmbedItemType> = - EmbedConfigItem & { type: Type }; diff --git a/packages/website/src/stores/embedState/index.ts b/packages/website/src/stores/embedState/index.ts deleted file mode 100644 index 04abd855c..000000000 --- a/packages/website/src/stores/embedState/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as radixColors from "@radix-ui/colors"; -import { Varuna } from "varuna"; -import { - type EmbedConfigItemType, - EmbedItemType, - radixGrays, - radixTextWeights, -} from "./constants"; - -export type RadixSize = `${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`; -export type RadixSizeWithout0 = Exclude<RadixSize, "0">; -export type RadixRadius = `${1 | 2 | 3 | 4}` | "full"; -export type RadixTextWeight = (typeof radixTextWeights)[number]; -export type RadixColorCompound = { - base: RadixColor; - variant: RadixColorVariant; -}; -export type RadixTextColor = - | Exclude<RadixColor, RadixColorGrays> - | "gray" - | undefined; -export type RadixColorVariantRaw = `${ - | 1 - | 2 - | 3 - | 4 - | 5 - | 6 - | 7 - | 8 - | 9 - | 10 - | 11 - | 12}`; -export type RadixColorVariant = `${"" | "a"}${RadixColorVariantRaw}`; -export type RadixColor = Exclude< - keyof typeof radixColors, - `${string}${"A" | "P3" | "Dark"}` ->; -export type RadixColorGrays = (typeof radixGrays)[number]; - -export type ExtractEmbedConfigTypes<Config extends EmbedConfig> = { - [Key in keyof Config]: ({ - type: Config[Key]["type"]; - } & EmbedConfigItem)["default"]; -}; - -export type EmbedConfig = { - width: EmbedConfigItemType<EmbedItemType.Slider>; - height: EmbedConfigItemType<EmbedItemType.Slider>; -} & Record<string, EmbedConfigItem>; - -export type EmbedConfigItem = ( - | { - type: EmbedItemType.Slider; - default: number; - min: number; - max: number; - } - | { - type: EmbedItemType.Boolean; - default: boolean; - } - | { - type: EmbedItemType.String; - default: string; - } - | { - type: EmbedItemType.Radius; - default: RadixRadius; - } - | { - type: EmbedItemType.Size; - default: RadixSize; - } - | { - type: EmbedItemType.SizeWithout0; - default: RadixSizeWithout0; - } - | { - type: EmbedItemType.Color; - default: string; - } - | { - type: EmbedItemType.RichText; - default: { - color: string; - size: RadixSizeWithout0; - weight: RadixTextWeight; - }; - } - | { - type: EmbedItemType.Enum; - default: string; - options: { - value: string; - locales: Record<string, string>; - }[]; - } -) & { - pad?: boolean; -}; - -export type EmbedStateStore = Record<string, EmbedConfigItem["default"]>; - -export const EmbedState = new Varuna<EmbedStateStore, EmbedStateStore>( - (data) => data -); diff --git a/packages/website/src/stores/embedState/utilities.ts b/packages/website/src/stores/embedState/utilities.ts deleted file mode 100644 index adb92cb80..000000000 --- a/packages/website/src/stores/embedState/utilities.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { TextProps } from '@radix-ui/themes'; -import { EmbedState, type EmbedConfig, type ExtractEmbedConfigTypes } from '.'; -import type { EmbedConfigItemType, EmbedItemType } from './constants'; - -export function useEmbedStateCurry<Config extends EmbedConfig>() { - function useEmbedState<Key extends keyof Config>(key: Key) { - return EmbedState.use((state) => { - return (state as ExtractEmbedConfigTypes<Config>)[key]; - }); - } - - function useRichText< - Key extends { - [K in keyof Config]: Config[K] extends { type: EmbedItemType.RichText } - ? K - : never; - }[keyof Config], - >(key: Key) { - const { color, ...state } = useEmbedState( - key, - ) as EmbedConfigItemType<EmbedItemType.RichText>['default']; - - return { - style: { color }, - ...state, - } satisfies TextProps; - } - - return { useEmbedState, useRichText }; -} diff --git a/packages/website/src/stores/gallery.ts b/packages/website/src/stores/gallery.ts deleted file mode 100644 index 238431771..000000000 --- a/packages/website/src/stores/gallery.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Varuna } from "varuna"; - -interface Gallery { - search?: string; -} - -export const Gallery = new Varuna<Gallery>({}); diff --git a/packages/website/src/stores/guess.ts b/packages/website/src/stores/guess.ts deleted file mode 100644 index 0a3e94f49..000000000 --- a/packages/website/src/stores/guess.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { TankDefinition } from "@blitzkit/core"; -import { times } from "lodash-es"; -import { Varuna } from "varuna"; - -export enum GuessState { - Correct, - Incorrect, - NotGuessed, -} - -export interface Guess { - tank: TankDefinition; - guessState: GuessState; - totalGuesses: number; - correctGuesses: number; - streak: number; - helpingReveal: boolean; - tiers: number[]; -} - -export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({ - tank, - guessState: GuessState.NotGuessed, - totalGuesses: 0, - correctGuesses: 0, - streak: 0, - helpingReveal: false, - tiers: times(10, (index) => index + 1), -})); diff --git a/packages/website/src/stores/mixer.ts b/packages/website/src/stores/mixer.ts deleted file mode 100644 index 9669268c1..000000000 --- a/packages/website/src/stores/mixer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { - GunDefinition, - TankDefinition, - TurretDefinition, -} from "@blitzkit/core"; -import { Varuna } from "varuna"; - -export interface Mixer { - hull: TankDefinition; - turret: { - tank: TankDefinition; - turret: TurretDefinition; - }; - gun: { - tank: TankDefinition; - turret: TurretDefinition; - gun: GunDefinition; - }; -} - -export const Mixer = new Varuna<Mixer, Mixer>((data) => data); diff --git a/packages/website/src/stores/performance.ts b/packages/website/src/stores/performance.ts deleted file mode 100644 index ef1f81841..000000000 --- a/packages/website/src/stores/performance.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Samples } from "@blitzkit/core"; -import { Varuna } from "varuna"; - -export interface Performance { - playerCountPeriod: PlayerCountPeriod; -} - -export type PlayerCountPeriod = keyof Samples; - -export const Performance = new Varuna<Performance>({ - playerCountPeriod: "total", -}); diff --git a/packages/website/src/stores/playlist.ts b/packages/website/src/stores/playlist.ts deleted file mode 100644 index f2f2ba132..000000000 --- a/packages/website/src/stores/playlist.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Varuna } from "varuna"; - -export interface PlaylistEntry { - id: number; - then?: { battles: number; wins: number; last: number }; - now?: { battles: number; wins: number; last: number }; - checked?: boolean; -} - -export interface Playlist { - list?: PlaylistEntry[]; -} - -export const Playlist = new Varuna<Playlist>({}, "playlist"); diff --git a/packages/website/src/stores/session.ts b/packages/website/src/stores/session.ts deleted file mode 100644 index f51ced8f7..000000000 --- a/packages/website/src/stores/session.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { - CompositeStatsKey, - IndividualTankStats, - Region, -} from "@blitzkit/core"; -import { Varuna } from "varuna"; - -interface SessionBase { - columns: CompositeStatsKey[]; -} - -export interface SessionTracking extends SessionBase { - tracking: true; - player: { - id: number; - region: Region; - since: number; - stats: IndividualTankStats[]; - }; -} - -interface SessionNotTracking extends SessionBase { - tracking: false; -} - -type Session = SessionTracking | SessionNotTracking; - -export const Session = new Varuna<Session>( - { - columns: [ - "cumulative_battles", - "normalized_wins", - "cumulative_wn8", - "normalized_damage_dealt", - ], - tracking: false, - }, - "session-4" -); diff --git a/packages/website/src/stores/tankFilters.ts b/packages/website/src/stores/tankFilters.ts deleted file mode 100644 index 7d1c1dcdb..000000000 --- a/packages/website/src/stores/tankFilters.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { - GunDefinition, - ShellType, - TankClass, - TankType, -} from "@blitzkit/core"; -import { Varuna } from "varuna"; - -export type CaseType<T> = T extends { - gun_type?: { $case: infer U; value: any }; -} - ? U - : never; - -export interface TankFilters { - tiers: number[]; - nations: string[]; - classes: TankClass[]; - types: TankType[]; - search: string | null; - searching: boolean; - gunType: CaseType<GunDefinition>[]; - shells: [ShellType | null, ShellType | null, ShellType | null]; - consumables: number[]; - provisions: number[]; - abilities: number[]; - powers: number[]; - - showTesting: boolean; - showNonTesting: boolean; - showOwned: boolean; - showUnowned: boolean; -} - -export const TankFilters = new Varuna<TankFilters>({ - tiers: [], - nations: [], - classes: [], - types: [], - search: null, - searching: false, - gunType: [], - shells: [null, null, null], - consumables: [], - provisions: [], - abilities: [], - powers: [], - - showTesting: true, - showNonTesting: true, - showOwned: true, - showUnowned: true, -}); diff --git a/packages/website/src/stores/tankPerformanceSort/constants.ts b/packages/website/src/stores/tankPerformanceSort/constants.ts deleted file mode 100644 index 273d6bff4..000000000 --- a/packages/website/src/stores/tankPerformanceSort/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -import en from '@blitzkit/i18n/strings/en.json'; -import type { TankPerformanceSortType } from '.'; - -export const TankPerformanceSortTypeNamesArray = Object.keys( - en.website.tools.performance.table.stats, -) as TankPerformanceSortType[]; diff --git a/packages/website/src/stores/tankPerformanceSort/index.ts b/packages/website/src/stores/tankPerformanceSort/index.ts deleted file mode 100644 index 3e9006d06..000000000 --- a/packages/website/src/stores/tankPerformanceSort/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type en from "@blitzkit/i18n/strings/en.json"; -import { Varuna } from "varuna"; - -export type TankPerformanceSortType = - keyof typeof en.website.tools.performance.table.stats; - -export interface TankPerformanceSort { - type: TankPerformanceSortType; - direction: -1 | 1; -} - -export const TankPerformanceSort = new Varuna<TankPerformanceSort>({ - type: "winrate", - direction: -1, -}); diff --git a/packages/website/src/stores/tankopedia.ts b/packages/website/src/stores/tankopedia.ts deleted file mode 100644 index d5f54b26c..000000000 --- a/packages/website/src/stores/tankopedia.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { - createDefaultSkills, - ShellDefinition, - type ModelDefinition, -} from "@blitzkit/core"; -import type { Vector3 } from "three"; -import { Varuna } from "varuna"; -import type { ArmorType } from "../components/Armor/components/SpacedArmorScene"; -import type { ExternalModuleVariant } from "../components/Armor/components/SpacedArmorSceneComponent"; -import type { XP_MULTIPLIERS } from "../components/Tankopedia/TechTreeSection"; -import { awaitableSkillDefinitions } from "../core/awaitables/skillDefinitions"; -import { TankopediaDisplay } from "./tankopediaPersistent/constants"; - -export interface ShotLayerBase { - index: number; - thickness: number; - point: Vector3; - surfaceNormal: Vector3; - status: "blocked" | "penetration" | "ricochet"; -} - -interface ShotLayerExternal extends ShotLayerBase { - type: ArmorType.External; - variant: ExternalModuleVariant; -} - -export interface ShotLayerNonExternal extends ShotLayerBase { - type: Exclude<ArmorType, ArmorType.External>; - angle: number; - thicknessAngled: number; -} - -export interface ShotLayerGap { - type: null; - status: "penetration" | "blocked"; - distance: number; -} - -export type ShotLayer = ShotLayerExternal | ShotLayerNonExternal | ShotLayerGap; - -export type Shot = { - containsGaps: boolean; - damage: number; - splashRadius?: number; - - in: { - surfaceNormal: Vector3; - status: ShotStatus; - layers: ShotLayer[]; - }; - out?: { - surfaceNormal: Vector3; - status: ShotStatus; - layers: ShotLayer[]; - }; -}; - -export type ShotStatus = "penetration" | "blocked" | "ricochet" | "splash"; - -export type ArmorPiercingLayer = - | { - distance: number; - nominal: number; - angled: number; - ricochet: boolean; - type: "core" | "spaced" | "external"; - } - | { - type: "gap"; - gap: number; - }; - -export enum TankopediaRelativeAgainst { - Class, - Tier, - All, -} - -interface Tankopedia { - disturbed: boolean; - revealed: boolean; - shot?: Shot; - skills: Record<string, number>; - relativeAgainst: TankopediaRelativeAgainst; - model: ModelDefinition; - editStatic: boolean; - highlightArmor?: { - editingPlate: boolean; - name: string; - point: Vector3; - thickness: number; - color: string; - } & ( - | { - type: ArmorType.Primary | ArmorType.Spaced; - thicknessAngled: number; - angle: number; - } - | { type: ArmorType.External } - ); - xpMultiplier: (typeof XP_MULTIPLIERS)[number]; - customShell?: ShellDefinition; - requestedDisplay: TankopediaDisplay; - display: TankopediaDisplay; - statSearch?: string; -} - -const skillDefinitions = await awaitableSkillDefinitions; - -export const Tankopedia = new Varuna<Tankopedia, ModelDefinition>((model) => ({ - disturbed: false, - revealed: false, - relativeAgainst: TankopediaRelativeAgainst.Class, - editStatic: false, - skills: createDefaultSkills(skillDefinitions), - model, - xpMultiplier: 1, - requestedDisplay: TankopediaDisplay.Model, - display: TankopediaDisplay.Model, -})); diff --git a/packages/website/src/stores/tankopediaPersistent/constants.ts b/packages/website/src/stores/tankopediaPersistent/constants.ts deleted file mode 100644 index 9ec03a40a..000000000 --- a/packages/website/src/stores/tankopediaPersistent/constants.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { TankopediaSortBy } from '.'; - -export const SORT_UNITS: Record<TankopediaSortBy, string | undefined> = { - 'meta.none': undefined, - 'survivability.health': 'hp', - 'fire.aimTime': 's', - 'fire.caliber': 'mm', - 'fire.damage': 'hp', - 'fire.moduleDamage': 'hp', - 'fire.dispersionMoving': 'm', - 'fire.dispersionStill': 'm', - 'fire.dpm': undefined, - 'fire.dpmPremium': undefined, - 'fire.gunDepression': '°', - 'fire.gunElevation': '°', - 'fire.premiumPenetration': 'mm', - 'fire.tertiaryPenetration': 'mm', - 'fire.reload': 's', - 'fire.shellVelocity': 'm/s', - 'fire.shellRange': 'm', - 'fire.shellCapacity': undefined, - 'fire.standardPenetration': 'mm', - 'maneuverability.backwardsSpeed': 'kph', - 'maneuverability.forwardsSpeed': 'kph', - 'maneuverability.power': 'hp', - 'maneuverability.powerToWeight': 'hp/tn', - 'maneuverability.traverseSpeed': '°/s', - 'maneuverability.weight': 'tn', - 'survivability.camouflageMoving': '%', - 'survivability.camouflageShooting': '%', - 'survivability.camouflageStill': '%', - 'survivability.length': 'm', - 'survivability.viewRange': 'm', - 'survivability.volume': 'm^3', -}; - -export enum TankopediaDisplay { - Model, - DynamicArmor, - StaticArmor, -} diff --git a/packages/website/src/stores/tankopediaPersistent/index.ts b/packages/website/src/stores/tankopediaPersistent/index.ts deleted file mode 100644 index ced9cf380..000000000 --- a/packages/website/src/stores/tankopediaPersistent/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type en from "@blitzkit/i18n/strings/en.json"; -import { Varuna } from "varuna"; - -export type TankopediaSortBy = keyof typeof en.website.common.tank_search.sort; -export type TankopediaSortDirection = "ascending" | "descending"; -export type TankopediaTestTankDisplay = "include" | "exclude" | "only"; - -export interface TankopediaPersistent { - wireframe: boolean; - opaque: boolean; - advancedHighlighting: boolean; - greenPenetration: boolean; - showSpacedArmor: boolean; - showExternalModules: boolean; - showPrimaryArmor: boolean; - recentlyViewed: number[]; - hideTankModelUnderArmor: boolean; - highGraphics: boolean; - - sort: { - by: TankopediaSortBy; - direction: TankopediaSortDirection; - }; -} - -export const TankopediaPersistent = new Varuna<TankopediaPersistent>( - { - hideTankModelUnderArmor: false, - wireframe: false, - opaque: false, - advancedHighlighting: false, - greenPenetration: false, - showSpacedArmor: true, - showExternalModules: true, - showPrimaryArmor: true, - recentlyViewed: [], - highGraphics: true, - sort: { - by: "meta.none", - direction: "ascending", - }, - }, - "tankopedia-2" -); diff --git a/packages/website/src/stores/tankopediaSort.ts b/packages/website/src/stores/tankopediaSort.ts deleted file mode 100644 index 45e3b169a..000000000 --- a/packages/website/src/stores/tankopediaSort.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Varuna } from 'varuna'; -import { - type TankopediaSortBy, - type TankopediaSortDirection, -} from './tankopediaPersistent'; - -export interface TankSort { - by: TankopediaSortBy; - direction: TankopediaSortDirection; -} - -export const TankSort = new Varuna<TankSort>({ - by: 'meta.none', - direction: 'descending', -}); diff --git a/packages/website/src/stores/tierList.ts b/packages/website/src/stores/tierList.ts deleted file mode 100644 index c82d009c4..000000000 --- a/packages/website/src/stores/tierList.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { enableMapSet } from "immer"; -import { Varuna } from "varuna"; -import { tierListRows } from "../components/TierList/Table/constants"; - -export interface TierList { - dragging: boolean; - rows: { name: string; tanks: number[] }[]; - placedTanks: Set<number>; -} - -export const TierList = new Varuna<TierList>({ - dragging: false, - rows: tierListRows.map((row) => ({ name: row.name, tanks: [] })), - placedTanks: new Set(), -}); - -enableMapSet(); diff --git a/packages/website/src/types/glsl.d.ts b/packages/website/src/types/glsl.d.ts deleted file mode 100644 index 77ffce2fc..000000000 --- a/packages/website/src/types/glsl.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.glsl' { - const value: string; - export default value; -} diff --git a/packages/website/src/types/maybeSkeletonComponentProps.ts b/packages/website/src/types/maybeSkeletonComponentProps.ts deleted file mode 100644 index 381d72dfa..000000000 --- a/packages/website/src/types/maybeSkeletonComponentProps.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type MaybeSkeletonComponentProps<T = {}> = - | { - skeleton: true; - onIntersection?: () => void; - } - | (T & { - skeleton?: false; - }); diff --git a/packages/website/src/types/tankGuide.ts b/packages/website/src/types/tankGuide.ts deleted file mode 100644 index a8b8f18db..000000000 --- a/packages/website/src/types/tankGuide.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface TankGuide { - url: string; - description?: string; - image?: string; -} diff --git a/packages/website/src/types/urlon.d.ts b/packages/website/src/types/urlon.d.ts deleted file mode 100644 index af21bdc51..000000000 --- a/packages/website/src/types/urlon.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module 'urlon' { - function stringify(value: any): string; - function parse(value: string): any; - export { parse, stringify }; -} diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json deleted file mode 100644 index a03ae083f..000000000 --- a/packages/website/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["../../tsconfig.json", "astro/tsconfigs/base"] -}