diff --git a/.github/assets/logo-gui.png b/.github/assets/logo-gui.png index 19649f894..c292af611 100644 Binary files a/.github/assets/logo-gui.png and b/.github/assets/logo-gui.png differ diff --git a/.github/assets/logo.png b/.github/assets/logo.png index 23ab4d1d2..c292af611 100644 Binary files a/.github/assets/logo.png and b/.github/assets/logo.png differ diff --git a/.github/assets/screenshots/about.png b/.github/assets/screenshots/about.png index 1a845c221..8176527bd 100644 Binary files a/.github/assets/screenshots/about.png and b/.github/assets/screenshots/about.png differ diff --git a/.github/assets/screenshots/logs.png b/.github/assets/screenshots/logs.png index ed34adf8b..8adab90ce 100644 Binary files a/.github/assets/screenshots/logs.png and b/.github/assets/screenshots/logs.png differ diff --git a/.github/assets/screenshots/main.png b/.github/assets/screenshots/main.png index 15c89f2d7..ca7257008 100644 Binary files a/.github/assets/screenshots/main.png and b/.github/assets/screenshots/main.png differ diff --git a/.github/assets/screenshots/themes.png b/.github/assets/screenshots/themes.png deleted file mode 100644 index 2fdf75773..000000000 Binary files a/.github/assets/screenshots/themes.png and /dev/null differ diff --git a/.github/workflows/release_cli.yml b/.github/workflows/release_cli.yml index 586b1e133..e9228903b 100644 --- a/.github/workflows/release_cli.yml +++ b/.github/workflows/release_cli.yml @@ -36,7 +36,7 @@ jobs: - name: Get Version id: get_version - run: echo "version=$(cargo run -p owmods_cli --release --bin=owmods version)" >> $GITHUB_OUTPUT + run: echo "version=$(cargo run -q -p owmods_cli --release --bin=owmods version)" >> $GITHUB_OUTPUT - name: Generate Dist Folder if: matrix.platform == 'ubuntu-20.04' diff --git a/.gitignore b/.gitignore index c1f992e8c..eb1dd61cb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target dist/ scripts/ owmods.tar.zst +owmods_gui/frontend/stats.html # Logs *.log diff --git a/Cargo.lock b/Cargo.lock index f5a52f28c..f8b929cec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -41,6 +41,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -67,15 +73,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -124,7 +130,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -148,23 +154,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.1.0", -] - -[[package]] -name = "attohttpc" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7" -dependencies = [ - "flate2", - "http", - "log", - "native-tls", - "serde", - "serde_json", - "serde_urlencoded", - "url", + "system-deps 6.1.1", ] [[package]] @@ -192,9 +182,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bincode" @@ -213,9 +203,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded" [[package]] name = "block" @@ -255,9 +245,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" dependencies = [ "memchr", "once_cell", @@ -267,9 +257,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" @@ -288,6 +278,9 @@ name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "cairo-rs" @@ -310,17 +303,17 @@ checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" dependencies = [ "glib-sys", "libc", - "system-deps 6.1.0", + "system-deps 6.1.1", ] [[package]] name = "cargo_toml" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f83bc2e401ed041b7057345ebc488c005efa0341d5541ce7004d30458d0090b" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" dependencies = [ "serde", - "toml 0.7.3", + "toml 0.7.5", ] [[package]] @@ -360,9 +353,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.1" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8790cf1286da485c72cf5fc7aeba308438800036ec67d89425924c4807268c9" +checksum = "215c0072ecc28f92eeb0eea38ba63ddfcb65c2828c46311d646f1a3ff5f9841c" dependencies = [ "smallvec", "target-lexicon", @@ -376,13 +369,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", "wasm-bindgen", @@ -391,9 +384,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.0" +version = "4.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +checksum = "d9394150f5b4273a1763355bd1c2ec54cc5a2593f790587bcd6b2c947cfa9211" dependencies = [ "clap_builder", "clap_derive", @@ -402,9 +395,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.0" +version = "4.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +checksum = "9a78fbdd3cc2914ddf37ba444114bc7765bbdcb55ec9cbe6fa054f0137400717" dependencies = [ "anstream", "anstyle", @@ -415,23 +408,23 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.3.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04ddfaacc3bc9e6ea67d024575fafc2a813027cf374b8f24f7bc233c6b6be12" +checksum = "7f6b5c519bab3ea61843a7923d074b04245624bb84a64a8c150f5deb014e388b" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.3.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -442,9 +435,9 @@ checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "clap_mangen" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bcbd911d903a985de775aabe3bcef9b88e2a7d943e36aa8691617012d2b7731" +checksum = "8f2e32b579dae093c2424a8b7e2bea09c89da01e1ce5065eb2f0a6f1cc15cc1f" dependencies = [ "clap", "roff", @@ -516,9 +509,9 @@ dependencies = [ [[package]] name = "console" -version = "0.15.6" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0525278dce688103060006713371cedbad27186c7d913f33d866b498da0f595" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", @@ -564,21 +557,20 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" dependencies = [ "bitflags 1.3.2", "core-foundation", - "foreign-types", "libc", ] [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" dependencies = [ "libc", ] @@ -604,9 +596,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -640,12 +632,12 @@ dependencies = [ [[package]] name = "cssparser-macros" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -658,12 +650,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" - [[package]] name = "darling" version = "0.20.1" @@ -685,7 +671,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -696,7 +682,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -781,15 +767,15 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dtoa" -version = "0.4.8" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" [[package]] name = "dtoa-short" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" dependencies = [ "dtoa", ] @@ -808,7 +794,7 @@ checksum = "80663502655af01a2902dff3f06869330782267924bf1788410b74edcd93770a" dependencies = [ "cc", "rustc_version", - "toml 0.7.3", + "toml 0.7.5", "vswhom", "winreg 0.11.0", ] @@ -834,6 +820,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + [[package]] name = "errno" version = "0.3.1" @@ -875,9 +867,9 @@ dependencies = [ [[package]] name = "field-offset" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf3a800ff6e860c863ca6d4b16fd999db8b752819c1606884047b73e468535" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", "rustc_version", @@ -928,22 +920,13 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "futf" version = "0.1.5" @@ -1010,7 +993,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -1091,7 +1074,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.1.0", + "system-deps 6.1.1", ] [[package]] @@ -1108,7 +1091,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.1.0", + "system-deps 6.1.1", ] [[package]] @@ -1122,7 +1105,7 @@ dependencies = [ "gobject-sys", "libc", "pkg-config", - "system-deps 6.1.0", + "system-deps 6.1.1", ] [[package]] @@ -1134,7 +1117,7 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps 6.1.0", + "system-deps 6.1.1", "x11", ] @@ -1174,9 +1157,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -1209,7 +1192,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.1.0", + "system-deps 6.1.1", "winapi", ] @@ -1255,7 +1238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" dependencies = [ "libc", - "system-deps 6.1.0", + "system-deps 6.1.1", ] [[package]] @@ -1285,7 +1268,7 @@ checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" dependencies = [ "glib-sys", "libc", - "system-deps 6.1.0", + "system-deps 6.1.1", ] [[package]] @@ -1326,7 +1309,7 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.1.0", + "system-deps 6.1.1", ] [[package]] @@ -1355,7 +1338,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -1368,6 +1351,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.3.3" @@ -1504,11 +1493,24 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1545,9 +1547,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1555,11 +1557,10 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "crossbeam-utils", "globset", "lazy_static", "log", @@ -1591,19 +1592,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "indicatif" -version = "0.17.3" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" dependencies = [ "console", + "instant", "number_prefix", - "portable-atomic 0.3.20", + "portable-atomic", "unicode-segmentation", "unicode-width", ] @@ -1661,9 +1673,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", @@ -1754,9 +1766,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1813,9 +1825,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "line-wrap" @@ -1834,9 +1846,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1844,11 +1856,10 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ - "cfg-if", "serde", ] @@ -1919,9 +1930,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -1950,14 +1961,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2029,34 +2040,20 @@ dependencies = [ [[package]] name = "notify" -version = "5.2.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51" dependencies = [ "bitflags 1.3.2", - "crossbeam-channel", "filetime", - "fsevent-sys", "inotify", "kqueue", "libc", "mio", - "serde", "walkdir", "windows-sys 0.45.0", ] -[[package]] -name = "notify-debouncer-mini" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e23e9fa24f094b143c1eb61f90ac6457de87be6987bc70746e0179f7dbc9007b" -dependencies = [ - "crossbeam-channel", - "notify", - "serde", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2128,15 +2125,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - [[package]] name = "number_prefix" version = "0.4.0" @@ -2166,9 +2154,9 @@ dependencies = [ [[package]] name = "objc-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da30ff0a93f6801dc6c4f384ebe70146a40aac8ebf34e4e8ff1f528e859318a3" +checksum = "99e1d07c6eab1ce8b6382b8e3c7246fe117ff3f8b34be065f5ebace6749fe845" [[package]] name = "objc2" @@ -2206,9 +2194,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "open" @@ -2233,9 +2221,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.52" +version = "0.10.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -2254,7 +2242,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -2265,9 +2253,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.87" +version = "0.9.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" dependencies = [ "cc", "libc", @@ -2300,7 +2288,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owmods_cli" -version = "0.6.1" +version = "0.7.0" dependencies = [ "anyhow", "clap", @@ -2315,7 +2303,7 @@ dependencies = [ [[package]] name = "owmods_core" -version = "0.6.1" +version = "0.7.0" dependencies = [ "anyhow", "directories", @@ -2340,10 +2328,12 @@ dependencies = [ [[package]] name = "owmods_gui" -version = "0.6.1" +version = "0.7.0" dependencies = [ "anyhow", "log", + "notify", + "opener", "owmods_core", "regex", "serde", @@ -2351,7 +2341,6 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-deep-link", - "tauri-plugin-fs-watch", "tauri-plugin-window-state", "time", "tokio", @@ -2380,7 +2369,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.1.0", + "system-deps 6.1.1", ] [[package]] @@ -2395,15 +2384,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.0", ] [[package]] @@ -2414,9 +2403,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "phf" @@ -2540,8 +2529,8 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" dependencies = [ - "base64 0.21.0", - "indexmap", + "base64 0.21.2", + "indexmap 1.9.3", "line-wrap", "quick-xml", "serde", @@ -2550,9 +2539,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" +checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -2563,18 +2552,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e" -dependencies = [ - "portable-atomic 1.3.2", -] - -[[package]] -name = "portable-atomic" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc59d1bcc64fc5d021d67521f818db868368028108d37f0e98d74e33f68297b5" +checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "ppv-lite86" @@ -2630,9 +2610,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -2648,9 +2628,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -2715,7 +2695,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", ] [[package]] @@ -2738,12 +2718,9 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" -dependencies = [ - "cty", -] +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "redox_syscall" @@ -2769,20 +2746,20 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.8.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ - "aho-corasick 1.0.1", + "aho-corasick 1.0.2", "memchr", - "regex-syntax 0.7.1", + "regex-syntax 0.7.2", ] [[package]] @@ -2802,9 +2779,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" @@ -2812,7 +2789,7 @@ version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -2822,10 +2799,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -2835,11 +2814,14 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", "winreg 0.10.1", @@ -2907,9 +2889,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags 1.3.2", "errno", @@ -2921,9 +2903,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" dependencies = [ "log", "ring", @@ -2937,7 +2919,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", ] [[package]] @@ -3062,29 +3044,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" dependencies = [ "itoa 1.0.6", "ryu", @@ -3099,14 +3081,14 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] name = "serde_spanned" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] @@ -3125,14 +3107,14 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.3.3" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" dependencies = [ - "base64 0.13.1", + "base64 0.21.2", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", "serde", "serde_json", "serde_with_macros", @@ -3141,14 +3123,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "2.3.3" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -3185,9 +3167,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -3343,15 +3325,28 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sys-locale" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee" +dependencies = [ + "js-sys", + "libc", + "wasm-bindgen", + "web-sys", + "windows-sys 0.45.0", +] + [[package]] name = "system-deps" version = "5.0.0" @@ -3367,22 +3362,22 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.1.0" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2" +checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" dependencies = [ - "cfg-expr 0.15.1", + "cfg-expr 0.15.3", "heck 0.4.1", "pkg-config", - "toml 0.7.3", + "toml 0.7.5", "version-compare 0.1.1", ] [[package]] name = "tao" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd3cde9c0cd2b872616bba26b818e0d6469330196869d7e5000dba96ce9431df" +checksum = "6a6d198e01085564cea63e976ad1566c1ba2c2e4cc79578e35d9f05521505e31" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -3449,19 +3444,19 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" +checksum = "1b1c7f239eb94671427157bd93b3694320f3668d4e1eff08c7285366fd777fac" [[package]] name = "tauri" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d42ba3a2e8556722f31336a0750c10dbb6a81396a1c452977f515da83f69f842" +checksum = "7fbe522898e35407a8e60dc3870f7579fea2fc262a6a6072eccdd37ae1e1d91e" dependencies = [ "anyhow", - "attohttpc", - "base64 0.21.0", + "base64 0.21.2", + "bytes", "cocoa", "dirs-next", "embed_plist", @@ -3483,6 +3478,7 @@ dependencies = [ "rand 0.8.5", "raw-window-handle", "regex", + "reqwest", "rfd", "semver", "serde", @@ -3490,6 +3486,7 @@ dependencies = [ "serde_repr", "serialize-to-javascript", "state", + "sys-locale", "tar", "tauri-macros", "tauri-runtime", @@ -3509,9 +3506,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929b3bd1248afc07b63e33a6a53c3f82c32d0b0a5e216e4530e94c467e019389" +checksum = "7d2edd6a259b5591c8efdeb9d5702cb53515b82a6affebd55c7fd6d3a27b7d1b" dependencies = [ "anyhow", "cargo_toml", @@ -3522,16 +3519,15 @@ dependencies = [ "serde_json", "tauri-utils", "tauri-winres", - "winnow", ] [[package]] name = "tauri-codegen" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a2105f807c6f50b2fa2ce5abd62ef207bc6f14c9fcc6b8caec437f6fb13bde" +checksum = "54ad2d49fdeab4a08717f5b49a163bdc72efc3b1950b6758245fcde79b645e1a" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "brotli", "ico", "json-patch", @@ -3553,9 +3549,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8784cfe6f5444097e93c69107d1ac5e8f13d02850efa8d8f2a40fe79674cef46" +checksum = "8eb12a2454e747896929338d93b0642144bb51e0dddbb36e579035731f0d76b7" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -3581,27 +3577,13 @@ dependencies = [ "winreg 0.50.0", ] -[[package]] -name = "tauri-plugin-fs-watch" -version = "0.0.0" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=dev#dce0f02bc571128308c30278cde3233f341e6a50" -dependencies = [ - "log", - "notify", - "notify-debouncer-mini", - "serde", - "serde_json", - "tauri", - "thiserror", -] - [[package]] name = "tauri-plugin-window-state" version = "0.1.0" source = "git+https://github.com/tauri-apps/plugins-workspace?branch=dev#dce0f02bc571128308c30278cde3233f341e6a50" dependencies = [ "bincode", - "bitflags 2.3.1", + "bitflags 2.3.2", "log", "serde", "serde_json", @@ -3611,9 +3593,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b80ea3fcd5fefb60739a3b577b277e8fc30434538a2f5bba82ad7d4368c422" +checksum = "108683199cb18f96d2d4134187bb789964143c845d2d154848dda209191fd769" dependencies = [ "gtk", "http", @@ -3632,9 +3614,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c396950b1ba06aee1b4ffe6c7cd305ff433ca0e30acbc5fa1a2f92a4ce70f1" +checksum = "0b7aa256a1407a3a091b5d843eccc1a5042289baf0a43d1179d9f0fcfea37c1b" dependencies = [ "cocoa", "gtk", @@ -3652,12 +3634,13 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6f9c2dafef5cbcf52926af57ce9561bd33bb41d7394f8bb849c0330260d864" +checksum = "03fc02bb6072bb397e1d473c6f76c953cda48b4a2d0cce605df284aa74a12e84" dependencies = [ "brotli", "ctor", + "dunce", "glob", "heck 0.4.1", "html5ever", @@ -3685,20 +3668,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" dependencies = [ "embed-resource", - "toml 0.7.3", + "toml 0.7.5", ] [[package]] name = "tempfile" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3735,7 +3719,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -3750,22 +3734,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.15" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ "itoa 1.0.6", - "libc", - "num_threads", "serde", + "time-core", "time-macros", ] +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + [[package]] name = "time-macros" -version = "0.2.4" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] [[package]] name = "tinyvec" @@ -3790,9 +3782,9 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" [[package]] name = "tokio" -version = "1.28.1" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", @@ -3814,14 +3806,24 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] name = "tokio-rustls" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", @@ -3876,9 +3878,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240" dependencies = [ "serde", "serde_spanned", @@ -3888,20 +3890,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" dependencies = [ - "indexmap", + "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", @@ -3928,13 +3930,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -4027,9 +4029,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -4060,9 +4062,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -4084,11 +4086,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.3.3" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "rand 0.8.5", ] @@ -4154,11 +4156,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -4176,9 +4177,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4186,24 +4187,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -4213,9 +4214,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4223,28 +4224,41 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasm-streams" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -4294,7 +4308,7 @@ dependencies = [ "pango-sys", "pkg-config", "soup2-sys", - "system-deps 6.1.0", + "system-deps 6.1.1", ] [[package]] @@ -4662,9 +4676,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.1" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" dependencies = [ "memchr", ] @@ -4768,7 +4782,7 @@ dependencies = [ [[package]] name = "xtask" -version = "0.6.1" +version = "0.7.0" dependencies = [ "anyhow", "clap", diff --git a/owmods_cli/Cargo.toml b/owmods_cli/Cargo.toml index cab9293fc..bcc2e1082 100644 --- a/owmods_cli/Cargo.toml +++ b/owmods_cli/Cargo.toml @@ -2,7 +2,7 @@ name = "owmods_cli" authors = ["Bwc9876 "] description = "A CLI Tool To Manage OWML Mods" -version = "0.6.1" +version = "0.7.0" edition = "2021" readme = "./README.md" repository = "https://github.com/Bwc9876/ow-mod-man/" @@ -12,7 +12,7 @@ license = "GPL-3.0-or-later" depends = "libssl1.1" [dependencies] -owmods_core = { version = "0.6.1", path = "../owmods_core"} +owmods_core = { version = "0.7.0", path = "../owmods_core"} clap = { version = "4.1.1", features = ["derive"] } colored = "2.0.0" anyhow = "1.0.68" diff --git a/owmods_cli/src/cli.rs b/owmods_cli/src/cli.rs index eea543363..b0135fc15 100644 --- a/owmods_cli/src/cli.rs +++ b/owmods_cli/src/cli.rs @@ -24,7 +24,15 @@ pub enum Commands { #[command(about = "Print Version")] Version, #[command(about = "Install/Update OWML (default installs to %APPDATA%/ow-mod-man/OWML)")] - Setup { owml_path: Option }, + Setup { + owml_path: Option, + #[arg( + help = "Use the prerelease version of OWML to setup (if there is one)", + short = 'p', + long = "prerelease" + )] + prerelease: bool, + }, #[command( about = "View the current database alert (if there is one)", alias = "alerts" diff --git a/owmods_cli/src/main.rs b/owmods_cli/src/main.rs index 97c629159..b3b11b7d1 100644 --- a/owmods_cli/src/main.rs +++ b/owmods_cli/src/main.rs @@ -36,7 +36,10 @@ async fn run_from_cli(cli: BaseCli) -> Result<()> { let ran_setup = matches!( &cli.command, - Commands::Setup { owml_path: _ } | Commands::Version + Commands::Setup { + owml_path: _, + prerelease: _ + } | Commands::Version ); if !config.check_owml() && !ran_setup { @@ -52,7 +55,10 @@ async fn run_from_cli(cli: BaseCli) -> Result<()> { Commands::Version => { info!(env!("CARGO_PKG_VERSION")); } - Commands::Setup { owml_path } => { + Commands::Setup { + owml_path, + prerelease, + } => { if let Some(owml_path) = owml_path { let mut new_config = config.clone(); new_config.owml_path = owml_path.to_str().unwrap().to_string(); @@ -73,7 +79,7 @@ async fn run_from_cli(cli: BaseCli) -> Result<()> { let owml = db .get_owml() .ok_or_else(|| anyhow!("OWML not found, is the database URL correct?"))?; - download_and_install_owml(&config, owml).await?; + download_and_install_owml(&config, owml, *prerelease).await?; info!("Done! Happy Modding!"); } } @@ -88,6 +94,13 @@ async fn run_from_cli(cli: BaseCli) -> Result<()> { .to_ascii_uppercase(), alert.message.unwrap_or_else(|| "No message".to_string()) ); + if let Some(url) = alert.url { + info!( + "{}: {}", + alert.url_label.unwrap_or(String::from("More Info")), + url + ); + } } else { info!("No alert"); }; diff --git a/owmods_core/Cargo.toml b/owmods_core/Cargo.toml index 543716008..40359af2c 100644 --- a/owmods_core/Cargo.toml +++ b/owmods_core/Cargo.toml @@ -2,7 +2,7 @@ name = "owmods_core" authors = ["Bwc9876 "] description = "The core library for the Outer Wilds Mod Manager" -version = "0.6.1" +version = "0.7.0" edition = "2021" readme = "./README.md" license = "GPL-3.0-or-later" diff --git a/owmods_core/src/alerts.rs b/owmods_core/src/alerts.rs index dfcd04fd3..4087318a5 100644 --- a/owmods_core/src/alerts.rs +++ b/owmods_core/src/alerts.rs @@ -8,10 +8,18 @@ use crate::mods::local::{LocalMod, ModWarning}; /// Represents an alert gotten from the database. #[typeshare] #[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct Alert { + /// Whether the alert should be shown pub enabled: bool, + /// The severity for the alert, should be `info`, `warning`, or `error` pub severity: Option, + /// The message for the alert pub message: Option, + /// Displays a link or button in the cli and gui respectively. **Note this is limited to GitHub, Discord, and the Mods Website** + pub url: Option, + /// Optional label to display for the link instead of "More Info" + pub url_label: Option, } /// Fetch an alert from the given url. @@ -22,12 +30,28 @@ pub struct Alert { /// /// ## Errors /// -/// Any errors that can happen when fetching json (HTTP errors, Deserialization errors, etc). +/// Any errors that can happen when fetching json (Networking errors, Deserialization errors). +/// +/// +/// It should be noted this will **NOT** error if we get a 404 or other HTTP error code, +/// and instead will return a disabled alert. /// pub async fn fetch_alert(url: &str) -> Result { - debug!("Fetching {}", url); - let alert: Alert = reqwest::get(url).await?.json().await?; - Ok(alert) + debug!("Fetching Alert At: {}", url); + let req = reqwest::get(url).await?.error_for_status(); + // If we get a 404 or anything that's not an actual networking issue simply return a disabled result + if let Ok(alert) = req { + let alert = alert.json().await?; + Ok(alert) + } else { + Ok(Alert { + enabled: false, + severity: None, + message: None, + url: None, + url_label: None, + }) + } } /// Get the warnings for a list of mods, ignoring the ones in `ignore` diff --git a/owmods_core/src/config.rs b/owmods_core/src/config.rs index cf4463b7d..264dd16c5 100644 --- a/owmods_core/src/config.rs +++ b/owmods_core/src/config.rs @@ -7,8 +7,8 @@ use typeshare::typeshare; use crate::{ constants::{ - CONFIG_FILE_NAME, DEFAULT_ALERT_URL, DEFAULT_DB_URL, OWML_DEFAULT_CONFIG_NAME, - OWML_EXE_NAME, OWML_MANIFEST_NAME, + CONFIG_FILE_NAME, DEFAULT_ALERT_URL, DEFAULT_DB_URL, OLD_ALERT_URL, + OWML_DEFAULT_CONFIG_NAME, OWML_EXE_NAME, OWML_MANIFEST_NAME, }, file::{deserialize_from_json, get_app_path, get_default_owml_path, serialize_to_json}, }; @@ -83,7 +83,15 @@ impl Config { debug!("Reading Config From {}", path.to_str().unwrap()); let mut new_conf: Config = deserialize_from_json(path)?; new_conf.path = path.to_path_buf(); - Ok(new_conf) + Ok(new_conf.migrate()) + } + + // Migrate a config from older versions + fn migrate(mut self) -> Self { + if self.alert_url == OLD_ALERT_URL { + self.alert_url = DEFAULT_ALERT_URL.to_string(); + } + self } /// Get the config from the provided path (or default one), creating a default file if it doesn't exist. @@ -176,6 +184,18 @@ mod tests { dir.close().unwrap(); } + #[test] + pub fn test_config_migrate_alert() { + let dir = make_test_dir(); + let path = dir.path().join("settings.json"); + let mut config = Config::default(Some(path.clone())).unwrap(); + config.alert_url = OLD_ALERT_URL.to_string(); + config.save().unwrap(); + let config = Config::get(Some(path)).unwrap(); + assert_eq!(config.alert_url, DEFAULT_ALERT_URL); + dir.close().unwrap(); + } + #[test] pub fn test_check_owml_no_folder() { let dir = make_test_dir(); diff --git a/owmods_core/src/constants.rs b/owmods_core/src/constants.rs index 1ce3c17e7..b8f284e86 100644 --- a/owmods_core/src/constants.rs +++ b/owmods_core/src/constants.rs @@ -1,7 +1,9 @@ pub const OWML_UNIQUE_NAME: &str = "Alek.OWML"; pub const DEFAULT_DB_URL: &str = "https://ow-mods.github.io/ow-mod-db/database.json"; -pub const DEFAULT_ALERT_URL: &str = +pub const OLD_ALERT_URL: &str = "https://raw.githubusercontent.com/ow-mods/ow-mod-db/source/alert.json"; +pub const DEFAULT_ALERT_URL: &str = + "https://raw.githubusercontent.com/ow-mods/ow-mod-db/source/alert-v2.json"; pub const CONFIG_FILE_NAME: &str = "settings.json"; pub const DB_REPO_URL: &str = "https://github.com/ow-mods/ow-mod-db"; pub const OWML_DOCS_URL: &str = "https://owml.outerwildsmods.com"; diff --git a/owmods_core/src/download.rs b/owmods_core/src/download.rs index 3d5d1a6d0..06bb9b061 100644 --- a/owmods_core/src/download.rs +++ b/owmods_core/src/download.rs @@ -196,8 +196,19 @@ fn extract_mod_zip( /// /// If we can't download or extract the OWML zip for any reason. /// -pub async fn download_and_install_owml(config: &Config, owml: &RemoteMod) -> Result<()> { - let url = &owml.download_url; +pub async fn download_and_install_owml( + config: &Config, + owml: &RemoteMod, + prerelease: bool, +) -> Result<()> { + let url = if prerelease { + owml.prerelease + .as_ref() + .map(|p| &p.download_url) + .ok_or_else(|| anyhow!("No prerelease for OWML found")) + } else { + Ok(&owml.download_url) + }?; let target_path = PathBuf::from(&config.owml_path); let temp_dir = TempDir::new()?; let download_path = temp_dir.path().join("OWML.zip"); diff --git a/owmods_core/src/io.rs b/owmods_core/src/io.rs index 49dd7d9be..79383d95b 100644 --- a/owmods_core/src/io.rs +++ b/owmods_core/src/io.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::Path; use anyhow::Result; @@ -7,7 +7,7 @@ use crate::{ db::{LocalDatabase, RemoteDatabase}, download::install_mods_parallel, file::deserialize_from_json, - toggle::{get_mod_enabled, toggle_mod}, + toggle::toggle_mod, }; /// Export all installed **and enabled** mods in the database @@ -46,8 +46,7 @@ pub async fn import_mods( if disable_missing { for local_mod in local_db.valid() { - let mod_path = &PathBuf::from(&local_mod.mod_path); - if get_mod_enabled(mod_path)? { + if local_mod.enabled { toggle_mod(&local_mod.manifest.unique_name, local_db, false, false)?; } } @@ -55,8 +54,7 @@ pub async fn import_mods( for name in unique_names.iter() { let local_mod = local_db.get_mod(name); if let Some(local_mod) = local_mod { - let mod_path = &PathBuf::from(&local_mod.mod_path); - if !get_mod_enabled(&PathBuf::from(&mod_path))? { + if !local_mod.enabled { toggle_mod(&local_mod.manifest.unique_name, local_db, true, false)?; } } else { diff --git a/owmods_core/src/progress.rs b/owmods_core/src/progress.rs index 042eacc93..45d868ec1 100644 --- a/owmods_core/src/progress.rs +++ b/owmods_core/src/progress.rs @@ -136,6 +136,7 @@ pub struct ProgressBar { id: String, len: ProgressValue, progress: ProgressValue, + throttled_progress: ProgressValue, failure_message: String, complete: bool, } @@ -153,6 +154,7 @@ impl ProgressBar { id: id.to_string(), len, progress: 0, + throttled_progress: 0, failure_message: failure_message.to_string(), complete: false, }; @@ -161,12 +163,18 @@ impl ProgressBar { } pub fn inc(&mut self, amount: ProgressValue) { + const THROTTLING_AMOUNT: ProgressValue = 30; + self.progress = if self.progress + amount >= self.len { self.len } else { self.progress + amount }; - info!(target: "progress", "Increment|{}|{}", self.id, self.progress); + + if self.progress - self.throttled_progress > self.len / THROTTLING_AMOUNT { + self.throttled_progress = self.progress; + info!(target: "progress", "Increment|{}|{}", self.id, self.progress); + } } pub fn set_msg(&self, msg: &str) { diff --git a/owmods_core/src/toggle.rs b/owmods_core/src/toggle.rs index e605be6eb..9ef6c4747 100644 --- a/owmods_core/src/toggle.rs +++ b/owmods_core/src/toggle.rs @@ -49,14 +49,14 @@ pub fn get_mod_enabled(mod_path: &Path) -> Result { let conf = read_config(&config_path)?; Ok(conf.enabled) } else { - Ok(false) + generate_config(&config_path)?; + Ok(true) } } /// Toggle a mod to a given enabled value. /// Also support applying this action recursively. /// -/// /// ## Returns /// /// A list of mod names that were disabled and use pre patchers, and therefore **should alert the user to check the mod's README for instructions on how to fully disable it**. @@ -256,7 +256,7 @@ mod tests { let (dir, db, new_mod) = setup(); let mod_path = PathBuf::from(new_mod.mod_path); remove_file(mod_path.join("config.json")).unwrap(); - assert!(!get_mod_enabled(&mod_path).unwrap()); + assert!(get_mod_enabled(&mod_path).unwrap()); toggle_mod("Bwc9876.TimeSaver", &db, false, false).unwrap(); assert!(mod_path.join("config.json").is_file()); let new_mod = LocalDatabase::read_local_mod(&mod_path.join("manifest.json")).unwrap(); diff --git a/owmods_core/src/updates.rs b/owmods_core/src/updates.rs index 80cf863d8..64b55adca 100644 --- a/owmods_core/src/updates.rs +++ b/owmods_core/src/updates.rs @@ -85,7 +85,7 @@ pub async fn update_all( owml.as_ref().unwrap().manifest.version, remote_owml.unwrap().version ); - download_and_install_owml(config, remote_owml.unwrap()).await?; + download_and_install_owml(config, remote_owml.unwrap(), false).await?; } } diff --git a/owmods_core/src/validate.rs b/owmods_core/src/validate.rs index 169261a7b..f74de80cc 100644 --- a/owmods_core/src/validate.rs +++ b/owmods_core/src/validate.rs @@ -108,17 +108,16 @@ pub async fn fix_deps( db: &LocalDatabase, remote_db: &RemoteDatabase, ) -> Result<()> { - let errors = check_mod_deps(local_mod, db); let mut missing: Vec = vec![]; - for error in errors { + for error in local_mod.errors.iter() { match error { ModValidationError::DisabledDep(unique_name) => { info!("Enabling {}", unique_name); - toggle_mod(&unique_name, db, true, true)?; + toggle_mod(unique_name, db, true, true)?; } ModValidationError::MissingDep(unique_name) => { info!("Marking {} For Install", unique_name); - missing.push(unique_name); + missing.push(unique_name.clone()); } _ => {} } diff --git a/owmods_core/test_files/Invalid/Mods/Dupe.Mod1/config.json b/owmods_core/test_files/Invalid/Mods/Dupe.Mod1/config.json new file mode 100644 index 000000000..37c8ceff4 --- /dev/null +++ b/owmods_core/test_files/Invalid/Mods/Dupe.Mod1/config.json @@ -0,0 +1,3 @@ +{ + "enabled": true +} \ No newline at end of file diff --git a/owmods_core/test_files/Invalid/Mods/Dupe.Mod2/config.json b/owmods_core/test_files/Invalid/Mods/Dupe.Mod2/config.json new file mode 100644 index 000000000..37c8ceff4 --- /dev/null +++ b/owmods_core/test_files/Invalid/Mods/Dupe.Mod2/config.json @@ -0,0 +1,3 @@ +{ + "enabled": true +} \ No newline at end of file diff --git a/owmods_gui/CONTRIBUTING.md b/owmods_gui/CONTRIBUTING.md index 0019373e4..1de6d83f9 100644 --- a/owmods_gui/CONTRIBUTING.md +++ b/owmods_gui/CONTRIBUTING.md @@ -6,6 +6,10 @@ This package is called `owmods_gui` so anytime you want to perform cargo command Ex: `cargo add tokio` should become `cargo add clap -p owmods_gui`. +## Setup on Linux + +Please follow the [tauri docs](https://tauri.app/v1/guides/getting-started/prerequisites#setting-up-linux) for instructions on installing the necessary system packages. + ## pnpm The frontend for this package is made with TS so you need to install related dependencies. First cd in to `owmods_gui/frontend`, then run `pnpm i` @@ -27,7 +31,7 @@ cd owmods_gui/frontend pnpm gen-types ``` -This will generate `types.d.ts` in `owmods_gui/frontend/src/types.d.ts`, **make sure to format this file with prettier (`pnpm prettify`)**. +This will generate `types.d.ts` in `owmods_gui/frontend/src/types.d.ts`. ## Formatting & Linting diff --git a/owmods_gui/README.md b/owmods_gui/README.md index 3dec59086..e7c82a80c 100644 --- a/owmods_gui/README.md +++ b/owmods_gui/README.md @@ -82,6 +82,5 @@ Just create an empty `dist/` folder next to `backend`, tauri expects one to be t ## Screenshots ![The main screen of the app](https://github.com/Bwc9876/ow-mod-man/raw/dev/.github/assets/screenshots/main.png) -![A few of the many themes you can grab](https://github.com/Bwc9876/ow-mod-man/raw/dev/.github/assets/screenshots/themes.png) ![The logs screen when starting the game](https://github.com/Bwc9876/ow-mod-man/raw/dev/.github/assets/screenshots/logs.png) ![The about modal](https://github.com/Bwc9876/ow-mod-man/raw/dev/.github/assets/screenshots/about.png) diff --git a/owmods_gui/backend/Cargo.toml b/owmods_gui/backend/Cargo.toml index e399ad1dc..43eb4b349 100644 --- a/owmods_gui/backend/Cargo.toml +++ b/owmods_gui/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "owmods_gui" -version = "0.6.1" +version = "0.7.0" license = "GPL-3.0-or-later" edition = "2021" @@ -8,7 +8,7 @@ edition = "2021" tauri-build = { version = "1.3.0", features = [] } [dependencies] -owmods_core = { version = "0.6.1", path = "../../owmods_core" } +owmods_core = { version = "0.7.0", path = "../../owmods_core" } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.3.0", features = ["app-all", "dialog-ask", "dialog-confirm", "dialog-message", "dialog-open", "dialog-save", "os-all", "shell-open", "updater", "window-set-focus", "window-set-title"] } @@ -16,11 +16,12 @@ anyhow = "1.0.69" tokio = { version = "1.25.0", features = ["sync", "macros"] } log = { version = "0.4.17", features = ["std", "serde"] } typeshare = "1.0.0" +notify = { version = "6.0.1", default-features = false, features = ["macos_kqueue"] } regex = "1.7.1" time = { version = "0.3.15", features = ["macros"] } tauri-plugin-deep-link = "0.1.0" tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } -tauri-plugin-fs-watch = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } +opener = "0.6.1" [features] default = ["custom-protocol"] diff --git a/owmods_gui/backend/icons/128x128.png b/owmods_gui/backend/icons/128x128.png index 819cad06b..db8785470 100644 Binary files a/owmods_gui/backend/icons/128x128.png and b/owmods_gui/backend/icons/128x128.png differ diff --git a/owmods_gui/backend/icons/128x128@2x.png b/owmods_gui/backend/icons/128x128@2x.png index 9b90af3ce..054ab7135 100644 Binary files a/owmods_gui/backend/icons/128x128@2x.png and b/owmods_gui/backend/icons/128x128@2x.png differ diff --git a/owmods_gui/backend/icons/32x32.png b/owmods_gui/backend/icons/32x32.png index b5c73dc21..1a5fef42d 100644 Binary files a/owmods_gui/backend/icons/32x32.png and b/owmods_gui/backend/icons/32x32.png differ diff --git a/owmods_gui/backend/icons/Square107x107Logo.png b/owmods_gui/backend/icons/Square107x107Logo.png index 956ed642d..de305112f 100644 Binary files a/owmods_gui/backend/icons/Square107x107Logo.png and b/owmods_gui/backend/icons/Square107x107Logo.png differ diff --git a/owmods_gui/backend/icons/Square142x142Logo.png b/owmods_gui/backend/icons/Square142x142Logo.png index e301ab30b..9b9ad2819 100644 Binary files a/owmods_gui/backend/icons/Square142x142Logo.png and b/owmods_gui/backend/icons/Square142x142Logo.png differ diff --git a/owmods_gui/backend/icons/Square150x150Logo.png b/owmods_gui/backend/icons/Square150x150Logo.png index b1de4464a..d87401c51 100644 Binary files a/owmods_gui/backend/icons/Square150x150Logo.png and b/owmods_gui/backend/icons/Square150x150Logo.png differ diff --git a/owmods_gui/backend/icons/Square284x284Logo.png b/owmods_gui/backend/icons/Square284x284Logo.png index 954a0b3da..4da73daa0 100644 Binary files a/owmods_gui/backend/icons/Square284x284Logo.png and b/owmods_gui/backend/icons/Square284x284Logo.png differ diff --git a/owmods_gui/backend/icons/Square30x30Logo.png b/owmods_gui/backend/icons/Square30x30Logo.png index e01b324da..190a26ba3 100644 Binary files a/owmods_gui/backend/icons/Square30x30Logo.png and b/owmods_gui/backend/icons/Square30x30Logo.png differ diff --git a/owmods_gui/backend/icons/Square310x310Logo.png b/owmods_gui/backend/icons/Square310x310Logo.png index 678e8561a..1cecef411 100644 Binary files a/owmods_gui/backend/icons/Square310x310Logo.png and b/owmods_gui/backend/icons/Square310x310Logo.png differ diff --git a/owmods_gui/backend/icons/Square44x44Logo.png b/owmods_gui/backend/icons/Square44x44Logo.png index bdf874280..39ade153e 100644 Binary files a/owmods_gui/backend/icons/Square44x44Logo.png and b/owmods_gui/backend/icons/Square44x44Logo.png differ diff --git a/owmods_gui/backend/icons/Square71x71Logo.png b/owmods_gui/backend/icons/Square71x71Logo.png index 09e052190..d4f2c7e20 100644 Binary files a/owmods_gui/backend/icons/Square71x71Logo.png and b/owmods_gui/backend/icons/Square71x71Logo.png differ diff --git a/owmods_gui/backend/icons/Square89x89Logo.png b/owmods_gui/backend/icons/Square89x89Logo.png index 91d9bb854..c2b4033de 100644 Binary files a/owmods_gui/backend/icons/Square89x89Logo.png and b/owmods_gui/backend/icons/Square89x89Logo.png differ diff --git a/owmods_gui/backend/icons/StoreLogo.png b/owmods_gui/backend/icons/StoreLogo.png index d27dae530..bfc6557ac 100644 Binary files a/owmods_gui/backend/icons/StoreLogo.png and b/owmods_gui/backend/icons/StoreLogo.png differ diff --git a/owmods_gui/backend/icons/icon.icns b/owmods_gui/backend/icons/icon.icns index 599dcb7da..c3e6ab094 100644 Binary files a/owmods_gui/backend/icons/icon.icns and b/owmods_gui/backend/icons/icon.icns differ diff --git a/owmods_gui/backend/icons/icon.ico b/owmods_gui/backend/icons/icon.ico index 7abf3c4d3..35f6b662d 100644 Binary files a/owmods_gui/backend/icons/icon.ico and b/owmods_gui/backend/icons/icon.ico differ diff --git a/owmods_gui/backend/icons/icon.png b/owmods_gui/backend/icons/icon.png index 9881b9333..7c84c3b47 100644 Binary files a/owmods_gui/backend/icons/icon.png and b/owmods_gui/backend/icons/icon.png differ diff --git a/owmods_gui/backend/installer-images/nsis/Header.bmp b/owmods_gui/backend/installer-images/nsis/Header.bmp new file mode 100644 index 000000000..b3ab35bc9 Binary files /dev/null and b/owmods_gui/backend/installer-images/nsis/Header.bmp differ diff --git a/owmods_gui/backend/installer-images/nsis/Header.xcf b/owmods_gui/backend/installer-images/nsis/Header.xcf new file mode 100644 index 000000000..bfea505bb Binary files /dev/null and b/owmods_gui/backend/installer-images/nsis/Header.xcf differ diff --git a/owmods_gui/backend/installer-images/wix/Background.png b/owmods_gui/backend/installer-images/wix/Background.png new file mode 100644 index 000000000..2dfbf8476 Binary files /dev/null and b/owmods_gui/backend/installer-images/wix/Background.png differ diff --git a/owmods_gui/backend/installer-images/wix/Background.xcf b/owmods_gui/backend/installer-images/wix/Background.xcf new file mode 100644 index 000000000..a5be70877 Binary files /dev/null and b/owmods_gui/backend/installer-images/wix/Background.xcf differ diff --git a/owmods_gui/backend/installer-images/wix/TopBanner.png b/owmods_gui/backend/installer-images/wix/TopBanner.png new file mode 100644 index 000000000..4035ce6b6 Binary files /dev/null and b/owmods_gui/backend/installer-images/wix/TopBanner.png differ diff --git a/owmods_gui/backend/installer-images/wix/TopBanner.xcf b/owmods_gui/backend/installer-images/wix/TopBanner.xcf new file mode 100644 index 000000000..ed06531e2 Binary files /dev/null and b/owmods_gui/backend/installer-images/wix/TopBanner.xcf differ diff --git a/owmods_gui/backend/src/commands.rs b/owmods_gui/backend/src/commands.rs index 9227956e8..4e7a23db7 100644 --- a/owmods_gui/backend/src/commands.rs +++ b/owmods_gui/backend/src/commands.rs @@ -2,11 +2,12 @@ use std::result::Result as StdResult; use std::{ fs::File, io::{BufWriter, Write}, - path::{Path, PathBuf}, + path::PathBuf, }; use anyhow::anyhow; use log::error; +use owmods_core::mods::local::LocalMod; use owmods_core::{ alerts::{fetch_alert, Alert}, config::Config, @@ -27,7 +28,7 @@ use owmods_core::{ validate::fix_deps, }; use serde::Serialize; -use tauri::{api::dialog, async_runtime, AppHandle, Manager, WindowEvent}; +use tauri::{api::dialog, async_runtime, Manager, WindowEvent}; use time::{macros::format_description, OffsetDateTime}; use tokio::{sync::mpsc, try_join}; @@ -57,10 +58,6 @@ impl Serialize for Error { } } -fn toggle_fs_watch(handle: &AppHandle, enabled: bool) { - handle.emit_all("TOGGLE_FS_WATCH", enabled).ok(); -} - pub async fn mark_mod_busy( unique_name: &str, busy: bool, @@ -85,14 +82,20 @@ pub async fn initial_setup(handle: tauri::AppHandle, state: tauri::State<'_, Sta *config = Config::get(None)?; let mut gui_config = state.gui_config.write().await; *gui_config = GuiConfig::get()?; - handle.emit_all("GUI_CONFIG_RELOAD", "").ok(); + handle + .emit_all("GUI_CONFIG_RELOAD", gui_config.watch_fs) + .ok(); + handle.trigger_global( + "GUI_CONFIG_RELOAD", + Some(serde_json::to_string::(&gui_config.watch_fs).unwrap()), + ); handle.emit_all("CONFIG_RELOAD", "").ok(); + Ok(()) } #[tauri::command] pub async fn refresh_local_db(handle: tauri::AppHandle, state: tauri::State<'_, State>) -> Result { - toggle_fs_watch(&handle, false); let conf = state.config.read().await; { let mut db = state.local_db.write().await; @@ -100,7 +103,7 @@ pub async fn refresh_local_db(handle: tauri::AppHandle, state: tauri::State<'_, *db = local_db; } handle.emit_all("LOCAL-REFRESH", "").ok(); - toggle_fs_watch(&handle, true); + let handle2 = handle.clone(); // Defer checking if a mod needs to update to prevent deadlock async_runtime::spawn(async move { @@ -121,7 +124,8 @@ pub async fn get_local_mods(filter: &str, state: tauri::State<'_, State>) -> Res mods.sort_by(|a, b| { let name_ord = a.get_name().cmp(b.get_name()); let errors_ord = a.get_errs().len().cmp(&b.get_errs().len()).reverse(); - errors_ord.then(name_ord) + let enabled_ord = a.get_enabled().cmp(&b.get_enabled()).reverse(); + errors_ord.then(enabled_ord.then(name_ord)) }); } else { mods = db.search(filter); @@ -154,7 +158,6 @@ pub async fn get_local_mod( #[tauri::command] pub async fn refresh_remote_db(handle: tauri::AppHandle, state: tauri::State<'_, State>) -> Result { - toggle_fs_watch(&handle, false); let conf = state.config.read().await; { let mut db = state.remote_db.write().await; @@ -162,7 +165,7 @@ pub async fn refresh_remote_db(handle: tauri::AppHandle, state: tauri::State<'_, *db = remote_db; } handle.emit_all("REMOTE-REFRESH", "").ok(); - toggle_fs_watch(&handle, true); + Ok(()) } @@ -242,8 +245,9 @@ pub async fn install_mod( let local_db = state.local_db.read().await; let remote_db = state.remote_db.read().await; let conf = state.config.read().await; + let mut should_install = true; if let Some(current_mod) = local_db.get_mod(unique_name) { - let res = dialog::blocking::confirm( + should_install = dialog::blocking::confirm( Some(&window), "Reinstall?", format!( @@ -251,37 +255,47 @@ pub async fn install_mod( current_mod.manifest.name ), ); - if !res { - return Ok(()); - } } - install_mod_from_db( - &unique_name.to_string(), - &conf, - &remote_db, - &local_db, - true, - prerelease.unwrap_or(false), - ) - .await?; + if should_install { + install_mod_from_db( + &unique_name.to_string(), + &conf, + &remote_db, + &local_db, + true, + prerelease.unwrap_or(false), + ) + .await?; + } mark_mod_busy(unique_name, false, true, &state, &handle).await; + Ok(()) } #[tauri::command] -pub async fn install_url(url: &str, state: tauri::State<'_, State>) -> Result { +pub async fn install_url( + url: &str, + state: tauri::State<'_, State>, + _handle: tauri::AppHandle, +) -> Result { let conf = state.config.read().await; let db = state.local_db.read().await; install_mod_from_url(url, &conf, &db).await?; + Ok(()) } #[tauri::command] -pub async fn install_zip(path: &str, state: tauri::State<'_, State>) -> Result { +pub async fn install_zip( + path: &str, + state: tauri::State<'_, State>, + _handle: tauri::AppHandle, +) -> Result { let conf = state.config.read().await; let db = state.local_db.read().await; println!("Installing {}", path); install_mod_from_zip(&PathBuf::from(path), &conf, &db)?; + Ok(()) } @@ -289,12 +303,14 @@ pub async fn install_zip(path: &str, state: tauri::State<'_, State>) -> Result { pub async fn uninstall_mod( unique_name: &str, state: tauri::State<'_, State>, + _handle: tauri::AppHandle, ) -> Result> { let db = state.local_db.read().await; let local_mod = db .get_mod(unique_name) .ok_or_else(|| anyhow!("Mod {} not found", unique_name))?; let warnings = remove_mod(local_mod, &db, false)?; + Ok(warnings) } @@ -336,6 +352,7 @@ pub async fn save_config( *conf_lock = config; } handle.emit_all("CONFIG_RELOAD", "").ok(); + Ok(()) } @@ -350,12 +367,18 @@ pub async fn save_gui_config( state: tauri::State<'_, State>, handle: tauri::AppHandle, ) -> Result { + let watch_fs = gui_config.watch_fs; gui_config.save()?; { let mut conf_lock = state.gui_config.write().await; *conf_lock = gui_config; } - handle.emit_all("GUI_CONFIG_RELOAD", "").ok(); + handle.emit_all("GUI_CONFIG_RELOAD", &watch_fs).ok(); + handle.trigger_global( + "GUI_CONFIG_RELOAD", + Some(serde_json::to_string::(&watch_fs).unwrap()), + ); + Ok(()) } @@ -373,6 +396,7 @@ pub async fn save_owml_config( let config = state.config.read().await; owml_config.save(&config)?; handle.emit_all("OWML_CONFIG_RELOAD", "").ok(); + Ok(()) } @@ -384,13 +408,17 @@ pub async fn get_owml_config(state: tauri::State<'_, State>) -> Result, handle: tauri::AppHandle) -> Result { +pub async fn install_owml( + prerelease: bool, + state: tauri::State<'_, State>, + handle: tauri::AppHandle, +) -> Result { let config = state.config.read().await; let db = state.remote_db.read().await; let owml = db .get_owml() - .ok_or_else(|| anyhow!("Couldn't Find OWML In The Database"))?; - download_and_install_owml(&config, owml).await?; + .ok_or_else(|| anyhow!("Error Installing OWML"))?; + download_and_install_owml(&config, owml, prerelease).await?; handle.emit_all("OWML_CONFIG_RELOAD", "").ok(); Ok(()) } @@ -401,11 +429,13 @@ pub async fn set_owml( state: tauri::State<'_, State>, handle: tauri::AppHandle, ) -> Result { - let path = Path::new(path); - if path.is_dir() && path.join("OWML.Manifest.json").is_file() { + let mut new_config = state.config.read().await.clone(); + new_config.owml_path = path.to_string(); + if new_config.check_owml() { let mut config = state.config.write().await; - config.owml_path = path.to_str().unwrap().to_string(); + *config = new_config; config.save()?; + handle.emit_all("CONFIG_RELOAD", "").ok(); handle.emit_all("OWML_CONFIG_RELOAD", "").ok(); Ok(true) } else { @@ -414,12 +444,35 @@ pub async fn set_owml( } #[tauri::command] -pub async fn get_updatable_mods(state: tauri::State<'_, State>) -> Result> { +pub async fn get_updatable_mods( + filter: &str, + state: tauri::State<'_, State>, +) -> Result> { let mut updates: Vec = vec![]; let local_db = state.local_db.read().await; let remote_db = state.remote_db.read().await; let config = state.config.read().await; - for local_mod in local_db.valid() { + + let mut mods: Vec<&LocalMod> = local_db.valid().collect(); + + if filter.is_empty() { + mods.sort_by(|a, b| { + let name_ord = a.manifest.name.cmp(&b.manifest.name); + let enabled_ord = a.enabled.cmp(&b.enabled); + enabled_ord.then(name_ord) + }); + } else { + mods = local_db + .search(filter) + .iter() + .filter_map(|m| match m { + UnsafeLocalMod::Invalid(_) => None, + UnsafeLocalMod::Valid(m) => Some(m), + }) + .collect(); + } + + for local_mod in mods { let (needs_update, _) = check_mod_needs_update(local_mod, &remote_db); if needs_update { updates.push(local_mod.manifest.unique_name.clone()); @@ -444,13 +497,14 @@ pub async fn update_mod( let config = state.config.read().await; let local_db = state.local_db.read().await; let remote_db = state.remote_db.read().await; - toggle_fs_watch(&handle, false); + if unique_name == OWML_UNIQUE_NAME { download_and_install_owml( &config, remote_db .get_owml() .ok_or_else(|| anyhow!("OWML Not Found!"))?, + false, ) .await?; } else { @@ -464,7 +518,7 @@ pub async fn update_mod( ) .await?; } - toggle_fs_watch(&handle, true); + mark_mod_busy(unique_name, false, true, &state, &handle).await; Ok(()) } @@ -475,7 +529,6 @@ pub async fn update_all_mods( state: tauri::State<'_, State>, handle: tauri::AppHandle, ) -> Result { - toggle_fs_watch(&handle, false); let config = state.config.read().await; let local_db = state.local_db.read().await; let remote_db = state.remote_db.read().await; @@ -492,7 +545,7 @@ pub async fn update_all_mods( let mut busy_mods = state.mods_in_progress.write().await; busy_mods.retain(|m| !unique_names.contains(m)); handle.emit_all("MOD-BUSY", "").ok(); - toggle_fs_watch(&handle, true); + Ok(()) } @@ -525,16 +578,22 @@ pub async fn active_log(port: LogPort, state: tauri::State<'_, State>) -> Result } #[tauri::command] -pub async fn run_game(state: tauri::State<'_, State>, window: tauri::Window) -> Result { +pub async fn run_game( + state: tauri::State<'_, State>, + window: tauri::Window, + handle: tauri::AppHandle, +) -> Result { let config = state.config.read().await.clone(); { let local_db = state.local_db.read().await; let new_config = show_warnings(&window, &local_db, &config)?; + new_config.save()?; { let mut config = state.config.write().await; *config = new_config; } + handle.emit_all("CONFIG_RELOAD", "").ok(); } let log_server = LogServer::new(0).await?; @@ -676,31 +735,44 @@ pub async fn export_mods(path: String, state: tauri::State<'_, State>) -> Result let path = PathBuf::from(path); let local_db = state.local_db.read().await; let output = owmods_core::io::export_mods(&local_db)?; - let file = File::create(path).map_err(|e| anyhow!("Error Saving File: {:?}", e))?; + let file = File::create(&path).map_err(|e| anyhow!("Error Saving File: {:?}", e))?; let mut writer = BufWriter::new(file); write!(&mut writer, "{}", output).map_err(|e| anyhow!("Error Saving File: {:?}", e))?; + opener::open(path).ok(); Ok(()) } #[tauri::command] -pub async fn import_mods(path: String, state: tauri::State<'_, State>) -> Result { +pub async fn import_mods( + path: String, + disable_missing: bool, + state: tauri::State<'_, State>, + _handle: tauri::AppHandle, +) -> Result { let local_db = state.local_db.read().await; let remote_db = state.remote_db.read().await; let config = state.config.read().await; let path = PathBuf::from(path); - owmods_core::io::import_mods(&config, &local_db, &remote_db, &path, false).await?; + owmods_core::io::import_mods(&config, &local_db, &remote_db, &path, disable_missing).await?; + Ok(()) } #[tauri::command] -pub async fn fix_mod_deps(unique_name: &str, state: tauri::State<'_, State>) -> Result { +pub async fn fix_mod_deps( + unique_name: &str, + state: tauri::State<'_, State>, + _handle: tauri::AppHandle, +) -> Result { let config = state.config.read().await; let local_db = state.local_db.read().await; let remote_db = state.remote_db.read().await; let local_mod = local_db .get_mod(unique_name) .ok_or_else(|| anyhow!("Can't find mod {}", unique_name))?; + fix_deps(local_mod, &config, &local_db, &remote_db).await?; + Ok(()) } @@ -718,20 +790,6 @@ pub async fn get_alert(state: tauri::State<'_, State>) -> Result { Ok(alert) } -#[tauri::command] -pub async fn get_watcher_paths(state: tauri::State<'_, State>) -> Result> { - let config = state.config.read().await; - Ok(vec![ - config.owml_path.clone(), - GuiConfig::path().unwrap().to_str().unwrap().to_string(), - Config::default_path() - .unwrap() - .to_str() - .unwrap() - .to_string(), - ]) -} - #[tauri::command] pub async fn pop_protocol_url(state: tauri::State<'_, State>, handle: tauri::AppHandle) -> Result { let mut protocol_url = state.protocol_url.write().await; @@ -768,7 +826,7 @@ pub async fn get_downloads(state: tauri::State<'_, State>) -> Result, handle: tauri::AppHandle) -> Result { let mut bars = state.progress_bars.write().await; - bars.0.clear(); + bars.bars.clear(); handle.emit_all("PROGRESS-UPDATE", "").ok(); Ok(()) } diff --git a/owmods_gui/backend/src/fs_watch.rs b/owmods_gui/backend/src/fs_watch.rs new file mode 100644 index 000000000..3159543f7 --- /dev/null +++ b/owmods_gui/backend/src/fs_watch.rs @@ -0,0 +1,132 @@ +use std::{ + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use anyhow::Result; +use log::{error, info}; +use notify::{Event, EventKind, RecursiveMode, Watcher}; +use owmods_core::config::Config; +use tauri::{AppHandle, Manager}; + +use crate::{gui_config::GuiConfig, State}; + +fn check_res(res: Result) -> bool { + if let Ok(e) = res { + matches!( + e.kind, + EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) + ) + } else { + false + } +} + +pub fn setup_fs_watch(handle: AppHandle) -> Result<()> { + let handle_gui = handle.clone(); + let handle_config = handle.clone(); + let handle_local = handle.clone(); + + let gui_settings_watcher = Mutex::new(notify::recommended_watcher(move |res| { + if check_res(res) { + handle_gui.emit_all("REQUEST-RELOAD", "GUI").ok(); + } + })?); + + let settings_watcher = Mutex::new(notify::recommended_watcher(move |res| { + if check_res(res) { + handle_config.emit_all("REQUEST-RELOAD", "CONFIG").ok(); + } + })?); + + let mods_watcher = Arc::new(Mutex::new(notify::recommended_watcher(move |res| { + if check_res(res) { + handle_local.emit_all("REQUEST-RELOAD", "LOCAL").ok(); + } + })?)); + + let gui_path = GuiConfig::path()?; + let config_path = Config::default_path()?; + + let watch_enabled = Mutex::new(false); + + let e_handle = handle.clone(); + + handle.listen_global("GUI_CONFIG_RELOAD", move |e| { + let watch_fs = e + .payload() + .map(|v| serde_json::from_str(v).unwrap()) + .unwrap_or(false); + + let mut gui_watcher = gui_settings_watcher.lock().unwrap(); + let mut settings_watcher = settings_watcher.lock().unwrap(); + let db_handle = e_handle.clone(); + let mods_watcher_ref = mods_watcher.clone(); + + let mut watch_enabled = watch_enabled.lock().unwrap(); + if *watch_enabled != watch_fs { + if watch_fs { + info!("File Watcher: Watching Filesystem"); + // GUI + let res = gui_watcher.watch(&gui_path, RecursiveMode::NonRecursive); + if let Err(why) = res { + error!("Error starting GUI settings watcher: {:?}", why); + } + + // SETTINGS + let res = settings_watcher.watch(&config_path, RecursiveMode::NonRecursive); + if let Err(why) = res { + error!("Error starting Settings watcher: {:?}", why); + } + + // LOCAL DB + tokio::spawn(async move { + let state = db_handle.state::(); + let config = state.config.read().await; + + let mods_path = PathBuf::from(&config.owml_path).join("Mods"); + + let mut local_db_watcher = mods_watcher_ref.lock().unwrap(); + + let res = local_db_watcher.watch(&mods_path, RecursiveMode::Recursive); + if let Err(why) = res { + error!("Error starting Mods watcher: {:?}", why); + } + }); + } else if *watch_enabled { + info!("File Watcher: Unwatching Filesystem"); + // GUI + let res = gui_watcher.unwatch(&gui_path); + if let Err(why) = res { + error!("Error stopping GUI watcher: {:?}", why); + } + + // SETTINGS + let res = settings_watcher.unwatch(&config_path); + if let Err(why) = res { + error!("Error stopping Settings watcher: {:?}", why); + } + + // LOCAL DB + tokio::spawn(async move { + let state = db_handle.state::(); + let config = state.config.read().await; + + let mods_path = PathBuf::from(&config.owml_path).join("Mods"); + + let mut local_db_watcher = mods_watcher_ref.lock().unwrap(); + + let res = local_db_watcher.unwatch(&mods_path); + if let Err(why) = res { + error!("Error stopping Mods watcher: {:?}", why); + } + }); + } + } + *watch_enabled = watch_fs; + }); + + info!("File Watcher Setup On Standby"); + + Ok(()) +} diff --git a/owmods_gui/backend/src/gui_config.rs b/owmods_gui/backend/src/gui_config.rs index 4fb2835ce..a634f503e 100644 --- a/owmods_gui/backend/src/gui_config.rs +++ b/owmods_gui/backend/src/gui_config.rs @@ -4,7 +4,6 @@ use owmods_core::file::{deserialize_from_json, get_app_path, serialize_to_json}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -#[typeshare] #[derive(Default, Serialize, Deserialize, Clone)] pub enum Theme { #[default] @@ -40,14 +39,10 @@ const fn _default_false() -> bool { #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct GuiConfig { - #[serde(default = "Theme::default")] - theme: Theme, - #[serde(default = "_default_false")] - rainbow: bool, #[serde(default = "Language::default")] language: Language, #[serde(default = "_default_true")] - watch_fs: bool, + pub watch_fs: bool, #[serde(default = "_default_false")] no_warning: bool, #[serde(default = "_default_false")] @@ -56,13 +51,18 @@ pub struct GuiConfig { auto_enable_deps: bool, #[serde(default = "_default_false")] pub no_log_server: bool, + // Old + #[serde(skip_serializing_if = "Option::is_none")] + theme: Option, + #[serde(skip_serializing_if = "Option::is_none")] + rainbow: Option, } impl Default for GuiConfig { fn default() -> Self { Self { - theme: Theme::default(), - rainbow: false, + theme: None, + rainbow: None, language: Language::default(), watch_fs: true, no_warning: false, @@ -79,8 +79,21 @@ impl GuiConfig { Ok(path) } + fn migrate(&mut self) -> bool { + if self.theme.is_some() || self.rainbow.is_some() { + self.theme = None; + self.rainbow = None; + return true; + } + false + } + fn read() -> Result { - deserialize_from_json::(&Self::path()?) + let mut conf = deserialize_from_json::(&Self::path()?)?; + if conf.migrate() { + conf.save()?; + } + Ok(conf) } fn write(config: &Self) -> Result<(), anyhow::Error> { diff --git a/owmods_gui/backend/src/logging.rs b/owmods_gui/backend/src/logging.rs index 7607ed599..8d8ee7baf 100644 --- a/owmods_gui/backend/src/logging.rs +++ b/owmods_gui/backend/src/logging.rs @@ -83,7 +83,9 @@ impl log::Log for Logger { handle.emit_all("PROGRESS-UPDATE", "").ok(); }); Ok(()) - } else if self.enabled(record.metadata()) && record.target().starts_with("owmods") { + } else if self.enabled(record.metadata()) && record.target().starts_with("owmods") + || record.target().starts_with("outer-wilds-mod-manager") + { let message = format!("{}", record.args()); self.write_log_to_file(record.level(), &message) .unwrap_or_else(|e| { diff --git a/owmods_gui/backend/src/main.rs b/owmods_gui/backend/src/main.rs index 7b5009d20..dde4c3432 100644 --- a/owmods_gui/backend/src/main.rs +++ b/owmods_gui/backend/src/main.rs @@ -6,9 +6,10 @@ use std::{collections::HashMap, error::Error, fs::File, io::BufWriter, sync::Arc}; use commands::*; +use fs_watch::setup_fs_watch; use game::GameMessage; use gui_config::GuiConfig; -use log::{debug, set_boxed_logger, set_max_level, warn}; +use log::{debug, error, set_boxed_logger, set_max_level, warn}; use logging::Logger; use owmods_core::{ config::Config, @@ -21,6 +22,7 @@ use tauri::Manager; use tokio::sync::RwLock as TokioLock; mod commands; +mod fs_watch; mod game; mod gui_config; mod logging; @@ -72,10 +74,12 @@ fn main() -> Result<(), Box> { gui_config: manage(gui_config), game_log: manage(HashMap::new()), protocol_url: manage(url), - progress_bars: manage(ProgressBars(HashMap::new())), + progress_bars: manage(ProgressBars::new()), mods_in_progress: manage(vec![]), }) .setup(move |app| { + // Logger Setup + let logger = Logger::new(app.handle()); logger .write_log_to_file( @@ -88,6 +92,8 @@ fn main() -> Result<(), Box> { .ok(); set_boxed_logger(Box::new(logger)).map(|_| set_max_level(log::LevelFilter::Debug))?; + // Protocol Listener Setup + let handle = app.handle(); let res = tauri_plugin_deep_link::register("owmods", move |request| { @@ -108,6 +114,16 @@ fn main() -> Result<(), Box> { warn!("Failed to register URI handler: {:?}", why); } + // File System Watch Setup + + let handle = app.handle(); + + let res = setup_fs_watch(handle); + + if let Err(why) = res { + error!("Failed to setup file watching: {:?}", why); + } + Ok(()) }) .invoke_handler(tauri::generate_handler![ @@ -149,7 +165,6 @@ fn main() -> Result<(), Box> { fix_mod_deps, db_has_issues, get_alert, - get_watcher_paths, pop_protocol_url, check_owml, get_defaults, @@ -159,7 +174,6 @@ fn main() -> Result<(), Box> { has_disabled_deps ]) .plugin(tauri_plugin_window_state::Builder::default().build()) - .plugin(tauri_plugin_fs_watch::init()) .run(tauri::generate_context!()) .expect("Error while running tauri application."); Ok(()) diff --git a/owmods_gui/backend/src/progress.rs b/owmods_gui/backend/src/progress.rs index bcb2bbcda..876ec080c 100644 --- a/owmods_gui/backend/src/progress.rs +++ b/owmods_gui/backend/src/progress.rs @@ -16,12 +16,12 @@ pub struct ProgressBar { progress_type: ProgressType, progress_action: ProgressAction, len: ProgressValue, - total: ProgressValue, success: Option, + position: u32, } -impl From for ProgressBar { - fn from(value: ProgressStartPayload) -> Self { +impl ProgressBar { + fn from_payload(value: ProgressStartPayload, position: u32) -> Self { Self { id: value.id, message: value.msg, @@ -30,35 +30,48 @@ impl From for ProgressBar { progress: 0, len: value.len, success: None, - total: value.len, + position, } } } #[typeshare] #[derive(Serialize, Clone)] -pub struct ProgressBars(pub HashMap); +pub struct ProgressBars { + pub bars: HashMap, + counter: u32, +} impl ProgressBars { + pub fn new() -> Self { + Self { + bars: HashMap::new(), + counter: 0, + } + } + pub fn process(&mut self, payload: &str) { let payload = ProgressPayload::parse(payload); match payload { ProgressPayload::Start(start_payload) => { - self.0 - .insert(start_payload.id.clone(), start_payload.into()); + self.bars.insert( + start_payload.id.clone(), + ProgressBar::from_payload(start_payload, self.counter), + ); + self.counter += 1; } ProgressPayload::Increment(payload) => { - if let Some(bar) = self.0.get_mut(&payload.id) { + if let Some(bar) = self.bars.get_mut(&payload.id) { bar.progress = payload.progress } } ProgressPayload::Msg(payload) => { - if let Some(bar) = self.0.get_mut(&payload.id) { + if let Some(bar) = self.bars.get_mut(&payload.id) { bar.message = payload.msg } } ProgressPayload::Finish(payload) => { - if let Some(bar) = self.0.get_mut(&payload.id) { + if let Some(bar) = self.bars.get_mut(&payload.id) { bar.message = payload.msg; bar.progress = bar.len; bar.success = Some(payload.success); diff --git a/owmods_gui/backend/tauri.conf.json b/owmods_gui/backend/tauri.conf.json index c2ee0431f..2b5fe0a99 100644 --- a/owmods_gui/backend/tauri.conf.json +++ b/owmods_gui/backend/tauri.conf.json @@ -7,7 +7,7 @@ }, "package": { "productName": "Outer Wilds Mod Manager", - "version": "0.6.1" + "version": "0.7.0" }, "tauri": { "allowlist": { @@ -27,7 +27,7 @@ "all": true }, "shell": { - "open": "(^https://github.com/)|(^https://discord.gg/)" + "open": "(^https://github.com/)|(^https://discord.gg/wusTQYbYTc$)|(^https://outerwildsmods.com/)" }, "window": { "setTitle": true, @@ -66,11 +66,26 @@ "windows": { "certificateThumbprint": null, "digestAlgorithm": "sha256", - "timestampUrl": "" + "timestampUrl": "", + "wix": { + "license": "../../LICENSE", + "bannerPath": "installer-images/wix/TopBanner.png", + "dialogImagePath": "installer-images/wix/Background.png" + }, + "nsis": { + "license": "../../LICENSE", + "installerIcon": "icons/icon.ico", + "installMode": "both", + "headerImage": "installer-images/nsis/Header.bmp" + } } }, "security": { - "csp": "default-src 'self'" + "csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' data:", + "dangerousDisableAssetCspModification": [ + "style-src", + "font-src" + ] }, "updater": { "active": true, diff --git a/owmods_gui/frontend/.npmrc b/owmods_gui/frontend/.npmrc new file mode 100644 index 000000000..f301fedf9 --- /dev/null +++ b/owmods_gui/frontend/.npmrc @@ -0,0 +1 @@ +auto-install-peers=false diff --git a/owmods_gui/frontend/.prettierignore b/owmods_gui/frontend/.prettierignore index 83d50b3af..443dd2af0 100644 --- a/owmods_gui/frontend/.prettierignore +++ b/owmods_gui/frontend/.prettierignore @@ -1,3 +1,4 @@ dist src-tauri/target +stats.html !.vscode diff --git a/owmods_gui/frontend/index.html b/owmods_gui/frontend/index.html index 06ef966f4..241086584 100644 --- a/owmods_gui/frontend/index.html +++ b/owmods_gui/frontend/index.html @@ -4,7 +4,8 @@ - + + Outer Wilds Mod Manager diff --git a/owmods_gui/frontend/logs/index.html b/owmods_gui/frontend/logs/index.html index f862e5e41..81ba72a76 100644 --- a/owmods_gui/frontend/logs/index.html +++ b/owmods_gui/frontend/logs/index.html @@ -4,7 +4,8 @@ - + + Log Window diff --git a/owmods_gui/frontend/package.json b/owmods_gui/frontend/package.json index 225cbca93..2619f8573 100644 --- a/owmods_gui/frontend/package.json +++ b/owmods_gui/frontend/package.json @@ -17,27 +17,29 @@ "preinstall": "npx only-allow pnpm" }, "dependencies": { - "@picocss/pico": "^1.5.10", - "@tauri-apps/api": "^1.3.0", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@fontsource/roboto": "^5.0.3", + "@mui/icons-material": "^5.11.16", + "@mui/lab": "5.0.0-alpha.134", + "@mui/material": "^5.13.6", + "@tauri-apps/api": "^1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-icons": "^4.8.0", - "react-virtuoso": "^4.3.7", - "tauri-plugin-fs-watch-api": "github:tauri-apps/tauri-plugin-fs-watch" + "react-virtuoso": "^4.3.11" }, "devDependencies": { - "@types/react": "^18.2.6", - "@types/react-dom": "^18.2.4", - "@typescript-eslint/eslint-plugin": "^5.59.6", - "@typescript-eslint/parser": "^5.59.6", - "@vitejs/plugin-react": "^4.0.0", - "eslint": "^8.41.0", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", + "@typescript-eslint/eslint-plugin": "^5.60.0", + "@typescript-eslint/parser": "^5.60.0", + "@vitejs/plugin-react": "^4.0.1", + "eslint": "^8.43.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "prettier": "^2.8.8", - "sass": "^1.62.1", - "typescript": "^5.0.4", - "vite": "^4.3.8", + "typescript": "^5.1.3", + "vite": "^4.3.9", "vite-imagetools": "^5.0.4", "vite-plugin-html": "^3.2.0" } diff --git a/owmods_gui/frontend/pnpm-lock.yaml b/owmods_gui/frontend/pnpm-lock.yaml index e3e7d961f..89b84faa8 100644 --- a/owmods_gui/frontend/pnpm-lock.yaml +++ b/owmods_gui/frontend/pnpm-lock.yaml @@ -1,71 +1,81 @@ lockfileVersion: "6.0" +settings: + autoInstallPeers: false + excludeLinksFromLockfile: false + dependencies: - "@picocss/pico": - specifier: ^1.5.10 - version: 1.5.10 + "@emotion/react": + specifier: ^11.11.1 + version: 11.11.1(@types/react@18.2.14)(react@18.2.0) + "@emotion/styled": + specifier: ^11.11.0 + version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0) + "@fontsource/roboto": + specifier: ^5.0.3 + version: 5.0.3 + "@mui/icons-material": + specifier: ^5.11.16 + version: 5.11.16(@mui/material@5.13.6)(@types/react@18.2.14)(react@18.2.0) + "@mui/lab": + specifier: 5.0.0-alpha.134 + version: 5.0.0-alpha.134(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.13.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + "@mui/material": + specifier: ^5.13.6 + version: 5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) "@tauri-apps/api": - specifier: ^1.3.0 - version: 1.3.0 + specifier: ^1.4.0 + version: 1.4.0 react: specifier: ^18.2.0 version: 18.2.0 react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) - react-icons: - specifier: ^4.8.0 - version: 4.8.0(react@18.2.0) react-virtuoso: - specifier: ^4.3.7 - version: 4.3.7(react-dom@18.2.0)(react@18.2.0) - tauri-plugin-fs-watch-api: - specifier: github:tauri-apps/tauri-plugin-fs-watch - version: github.com/tauri-apps/tauri-plugin-fs-watch/5876048ff3d3d23c8863a37a0f0e91ed90553026 + specifier: ^4.3.11 + version: 4.3.11(react-dom@18.2.0)(react@18.2.0) devDependencies: "@types/react": + specifier: ^18.2.14 + version: 18.2.14 + "@types/react-dom": specifier: ^18.2.6 version: 18.2.6 - "@types/react-dom": - specifier: ^18.2.4 - version: 18.2.4 "@typescript-eslint/eslint-plugin": - specifier: ^5.59.6 - version: 5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.41.0)(typescript@5.0.4) + specifier: ^5.60.0 + version: 5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.1.3) "@typescript-eslint/parser": - specifier: ^5.59.6 - version: 5.59.6(eslint@8.41.0)(typescript@5.0.4) + specifier: ^5.60.0 + version: 5.60.0(eslint@8.43.0)(typescript@5.1.3) "@vitejs/plugin-react": - specifier: ^4.0.0 - version: 4.0.0(vite@4.3.8) + specifier: ^4.0.1 + version: 4.0.1(vite@4.3.9) eslint: - specifier: ^8.41.0 - version: 8.41.0 + specifier: ^8.43.0 + version: 8.43.0 eslint-plugin-react: specifier: ^7.32.2 - version: 7.32.2(eslint@8.41.0) + version: 7.32.2(eslint@8.43.0) eslint-plugin-react-hooks: specifier: ^4.6.0 - version: 4.6.0(eslint@8.41.0) + version: 4.6.0(eslint@8.43.0) prettier: specifier: ^2.8.8 version: 2.8.8 - sass: - specifier: ^1.62.1 - version: 1.62.1 typescript: - specifier: ^5.0.4 - version: 5.0.4 + specifier: ^5.1.3 + version: 5.1.3 vite: - specifier: ^4.3.8 - version: 4.3.8(sass@1.62.1) + specifier: ^4.3.9 + version: 4.3.9 vite-imagetools: specifier: ^5.0.4 version: 5.0.4 vite-plugin-html: specifier: ^3.2.0 - version: 3.2.0(vite@4.3.8) + version: 3.2.0(vite@4.3.9) packages: /@ampproject/remapping@2.2.1: @@ -79,41 +89,40 @@ packages: "@jridgewell/trace-mapping": 0.3.18 dev: true - /@babel/code-frame@7.21.4: + /@babel/code-frame@7.22.5: resolution: { - integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== + integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== } engines: { node: ">=6.9.0" } dependencies: - "@babel/highlight": 7.18.6 - dev: true + "@babel/highlight": 7.22.5 - /@babel/compat-data@7.21.7: + /@babel/compat-data@7.22.5: resolution: { - integrity: sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA== + integrity: sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA== } engines: { node: ">=6.9.0" } dev: true - /@babel/core@7.21.8: + /@babel/core@7.22.5: resolution: { - integrity: sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ== + integrity: sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg== } engines: { node: ">=6.9.0" } dependencies: "@ampproject/remapping": 2.2.1 - "@babel/code-frame": 7.21.4 - "@babel/generator": 7.21.5 - "@babel/helper-compilation-targets": 7.21.5(@babel/core@7.21.8) - "@babel/helper-module-transforms": 7.21.5 - "@babel/helpers": 7.21.5 - "@babel/parser": 7.21.8 - "@babel/template": 7.20.7 - "@babel/traverse": 7.21.5 - "@babel/types": 7.21.5 + "@babel/code-frame": 7.22.5 + "@babel/generator": 7.22.5 + "@babel/helper-compilation-targets": 7.22.5(@babel/core@7.22.5) + "@babel/helper-module-transforms": 7.22.5 + "@babel/helpers": 7.22.5 + "@babel/parser": 7.22.5 + "@babel/template": 7.22.5 + "@babel/traverse": 7.22.5 + "@babel/types": 7.22.5 convert-source-map: 1.9.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -123,253 +132,413 @@ packages: - supports-color dev: true - /@babel/generator@7.21.5: + /@babel/generator@7.22.5: resolution: { - integrity: sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w== + integrity: sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA== } engines: { node: ">=6.9.0" } dependencies: - "@babel/types": 7.21.5 + "@babel/types": 7.22.5 "@jridgewell/gen-mapping": 0.3.3 "@jridgewell/trace-mapping": 0.3.18 jsesc: 2.5.2 dev: true - /@babel/helper-compilation-targets@7.21.5(@babel/core@7.21.8): + /@babel/helper-compilation-targets@7.22.5(@babel/core@7.22.5): resolution: { - integrity: sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== + integrity: sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw== } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0 dependencies: - "@babel/compat-data": 7.21.7 - "@babel/core": 7.21.8 - "@babel/helper-validator-option": 7.21.0 - browserslist: 4.21.5 + "@babel/compat-data": 7.22.5 + "@babel/core": 7.22.5 + "@babel/helper-validator-option": 7.22.5 + browserslist: 4.21.9 lru-cache: 5.1.1 semver: 6.3.0 dev: true - /@babel/helper-environment-visitor@7.21.5: + /@babel/helper-environment-visitor@7.22.5: resolution: { - integrity: sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ== + integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== } engines: { node: ">=6.9.0" } dev: true - /@babel/helper-function-name@7.21.0: + /@babel/helper-function-name@7.22.5: resolution: { - integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== + integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== } engines: { node: ">=6.9.0" } dependencies: - "@babel/template": 7.20.7 - "@babel/types": 7.21.5 + "@babel/template": 7.22.5 + "@babel/types": 7.22.5 dev: true - /@babel/helper-hoist-variables@7.18.6: + /@babel/helper-hoist-variables@7.22.5: resolution: { - integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== } engines: { node: ">=6.9.0" } dependencies: - "@babel/types": 7.21.5 + "@babel/types": 7.22.5 dev: true - /@babel/helper-module-imports@7.21.4: + /@babel/helper-module-imports@7.22.5: resolution: { - integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg== + integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== } engines: { node: ">=6.9.0" } dependencies: - "@babel/types": 7.21.5 - dev: true + "@babel/types": 7.22.5 - /@babel/helper-module-transforms@7.21.5: + /@babel/helper-module-transforms@7.22.5: resolution: { - integrity: sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw== + integrity: sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw== } engines: { node: ">=6.9.0" } dependencies: - "@babel/helper-environment-visitor": 7.21.5 - "@babel/helper-module-imports": 7.21.4 - "@babel/helper-simple-access": 7.21.5 - "@babel/helper-split-export-declaration": 7.18.6 - "@babel/helper-validator-identifier": 7.19.1 - "@babel/template": 7.20.7 - "@babel/traverse": 7.21.5 - "@babel/types": 7.21.5 + "@babel/helper-environment-visitor": 7.22.5 + "@babel/helper-module-imports": 7.22.5 + "@babel/helper-simple-access": 7.22.5 + "@babel/helper-split-export-declaration": 7.22.5 + "@babel/helper-validator-identifier": 7.22.5 + "@babel/template": 7.22.5 + "@babel/traverse": 7.22.5 + "@babel/types": 7.22.5 transitivePeerDependencies: - supports-color dev: true - /@babel/helper-plugin-utils@7.21.5: + /@babel/helper-plugin-utils@7.22.5: resolution: { - integrity: sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== + integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== } engines: { node: ">=6.9.0" } dev: true - /@babel/helper-simple-access@7.21.5: + /@babel/helper-simple-access@7.22.5: resolution: { - integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg== + integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== } engines: { node: ">=6.9.0" } dependencies: - "@babel/types": 7.21.5 + "@babel/types": 7.22.5 dev: true - /@babel/helper-split-export-declaration@7.18.6: + /@babel/helper-split-export-declaration@7.22.5: resolution: { - integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + integrity: sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ== } engines: { node: ">=6.9.0" } dependencies: - "@babel/types": 7.21.5 + "@babel/types": 7.22.5 dev: true - /@babel/helper-string-parser@7.21.5: + /@babel/helper-string-parser@7.22.5: resolution: { - integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== + integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== } engines: { node: ">=6.9.0" } - dev: true - /@babel/helper-validator-identifier@7.19.1: + /@babel/helper-validator-identifier@7.22.5: resolution: { - integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== } engines: { node: ">=6.9.0" } - dev: true - /@babel/helper-validator-option@7.21.0: + /@babel/helper-validator-option@7.22.5: resolution: { - integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== + integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== } engines: { node: ">=6.9.0" } dev: true - /@babel/helpers@7.21.5: + /@babel/helpers@7.22.5: resolution: { - integrity: sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA== + integrity: sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q== } engines: { node: ">=6.9.0" } dependencies: - "@babel/template": 7.20.7 - "@babel/traverse": 7.21.5 - "@babel/types": 7.21.5 + "@babel/template": 7.22.5 + "@babel/traverse": 7.22.5 + "@babel/types": 7.22.5 transitivePeerDependencies: - supports-color dev: true - /@babel/highlight@7.18.6: + /@babel/highlight@7.22.5: resolution: { - integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== } engines: { node: ">=6.9.0" } dependencies: - "@babel/helper-validator-identifier": 7.19.1 + "@babel/helper-validator-identifier": 7.22.5 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true - /@babel/parser@7.21.8: + /@babel/parser@7.22.5: resolution: { - integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== + integrity: sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q== } engines: { node: ">=6.0.0" } hasBin: true dependencies: - "@babel/types": 7.21.5 + "@babel/types": 7.22.5 dev: true - /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.8): + /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.22.5): resolution: { - integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA== + integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g== } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 dependencies: - "@babel/core": 7.21.8 - "@babel/helper-plugin-utils": 7.21.5 + "@babel/core": 7.22.5 + "@babel/helper-plugin-utils": 7.22.5 dev: true - /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.8): + /@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.22.5): resolution: { - integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ== + integrity: sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w== } engines: { node: ">=6.9.0" } peerDependencies: "@babel/core": ^7.0.0-0 dependencies: - "@babel/core": 7.21.8 - "@babel/helper-plugin-utils": 7.21.5 + "@babel/core": 7.22.5 + "@babel/helper-plugin-utils": 7.22.5 dev: true - /@babel/template@7.20.7: + /@babel/runtime@7.22.5: resolution: { - integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== + integrity: sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== } engines: { node: ">=6.9.0" } dependencies: - "@babel/code-frame": 7.21.4 - "@babel/parser": 7.21.8 - "@babel/types": 7.21.5 + regenerator-runtime: 0.13.11 + dev: false + + /@babel/template@7.22.5: + resolution: + { + integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== + } + engines: { node: ">=6.9.0" } + dependencies: + "@babel/code-frame": 7.22.5 + "@babel/parser": 7.22.5 + "@babel/types": 7.22.5 dev: true - /@babel/traverse@7.21.5: + /@babel/traverse@7.22.5: resolution: { - integrity: sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw== + integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ== } engines: { node: ">=6.9.0" } dependencies: - "@babel/code-frame": 7.21.4 - "@babel/generator": 7.21.5 - "@babel/helper-environment-visitor": 7.21.5 - "@babel/helper-function-name": 7.21.0 - "@babel/helper-hoist-variables": 7.18.6 - "@babel/helper-split-export-declaration": 7.18.6 - "@babel/parser": 7.21.8 - "@babel/types": 7.21.5 + "@babel/code-frame": 7.22.5 + "@babel/generator": 7.22.5 + "@babel/helper-environment-visitor": 7.22.5 + "@babel/helper-function-name": 7.22.5 + "@babel/helper-hoist-variables": 7.22.5 + "@babel/helper-split-export-declaration": 7.22.5 + "@babel/parser": 7.22.5 + "@babel/types": 7.22.5 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color dev: true - /@babel/types@7.21.5: + /@babel/types@7.22.5: resolution: { - integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== + integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== } engines: { node: ">=6.9.0" } dependencies: - "@babel/helper-string-parser": 7.21.5 - "@babel/helper-validator-identifier": 7.19.1 + "@babel/helper-string-parser": 7.22.5 + "@babel/helper-validator-identifier": 7.22.5 to-fast-properties: 2.0.0 - dev: true + + /@emotion/babel-plugin@11.11.0: + resolution: + { + integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + } + dependencies: + "@babel/helper-module-imports": 7.22.5 + "@babel/runtime": 7.22.5 + "@emotion/hash": 0.9.1 + "@emotion/memoize": 0.8.1 + "@emotion/serialize": 1.1.2 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + dev: false + + /@emotion/cache@11.11.0: + resolution: + { + integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + } + dependencies: + "@emotion/memoize": 0.8.1 + "@emotion/sheet": 1.2.2 + "@emotion/utils": 1.2.1 + "@emotion/weak-memoize": 0.3.1 + stylis: 4.2.0 + dev: false + + /@emotion/hash@0.9.1: + resolution: + { + integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + } + dev: false + + /@emotion/is-prop-valid@1.2.1: + resolution: + { + integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== + } + dependencies: + "@emotion/memoize": 0.8.1 + dev: false + + /@emotion/memoize@0.8.1: + resolution: + { + integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + } + dev: false + + /@emotion/react@11.11.1(@types/react@18.2.14)(react@18.2.0): + resolution: + { + integrity: sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== + } + peerDependencies: + "@types/react": "*" + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@emotion/babel-plugin": 11.11.0 + "@emotion/cache": 11.11.0 + "@emotion/serialize": 1.1.2 + "@emotion/use-insertion-effect-with-fallbacks": 1.0.1(react@18.2.0) + "@emotion/utils": 1.2.1 + "@emotion/weak-memoize": 0.3.1 + "@types/react": 18.2.14 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + dev: false + + /@emotion/serialize@1.1.2: + resolution: + { + integrity: sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== + } + dependencies: + "@emotion/hash": 0.9.1 + "@emotion/memoize": 0.8.1 + "@emotion/unitless": 0.8.1 + "@emotion/utils": 1.2.1 + csstype: 3.1.2 + dev: false + + /@emotion/sheet@1.2.2: + resolution: + { + integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + } + dev: false + + /@emotion/styled@11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0): + resolution: + { + integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng== + } + peerDependencies: + "@emotion/react": ^11.0.0-rc.0 + "@types/react": "*" + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@emotion/babel-plugin": 11.11.0 + "@emotion/is-prop-valid": 1.2.1 + "@emotion/react": 11.11.1(@types/react@18.2.14)(react@18.2.0) + "@emotion/serialize": 1.1.2 + "@emotion/use-insertion-effect-with-fallbacks": 1.0.1(react@18.2.0) + "@emotion/utils": 1.2.1 + "@types/react": 18.2.14 + react: 18.2.0 + dev: false + + /@emotion/unitless@0.8.1: + resolution: + { + integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + } + dev: false + + /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0): + resolution: + { + integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + } + peerDependencies: + react: ">=16.8.0" + dependencies: + react: 18.2.0 + dev: false + + /@emotion/utils@1.2.1: + resolution: + { + integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + } + dev: false + + /@emotion/weak-memoize@0.3.1: + resolution: + { + integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + } + dev: false /@esbuild/android-arm64@0.17.19: resolution: @@ -635,7 +804,7 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.41.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.43.0): resolution: { integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== @@ -644,7 +813,7 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.41.0 + eslint: 8.43.0 eslint-visitor-keys: 3.4.1 dev: true @@ -676,18 +845,25 @@ packages: - supports-color dev: true - /@eslint/js@8.41.0: + /@eslint/js@8.43.0: resolution: { - integrity: sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA== + integrity: sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dev: true - /@humanwhocodes/config-array@0.11.8: + /@fontsource/roboto@5.0.3: resolution: { - integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== + integrity: sha512-jbZDFwEFARDlo8TqG7th/xjhuq87GYfFpFb+uxuy+0Ng1bhRVgBRWlLj8+WIKhCTOr+h4QXbjpybLWFLUirOwQ== + } + dev: false + + /@humanwhocodes/config-array@0.11.10: + resolution: + { + integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== } engines: { node: ">=10.10.0" } dependencies: @@ -775,6 +951,272 @@ packages: "@jridgewell/sourcemap-codec": 1.4.14 dev: true + /@mui/base@5.0.0-beta.4(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-ejhtqYJpjDgHGEljjMBQWZ22yEK0OzIXNa7toJmmXsP4TT3W7xVy8bTJ0TniPDf+JNjrsgfgiFTDGdlEhV1E+g== + } + engines: { node: ">=12.0.0" } + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@emotion/is-prop-valid": 1.2.1 + "@mui/types": 7.2.4(@types/react@18.2.14) + "@mui/utils": 5.13.6(react@18.2.0) + "@popperjs/core": 2.11.8 + "@types/react": 18.2.14 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + dev: false + + /@mui/base@5.0.0-beta.5(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-vy3TWLQYdGNecTaufR4wDNQFV2WEg6wRPi6BVbx6q1vP3K1mbxIn1+XOqOzfYBXjFHvMx0gZAo2TgWbaqfgvAA== + } + engines: { node: ">=12.0.0" } + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@emotion/is-prop-valid": 1.2.1 + "@mui/types": 7.2.4(@types/react@18.2.14) + "@mui/utils": 5.13.6(react@18.2.0) + "@popperjs/core": 2.11.8 + "@types/react": 18.2.14 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + dev: false + + /@mui/core-downloads-tracker@5.13.4: + resolution: + { + integrity: sha512-yFrMWcrlI0TqRN5jpb6Ma9iI7sGTHpytdzzL33oskFHNQ8UgrtPas33Y1K7sWAMwCrr1qbWDrOHLAQG4tAzuSw== + } + dev: false + + /@mui/icons-material@5.11.16(@mui/material@5.13.6)(@types/react@18.2.14)(react@18.2.0): + resolution: + { + integrity: sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A== + } + engines: { node: ">=12.0.0" } + peerDependencies: + "@mui/material": ^5.0.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@mui/material": 5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + "@types/react": 18.2.14 + react: 18.2.0 + dev: false + + /@mui/lab@5.0.0-alpha.134(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.13.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-GhvuM2dNOi6hzjbeGEocWVozgyyeUn7RBmZhLFtniROauxmPCZMcTsEU+GAxmpyYppqHuI8flP6tGKgMuEAK/g== + } + engines: { node: ">=12.0.0" } + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material": ^5.0.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@emotion/react": 11.11.1(@types/react@18.2.14)(react@18.2.0) + "@emotion/styled": 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0) + "@mui/base": 5.0.0-beta.4(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + "@mui/material": 5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + "@mui/system": 5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react@18.2.0) + "@mui/types": 7.2.4(@types/react@18.2.14) + "@mui/utils": 5.13.6(react@18.2.0) + "@types/react": 18.2.14 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + dev: false + + /@mui/material@5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-/c2ZApeQm2sTYdQXjqEnldaBMBcUEiyu2VRS6bS39ZeNaAcCLBQbYocLR46R+f0S5dgpBzB0T4AsOABPOFYZ5Q== + } + engines: { node: ">=12.0.0" } + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@emotion/react": 11.11.1(@types/react@18.2.14)(react@18.2.0) + "@emotion/styled": 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0) + "@mui/base": 5.0.0-beta.5(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + "@mui/core-downloads-tracker": 5.13.4 + "@mui/system": 5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react@18.2.0) + "@mui/types": 7.2.4(@types/react@18.2.14) + "@mui/utils": 5.13.6(react@18.2.0) + "@types/react": 18.2.14 + "@types/react-transition-group": 4.4.6 + clsx: 1.2.1 + csstype: 3.1.2 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + dev: false + + /@mui/private-theming@5.13.1(@types/react@18.2.14)(react@18.2.0): + resolution: + { + integrity: sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ== + } + engines: { node: ">=12.0.0" } + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@mui/utils": 5.13.6(react@18.2.0) + "@types/react": 18.2.14 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/styled-engine@5.13.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0): + resolution: + { + integrity: sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw== + } + engines: { node: ">=12.0.0" } + peerDependencies: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@emotion/cache": 11.11.0 + "@emotion/react": 11.11.1(@types/react@18.2.14)(react@18.2.0) + "@emotion/styled": 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0) + csstype: 3.1.2 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/system@5.13.6(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.14)(react@18.2.0): + resolution: + { + integrity: sha512-G3Xr28uLqU3DyF6r2LQkHGw/ku4P0AHzlKVe7FGXOPl7X1u+hoe2xxj8Vdiq/69II/mh9OP21i38yBWgWb7WgQ== + } + engines: { node: ">=12.0.0" } + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.22.5 + "@emotion/react": 11.11.1(@types/react@18.2.14)(react@18.2.0) + "@emotion/styled": 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.14)(react@18.2.0) + "@mui/private-theming": 5.13.1(@types/react@18.2.14)(react@18.2.0) + "@mui/styled-engine": 5.13.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) + "@mui/types": 7.2.4(@types/react@18.2.14) + "@mui/utils": 5.13.6(react@18.2.0) + "@types/react": 18.2.14 + clsx: 1.2.1 + csstype: 3.1.2 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/types@7.2.4(@types/react@18.2.14): + resolution: + { + integrity: sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA== + } + peerDependencies: + "@types/react": "*" + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@types/react": 18.2.14 + dev: false + + /@mui/utils@5.13.6(react@18.2.0): + resolution: + { + integrity: sha512-ggNlxl5NPSbp+kNcQLmSig6WVB0Id+4gOxhx644987v4fsji+CSXc+MFYLocFB/x4oHtzCUlSzbVHlJfP/fXoQ== + } + engines: { node: ">=12.0.0" } + peerDependencies: + react: ^17.0.0 || ^18.0.0 + dependencies: + "@babel/runtime": 7.22.5 + "@types/prop-types": 15.7.5 + "@types/react-is": 18.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-is: 18.2.0 + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: { @@ -805,10 +1247,10 @@ packages: fastq: 1.15.0 dev: true - /@picocss/pico@1.5.10: + /@popperjs/core@2.11.8: resolution: { - integrity: sha512-+LafMsrwPxXQMk6sI///TmSInCwwZmq+K7SikyL3N/4GhhwzyPC+TQLUEqmrLyjluR+uIpFFcqjty30Rtr6GxQ== + integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== } dev: false @@ -840,10 +1282,10 @@ packages: picomatch: 2.3.1 dev: true - /@tauri-apps/api@1.3.0: + /@tauri-apps/api@1.4.0: resolution: { - integrity: sha512-AH+3FonkKZNtfRtGrObY38PrzEj4d+1emCbwNGu0V2ENbXjlLHMZQlUh+Bhu/CRmjaIwZMGJ3yFvWaZZgTHoog== + integrity: sha512-Jd6HPoTM1PZSFIzq7FB8VmMu3qSSyo/3lSwLpoapW+lQ41CL5Dow2KryLg+gyazA/58DRWI9vu/XpEeHK4uMdw== } engines: { node: ">= 14.6.0", npm: ">= 6.6.0", yarn: ">= 1.19.1" } dev: false @@ -855,46 +1297,68 @@ packages: } dev: true - /@types/json-schema@7.0.11: + /@types/json-schema@7.0.12: resolution: { - integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== } dev: true + /@types/parse-json@4.0.0: + resolution: + { + integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + } + dev: false + /@types/prop-types@15.7.5: resolution: { integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== } - dev: true - /@types/react-dom@18.2.4: + /@types/react-dom@18.2.6: resolution: { - integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw== + integrity: sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A== } dependencies: - "@types/react": 18.2.6 + "@types/react": 18.2.14 dev: true - /@types/react@18.2.6: + /@types/react-is@18.2.1: + resolution: + { + integrity: sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw== + } + dependencies: + "@types/react": 18.2.14 + dev: false + + /@types/react-transition-group@4.4.6: resolution: { - integrity: sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA== + integrity: sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew== + } + dependencies: + "@types/react": 18.2.14 + dev: false + + /@types/react@18.2.14: + resolution: + { + integrity: sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g== } dependencies: "@types/prop-types": 15.7.5 "@types/scheduler": 0.16.3 csstype: 3.1.2 - dev: true /@types/scheduler@0.16.3: resolution: { integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== } - dev: true /@types/semver@7.5.0: resolution: @@ -903,10 +1367,10 @@ packages: } dev: true - /@typescript-eslint/eslint-plugin@5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.41.0)(typescript@5.0.4): + /@typescript-eslint/eslint-plugin@5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.1.3): resolution: { - integrity: sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw== + integrity: sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: @@ -918,26 +1382,26 @@ packages: optional: true dependencies: "@eslint-community/regexpp": 4.5.1 - "@typescript-eslint/parser": 5.59.6(eslint@8.41.0)(typescript@5.0.4) - "@typescript-eslint/scope-manager": 5.59.6 - "@typescript-eslint/type-utils": 5.59.6(eslint@8.41.0)(typescript@5.0.4) - "@typescript-eslint/utils": 5.59.6(eslint@8.41.0)(typescript@5.0.4) + "@typescript-eslint/parser": 5.60.0(eslint@8.43.0)(typescript@5.1.3) + "@typescript-eslint/scope-manager": 5.60.0 + "@typescript-eslint/type-utils": 5.60.0(eslint@8.43.0)(typescript@5.1.3) + "@typescript-eslint/utils": 5.60.0(eslint@8.43.0)(typescript@5.1.3) debug: 4.3.4 - eslint: 8.41.0 + eslint: 8.43.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - semver: 7.5.1 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + semver: 7.5.3 + tsutils: 3.21.0(typescript@5.1.3) + typescript: 5.1.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.59.6(eslint@8.41.0)(typescript@5.0.4): + /@typescript-eslint/parser@5.60.0(eslint@8.43.0)(typescript@5.1.3): resolution: { - integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA== + integrity: sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: @@ -947,31 +1411,31 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/scope-manager": 5.59.6 - "@typescript-eslint/types": 5.59.6 - "@typescript-eslint/typescript-estree": 5.59.6(typescript@5.0.4) + "@typescript-eslint/scope-manager": 5.60.0 + "@typescript-eslint/types": 5.60.0 + "@typescript-eslint/typescript-estree": 5.60.0(typescript@5.1.3) debug: 4.3.4 - eslint: 8.41.0 - typescript: 5.0.4 + eslint: 8.43.0 + typescript: 5.1.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@5.59.6: + /@typescript-eslint/scope-manager@5.60.0: resolution: { - integrity: sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ== + integrity: sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: - "@typescript-eslint/types": 5.59.6 - "@typescript-eslint/visitor-keys": 5.59.6 + "@typescript-eslint/types": 5.60.0 + "@typescript-eslint/visitor-keys": 5.60.0 dev: true - /@typescript-eslint/type-utils@5.59.6(eslint@8.41.0)(typescript@5.0.4): + /@typescript-eslint/type-utils@5.60.0(eslint@8.43.0)(typescript@5.1.3): resolution: { - integrity: sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ== + integrity: sha512-X7NsRQddORMYRFH7FWo6sA9Y/zbJ8s1x1RIAtnlj6YprbToTiQnM6vxcMu7iYhdunmoC0rUWlca13D5DVHkK2g== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: @@ -981,28 +1445,28 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/typescript-estree": 5.59.6(typescript@5.0.4) - "@typescript-eslint/utils": 5.59.6(eslint@8.41.0)(typescript@5.0.4) + "@typescript-eslint/typescript-estree": 5.60.0(typescript@5.1.3) + "@typescript-eslint/utils": 5.60.0(eslint@8.43.0)(typescript@5.1.3) debug: 4.3.4 - eslint: 8.41.0 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + eslint: 8.43.0 + tsutils: 3.21.0(typescript@5.1.3) + typescript: 5.1.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@5.59.6: + /@typescript-eslint/types@5.60.0: resolution: { - integrity: sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA== + integrity: sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dev: true - /@typescript-eslint/typescript-estree@5.59.6(typescript@5.0.4): + /@typescript-eslint/typescript-estree@5.60.0(typescript@5.1.3): resolution: { - integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA== + integrity: sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: @@ -1011,71 +1475,71 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/types": 5.59.6 - "@typescript-eslint/visitor-keys": 5.59.6 + "@typescript-eslint/types": 5.60.0 + "@typescript-eslint/visitor-keys": 5.60.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.1 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + semver: 7.5.3 + tsutils: 3.21.0(typescript@5.1.3) + typescript: 5.1.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.59.6(eslint@8.41.0)(typescript@5.0.4): + /@typescript-eslint/utils@5.60.0(eslint@8.43.0)(typescript@5.1.3): resolution: { - integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg== + integrity: sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - "@eslint-community/eslint-utils": 4.4.0(eslint@8.41.0) - "@types/json-schema": 7.0.11 + "@eslint-community/eslint-utils": 4.4.0(eslint@8.43.0) + "@types/json-schema": 7.0.12 "@types/semver": 7.5.0 - "@typescript-eslint/scope-manager": 5.59.6 - "@typescript-eslint/types": 5.59.6 - "@typescript-eslint/typescript-estree": 5.59.6(typescript@5.0.4) - eslint: 8.41.0 + "@typescript-eslint/scope-manager": 5.60.0 + "@typescript-eslint/types": 5.60.0 + "@typescript-eslint/typescript-estree": 5.60.0(typescript@5.1.3) + eslint: 8.43.0 eslint-scope: 5.1.1 - semver: 7.5.1 + semver: 7.5.3 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@5.59.6: + /@typescript-eslint/visitor-keys@5.60.0: resolution: { - integrity: sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q== + integrity: sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: - "@typescript-eslint/types": 5.59.6 + "@typescript-eslint/types": 5.60.0 eslint-visitor-keys: 3.4.1 dev: true - /@vitejs/plugin-react@4.0.0(vite@4.3.8): + /@vitejs/plugin-react@4.0.1(vite@4.3.9): resolution: { - integrity: sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ== + integrity: sha512-g25lL98essfeSj43HJ0o4DMp0325XK0ITkxpgChzJU/CyemgyChtlxfnRbjfwxDGCTRxTiXtQAsdebQXKMRSOA== } engines: { node: ^14.18.0 || >=16.0.0 } peerDependencies: vite: ^4.2.0 dependencies: - "@babel/core": 7.21.8 - "@babel/plugin-transform-react-jsx-self": 7.21.0(@babel/core@7.21.8) - "@babel/plugin-transform-react-jsx-source": 7.19.6(@babel/core@7.21.8) + "@babel/core": 7.22.5 + "@babel/plugin-transform-react-jsx-self": 7.22.5(@babel/core@7.22.5) + "@babel/plugin-transform-react-jsx-source": 7.22.5(@babel/core@7.22.5) react-refresh: 0.14.0 - vite: 4.3.8(sass@1.62.1) + vite: 4.3.9 transitivePeerDependencies: - supports-color dev: true - /acorn-jsx@5.3.2(acorn@8.8.2): + /acorn-jsx@5.3.2(acorn@8.9.0): resolution: { integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -1083,13 +1547,13 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.8.2 + acorn: 8.9.0 dev: true - /acorn@8.8.2: + /acorn@8.9.0: resolution: { - integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== } engines: { node: ">=0.4.0" } hasBin: true @@ -1123,7 +1587,6 @@ packages: engines: { node: ">=4" } dependencies: color-convert: 1.9.3 - dev: true /ansi-styles@4.3.0: resolution: @@ -1135,17 +1598,6 @@ packages: color-convert: 2.0.1 dev: true - /anymatch@3.1.3: - resolution: - { - integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - } - engines: { node: ">= 8" } - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: true - /argparse@2.0.1: resolution: { @@ -1226,26 +1678,30 @@ packages: engines: { node: ">= 0.4" } dev: true - /balanced-match@1.0.2: + /babel-plugin-macros@3.1.0: resolution: { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== } - dev: true + engines: { node: ">=10", npm: ">=6" } + dependencies: + "@babel/runtime": 7.22.5 + cosmiconfig: 7.1.0 + resolve: 1.22.2 + dev: false - /base64-js@1.5.1: + /balanced-match@1.0.2: resolution: { - integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } dev: true - /binary-extensions@2.2.0: + /base64-js@1.5.1: resolution: { - integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== } - engines: { node: ">=8" } dev: true /bl@4.1.0: @@ -1295,18 +1751,18 @@ packages: fill-range: 7.0.1 dev: true - /browserslist@4.21.5: + /browserslist@4.21.9: resolution: { - integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== + integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== } engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true dependencies: - caniuse-lite: 1.0.30001488 - electron-to-chromium: 1.4.402 - node-releases: 2.0.11 - update-browserslist-db: 1.0.11(browserslist@4.21.5) + caniuse-lite: 1.0.30001507 + electron-to-chromium: 1.4.440 + node-releases: 2.0.12 + update-browserslist-db: 1.0.11(browserslist@4.21.9) dev: true /buffer-from@1.1.2: @@ -1342,7 +1798,6 @@ packages: integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } engines: { node: ">=6" } - dev: true /camel-case@4.1.2: resolution: @@ -1351,13 +1806,13 @@ packages: } dependencies: pascal-case: 3.1.2 - tslib: 2.5.2 + tslib: 2.5.3 dev: true - /caniuse-lite@1.0.30001488: + /caniuse-lite@1.0.30001507: resolution: { - integrity: sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ== + integrity: sha512-SFpUDoSLCaE5XYL2jfqe9ova/pbQHEmbheDf5r4diNwbAgR3qxM9NQtfsiSscjqoya5K7kFcHPUQ+VsUkIJR4A== } dev: true @@ -1371,7 +1826,6 @@ packages: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - dev: true /chalk@4.1.2: resolution: @@ -1384,24 +1838,6 @@ packages: supports-color: 7.2.0 dev: true - /chokidar@3.5.3: - resolution: - { - integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - } - engines: { node: ">= 8.10.0" } - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true - /chownr@1.1.4: resolution: { @@ -1419,6 +1855,14 @@ packages: source-map: 0.6.1 dev: true + /clsx@1.2.1: + resolution: + { + integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + } + engines: { node: ">=6" } + dev: false + /color-convert@1.9.3: resolution: { @@ -1426,7 +1870,6 @@ packages: } dependencies: color-name: 1.1.3 - dev: true /color-convert@2.0.1: resolution: @@ -1443,7 +1886,6 @@ packages: { integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== } - dev: true /color-name@1.1.4: resolution: @@ -1522,7 +1964,20 @@ packages: { integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== } - dev: true + + /cosmiconfig@7.1.0: + resolution: + { + integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + } + engines: { node: ">=10" } + dependencies: + "@types/parse-json": 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + dev: false /cross-spawn@7.0.3: resolution: @@ -1562,7 +2017,6 @@ packages: { integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== } - dev: true /debug@4.3.4: resolution: @@ -1653,6 +2107,16 @@ packages: esutils: 2.0.3 dev: true + /dom-helpers@5.2.1: + resolution: + { + integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + } + dependencies: + "@babel/runtime": 7.22.5 + csstype: 3.1.2 + dev: false + /dom-serializer@1.4.1: resolution: { @@ -1699,7 +2163,7 @@ packages: } dependencies: no-case: 3.0.4 - tslib: 2.5.2 + tslib: 2.5.3 dev: true /dotenv-expand@8.0.3: @@ -1710,10 +2174,10 @@ packages: engines: { node: ">=12" } dev: true - /dotenv@16.0.3: + /dotenv@16.3.1: resolution: { - integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== } engines: { node: ">=12" } dev: true @@ -1726,13 +2190,13 @@ packages: engines: { node: ">=0.10.0" } hasBin: true dependencies: - jake: 10.8.6 + jake: 10.8.7 dev: true - /electron-to-chromium@1.4.402: + /electron-to-chromium@1.4.440: resolution: { - integrity: sha512-gWYvJSkohOiBE6ecVYXkrDgNaUjo47QEKK0kQzmWyhkH+yoYiG44bwuicTGNSIQRG3WDMsWVZJLRnJnLNkbWvA== + integrity: sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw== } dev: true @@ -1752,6 +2216,15 @@ packages: } dev: true + /error-ex@1.3.2: + resolution: + { + integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + } + dependencies: + is-arrayish: 0.2.1 + dev: false + /es-abstract@1.21.2: resolution: { @@ -1875,7 +2348,6 @@ packages: integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== } engines: { node: ">=0.8.0" } - dev: true /escape-string-regexp@4.0.0: resolution: @@ -1883,9 +2355,8 @@ packages: integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } engines: { node: ">=10" } - dev: true - /eslint-plugin-react-hooks@4.6.0(eslint@8.41.0): + /eslint-plugin-react-hooks@4.6.0(eslint@8.43.0): resolution: { integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== @@ -1894,10 +2365,10 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: - eslint: 8.41.0 + eslint: 8.43.0 dev: true - /eslint-plugin-react@7.32.2(eslint@8.41.0): + /eslint-plugin-react@7.32.2(eslint@8.43.0): resolution: { integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg== @@ -1910,7 +2381,7 @@ packages: array.prototype.flatmap: 1.3.1 array.prototype.tosorted: 1.1.1 doctrine: 2.1.0 - eslint: 8.41.0 + eslint: 8.43.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.3 minimatch: 3.1.2 @@ -1954,19 +2425,19 @@ packages: engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dev: true - /eslint@8.41.0: + /eslint@8.43.0: resolution: { - integrity: sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q== + integrity: sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } hasBin: true dependencies: - "@eslint-community/eslint-utils": 4.4.0(eslint@8.41.0) + "@eslint-community/eslint-utils": 4.4.0(eslint@8.43.0) "@eslint-community/regexpp": 4.5.1 "@eslint/eslintrc": 2.0.3 - "@eslint/js": 8.41.0 - "@humanwhocodes/config-array": 0.11.8 + "@eslint/js": 8.43.0 + "@humanwhocodes/config-array": 0.11.10 "@humanwhocodes/module-importer": 1.0.1 "@nodelib/fs.walk": 1.2.8 ajv: 6.12.6 @@ -2012,8 +2483,8 @@ packages: } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: - acorn: 8.8.2 - acorn-jsx: 5.3.2(acorn@8.8.2) + acorn: 8.9.0 + acorn-jsx: 5.3.2(acorn@8.9.0) eslint-visitor-keys: 3.4.1 dev: true @@ -2149,6 +2620,13 @@ packages: to-regex-range: 5.0.1 dev: true + /find-root@1.1.0: + resolution: + { + integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + } + dev: false + /find-up@5.0.0: resolution: { @@ -2229,7 +2707,6 @@ packages: { integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== } - dev: true /function.prototype.name@1.1.5: resolution: @@ -2409,7 +2886,6 @@ packages: integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== } engines: { node: ">=4" } - dev: true /has-flag@4.0.0: resolution: @@ -2462,7 +2938,6 @@ packages: engines: { node: ">= 0.4.0" } dependencies: function-bind: 1.1.1 - dev: true /he@1.2.0: resolution: @@ -2472,6 +2947,15 @@ packages: hasBin: true dev: true + /hoist-non-react-statics@3.3.2: + resolution: + { + integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + } + dependencies: + react-is: 16.13.1 + dev: false + /html-minifier-terser@6.1.0: resolution: { @@ -2486,7 +2970,7 @@ packages: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.17.4 + terser: 5.18.1 dev: true /ieee754@1.2.1: @@ -2514,13 +2998,6 @@ packages: sharp: 0.32.1 dev: true - /immutable@4.3.0: - resolution: - { - integrity: sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg== - } - dev: true - /import-fresh@3.3.0: resolution: { @@ -2530,7 +3007,6 @@ packages: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: true /imurmurhash@0.1.4: resolution: @@ -2587,6 +3063,13 @@ packages: is-typed-array: 1.1.10 dev: true + /is-arrayish@0.2.1: + resolution: + { + integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + } + dev: false + /is-arrayish@0.3.2: resolution: { @@ -2603,16 +3086,6 @@ packages: has-bigints: 1.0.2 dev: true - /is-binary-path@2.1.0: - resolution: - { - integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - } - engines: { node: ">=8" } - dependencies: - binary-extensions: 2.2.0 - dev: true - /is-boolean-object@1.1.2: resolution: { @@ -2639,7 +3112,6 @@ packages: } dependencies: has: 1.0.3 - dev: true /is-date-object@1.0.5: resolution: @@ -2773,10 +3245,10 @@ packages: } dev: true - /jake@10.8.6: + /jake@10.8.7: resolution: { - integrity: sha512-G43Ub9IYEFfu72sua6rzooi8V8Gz2lkfk48rW20vEWCGizeaEPlKB1Kh8JIA84yQbiAEfqlPmSpGgCKKxH3rDA== + integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w== } engines: { node: ">=10" } hasBin: true @@ -2812,6 +3284,13 @@ packages: hasBin: true dev: true + /json-parse-even-better-errors@2.3.1: + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + } + dev: false + /json-schema-traverse@0.4.1: resolution: { @@ -2868,6 +3347,13 @@ packages: type-check: 0.4.0 dev: true + /lines-and-columns@1.2.4: + resolution: + { + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + } + dev: false + /locate-path@6.0.0: resolution: { @@ -2900,7 +3386,7 @@ packages: integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== } dependencies: - tslib: 2.5.2 + tslib: 2.5.3 dev: true /lru-cache@5.1.1: @@ -3026,17 +3512,17 @@ packages: } dependencies: lower-case: 2.0.2 - tslib: 2.5.2 + tslib: 2.5.3 dev: true - /node-abi@3.40.0: + /node-abi@3.45.0: resolution: { - integrity: sha512-zNy02qivjjRosswoYmPi8hIKJRr8MpQyeKT6qlcq/OnOgA3Rhoae+IYOqsM9V5+JnHWmxKnWOT2GxvtqdtOCXA== + integrity: sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ== } engines: { node: ">=10" } dependencies: - semver: 7.5.1 + semver: 7.5.3 dev: true /node-addon-api@6.1.0: @@ -3056,19 +3542,11 @@ packages: he: 1.2.0 dev: true - /node-releases@2.0.11: - resolution: - { - integrity: sha512-+M0PwXeU80kRohZ3aT4J/OnR+l9/KD2nVLNNoRgFtnf+umQVFdGBAO2N8+nCnEi0xlh/Wk3zOGC+vNNx+uM79Q== - } - dev: true - - /normalize-path@3.0.0: + /node-releases@2.0.12: resolution: { - integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== } - engines: { node: ">=0.10.0" } dev: true /nth-check@2.1.1: @@ -3086,7 +3564,6 @@ packages: integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== } engines: { node: ">=0.10.0" } - dev: true /object-inspect@1.12.3: resolution: @@ -3213,7 +3690,7 @@ packages: } dependencies: dot-case: 3.0.4 - tslib: 2.5.2 + tslib: 2.5.3 dev: true /parent-module@1.0.1: @@ -3224,7 +3701,19 @@ packages: engines: { node: ">=6" } dependencies: callsites: 3.1.0 - dev: true + + /parse-json@5.2.0: + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + } + engines: { node: ">=8" } + dependencies: + "@babel/code-frame": 7.22.5 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: false /pascal-case@3.1.2: resolution: @@ -3233,7 +3722,7 @@ packages: } dependencies: no-case: 3.0.4 - tslib: 2.5.2 + tslib: 2.5.3 dev: true /path-exists@4.0.0: @@ -3265,7 +3754,6 @@ packages: { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== } - dev: true /path-type@4.0.0: resolution: @@ -3273,7 +3761,6 @@ packages: integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== } engines: { node: ">=8" } - dev: true /pathe@0.2.0: resolution: @@ -3297,10 +3784,10 @@ packages: engines: { node: ">=8.6" } dev: true - /postcss@8.4.23: + /postcss@8.4.24: resolution: { - integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA== + integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== } engines: { node: ^10 || ^12 || >=14 } dependencies: @@ -3323,7 +3810,7 @@ packages: minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 1.0.2 - node-abi: 3.40.0 + node-abi: 3.45.0 pump: 3.0.0 rc: 1.2.8 simple-get: 4.0.1 @@ -3357,7 +3844,6 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - dev: true /pump@3.0.0: resolution: @@ -3410,23 +3896,18 @@ packages: scheduler: 0.23.0 dev: false - /react-icons@4.8.0(react@18.2.0): + /react-is@16.13.1: resolution: { - integrity: sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg== + integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== } - peerDependencies: - react: "*" - dependencies: - react: 18.2.0 - dev: false - /react-is@16.13.1: + /react-is@18.2.0: resolution: { - integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== } - dev: true + dev: false /react-refresh@0.14.0: resolution: @@ -3436,10 +3917,27 @@ packages: engines: { node: ">=0.10.0" } dev: true - /react-virtuoso@4.3.7(react-dom@18.2.0)(react@18.2.0): + /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): resolution: { - integrity: sha512-XPNRzmhXUyBoXjPxNYdqD5wubNXtDIbBFbhTR4awx4yEC98EegM5RLeaghIK0BBAhZyRFu8sMvrPnwE12KLOJg== + integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + } + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + dependencies: + "@babel/runtime": 7.22.5 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react-virtuoso@4.3.11(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-0YrCvQ5GsIKRcN34GxrzhSJGuMNI+hGxWci5cTVuPQ8QWTEsrKfCyqm7YNBMmV3pu7onG1YVUBo86CyCXdejXg== } engines: { node: ">=10" } peerDependencies: @@ -3472,15 +3970,12 @@ packages: util-deprecate: 1.0.2 dev: true - /readdirp@3.6.0: + /regenerator-runtime@0.13.11: resolution: { - integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== } - engines: { node: ">=8.10.0" } - dependencies: - picomatch: 2.3.1 - dev: true + dev: false /regexp.prototype.flags@1.5.0: resolution: @@ -3508,7 +4003,18 @@ packages: integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== } engines: { node: ">=4" } - dev: true + + /resolve@1.22.2: + resolution: + { + integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + } + hasBin: true + dependencies: + is-core-module: 2.12.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: false /resolve@2.0.0-next.4: resolution: @@ -3540,10 +4046,10 @@ packages: glob: 7.2.3 dev: true - /rollup@3.22.0: + /rollup@3.25.1: resolution: { - integrity: sha512-imsigcWor5Y/dC0rz2q0bBt9PabcL3TORry2hAa6O6BuMvY71bqHyfReAz5qyAqiQATD1m70qdntqBfBQjVWpQ== + integrity: sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ== } engines: { node: ">=14.18.0", npm: ">=8.0.0" } hasBin: true @@ -3578,19 +4084,6 @@ packages: is-regex: 1.1.4 dev: true - /sass@1.62.1: - resolution: - { - integrity: sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A== - } - engines: { node: ">=14.0.0" } - hasBin: true - dependencies: - chokidar: 3.5.3 - immutable: 4.3.0 - source-map-js: 1.0.2 - dev: true - /scheduler@0.23.0: resolution: { @@ -3608,10 +4101,10 @@ packages: hasBin: true dev: true - /semver@7.5.1: + /semver@7.5.3: resolution: { - integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== + integrity: sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== } engines: { node: ">=10" } hasBin: true @@ -3631,7 +4124,7 @@ packages: detect-libc: 2.0.1 node-addon-api: 6.1.0 prebuild-install: 7.1.1 - semver: 7.5.1 + semver: 7.5.3 simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 @@ -3719,6 +4212,14 @@ packages: source-map: 0.6.1 dev: true + /source-map@0.5.7: + resolution: + { + integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + } + engines: { node: ">=0.10.0" } + dev: false + /source-map@0.6.1: resolution: { @@ -3812,6 +4313,13 @@ packages: engines: { node: ">=8" } dev: true + /stylis@4.2.0: + resolution: + { + integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + } + dev: false + /supports-color@5.5.0: resolution: { @@ -3820,7 +4328,6 @@ packages: engines: { node: ">=4" } dependencies: has-flag: 3.0.0 - dev: true /supports-color@7.2.0: resolution: @@ -3838,7 +4345,6 @@ packages: integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== } engines: { node: ">= 0.4" } - dev: true /tar-fs@2.1.1: resolution: @@ -3866,16 +4372,16 @@ packages: readable-stream: 3.6.2 dev: true - /terser@5.17.4: + /terser@5.18.1: resolution: { - integrity: sha512-jcEKZw6UPrgugz/0Tuk/PVyLAPfMBJf5clnGueo45wTweoV8yh7Q7PEkhkJ5uuUbC7zAxEcG3tqNr1bstkQ8nw== + integrity: sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ== } engines: { node: ">=10" } hasBin: true dependencies: "@jridgewell/source-map": 0.3.3 - acorn: 8.8.2 + acorn: 8.9.0 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -3893,7 +4399,6 @@ packages: integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== } engines: { node: ">=4" } - dev: true /to-regex-range@5.0.1: resolution: @@ -3912,14 +4417,14 @@ packages: } dev: true - /tslib@2.5.2: + /tslib@2.5.3: resolution: { - integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA== + integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== } dev: true - /tsutils@3.21.0(typescript@5.0.4): + /tsutils@3.21.0(typescript@5.1.3): resolution: { integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== @@ -3929,7 +4434,7 @@ packages: typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" dependencies: tslib: 1.14.1 - typescript: 5.0.4 + typescript: 5.1.3 dev: true /tunnel-agent@0.6.0: @@ -3970,12 +4475,12 @@ packages: is-typed-array: 1.1.10 dev: true - /typescript@5.0.4: + /typescript@5.1.3: resolution: { - integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== + integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== } - engines: { node: ">=12.20" } + engines: { node: ">=14.17" } hasBin: true dev: true @@ -3999,7 +4504,7 @@ packages: engines: { node: ">= 10.0.0" } dev: true - /update-browserslist-db@1.0.11(browserslist@4.21.5): + /update-browserslist-db@1.0.11(browserslist@4.21.9): resolution: { integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== @@ -4008,7 +4513,7 @@ packages: peerDependencies: browserslist: ">= 4.21.0" dependencies: - browserslist: 4.21.5 + browserslist: 4.21.9 escalade: 3.1.1 picocolors: 1.0.0 dev: true @@ -4042,7 +4547,7 @@ packages: - rollup dev: true - /vite-plugin-html@3.2.0(vite@4.3.8): + /vite-plugin-html@3.2.0(vite@4.3.9): resolution: { integrity: sha512-2VLCeDiHmV/BqqNn5h2V+4280KRgQzCFN47cst3WiNK848klESPQnzuC3okH5XHtgwHH/6s1Ho/YV6yIO0pgoQ== @@ -4054,7 +4559,7 @@ packages: colorette: 2.0.20 connect-history-api-fallback: 1.6.0 consola: 2.15.3 - dotenv: 16.0.3 + dotenv: 16.3.1 dotenv-expand: 8.0.3 ejs: 3.1.9 fast-glob: 3.2.12 @@ -4062,13 +4567,13 @@ packages: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 4.3.8(sass@1.62.1) + vite: 4.3.9 dev: true - /vite@4.3.8(sass@1.62.1): + /vite@4.3.9: resolution: { - integrity: sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ== + integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg== } engines: { node: ^14.18.0 || >=16.0.0 } hasBin: true @@ -4094,9 +4599,8 @@ packages: optional: true dependencies: esbuild: 0.17.19 - postcss: 8.4.23 - rollup: 3.22.0 - sass: 1.62.1 + postcss: 8.4.24 + rollup: 3.25.1 optionalDependencies: fsevents: 2.3.2 dev: true @@ -4169,21 +4673,18 @@ packages: } dev: true - /yocto-queue@0.1.0: + /yaml@1.10.2: resolution: { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== } - engines: { node: ">=10" } - dev: true + engines: { node: ">= 6" } + dev: false - github.com/tauri-apps/tauri-plugin-fs-watch/5876048ff3d3d23c8863a37a0f0e91ed90553026: + /yocto-queue@0.1.0: resolution: { - tarball: https://codeload.github.com/tauri-apps/tauri-plugin-fs-watch/tar.gz/5876048ff3d3d23c8863a37a0f0e91ed90553026 + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== } - name: tauri-plugin-fs-watch-api - version: 0.0.0 - dependencies: - "@tauri-apps/api": 1.3.0 - dev: false + engines: { node: ">=10" } + dev: true diff --git a/owmods_gui/frontend/src/assets/images/logo.png b/owmods_gui/frontend/src/assets/images/logo.png index 23ab4d1d2..c292af611 100644 Binary files a/owmods_gui/frontend/src/assets/images/logo.png and b/owmods_gui/frontend/src/assets/images/logo.png differ diff --git a/owmods_gui/frontend/src/assets/translations/english.json b/owmods_gui/frontend/src/assets/translations/english.json index 6d598ab6a..dad8f564d 100644 --- a/owmods_gui/frontend/src/assets/translations/english.json +++ b/owmods_gui/frontend/src/assets/translations/english.json @@ -1,17 +1,15 @@ { "ABOUT": "About", + "ACTIONS": "Actions", "ALERT_URL": "Alert URL", "ANY": "Any", "APP_TITLE": "Outer Wilds Mod Manager", + "APP_VERSION": "Version: $version$", "ARCHITECTURE": "Architecture: $arch$", "AUTO_ENABLE_DEPS": "Auto Enable Dependencies", "BROWSE": "Browse", - "BY": "v$version$ by $author$", - "Blue": "Blue", - "Blurple": "Blurple", + "BY": "by $author$", "CANCEL": "Cancel", - "CANT_LOAD": "Error While Loading", - "CLEAR_DOWNLOADS": "Clear All", "CLEAR_LOGS": "Clear Logs", "CONFIRM": "Confirm", "CONTINUE": "Continue", @@ -19,19 +17,21 @@ "DB_URL": "Database URL", "DEBUG_MODE": "Debug Mode", "DISABLE_ALL": "Disable All", + "DISABLE_MISSING_MODS": "Disable any mods that aren't in the list", "DISABLE_WARNING": "Don't Show Errors On Game Start", "DISCORD": "Discord", "DISMISS": "Dismiss", - "DONT_FIX": "Don't Fix", "DOWNLOADS": "Downloads", "Debug": "Debug", "DisabledDep": "Dependency $payload$ is installed, but it's disabled", "DuplicateMod": "A mod with this unique name was already loaded from $payload$", + "EDIT_OWML": "Edit OWML Install", "ENABLE_ALL": "Enable All", "ENABLE_DEPS_MESSAGE": "This mod has dependencies that are currently disabled\n Would you like to enable the dependencies as well?\n(You can always enable dependencies and skip this dialog in Settings)", - "EXPORT_MODS": "Export Mods", + "EXPORT_MODS": "Export Mod List", "Error": "Error", "FATAL_ERROR": "Fatal Error", + "FILE_PATH": "File Path", "FILTER": "Filter", "FIX": "Fix Issues", "FORCE_EXE": "Force Exe", @@ -40,19 +40,21 @@ "GET_MODS": "Get Mods", "GITHUB": "GitHub", "GUI_SETTINGS": "GUI Settings", - "GhostlyGreen": "Ghostly Green", - "Green": "Green", "HELP": "Help", + "IMPORT": "Import", + "IMPORT_MODS": "Import Mod List", + "IMPORT_MODS_EXPLANATION": "Select a JSON file generated by \"Export Mod List\" to install mods from.", "INCREMENTAL_GC": "Incremental Garbage Collection", "INSTALL": "Install", "INSTALLED_MODS": "Installed Mods", "INSTALL_FROM": "Install From", "INSTALL_OWML": "Install OWML", + "INSTALL_OWML_PRERELEASE": "Install Prerelease Version Of OWML", "INSTALL_WARNING": "Please be careful when downloading mods from sources other than the default database, make sure the link or zip file comes from a trustworthy source such as the Modding Discord.", "INVALID_OWML": "Invalid OWML path, please select the folder containing OWML.Manifest.json", "Info": "Info", "InvalidManifest": "Invalid manifest file: \"$payload$\"", - "JSON": "JSON File", + "JSON_FILE": "JSON File", "LANGUAGE": "Language", "LAUNCH_ANYWAY": "Issues have been detected with your mod configuration that may result in broken or unloaded mods, continue nevertheless?", "LET_OWML_HANDLE_LOGS": "Let OWML Handle Logs", @@ -60,31 +62,31 @@ "LOGS": "Logs", "LOGS_TITLE": "Game Logs (Port $port$)", "LOG_COUNT": "$count$ Log(s)", + "LOG_MESSAGE": "Message", "LOG_MULTI_WINDOW": "Use Multiple Windows For Logs", - "MOD_HAS_ERRORS": "This mod has errors, click to view", - "MOD_HAS_WARNINGS": "This mod may have issues, click to view", "MORE": "More", "Message": "Message", "MissingDLL": "Unable to find DLL file specified (\"$payload$\")", "MissingDep": "Missing dependency: $payload$", - "NO_DESCRIPTION": "No Description Provided", + "NAME": "Name", "NO_DOWNLOADS": "No Downloads", "NO_MODS": "No Mods Installed, Click \"Get Mods\" To Grab Some!", + "NO_REMOTE_MODS": "No Mods Found In The Database, Check Your Network Connection and Database URL", "NO_UPDATES": "No Updates Available, Check Back Soon!", "OK": "Ok", - "OPEN_WEBSITE": "Show On Website", + "OPEN_README": "Show On Website", + "OPEN_WEBSITE": "Browse Mods Website", + "OUTDATED": "Outdated", "OWML_PATH": "OWML Path", "OWML_SETTINGS": "OWML Settings", "OWML_SETUP_MESSAGE": "You'll need to setup OWML to continue, please select an option below.", - "Orange": "Orange", + "OWML_INSTALL_ERROR": "There was an error downloading OWML, please check your network connection and try again.", + "OWML_NO_PRERELEASE": "There is no prerelease for OWML available", "Outdated": "This mod is outdated, consider updating (latest version is v$payload$)", "PLATFORM": "Platform: $platform$", "PREPATCHER_WARNING": "$name$ possibly modified game files. In order to disable it completely, use the \"verify game files\" option in Steam / Epic. Check $name$'s readme for more information.", "PREPATCHER_WARNING_TITLE": "Warning for $name$", "PRERELEASE_WARNING": "Prereleases are experimental versions of mods that may not work correctly. Are you sure you want to install?", - "Pink": "Pink", - "Purple": "Purple", - "RAINBOW": "Rainbow Mode", "REFRESH": "Refresh", "RESET": "Reset", "RUN_GAME": "Run Game", @@ -92,13 +94,13 @@ "SEARCH": "Search Mods", "SEARCH_LOGS": "Search Logs", "SELECT": "Select $name$", + "SENDER": "Sender", "SETTINGS": "Settings", "SETUP": "Setup", "SHOW_FOLDER": "Open Folder", "Success": "Success", - "THEME": "Theme", "TOOLTIP_ALERT_URL": "URL used to get alerts to display in the Mod Manager.", - "TOOLTIP_AUTO_ENABLE_DEPS": "When enabling a mod that has dependencies that are disabled, enable the dependencies without a prompt.", + "TOOLTIP_AUTO_ENABLE_DEPS": "When enabling a mod that has disabled dependencies, enable the dependencies without a prompt.", "TOOLTIP_DATABASE_URL": "URL used to check for updates for mods and OWML", "TOOLTIP_DISABLE_WARNING": "Any mod issues (Missing Dependencies, Conflicts, etc) will be ignored on launch", "TOOLTIP_FORCE_EXE": "If enabled, launches game exe instead of launching via Steam / Epic. Needs to be disabled for DLC to work via the Epic store.", @@ -108,7 +110,7 @@ "TOOLTIP_LOG_MULTI_WINDOW": "Makes launching multiple instances of the game open multiple windows instead of all logging to one", "TOOLTIP_OWML_DEBUG_MODE": "Makes OWML log more stuff", "TOOLTIP_OWML_PATH": "Must be an absolute path. All mods are installed in a 'Mods' folder inside this.", - "TOOLTIP_WATCH_FS": "Watches OWML's Mods folder for changes in files and refreshes if detected", + "TOOLTIP_WATCH_FS": "Watches OWML's Mods folder and settings for changes and refreshes if detected", "UNINSTALL": "Uninstall", "UNINSTALL_CONFIRM": "Are you sure you want to uninstall $name$?", "UNIQUE_NAME": "Unique Name", @@ -118,14 +120,11 @@ "UPDATING_ALL": "Updating All Mods", "URL": "URL", "USE_PRERELEASE": "Use Prerelease $version$", - "VALIDATION_FIX_MESSAGE": "These issues can be fixed automatically, press \"Fix Issues\".", - "VALIDATION_HEADER": "Errors/Warnings For $name$", - "VALIDATION_MESSAGE": "The following issues were detected with $name$", - "VERSION": "Version: $version$", - "WATCH_FS": "Auto-Refresh When Mods Folder Changes", + "USE_PRERELEASE_CHECKBOX": "Use Prerelease (If Available)", + "VALIDATION_FIX_MESSAGE": "These issues can be fixed automatically, press \"Fix Issues\". (The Wrench and Hammer)", + "VERSION": "Version", + "WATCH_FS": "Auto-Refresh When Mods Folder or Settings Change", "Warning": "Warning", - "White": "White", - "Yellow": "Yellow", "ZIP": "Zip File", "_": "(MISSING $key$): $fallback$" } diff --git a/owmods_gui/frontend/src/assets/translations/portuguese-br.json b/owmods_gui/frontend/src/assets/translations/portuguese-br.json index 21e79f4ac..d7e1b6bd1 100644 --- a/owmods_gui/frontend/src/assets/translations/portuguese-br.json +++ b/owmods_gui/frontend/src/assets/translations/portuguese-br.json @@ -6,11 +6,7 @@ "ARCHITECTURE": "Arquitetura: $arch$", "BROWSE": "Navegar", "BY": "v$version$ por $author$", - "Blue": "Azul", - "Blurple": "Azuloxo", "CANCEL": "Cancelar", - "CANT_LOAD": "Erro ao Carregar", - "CLEAR_DOWNLOADS": "Limpar Todos", "CLEAR_LOGS": "Limpar Logs", "CONFIRM": "Confirmar", "CONTINUE": "Continuar", @@ -21,7 +17,6 @@ "DISABLE_WARNING": "Desativar Avisos de Problema com Inicializaรงรฃo do Jogo", "DISCORD": "Discord", "DISMISS": "Ignorar", - "DONT_FIX": "Nรฃo Consertar", "DOWNLOADS": "Downloads", "Debug": "Depurar", "DisabledDep": "Dependรชncia $payload$ estรก instalada, mas desativada", @@ -38,8 +33,6 @@ "GET_MODS": "Adquira Mods", "GITHUB": "GitHub", "GUI_SETTINGS": "Configuraรงรตes Grรกficas Usuรกrio", - "GhostlyGreen": "Verde Fantasmagรณrico", - "Green": "Verde", "HELP": "Ajuda", "INCREMENTAL_GC": "Garbage Collector Incremental", "INSTALL": "Instalar", @@ -58,13 +51,10 @@ "LOGS_TITLE": "Logs do Jogo (Porta $port$)", "LOG_COUNT": "$count$ Log(s)", "LOG_MULTI_WINDOW": "Usar Mรบltiplas Janelas Para Logs", - "MOD_HAS_ERRORS": "Esse mod tem erros, clique para visualizar", - "MOD_HAS_WARNINGS": "Esse mod talvez tenha problemas, clique para visualizar", "MORE": "Mais", "Message": "Mensagem", "MissingDLL": "Incapaz de encontrar arquivo DLL especificado (\"$payload$\")", "MissingDep": "Faltando dependรชncia: $payload$", - "NO_DESCRIPTION": "Descriรงรฃo Nรฃo Oferecida", "NO_DOWNLOADS": "Sem Baixados", "NO_MODS": "Nenhum Mod Instalado, Clique \"Adquira Mods\" Para Baixar Alguns!", "NO_UPDATES": "Nenhuma Atualizaรงรฃo Disponรญvel, Cheque Mais Tarde!", @@ -73,12 +63,8 @@ "OWML_PATH": "Diretรณrio do OWML", "OWML_SETTINGS": "Configuraรงรตes do OWML", "OWML_SETUP_MESSAGE": "Vocรช precisa configurar OWML antes de continuar, selecione uma opรงรฃo abaixo.", - "Orange": "Laranja", "PLATFORM": "Plataforma: $platform$", "PRERELEASE_WARNING": "Pre-lanรงamentos sรฃo versรตes experimentais de mods que podem nรฃo funcionar corretamente. Vocรช tem certeza que quer instalar?", - "Pink": "Roza", - "Purple": "Roxo", - "RAINBOW": "Modo Arco-รญris", "REFRESH": "Atualizar", "RESET": "Resetar", "RUN_GAME": "Rodar Jogo", @@ -90,7 +76,6 @@ "SETUP": "Configurar", "SHOW_FOLDER": "Abrir Diretรณrio", "Success": "Sucesso", - "THEME": "Tema", "UNINSTALL": "Desinstalar", "UNINSTALL_CONFIRM": "Vocรช tem certeza que quer desinstalar $name$?", "UNIQUE_NAME": "Nome รšnico", @@ -101,13 +86,9 @@ "URL": "URL", "USE_PRERELEASE": "Usar Pre-lanรงamento $version$", "VALIDATION_FIX_MESSAGE": "Esses problemas podem ser automaticamente concertados, pressione \"Consertar Problemas\"", - "VALIDATION_HEADER": "Erros/Avisos Para $name$", - "VALIDATION_MESSAGE": "Os seguintes problemas foram detectados com $name$", "VERSION": "Versรฃo: $version$", "WATCH_FS": "Veja Sistema de Arquivos", "Warning": "Avisos", - "White": "Branco", - "Yellow": "Amarelo", "ZIP": "Arquivo ZIP", "_": "CHAVE DE TRADUร‡รƒO FALTANDO $key$: $fallback$" } diff --git a/owmods_gui/frontend/src/assets/translations/template.json b/owmods_gui/frontend/src/assets/translations/template.json index 7fdd9e042..6db622c56 100644 --- a/owmods_gui/frontend/src/assets/translations/template.json +++ b/owmods_gui/frontend/src/assets/translations/template.json @@ -1,17 +1,15 @@ { "ABOUT": "", + "ACTIONS": "", "ALERT_URL": "", "ANY": "", "APP_TITLE": "", + "APP_VERSION": "", "ARCHITECTURE": "", "AUTO_ENABLE_DEPS": "", "BROWSE": "", "BY": "", - "Blue": "", - "Blurple": "", "CANCEL": "", - "CANT_LOAD": "", - "CLEAR_DOWNLOADS": "", "CLEAR_LOGS": "", "CONFIRM": "", "CONTINUE": "", @@ -19,19 +17,21 @@ "DB_URL": "", "DEBUG_MODE": "", "DISABLE_ALL": "", + "DISABLE_MISSING_MODS": "", "DISABLE_WARNING": "", "DISCORD": "", "DISMISS": "", - "DONT_FIX": "", "DOWNLOADS": "", "Debug": "", "DisabledDep": "", "DuplicateMod": "", + "EDIT_OWML": "", "ENABLE_ALL": "", "ENABLE_DEPS_MESSAGE": "", "EXPORT_MODS": "", "Error": "", "FATAL_ERROR": "", + "FILE_PATH": "", "FILTER": "", "FIX": "", "FORCE_EXE": "", @@ -40,19 +40,21 @@ "GET_MODS": "", "GITHUB": "", "GUI_SETTINGS": "", - "GhostlyGreen": "", - "Green": "", "HELP": "", + "IMPORT": "", + "IMPORT_MODS": "", + "IMPORT_MODS_EXPLANATION": "", "INCREMENTAL_GC": "", "INSTALL": "", "INSTALLED_MODS": "", "INSTALL_FROM": "", "INSTALL_OWML": "", + "INSTALL_OWML_PRERELEASE": "", "INSTALL_WARNING": "", "INVALID_OWML": "", "Info": "", "InvalidManifest": "", - "JSON": "", + "JSON_FILE": "", "LANGUAGE": "", "LAUNCH_ANYWAY": "", "LET_OWML_HANDLE_LOGS": "", @@ -60,31 +62,31 @@ "LOGS": "", "LOGS_TITLE": "", "LOG_COUNT": "", + "LOG_MESSAGE": "", "LOG_MULTI_WINDOW": "", - "MOD_HAS_ERRORS": "", - "MOD_HAS_WARNINGS": "", "MORE": "", "Message": "", "MissingDLL": "", "MissingDep": "", - "NO_DESCRIPTION": "", + "NAME": "", "NO_DOWNLOADS": "", "NO_MODS": "", + "NO_REMOTE_MODS": "", "NO_UPDATES": "", "OK": "", + "OPEN_README": "", "OPEN_WEBSITE": "", + "OUTDATED": "", "OWML_PATH": "", "OWML_SETTINGS": "", "OWML_SETUP_MESSAGE": "", - "Orange": "", + "OWML_INSTALL_ERROR": "", + "OWML_NO_PRERELEASE": "", "Outdated": "", "PLATFORM": "", "PREPATCHER_WARNING": "", "PREPATCHER_WARNING_TITLE": "", "PRERELEASE_WARNING": "", - "Pink": "", - "Purple": "", - "RAINBOW": "", "REFRESH": "", "RESET": "", "RUN_GAME": "", @@ -92,11 +94,11 @@ "SEARCH": "", "SEARCH_LOGS": "", "SELECT": "", + "SENDER": "", "SETTINGS": "", "SETUP": "", "SHOW_FOLDER": "", "Success": "", - "THEME": "", "TOOLTIP_ALERT_URL": "", "TOOLTIP_AUTO_ENABLE_DEPS": "", "TOOLTIP_DATABASE_URL": "", @@ -118,14 +120,11 @@ "UPDATING_ALL": "", "URL": "", "USE_PRERELEASE": "", + "USE_PRERELEASE_CHECKBOX": "", "VALIDATION_FIX_MESSAGE": "", - "VALIDATION_HEADER": "", - "VALIDATION_MESSAGE": "", "VERSION": "", "WATCH_FS": "", "Warning": "", - "White": "", - "Yellow": "", "ZIP": "", "_": "" } diff --git a/owmods_gui/frontend/src/assets/translations/wario.json b/owmods_gui/frontend/src/assets/translations/wario.json index 99d499110..b55855108 100644 --- a/owmods_gui/frontend/src/assets/translations/wario.json +++ b/owmods_gui/frontend/src/assets/translations/wario.json @@ -1,3 +1,4 @@ { + "APP_TITLE": "Wah! (APP_TITLE)", "_": "Wah! ($key$)" } diff --git a/owmods_gui/frontend/src/commands.ts b/owmods_gui/frontend/src/commands.ts index 6a2ca1774..c92dfd0f8 100644 --- a/owmods_gui/frontend/src/commands.ts +++ b/owmods_gui/frontend/src/commands.ts @@ -33,7 +33,7 @@ const commandInfo = { getDefaultConfigs: $>("get_defaults"), getLocalMods: $>("get_local_mods"), getRemoteMods: $>("get_remote_mods"), - getUpdatableMods: $>("get_updatable_mods"), + getUpdatableMods: $>("get_updatable_mods"), getLocalMod: $>("get_local_mod"), getRemoteMod: $>("get_remote_mod"), getLogLine: $>("get_game_message"), @@ -49,7 +49,7 @@ const commandInfo = { installMod: $>("install_mod"), installUrl: $>("install_url"), installZip: $>("install_zip"), - installOwml: $("install_owml"), + installOwml: $>("install_owml"), setOwml: $>("set_owml"), saveConfig: $>("save_config"), saveGuiConfig: $>("save_gui_config"), @@ -71,11 +71,10 @@ const commandInfo = { > >("get_log_lines"), exportMods: $>("export_mods"), - importMods: $>("import_mods"), + importMods: $>("import_mods"), fixDeps: $>("fix_mod_deps"), checkDBForIssues: $>("db_has_issues"), getAlert: $>("get_alert"), - getWatcherPaths: $>("get_watcher_paths"), popProtocolURL: $("pop_protocol_url"), checkOWML: $>("check_owml"), getDownloads: $>("get_downloads"), diff --git a/owmods_gui/frontend/src/components/MainApp.tsx b/owmods_gui/frontend/src/components/MainApp.tsx deleted file mode 100644 index 448ca250e..000000000 --- a/owmods_gui/frontend/src/components/MainApp.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import Nav from "@components/nav/Nav"; -import Tabs from "@components/tabs/Tabs"; -import { getCurrent } from "@tauri-apps/api/window"; -import { useCallback, useEffect, useRef } from "react"; -import { TranslationContext, TranslationMap } from "@components/common/TranslationContext"; - -import OwmlSetupModal from "./modals/OwmlSetupModal"; -import { listen } from "@tauri-apps/api/event"; -import { startConsoleLogListen } from "../logging"; -import { commands, hooks } from "@commands"; -import { useTheme } from "@hooks"; -import { Theme } from "@types"; -import CenteredSpinner from "./common/CenteredSpinner"; -import AlertBar from "./alerts/AlertBar"; -import { ModalHandle } from "./modals/Modal"; - -startConsoleLogListen(); - -const App = () => { - const [status, guiConfig, err] = hooks.getGuiConfig("GUI_CONFIG_RELOAD"); - useTheme(guiConfig?.theme ?? Theme.White, guiConfig?.rainbow ?? false); - const owmlRef = useRef(); - - if (import.meta.env.DEV) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - window._DEBUG_OPEN_SETUP = () => openOwmlSetup.current(); - } - - useEffect(() => { - commands.initialSetup(); - }, []); - - useEffect(() => { - getCurrent() - .setTitle( - TranslationMap[guiConfig?.language ?? "English"]["APP_TITLE"] ?? - "Outer Wilds Mod Manager" - ) - .catch(console.warn); - }, [guiConfig?.language]); - - const owmlCheck = useCallback(() => { - commands.checkOWML().then((valid) => { - if (!valid) owmlRef.current?.open(); - }); - }, []); - - useEffect(() => { - if (status === "Done") { - owmlCheck(); - } - }, [status, owmlCheck]); - - useEffect(() => { - let cancelled = false; - listen("OWML_CONFIG_RELOAD", () => { - if (cancelled) return; - owmlCheck(); - }); - return () => { - cancelled = true; - }; - }, [owmlCheck]); - - if (status === "Loading" && guiConfig === null) { - return ; - } else if (status === "Error") { - return

Error loading configuration: {err!.toString()}

; - } else { - return ( - -
- -
- -
- -
-
- ); - } -}; - -export default App; diff --git a/owmods_gui/frontend/src/components/alerts/AlertBar.tsx b/owmods_gui/frontend/src/components/alerts/AlertBar.tsx deleted file mode 100644 index 3cb3c3dbc..000000000 --- a/owmods_gui/frontend/src/components/alerts/AlertBar.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { hooks } from "@commands"; -import { memo } from "react"; - -const AlertBar = memo(function AlertBar() { - const [status, alert, err] = hooks.getAlert("CONFIG_RELOAD"); - - if (status === "Loading") { - return <>; - } else if (status === "Error") { - console.error(err); - return <>; - } else { - if (alert!.enabled) { - return {alert!.message}; - } else { - return <>; - } - } -}); - -export default AlertBar; diff --git a/owmods_gui/frontend/src/components/common/BaseApp.tsx b/owmods_gui/frontend/src/components/common/BaseApp.tsx new file mode 100644 index 000000000..f0277f688 --- /dev/null +++ b/owmods_gui/frontend/src/components/common/BaseApp.tsx @@ -0,0 +1,48 @@ +import { ThemeProvider } from "@emotion/react"; +import { ErrorRounded } from "@mui/icons-material"; +import { CssBaseline, Box, CircularProgress, Typography } from "@mui/material"; +import theme from "../../theme"; +import { TranslationContext } from "./TranslationContext"; +import { Language } from "@types"; +import { ReactNode, memo } from "react"; + +export interface BaseAppProps { + isLoading: boolean; + children: ReactNode; + language?: Language; + fatalError?: string; +} + +const BaseApp = memo(function BaseApp(props: BaseAppProps) { + return ( + + + {props.isLoading ? ( + + {props.isLoading ? ( + + ) : ( + + Fatal Error
{props.fatalError} +
+ )} +
+ ) : ( + + + {props.children} + + + )} +
+
+ ); +}); + +export default BaseApp; diff --git a/owmods_gui/frontend/src/components/common/CenteredSpinner.tsx b/owmods_gui/frontend/src/components/common/CenteredSpinner.tsx deleted file mode 100644 index 299ca6a33..000000000 --- a/owmods_gui/frontend/src/components/common/CenteredSpinner.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import Spinner, { SpinnerProps } from "@components/common/Spinner"; - -const CenteredSpinner = (props: SpinnerProps) => { - return ; -}; - -export default CenteredSpinner; diff --git a/owmods_gui/frontend/src/components/common/FileInput.tsx b/owmods_gui/frontend/src/components/common/FileInput.tsx index 8383bddf4..f41a4f9e7 100644 --- a/owmods_gui/frontend/src/components/common/FileInput.tsx +++ b/owmods_gui/frontend/src/components/common/FileInput.tsx @@ -1,24 +1,19 @@ import { useGetTranslation } from "@hooks"; import { dialog } from "@tauri-apps/api"; -import { IconType } from "react-icons"; -import { BsFolderFill } from "react-icons/bs"; -import Icon from "./Icon"; -import { type TranslationKey } from "./TranslationContext"; +import { FolderRounded } from "@mui/icons-material"; +import { Box, TextField, Button, useTheme } from "@mui/material"; export interface FileInputProps { dialogOptions: T; id: string; label?: string; - className?: string; - browseButtonIcon?: IconType; value?: string; onChange?: (path: string) => void; - tooltip?: string; - tooltipPlacement?: string; } const FileInput = (openFunc: (options?: T) => Promise) => function FileInput(props: FileInputProps) { + const theme = useTheme(); const getTranslation = useGetTranslation(); const onBrowse = () => { @@ -30,27 +25,19 @@ const FileInput = (openFunc: (options?: T) => Promise - -
- props.onChange?.(e.target.value)} - /> - -
- + + props.onChange?.(e.target.value)} + id={props.id} + label={props.label} + sx={{ flexGrow: 1 }} + /> + + ); }; diff --git a/owmods_gui/frontend/src/components/common/FilterInput.tsx b/owmods_gui/frontend/src/components/common/FilterInput.tsx new file mode 100644 index 000000000..bf992ebe4 --- /dev/null +++ b/owmods_gui/frontend/src/components/common/FilterInput.tsx @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from "react"; +import { InputAdornment, IconButton, TextField } from "@mui/material"; +import { Close as CloseIcon, Search as SearchIcon } from "@mui/icons-material"; +import { useDebounce } from "@hooks"; + +export interface FilterInputProps { + value: string; + label: string; + onChange: (value: string) => void; + [rest: string | number | symbol]: unknown; +} + +const FilterInput: React.FunctionComponent = ({ + value, + onChange, + label, + ...rest +}) => { + const [filterText, setFilterText] = useState(value); + const debouncedFilterText = useDebounce(filterText, 200); + + useEffect(() => { + onChange(debouncedFilterText); + }, [debouncedFilterText, onChange]); + + // Instantly reflect changes on clear, don't debounce + const onClear = () => { + setFilterText(""); + onChange(""); + }; + + return ( + { + setFilterText(currentTarget.value); + }} + value={filterText} + placeholder={label} + variant="outlined" + {...rest} + InputProps={{ + startAdornment: ( + + + + ), + endAdornment: filterText !== "" && ( + + + + + + ) + }} + /> + ); +}; + +export default FilterInput; diff --git a/owmods_gui/frontend/src/components/common/Icon.tsx b/owmods_gui/frontend/src/components/common/Icon.tsx deleted file mode 100644 index b0487c10d..000000000 --- a/owmods_gui/frontend/src/components/common/Icon.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { memo } from "react"; -import { IconType } from "react-icons"; - -export interface IconProps { - iconType: IconType; - label?: string; - iconClassName?: string; -} - -// "Pure" icon component, use to prevent expensive rerenders -const Icon = memo( - function Icon(props: IconProps) { - return ( - <> - {props.iconType({ className: props.iconClassName })} - {props.label} - - ); - }, - (prev, next) => prev.label === next.label && prev.iconClassName === next.iconClassName -); - -export default Icon; diff --git a/owmods_gui/frontend/src/components/common/ODTooltip.tsx b/owmods_gui/frontend/src/components/common/ODTooltip.tsx new file mode 100644 index 000000000..dbf6795ce --- /dev/null +++ b/owmods_gui/frontend/src/components/common/ODTooltip.tsx @@ -0,0 +1,18 @@ +import { useState } from "react"; +import Tooltip, { TooltipProps } from "@mui/material/Tooltip"; + +const ODTooltip = ({ children, ...rest }: TooltipProps) => { + const [renderTooltip, setRenderTooltip] = useState(false); + + return ( +
renderTooltip && setRenderTooltip(false)} + onMouseEnter={() => !renderTooltip && setRenderTooltip(true)} + > + {!renderTooltip && children} + {renderTooltip && {children}} +
+ ); +}; + +export default ODTooltip; diff --git a/owmods_gui/frontend/src/components/common/Spinner.tsx b/owmods_gui/frontend/src/components/common/Spinner.tsx deleted file mode 100644 index 291ad1a3f..000000000 --- a/owmods_gui/frontend/src/components/common/Spinner.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { CSSProperties } from "react"; - -export interface SpinnerProps { - className?: string; - style?: CSSProperties; - children?: string; -} - -const Spinner = (props: SpinnerProps) => { - return ( -

- {props.children ?? ""} -

- ); -}; - -export default Spinner; diff --git a/owmods_gui/frontend/src/components/downloads/Downloads.tsx b/owmods_gui/frontend/src/components/downloads/Downloads.tsx deleted file mode 100644 index 2bfc8e714..000000000 --- a/owmods_gui/frontend/src/components/downloads/Downloads.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import Icon from "@components/common/Icon"; -import { BsArrowDown } from "react-icons/bs"; -import DownloadsBadge from "./DownloadsBadge"; -import DownloadsPopout from "./DownloadsPopout"; -import NavButton from "../nav/NavButton"; -import { useGetTranslation } from "@hooks"; - -const Downloads = () => { - const getTranslation = useGetTranslation(); - - return ( -
  • -
    - - - - - - - -
    -
  • - ); -}; - -export default Downloads; diff --git a/owmods_gui/frontend/src/components/downloads/DownloadsBadge.tsx b/owmods_gui/frontend/src/components/downloads/DownloadsBadge.tsx deleted file mode 100644 index b90f1ead9..000000000 --- a/owmods_gui/frontend/src/components/downloads/DownloadsBadge.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { hooks } from "@commands"; -import { memo } from "react"; - -const DownloadsBadge = memo(function DownloadsBadge() { - const count = Object.values(hooks.getDownloads("PROGRESS-UPDATE")[1] ?? {}).filter( - (d) => d.success === null - ).length; - return
    {count}
    ; -}); - -export default DownloadsBadge; diff --git a/owmods_gui/frontend/src/components/downloads/DownloadsPopout.tsx b/owmods_gui/frontend/src/components/downloads/DownloadsPopout.tsx deleted file mode 100644 index c1899211c..000000000 --- a/owmods_gui/frontend/src/components/downloads/DownloadsPopout.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { commands, hooks } from "@commands"; -import Icon from "@components/common/Icon"; -import { useGetTranslation } from "@hooks"; -import { ProgressBar } from "@types"; -import { BsCheck2, BsTrashFill, BsXCircleFill } from "react-icons/bs"; - -const ActiveDownload = (props: ProgressBar) => { - const done = props.success !== null; - const doneClass = props.success ? "download-done" : "download-failed"; - - return ( -
    -

    - - {props.message} -

    - -
    - ); -}; - -const DownloadsPopout = () => { - const getTranslation = useGetTranslation(); - - const downloads = hooks.getDownloads("PROGRESS-UPDATE")[1] ?? {}; - - return ( -
    -
    {getTranslation("DOWNLOADS")}
    - {Object.keys(downloads).length === 0 ? ( -

    {getTranslation("NO_DOWNLOADS")}

    - ) : ( - <> - commands.clearDownloads()} - > - - - {Object.values(downloads).map((d) => ( - - ))} - - )} -
    - ); -}; - -export default DownloadsPopout; diff --git a/owmods_gui/frontend/src/components/logs/LogApp.tsx b/owmods_gui/frontend/src/components/logs/LogApp.tsx index 61bd2a7a1..029a6c585 100644 --- a/owmods_gui/frontend/src/components/logs/LogApp.tsx +++ b/owmods_gui/frontend/src/components/logs/LogApp.tsx @@ -1,16 +1,18 @@ import { commands, hooks } from "@commands"; -import { TranslationContext, TranslationMap } from "@components/common/TranslationContext"; -import { useTheme, useGetTranslation } from "@hooks"; -import { GameMessage, SocketMessageType, Theme } from "@types"; -import { useCallback, useEffect, useState } from "react"; -import LogHeader from "@components/logs/LogHeader"; -import LogList from "@components/logs/LogList"; -import CenteredSpinner from "@components/common/CenteredSpinner"; +import BaseApp from "@components/common/BaseApp"; +import { TranslationMap } from "@components/common/TranslationContext"; +import { useGetTranslation } from "@hooks"; +import { dialog } from "@tauri-apps/api"; import { listen } from "@tauri-apps/api/event"; import { getCurrent } from "@tauri-apps/api/window"; -import { dialog } from "@tauri-apps/api"; +import { GameMessage, SocketMessageType } from "@types"; +import { useState, useCallback, useEffect } from "react"; +import LogHeader from "./LogHeader"; +import { Container, useTheme } from "@mui/material"; +import LogTable from "./LogTable"; export type LogFilter = keyof typeof SocketMessageType | "Any"; +export type LogLines = [number, number][]; const thisWindow = getCurrent(); @@ -22,11 +24,14 @@ const getFilterToPass = (activeFilter: LogFilter) => { } }; -const App = ({ port }: { port: number }) => { +const LogApp = ({ port }: { port: number }) => { + const [status, guiConfig, err] = hooks.getGuiConfig("GUI_CONFIG_RELOAD"); + const [activeFilter, setActiveFilter] = useState("Any"); const [activeSearch, setActiveSearch] = useState(""); - const [logLines, setLogLines] = useState<[number, number][]>([]); + const [logLines, setLogLines] = useState([]); const getTranslation = useGetTranslation(); + const theme = useTheme(); const fetchLogLines = useCallback(() => { commands @@ -35,10 +40,6 @@ const App = ({ port }: { port: number }) => { .catch(() => null); }, [activeFilter, activeSearch, port]); - const guiConfig = hooks.getGuiConfig("GUI_CONFIG_RELOAD")[1]; - - useTheme(guiConfig?.theme ?? Theme.White, guiConfig?.rainbow ?? false); - useEffect(() => { const logsTitleTranslation = TranslationMap[guiConfig?.language ?? "English"]["LOGS_TITLE"]; if (logsTitleTranslation) { @@ -76,30 +77,35 @@ const App = ({ port }: { port: number }) => { fetchLogLines(); }, [activeFilter, activeSearch, fetchLogLines]); - if (guiConfig === null || logLines === null) { - return ; - } else { - return ( - -
    - - -
    -
    - ); - } + return ( + + + + + + + ); }; -export default App; +export default LogApp; diff --git a/owmods_gui/frontend/src/components/logs/LogHeader.tsx b/owmods_gui/frontend/src/components/logs/LogHeader.tsx index 9f67e20a0..114bb5301 100644 --- a/owmods_gui/frontend/src/components/logs/LogHeader.tsx +++ b/owmods_gui/frontend/src/components/logs/LogHeader.tsx @@ -1,8 +1,23 @@ import { useGetTranslation } from "@hooks"; -import { SocketMessageType } from "@types"; -import { memo, useCallback, useRef, useState } from "react"; +import { memo } from "react"; import { LogFilter } from "./LogApp"; +import { + Box, + FormControl, + IconButton, + InputLabel, + MenuItem, + Paper, + Select, + Toolbar, + Typography, + useTheme +} from "@mui/material"; +import FilterInput from "@components/common/FilterInput"; import { TranslationKey } from "@components/common/TranslationContext"; +import { SocketMessageType } from "@types"; +import { DeleteSweepRounded } from "@mui/icons-material"; +import ODTooltip from "@components/common/ODTooltip"; export interface LogHeaderProps { logsLen: number; @@ -13,78 +28,61 @@ export interface LogHeaderProps { onClear: () => void; } -const LogHeader = memo( - function LogHeader({ setActiveSearch, ...props }: LogHeaderProps) { - const [tempSearch, setTempSearch] = useState(""); - const searchTimeout = useRef(undefined); - const getTranslation = useGetTranslation(); +const LogHeader = memo(function LogHeader({ setActiveSearch, ...props }: LogHeaderProps) { + const theme = useTheme(); + const getTranslation = useGetTranslation(); - const onSearchChange = useCallback( - (val: string) => { - setTempSearch(val); - if (searchTimeout) clearTimeout(searchTimeout.current); - searchTimeout.current = setTimeout(() => { - setActiveSearch(val); - }, 200); - }, - [setActiveSearch] - ); + const labelId = "logs-filter-label"; - return ( - <> -
    - - -
    - - {getTranslation("LOG_COUNT", { count: props.logsLen.toString() })} - - props.onClear()} - > - {getTranslation("CLEAR_LOGS")} - -
    -
    - - ); - }, - (current, next) => - current.activeFilter === next.activeFilter && current.logsLen === next.logsLen -); + {getTranslation("ANY")} + {Object.keys(SocketMessageType).map((k) => { + { + return ( + k !== "Fatal" && + k !== "Quit" && ( + + {getTranslation(k as TranslationKey)} + + ) + ); + } + })} + + + + + {getTranslation("LOG_COUNT", { count: props.logsLen.toString() })} + + + + + + + + + + + ); +}); export default LogHeader; diff --git a/owmods_gui/frontend/src/components/logs/LogLine.tsx b/owmods_gui/frontend/src/components/logs/LogLine.tsx deleted file mode 100644 index 13c471ece..000000000 --- a/owmods_gui/frontend/src/components/logs/LogLine.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { hooks } from "@commands"; -import { SocketMessageType } from "@types"; -import { CSSProperties, MutableRefObject, memo, useLayoutEffect, useMemo } from "react"; -import { VirtuosoHandle } from "react-virtuoso"; - -export interface LogLineProps { - port: number; - line: number; - count: number; - style?: CSSProperties; - virtuosoRef?: MutableRefObject; -} - -const LogLine = memo(function LogLine(props: LogLineProps) { - const [status, msg, err] = hooks.getLogLine("", { port: props.port, line: props.line }); - - const messageType = useMemo(() => { - return Object.keys(SocketMessageType)[(msg?.message.messageType as unknown as number) ?? 0]; - }, [msg?.message.messageType]); - - const msgClassName = messageType.toLowerCase(); - - const senderName = msg?.message.senderName ?? "Unknown"; - const senderType = msg?.message.senderType ?? "Unknown"; - - const messageLines = useMemo( - () => (msg?.message.message ?? "").split("\n"), - [msg?.message.message] - ); - - useLayoutEffect(() => { - props.virtuosoRef?.current?.autoscrollToBottom?.(); - }, [status, props.virtuosoRef]); - - if (status === "Error") { - return

    {err!.toString()}

    ; - } else { - return ( -
    - - {senderName} - - - {messageLines.map((line, i) => ( -
    {line}
    - ))} -
    - {props.count > 1 && {props.count}} -
    - ); - } -}); - -export default LogLine; diff --git a/owmods_gui/frontend/src/components/logs/LogList.tsx b/owmods_gui/frontend/src/components/logs/LogList.tsx deleted file mode 100644 index b54c4a928..000000000 --- a/owmods_gui/frontend/src/components/logs/LogList.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { memo, useRef } from "react"; -import { LogFilter } from "./LogApp"; -import LogLine from "./LogLine"; -import { Virtuoso } from "react-virtuoso"; -import { VirtuosoHandle } from "react-virtuoso"; - -export interface LogListProps { - port: number; - logLines: [number, number][]; - activeFilter: LogFilter; - search: string; -} - -const LogList = memo(function LogList(props: LogListProps) { - const virtuoso = useRef(null); - - return ( - `${index}-${props.logLines[index][0]}`} - data={props.logLines} - itemContent={(_, data) => ( - - )} - atBottomThreshold={1000} - followOutput - alignToBottom - /> - ); -}); - -export default LogList; diff --git a/owmods_gui/frontend/src/components/logs/LogRow.tsx b/owmods_gui/frontend/src/components/logs/LogRow.tsx new file mode 100644 index 000000000..113563c22 --- /dev/null +++ b/owmods_gui/frontend/src/components/logs/LogRow.tsx @@ -0,0 +1,111 @@ +import { hooks } from "@commands"; +import ODTooltip from "@components/common/ODTooltip"; +import { Box, Chip, Palette, Skeleton, TableCell, Typography, useTheme } from "@mui/material"; +import { SocketMessageType } from "@types"; +import { Fragment, MutableRefObject, memo, useLayoutEffect, useMemo } from "react"; +import { VirtuosoHandle } from "react-virtuoso"; + +export interface LogRowProps { + port: number; + index: number; + count: number; + virtuosoRef?: MutableRefObject; +} + +const getColor = (palette: Palette, messageType: SocketMessageType) => { + switch (messageType) { + case SocketMessageType.Info: + return palette.info.light; + case SocketMessageType.Success: + return palette.success.light; + case SocketMessageType.Warning: + return palette.warning.light; + case SocketMessageType.Error: + case SocketMessageType.Fatal: + return palette.error.light; + case SocketMessageType.Debug: + return palette.grey[800]; + default: + return palette.text.primary; + } +}; + +const LogRow = memo(function LogRow(props: LogRowProps) { + const theme = useTheme(); + + const [status, logLine, err] = hooks.getLogLine("LOG-UPDATE", { + port: props.port, + line: props.index + }); + + const messageType = useMemo(() => { + return Object.values(SocketMessageType)[ + (logLine?.message.messageType as unknown as number) ?? 0 + ] as SocketMessageType; + }, [logLine?.message.messageType]); + + const cellStyle = { + backgroundColor: theme.palette.grey[900], + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1) + }; + + const messageLines = useMemo( + () => (logLine?.message.message ?? "").split("\n"), + [logLine?.message.message] + ); + + useLayoutEffect(() => { + props.virtuosoRef?.current?.autoscrollToBottom?.(); + }, [status, props.virtuosoRef]); + + return ( + <> + + {status === "Loading" && logLine === null ? ( + + ) : ( + + {logLine?.message.senderName ?? "Unknown"} + + )} + + + + + {status === "Loading" && logLine === null ? ( + <> + + + + ) : status === "Error" ? ( + + {err?.toString()} + + ) : ( + + {messageLines.map((line, i) => ( + + {line} + {i !== messageLines.length - 1 &&
    } +
    + ))} +
    + )}{" "} +
    + {props.count > 1 && ( + + + + )} +
    +
    + + ); +}); + +export default LogRow; diff --git a/owmods_gui/frontend/src/components/logs/LogTable.tsx b/owmods_gui/frontend/src/components/logs/LogTable.tsx new file mode 100644 index 000000000..f7ba10ee9 --- /dev/null +++ b/owmods_gui/frontend/src/components/logs/LogTable.tsx @@ -0,0 +1,71 @@ +import { TableCell, useTheme } from "@mui/material"; +import { TableContainer, Paper, Table, TableBody, TableHead, TableRow } from "@mui/material"; +import { forwardRef, memo, useRef } from "react"; +import { TableProps, TableVirtuoso, VirtuosoHandle } from "react-virtuoso"; +import { LogLines } from "./LogApp"; +import { useGetTranslation } from "@hooks"; +import LogRow from "./LogRow"; + +const ScrollerComp = forwardRef(function TScroller(props, ref) { + return ; +}); +const TableComp = (props: TableProps) => { + const theme = useTheme(); + return ( + + ); +}; +const BodyComp = forwardRef(function TBody(props, ref) { + return ; +}); + +const LogTableComponents = { + Scroller: ScrollerComp, + Table: TableComp, + TableHead: TableHead, + TableRow: TableRow, + TableBody: BodyComp +}; + +export interface LogTableProps { + port: number; + logLines: LogLines; +} + +const LogTable = memo(function LogTable(props: LogTableProps) { + const getTranslation = useGetTranslation(); + const theme = useTheme(); + + const virtuoso = useRef(null); + + return ( + `${index}-${props.logLines[index][0]}`} + increaseViewportBy={500} + atBottomThreshold={1000} + data={props.logLines} + fixedHeaderContent={() => ( + + {getTranslation("SENDER")} + {getTranslation("LOG_MESSAGE")} + + )} + itemContent={(_, data) => ( + + )} + followOutput + alignToBottom + /> + ); +}); + +export default LogTable; diff --git a/owmods_gui/frontend/src/components/main/AppAlert.tsx b/owmods_gui/frontend/src/components/main/AppAlert.tsx new file mode 100644 index 000000000..011ad5b41 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/AppAlert.tsx @@ -0,0 +1,77 @@ +import { hooks } from "@commands"; +import { ErrorRounded, InfoRounded, LaunchRounded, WarningRounded } from "@mui/icons-material"; +import { Box, Button, Palette, Typography, useTheme } from "@mui/material"; +import { shell } from "@tauri-apps/api"; +import { Alert } from "@types"; +import { memo, useCallback } from "react"; + +type AlertSeverity = "warning" | "error" | "info"; + +const getColor = (palette: Palette, severity: AlertSeverity) => { + switch (severity) { + case "error": + return palette.error.dark; + case "warning": + return palette.secondary.dark; + default: + return palette.info.dark; + } +}; + +const AlertIcon = (props: { severity: AlertSeverity }) => { + switch (props.severity) { + case "error": + return ; + case "warning": + return ; + default: + return ; + } +}; + +const AppAlert = memo(function AppAlert() { + const theme = useTheme(); + const alert: Alert | null = hooks.getAlert("CONFIG_RELOAD")[1]; + + const severity = (alert?.severity ?? "info") as AlertSeverity; + + const onClick = useCallback(() => { + shell.open(alert?.url ?? ""); + }, [alert?.url]); + + if (alert === null || !alert.enabled) { + return <>; + } + + return ( + + + {alert.message} + {alert.url && ( + + )} + + + ); +}); + +export default AppAlert; diff --git a/owmods_gui/frontend/src/components/main/MainApp.tsx b/owmods_gui/frontend/src/components/main/MainApp.tsx new file mode 100644 index 000000000..73f3aa550 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/MainApp.tsx @@ -0,0 +1,65 @@ +import { Box } from "@mui/material"; +import TopBar from "./top-bar/TopBar"; +import { Suspense, lazy, useCallback, useEffect, useState } from "react"; +import { TabContext } from "@mui/lab"; +import AppTabs, { ModsTab } from "./top-bar/AppTabs"; +import LocalModsPage from "./mods/local/LocalModsPage"; +import { TranslationMap } from "@components/common/TranslationContext"; +import { commands, hooks } from "@commands"; +import { getCurrent } from "@tauri-apps/api/window"; +import AppAlert from "./AppAlert"; +import BaseApp from "@components/common/BaseApp"; +import OwmlModal from "./OwmlModal"; + +const RemoteModsPage = lazy(() => import("./mods/remote/RemoteModsPage")); +const UpdateModsPage = lazy(() => import("./mods/updates/UpdateModsPage")); + +const MainApp = () => { + const [selectedTab, setSelectedTab] = useState("local"); + const [status, guiConfig, err] = hooks.getGuiConfig("GUI_CONFIG_RELOAD"); + + useEffect(() => { + commands.initialSetup(); + }, []); + + useEffect(() => { + if (guiConfig?.language !== null) { + getCurrent() + .setTitle( + TranslationMap[guiConfig?.language ?? "English"]["APP_TITLE"] ?? + "Outer Wilds Mod Manager (*)" + ) + .catch(console.warn); + } + }, [guiConfig?.language]); + + const onTabChange = useCallback((newVal: string) => { + setSelectedTab(newVal as ModsTab); + }, []); + + return ( + + + + + + + + + + + + + + + + + + ); +}; + +export default MainApp; diff --git a/owmods_gui/frontend/src/components/main/OwmlModal.tsx b/owmods_gui/frontend/src/components/main/OwmlModal.tsx new file mode 100644 index 000000000..f4019cbf4 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/OwmlModal.tsx @@ -0,0 +1,161 @@ +import { commands } from "@commands"; +import { OpenFileInput } from "@components/common/FileInput"; +import { useGetTranslation } from "@hooks"; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + MenuItem, + TextField, + useTheme +} from "@mui/material"; +import { dialog } from "@tauri-apps/api"; +import { listen } from "@tauri-apps/api/event"; +import { memo, useCallback, useEffect, useState } from "react"; + +type SetupType = "INSTALL_OWML" | "LOCATE_OWML" | "INSTALL_OWML_PRERELEASE"; + +const OwmlModal = memo(function OwmlModal() { + const getTranslation = useGetTranslation(); + const theme = useTheme(); + + const [open, setOpen] = useState(false); + const [canCancel, setCanCancel] = useState(false); + const [installingOwml, setInstallingOwml] = useState(false); + + if (import.meta.env.DEV) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + window._DEBUG_OPEN_SETUP = () => setOpen(true); + } + + const [setupMethod, setSetupMethod] = useState("INSTALL_OWML"); + const [owmlPath, setOwmlPath] = useState(""); + + const handleClose = useCallback(() => setOpen(false), []); + + const owmlCheck = useCallback(() => { + commands.checkOWML().then((valid) => { + if (!valid) { + setOpen(true); + setCanCancel(false); + } + }); + }, []); + + const onSubmit = useCallback(() => { + setInstallingOwml(true); + if (setupMethod === "INSTALL_OWML" || setupMethod === "INSTALL_OWML_PRERELEASE") { + commands + .installOwml({ prerelease: setupMethod === "INSTALL_OWML_PRERELEASE" }, false) + .then(() => { + handleClose(); + }) + .catch((e) => { + dialog.message( + getTranslation( + e === "No prerelease for OWML found" + ? "OWML_NO_PRERELEASE" + : "OWML_INSTALL_ERROR" + ), + { + type: "error", + title: getTranslation("FATAL_ERROR") + } + ); + }) + .finally(() => setInstallingOwml(false)); + } else { + commands + .setOwml({ path: owmlPath }, false) + .then((valid) => { + if (valid) { + handleClose(); + } else { + dialog.message(getTranslation("INVALID_OWML")); + } + }) + .catch(dialog.message) + .finally(() => setInstallingOwml(false)); + } + }, [getTranslation, handleClose, owmlPath, setupMethod]); + + useEffect(() => owmlCheck(), [owmlCheck]); + + useEffect(() => { + let cancel = false; + listen("OPEN_OWML_SETUP", () => { + if (cancel) return; + setOpen(true); + setCanCancel(true); + }); + return () => { + cancel = true; + }; + }, []); + + useEffect(() => { + let cancelled = false; + listen("OWML_CONFIG_RELOAD", () => { + if (cancelled) return; + owmlCheck(); + }); + return () => { + cancelled = true; + }; + }, [owmlCheck]); + + return ( + + {getTranslation("SETUP")} + + + {getTranslation("OWML_SETUP_MESSAGE")} + setSetupMethod(e.target.value as SetupType)} + > + {getTranslation("INSTALL_OWML")} + + {getTranslation("INSTALL_OWML_PRERELEASE")} + + {getTranslation("LOCATE_OWML")} + + {setupMethod === "LOCATE_OWML" && ( + + )} + + + + {canCancel && } + + + + ); +}); + +export default OwmlModal; diff --git a/owmods_gui/frontend/src/components/main/mods/ModActionIcon.tsx b/owmods_gui/frontend/src/components/main/mods/ModActionIcon.tsx new file mode 100644 index 000000000..f5bde4509 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/ModActionIcon.tsx @@ -0,0 +1,25 @@ +import ODTooltip from "@components/common/ODTooltip"; +import { Box, IconButton, IconButtonProps } from "@mui/material"; +import { ReactNode, MouseEvent, memo } from "react"; + +export interface ModActionIconProps { + label: string; + icon: ReactNode; + onClick?: (e: MouseEvent) => void; + color?: IconButtonProps["color"]; + [rest: string | number | symbol]: unknown; +} + +const ModActionIcon = memo(function ModActionButton(props: ModActionIconProps) { + return ( + + + + {props.icon} + + + + ); +}); + +export default ModActionIcon; diff --git a/owmods_gui/frontend/src/components/main/mods/ModActionOverflow.tsx b/owmods_gui/frontend/src/components/main/mods/ModActionOverflow.tsx new file mode 100644 index 000000000..0569d679e --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/ModActionOverflow.tsx @@ -0,0 +1,92 @@ +import { MoreVertRounded } from "@mui/icons-material"; +import { Menu, MenuItem, ListItemIcon } from "@mui/material"; +import { + ReactNode, + memo, + MouseEvent, + useState, + useCallback, + useImperativeHandle, + forwardRef +} from "react"; +import ModActionIcon from "./ModActionIcon"; +import { useGetTranslation } from "@hooks"; + +export interface ModActionOverflowProps { + id: string; + children: ReactNode; +} + +export interface ModActionOverflowItemProps { + label: string; + icon: ReactNode; + disabled?: boolean; + onClose?: () => void; + onClick?: () => void; +} + +export const ModActionOverflowItem = memo(function ModOverflowItem( + props: ModActionOverflowItemProps +) { + return ( + { + props.onClose?.(); + props.onClick?.(); + }} + disabled={props.disabled} + > + {props.icon} + {props.label} + + ); +}); + +const ModActionOverflow = forwardRef(function ModActionOverflow( + props: ModActionOverflowProps, + ref +) { + const getTranslation = useGetTranslation(); + + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const onClick = useCallback((event: MouseEvent) => { + setAnchorEl(event.currentTarget); + }, []); + const onClose = useCallback(() => { + setAnchorEl(null); + }, []); + + useImperativeHandle(ref, () => ({ onClose }), [onClose]); + + const overflowId = `${props.id}-actions-overflow`; + const overflowButtonId = `${props.id}-actions-overflow-button`; + + return ( + <> + } + /> + + {props.children} + + + ); +}); + +export default memo(ModActionOverflow); diff --git a/owmods_gui/frontend/src/components/main/mods/ModRow.tsx b/owmods_gui/frontend/src/components/main/mods/ModRow.tsx new file mode 100644 index 000000000..2ead0ff62 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/ModRow.tsx @@ -0,0 +1,176 @@ +import { useGetTranslation } from "@hooks"; +import { Box, Chip, Skeleton, TableCell, Theme, Typography, useTheme } from "@mui/material"; +import { ReactNode, memo, useMemo } from "react"; + +// Stolen from mods website, Rai will never catch me! +const magnitudeMap = [ + { value: 1, symbol: "" }, + { value: 1e3, symbol: "k" }, + { value: 1e6, symbol: "M" }, + { value: 1e9, symbol: "G" }, + { value: 1e12, symbol: "T" }, + { value: 1e15, symbol: "P" }, + { value: 1e18, symbol: "E" } +]; + +const numberFormatRegex = /\.0+$|(\.[0-9]*[1-9])0+$/; + +export const formatNumber = (value: number, digits = 1) => { + const magnitude = magnitudeMap + .slice() + .reverse() + .find((item) => { + return value >= item.value; + }); + return magnitude + ? (value / magnitude.value).toFixed(digits).replace(numberFormatRegex, "$1") + + magnitude.symbol + : "0"; +}; + +const getBgColorFromErrorLevel = (theme: Theme, level?: "warn" | "err") => { + if (level === "warn") { + return theme.palette.secondary.dark; + } else if (level === "err") { + return theme.palette.error.dark; + } else { + return theme.palette.grey[900]; + } +}; + +export interface OverflowMenuItem { + icon: ReactNode; + label: string; + onClick?: () => void; +} + +export interface ModRowProps { + isLoading: boolean; + uniqueName: string; + name: string; + author: string; + downloads: number; + version: string; + description?: string; + remoteIsLoading?: boolean; + children?: ReactNode; + isOutdated?: boolean; + errorLevel?: "warn" | "err"; +} + +const ModRow = memo(function GenericModRow(props: ModRowProps) { + const getTranslation = useGetTranslation(); + const theme = useTheme(); + + const bgColor = useMemo( + () => getBgColorFromErrorLevel(theme, props.errorLevel), + [theme, props.errorLevel] + ); + + const isErr = props.errorLevel === "err"; + + const cellStyle = { + backgroundColor: bgColor, + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1) + }; + + const formattedDownloads = useMemo( + () => (props.downloads === -1 ? "โ€”" : formatNumber(props.downloads)), + [props.downloads] + ); + + const errorList = useMemo(() => { + if (props.errorLevel) { + return props.description?.split("\n") ?? []; + } else { + return []; + } + }, [props.errorLevel, props.description]); + + return ( + <> + + + + + {props.isLoading ? : props.name} + + + + + {props.isLoading ? ( + <> + ) : ( + getTranslation("BY", { author: props.author }) + )} + + + + + + {props.isLoading || props.remoteIsLoading ? ( + <> + + + + ) : props.errorLevel ? ( +
      + {errorList.map((e) => ( +
    • {e}
    • + ))} +
    + ) : ( + props.description + )} +
    +
    +
    + + {props.isLoading || props.remoteIsLoading ? ( + + ) : ( + formattedDownloads + )} + + + + {props.version} +
    + {props.isOutdated && getTranslation("OUTDATED")} + + } + /> +
    + + + {props.children} + + + + ); +}); + +export default ModRow; diff --git a/owmods_gui/frontend/src/components/main/mods/ModsPage.tsx b/owmods_gui/frontend/src/components/main/mods/ModsPage.tsx new file mode 100644 index 000000000..a3d5d740a --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/ModsPage.tsx @@ -0,0 +1,54 @@ +import { Box, CircularProgress, Container, Paper, Typography, useTheme } from "@mui/material"; +import { ReactNode, memo } from "react"; +import ModsToolbar from "./ModsToolbar"; +import ModsTable from "./ModsTable"; + +export interface ModsPageProps { + isLoading: boolean; + actionsSize: number; + show: boolean; + filter: string; + noModsText: string; + onFilterChange: (newVal: string) => void; + uniqueNames: string[]; + renderRow: (uniqueName: string) => ReactNode; + children?: ReactNode; +} + +const ModsPage = memo(function ModsPage(props: ModsPageProps) { + const theme = useTheme(); + + return ( + + + {props.children} + + {props.isLoading ? ( + + + + + + ) : props.uniqueNames.length !== 0 ? ( + + ) : ( + + + {props.noModsText} + + + )} + + ); +}); + +export default ModsPage; diff --git a/owmods_gui/frontend/src/components/main/mods/ModsTable.tsx b/owmods_gui/frontend/src/components/main/mods/ModsTable.tsx new file mode 100644 index 000000000..a5140bafd --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/ModsTable.tsx @@ -0,0 +1,76 @@ +import { useGetTranslation } from "@hooks"; +import { + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableProps, + TableRow, + useTheme +} from "@mui/material"; +import { ReactNode, forwardRef } from "react"; +import { TableVirtuoso } from "react-virtuoso"; + +export interface ModsTableProps { + uniqueNames: string[]; + actionsSize: number; + renderRow: (uniqueName: string) => ReactNode; + addToToolbar?: ReactNode; +} + +const ScrollerComp = forwardRef(function TScroller(props, ref) { + const theme = useTheme(); + return ( + + ); +}); +const TableComp = (props: TableProps) => ( +
    +); +const BodyComp = forwardRef(function TBody(props, ref) { + return ; +}); + +const ModsTableComponents = { + Scroller: ScrollerComp, + Table: TableComp, + TableHead: TableHead, + TableRow: TableRow, + TableBody: BodyComp +}; + +const ModsTable = (props: ModsTableProps) => { + const getTranslation = useGetTranslation(); + const theme = useTheme(); + + return ( + `${index}-${props.uniqueNames[index]}`} + increaseViewportBy={{ top: 200, bottom: 0 }} + data={props.uniqueNames} + fixedHeaderContent={() => ( + + {getTranslation("NAME")} + {getTranslation("DOWNLOADS")} + + {getTranslation("VERSION")} + + + {getTranslation("ACTIONS")} + + + )} + itemContent={(_, uniqueName) => props.renderRow(uniqueName)} + /> + ); +}; + +export default ModsTable; diff --git a/owmods_gui/frontend/src/components/main/mods/ModsToolbar.tsx b/owmods_gui/frontend/src/components/main/mods/ModsToolbar.tsx new file mode 100644 index 000000000..6bd38e263 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/ModsToolbar.tsx @@ -0,0 +1,36 @@ +import { Paper, Toolbar } from "@mui/material"; +import FilterInput from "@components/common/FilterInput"; +import { ReactNode, memo } from "react"; +import { useGetTranslation } from "@hooks"; + +export interface ModsToolbarProps { + filter: string; + onFilterChanged: (newFilter: string) => void; + children?: ReactNode; +} + +const ModsToolbar = memo(function GenericModsToolbar(props: ModsToolbarProps) { + const getTranslation = useGetTranslation(); + + return ( + + + + {props.children} + + + ); +}); + +export default ModsToolbar; diff --git a/owmods_gui/frontend/src/components/main/mods/local/LocalModActions.tsx b/owmods_gui/frontend/src/components/main/mods/local/LocalModActions.tsx new file mode 100644 index 000000000..b7df76d71 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/local/LocalModActions.tsx @@ -0,0 +1,81 @@ +import { + DescriptionRounded, + FolderRounded, + DeleteRounded, + ConstructionRounded +} from "@mui/icons-material"; +import { Checkbox, useTheme } from "@mui/material"; +import { memo, useRef } from "react"; +import ModActionIcon from "../ModActionIcon"; +import ModActionOverflow, { ModActionOverflowItem } from "../ModActionOverflow"; +import { useGetTranslation } from "@hooks"; + +export interface LocalModActionsProps { + uniqueName: string; + enabled: boolean; + isErr: boolean; + hasRemote: boolean; + canFix: boolean; + onToggle: (newVal: boolean) => void; + onReadme: () => void; + onFolder: () => void; + onFix: () => void; + onUninstall: () => void; +} + +const LocalModActions = memo(function LocalModTools(props: LocalModActionsProps) { + const theme = useTheme(); + const getTranslation = useGetTranslation(); + const overflowRef = useRef<{ onClose: () => void }>(); + + return ( + <> + props.onToggle(e.target.checked)} + disabled={props.isErr} + checked={props.enabled} + /> + {props.canFix ? ( + } + onClick={props.onFix} + label={getTranslation("FIX")} + /> + ) : ( + } + /> + )} + + {props.canFix && ( + } + onClick={props.onReadme} + onClose={overflowRef.current?.onClose} + /> + )} + } + onClick={props.onFolder} + onClose={overflowRef.current?.onClose} + /> + } + onClick={props.onUninstall} + onClose={overflowRef.current?.onClose} + /> + + + ); +}); + +export default LocalModActions; diff --git a/owmods_gui/frontend/src/components/main/mods/local/LocalModRow.tsx b/owmods_gui/frontend/src/components/main/mods/local/LocalModRow.tsx new file mode 100644 index 000000000..1a1c21486 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/local/LocalModRow.tsx @@ -0,0 +1,202 @@ +import { commands, hooks } from "@commands"; +import ModRow from "../ModRow"; +import { memo, useCallback, useMemo } from "react"; +import { useGetTranslation, useUnifiedMod } from "@hooks"; +import { dialog } from "@tauri-apps/api"; +import LocalModActions from "./LocalModActions"; +import { LocalMod, UnsafeLocalMod } from "@types"; + +const getErrorLevel = (mod?: UnsafeLocalMod): "err" | "warn" | undefined => { + if (mod?.loadState === "invalid") { + return "err"; + } else if (mod?.mod.errors.length !== 0) { + if ( + mod?.mod.errors.find( + (e) => e.errorType === "InvalidManifest" || e.errorType === "DuplicateMod" + ) + ) { + return "err"; + } else if (mod?.mod.enabled) { + return "warn"; + } + } +}; + +const getDisplayErrors = ( + getTranslation: ReturnType, + mod?: UnsafeLocalMod +): string[] => { + let errors: string[] = []; + + if (mod) { + if (mod.loadState === "invalid") { + errors.push( + getTranslation(mod.mod.error.errorType, { payload: mod.mod.error.payload ?? "" }) + ); + } else { + errors = mod.mod.errors.map((e) => + getTranslation(e.errorType, { payload: e.payload ?? "" }) + ); + } + } + + return errors; +}; + +const canFix = (mod?: UnsafeLocalMod): boolean => { + if ( + mod === undefined || + mod.loadState === "invalid" || + mod.mod.errors.length === 0 || + !mod.mod.enabled + ) { + return false; + } + + return (mod.mod as LocalMod).errors.every( + (e) => e.errorType === "MissingDep" || e.errorType === "DisabledDep" + ); +}; + +export interface LocalModRowProps { + uniqueName: string; +} + +const LocalModRow = memo(function LocalModRow(props: LocalModRowProps) { + // Simple hooks + const getTranslation = useGetTranslation(); + + // Fetch data + const [status1, local] = hooks.getLocalMod("LOCAL-REFRESH", { ...props }); + const [status2, remote] = hooks.getRemoteMod("REMOTE-REFRESH", { ...props }); + const autoEnableDeps = hooks.getGuiConfig("GUI_CONFIG_RELOAD")[1]?.autoEnableDeps ?? false; + + // Transform data + const { name, author, description, version, outdated, enabled } = useUnifiedMod(local, remote); + const errorLevel = useMemo(() => getErrorLevel(local ?? undefined), [local]); + const isErr = errorLevel === "err"; + const canFixWarn = useMemo(() => canFix(local ?? undefined), [local]); + const displayErrors = useMemo( + () => getDisplayErrors(getTranslation, local ?? undefined), + [local, getTranslation] + ); + const hasRemote = remote !== null; + + // Event Handlers + const onReadme = useCallback( + () => commands.openModReadme({ uniqueName: props.uniqueName }), + [props.uniqueName] + ); + const onFolder = useCallback( + () => commands.openModFolder({ uniqueName: props.uniqueName }), + [props.uniqueName] + ); + const onToggle = useCallback( + (newVal: boolean) => { + const task = async () => { + let enableDeps = false; + const hasDisabledDeps = newVal + ? await commands.hasDisabledDeps({ uniqueName: props.uniqueName }) + : false; + if (hasDisabledDeps) { + enableDeps = + autoEnableDeps || + (await dialog.ask(getTranslation("ENABLE_DEPS_MESSAGE"), { + type: "info", + title: getTranslation("CONFIRM") + })); + } + const warnings = await commands.toggleMod({ + uniqueName: props.uniqueName, + enabled: newVal, + recursive: enableDeps + }); + commands.refreshLocalDb(); + for (const modName of warnings) { + dialog.message(getTranslation("PREPATCHER_WARNING", { name: modName }), { + type: "warning", + title: getTranslation("PREPATCHER_WARNING_TITLE", { + name: modName + }) + }); + } + }; + task(); + }, + [autoEnableDeps, getTranslation, props.uniqueName] + ); + const onUninstall = useCallback(() => { + const uninstallConfirmText = getTranslation("UNINSTALL_CONFIRM", { + name + }); + dialog.confirm(uninstallConfirmText, getTranslation("CONFIRM")).then((answer) => { + if (answer) { + commands.uninstallMod({ uniqueName: props.uniqueName }).then((warnings) => { + commands.refreshLocalDb(); + for (const modName of warnings) { + dialog.message(getTranslation("PREPATCHER_WARNING", { name: modName }), { + type: "warning", + title: getTranslation("PREPATCHER_WARNING_TITLE", { name: modName }) + }); + } + }); + } + }); + }, [getTranslation, name, props.uniqueName]); + const onFix = useCallback(() => { + commands.fixDeps({ uniqueName: props.uniqueName }).then(() => commands.refreshLocalDb()); + }, [props.uniqueName]); + + const modsToolbar = useMemo( + () => ( + + ), + [ + props.uniqueName, + enabled, + isErr, + hasRemote, + canFixWarn, + onToggle, + onReadme, + onFix, + onFolder, + onUninstall + ] + ); + + return ( + + {modsToolbar} + + ); +}); + +export default LocalModRow; diff --git a/owmods_gui/frontend/src/components/main/mods/local/LocalModsPage.tsx b/owmods_gui/frontend/src/components/main/mods/local/LocalModsPage.tsx new file mode 100644 index 000000000..103c2482b --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/local/LocalModsPage.tsx @@ -0,0 +1,48 @@ +import { commands, hooks } from "@commands"; +import { memo, useCallback, useEffect, useMemo, useState } from "react"; +import ModsPage from "../ModsPage"; +import LocalModRow from "./LocalModRow"; +import LocalModsToggleButtons from "./LocalModsToggleButtons"; +import { useGetTranslation } from "@hooks"; + +const LocalModsPage = memo(function LocalModsPage(props: { show: boolean }) { + useEffect(() => { + commands.refreshLocalDb(); + }, []); + + const getTranslation = useGetTranslation(); + + const [filter, setFilter] = useState(""); + + const [status, localMods] = hooks.getLocalMods("LOCAL-REFRESH", { filter }); + + const onToggleAll = useCallback((newVal: boolean) => { + commands.toggleAll({ enabled: newVal }).then(() => commands.refreshLocalDb()); + }, []); + + const renderRow = useCallback((uniqueName: string) => { + return ; + }, []); + + const toggleButtons = useMemo( + () => , + [onToggleAll] + ); + + return ( + + {toggleButtons} + + ); +}); + +export default LocalModsPage; diff --git a/owmods_gui/frontend/src/components/main/mods/local/LocalModsToggleButtons.tsx b/owmods_gui/frontend/src/components/main/mods/local/LocalModsToggleButtons.tsx new file mode 100644 index 000000000..3cadf3b17 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/local/LocalModsToggleButtons.tsx @@ -0,0 +1,20 @@ +import { useGetTranslation } from "@hooks"; +import { Button, ButtonGroup } from "@mui/material"; +import { memo } from "react"; + +export interface LocalModsToggleButtonsProps { + onToggle: (newVal: boolean) => void; +} + +const LocalModsToggleButtons = memo(function LocalModsToolbar(props: LocalModsToggleButtonsProps) { + const getTranslation = useGetTranslation(); + + return ( + + + + + ); +}); + +export default LocalModsToggleButtons; diff --git a/owmods_gui/frontend/src/components/main/mods/remote/RemoteModActions.tsx b/owmods_gui/frontend/src/components/main/mods/remote/RemoteModActions.tsx new file mode 100644 index 000000000..fdc383958 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/remote/RemoteModActions.tsx @@ -0,0 +1,56 @@ +import { DownloadRounded, DescriptionRounded, ScienceRounded } from "@mui/icons-material"; +import { Box, CircularProgress } from "@mui/material"; +import { memo, useRef } from "react"; +import ModActionIcon from "../ModActionIcon"; +import ModActionOverflow, { ModActionOverflowItem } from "../ModActionOverflow"; +import { useGetTranslation } from "@hooks"; + +export interface RemoteModActionsProps { + uniqueName: string; + busy: boolean; + showPrerelease: boolean; + prereleaseLabel: string; + onInstall: () => void; + onReadme: () => void; + onPrerelease: () => void; +} + +const RemoteModActions = memo(function RemoteModToolbar(props: RemoteModActionsProps) { + const getTranslation = useGetTranslation(); + const overflowRef = useRef<{ onClose: () => void }>(); + + return ( + <> + {props.busy ? ( + + + + ) : ( + } + /> + )} + + } + onClick={props.onReadme} + onClose={overflowRef.current?.onClose} + /> + {props.showPrerelease && ( + } + onClick={props.onPrerelease} + disabled={props.busy ?? false} + onClose={overflowRef.current?.onClose} + /> + )} + + + ); +}); + +export default RemoteModActions; diff --git a/owmods_gui/frontend/src/components/main/mods/remote/RemoteModRow.tsx b/owmods_gui/frontend/src/components/main/mods/remote/RemoteModRow.tsx new file mode 100644 index 000000000..69a955ee7 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/remote/RemoteModRow.tsx @@ -0,0 +1,86 @@ +import { memo, useCallback, useMemo } from "react"; +import ModRow from "../ModRow"; +import { commands, hooks } from "@commands"; +import { useGetTranslation } from "@hooks"; +import { dialog } from "@tauri-apps/api"; +import RemoteModActions from "./RemoteModActions"; + +export interface RemoteModRowProps { + uniqueName: string; +} + +const RemoteModRow = memo(function RemoteModRow(props: RemoteModRowProps) { + const getTranslation = useGetTranslation(); + + const [status, remote] = hooks.getRemoteMod("REMOTE-REFRESH", { uniqueName: props.uniqueName }); + const busy = hooks.getModBusy("MOD-BUSY", { uniqueName: props.uniqueName })[1]; + + const hasPrerelease = useMemo(() => remote?.prerelease !== undefined, [remote?.prerelease]); + + const prereleaseLabel = hasPrerelease + ? getTranslation("USE_PRERELEASE", { + version: remote?.prerelease?.version ?? "" + }) + : ""; + + const onInstall = useCallback(() => { + commands + .installMod({ uniqueName: props.uniqueName }) + .then(() => { + commands.refreshLocalDb().catch(console.error); + }) + .catch(console.error); + }, [props.uniqueName]); + + const onPrerelease = useCallback(() => { + const task = async () => { + const result = await dialog.ask(getTranslation("PRERELEASE_WARNING"), { + title: prereleaseLabel + }); + if (result) { + commands + .installMod({ uniqueName: props.uniqueName, prerelease: true }) + .then(() => { + commands.refreshLocalDb().catch(console.error); + }) + .catch(console.error); + } + }; + task(); + }, [getTranslation, prereleaseLabel, props.uniqueName]); + + const onReadme = useCallback(() => { + commands.openModReadme({ uniqueName: props.uniqueName }).catch(console.warn); + }, [props.uniqueName]); + + const modActions = useMemo( + () => ( + + ), + [busy, onInstall, onPrerelease, onReadme, prereleaseLabel, props.uniqueName, hasPrerelease] + ); + + return ( + + {modActions} + + ); +}); + +export default RemoteModRow; diff --git a/owmods_gui/frontend/src/components/main/mods/remote/RemoteModsPage.tsx b/owmods_gui/frontend/src/components/main/mods/remote/RemoteModsPage.tsx new file mode 100644 index 000000000..dbcbd8e8b --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/remote/RemoteModsPage.tsx @@ -0,0 +1,49 @@ +import { commands, hooks } from "@commands"; +import { memo, useEffect, useMemo, useState } from "react"; +import ModsPage from "../ModsPage"; +import RemoteModRow from "./RemoteModRow"; +import { Button } from "@mui/material"; +import { useGetTranslation } from "@hooks"; +import { PublicRounded } from "@mui/icons-material"; +import { shell } from "@tauri-apps/api"; + +const RemoteModsPage = memo(function RemoteModsPage(props: { show: boolean }) { + const getTranslation = useGetTranslation(); + + useEffect(() => { + commands.refreshRemoteDb({}, false).catch(console.warn); + }, []); + + const [filter, setFilter] = useState(""); + + const [status, remoteMods] = hooks.getRemoteMods("REMOTE-REFRESH", { filter }); + + const modsWebsiteButton = useMemo( + () => ( + + ), + [getTranslation] + ); + + return ( + } + > + {modsWebsiteButton} + + ); +}); + +export default RemoteModsPage; diff --git a/owmods_gui/frontend/src/components/main/mods/updates/UpdateModRow.tsx b/owmods_gui/frontend/src/components/main/mods/updates/UpdateModRow.tsx new file mode 100644 index 000000000..8731418d9 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/updates/UpdateModRow.tsx @@ -0,0 +1,61 @@ +import { commands, hooks } from "@commands"; +import { useGetTranslation, useUnifiedMod } from "@hooks"; +import { memo, useCallback, useMemo } from "react"; +import ModRow from "../ModRow"; +import { UpdateRounded } from "@mui/icons-material"; +import { Box, CircularProgress } from "@mui/material"; +import ModActionIcon from "../ModActionIcon"; + +export interface UpdateModRowProps { + uniqueName: string; +} + +const UpdateModRow = memo(function UpdateModRow(props: UpdateModRowProps) { + const getTranslation = useGetTranslation(); + + // Fetch data + const [status1, local] = hooks.getLocalMod("LOCAL-REFRESH", { ...props }); + const [status2, remote] = hooks.getRemoteMod("REMOTE-REFRESH", { ...props }); + const busy = hooks.getModBusy("MOD-BUSY", { uniqueName: props.uniqueName })[1]; + + // Transform data + const { name, author, description, version, outdated } = useUnifiedMod(local, remote); + + const onUpdate = useCallback(() => { + commands.updateMod({ uniqueName: props.uniqueName }).then(() => commands.refreshLocalDb()); + }, [props.uniqueName]); + + const updateButton = useMemo( + () => + busy ? ( + + + + ) : ( + } + /> + ), + [busy, getTranslation, onUpdate] + ); + + return ( + + {updateButton} + + ); +}); + +export default UpdateModRow; diff --git a/owmods_gui/frontend/src/components/main/mods/updates/UpdateModsPage.tsx b/owmods_gui/frontend/src/components/main/mods/updates/UpdateModsPage.tsx new file mode 100644 index 000000000..761797f74 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/mods/updates/UpdateModsPage.tsx @@ -0,0 +1,60 @@ +import { commands, hooks } from "@commands"; +import { memo, useCallback, useMemo, useState } from "react"; +import ModsPage from "../ModsPage"; +import UpdateModRow from "./UpdateModRow"; +import { UpdateRounded } from "@mui/icons-material"; +import { Button } from "@mui/material"; +import { useGetTranslation } from "@hooks"; + +const UpdateModsPage = memo(function UpdateModsPage(props: { show: boolean }) { + const getTranslation = useGetTranslation(); + + const [filter, setFilter] = useState(""); + const [updatingAll, setUpdatingAll] = useState(false); + + const [status, updateMods] = hooks.getUpdatableMods(["LOCAL-REFRESH", "REMOTE-REFRESH"], { + filter + }); + + const renderRow = useCallback((uniqueName: string) => { + return ; + }, []); + + const onUpdateAll = useCallback(() => { + setUpdatingAll(true); + commands + .updateAll({ uniqueNames: updateMods ?? [] }) + .then(() => commands.refreshLocalDb()) + .finally(() => setUpdatingAll(false)); + }, [updateMods]); + + const updateAllButton = useMemo( + () => ( + + ), + [updateMods?.length, updatingAll, onUpdateAll, getTranslation] + ); + + return ( + + {updateAllButton} + + ); +}); + +export default UpdateModsPage; diff --git a/owmods_gui/frontend/src/components/main/top-bar/AppIcons.tsx b/owmods_gui/frontend/src/components/main/top-bar/AppIcons.tsx new file mode 100644 index 000000000..b14989b57 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/AppIcons.tsx @@ -0,0 +1,43 @@ +import { Box, IconButton, Tooltip, useTheme } from "@mui/material"; +import { ReactNode } from "react"; +import OverflowMenu from "./overflow/OverflowMenu"; +import ReloadIcon from "./ReloadIcon"; +import SettingsIcon from "./settings/SettingsIcon"; +import DownloadsIcon from "./downloads/DownloadsIcon"; + +interface AppIconProps { + label: string; + children: ReactNode; + disabled?: boolean; + onClick?: (e: React.MouseEvent) => void; +} + +export const AppIcon = (props: AppIconProps) => { + return ( + + + {props.children} + + + ); +}; + +const AppIcons = () => { + const theme = useTheme(); + + return ( + + + + + + + ); +}; + +export default AppIcons; diff --git a/owmods_gui/frontend/src/components/main/top-bar/AppTabs.tsx b/owmods_gui/frontend/src/components/main/top-bar/AppTabs.tsx new file mode 100644 index 000000000..c67a7a16b --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/AppTabs.tsx @@ -0,0 +1,54 @@ +import { hooks } from "@commands"; +import { useGetTranslation } from "@hooks"; +import { ComputerRounded, PublicRounded, UpdateRounded } from "@mui/icons-material"; +import TabList from "@mui/lab/TabList"; +import { AppBar, useTheme } from "@mui/material"; +import Tab from "@mui/material/Tab"; +import { memo } from "react"; + +export type ModsTab = "local" | "remote" | "updates"; + +export interface AppTabsProps { + onChange: (newVal: ModsTab) => void; +} + +const AppTabs = memo(function AppTabs({ onChange }: AppTabsProps) { + const getTranslation = useGetTranslation(); + const theme = useTheme(); + const count = + hooks.getUpdatableMods(["LOCAL-REFRESH", "REMOTE-REFRESH"], { filter: "" })[1]?.length ?? 0; + const countText = count === 0 ? "" : `(${count})`; + + return ( + + onChange(newVal)} + variant="fullWidth" + textColor="inherit" + indicatorColor="secondary" + > + } + iconPosition="start" + label={getTranslation("INSTALLED_MODS")} + /> + } + iconPosition="start" + label={getTranslation("GET_MODS")} + /> + } + iconPosition="start" + label={getTranslation("UPDATES", { amount: countText })} + /> + + + ); +}); + +export default AppTabs; diff --git a/owmods_gui/frontend/src/components/main/top-bar/ReloadIcon.tsx b/owmods_gui/frontend/src/components/main/top-bar/ReloadIcon.tsx new file mode 100644 index 000000000..f8635b2d1 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/ReloadIcon.tsx @@ -0,0 +1,71 @@ +import { commands } from "@commands"; +import { useGetTranslation } from "@hooks"; +import { memo, useCallback, useEffect, useRef, useState } from "react"; +import { CircularProgress } from "@mui/material"; +import { RefreshRounded } from "@mui/icons-material"; +import { AppIcon } from "./AppIcons"; +import { listen } from "@tauri-apps/api/event"; + +const ReloadIcon = memo(function ReloadIcon() { + const getTranslation = useGetTranslation(); + + const [isRefreshing, setRefreshing] = useState(false); + const currentTimeout = useRef(null); + + const onRefresh = useCallback( + (refreshLocal: boolean, refreshRemote: boolean, refreshConfigs: boolean) => { + const task = async () => { + setRefreshing(true); + if (refreshLocal) { + await commands.refreshLocalDb(); + } + if (refreshConfigs) { + await commands.initialSetup(); + } + if (refreshRemote) { + await commands.refreshRemoteDb(); + } + }; + task().finally(() => { + setRefreshing(false); + }); + }, + [] + ); + + const onRefreshButton = useCallback(() => { + onRefresh(true, true, true); + }, [onRefresh]); + + useEffect(() => { + let cancel = false; + listen("REQUEST-RELOAD", (e) => { + if (cancel) return; + const reloadType = e.payload as string; + if (currentTimeout.current !== null) { + clearTimeout(currentTimeout.current); + } + currentTimeout.current = setTimeout(() => { + onRefresh( + reloadType === "LOCAL", + false, + reloadType === "GUI" || reloadType === "CONFIG" + ); + }, 500); + }); + return () => { + cancel = true; + }; + }, [onRefresh]); + + return ( + + {isRefreshing ? : } + + ); +}); +export default ReloadIcon; diff --git a/owmods_gui/frontend/src/components/main/top-bar/StartGameButton.tsx b/owmods_gui/frontend/src/components/main/top-bar/StartGameButton.tsx new file mode 100644 index 000000000..d4b6f9741 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/StartGameButton.tsx @@ -0,0 +1,55 @@ +import { Button } from "@mui/material"; +import { PlayArrow as PlayIcon } from "@mui/icons-material"; +import { useCallback, useState } from "react"; +import { commands } from "@commands"; +import { useGetTranslation } from "@hooks"; +import { dialog } from "@tauri-apps/api"; + +const StartGameButton = () => { + const getTranslation = useGetTranslation(); + const [areLogsStarting, setLogsStarting] = useState(false); + + const onPlay = useCallback(() => { + const start = () => + commands + .startLogs() + .then(() => setLogsStarting(false)) + .catch(console.warn); + setLogsStarting(true); + const task = async () => { + const hasIssues = await commands.checkDBForIssues(); + const skipWarning = (await commands.getGuiConfig()).noWarning; + if (!skipWarning && hasIssues) { + const yes = await dialog.ask(getTranslation("LAUNCH_ANYWAY"), { + type: "warning", + title: getTranslation("CONFIRM") + }); + if (yes) { + start(); + } else { + setLogsStarting(false); + } + } else { + start(); + } + }; + task(); + }, [getTranslation]); + + return ( + + + + ); +}; + +export default StartGameButton; diff --git a/owmods_gui/frontend/src/components/main/top-bar/TopBar.tsx b/owmods_gui/frontend/src/components/main/top-bar/TopBar.tsx new file mode 100644 index 000000000..49e5069b5 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/TopBar.tsx @@ -0,0 +1,18 @@ +import { AppBar, Toolbar } from "@mui/material"; +import StartGameButton from "./StartGameButton"; +import AppIcons from "./AppIcons"; +import { memo } from "react"; + +const TopBar = memo(function TopBar() { + return ( + + + +
    + + + + ); +}); + +export default TopBar; diff --git a/owmods_gui/frontend/src/components/main/top-bar/downloads/DownloadRow.tsx b/owmods_gui/frontend/src/components/main/top-bar/downloads/DownloadRow.tsx new file mode 100644 index 000000000..83fc93cbf --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/downloads/DownloadRow.tsx @@ -0,0 +1,43 @@ +import { CheckCircleRounded, ErrorRounded } from "@mui/icons-material"; +import { Box, Card, CardContent, LinearProgress, Typography, useTheme } from "@mui/material"; +import { ProgressBar } from "@types"; +import { memo } from "react"; +import { determineProgressVariant } from "./DownloadsIcon"; + +const DownloadRow = memo(function DownloadRow(props: ProgressBar) { + const theme = useTheme(); + const done = props.success !== undefined && props.success !== null; + + const percent = (props.progress / props.len) * 100; + + return ( + + + {props.message} + + {done ? ( + props.success ? ( + + ) : ( + + ) + ) : ( + + {props.progressType === "Definite" ? `${Math.round(percent)}%` : "โ€”%"} + + )} + + + + + + + + ); +}); + +export default DownloadRow; diff --git a/owmods_gui/frontend/src/components/main/top-bar/downloads/DownloadsIcon.tsx b/owmods_gui/frontend/src/components/main/top-bar/downloads/DownloadsIcon.tsx new file mode 100644 index 000000000..abc6f06ae --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/downloads/DownloadsIcon.tsx @@ -0,0 +1,93 @@ +import { hooks } from "@commands"; +import { DownloadingRounded } from "@mui/icons-material"; +import { Suspense, lazy, memo, useMemo, useState } from "react"; +import { AppIcon } from "../AppIcons"; +import { Box, CircularProgress, CircularProgressProps, Typography } from "@mui/material"; +import { ProgressBar } from "@types"; + +const DownloadsPopover = lazy(() => import("./DownloadsPopover")); + +export const determineProgressVariant = (bar: ProgressBar): CircularProgressProps["variant"] => { + if (bar.success && bar.progressAction === "Download") { + // After downloading don't give the wrong idea + return "indeterminate"; + } else if (bar.progressType === "Indefinite" && bar.success !== undefined) { + // Show a complete bar if the indefinite action is done + return "determinate"; + } else { + return bar.progressType === "Definite" ? "determinate" : "indeterminate"; + } +}; + +const DownloadsIcon = memo(function DownloadsIcon() { + const [anchorEl, setAnchorEl] = useState(); + const downloads = hooks.getDownloads("PROGRESS-UPDATE")[1]; + + const sortedDownloads = Object.values(downloads?.bars ?? {}); + + sortedDownloads.sort((a, b) => b.position - a.position); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const openPopover = Boolean(anchorEl); + + const activeDownloads = useMemo( + () => sortedDownloads.filter((d) => d.success === null || d.progressAction === "Download"), + [sortedDownloads] + ); + + const len = activeDownloads.length; + + const current = activeDownloads[0]; + + return ( + <> + + + + + + + {len !== 0 && ( + <> + + {len.toString()} + + {current && ( + + + + )} + + )} + + + + + + ); +}); + +export default DownloadsIcon; diff --git a/owmods_gui/frontend/src/components/main/top-bar/downloads/DownloadsPopover.tsx b/owmods_gui/frontend/src/components/main/top-bar/downloads/DownloadsPopover.tsx new file mode 100644 index 000000000..d88dfb086 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/downloads/DownloadsPopover.tsx @@ -0,0 +1,61 @@ +import { Box, Popover, Typography, useTheme } from "@mui/material"; +import { memo } from "react"; +import DownloadRow from "./DownloadRow"; +import { useGetTranslation } from "@hooks"; +import { ProgressBar } from "@types"; + +export interface DownloadsPopoverProps { + open: boolean; + downloads: ProgressBar[]; + handleClose: () => void; + anchorEl: HTMLElement | null | undefined; +} + +const DownloadsPopover = memo(function DownloadsPopover(props: DownloadsPopoverProps) { + const getTranslation = useGetTranslation(); + const theme = useTheme(); + + return ( + + + {props.downloads.length === 0 ? ( + + + {getTranslation("NO_DOWNLOADS")} + + + ) : ( + props.downloads.map((d) => ) + )} + + + ); +}); + +export default DownloadsPopover; diff --git a/owmods_gui/frontend/src/components/main/top-bar/overflow/About.tsx b/owmods_gui/frontend/src/components/main/top-bar/overflow/About.tsx new file mode 100644 index 000000000..a65e1d2c7 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/overflow/About.tsx @@ -0,0 +1,101 @@ +import { useGetTranslation } from "@hooks"; +import { ChatBubbleRounded, GitHub, InfoRounded } from "@mui/icons-material"; +import { + MenuItem, + ListItemIcon, + ListItemText, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + DialogContentText, + Box, + IconButton +} from "@mui/material"; +import { app, os, shell } from "@tauri-apps/api"; +import { memo, useCallback, useEffect, useState } from "react"; +import logo from "@assets/images/logo.png?w=256&h=256&format=webp&imagetools"; +import ODTooltip from "@components/common/ODTooltip"; + +export interface ModalProps { + onClick?: () => void; +} + +const About = memo(function About({ onClick }: ModalProps) { + const getTranslation = useGetTranslation(); + + const [open, setOpen] = useState(false); + + const [appVersion, setVersion] = useState(""); + const [appPlatform, setPlatform] = useState(""); + const [archRaw, setArch] = useState(""); + + useEffect(() => { + app.getVersion().then(setVersion); + os.platform().then(setPlatform); + os.arch().then(setArch); + }, []); + + const handleClick = useCallback(() => { + setOpen(true); + onClick?.(); + }, [onClick]); + + const onClose = useCallback(() => { + setOpen(false); + }, []); + + const onGithub = useCallback(() => { + shell.open("https://github.com/Bwc9876/ow-mod-man/"); + }, []); + + const onDiscord = useCallback(() => { + shell.open("https://discord.gg/wusTQYbYTc"); + }, []); + + return ( + <> + + + + + {getTranslation("ABOUT")} + + + {getTranslation("ABOUT")} + + + + + +

    {getTranslation("APP_TITLE")}

    + + + + + + + + + + + + + {getTranslation("APP_VERSION", { version: appVersion })} +
    + {getTranslation("PLATFORM", { platform: appPlatform })} +
    + {getTranslation("ARCHITECTURE", { arch: archRaw })} +
    +
    +
    + + + +
    + + ); +}); + +export default About; diff --git a/owmods_gui/frontend/src/components/main/top-bar/overflow/Export.tsx b/owmods_gui/frontend/src/components/main/top-bar/overflow/Export.tsx new file mode 100644 index 000000000..16052d06c --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/overflow/Export.tsx @@ -0,0 +1,47 @@ +import { useGetTranslation } from "@hooks"; +import { ModalProps } from "./About"; +import { memo, useCallback } from "react"; +import { NorthEastRounded } from "@mui/icons-material"; +import { MenuItem, ListItemIcon, ListItemText } from "@mui/material"; +import { dialog } from "@tauri-apps/api"; +import { commands } from "@commands"; + +const Export = memo(function Export({ onClick }: ModalProps) { + const getTranslation = useGetTranslation(); + + const onExport = useCallback(() => { + dialog + .save({ + title: getTranslation("EXPORT_MODS"), + filters: [ + { + name: "JSON File", + extensions: ["json"] + } + ] + }) + .then((path) => { + if (path) { + commands.exportMods({ path }).catch(console.error); + } + }); + }, [getTranslation]); + + const handleClick = useCallback(() => { + onExport(); + onClick?.(); + }, [onClick, onExport]); + + return ( + <> + + + + + {getTranslation("EXPORT_MODS")} + + + ); +}); + +export default Export; diff --git a/owmods_gui/frontend/src/components/main/top-bar/overflow/Import.tsx b/owmods_gui/frontend/src/components/main/top-bar/overflow/Import.tsx new file mode 100644 index 000000000..3df77985c --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/overflow/Import.tsx @@ -0,0 +1,96 @@ +import { useGetTranslation } from "@hooks"; +import { ModalProps } from "./About"; +import { memo, useCallback, useState } from "react"; +import { SouthEastRounded } from "@mui/icons-material"; +import { + MenuItem, + Checkbox, + ListItemIcon, + ListItemText, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + DialogContentText, + FormControlLabel, + useTheme +} from "@mui/material"; +import { OpenFileInput } from "@components/common/FileInput"; +import { commands } from "@commands"; + +const Import = memo(function Import({ onClick }: ModalProps) { + const getTranslation = useGetTranslation(); + const [open, setOpen] = useState(false); + const theme = useTheme(); + + const [filePath, setFilePath] = useState(""); + const [disableMissing, setDisableMissing] = useState(false); + + const handleClick = useCallback(() => { + setOpen(true); + onClick?.(); + }, [onClick]); + + const onClose = useCallback(() => { + setOpen(false); + }, []); + + const onImport = useCallback(() => { + commands + .importMods({ path: filePath, disableMissing }) + .then(() => commands.refreshLocalDb()) + .catch(console.error); + onClose(); + }, [disableMissing, filePath, onClose]); + + return ( + <> + + + + + {getTranslation("IMPORT_MODS")} + + + {getTranslation("IMPORT_MODS")} + + + {getTranslation("IMPORT_MODS_EXPLANATION")} + + + setDisableMissing(!disableMissing)} + control={} + label={getTranslation("DISABLE_MISSING_MODS")} + /> + + + + + + + + ); +}); + +export default Import; diff --git a/owmods_gui/frontend/src/components/main/top-bar/overflow/InstallFrom.tsx b/owmods_gui/frontend/src/components/main/top-bar/overflow/InstallFrom.tsx new file mode 100644 index 000000000..abc79e2fe --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/overflow/InstallFrom.tsx @@ -0,0 +1,209 @@ +import { useGetTranslation } from "@hooks"; +import { ModalProps } from "./About"; +import { memo, useCallback, useEffect, useMemo, useState } from "react"; +import { InstallDesktopRounded, WarningRounded } from "@mui/icons-material"; +import { + MenuItem, + ListItemIcon, + ListItemText, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Select, + InputLabel, + FormControl, + TextField, + Box, + useTheme, + FormControlLabel, + Checkbox, + DialogContentText +} from "@mui/material"; +import { ProtocolInstallType, ProtocolPayload } from "@types"; +import { commands } from "@commands"; +import { listen } from "@tauri-apps/api/event"; +import { getCurrent } from "@tauri-apps/api/window"; +import { OpenFileInput } from "@components/common/FileInput"; + +type SourceType = "UNIQUE_NAME" | "URL" | "ZIP"; + +const getSourceTypeFromProtocol = (installType: ProtocolInstallType): SourceType | null => { + switch (installType) { + case "installMod": + return "UNIQUE_NAME"; + case "installURL": + return "URL"; + case "installPreRelease": + return "UNIQUE_NAME"; + default: + return null; + } +}; + +const InstallFrom = memo(function InstallFrom({ onClick }: ModalProps) { + const getTranslation = useGetTranslation(); + const [open, setOpen] = useState(false); + const theme = useTheme(); + + const [source, setSource] = useState("UNIQUE_NAME"); + const [target, setTarget] = useState(""); + const [prerelease, setPrerelease] = useState(false); + + const lblMap: Record = useMemo( + () => ({ + UNIQUE_NAME: getTranslation("UNIQUE_NAME"), + URL: getTranslation("URL"), + ZIP: getTranslation("ZIP") + }), + [getTranslation] + ); + + useEffect(() => { + let cancel = false; + listen("PROTOCOL_INVOKE", ({ payload }) => { + if (cancel) return; + const protocolPayload = payload as ProtocolPayload; + const sourceType = getSourceTypeFromProtocol(protocolPayload.installType); + if (sourceType !== null) { + setSource(sourceType); + setTarget(protocolPayload.payload); + if ( + protocolPayload.installType === "installPreRelease" || + protocolPayload.installType === "installMod" + ) { + setPrerelease(protocolPayload.installType === "installPreRelease"); + } + setOpen(true); + getCurrent().setFocus().catch(console.warn); + } + }) + .then(() => commands.popProtocolURL()) + .catch(console.warn); + return () => { + cancel = true; + }; + }, []); + + const handleClick = useCallback(() => { + setOpen(true); + onClick?.(); + }, [onClick]); + + const onClose = useCallback(() => { + setOpen(false); + }, []); + + const onInstall = useCallback(() => { + switch (source) { + case "UNIQUE_NAME": + commands + .installMod({ uniqueName: target, prerelease }) + .then(() => commands.refreshLocalDb()) + .catch(console.error); + break; + case "URL": + commands + .installUrl({ url: target }) + .then(() => commands.refreshLocalDb()) + .catch(console.error); + break; + case "ZIP": + commands + .installZip({ path: target }) + .then(() => commands.refreshLocalDb()) + .catch(console.error); + break; + } + onClose(); + }, [onClose, prerelease, source, target]); + + return ( + <> + + + + + {getTranslation("INSTALL_FROM")} + + + {getTranslation("INSTALL_FROM")} + + + + + {getTranslation("INSTALL_FROM")} + + + + {source === "ZIP" ? ( + + ) : ( + setTarget(e.target.value)} + /> + )} + {source === "UNIQUE_NAME" ? ( + setPrerelease(!prerelease)} + control={} + label={getTranslation("USE_PRERELEASE_CHECKBOX")} + /> + ) : ( + + + + {getTranslation("INSTALL_WARNING")} + + + )} + + + + + + + + + ); +}); + +export default InstallFrom; diff --git a/owmods_gui/frontend/src/components/main/top-bar/overflow/OverflowMenu.tsx b/owmods_gui/frontend/src/components/main/top-bar/overflow/OverflowMenu.tsx new file mode 100644 index 000000000..3608bc8ae --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/overflow/OverflowMenu.tsx @@ -0,0 +1,86 @@ +import { useGetTranslation } from "@hooks"; +import { BuildRounded, HelpRounded, MoreHoriz } from "@mui/icons-material"; +import { ListItemIcon, ListItemText, MenuItem, Tooltip } from "@mui/material"; +import IconButton from "@mui/material/IconButton"; +import Menu from "@mui/material/Menu"; +import { shell } from "@tauri-apps/api"; +import { emit } from "@tauri-apps/api/event"; +import { useState, MouseEvent, useCallback, lazy, Suspense } from "react"; + +const About = lazy(() => import("./About")); +const Import = lazy(() => import("./Import")); +const Export = lazy(() => import("./Export")); +const InstallFrom = lazy(() => import("./InstallFrom")); + +const OverflowMenu = () => { + const getTranslation = useGetTranslation(); + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const onClick = useCallback((event: MouseEvent) => { + setAnchorEl(event.currentTarget); + }, []); + const onClose = useCallback(() => { + setAnchorEl(null); + }, []); + + const onHelp = useCallback(() => { + shell.open("https://github.com/Bwc9876/ow-mod-man/blob/main/owmods_gui/HELP.md"); + onClose(); + }, [onClose]); + + const onOwmlEdit = useCallback(() => { + emit("OPEN_OWML_SETUP", {}); + onClose(); + }, [onClose]); + + return ( + <> + + + + + + + + + + + + + + + + {getTranslation("EDIT_OWML")} + + + + + + {getTranslation("HELP")} + + + + + + + ); +}; + +export default OverflowMenu; diff --git a/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsCheck.tsx b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsCheck.tsx new file mode 100644 index 000000000..1bde32f5f --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsCheck.tsx @@ -0,0 +1,23 @@ +import ODTooltip from "@components/common/ODTooltip"; +import { FormControlLabel, Checkbox } from "@mui/material"; +import { SettingsRowProps } from "./SettingsForm"; + +export interface SettingsCheckProps extends SettingsRowProps { + value: boolean; + onChange?: (id: string, newVal: boolean) => void; +} + +const SettingsCheck = (props: SettingsCheckProps) => { + return ( + + props.onChange?.(props.id, newVal)} + checked={props.value} + control={} + label={props.label} + /> + + ); +}; + +export default SettingsCheck; diff --git a/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsFolder.tsx b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsFolder.tsx new file mode 100644 index 000000000..fd8531d84 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsFolder.tsx @@ -0,0 +1,34 @@ +import { OpenFileInput } from "@components/common/FileInput"; +import ODTooltip from "@components/common/ODTooltip"; +import { useGetTranslation } from "@hooks"; +import { SettingsTextProps } from "./SettingsText"; +import { Box } from "@mui/material"; + +const SettingsFolder = (props: SettingsTextProps) => { + const getTranslation = useGetTranslation(); + + const onChange = (e: string) => { + props.onChange?.(props.id, e); + }; + + return ( + + + + + + ); +}; + +export default SettingsFolder; diff --git a/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsForm.tsx b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsForm.tsx new file mode 100644 index 000000000..7dd106ddc --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsForm.tsx @@ -0,0 +1,215 @@ +import { + ReactNode, + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useState +} from "react"; +import { Config, GuiConfig, Language, OWMLConfig } from "@types"; +import { useGetTranslation } from "@hooks"; +import { commands } from "@commands"; +import { TranslationNameMap } from "@components/common/TranslationContext"; +import { os } from "@tauri-apps/api"; +import { Box, useTheme } from "@mui/material"; +import SettingsFolder from "./SettingsFolder"; +import SettingsSelect from "./SettingsSelect"; +import SettingsText from "./SettingsText"; +import SettingsCheck from "./SettingsCheck"; +import SettingsHeader from "./SettingsHeader"; + +const LanguageArr = Object.values(Language); + +interface SettingsFormProps { + initialConfig: Config; + initialOwmlConfig: OWMLConfig; + initialGuiConfig: GuiConfig; +} + +export interface SettingsFormHandle { + save: () => void; + reset: () => void; +} + +export interface SettingsRowProps { + label: string; + id: string; + children?: ReactNode; + tooltip?: string; +} + +const SettingsForm = forwardRef(function SettingsForm(props: SettingsFormProps, ref) { + const [config, setConfig] = useState(props.initialConfig); + const [owmlConfig, setOwmlConfig] = useState(props.initialOwmlConfig); + const [guiConfig, setGuiConfig] = useState(props.initialGuiConfig); + const getTranslation = useGetTranslation(); + const theme = useTheme(); + + const [showLogServerOption, setShowLogServerOption] = useState(false); + + useEffect(() => { + os.platform().then((p) => { + setShowLogServerOption(p === "win32"); + }); + }, []); + + useImperativeHandle( + ref, + () => + ({ + save: () => { + const task = async () => { + await commands.saveConfig({ config }); + await commands.saveGuiConfig({ guiConfig }); + if (config.owmlPath !== props.initialConfig.owmlPath) { + await commands.refreshLocalDb(); + } else { + await commands.saveOwmlConfig({ owmlConfig }); + } + if (config.databaseUrl !== props.initialConfig.databaseUrl) { + await commands.refreshRemoteDb(); + } + }; + task().catch(console.error); + }, + reset: () => { + setConfig(props.initialConfig); + setGuiConfig(props.initialGuiConfig); + setOwmlConfig(props.initialOwmlConfig); + } + } as SettingsFormHandle), + [ + config, + owmlConfig, + guiConfig, + props.initialConfig, + props.initialGuiConfig, + props.initialOwmlConfig + ] + ); + + const handleConf = (id: string, newVal: string | boolean) => { + setConfig({ ...config, [id]: newVal }); + }; + + const handleOwml = (id: string, newVal: string | boolean) => { + setOwmlConfig({ ...owmlConfig, [id]: newVal }); + }; + + const handleGui = (id: string, newVal: string | boolean) => { + setGuiConfig({ ...guiConfig, [id]: newVal }); + }; + + const onReset = useCallback((i: number) => { + commands.getDefaultConfigs().then((data) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + [setConfig, setGuiConfig, setOwmlConfig][i](data[i]); + }); + }, []); + + return ( + + onReset(1)} /> + + + + + + {showLogServerOption && ( + + )} + onReset(2)} /> + + + + + onReset(3)} /> + + + + + ); +}); + +export default SettingsForm; diff --git a/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsHeader.tsx b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsHeader.tsx new file mode 100644 index 000000000..c41985531 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsHeader.tsx @@ -0,0 +1,34 @@ +import { useGetTranslation } from "@hooks"; +import { SettingsBackupRestoreRounded } from "@mui/icons-material"; +import { Box, Button, Typography, useTheme } from "@mui/material"; +import { memo } from "react"; + +export interface SettingsHeaderProps { + text: string; + onReset?: () => void; +} + +const ResetButton = memo(function ResetButton(props: { onClick?: () => void }) { + const getTranslation = useGetTranslation(); + + return ( + + ); +}); + +const SettingsHeader = memo(function SettingsHeader(props: SettingsHeaderProps) { + const theme = useTheme(); + + return ( + + + {props.text} + + + + ); +}); + +export default SettingsHeader; diff --git a/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsIcon.tsx b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsIcon.tsx new file mode 100644 index 000000000..df1020b24 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsIcon.tsx @@ -0,0 +1,32 @@ +import { Suspense, lazy, memo, useCallback, useState } from "react"; +import { AppIcon } from "../AppIcons"; +import { useGetTranslation } from "@hooks"; +import { SettingsRounded } from "@mui/icons-material"; + +const SettingsModal = lazy(() => import("./SettingsModal")); + +const SettingsIcon = memo(function SettingsIcon() { + const getTranslation = useGetTranslation(); + const [open, setOpen] = useState(false); + + const onOpen = useCallback(() => { + setOpen(true); + }, []); + + const onClose = useCallback(() => { + setOpen(false); + }, []); + + return ( + <> + + + + + + + + ); +}); + +export default SettingsIcon; diff --git a/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsModal.tsx b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsModal.tsx new file mode 100644 index 000000000..b503f3d1c --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsModal.tsx @@ -0,0 +1,78 @@ +import { hooks } from "@commands"; +import { useGetTranslation } from "@hooks"; +import { memo, useCallback, useRef } from "react"; +import SettingsForm, { SettingsFormHandle } from "./SettingsForm"; +import { + Box, + Button, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle +} from "@mui/material"; + +export interface SettingsModalProps { + open: boolean; + onClose: () => void; +} + +const SettingsModal = memo(function SettingsModal({ open, onClose }: SettingsModalProps) { + const settingsFormRef = useRef(); + + const [configStatus, config, err1] = hooks.getConfig("CONFIG_RELOAD"); + const [guiConfigStatus, guiConfig, err2] = hooks.getGuiConfig("GUI_CONFIG_RELOAD"); + const [owmlConfigStatus, owmlConfig, err3] = hooks.getOwmlConfig("OWML_CONFIG_RELOAD"); + + const status = [configStatus, guiConfigStatus, owmlConfigStatus]; + + const getTranslation = useGetTranslation(); + + const onSave = useCallback(() => { + settingsFormRef.current?.save(); + onClose?.(); + }, [onClose]); + + const onCancel = useCallback(() => { + settingsFormRef.current?.reset(); + onClose?.(); + }, [onClose]); + + return ( + + {getTranslation("SETTINGS")} + + {status.includes("Error") ? ( + + {/* Since we couldn't load settings we'll be stuck in English anyway, + so no need to translate this */} + Error: Couldn't Load Settings: {err1?.toString() ?? ""}{" "} + {err2?.toString() ?? ""} {err3?.toString() ?? ""} + + ) : status.includes("Loading") && + (config === null || guiConfig === null || owmlConfig === null) ? ( + + + + ) : ( + + )} + + + + + + + ); +}); + +export default SettingsModal; diff --git a/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsSelect.tsx b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsSelect.tsx new file mode 100644 index 000000000..965ae425d --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsSelect.tsx @@ -0,0 +1,41 @@ +import { TranslationKey } from "@components/common/TranslationContext"; +import { useGetTranslation } from "@hooks"; +import { FormControl, InputLabel, Select, MenuItem } from "@mui/material"; +import { SettingsRowProps } from "./SettingsForm"; + +export interface SettingsSelectProps extends SettingsRowProps { + value: string; + options: readonly string[]; + translate: boolean; + onChange?: (id: string, newVal: string) => void; + nameMap?: Record; +} + +const SettingsSelect = (props: SettingsSelectProps) => { + const getTranslation = useGetTranslation(); + + const labelId = `${props.id}-label`; + + return ( + + {props.label} + + + ); +}; + +export default SettingsSelect; diff --git a/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsText.tsx b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsText.tsx new file mode 100644 index 000000000..035ff98c7 --- /dev/null +++ b/owmods_gui/frontend/src/components/main/top-bar/settings/SettingsText.tsx @@ -0,0 +1,25 @@ +import ODTooltip from "@components/common/ODTooltip"; +import { TextField } from "@mui/material"; +import { SettingsRowProps } from "./SettingsForm"; + +export interface SettingsTextProps extends SettingsRowProps { + value: string; + onChange?: (id: string, newVal: string) => void; +} + +const SettingsText = (props: SettingsTextProps) => { + return ( + + props.onChange?.(props.id, e.target.value)} + /> + + ); +}; + +export default SettingsText; diff --git a/owmods_gui/frontend/src/components/modals/AboutModal.tsx b/owmods_gui/frontend/src/components/modals/AboutModal.tsx deleted file mode 100644 index 9a3b84647..000000000 --- a/owmods_gui/frontend/src/components/modals/AboutModal.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import Modal from "./Modal"; -import logo from "@assets/images/logo.png?w=256&h=256&format=webp&imagetools"; -import Icon from "@components/common/Icon"; -import { BsDiscord, BsGithub } from "react-icons/bs"; -import { useGetTranslation } from "@hooks"; -import { app, os, shell } from "@tauri-apps/api"; -import { forwardRef, useEffect, useState } from "react"; - -const AboutModal = forwardRef(function AboutModal(_: object, ref) { - const [appVersion, setVersion] = useState(""); - const [appPlatform, setPlatform] = useState(""); - const [archRaw, setArch] = useState(""); - const getTranslation = useGetTranslation(); - - useEffect(() => { - app.getVersion().then(setVersion); - os.platform().then(setPlatform); - os.arch().then(setArch); - }, []); - - return ( - -
    - -

    {getTranslation("APP_TITLE")}

    -

    {getTranslation("VERSION", { version: appVersion })}

    -

    {getTranslation("PLATFORM", { platform: appPlatform })}

    -

    {getTranslation("ARCHITECTURE", { arch: archRaw })}

    - -
    -
    - ); -}); - -export default AboutModal; diff --git a/owmods_gui/frontend/src/components/modals/InstallFromModal.tsx b/owmods_gui/frontend/src/components/modals/InstallFromModal.tsx deleted file mode 100644 index 4da529bf6..000000000 --- a/owmods_gui/frontend/src/components/modals/InstallFromModal.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { commands } from "@commands"; -import { OpenFileInput } from "@components/common/FileInput"; -import Icon from "@components/common/Icon"; -import { useGetTranslation } from "@hooks"; -import { listen } from "@tauri-apps/api/event"; -import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react"; -import { BsExclamationTriangleFill } from "react-icons/bs"; -import Modal, { ModalHandle } from "./Modal"; -import { ProtocolInstallType, ProtocolPayload } from "@types"; -import { getCurrent } from "@tauri-apps/api/window"; - -type SourceType = "UNIQUE_NAME" | "URL" | "ZIP" | "JSON"; - -const getSourceTypeFromProtocol = (installType: ProtocolInstallType): SourceType | null => { - switch (installType) { - case "installMod": - return "UNIQUE_NAME"; - case "installURL": - return "URL"; - case "installPreRelease": - return "UNIQUE_NAME"; - default: - return null; - } -}; - -const InstallFromModal = forwardRef(function InstallFromModal(_: object, ref) { - const modalRef = useRef(); - const [source, setSource] = useState("UNIQUE_NAME"); - const [target, setTarget] = useState(""); - const [prerelease, setPrerelease] = useState(false); - const getTranslation = useGetTranslation(); - - useImperativeHandle( - ref, - () => ({ - open: () => modalRef.current?.open(), - close: () => modalRef.current?.close() - }), - [] - ); - - const lblMap: Record = { - UNIQUE_NAME: getTranslation("UNIQUE_NAME"), - URL: getTranslation("URL"), - ZIP: getTranslation("ZIP"), - JSON: getTranslation("JSON") - }; - - const onInstall = () => { - switch (source) { - case "UNIQUE_NAME": - commands - .installMod({ uniqueName: target, prerelease }) - .then(() => commands.refreshLocalDb()) - .catch(console.error); - break; - case "URL": - commands - .installUrl({ url: target }) - .then(() => commands.refreshLocalDb()) - .catch(console.error); - break; - case "ZIP": - commands - .installZip({ path: target }) - .then(() => commands.refreshLocalDb()) - .catch(console.error); - break; - case "JSON": - commands - .importMods({ path: target }) - .then(() => commands.refreshLocalDb()) - .catch(console.error); - } - }; - - useEffect(() => { - let cancel = false; - listen("PROTOCOL_INVOKE", ({ payload }) => { - if (cancel) return; - const protocolPayload = payload as ProtocolPayload; - const sourceType = getSourceTypeFromProtocol(protocolPayload.installType); - if (sourceType !== null) { - setSource(sourceType); - setTarget(protocolPayload.payload); - if ( - protocolPayload.installType === "installPreRelease" || - protocolPayload.installType === "installMod" - ) { - setPrerelease(protocolPayload.installType === "installPreRelease"); - } - modalRef.current?.open(); - getCurrent().setFocus().catch(console.warn); - } - }) - .then(() => commands.popProtocolURL()) - .catch(console.warn); - return () => { - cancel = true; - }; - }, []); - - return ( - -
    - - {source === "ZIP" || source === "JSON" ? ( - - ) : ( - - )} - {source === "UNIQUE_NAME" && ( - - )} - {(source === "ZIP" || source === "URL") && ( -

    - - {getTranslation("INSTALL_WARNING")} -

    - )} - -
    - ); -}); - -export default InstallFromModal; diff --git a/owmods_gui/frontend/src/components/modals/ModValidationModal.tsx b/owmods_gui/frontend/src/components/modals/ModValidationModal.tsx deleted file mode 100644 index 646272676..000000000 --- a/owmods_gui/frontend/src/components/modals/ModValidationModal.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { commands } from "@commands"; -import { useGetTranslation } from "@hooks"; -import { ModValidationError } from "@types"; -import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from "react"; -import Modal, { ModalHandle } from "./Modal"; - -export interface OpenModValidationModalPayload { - uniqueName: string; - modName: string; - errors: ModValidationError[]; -} - -export interface ModValidationModalHandle { - open: (payload: OpenModValidationModalPayload) => void; - close: () => void; -} - -const ValidationError = (props: ModValidationError) => { - const getTranslation = useGetTranslation(); - const message = getTranslation(props.errorType, { payload: props.payload ?? "" }); - return
  • {message}
  • ; -}; - -const ModValidationModal = forwardRef(function ModValidationModal(_: object, ref) { - const modalRef = useRef(); - const [uniqueName, setUniqueName] = useState(""); - const [modName, setModName] = useState(""); - const [errors, setErrors] = useState([]); - const getTranslation = useGetTranslation(); - - useImperativeHandle( - ref, - () => ({ - open: (payload: OpenModValidationModalPayload) => { - setModName(payload.modName); - setUniqueName(payload.uniqueName); - setErrors(payload.errors); - modalRef.current?.open(); - }, - close: () => { - modalRef.current?.close(); - } - }), - [] - ); - - const onConfirm = () => { - if (canFix) { - commands - .fixDeps({ uniqueName }) - .then(() => commands.refreshLocalDb()) - .catch(console.warn); - } - }; - - const canFix = useMemo(() => { - return errors.every((e) => e.errorType === "DisabledDep" || e.errorType === "MissingDep"); - }, [errors]); - - return ( - -
    {getTranslation("VALIDATION_MESSAGE", { name: modName })}
    -
      - {errors.map((e) => ( - - ))} -
    - {canFix &&

    {getTranslation("VALIDATION_FIX_MESSAGE")}

    } -
    - ); -}); - -export default ModValidationModal; diff --git a/owmods_gui/frontend/src/components/modals/Modal.tsx b/owmods_gui/frontend/src/components/modals/Modal.tsx deleted file mode 100644 index c35ae6426..000000000 --- a/owmods_gui/frontend/src/components/modals/Modal.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { useGetTranslation } from "@hooks"; -import { - ReactNode, - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState -} from "react"; -import { IconContext } from "react-icons"; - -export interface ModalProps { - heading?: string; - confirmText?: string; - showCancel?: boolean; - cancelText?: string; - allowCloseOnOutsideClick?: boolean; - children: ReactNode; - onCancel?: () => boolean | void; - onConfirm?: () => boolean | void; -} - -export interface ModalHandle { - open: () => void; - close: () => void; -} - -interface OpenState { - open: boolean; - closing: boolean; -} - -const Modal = forwardRef(function Modal(props: ModalProps, ref) { - const [state, setState] = useState({ open: false, closing: false }); - const [awaitingClose, setAwaitingClose] = useState(false); - const activeTimeout = useRef(); - const getTranslation = useGetTranslation(); - - const onClose = useCallback(() => { - setAwaitingClose(false); - setState({ open: true, closing: true }); - }, []); - - useImperativeHandle( - ref, - () => ({ - open: () => setState({ open: true, closing: false }), - close: onClose - }), - [onClose] - ); - - useEffect(() => { - if (state.open) { - document.documentElement.classList.add("modal-is-opening", "modal-is-open"); - if (activeTimeout.current) clearTimeout(activeTimeout.current); - activeTimeout.current = setTimeout(() => { - document.documentElement.classList.remove("modal-is-opening"); - }, 1000); - } else { - document.documentElement.classList.remove("modal-is-closing"); - } - if (state.closing) { - document.documentElement.classList.remove("modal-is-open"); - document.documentElement.classList.add("modal-is-closing"); - if (activeTimeout.current) clearTimeout(activeTimeout.current); - activeTimeout.current = setTimeout(() => { - setState({ open: false, closing: false }); - }, 1000); - } - return () => { - if (activeTimeout.current) clearTimeout(activeTimeout.current); - }; - }, [state]); - - return ( - { - if ((props.allowCloseOnOutsideClick ?? true) && state.open && !state.closing) - onClose(); - }} - className={state.open ? "" : "d-none"} - dir="ltr" - open={state.open} - > - - - - - ); -}); - -export default Modal; diff --git a/owmods_gui/frontend/src/components/modals/OwmlSetupModal.tsx b/owmods_gui/frontend/src/components/modals/OwmlSetupModal.tsx deleted file mode 100644 index aa6c8c577..000000000 --- a/owmods_gui/frontend/src/components/modals/OwmlSetupModal.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { commands } from "@commands"; -import { OpenFileInput } from "@components/common/FileInput"; -import { useGetTranslation } from "@hooks"; -import { dialog } from "@tauri-apps/api"; -import { forwardRef, useRef, useState } from "react"; -import Modal, { ModalHandle } from "./Modal"; - -type SetupMethod = "Install" | "Locate"; - -const OwmlSetupModal = forwardRef(function OwmlSetupModal(_: object, ref) { - const modalRef = useRef(); - const [setupMethod, setSetupMethod] = useState("Install"); - const [owmlPath, setOwmlPath] = useState(""); - const getTranslation = useGetTranslation(); - - const onClose = () => { - if (setupMethod === "Install") { - commands - .installOwml() - .then(() => { - modalRef.current?.close(); - window.location.reload(); - }) - .catch(dialog.message); - } else { - commands - .setOwml({ path: owmlPath }) - .then((valid) => { - if (valid) { - window.location.reload(); - } else { - dialog - .message(getTranslation("INVALID_OWML")) - .then(() => window.location.reload()); - } - }) - .catch(dialog.message); - } - return false; - }; - - return ( - -
    -

    {getTranslation("OWML_SETUP_MESSAGE")}

    - - {setupMethod === "Locate" && ( - - )} - -
    - ); -}); - -export default OwmlSetupModal; diff --git a/owmods_gui/frontend/src/components/modals/SettingsModal.tsx b/owmods_gui/frontend/src/components/modals/SettingsModal.tsx deleted file mode 100644 index 300072732..000000000 --- a/owmods_gui/frontend/src/components/modals/SettingsModal.tsx +++ /dev/null @@ -1,442 +0,0 @@ -import { - ChangeEvent, - ReactNode, - forwardRef, - memo, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState -} from "react"; -import { Config, GuiConfig, Language, OWMLConfig, Theme } from "@types"; -import Modal, { ModalHandle } from "./Modal"; -import { useGetTranslation } from "@hooks"; -import { commands, hooks } from "@commands"; -import { OpenFileInput } from "@components/common/FileInput"; -import Icon from "@components/common/Icon"; -import { BsArrowCounterclockwise } from "react-icons/bs"; -import { type TranslationKey, TranslationNameMap } from "@components/common/TranslationContext"; -import { os } from "@tauri-apps/api"; - -const ThemeArr = Object.values(Theme); -const LanguageArr = Object.values(Language); - -interface SettingsFormProps { - initialConfig: Config; - initialOwmlConfig: OWMLConfig; - initialGuiConfig: GuiConfig; -} - -interface SettingsFormHandle { - save: () => void; - reset: () => void; -} - -interface SettingsRowProps { - label: string; - id: string; - children?: ReactNode; - tooltip?: string; - tooltipPlacement?: string; -} - -interface SettingsTextProps extends SettingsRowProps { - value: string; - onChange?: (e: ChangeEvent) => void; -} - -interface SettingsSwitchProps extends SettingsRowProps { - value: boolean; - onChange?: (e: ChangeEvent) => void; -} - -interface SettingsSelectProps extends SettingsRowProps { - value: string; - options: readonly string[]; - translate: boolean; - onChange?: (e: ChangeEvent) => void; - nameMap?: Record; -} - -const SettingsRow = (props: SettingsRowProps) => { - return ( -
    - -
    {props.children}
    -
    - ); -}; - -const SettingsText = (props: SettingsTextProps) => { - return ( - - props.onChange?.(e)} - name={props.id} - value={props.value} - id={props.id} - /> - {props.children} - - ); -}; - -const SettingsSelect = (props: SettingsSelectProps) => { - const getTranslation = useGetTranslation(); - - return ( - - - - ); -}; - -const SettingsFolder = (props: SettingsTextProps) => { - const getTranslation = useGetTranslation(); - - const onChange = (e: string) => { - props.onChange?.({ - target: { - id: props.id, - value: e - } - } as ChangeEvent); - }; - - return ( - - ); -}; - -const SettingsSwitch = (props: SettingsSwitchProps) => { - return ( -
    - props.onChange?.(e)} - type="checkbox" - role="switch" - checked={props.value} - id={props.id} - name={props.id} - /> - -
    - ); -}; - -const ResetButton = memo(function ResetButton(props: { onClick: () => void }) { - const getTranslation = useGetTranslation(); - - return ( - - - - ); -}); - -const SettingsForm = forwardRef(function SettingsForm(props: SettingsFormProps, ref) { - const [config, setConfig] = useState(props.initialConfig); - const [owmlConfig, setOwmlConfig] = useState(props.initialOwmlConfig); - const [guiConfig, setGuiConfig] = useState(props.initialGuiConfig); - const getTranslation = useGetTranslation(); - - const [showLogServerOption, setShowLogServerOption] = useState(false); - - useEffect(() => { - os.platform().then((p) => { - setShowLogServerOption(p === "win32"); - }); - }, []); - - useImperativeHandle( - ref, - () => ({ - save: () => { - const task = async () => { - await commands.saveConfig({ config }); - await commands.saveGuiConfig({ guiConfig }); - if (config.owmlPath !== props.initialConfig.owmlPath) { - await commands.refreshLocalDb(); - } else { - await commands.saveOwmlConfig({ owmlConfig }); - } - if (config.databaseUrl !== props.initialConfig.databaseUrl) { - await commands.refreshRemoteDb(); - } - }; - task().catch(console.error); - }, - reset: () => { - setConfig(props.initialConfig); - setGuiConfig(props.initialGuiConfig); - setOwmlConfig(props.initialOwmlConfig); - } - }), - [ - config, - owmlConfig, - guiConfig, - props.initialConfig, - props.initialGuiConfig, - props.initialOwmlConfig - ] - ); - - const getVal = (e: HTMLInputElement | HTMLSelectElement) => { - const type = e.type; - if (type && type === "checkbox") { - return (e as HTMLInputElement).checked; - } else { - return e.value; - } - }; - - const handleConf = (e: ChangeEvent) => { - setConfig({ ...config, [e.target.id]: getVal(e.target) }); - }; - - const handleOwml = (e: ChangeEvent) => { - setOwmlConfig({ ...owmlConfig, [e.target.id]: getVal(e.target) }); - }; - - const handleGui = (e: ChangeEvent) => { - setGuiConfig({ ...guiConfig, [e.target.id]: getVal(e.target) }); - }; - - const onReset = useCallback((i: number) => { - commands.getDefaultConfigs().then((data) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - [setConfig, setGuiConfig, setOwmlConfig][i](data[i]); - }); - }, []); - - return ( -
    -

    - {getTranslation("GUI_SETTINGS")} onReset(1)} /> -

    - - - - - - - - {showLogServerOption && ( - - )} -

    - {getTranslation("OWML_SETTINGS")} onReset(2)} /> -

    - - - - -

    - {getTranslation("GENERAL_SETTINGS")} onReset(0)} /> -

    - - - - - ); -}); - -const SettingsModal = forwardRef(function SettingsModal(_: object, ref) { - const settingsFormRef = useRef(); - const modalRef = useRef(null); - - useImperativeHandle( - ref, - () => ({ - open: () => { - settingsFormRef.current?.reset?.(); - modalRef.current?.open?.(); - }, - close: () => modalRef.current?.close?.() - }), - [] - ); - - const [configStatus, config, err1] = hooks.getConfig("CONFIG_RELOAD"); - const [guiConfigStatus, guiConfig, err2] = hooks.getGuiConfig("GUI_CONFIG_RELOAD"); - const [owmlConfigStatus, owmlConfig, err3] = hooks.getOwmlConfig("OWML_CONFIG_RELOAD"); - - const status = [configStatus, guiConfigStatus, owmlConfigStatus]; - - const getTranslation = useGetTranslation(); - - if (status.includes("Loading")) { - return <>; - } else if (status.includes("Error")) { - return ( - - <> -

    - <> - Error: Couldn't Load Settings: {err1 ?? ""} {err2 ?? ""}{" "} - {err3 ?? ""} - -

    - -
    - ); - } else { - return ( - settingsFormRef.current?.save()} - onCancel={() => settingsFormRef.current?.reset()} - showCancel - heading={getTranslation("SETTINGS")} - confirmText={getTranslation("SAVE")} - ref={modalRef} - > - - - ); - } -}); - -export default SettingsModal; diff --git a/owmods_gui/frontend/src/components/mods/ModActionButton.tsx b/owmods_gui/frontend/src/components/mods/ModActionButton.tsx deleted file mode 100644 index 762baeb2f..000000000 --- a/owmods_gui/frontend/src/components/mods/ModActionButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { ReactNode } from "react"; - -export interface ModActionButtonProps { - children: ReactNode; - ariaLabel: string; - onClick?: () => void; - className?: string; - disabled?: boolean; -} - -const ModActionButton = (props: ModActionButtonProps) => { - return ( - { - e.preventDefault(); - props.onClick?.(); - }} - aria-label={props.ariaLabel} - > - {props.children} - - ); -}; - -export default ModActionButton; diff --git a/owmods_gui/frontend/src/components/mods/ModHeader.tsx b/owmods_gui/frontend/src/components/mods/ModHeader.tsx deleted file mode 100644 index a5e9f11a5..000000000 --- a/owmods_gui/frontend/src/components/mods/ModHeader.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ReactNode } from "react"; - -export interface ModHeaderProps { - children: ReactNode; - name: string; - subtitle: string; -} - -const ModHeader = (props: ModHeaderProps) => { - return ( - -
    - {props.name} - {props.subtitle} -
    -
    {props.children}
    -
    - ); -}; - -export default ModHeader; diff --git a/owmods_gui/frontend/src/components/mods/local/FailedModRow.tsx b/owmods_gui/frontend/src/components/mods/local/FailedModRow.tsx deleted file mode 100644 index bdad29e16..000000000 --- a/owmods_gui/frontend/src/components/mods/local/FailedModRow.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { OpenModValidationModalPayload } from "@components/modals/ModValidationModal"; -import { FailedMod, ModValidationError } from "@types"; -import LocalModRow from "./LocalModRow"; -import { confirm } from "@tauri-apps/api/dialog"; -import { useCallback } from "react"; -import { commands } from "@commands"; -import { useGetTranslation } from "@hooks"; - -export interface FailedModRowProps { - mod: FailedMod; - onValidationClick?: (p: OpenModValidationModalPayload) => void; -} - -const FailedModRow = ({ mod, onValidationClick }: FailedModRowProps) => { - const getTranslation = useGetTranslation(); - - const onValidationClicked = useCallback( - (errs: ModValidationError[]) => { - onValidationClick?.({ - uniqueName: mod.modPath, - modName: mod.displayPath, - errors: errs - }); - }, - [mod.modPath, mod.displayPath, onValidationClick] - ); - - const onUninstall = useCallback(() => { - confirm( - getTranslation("UNINSTALL_CONFIRM", { - name: mod.displayPath - }), - getTranslation("CONFIRM") - ).then((answer) => { - if (answer) { - commands - .uninstallBrokenMod({ modPath: mod.modPath }) - .then(() => commands.refreshLocalDb()); - } - }); - }, [getTranslation, mod.displayPath, mod.modPath]); - - const onOpen = useCallback(() => { - commands.openModFolder({ uniqueName: mod.modPath }); - }, [mod.modPath]); - - return ( - - ); -}; - -export default FailedModRow; diff --git a/owmods_gui/frontend/src/components/mods/local/LocalModRow.tsx b/owmods_gui/frontend/src/components/mods/local/LocalModRow.tsx deleted file mode 100644 index c2b7ff7ec..000000000 --- a/owmods_gui/frontend/src/components/mods/local/LocalModRow.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import Icon from "@components/common/Icon"; -import ModActionButton from "@components/mods/ModActionButton"; -import ModHeader from "@components/mods/ModHeader"; -import { useGetTranslation } from "@hooks"; -import { ModValidationError } from "@types"; -import { memo, useCallback } from "react"; -import { BsFolderFill, BsGlobe, BsTrashFill } from "react-icons/bs"; -import LocalModValidationIcon from "./LocalModValidationIcon"; -import { commands } from "@commands"; - -interface LocalModRowProps { - uniqueName: string; - name: string; - showValidation: boolean; - errors: ModValidationError[]; - description?: string; - readme?: boolean; - subtitle?: string; - enabled?: boolean; - onValidationClick?: (p: ModValidationError[]) => void; - onToggle?: (newState: boolean) => void; - onOpen?: () => void; - onUninstall?: () => void; -} - -const LocalModRow = memo(function LocalModRow(props: LocalModRowProps) { - const getTranslation = useGetTranslation(); - - const onReadme = useCallback(() => { - commands.openModReadme({ uniqueName: props.uniqueName }).catch(console.warn); - }, [props.uniqueName]); - - return ( -
    - - {props.showValidation && ( - - )} - - - - {props.readme && ( - - - - )} - - - - props.onToggle?.(e.target.checked)} - checked={props.enabled ?? false} - disabled={props.enabled === undefined} - /> - - {props.description && {props.description}} -
    - ); -}); - -export default LocalModRow; diff --git a/owmods_gui/frontend/src/components/mods/local/LocalModValidationIcon.tsx b/owmods_gui/frontend/src/components/mods/local/LocalModValidationIcon.tsx deleted file mode 100644 index 9762bb2f3..000000000 --- a/owmods_gui/frontend/src/components/mods/local/LocalModValidationIcon.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import Icon from "@components/common/Icon"; -import { useGetTranslation } from "@hooks"; -import { ModValidationError } from "@types"; -import { useCallback } from "react"; -import { BsExclamationDiamondFill } from "react-icons/bs"; -import ModActionButton from "../ModActionButton"; - -export interface LocalModValidationIconProps { - errors: ModValidationError[]; - onClickProp?: (errors: ModValidationError[]) => void; -} - -const LocalModValidationIcon = ({ errors, onClickProp }: LocalModValidationIconProps) => { - const getTranslation = useGetTranslation(); - - const onClick = useCallback(() => { - onClickProp?.(errors); - }, [errors, onClickProp]); - - if (errors.length === 0) { - return <>; - } else { - const errorInList = - errors.find( - (e) => - e.errorType === "MissingDep" || - e.errorType === "DisabledDep" || - e.errorType === "InvalidManifest" || - e.errorType === "DuplicateMod" - ) !== undefined; - - return ( - - - - ); - } -}; - -export default LocalModValidationIcon; diff --git a/owmods_gui/frontend/src/components/mods/local/LocalMods.tsx b/owmods_gui/frontend/src/components/mods/local/LocalMods.tsx deleted file mode 100644 index ba428756a..000000000 --- a/owmods_gui/frontend/src/components/mods/local/LocalMods.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { commands, hooks } from "@commands"; -import CenteredSpinner from "@components/common/CenteredSpinner"; -import ModValidationModal, { - ModValidationModalHandle, - OpenModValidationModalPayload -} from "@components/modals/ModValidationModal"; -import { useGetTranslation } from "@hooks"; -import { memo, useCallback, useEffect, useRef, useState } from "react"; -import UnsafeModRow from "./UnsafeModRow"; -import { dialog } from "@tauri-apps/api"; - -const LocalMods = memo(function LocalMods() { - const validationModalRef = useRef(); - const [filter, setFilter] = useState(""); - const [tempFilter, setTempFilter] = useState(""); - const activeTimeout = useRef(undefined); - const [status, mods, err] = hooks.getLocalMods("LOCAL-REFRESH", { filter }); - const getTranslation = useGetTranslation(); - - useEffect(() => { - commands.refreshLocalDb(); - }, []); - - const onToggleAll = useCallback( - (enabled: boolean) => { - commands - .toggleAll({ enabled }) - .then((warnings) => { - commands.refreshLocalDb(); - for (const modName of warnings) { - dialog.message(getTranslation("PREPATCHER_WARNING", { name: modName }), { - type: "warning", - title: getTranslation("PREPATCHER_WARNING_TITLE", { name: modName }) - }); - } - }) - .catch(console.warn); - }, - [getTranslation] - ); - - const onSearch = (newFilter: string) => { - if (activeTimeout !== null) { - clearTimeout(activeTimeout.current); - } - setTempFilter(newFilter); - activeTimeout.current = setTimeout(() => { - setFilter(newFilter); - }, 450); - }; - - const onValidationIconClicked = useCallback((p: OpenModValidationModalPayload) => { - validationModalRef.current?.open(p); - }, []); - - if (status === "Loading" && mods === null) { - return ; - } else if (status === "Error") { - return
    {err!.toString()}
    ; - } else { - return ( - <> - - {(filter.length >= 0 || mods!.length !== 0) && ( -
    - onSearch(e.target.value)} - /> -
    -
    - - -
    -
    - )} -
    - {filter.length === 0 && mods!.length === 0 && ( -

    {getTranslation("NO_MODS")}

    - )} - {filter !== tempFilter ? ( - - ) : ( - mods!.map((m) => ( - - )) - )} -
    - - ); - } -}); - -export default LocalMods; diff --git a/owmods_gui/frontend/src/components/mods/local/UnsafeModRow.tsx b/owmods_gui/frontend/src/components/mods/local/UnsafeModRow.tsx deleted file mode 100644 index 8a8ba8038..000000000 --- a/owmods_gui/frontend/src/components/mods/local/UnsafeModRow.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { hooks } from "@commands"; -import CenteredSpinner from "@components/common/CenteredSpinner"; -import { memo } from "react"; -import ValidModRow from "./ValidModRow"; -import { OpenModValidationModalPayload } from "@components/modals/ModValidationModal"; -import FailedModRow from "./FailedModRow"; - -export interface UnsafeModRowProps { - uniqueName: string; - onValidationClicked?: (p: OpenModValidationModalPayload) => void; -} - -const UnsafeModRow = memo(function UnsafeModRow(props: UnsafeModRowProps) { - const [status, mod, err] = hooks.getLocalMod("LOCAL-REFRESH", { uniqueName: props.uniqueName }); - - if (status === "Loading" && mod === null) { - return ; - } else if (status === "Error") { - return

    {err!.toString()}

    ; - } else { - if (mod === null) { - return <>; - } else if (mod!.loadState === "invalid") { - return ; - } else { - return ; - } - } -}); - -export default UnsafeModRow; diff --git a/owmods_gui/frontend/src/components/mods/local/ValidModRow.tsx b/owmods_gui/frontend/src/components/mods/local/ValidModRow.tsx deleted file mode 100644 index d270fc178..000000000 --- a/owmods_gui/frontend/src/components/mods/local/ValidModRow.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { commands, hooks } from "@commands"; -import { OpenModValidationModalPayload } from "@components/modals/ModValidationModal"; -import { useGetTranslation } from "@hooks"; -import { confirm } from "@tauri-apps/api/dialog"; -import { LocalMod, ModValidationError } from "@types"; -import { memo, useCallback } from "react"; -import LocalModRow from "./LocalModRow"; -import { dialog } from "@tauri-apps/api"; - -interface LocalModRowProps { - mod: LocalMod; - onValidationClick?: (p: OpenModValidationModalPayload) => void; -} - -const ValidModRow = memo(function ValidModRow({ mod, onValidationClick }: LocalModRowProps) { - const getTranslation = useGetTranslation(); - - const remoteMod = hooks.getRemoteMod("REMOTE-REFRESH", { - uniqueName: mod.manifest.uniqueName - })[1]; - - const autoEnableDeps = hooks.getGuiConfig("GUI_CONFIG_RELOAD")[1]?.autoEnableDeps ?? false; - - const uninstallConfirmText = getTranslation("UNINSTALL_CONFIRM", { - name: mod.manifest.name - }); - - const subtitle = getTranslation("BY", { - author: mod.manifest.author, - version: mod.manifest.version - }); - - const onValidationClicked = useCallback( - (errs: ModValidationError[]) => { - onValidationClick?.({ - uniqueName: mod.manifest.uniqueName, - modName: mod.manifest.name, - errors: errs - }); - }, - [mod.manifest.uniqueName, mod.manifest.name, onValidationClick] - ); - - const onToggle = useCallback( - (newVal: boolean) => { - const task = async () => { - let enableDeps = false; - const hasDisabledDeps = newVal - ? await commands.hasDisabledDeps({ uniqueName: mod.manifest.uniqueName }) - : false; - if (hasDisabledDeps) { - enableDeps = - autoEnableDeps || - (await dialog.ask(getTranslation("ENABLE_DEPS_MESSAGE"), { - type: "info", - title: getTranslation("CONFIRM") - })); - } - const warnings = await commands.toggleMod({ - uniqueName: mod.manifest.uniqueName, - enabled: newVal, - recursive: enableDeps - }); - commands.refreshLocalDb(); - for (const modName of warnings) { - dialog.message(getTranslation("PREPATCHER_WARNING", { name: modName }), { - type: "warning", - title: getTranslation("PREPATCHER_WARNING_TITLE", { - name: modName - }) - }); - } - }; - task(); - }, - [mod.manifest.uniqueName, getTranslation, autoEnableDeps] - ); - - const onOpen = useCallback(() => { - commands.openModFolder({ uniqueName: mod.manifest.uniqueName }); - }, [mod.manifest.uniqueName]); - - const onUninstall = useCallback(() => { - confirm(uninstallConfirmText, getTranslation("CONFIRM")).then((answer) => { - if (answer) { - commands.uninstallMod({ uniqueName: mod.manifest.uniqueName }).then((warnings) => { - commands.refreshLocalDb(); - for (const modName of warnings) { - dialog.message(getTranslation("PREPATCHER_WARNING", { name: modName }), { - type: "warning", - title: getTranslation("PREPATCHER_WARNING_TITLE", { name: modName }) - }); - } - }); - } - }); - }, [uninstallConfirmText, getTranslation, mod.manifest.uniqueName]); - - return ( - - ); -}); - -export default ValidModRow; diff --git a/owmods_gui/frontend/src/components/mods/remote/RemoteModRow.tsx b/owmods_gui/frontend/src/components/mods/remote/RemoteModRow.tsx deleted file mode 100644 index 1d86556bd..000000000 --- a/owmods_gui/frontend/src/components/mods/remote/RemoteModRow.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { commands, hooks } from "@commands"; -import CenteredSpinner from "@components/common/CenteredSpinner"; -import Icon from "@components/common/Icon"; -import ModActionButton from "@components/mods/ModActionButton"; -import ModHeader from "@components/mods/ModHeader"; -import { useGetTranslation } from "@hooks"; -import { dialog } from "@tauri-apps/api"; -import { memo, useCallback } from "react"; -import { BsArrowDown, BsGlobe, BsHammer } from "react-icons/bs"; - -// Stolen from mods website, Rai will never catch me! -const magnitudeMap = [ - { value: 1, symbol: "" }, - { value: 1e3, symbol: "k" }, - { value: 1e6, symbol: "M" }, - { value: 1e9, symbol: "G" }, - { value: 1e12, symbol: "T" }, - { value: 1e15, symbol: "P" }, - { value: 1e18, symbol: "E" } -]; - -const numberFormatRegex = /\.0+$|(\.[0-9]*[1-9])0+$/; - -export const formatNumber = (value: number, digits = 1) => { - const magnitude = magnitudeMap - .slice() - .reverse() - .find((item) => { - return value >= item.value; - }); - return magnitude - ? (value / magnitude.value).toFixed(digits).replace(numberFormatRegex, "$1") + - magnitude.symbol - : "0"; -}; - -export interface RemoteModRowProps { - uniqueName: string; -} - -const RemoteModRow = memo(function RemoteModRow(props: RemoteModRowProps) { - const getTranslation = useGetTranslation(); - const [status, mod, err] = hooks.getRemoteMod("REMOTE-REFRESH", { - uniqueName: props.uniqueName - }); - const busy = hooks.getModBusy("MOD-BUSY", { uniqueName: props.uniqueName })[1]; - - const usePrerelease = getTranslation("USE_PRERELEASE", { - version: mod?.prerelease?.version ?? "" - }); - - const subtitle = getTranslation("BY", { - author: mod?.authorDisplay ?? mod?.author ?? "", - version: mod?.version ?? "" - }); - - const onInstall = useCallback(() => { - commands - .installMod({ uniqueName: props.uniqueName }) - .then(() => { - commands.refreshLocalDb().catch(console.error); - }) - .catch(console.error); - }, [props.uniqueName]); - - const onPrerelease = useCallback(() => { - const task = async () => { - const result = await dialog.ask(getTranslation("PRERELEASE_WARNING"), { - title: usePrerelease - }); - if (result) { - commands - .installMod({ uniqueName: props.uniqueName, prerelease: true }) - .then(() => { - commands.refreshLocalDb().catch(console.error); - }) - .catch(console.error); - } - }; - task(); - }, [getTranslation, usePrerelease, props.uniqueName]); - - const onReadme = useCallback(() => { - commands.openModReadme({ uniqueName: props.uniqueName }).catch(console.warn); - }, [props.uniqueName]); - - if (status === "Loading" && mod === null) { - return ; - } else if (status === "Error") { - return

    {err!.toString()}

    ; - } else { - const remoteMod = mod!; - let desc = remoteMod.description ?? getTranslation("NO_DESCRIPTION"); - if (desc.trim() === "") desc = getTranslation("NO_DESCRIPTION"); - return ( -
    - - {formatNumber(remoteMod.downloadCount)} - {busy ? ( -
    - ) : ( - <> - - - - {mod?.prerelease !== null && ( - - - - )} - - )} - - - -
    - {desc} -
    - ); - } -}); - -const Wrapper = memo(function Wrapper(props: RemoteModRowProps) { - return ( -
    - -
    - ); -}); - -export default Wrapper; diff --git a/owmods_gui/frontend/src/components/mods/remote/RemoteMods.tsx b/owmods_gui/frontend/src/components/mods/remote/RemoteMods.tsx deleted file mode 100644 index b6125da37..000000000 --- a/owmods_gui/frontend/src/components/mods/remote/RemoteMods.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { commands } from "@commands"; -import CenteredSpinner from "@components/common/CenteredSpinner"; -import { useGetTranslation } from "@hooks"; -import { memo, useEffect, useRef, useState } from "react"; -import RemoteModsList from "./RemoteModsList"; - -const RemoteMods = memo(function RemoteMods() { - const [filter, setFilter] = useState(""); - const [tempFilter, setTempFilter] = useState(""); - const getTranslation = useGetTranslation(); - - useEffect(() => { - commands.refreshRemoteDb(); - }, []); - - const activeTimeout = useRef(null); - - const onChangeFilter = (newFilter: string) => { - setTempFilter(newFilter); - if (activeTimeout.current) { - clearTimeout(activeTimeout.current); - } - activeTimeout.current = setTimeout(() => setFilter(newFilter), 450); - }; - - return ( - <> - onChangeFilter(e.target.value)} - /> - {tempFilter !== filter ? : } - - ); -}); - -export default RemoteMods; diff --git a/owmods_gui/frontend/src/components/mods/remote/RemoteModsList.tsx b/owmods_gui/frontend/src/components/mods/remote/RemoteModsList.tsx deleted file mode 100644 index 39b6a5bd1..000000000 --- a/owmods_gui/frontend/src/components/mods/remote/RemoteModsList.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { hooks } from "@commands"; -import CenteredSpinner from "@components/common/CenteredSpinner"; -import { memo } from "react"; -import RemoteModRow from "./RemoteModRow"; -import { Virtuoso } from "react-virtuoso"; - -export interface RemoteModsListProps { - filter: string; -} - -const RemoteModsList = memo(function RemoteModsList(props: RemoteModsListProps) { - const [status, mods, err] = hooks.getRemoteMods("REMOTE-REFRESH", { filter: props.filter }); - - if (status === "Loading" && mods === null) { - return ; - } else if (status === "Error") { - return

    {err!.toString()}

    ; - } else { - return ( - `${index}-${mods![index]}`} - className="mod-list remote" - data={mods!} - itemContent={(_, uniqueName) => } - /> - ); - } -}); - -export default RemoteModsList; diff --git a/owmods_gui/frontend/src/components/mods/updates/UpdateModRow.tsx b/owmods_gui/frontend/src/components/mods/updates/UpdateModRow.tsx deleted file mode 100644 index 6aa66cff7..000000000 --- a/owmods_gui/frontend/src/components/mods/updates/UpdateModRow.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { commands, hooks } from "@commands"; -import CenteredSpinner from "@components/common/CenteredSpinner"; -import Icon from "@components/common/Icon"; -import { useGetTranslation } from "@hooks"; -import { memo, useCallback, useMemo } from "react"; -import { BsArrowUp } from "react-icons/bs"; -import ModActionButton from "../ModActionButton"; -import ModHeader from "../ModHeader"; -import { LocalMod } from "@types"; - -export interface UpdateModRowProps { - uniqueName: string; -} - -const UpdateModRow = memo( - function UpdateModRow({ uniqueName }: UpdateModRowProps) { - const getTranslation = useGetTranslation(); - const [remoteStatus, remoteMod, err1] = hooks.getRemoteMod("REMOTE-REFRESH", { - uniqueName - }); - const [localStatus, unsafeLocalMod, err2] = hooks.getLocalMod("LOCAL-REFRESH", { - uniqueName - }); - const busy = hooks.getModBusy("MOD-BUSY", { uniqueName })[1]; - - // Assertion is safe bc we're only iterating over valid mods - const localMod = unsafeLocalMod?.mod as LocalMod | null; - - const subtitleString = useMemo( - () => `${localMod?.manifest.version ?? "v0"} ๐Ÿกข ${remoteMod?.version ?? "v0"}`, - [remoteMod, localMod] - ); - - const status = [remoteStatus, localStatus]; - - const onModUpdate = useCallback(() => { - commands - .updateMod({ uniqueName }) - .then(() => { - commands.refreshLocalDb().catch(console.warn); - }) - .catch(console.error); - }, [uniqueName]); - - if (status.includes("Loading") && (remoteMod === null || localMod === null)) { - return ; - } else if (status.includes("Error")) { - return ( -

    - {err1?.toString() ?? ""} {err2?.toString() ?? ""} -

    - ); - } else { - return ( -
    - - {busy ? ( - - ) : ( - - - - )} - -
    - ); - } - }, - (prev, next) => prev.uniqueName === next.uniqueName -); - -export default UpdateModRow; diff --git a/owmods_gui/frontend/src/components/mods/updates/UpdateMods.tsx b/owmods_gui/frontend/src/components/mods/updates/UpdateMods.tsx deleted file mode 100644 index 60c56daa5..000000000 --- a/owmods_gui/frontend/src/components/mods/updates/UpdateMods.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { commands, hooks } from "@commands"; -import CenteredSpinner from "@components/common/CenteredSpinner"; -import { useGetTranslation } from "@hooks"; -import { memo, useCallback, useState } from "react"; -import UpdateModRow from "./UpdateModRow"; - -const UpdateMods = memo(function UpdateMods() { - const [status, updates, err] = hooks.getUpdatableMods(["REMOTE-REFRESH", "LOCAL-REFRESH"]); - const [updating, setUpdating] = useState(false); - const getTranslation = useGetTranslation(); - - const onUpdateAll = useCallback(() => { - setUpdating(true); - commands - .updateAll({ uniqueNames: updates ?? [] }) - .then(() => { - commands.refreshLocalDb().catch(console.warn); - }) - .catch(console.warn) - .finally(() => setUpdating(false)); - }, [updates]); - - if (status === "Loading" && updates === null) { - return ; - } else if (status === "Error") { - return
    {err!.toString()}
    ; - } else { - return ( - <> - {updates!.length > 0 ? ( - <> - -
    - {updates!.map((m) => ( - - ))} -
    - - ) : ( -

    {getTranslation("NO_UPDATES")}

    - )} - - ); - } -}); - -export default UpdateMods; diff --git a/owmods_gui/frontend/src/components/nav/Nav.tsx b/owmods_gui/frontend/src/components/nav/Nav.tsx deleted file mode 100644 index e298a1634..000000000 --- a/owmods_gui/frontend/src/components/nav/Nav.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { - BsPlayFill, - BsGearFill, - BsInfoCircleFill, - BsBoxArrowInDown, - BsBoxArrowUpRight, - BsQuestionLg -} from "react-icons/bs"; - -import NavButton from "@components/nav/NavButton"; -import { IconContext } from "react-icons"; -import Icon from "@components/common/Icon"; -import NavMore from "./NavMore"; -import { useCallback, useRef, useState } from "react"; -import SettingsModal from "@components/modals/SettingsModal"; -import InstallFromModal from "@components/modals/InstallFromModal"; -import AboutModal from "@components/modals/AboutModal"; -import Downloads from "../downloads/Downloads"; -import { useGetTranslation } from "@hooks"; -import { commands } from "@commands"; -import { dialog, shell } from "@tauri-apps/api"; -import CenteredSpinner from "@components/common/CenteredSpinner"; -import NavRefreshButton from "./NavRefresh"; -import { ModalHandle } from "@components/modals/Modal"; - -const Nav = () => { - const settingsRef = useRef(); - const installFromRef = useRef(); - const aboutRef = useRef(); - const getTranslation = useGetTranslation(); - - const [areLogsStarting, setLogsStarting] = useState(false); - - const onPlay = useCallback(() => { - const start = () => - commands - .startLogs() - .then(() => setLogsStarting(false)) - .catch(console.warn); - setLogsStarting(true); - const task = async () => { - const hasIssues = await commands.checkDBForIssues(); - const skipWarning = (await commands.getGuiConfig()).noWarning; - if (!skipWarning && hasIssues) { - dialog - .ask(getTranslation("LAUNCH_ANYWAY"), { - type: "warning", - title: getTranslation("CONFIRM") - }) - .then((yes) => { - if (yes) { - start(); - } else { - setLogsStarting(false); - } - }); - } else { - start(); - } - }; - task(); - }, [getTranslation]); - - const onExport = useCallback(() => { - dialog - .save({ - title: getTranslation("EXPORT_MODS"), - filters: [ - { - name: "JSON File", - extensions: ["json"] - } - ] - }) - .then((path) => { - if (path) { - commands.exportMods({ path }).catch(console.error); - } - }); - }, [getTranslation]); - - const onHelp = useCallback(() => { - shell.open("https://github.com/Bwc9876/ow-mod-man/blob/main/owmods_gui/HELP.md"); - }, []); - - return ( - - - - -
    - -
    -
    - ); -}; - -export default Nav; diff --git a/owmods_gui/frontend/src/components/nav/NavButton.tsx b/owmods_gui/frontend/src/components/nav/NavButton.tsx deleted file mode 100644 index e37a24cc5..000000000 --- a/owmods_gui/frontend/src/components/nav/NavButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ReactNode } from "react"; - -export interface NavButtonProps { - children: ReactNode; - labelPlacement?: string; - ariaLabel?: string; - onClick?: () => void; - disabled?: boolean; - className?: string; -} - -const NavButton = (props: NavButtonProps) => { - return ( -
  • - { - if (!props.disabled) props.onClick?.(); - }} - data-tooltip={props.ariaLabel} - data-placement={props.labelPlacement} - > - {props.children} - -
  • - ); -}; - -export default NavButton; diff --git a/owmods_gui/frontend/src/components/nav/NavMore.tsx b/owmods_gui/frontend/src/components/nav/NavMore.tsx deleted file mode 100644 index fda039505..000000000 --- a/owmods_gui/frontend/src/components/nav/NavMore.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import Icon from "@components/common/Icon"; -import { useGetTranslation } from "@hooks"; -import { ReactNode, useRef } from "react"; -import { HiDotsVertical } from "react-icons/hi"; -import NavButton from "./NavButton"; - -export interface NavMoreProps { - children: ReactNode; -} - -const NavMore = (props: NavMoreProps) => { - const getTranslation = useGetTranslation(); - const detailsRef = useRef(null); - - return ( -
  • -
    - - - - - -
      { - if (!detailsRef.current) return; - detailsRef.current.open = false; - }} - role="listbox" - > - {props.children} -
    -
    -
  • - ); -}; - -export default NavMore; diff --git a/owmods_gui/frontend/src/components/nav/NavRefresh.tsx b/owmods_gui/frontend/src/components/nav/NavRefresh.tsx deleted file mode 100644 index 20cdeb230..000000000 --- a/owmods_gui/frontend/src/components/nav/NavRefresh.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import Icon from "@components/common/Icon"; -import NavButton from "./NavButton"; -import { BsArrowRepeat } from "react-icons/bs"; -import { useGetTranslation } from "@hooks"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { commands, hooks } from "@commands"; -import { watchImmediate } from "tauri-plugin-fs-watch-api"; -import { listen } from "@tauri-apps/api/event"; -import { IconContext } from "react-icons"; - -const checkPaths = (paths: string[]) => { - for (const path of paths) { - if ( - path.endsWith("config.json") || - path.endsWith("manifest.json") || - path.endsWith(".dll") || - path.endsWith("OWML.Config.json") || - path.endsWith("settings.json") || - path.endsWith("gui_settings.json") - ) { - return true; - } - } - return false; -}; - -const NavRefreshButton = () => { - const [isRefreshing, setRefreshing] = useState(false); - const [watchingFileSystem, setWatchFS] = useState(false); - const [status, config, err] = hooks.getConfig("CONFIG_RELOAD"); - const guiConfig = hooks.getGuiConfig("GUI_CONFIG_RELOAD")[1]; - const getTranslation = useGetTranslation(); - const currentTimeout = useRef(null); - - const onRefresh = useCallback(() => { - const task = async () => { - setRefreshing(true); - setWatchFS(false); - await commands.refreshLocalDb(); - await commands.refreshRemoteDb(); - await commands.initialSetup(); - setWatchFS(true); - setRefreshing(false); - }; - task(); - }, []); - - useEffect(() => { - let cancel = false; - listen("TOGGLE_FS_WATCH", (e) => { - if (cancel) return; - const enabled = e.payload as boolean; - if (!enabled && currentTimeout.current) { - clearTimeout(currentTimeout.current); - currentTimeout.current = null; - } - setWatchFS(e.payload as boolean); - }); - return () => { - cancel = true; - }; - }, []); - - useEffect(() => { - let cancel = false; - if (status === "Done" && (guiConfig?.watchFs ?? false)) { - commands.getWatcherPaths().then((paths) => { - watchImmediate( - paths, - (e) => { - if (cancel || !watchingFileSystem || !checkPaths(e.paths)) return; - if (currentTimeout.current) { - clearTimeout(currentTimeout.current); - currentTimeout.current = null; - } - currentTimeout.current = setTimeout(onRefresh, 500); - }, - { recursive: true } - ); - }); - } else if (status === "Error") { - console.error(err); - } - return () => { - cancel = true; - }; - }, [onRefresh, err, watchingFileSystem, status, config, guiConfig?.watchFs]); - - return ( - - {/* react-icon's IconContext overrides props sent to the component directly, */} - {/* and since we use that context higher up in the tree we can't pass props, so we need to do this */} - - - - - ); -}; - -export default NavRefreshButton; diff --git a/owmods_gui/frontend/src/components/tabs/Section.tsx b/owmods_gui/frontend/src/components/tabs/Section.tsx deleted file mode 100644 index 5653582ec..000000000 --- a/owmods_gui/frontend/src/components/tabs/Section.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ReactNode, memo } from "react"; - -export interface SectionProps { - shown: boolean; - children: ReactNode; - className?: string; -} - -const Section = memo(function Section(props: SectionProps) { - return ( -
    - {props.children} -
    - ); -}); - -export default Section; diff --git a/owmods_gui/frontend/src/components/tabs/Tab.tsx b/owmods_gui/frontend/src/components/tabs/Tab.tsx deleted file mode 100644 index 11b889aff..000000000 --- a/owmods_gui/frontend/src/components/tabs/Tab.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ReactNode } from "react"; - -export interface TabProps { - selected: boolean; - children: ReactNode; - onClick?: () => void; -} - -const Tab = (props: TabProps) => { - return ( -
    props.onClick?.()} className={`tab${props.selected ? " shown" : ""}`}> -
    {props.children}
    -
    - ); -}; - -export default Tab; diff --git a/owmods_gui/frontend/src/components/tabs/Tabs.tsx b/owmods_gui/frontend/src/components/tabs/Tabs.tsx deleted file mode 100644 index 2123c7d07..000000000 --- a/owmods_gui/frontend/src/components/tabs/Tabs.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { BsDisplay, BsGlobe } from "react-icons/bs"; - -import Tab from "@components/tabs/Tab"; -import Section from "./Section"; -import { memo, useState } from "react"; -import LocalMods from "@components/mods/local/LocalMods"; -import { IconContext } from "react-icons"; -import RemoteMods from "@components/mods/remote/RemoteMods"; -import Icon from "@components/common/Icon"; -import { useGetTranslation } from "@hooks"; -import UpdateMods from "@components/mods/updates/UpdateMods"; -import UpdatesTab from "./UpdatesTab"; - -enum SectionType { - Local, - Remote, - Updates -} - -const Tabs = memo(function Tabs() { - const [shownSection, setShownSection] = useState(SectionType.Local); - const getTranslation = useGetTranslation(); - - return ( - <> - -
    -
    - setShownSection(SectionType.Local)} - > - - - setShownSection(SectionType.Remote)} - > - - - setShownSection(SectionType.Updates)} - /> -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    - - ); -}); - -export default Tabs; diff --git a/owmods_gui/frontend/src/components/tabs/UpdatesTab.tsx b/owmods_gui/frontend/src/components/tabs/UpdatesTab.tsx deleted file mode 100644 index 35f68ff66..000000000 --- a/owmods_gui/frontend/src/components/tabs/UpdatesTab.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { hooks } from "@commands"; -import Icon from "@components/common/Icon"; -import { useGetTranslation } from "@hooks"; -import { BsArrowUpCircle, BsArrowUpCircleFill } from "react-icons/bs"; -import Tab, { TabProps } from "./Tab"; - -const UpdatesTab = (props: Omit) => { - const updatesList = hooks.getUpdatableMods(["LOCAL-REFRESH", "REMOTE-REFRESH"])[1]; - const getTranslation = useGetTranslation(); - - const count = updatesList?.length ?? 0; - const countLabel = count === 0 ? "" : `(${updatesList?.length})`; - - return ( - - - - ); -}; - -export default UpdatesTab; diff --git a/owmods_gui/frontend/src/fonts.ts b/owmods_gui/frontend/src/fonts.ts new file mode 100644 index 000000000..a5362a097 --- /dev/null +++ b/owmods_gui/frontend/src/fonts.ts @@ -0,0 +1,4 @@ +import "@fontsource/roboto/300.css"; +import "@fontsource/roboto/400.css"; +import "@fontsource/roboto/500.css"; +import "@fontsource/roboto/700.css"; diff --git a/owmods_gui/frontend/src/hooks.ts b/owmods_gui/frontend/src/hooks.ts index f15f2cd19..acb4890e8 100644 --- a/owmods_gui/frontend/src/hooks.ts +++ b/owmods_gui/frontend/src/hooks.ts @@ -5,9 +5,7 @@ import { TranslationMap, type TranslationKey } from "@components/common/TranslationContext"; -import ThemeMap from "./theme"; -import { Theme } from "@types"; -import rainbowTheme from "@styles/rainbow.scss?inline"; +import { FailedMod, LocalMod, RemoteMod, UnsafeLocalMod } from "@types"; export type LoadState = "Loading" | "Done" | "Error"; @@ -77,12 +75,70 @@ export const useGetTranslation = () => { ); }; -export const useTheme = (theme: Theme, rainbow: boolean) => { +export function useDebounce(value: TValue, delayMs: number): TValue { + const [debouncedValue, setDebouncedValue] = useState(value); + useEffect(() => { - let newTheme = ThemeMap[theme ?? "White"]; - if (rainbow) { - newTheme += rainbowTheme; - } - document.getElementById("currentTheme")!.textContent = newTheme; - }, [theme, rainbow]); + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delayMs); + + return () => { + clearTimeout(handler); + }; + }, [value, delayMs]); + + return debouncedValue; +} + +export interface UnifiedMod { + name: string; + author: string; + description: string | undefined; + version: string; + enabled: boolean; + outdated: boolean; +} + +const safeOrNull = (mod: UnsafeLocalMod | null) => { + if (mod === null) return null; + if (mod.loadState === "invalid") { + return null; + } else { + return mod.mod as LocalMod; + } }; + +export function useUnifiedMod(local: UnsafeLocalMod | null, remote: RemoteMod | null) { + const name = useMemo( + () => + remote?.name ?? + safeOrNull(local)?.manifest.name ?? + ((local?.mod as FailedMod) ?? { displayPath: null }).displayPath ?? + "", + [local, remote] + ); + const author = useMemo( + () => remote?.authorDisplay ?? remote?.author ?? safeOrNull(local)?.manifest.author ?? "โ€”", + [local, remote] + ); + + const description = remote?.description; + + const version = useMemo(() => safeOrNull(local)?.manifest.version ?? "โ€”", [local]); + + const enabled = safeOrNull(local)?.enabled ?? false; + + const outdated = useMemo( + () => safeOrNull(local)?.errors.find((e) => e.errorType === "Outdated") ?? false, + [local] + ); + return { + name, + author, + description, + version, + enabled, + outdated + } as UnifiedMod; +} diff --git a/owmods_gui/frontend/src/logging.ts b/owmods_gui/frontend/src/logging.ts deleted file mode 100644 index 038bb9b38..000000000 --- a/owmods_gui/frontend/src/logging.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { listen } from "@tauri-apps/api/event"; -import { LogPayload } from "@types"; - -type LogType = "DEBUG" | "INFO" | "WARNING" | "ERROR"; - -export const startConsoleLogListen = () => { - listen("LOG", (e) => { - const msg = e.payload as LogPayload; - if (msg.target !== "progress" && !msg.target?.startsWith("game")) { - switch (msg.logType as LogType) { - case "DEBUG": - console.debug(msg.message); - break; - case "INFO": - console.info(msg.message); - break; - case "WARNING": - console.warn(msg.message); - break; - case "ERROR": - console.error(msg.message); - break; - default: - console.log(msg.message); - break; - } - } - }); -}; diff --git a/owmods_gui/frontend/src/logs.tsx b/owmods_gui/frontend/src/logs.tsx index 39d1de19a..daf273dba 100644 --- a/owmods_gui/frontend/src/logs.tsx +++ b/owmods_gui/frontend/src/logs.tsx @@ -1,10 +1,8 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import App from "@components/logs/LogApp"; -import "@styles/style.scss"; import { commands } from "@commands"; import { listen } from "@tauri-apps/api/event"; -import { startConsoleLogListen } from "./logging"; +import LogApp from "@components/logs/LogApp"; let port = 0; @@ -15,9 +13,7 @@ listen("GAME-START", (e) => { ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + ); }).then(() => commands.runGame().catch(console.warn)); - -startConsoleLogListen(); diff --git a/owmods_gui/frontend/src/main.tsx b/owmods_gui/frontend/src/main.tsx index ebce66c62..9209e5d04 100644 --- a/owmods_gui/frontend/src/main.tsx +++ b/owmods_gui/frontend/src/main.tsx @@ -1,7 +1,6 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import App from "@components/MainApp"; -import "@styles/style.scss"; +import App from "@components/main/MainApp"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/owmods_gui/frontend/src/styles.css b/owmods_gui/frontend/src/styles.css new file mode 100644 index 000000000..a4281987e --- /dev/null +++ b/owmods_gui/frontend/src/styles.css @@ -0,0 +1,11 @@ +body { + /* Try to reduce white screen */ + background-color: #121212; + height: 100vh; +} + +#root { + height: 100vh; + display: flex; + flex-direction: column; +} diff --git a/owmods_gui/frontend/src/styles/about.scss b/owmods_gui/frontend/src/styles/about.scss deleted file mode 100644 index d2477f093..000000000 --- a/owmods_gui/frontend/src/styles/about.scss +++ /dev/null @@ -1,41 +0,0 @@ -.about-modal { - display: grid; - grid-auto-flow: row; - justify-content: center; - text-align: center; - align-content: center; - height: 100%; -} - -$logo-size: 10em; - -.about-modal img { - justify-self: center; - width: $logo-size; - height: $logo-size; - filter: drop-shadow(0px -1px 10px var(--primary)); -} - -.about-modal h1, -.about-modal p { - padding: 0; - margin: 0; -} - -.about-modal a[role="button"] { - margin-right: $margin-md; - justify-content: center; - justify-self: center; -} - -.about-modal a[role="button"] svg { - margin: $margin-sm !important; -} - -.about-modal .links { - margin-top: $margin; - display: flex; - flex-direction: row; - align-content: center; - justify-content: center; -} diff --git a/owmods_gui/frontend/src/styles/downloads.scss b/owmods_gui/frontend/src/styles/downloads.scss deleted file mode 100644 index ae00ad76b..000000000 --- a/owmods_gui/frontend/src/styles/downloads.scss +++ /dev/null @@ -1,100 +0,0 @@ -.download-badge { - position: absolute; - top: -$margin-md; - right: $margin; - color: var(--primary-dark); - padding: 2px 6px; - font-size: small; - border-radius: 50px; -} - -.downloads-popout { - z-index: 99; - position: absolute; - top: 80%; - left: 1%; - right: auto; - bottom: auto; - width: 1000%; - max-width: 60vw; - max-height: 35vh; - padding: 0; - overflow-y: auto; - border-radius: 5px; - border: 1px solid var(--accent-bg); - background-color: var(--accent-bg-darker); -} - -.downloads-popout::-webkit-scrollbar { - width: $margin; -} - -.clear-downloads { - position: absolute; - top: $margin-md; - right: $margin-md; - padding: 0.1em $margin !important; - left: auto; - bottom: auto; - color: var(--primary-muted); -} - -a.clear-downloads svg { - margin: $margin-sm; - margin-right: $margin-sm !important; -} - -.downloads-popout header { - color: var(--primary-really-light); - background-color: var(--accent-bg); - font-size: large; - border-bottom: $row-border; - padding: 0; - margin: 0; - text-align: center; -} - -.downloads-popout p { - margin: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.downloads-row { - display: flex; - flex-direction: column; - justify-content: center; - margin: 0 $margin; - padding: $margin; - border-bottom: 1px solid var(--accent-bg); -} - -.downloads-row svg { - font-size: x-large; - display: none; - padding-right: $margin; -} - -.downloads-row progress { - box-sizing: content-box; - margin: $margin 0; -} - -.downloads-row.download-done progress { - background-color: var(--primary-muted); -} - -.downloads-row.download-done svg.download-icon-success { - display: initial; -} - -.downloads-row.download-failed svg.download-icon-failure { - display: initial; -} - -.no-downloads { - padding: $margin-xl; - text-align: center; - color: var(--primary-dark); -} diff --git a/owmods_gui/frontend/src/styles/files.scss b/owmods_gui/frontend/src/styles/files.scss deleted file mode 100644 index 10e34a9ac..000000000 --- a/owmods_gui/frontend/src/styles/files.scss +++ /dev/null @@ -1,27 +0,0 @@ -input::-webkit-file-upload-button { - background-color: var(--button-bg) !important; - border: none !important; - padding: $margin $margin-md !important; - margin-right: $margin !important; -} - -input::-webkit-file-upload-button:hover { - background-color: var(--button-bg-hover) !important; -} - -.file-input-row { - display: grid; - grid-template-columns: 0.8fr 0.2fr; - grid-column-gap: $margin; -} - -.file-input-row .browse-button { - display: flex; - align-content: center; - justify-content: center; - height: 93%; -} - -.file-input-row .browse-button svg { - margin-right: $margin; -} diff --git a/owmods_gui/frontend/src/styles/install-from.scss b/owmods_gui/frontend/src/styles/install-from.scss deleted file mode 100644 index 3659f9a22..000000000 --- a/owmods_gui/frontend/src/styles/install-from.scss +++ /dev/null @@ -1,31 +0,0 @@ -div.install-source { - display: grid; - grid-template-rows: 1fr; - grid-template-columns: 1fr 0.3fr; - column-gap: $margin-md; - height: 100%; -} - -.install-source button { - justify-content: center; - height: 100%; -} - -.install-source button, -.install-source input { - padding: $margin $margin-md; -} - -.install-source button svg { - margin: 0 $margin !important; -} - -.install-warning svg { - padding-top: $margin-sm; - margin-right: $margin-sm; -} - -.install-warning { - color: var(--primary-muted); - padding: 0; -} diff --git a/owmods_gui/frontend/src/styles/logs.scss b/owmods_gui/frontend/src/styles/logs.scss deleted file mode 100644 index 498128645..000000000 --- a/owmods_gui/frontend/src/styles/logs.scss +++ /dev/null @@ -1,127 +0,0 @@ -$log-border: 1px solid var(--accent-bg); - -.logs { - display: flex; - overflow: hidden; - padding: $margin-md; - - & .log-list { - height: 100%; - overflow-x: hidden; - overflow-y: auto; - contain: strict; - background-color: var(--accent-bg-darker); - padding: $margin-sm; - border-radius: 5px; - - & .log-line { - display: flex; - width: 100%; - padding: $margin $margin-md; - border-bottom: $log-border; - align-items: center; - - & .message { - flex-grow: 1; - width: 65%; - -webkit-user-select: text; - user-select: text; - } - - & .sender { - width: 20%; - margin-right: $margin-lg; - border-bottom: none !important; - - & > span { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - - & .count { - font-size: small; - margin-left: $margin-lg; - margin-right: $margin; - background-color: var(--accent-bg-lighter); - padding: 0 $margin-md; - border-radius: 10px; - } - - & .message.type- { - &message { - color: var(--log-message); - } - - &info { - color: var(--log-info); - } - - &success { - color: var(--log-success); - } - - &warning { - color: var(--log-warning); - } - - &fatal, - &error { - color: var(--log-error); - } - - &fatal { - font-weight: bolder; - } - - &debug { - color: var(--log-debug); - } - } - } - } - - & .log-actions { - display: flex; - flex-direction: row; - align-content: center; - justify-content: center; - height: fit-content; - - & a[role="button"] { - align-self: center; - white-space: nowrap; - padding: $margin-md; - margin: $margin $margin-xl; - } - - & > div { - margin: $margin; - flex-grow: 1; - display: flex; - align-content: center; - height: 100%; - } - - & span { - display: flex; - align-self: center; - white-space: nowrap; - margin: $margin-sm; - } - - & > label { - flex-shrink: 1; - width: 50%; - height: 100%; - margin: $margin; - } - - & > label[for="filter"], - & > label[for="search"] { - flex-direction: column; - } - } -} diff --git a/owmods_gui/frontend/src/styles/modals.scss b/owmods_gui/frontend/src/styles/modals.scss deleted file mode 100644 index 0b8600e52..000000000 --- a/owmods_gui/frontend/src/styles/modals.scss +++ /dev/null @@ -1,45 +0,0 @@ -dialog article { - width: 80%; - max-height: 70%; - background-color: var(--bg); - border-radius: 5px; - display: flex; - flex-direction: column; - overflow: hidden; -} - -dialog article header { - background-color: var(--accent-bg); - margin-bottom: $margin-xl; - padding: $margin-lg; - display: flex; - align-items: center; - justify-content: center; - border: none; -} - -dialog article div.modal-body { - flex-grow: 1; - overflow-x: hidden; - overflow-y: auto; - margin: 0 $margin-lg; -} - -dialog article footer { - background-color: var(--bg); - padding: $margin $margin-lg; - margin-top: $margin; -} - -dialog article footer a:not(:last-child) { - margin-right: $margin; -} - -dialog article form { - margin-bottom: 0; -} - -dialog article form input, -dialog article form select { - margin-bottom: $margin !important; -} diff --git a/owmods_gui/frontend/src/styles/mods.scss b/owmods_gui/frontend/src/styles/mods.scss deleted file mode 100644 index fa69db6bb..000000000 --- a/owmods_gui/frontend/src/styles/mods.scss +++ /dev/null @@ -1,141 +0,0 @@ -.mod-list { - height: 100%; - contain: strict; - overflow-y: auto; - overflow-x: hidden; - margin-top: $margin; - scroll-behavior: auto; - background-color: var(--accent-bg-darker); - padding: 0 $margin-lg; - border-radius: 5px; -} - -$row-border: 1px solid var(--accent-bg); - -.mod-row { - border-bottom: $row-border; - padding: $margin 0; - width: 100%; - min-width: 0; -} - -.remote-mod-search { - margin-bottom: 0 !important; -} - -.mod-row.remote { - padding-right: 1.7em; -} - -.mod-row.local:last-child, -div:has(.mod-row.remote):last-child > .mod-row { - border-bottom: none; -} - -.mod-row.loading { - height: 55px; -} - -.mod-row.remote .loading { - height: 45px; -} - -.mod-header { - display: grid; - grid-template-columns: minmax(0, 1fr); - gap: $margin; - grid-template-areas: "heading actions"; -} - -.mod-header:focus { - color: var(--primary); -} - -.mod-header::after { - grid-area: expand; - margin-right: $margin; -} - -.mod-name { - margin-right: $margin; - font-weight: bold; - white-space: nowrap; -} - -.mod-actions { - display: grid; - grid-auto-flow: column; - grid-area: actions; - justify-self: right; - align-items: center; - column-gap: $margin; -} - -.mod-actions svg { - font-size: large; -} - -.mod-actions .mod-warning { - color: orange; -} - -.mod-actions .mod-error { - color: #b8211c; -} - -.mod-authors { - color: var(--primary-muted); -} - -.mod-description { - width: 100%; - display: block; - padding-right: 6.5em; -} - -.update-all-button { - margin: 0; -} - -.mod-row.local .mod-name, -.mod-row.local .mod-description { - font-weight: light; - color: var(--primary-muted); -} - -.mod-row.local:has(input:checked) .mod-name, -.mod-row.local:has(input:checked) .mod-description { - color: var(--body-color); -} - -.local-toolbar { - display: flex; - flex-direction: row; -} - -.local-toolbar .search, -.local-mods-buttons button { - padding: $margin-sm initial; -} - -.local-toolbar .search { - width: 70%; - flex-grow: 1; - margin: 0 $margin 0 0; -} - -.local-toolbar .gap { - flex-grow: 1; -} - -.local-mods-buttons { - display: flex; - flex-grow: 1; - flex-direction: row; - column-gap: $margin; -} - -.local-mods-buttons button { - white-space: nowrap; - margin-bottom: 0; -} diff --git a/owmods_gui/frontend/src/styles/nav.scss b/owmods_gui/frontend/src/styles/nav.scss deleted file mode 100644 index 40f251ab8..000000000 --- a/owmods_gui/frontend/src/styles/nav.scss +++ /dev/null @@ -1,87 +0,0 @@ -.nav-wrapper { - background-color: var(--accent-bg-darkest); - width: 100%; -} - -svg.nav-icon { - width: $margin-lg; - height: $margin-lg; - margin: 0; - font-size: medium; -} - -.nav-icon:not(details[role="list"] .nav-icon) { - margin: 0 $margin-lg; -} - -svg.main-icon { - font-size: larger; - height: $margin-xl; - width: auto; -} - -// Overriding all of pico's dropdown stuff -// Translation: !important time - -details[role="list"] summary { - padding: 0 !important; - background-color: var(--accent-bg-darkest) !important; - border: none !important; -} - -details[role="list"] summary::after { - display: none; -} - -details[role="list"] summary:focus { - outline: none !important; - box-shadow: none !important; -} - -details[role="list"] summary + ul { - top: calc($margin-lg * 2); - right: $margin; -} - -details[role="list"] a { - display: flex !important; - justify-content: left; -} - -details[role="list"] a:hover { - cursor: pointer; -} - -details[role="list"] a svg { - margin-right: $margin; -} - -span.alert-row { - z-index: 100; - position: fixed; - left: 0; - right: 0; - top: 0; - display: block; - text-align: center; - width: 100%; - padding: $margin-sm; - margin-top: 0; - background-color: var(--accent-bg); -} - -.refresh-icon-loading { - animation: spin 1.5s linear infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 50% { - transform: rotate(180deg); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/owmods_gui/frontend/src/styles/pico.scss b/owmods_gui/frontend/src/styles/pico.scss deleted file mode 100644 index 03e5b4189..000000000 --- a/owmods_gui/frontend/src/styles/pico.scss +++ /dev/null @@ -1,23 +0,0 @@ -// Edit the modules we need for pico here - -$enable-transitions: true; - -@import "@picocss/pico/scss/variables.scss"; - -@import "@picocss/pico/scss/themes/default/styles.scss"; - -@import "@picocss/pico/scss/layout/document.scss"; - -@import "@picocss/pico/scss/content/typography.scss"; -@import "@picocss/pico/scss/content/button.scss"; -@import "@picocss/pico/scss/content/form.scss"; -@import "@picocss/pico/scss/content/form-checkbox-radio.scss"; - -@import "@picocss/pico/scss/components/nav.scss"; -@import "@picocss/pico/scss/components/dropdown.scss"; -@import "@picocss/pico/scss/components/modal.scss"; -@import "@picocss/pico/scss/components/progress.scss"; - -@import "@picocss/pico/scss/utilities/accessibility.scss"; -@import "@picocss/pico/scss/utilities/loading.scss"; -@import "@picocss/pico/scss/utilities/tooltip.scss"; diff --git a/owmods_gui/frontend/src/styles/rainbow.scss b/owmods_gui/frontend/src/styles/rainbow.scss deleted file mode 100644 index 6022839c7..000000000 --- a/owmods_gui/frontend/src/styles/rainbow.scss +++ /dev/null @@ -1,12 +0,0 @@ -:root { - animation: rainbow 3s linear infinite; -} - -@keyframes rainbow { - 0% { - filter: hue-rotate(0deg); - } - 100% { - filter: hue-rotate(360deg); - } -} diff --git a/owmods_gui/frontend/src/styles/scrollbars.scss b/owmods_gui/frontend/src/styles/scrollbars.scss deleted file mode 100644 index 418ee3237..000000000 --- a/owmods_gui/frontend/src/styles/scrollbars.scss +++ /dev/null @@ -1,15 +0,0 @@ -*::-webkit-scrollbar { - background-color: var(--accent-bg); - border-radius: $tab-border-rad; - width: $margin-md; -} - -*::-webkit-scrollbar-thumb { - border-radius: $tab-border-rad; - background-color: var(--primary-dark); - transition: background-color 4s; -} - -*::-webkit-scrollbar-thumb:hover { - background-color: var(--primary-really-dark); -} diff --git a/owmods_gui/frontend/src/styles/settings.scss b/owmods_gui/frontend/src/styles/settings.scss deleted file mode 100644 index ced873ee2..000000000 --- a/owmods_gui/frontend/src/styles/settings.scss +++ /dev/null @@ -1,56 +0,0 @@ -.settings h4 { - margin-bottom: $margin; - margin-top: $margin-md; - display: flex; - align-items: center; - justify-items: stretch; - padding-bottom: $margin-sm; - border-bottom: 1px solid var(--accent-bg); - - & .reset-button { - height: 100%; - display: flex; - margin-left: auto; - margin-right: $margin; - - & svg { - height: 100%; - } - } -} - -.settings-row > div { - display: grid; - grid-template-rows: 1fr; - grid-template-columns: 1fr 0.3fr; - column-gap: $margin-md; - height: 100%; -} - -.settings-row .file-input-row .browse-button { - width: 90%; -} - -.settings-row input:not([type="checkbox"]), -.settings-row button { - margin-bottom: $margin; - padding: $margin $margin-md; -} - -.settings-row input { - border-color: var(--accent-bg); -} - -.settings-row label::before { - white-space: normal !important; - width: 15em; -} - -.settings-row label[data-tooltip], -.file-input-row label[data-tooltip] { - border-bottom: none !important; -} - -.settings-row input[role="switch"] { - margin-top: $margin-sm; -} diff --git a/owmods_gui/frontend/src/styles/setup.scss b/owmods_gui/frontend/src/styles/setup.scss deleted file mode 100644 index 23cc32928..000000000 --- a/owmods_gui/frontend/src/styles/setup.scss +++ /dev/null @@ -1,5 +0,0 @@ -.owml-setup { - display: grid; - grid-auto-flow: row; - row-gap: $margin-md; -} diff --git a/owmods_gui/frontend/src/styles/style.scss b/owmods_gui/frontend/src/styles/style.scss deleted file mode 100644 index 88e564d4c..000000000 --- a/owmods_gui/frontend/src/styles/style.scss +++ /dev/null @@ -1,111 +0,0 @@ -@import "pico.scss"; -@import "vars.scss"; - -html { - overflow: hidden; -} - -body { - -webkit-user-select: none; - user-select: none; - margin: 0; -} - -*::selection { - background-color: var(--primary-muted); -} - -summary::marker, -summary::-webkit-details-marker { - display: none !important; -} - -select { - cursor: pointer; -} - -.container, -.container-fluid { - margin: 0; - height: 100vh; - overflow: hidden; - display: flex; - flex-direction: column; -} - -.container { - width: 100vw; -} - -.fix-icons { - display: flex; - align-items: center; - align-content: center; -} - -.center { - height: 100%; - display: flex; - justify-content: center; - align-items: center; - margin-bottom: 0; -} - -.d-none { - display: none; -} - -.fill { - width: 100vw !important; - height: 100vh !important; -} - -a[role="button"], -button { - background-color: var(--button-bg); - border: none; - padding: $margin $margin-md; -} - -a[role="button"]:hover, -button:hover { - background-color: var(--button-bg-hover); -} - -a[role="button"].secondary, -button.secondary { - background-color: var(--secondary-button-bg); -} - -a[role="button"].secondary:hover, -button.secondary:hover { - background-color: var(--secondary-button-bg-hover); -} - -.muted { - color: var(--primary-muted); -} - -a { - cursor: pointer; -} - -.max-width { - padding: 0 $margin-xl; - max-width: $max-width; - margin: auto; - width: 100%; -} - -@import "files.scss"; -@import "nav.scss"; -@import "tabs.scss"; -@import "mods.scss"; -@import "modals.scss"; -@import "settings.scss"; -@import "about.scss"; -@import "downloads.scss"; -@import "logs.scss"; -@import "install-from.scss"; -@import "scrollbars.scss"; -@import "setup.scss"; diff --git a/owmods_gui/frontend/src/styles/tabs.scss b/owmods_gui/frontend/src/styles/tabs.scss deleted file mode 100644 index 8c130592c..000000000 --- a/owmods_gui/frontend/src/styles/tabs.scss +++ /dev/null @@ -1,67 +0,0 @@ -.tabs-wrapper { - background-color: var(--accent-bg-darkest); - border-bottom: $border-size solid var(--accent-bg); -} - -.tabs { - display: grid; - grid-template-columns: repeat(3, 1fr); - grid-template-rows: 1fr; - grid-column-gap: $margin; - grid-row-gap: 0px; - align-items: center; -} - -.tab { - color: var(--primary); - background-color: var(--accent-bg-darker); - cursor: pointer; - text-align: center; - transition: background-color 0.2s, color 0.2s; - border-top-left-radius: $tab-border-rad; - border-top-right-radius: $tab-border-rad; - padding: $margin 0; -} - -.tab .fix-icons { - justify-content: center; -} - -.tab.shown { - color: var(--primary-light); - background-color: var(--accent-bg); -} - -.tab:hover { - color: var(--primary-light); - background-color: var(--accent-bg-lighter); -} - -.tab-icon { - margin-right: $margin; -} - -.sections { - display: flex; - flex-direction: column; - flex-grow: 1; - overflow: hidden; - align-items: center; -} - -.section { - @extend .max-width; - display: none; - flex-direction: column; - height: 100%; - overflow: hidden; - padding: $margin-xl; -} - -.section.remote { - overflow-y: hidden; -} - -.section.shown { - display: flex; -} diff --git a/owmods_gui/frontend/src/styles/themes/blue.scss b/owmods_gui/frontend/src/styles/themes/blue.scss deleted file mode 100644 index f563ddd22..000000000 --- a/owmods_gui/frontend/src/styles/themes/blue.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "theme-base.scss"; -@include theme_base(skyblue); diff --git a/owmods_gui/frontend/src/styles/themes/blurple.scss b/owmods_gui/frontend/src/styles/themes/blurple.scss deleted file mode 100644 index cdd114bc2..000000000 --- a/owmods_gui/frontend/src/styles/themes/blurple.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "theme-base.scss"; -@include theme_base(#94a0cc); diff --git a/owmods_gui/frontend/src/styles/themes/ghostGreen.scss b/owmods_gui/frontend/src/styles/themes/ghostGreen.scss deleted file mode 100644 index 093fd3395..000000000 --- a/owmods_gui/frontend/src/styles/themes/ghostGreen.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "theme-base.scss"; -@include theme_base(rgb(51 255 204)); diff --git a/owmods_gui/frontend/src/styles/themes/green.scss b/owmods_gui/frontend/src/styles/themes/green.scss deleted file mode 100644 index 9242a75bd..000000000 --- a/owmods_gui/frontend/src/styles/themes/green.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "theme-base.scss"; -@include theme_base(lightgreen); diff --git a/owmods_gui/frontend/src/styles/themes/orange.scss b/owmods_gui/frontend/src/styles/themes/orange.scss deleted file mode 100644 index afc46ce85..000000000 --- a/owmods_gui/frontend/src/styles/themes/orange.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "theme-base.scss"; -@include theme_base(#ffab8a); diff --git a/owmods_gui/frontend/src/styles/themes/pink.scss b/owmods_gui/frontend/src/styles/themes/pink.scss deleted file mode 100644 index eb30c8cab..000000000 --- a/owmods_gui/frontend/src/styles/themes/pink.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "theme-base.scss"; -@include theme_base(pink); diff --git a/owmods_gui/frontend/src/styles/themes/purple.scss b/owmods_gui/frontend/src/styles/themes/purple.scss deleted file mode 100644 index ebd09dc55..000000000 --- a/owmods_gui/frontend/src/styles/themes/purple.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "theme-base.scss"; -@include theme_base(rgb(127, 83, 198)); diff --git a/owmods_gui/frontend/src/styles/themes/theme-base.scss b/owmods_gui/frontend/src/styles/themes/theme-base.scss deleted file mode 100644 index 18e5fff84..000000000 --- a/owmods_gui/frontend/src/styles/themes/theme-base.scss +++ /dev/null @@ -1,42 +0,0 @@ -$msg-types: ( - "message": white, - "debug": grey, - "info": rgb(64, 145, 211), - "warning": orange, - "error": red, - "success": yellowgreen -); - -@mixin theme_base($primary) { - $bg: mix($primary, black, 10%); - $accent-bg: mix($primary, $bg, 10%); - $accent-bg-darker: mix($accent-bg, black, 40%); - $primary-really-dark: mix($primary, black, 50%); - $accent-bg-lighter: mix($accent-bg, white, 80%); - $button-bg: $primary-really-dark; - $secondary-button-bg: $accent-bg-lighter; - :root { - @each $name, $color in $msg-types { - --log-#{$name}: #{mix($primary, $color, 20%)}; - } - --primary: #{$primary} !important; - --bg: #{$bg}; - --accent-bg: #{$accent-bg}; - --accent-bg-lighter: #{$accent-bg-lighter}; - --primary-really-dark: #{$primary-really-dark}; - --accent-bg-darker: #{$accent-bg-darker}; - --accent-bg-darkest: #{mix($accent-bg-darker, black, 40%)}; - --button-bg: #{$button-bg}; - --secondary-button-bg: #{$secondary-button-bg}; - --primary-muted: #{mix($primary, grey, 10%)}; - --primary-dark: #{mix($primary, black, 80%)}; - --primary-light: #{mix($primary, white, 80%)}; - --primary-really-light: #{mix($primary, white, 10%)}; - --accent-bg-lightest: #{mix($accent-bg, white, 50%)}; - --active-bg: #{mix($primary, $bg, 20%)}; - --button-bg-hover: #{mix($button-bg, black, 90%)}; - --secondary-button-bg-hover: #{mix($secondary-button-bg, black, 90%)}; - --modal-overlay-background-color: #{scale-color($accent-bg-darker, $alpha: -15%)} !important; - --themed-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{$primary}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); - } -} diff --git a/owmods_gui/frontend/src/styles/themes/white.scss b/owmods_gui/frontend/src/styles/themes/white.scss deleted file mode 100644 index 12c907dd6..000000000 --- a/owmods_gui/frontend/src/styles/themes/white.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "theme-base.scss"; -@include theme_base(white); diff --git a/owmods_gui/frontend/src/styles/themes/yellow.scss b/owmods_gui/frontend/src/styles/themes/yellow.scss deleted file mode 100644 index 545f0b46b..000000000 --- a/owmods_gui/frontend/src/styles/themes/yellow.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "theme-base.scss"; -@include theme_base(rgb(235, 235, 173)); diff --git a/owmods_gui/frontend/src/styles/vars.scss b/owmods_gui/frontend/src/styles/vars.scss deleted file mode 100644 index ae8283778..000000000 --- a/owmods_gui/frontend/src/styles/vars.scss +++ /dev/null @@ -1,44 +0,0 @@ -// Manages *just* the theming for the app, use styles.scss for actual classes and such - -// Sizing/Spacing -$border-size: 0.15em; -$tab-border-rad: 0.2em; -$margin: 0.4em; -$margin-sm: 0.2em; -$margin-md: 0.6em; -$margin-lg: 1em; -$margin-xl: 1.2em; -$max-width: 1200px; - -:root { - --primary-hover: var(--primary-dark); - --color: var(--primary-really-light); - --muted-color: var(--primary-muted); - --h4-color: var(--primary-really-light); - --background-color: var(--bg); - --switch-color: var(--primary-really-light); - --switch-background-color: var(--accent-bg); - --switch-checked-background-color: var(--primary-really-dark); - --accordion-open-summary-color: var(--primary); - --accordion-border-color: var(--accent-bg); - --tooltip-color: var(--primary-really-light); - --tooltip-background-color: var(--accent-bg-lighter); - --dropdown-color: var(--primary); - --dropdown-border-color: var(--accent-bg); - --dropdown-background-color: var(--accent-bg-darker); - --dropdown-hover-background-color: var(--accent-bg); - --form-element-background-color: var(--accent-bg); - --form-element-disabled-background-color: var(--accent-bg-lighter); - --form-element-border-color: var(--accent-bg); - --form-element-disabled-border-color: var(--accent-bg-lighter); - --form-element-disabled-opacity: 60%; - --form-element-color: var(--primary-really-light); - --form-element-placeholder-color: var(--muted-color); - --form-element-active-background-color: var(--form-element-background-color); - --form-element-active-border-color: var(--primary); - --form-element-focus-color: var(--primary-focus); - --progress-color: var(--primary); - --progress-background-color: var(--accent-bg); - --loading-spinner-opacity: 0.5; - --font-size: 16px !important; // Pico resizes the font based on breakpoints, don't do that -} diff --git a/owmods_gui/frontend/src/theme.ts b/owmods_gui/frontend/src/theme.ts index fbbf46eb8..75c62317c 100644 --- a/owmods_gui/frontend/src/theme.ts +++ b/owmods_gui/frontend/src/theme.ts @@ -1,25 +1,121 @@ -import { Theme } from "@types"; +import { createTheme } from "@mui/material"; +import { green, grey, red } from "@mui/material/colors"; -import blue from "@styles/themes/blue.scss?inline"; -import blurple from "@styles/themes/blurple.scss?inline"; -import ghostGreen from "@styles/themes/ghostGreen.scss?inline"; -import green from "@styles/themes/green.scss?inline"; -import orange from "@styles/themes/orange.scss?inline"; -import pink from "@styles/themes/pink.scss?inline"; -import purple from "@styles/themes/purple.scss?inline"; -import white from "@styles/themes/white.scss?inline"; -import yellow from "@styles/themes/yellow.scss?inline"; +declare module "@mui/material/styles" { + export interface Palette { + neutral: Palette["primary"]; + } -const ThemeMap: Record = { - Blue: blue, - Blurple: blurple, - GhostlyGreen: ghostGreen, - Green: green, - Orange: orange, - Pink: pink, - Purple: purple, - White: white, - Yellow: yellow -}; + export interface PaletteOptions { + neutral: PaletteOptions["primary"]; + } +} -export default ThemeMap; +declare module "@mui/material/Button" { + export interface ButtonPropsColorOverrides { + neutral: true; + } +} + +declare module "@mui/material/ButtonGroup" { + export interface ButtonGroupPropsColorOverrides { + neutral: true; + } +} + +declare module "@mui/material/CircularProgress" { + export interface CircularProgressPropsColorOverrides { + neutral: true; + } +} + +export default createTheme({ + palette: { + mode: "dark", + primary: { + main: green[700] + }, + secondary: { + main: "#ca7300", + dark: "#975d2e", + light: "#ffc380" + }, + neutral: { + main: grey[300], + contrastText: "#1c1c1c" + }, + error: { + main: red[500], + dark: "#7e1e1e" + } + }, + components: { + MuiCssBaseline: { + styleOverrides: { + body: { + innerHeight: "100vh", + overflowY: "hidden" + }, + "*::-webkit-scrollbar": { + width: "1em", + cursor: "pointer" + }, + "*::-webkit-scrollbar-track": { + background: grey[800], + borderRadius: "5px" + }, + "*::-webkit-scrollbar-thumb": { + background: grey[600], + border: `2px solid ${grey[800]}`, + borderRadius: "5px", + "&:hover": { + background: grey[600] + } + } + } + }, + MuiTab: { + styleOverrides: { + root: { + minHeight: 0, + padding: 10 + } + } + }, + MuiTabs: { + styleOverrides: { + root: { + minHeight: 0, + padding: 0 + } + } + }, + MuiTableCell: { + styleOverrides: { + head: { + paddingTop: 10, + paddingBottom: 10 + } + } + }, + MuiTooltip: { + styleOverrides: { + tooltip: { + fontSize: "1em" + } + } + }, + MuiButton: { + defaultProps: { + variant: "outlined", + color: "neutral" + } + }, + MuiButtonGroup: { + defaultProps: { + variant: "outlined", + color: "neutral" + } + } + } +}); diff --git a/owmods_gui/frontend/src/types.d.ts b/owmods_gui/frontend/src/types.d.ts index 0441399f0..b13ad119a 100644 --- a/owmods_gui/frontend/src/types.d.ts +++ b/owmods_gui/frontend/src/types.d.ts @@ -2,13 +2,18 @@ Generated by typeshare 1.2.0 */ -export type ProgressBars = Record; - /** Represents an alert gotten from the database. */ export interface Alert { + /** Whether the alert should be shown */ enabled: boolean; + /** The severity for the alert, should be `info`, `warning`, or `error` */ severity?: string; + /** The message for the alert */ message?: string; + /** Displays a link or button in the cli and gui respectively. **Note this is limited to GitHub, Discord, and the Mods Website** */ + url?: string; + /** Optional label to display for the link instead of "More Info" */ + urlLabel?: string; } /** Represents the core config, contains critical info needed by the core API */ @@ -111,14 +116,14 @@ export interface GameMessage { } export interface GuiConfig { - theme: Theme; - rainbow: boolean; language: Language; watchFs: boolean; noWarning: boolean; logMultiWindow: boolean; autoEnableDeps: boolean; noLogServer: boolean; + theme?: Theme; + rainbow?: boolean; } export interface LogPayload { @@ -134,8 +139,13 @@ export interface ProgressBar { progressType: ProgressType; progressAction: ProgressAction; len: ProgressValue; - total: ProgressValue; success?: boolean; + position: number; +} + +export interface ProgressBars { + bars: Record; + counter: number; } /** @@ -190,18 +200,6 @@ export type ModValidationError = /** The mod is outdated, contains the newest version */ | { errorType: "Outdated"; payload: string }; -export enum Theme { - White = "White", - Blue = "Blue", - Green = "Green", - Pink = "Pink", - Purple = "Purple", - Yellow = "Yellow", - Orange = "Orange", - Blurple = "Blurple", - GhostlyGreen = "GhostlyGreen" -} - export enum Language { English = "English", Wario = "Wario" diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index e8289f14f..20f3c851f 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xtask" -version = "0.6.1" +version = "0.7.0" edition = "2021" [dependencies] diff --git a/xtask/src/cli_templates/PKGBUILD b/xtask/src/cli_templates/PKGBUILD index 3cfb8c0e5..9a7964f4c 100644 --- a/xtask/src/cli_templates/PKGBUILD +++ b/xtask/src/cli_templates/PKGBUILD @@ -4,7 +4,7 @@ pkgver=~~VERSION~~ pkgrel=1 pkgdesc="A CLI To Manage Outer Wilds Mods" arch=("x86_64") -url="https://github.com/Bwc9876/ow-mod-man/owmods_cli" +url="https://github.com/Bwc9876/ow-mod-man/tree/main/owmods_cli" license=('GPL3') depends=('glibc' 'gcc-libs' 'mono') source=("$pkgname-$pkgver.tar.gz::https://github.com/Bwc9876/ow-mod-man/releases/download/cli_v$pkgver/owmods.tar.zst") diff --git a/xtask/src/gui_templates/PKGBUILD b/xtask/src/gui_templates/PKGBUILD index 98a4edd45..ca8973d4f 100644 --- a/xtask/src/gui_templates/PKGBUILD +++ b/xtask/src/gui_templates/PKGBUILD @@ -5,7 +5,7 @@ pkgver=~~VERSION~~ pkgrel=1 pkgdesc="A GUI To Manage Outer Wilds Mods" arch=('x86_64') -url="https://github.com/Bwc9876/ow-mod-man/owmods_gui" +url="https://github.com/Bwc9876/ow-mod-man/tree/main/owmods_gui" license=('GPL3') depends=('glibc' 'gtk3' 'openssl-1.1' 'hicolor-icon-theme' 'glib2' 'gcc-libs' 'gdk-pixbuf2' 'webkit2gtk' 'cairo' 'mono') source=("${_appname}-${pkgver}-${arch}.deb::https://github.com/Bwc9876/ow-mod-man/releases/download/gui_v${pkgver}/${_appname}_${pkgver}_amd64.deb")