diff options
55 files changed, 3117 insertions, 1187 deletions
diff --git a/.github/workflows/push-test.yml b/.github/workflows/push-test.yml index cb7491e9..4cac9da3 100644 --- a/.github/workflows/push-test.yml +++ b/.github/workflows/push-test.yml @@ -27,6 +27,34 @@ jobs: - name: Format check run: cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check + clippy: + strategy: + fail-fast: false + matrix: + platform: [ubuntu-22.04, windows-latest] + + name: clippy-check + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: clippy + - name: install dependencies (ubuntu only) + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf + - name: Create dist folder (Ubuntu only) + if: matrix.platform == 'ubuntu-22.04' + run: mkdir --parent src-vue/dist + - name: Create dist folder (Windows only) + if: matrix.platform == 'windows-latest' + run: New-Item -ItemType Directory -Force -Path "src-vue\dist" + - name: Run clippy + run: cargo clippy --manifest-path src-tauri/Cargo.toml -- --no-deps --deny warnings + # Ensure committed bindings correct autogen-ts-bindings-check: runs-on: ubuntu-latest @@ -95,23 +123,39 @@ jobs: env: TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} run: | - npm install - cd src-vue && npm install && cd .. + npm clean-install + cd src-vue && npm clean-install && cd .. npm run tauri build - uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} - name: Upload Linux artifact + if: matrix.platform == 'ubuntu-22.04' uses: actions/upload-artifact@v3 with: name: linux-artifacts path: | src-tauri/target/release/bundle/appimage/* + - name: Upload Linux AppImage + if: matrix.platform == 'ubuntu-22.04' + uses: actions/upload-artifact@v3 + with: + name: linux-appimage + path: | + src-tauri/target/release/bundle/appimage/*.AppImage - name: Upload Windows artifact + if: matrix.platform == 'windows-latest' uses: actions/upload-artifact@v3 with: name: windows-artifacts path: | src-tauri/target/release/bundle/msi/* src-tauri/target/release/app.pdb + - name: Additionally upload Windows installer separately + if: matrix.platform == 'windows-latest' + uses: actions/upload-artifact@v3 + with: + name: windows-msi + path: | + src-tauri/target/release/bundle/msi/*.msi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 23c504aa..8b2235e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,11 @@ name: "release" on: - release: - types: [ created ] + push: + tags: + - 'v*' + +permissions: + contents: write # Needed to write to GitHub draft release jobs: # Ensure version numbers in various places match up @@ -14,7 +18,7 @@ jobs: pip install toml - name: Run check run: | - python3 scripts/check_version_numbers.py --release ${{github.event.release.tag_name}} + python3 scripts/check_version_numbers.py --release ${{github.ref_name}} build: needs: ensure-same-version @@ -47,8 +51,8 @@ jobs: env: TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} run: | - npm install - cd src-vue && npm install && cd .. + npm clean-install + cd src-vue && npm clean-install && cd .. npm run tauri build - uses: tauri-apps/tauri-action@v0 env: @@ -76,6 +80,7 @@ jobs: - name: Release uses: softprops/action-gh-release@v1 with: + draft: true files: | src-tauri/target/release/bundle/appimage/*AppImage* src-tauri/target/release/bundle/msi/*msi* @@ -88,10 +93,11 @@ jobs: - uses: actions/download-artifact@v3 - name: Create release file run: | - python3 scripts/create-release-file.py --version ${{github.event.release.tag_name}} + python3 scripts/create-release-file.py --version ${{github.ref_name}} - name: upload release file uses: softprops/action-gh-release@v1 with: + draft: true files: | latest-release.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b28dbe36 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 R2NorthstarTools + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. @@ -33,6 +33,8 @@ Answers to frequently asked questions can be found in [docs/FAQ.md](docs/FAQ.md) If you'd like to contribute to FlightCore, see [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) +If you are a Northstar developer/contributor, you might want to look at [docs/DEV-TOOLS.md](docs/DEV-TOOLS.md) + ## Roadmap --> See https://github.com/R2NorthstarTools/FlightCore/issues/1 diff --git a/docs/DEV-TOOLS.md b/docs/DEV-TOOLS.md new file mode 100644 index 00000000..cf880d9d --- /dev/null +++ b/docs/DEV-TOOLS.md @@ -0,0 +1,28 @@ +# Dev tools + +![dev view screenshot](assets/dev-view-screenshot.png) + +FlightCore features a hidden view that contains development features. + +It's targetted at both Northstar and FlightCore contributors. Among other things it contains buttons for unreleased features in FlightCore and tools to help with Northstar development. + +To activate it, spam click the FlightCore version number in the settings view at least 6 times. After that a new entry named _DEV_ should appear in the menubar. + +## Northstar + +### Pull request install + +The dev view offers a way to install pull request from the [NorthstarLauncher](https://github.com/R2Northstar/NorthstarLauncher) and [NorthstarMods](https://github.com/R2Northstar/NorthstarMods) repositories. + +Launcher pull requests overwrite `NorthstarLauncher.exe` and `Northstar.dll`. + +Mod pull requests install into a separate profile called `R2Northstar-PR-test-managed-folder`. \ +When installing a mods PR, FlightCore will place `r2ns-launch-mod-pr-version.bat` into your Titanfall2 directory that can be used to run that PR profile directly. \ +The batch file simply runs `NorthstarLauncher.exe -profile=R2Northstar-PR-test-managed-folder` + + +## FlightCore + +The dev view contains various buttons that call functions that might not be fully implemented or tested yet. + +Additionally it has some buttons for testing like the _Panic button_ which force crashes the application to test automatic log uploading on crash among other things. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 8dbb1cea..ce6ba010 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -14,7 +14,6 @@ As for splitting logic between _frontend_ and _backend_, state and UI related lo Make sure you have the necessary dependencies for Tauri installed as described in this link: https://tauri.app/v1/guides/getting-started/prerequisites - Then, install `npm` dependencies with ```sh @@ -24,7 +23,7 @@ npm install Install UI dependencies too ```sh -cd src-vue && npm install +cd src-vue && npm install && cd .. ``` Then for developing @@ -35,6 +34,30 @@ npx tauri dev Automatic recompiling on save is enabled for both the Rust and the Typescript/Vue code. +If you want to build FlightCore from source, run + +```sh +npx tauri build +``` + +This will build the executable and bundles, such as `AppImage`, `.deb` or `.msi`. + +To build just the executable, edit [tauri.conf.json](https://github.com/R2NorthstarTools/FlightCore/blob/main/src-tauri/tauri.conf.json) in the same folder: + +```json + "bundle": { + "active": true, +``` + +Change `active` from `true` to `false`, and bundles won't be included afterwards. + +To disable the updater (which requires a private key) change `active` value to `false`: + +```json + "updater": { + "active": true, +``` + ## Tauri An introduction to Tauri can be seen in this short YouTube video: https://youtu.be/-X8evddpu7M @@ -145,6 +168,7 @@ const persistentStore = new Store('flight-core-settings.json'); // Save change in persistent store await persistentStore.set('northstar-release-canal', { value: "NorthstarReleasecandidate" }); +await persistentStore.save(); // explicit save to disk // Grab Northstar release canal value from store if exists var persistent_northstar_release_canal = (await persistentStore.get('northstar-release-canal')) as any; @@ -178,6 +202,89 @@ struct User { then simply run `cargo test`. The generated bindings are placed in `src-tauri/bindings/`. Make sure to add and commit them as well! +### Internationalization + +For FlightCore to be used by the largest number, its interface is translated in several languages; users can choose used language through FlightCore settings. + +Localization files are located in `src-vue/src/i18n/lang`. + +To add a new language, you have to create associated file, *e.g. `src-vue/src/i18n/lang/de.json`*, and import it in the i18n application object in `main.ts`: +```javascript +import de from "./i18n/lang/de.json"; + +export const i18n = createI18n({ + locale: 'en', + fallbackLocale: 'en', + messages: { + en, fr, de + } +}); +``` + +In order to be able to select it, make sure to that it to the `LanguageSelector` componenent in `src-vue/src/components/LanguageSelector.vue`. + +```vue +export default defineComponent({ + name: 'LanguageSelector', + data: () => ({ + value: '', + options: [ + { + value: 'en', + label: 'English' + }, + <!-- ... --> + { + value: 'de', + label: 'Deutsch' + }, + ] + }), + <!-- ... --> +}) +``` + +There are different ways to use translations in views; in HTML template, invoke the `$t` method with translation key: + +```html +<div> + {{ $t('menu.play') }} +</div> +``` + +For use in Typescript code (inside components), invoke the `this.$t` method: + +```javascript +return this.$t("play.button.select_game_dir"); +``` + +For Typescript code outside components, translations are still accessible: + +```javascript +import { i18n } from '../main'; +i18n.global.tc('notification.game_folder.new.text'); +``` + +--- + +It is possible to inject variables into translations: + +```json +"channels": { + "release": { + "component": { + "text": "Switched release channel to {canal}." + } + } +} +``` + +```javascript +return this.$t("channels.release.component.text", {canal: "MyCanalName"}); +``` + + + ## Other This repo uses [EditorConfig](https://editorconfig.org/) to define some basic formatting rules. Find a plugin for your IDE [here](https://editorconfig.org/#download). diff --git a/docs/FAQ.md b/docs/FAQ.md index eb381d68..8dd2dfa4 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -16,6 +16,10 @@ You use it to do stuff like - [or this](https://www.youtube.com/watch?v=suhBGqzDbNA) - [or this](https://www.youtube.com/watch?v=vyUxAwobY60) +## I have an issue with FlightCore, where do I go? + +Check [TROUBLESHOOTING.md](TROUBLESHOOTING.md). + ## Why yet another Northstar intaller/updater/mod-manager instead of contributing to an existing one? The 3 main GUI tools for handling such tasks with Norhtstar are diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 00000000..aed174e0 --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,14 @@ +# Troubleshooting + +Got an issue with FlightCore? Hopefully one of the steps below will help you resolve it. If not open an [issue on GitHub](https://github.com/R2NorthstarTools/FlightCore/issues/new) or ping `Gecko#7945` on the Northstar Discord. + +## FlightCore won't launch + +If you are on Windows on FlightCore won't start, make sure you have WebView2 installed. You can grab the latest version from the Microsoft website: \ +https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section + +![webview2 download screenshot](assets/webview2-download-screenshot.png) + +(make sure to select _Evergreen Bootstrapper_ -> _Download_). + + diff --git a/docs/assets/dev-view-screenshot.png b/docs/assets/dev-view-screenshot.png Binary files differnew file mode 100644 index 00000000..5cf28043 --- /dev/null +++ b/docs/assets/dev-view-screenshot.png diff --git a/docs/assets/webview2-download-screenshot.png b/docs/assets/webview2-download-screenshot.png Binary files differnew file mode 100644 index 00000000..2f0bfdc8 --- /dev/null +++ b/docs/assets/webview2-download-screenshot.png diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 70e50a8e..6c64bf7b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -87,7 +87,7 @@ checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "app" -version = "1.10.2" +version = "1.13.2" dependencies = [ "anyhow", "async-recursion", @@ -96,9 +96,13 @@ dependencies = [ "game-scanner", "json5", "libthermite", + "log", + "open", + "pretty_env_logger", "regex", "reqwest", "sentry", + "sentry-log", "serde", "serde_json", "steamlocate", @@ -164,6 +168,17 @@ dependencies = [ ] [[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -413,6 +428,15 @@ dependencies = [ ] [[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] name = "cocoa" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -786,16 +810,16 @@ version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", ] [[package]] name = "dirs" -version = "3.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.0", ] [[package]] @@ -820,6 +844,17 @@ dependencies = [ ] [[package]] +name = "dirs-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" +dependencies = [ + "libc", + "redox_users", + "windows-sys 0.45.0", +] + +[[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -879,6 +914,19 @@ dependencies = [ ] [[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1092,9 +1140,8 @@ dependencies = [ [[package]] name = "game-scanner" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff11681d503562e5ff8fbd362ec8dd18d72f1470119e82708a55c3158eeaa0c" +version = "1.2.0" +source = "git+https://github.com/EqualGames/game-scanner.git?rev=94dcbd086a5987361a2847988a060da9b5dd3908#94dcbd086a5987361a2847988a060da9b5dd3908" dependencies = [ "bytes", "case", @@ -1107,9 +1154,9 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "sysinfo 0.23.13", + "sysinfo 0.28.4", "url", - "winreg", + "winreg 0.10.1", ] [[package]] @@ -1416,26 +1463,20 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] name = "hashlink" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" dependencies = [ - "hashbrown 0.11.2", + "hashbrown", ] [[package]] @@ -1455,6 +1496,15 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" @@ -1543,6 +1593,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] name = "hyper" version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1667,7 +1726,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -1820,6 +1879,20 @@ dependencies = [ ] [[package]] +name = "keyvalues-serde" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da419ac133bb3ddf0dbf9c12fcc0ce01d994fcb65f6f1713faf15cc689320b5f" +dependencies = [ + "keyvalues-parser", + "once_cell", + "paste", + "regex", + "serde", + "thiserror", +] + +[[package]] name = "kuchiki" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1854,9 +1927,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.24.2" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" dependencies = [ "cc", "pkg-config", @@ -2146,15 +2219,6 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - -[[package]] -name = "ntapi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" @@ -2208,7 +2272,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -2673,6 +2737,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + +[[package]] name = "proc-macro-crate" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2723,9 +2797,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" dependencies = [ "bytes", "prost-derive", @@ -2733,12 +2807,14 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" dependencies = [ "bytes", - "heck 0.3.3", + "cfg-if", + "cmake", + "heck 0.4.1", "itertools", "lazy_static", "log", @@ -2753,9 +2829,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" dependencies = [ "anyhow", "itertools", @@ -2766,15 +2842,21 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" dependencies = [ "bytes", "prost", ] [[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] name = "quick-xml" version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2993,7 +3075,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.10.1", ] [[package]] @@ -3037,16 +3119,15 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" dependencies = [ "bitflags", "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", - "memchr", "smallvec", ] @@ -3303,6 +3384,16 @@ dependencies = [ ] [[package]] +name = "sentry-log" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa3a3f4477e77541c26eb84d0e355729dfa35c74c682eb8678f146db5126013" +dependencies = [ + "log", + "sentry-core", +] + +[[package]] name = "sentry-panic" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3581,14 +3672,16 @@ dependencies = [ [[package]] name = "steamlocate" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf364a9446f9f4423694ad8db5be8c478088970138e557ff2ddb3167fb0a2944" +checksum = "6b3962619891cd4c87dc0ae9332b97285eb59ab7cf65c9bec3fba62188390e74" dependencies = [ "dirs", "keyvalues-parser", + "keyvalues-serde", + "serde", "steamy-vdf", - "winreg", + "winreg 0.11.0", ] [[package]] @@ -3672,14 +3765,14 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.23.13" +version = "0.26.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3977ec2e0520829be45c8a2df70db2bf364714d8a748316a10c3c35d4d2b01c9" +checksum = "5c18a6156d1f27a9592ee18c1a846ca8dd5c258b7179fc193ae87c74ebb666f5" dependencies = [ "cfg-if", "core-foundation-sys", "libc", - "ntapi 0.3.7", + "ntapi", "once_cell", "rayon", "winapi", @@ -3687,14 +3780,14 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.26.9" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c18a6156d1f27a9592ee18c1a846ca8dd5c258b7179fc193ae87c74ebb666f5" +checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b" dependencies = [ "cfg-if", "core-foundation-sys", "libc", - "ntapi 0.4.0", + "ntapi", "once_cell", "rayon", "winapi", @@ -3898,8 +3991,8 @@ dependencies = [ [[package]] name = "tauri-plugin-store" -version = "0.0.0" -source = "git+https://github.com/tauri-apps/tauri-plugin-store?rev=9bd993aa67766596638bbfd91e79a1bf8f632014#9bd993aa67766596638bbfd91e79a1bf8f632014" +version = "0.1.0" +source = "git+https://github.com/tauri-apps/plugins-workspace?rev=5a6abd3203dc94c38f96d0c4bf7ecbef399f8c25#5a6abd3203dc94c38f96d0c4bf7ecbef399f8c25" dependencies = [ "log", "serde", @@ -4932,6 +5025,16 @@ dependencies = [ ] [[package]] +name = "winreg" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] name = "winres" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0e972064..76315fae 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "app" -version = "1.10.2" +version = "1.13.2" description = "A Tauri App" authors = ["you"] license = "" @@ -25,9 +25,11 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.2", features = ["api-all", "updater"] } tokio = { version = "1", features = ["full"] } +# Sentry (crash) logging sentry = "0.30" +sentry-log = "0.30.0" # Find steam games -steamlocate = "1.0.2" +steamlocate = "1.2" # Error messages anyhow = "1.0" # libthermite for Northstar/mod install handling @@ -41,21 +43,27 @@ sysinfo = "0.26.2" # HTTP requests reqwest = { version = "0.11", features = ["blocking"] } # Persistent store for settings -tauri-plugin-store = { git = "https://github.com/tauri-apps/tauri-plugin-store", rev = "9bd993aa67766596638bbfd91e79a1bf8f632014" } +tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", rev = "5a6abd3203dc94c38f96d0c4bf7ecbef399f8c25" } # JSON5 parsing support (allows comments in JSON) json5 = "0.4.1" # Async recursion for recursive mod install async-recursion = "1.0.0" # Game scanner -game-scanner = "1.1.4" +# Use specific commit that updates deprecated library +game-scanner = { git = "https://github.com/EqualGames/game-scanner.git", rev = "94dcbd086a5987361a2847988a060da9b5dd3908" } # For parsing timestamps chrono = "0.4.23" # TypeScript bindings ts-rs = "6.1" # const formatting const_format = "0.2.30" +# Logging libraries +pretty_env_logger = "0.4.0" +log = "0.4.17" # Extracting zip files easily zip-extract = "0.1.2" +# open urls +open = "3.2.0" [features] # by default Tauri runs in production mode diff --git a/src-tauri/bindings/InstallProgress.ts b/src-tauri/bindings/InstallProgress.ts new file mode 100644 index 00000000..7bea9bb8 --- /dev/null +++ b/src-tauri/bindings/InstallProgress.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { InstallState } from "./InstallState"; + +export interface InstallProgress { current_downloaded: bigint, total_size: bigint, state: InstallState, }
\ No newline at end of file diff --git a/src-tauri/bindings/InstallState.ts b/src-tauri/bindings/InstallState.ts new file mode 100644 index 00000000..21dbc0c7 --- /dev/null +++ b/src-tauri/bindings/InstallState.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type InstallState = "DOWNLOADING" | "EXTRACTING" | "DONE";
\ No newline at end of file diff --git a/src-tauri/bindings/PullRequestType.ts b/src-tauri/bindings/PullRequestType.ts index 2d1fd0a8..582f831a 100644 --- a/src-tauri/bindings/PullRequestType.ts +++ b/src-tauri/bindings/PullRequestType.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type PullRequestType = "MODS" | "LAUNCHER";
\ No newline at end of file +export type PullRequestType = "Mods" | "Launcher";
\ No newline at end of file diff --git a/src-tauri/bindings/Tag.ts b/src-tauri/bindings/Tag.ts new file mode 100644 index 00000000..adbbff33 --- /dev/null +++ b/src-tauri/bindings/Tag.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface Tag { name: string, }
\ No newline at end of file diff --git a/src-tauri/bindings/TagWrapper.ts b/src-tauri/bindings/TagWrapper.ts new file mode 100644 index 00000000..f9f56a51 --- /dev/null +++ b/src-tauri/bindings/TagWrapper.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Tag } from "./Tag"; + +export interface TagWrapper { label: string, value: Tag, }
\ No newline at end of file diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs index 57a10e02..8c88df1e 100644 --- a/src-tauri/src/constants.rs +++ b/src-tauri/src/constants.rs @@ -1,6 +1,6 @@ // This file stores various global constants values - use const_format::concatcp; +use std::time::Duration; // FlightCore user agent for web requests pub const APP_USER_AGENT: &str = concatcp!("FlightCore/", env!("CARGO_PKG_VERSION")); @@ -29,8 +29,22 @@ pub const BLACKLISTED_MODS: [&str; 3] = [ // Titanfall2 game IDs on Origin/EA-App pub const TITANFALL2_ORIGIN_IDS: [&str; 2] = ["Origin.OFR.50.0001452", "Origin.OFR.50.0001456"]; +// Titanfall2 Steam App ID +pub const TITANFALL2_STEAM_ID: &str = "1237970"; + +// Order in which the sections for release notes should be displayed +pub const SECTION_ORDER: [&str; 9] = [ + "feat", "fix", "docs", "style", "refactor", "build", "test", "chore", "other", +]; + // GitHub API endpoints for launcher/mods PRs pub const PULLS_API_ENDPOINT_LAUNCHER: &str = "https://api.github.com/repos/R2Northstar/NorthstarLauncher/pulls"; pub const PULLS_API_ENDPOINT_MODS: &str = "https://api.github.com/repos/R2Northstar/NorthstarMods/pulls"; + +// Statistics (players and servers counts) refresh delay +pub const REFRESH_DELAY: Duration = Duration::from_secs(5 * 60); + +// Flightcore repo name and org name on GitHub +pub const FLIGHTCORE_REPO_NAME: &str = "R2NorthstarTools/FlightCore"; diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 942f0db2..b336ab5c 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -1,2 +1,183 @@ pub mod pull_requests; pub mod release_notes; + +use app::constants::{APP_USER_AGENT, FLIGHTCORE_REPO_NAME, SECTION_ORDER}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use ts_rs::TS; + +#[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +pub struct Tag { + name: String, +} + +/// Wrapper type needed for frontend +#[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +pub struct TagWrapper { + label: String, + value: Tag, +} + +#[derive(Debug, Deserialize)] +struct CommitInfo { + sha: String, + commit: Commit, +} + +#[derive(Debug, Deserialize)] +struct Commit { + message: String, +} + +#[derive(Debug, Deserialize)] +struct Comparison { + commits: Vec<CommitInfo>, +} + +/// Get a list of tags on the FlightCore repo +#[tauri::command] +pub fn get_list_of_tags() -> Result<Vec<TagWrapper>, String> { + // Set the repository name. + + // Create a `reqwest` client with a user agent. + let client = reqwest::blocking::Client::builder() + .user_agent(APP_USER_AGENT) + .build() + .unwrap(); + + // Fetch the list of tags for the repository as a `Vec<Tag>`. + let tags_url = format!("https://api.github.com/repos/{}/tags", FLIGHTCORE_REPO_NAME); + let tags: Vec<Tag> = client.get(tags_url).send().unwrap().json().unwrap(); + + // Map each `Tag` element to a `TagWrapper` element with the desired label and `Tag` value. + let tag_wrappers: Vec<TagWrapper> = tags + .into_iter() + .map(|tag| TagWrapper { + label: tag.name.clone(), + value: tag, + }) + .collect(); + + Ok(tag_wrappers) +} + +/// Use GitHub API to compare two tags of the same repo against each other and get the resulting changes +#[tauri::command] +pub fn compare_tags(first_tag: Tag, second_tag: Tag) -> Result<String, String> { + // Fetch the list of commits between the two tags. + + // Create a `reqwest` client with a user agent. + let client = reqwest::blocking::Client::builder() + .user_agent(APP_USER_AGENT) + .build() + .unwrap(); + + let repo = "R2NorthstarTools/FlightCore"; + + let mut full_patch_notes = "".to_string(); + + let mut patch_notes: Vec<String> = [].to_vec(); + println!("{}", repo); + // let repo = "R2Northstar/NorthstarLauncher"; + let comparison_url = format!( + "https://api.github.com/repos/{}/compare/{}...{}", + repo, first_tag.name, second_tag.name + ); + + let comparison: Comparison = client.get(comparison_url).send().unwrap().json().unwrap(); + let commits = comparison.commits; + + // Display the list of commits. + println!( + "Commits between {} and {}:", + first_tag.name, second_tag.name + ); + + // Iterate over all commits in the diff + for commit in commits { + println!( + " * {} : {}", + commit.sha, + commit.commit.message.split('\n').next().unwrap() + ); + patch_notes.push( + commit + .commit + .message + .split('\n') + .next() + .unwrap() + .to_string(), + ); + } + + full_patch_notes += &generate_flightcore_release_notes(patch_notes); + + Ok(full_patch_notes.to_string()) +} + +/// Generate release notes in the format used for FlightCore +fn generate_flightcore_release_notes(commits: Vec<String>) -> String { + let grouped_commits = group_commits_by_type(commits); + let mut release_notes = String::new(); + + // Go over commit types and generate notes + for commit_type in SECTION_ORDER { + if let Some(commit_list) = grouped_commits.get(commit_type) { + if !commit_list.is_empty() { + let section_title = match commit_type { + "feat" => "**Features:**", + "fix" => "**Bug Fixes:**", + "docs" => "**Documentation:**", + "style" => "**Styles:**", + "refactor" => "**Code Refactoring:**", + "build" => "**Build:**", + "test" => "**Tests:**", + "chore" => "**Chores:**", + _ => "**Other:**", + }; + + release_notes.push_str(&format!("{}\n", section_title)); + + for commit_message in commit_list { + release_notes.push_str(&format!("- {}\n", commit_message)); + } + + release_notes.push('\n'); + } + } + } + + release_notes +} + +/// Group semantic commit messages by type +/// Commmit messages that are not formatted accordingly are marked as "other" +fn group_commits_by_type(commits: Vec<String>) -> HashMap<String, Vec<String>> { + let mut grouped_commits: HashMap<String, Vec<String>> = HashMap::new(); + let mut other_commits: Vec<String> = vec![]; + + for commit in commits { + let commit_parts: Vec<&str> = commit.splitn(2, ':').collect(); + if commit_parts.len() == 2 { + let commit_type = commit_parts[0].to_lowercase(); + let commit_description = commit_parts[1].trim().to_string(); + + // Check if known commit type + if SECTION_ORDER.contains(&commit_type.as_str()) { + let commit_list = grouped_commits.entry(commit_type.to_string()).or_default(); + commit_list.push(commit_description); + } else { + // otherwise add to list of "other" + other_commits.push(commit.to_string()); + } + } else { + other_commits.push(commit.to_string()); + } + } + grouped_commits.insert("other".to_string(), other_commits); + + grouped_commits +} diff --git a/src-tauri/src/github/pull_requests.rs b/src-tauri/src/github/pull_requests.rs index 586a4fb3..96ac623f 100644 --- a/src-tauri/src/github/pull_requests.rs +++ b/src-tauri/src/github/pull_requests.rs @@ -60,15 +60,15 @@ struct ArtifactsResponse { #[derive(Serialize, Deserialize, Debug, Clone, TS)] #[ts(export)] pub enum PullRequestType { - MODS, - LAUNCHER, + Mods, + Launcher, } /// Parse pull requests from specified URL pub async fn get_pull_requests(url: String) -> Result<Vec<PullsApiResponseElement>, String> { let json_response = match fetch_github_releases_api(&url).await { Ok(result) => result, - Err(err) => return Err(err.to_string()), + Err(err) => return Err(err), }; let pulls_response: Vec<PullsApiResponseElement> = match serde_json::from_str(&json_response) { @@ -85,8 +85,8 @@ pub async fn get_pull_requests_wrapper( install_type: PullRequestType, ) -> Result<Vec<PullsApiResponseElement>, String> { let api_pr_url = match install_type { - PullRequestType::MODS => PULLS_API_ENDPOINT_MODS, - PullRequestType::LAUNCHER => PULLS_API_ENDPOINT_LAUNCHER, + PullRequestType::Mods => PULLS_API_ENDPOINT_MODS, + PullRequestType::Launcher => PULLS_API_ENDPOINT_LAUNCHER, }; get_pull_requests(api_pr_url.to_string()).await @@ -142,7 +142,8 @@ fn get_mods_download_link(pull_request: PullsApiResponseElement) -> Result<Strin } /// Gets `nightly.link` artifact download link of a launcher PR -async fn get_launcher_download_link( +#[tauri::command] +pub async fn get_launcher_download_link( pull_request: PullsApiResponseElement, ) -> Result<String, String> { // Iterate over the first 10 pages of @@ -207,7 +208,7 @@ fn add_batch_file(game_install_path: &str) { match file.write_all(batch_file_content.as_bytes()) { Err(why) => panic!("couldn't write to {}: {}", display, why), - Ok(_) => println!("successfully wrote to {}", display), + Ok(_) => log::info!("successfully wrote to {}", display), } } @@ -258,7 +259,7 @@ pub async fn apply_launcher_pr( for file_name in files_to_copy { let source_file_path = format!("{}/{}", extract_directory, file_name); let destination_file_path = format!("{}/{}", game_install_path, file_name); - match std::fs::copy(&source_file_path, &destination_file_path) { + match std::fs::copy(source_file_path, destination_file_path) { Ok(_result) => (), Err(err) => { return Err(format!( @@ -280,7 +281,7 @@ pub async fn apply_launcher_pr( } } - println!("All done with installing launcher PR"); + log::info!("All done with installing launcher PR"); Ok(()) } @@ -308,9 +309,9 @@ pub async fn apply_mods_pr( // Delete previously managed folder if std::fs::remove_dir_all(profile_folder.clone()).is_err() { if std::path::Path::new(&profile_folder).exists() { - println!("Failed removing previous dir"); + log::error!("Failed removing previous dir"); } else { - println!("Failed removing folder that doesn't exist. Probably cause first run"); + log::warn!("Failed removing folder that doesn't exist. Probably cause first run"); } }; @@ -330,6 +331,6 @@ pub async fn apply_mods_pr( // Add batch file to launch right profile add_batch_file(game_install_path); - println!("All done with installing mods PR"); + log::info!("All done with installing mods PR"); Ok(()) } diff --git a/src-tauri/src/github/release_notes.rs b/src-tauri/src/github/release_notes.rs index b59358ec..b45442e0 100644 --- a/src-tauri/src/github/release_notes.rs +++ b/src-tauri/src/github/release_notes.rs @@ -20,7 +20,7 @@ pub struct FlightCoreVersion { // Fetches repo release API and returns response as string pub async fn fetch_github_releases_api(url: &str) -> Result<String, String> { - println!("Fetching releases notes from GitHub API"); + log::info!("Fetching releases notes from GitHub API"); let client = reqwest::Client::new(); let res = client @@ -40,13 +40,13 @@ pub async fn fetch_github_releases_api(url: &str) -> Result<String, String> { #[tauri::command] pub async fn get_newest_flightcore_version() -> Result<FlightCoreVersion, String> { // Get newest version number from GitHub API - println!("Checking GitHub API"); + log::info!("Checking GitHub API"); let url = "https://api.github.com/repos/R2NorthstarTools/FlightCore/releases/latest"; let res = fetch_github_releases_api(url).await?; let flightcore_version: FlightCoreVersion = serde_json::from_str(&res).expect("JSON was not well-formatted"); - println!("Done checking GitHub API"); + log::info!("Done checking GitHub API"); Ok(flightcore_version) } @@ -98,7 +98,7 @@ pub async fn get_northstar_release_notes() -> Result<Vec<ReleaseInfo>, String> { let release_info_vector: Vec<ReleaseInfo> = serde_json::from_str(&res).expect("JSON was not well-formatted"); - println!("Done checking GitHub API"); + log::info!("Done checking GitHub API"); - return Ok(release_info_vector); + Ok(release_info_vector) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 12c839fe..8ec7e4b1 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,7 +1,6 @@ -use std::env; +use std::{cell::RefCell, env, fs, path::Path, time::Duration, time::Instant}; use anyhow::{anyhow, Context, Result}; -use sentry::{add_breadcrumb, Breadcrumb, Level}; mod northstar; @@ -15,11 +14,14 @@ use platform_specific::linux; use serde::{Deserialize, Serialize}; use sysinfo::SystemExt; +use tokio::time::sleep; use ts_rs::TS; use zip::ZipArchive; use northstar::get_northstar_version_number; +use crate::constants::TITANFALL2_STEAM_ID; + #[derive(Serialize, Deserialize, Debug, Clone)] pub enum InstallType { STEAM, @@ -50,10 +52,26 @@ pub struct NorthstarServer { pub player_count: i32, } +#[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +pub enum InstallState { + DOWNLOADING, + EXTRACTING, + DONE, +} + +#[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +struct InstallProgress { + current_downloaded: u64, + total_size: u64, + state: InstallState, +} + /// Check version number of a mod -pub fn check_mod_version_number(path_to_mod_folder: String) -> Result<String, anyhow::Error> { +pub fn check_mod_version_number(path_to_mod_folder: &str) -> Result<String, anyhow::Error> { // println!("{}", format!("{}/mod.json", path_to_mod_folder)); - let data = std::fs::read_to_string(format!("{}/mod.json", path_to_mod_folder))?; + let data = std::fs::read_to_string(format!("{path_to_mod_folder}/mod.json"))?; let parsed_json: serde_json::Value = serde_json::from_str(&data)?; // println!("{}", parsed_json); let mod_version_number = match parsed_json.get("Version").and_then(|value| value.as_str()) { @@ -61,7 +79,7 @@ pub fn check_mod_version_number(path_to_mod_folder: String) -> Result<String, an None => return Err(anyhow!("No version number found")), }; - println!("{}", mod_version_number); + log::info!("{}", mod_version_number); Ok(mod_version_number.to_string()) } @@ -81,8 +99,7 @@ pub fn linux_checks_librs() -> Result<(), String> { return Err(format!( "GLIBC is not version {} or greater", min_required_ldd_version - ) - .to_string()); + )); }; // All checks passed @@ -94,7 +111,7 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { // Attempt parsing Steam library directly match steamlocate::SteamDir::locate() { Some(mut steamdir) => { - let titanfall2_steamid = 1237970; + let titanfall2_steamid = TITANFALL2_STEAM_ID.parse().unwrap(); match steamdir.app(&titanfall2_steamid) { Some(app) => { // println!("{:#?}", app); @@ -104,10 +121,10 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { }; return Ok(game_install); } - None => println!("Couldn't locate Titanfall2"), + None => log::info!("Couldn't locate Titanfall2 Steam instal"), } } - None => println!("Couldn't locate Steam on this computer!"), + None => log::info!("Couldn't locate Steam on this computer!"), } // (On Windows only) try parsing Windows registry for Origin install path @@ -115,13 +132,13 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { match windows::origin_install_location_detection() { Ok(game_path) => { let game_install = GameInstall { - game_path: game_path, + game_path, install_type: InstallType::ORIGIN, }; return Ok(game_install); } Err(err) => { - println!("{}", err); + log::info!("{}", err); } }; @@ -130,13 +147,13 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { /// Checks whether the provided path is a valid Titanfall2 gamepath by checking against a certain set of criteria pub fn check_is_valid_game_path(game_install_path: &str) -> Result<(), String> { - let path_to_titanfall2_exe = format!("{}/Titanfall2.exe", game_install_path); + let path_to_titanfall2_exe = format!("{game_install_path}/Titanfall2.exe"); let is_correct_game_path = std::path::Path::new(&path_to_titanfall2_exe).exists(); - println!("Titanfall2.exe exists in path? {}", is_correct_game_path); + log::info!("Titanfall2.exe exists in path? {}", is_correct_game_path); // Exit early if wrong game path if !is_correct_game_path { - return Err(format!("Incorrect game path \"{}\"", game_install_path)); // Return error cause wrong game path + return Err(format!("Incorrect game path \"{game_install_path}\"")); // Return error cause wrong game path } Ok(()) } @@ -159,12 +176,12 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> { ); if (*f.name()).ends_with('/') { - println!("Create directory {}", f.name()); + log::info!("Create directory {}", f.name()); std::fs::create_dir_all(target.join(f.name())) .context("Unable to create directory")?; continue; } else if let Some(p) = out.parent() { - std::fs::create_dir_all(&p).context("Unable to create directory")?; + std::fs::create_dir_all(p).context("Unable to create directory")?; } let mut outfile = std::fs::OpenOptions::new() @@ -173,7 +190,7 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> { .truncate(true) .open(&out)?; - println!("Write file {}", out.display()); + log::info!("Write file {}", out.display()); std::io::copy(&mut f, &mut outfile).context("Unable to write to file")?; } @@ -186,33 +203,83 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> { ///Install N* from the provided mod /// ///Checks cache, else downloads the latest version -async fn do_install(nmod: &thermite::model::ModVersion, game_path: &std::path::Path) -> Result<()> { +async fn do_install( + window: tauri::Window, + nmod: &thermite::model::ModVersion, + game_path: &std::path::Path, +) -> Result<()> { let filename = format!("northstar-{}.zip", nmod.version); let download_directory = format!("{}/___flightcore-temp-download-dir/", game_path.display()); std::fs::create_dir_all(download_directory.clone())?; - let download_path = format!("{}/{}", download_directory.clone(), filename); - println!("{}", download_path); - - let nfile = match thermite::core::manage::download_file(&nmod.url, download_path) { + let download_path = format!("{}/{}", download_directory, filename); + log::info!("Download path: {download_path}"); + + let last_emit = RefCell::new(Instant::now()); // Keep track of the last time a signal was emitted + let nfile = match thermite::core::manage::download_file_with_progress( + &nmod.url, + download_path, + |delta, current, total| { + if delta != 0 { + // Only emit a signal once every 100ms + // This way we don't bombard the frontend with events on fast download speeds + let time_since_last_emit = Instant::now().duration_since(*last_emit.borrow()); + if time_since_last_emit >= Duration::from_millis(100) { + window + .emit( + "northstar-install-download-progress", + InstallProgress { + current_downloaded: current, + total_size: total, + state: InstallState::DOWNLOADING, + }, + ) + .unwrap(); + *last_emit.borrow_mut() = Instant::now(); + } + } + }, + ) { Ok(res) => res, Err(err) => return Err(anyhow!("Failed downloading Northstar {}", err)), }; - println!("Extracting Northstar..."); + window + .emit( + "northstar-install-download-progress", + InstallProgress { + current_downloaded: 0, + total_size: 0, + state: InstallState::EXTRACTING, + }, + ) + .unwrap(); + + log::info!("Extracting Northstar..."); extract(nfile, game_path)?; // Delete old copy - println!("Delete temp folder again"); + log::info!("Delete temp folder again"); std::fs::remove_dir_all(download_directory).unwrap(); - println!("Done!"); + log::info!("Done installing Northstar!"); + window + .emit( + "northstar-install-download-progress", + InstallProgress { + current_downloaded: 0, + total_size: 0, + state: InstallState::DONE, + }, + ) + .unwrap(); Ok(()) } pub async fn install_northstar( + window: tauri::Window, game_path: &str, northstar_package_name: Option<String>, ) -> Result<String, String> { @@ -234,20 +301,30 @@ pub async fn install_northstar( .ok_or_else(|| panic!("Couldn't find Northstar on thunderstore???")) .unwrap(); - // Breadcrumb for sentry to debug crash - add_breadcrumb(Breadcrumb { - // category: Some("auth".into()), - message: Some(format!("Install path \"{}\"", game_path)), - level: Level::Info, - ..Default::default() - }); + log::info!("Install path \"{}\"", game_path); - do_install( + match do_install( + window, nmod.versions.get(&nmod.latest).unwrap(), std::path::Path::new(game_path), ) .await - .unwrap(); + { + Ok(_) => (), + Err(err) => { + if game_path + .to_lowercase() + .contains(&r#"C:\Program Files\"#.to_lowercase()) + // default is `C:\Program Files\EA Games\Titanfall2` + { + return Err( + "Cannot install to default EA App install path, please move Titanfall2 to a different install location.".to_string(), + ); + } else { + return Err(err.to_string()); + } + } + } Ok(nmod.latest.clone()) } @@ -258,7 +335,7 @@ pub fn get_host_os() -> String { } pub fn launch_northstar( - game_install: GameInstall, + game_install: &GameInstall, bypass_checks: Option<bool>, ) -> Result<String, String> { dbg!(game_install.clone()); @@ -278,15 +355,12 @@ pub fn launch_northstar( )); } - let bypass_checks = match bypass_checks { - Some(bypass_checks) => bypass_checks, - None => false, - }; + let bypass_checks = bypass_checks.unwrap_or(false); // Only check guards if bypassing checks is not enabled if !bypass_checks { // Some safety checks before, should have more in the future - if get_northstar_version_number(game_install.game_path.clone()).is_err() { + if get_northstar_version_number(&game_install.game_path).is_err() { return Err(anyhow!("Not all checks were met").to_string()); } @@ -314,7 +388,7 @@ pub fn launch_northstar( { let ns_exe_path = format!("{}/NorthstarLauncher.exe", game_install.game_path); let _output = std::process::Command::new("C:\\Windows\\System32\\cmd.exe") - .args(&["/C", "start", "", &ns_exe_path]) + .args(["/C", "start", "", &ns_exe_path]) .spawn() .expect("failed to execute process"); return Ok("Launched game".to_string()); @@ -327,31 +401,109 @@ pub fn launch_northstar( )) } -pub fn check_origin_running() -> bool { - let s = sysinfo::System::new_all(); - for _process in s.processes_by_name("Origin.exe") { - // check here if this is your process - // dbg!(process); - // There's at least one Origin process, so we can launch - return true; +/// Prepare Northstar and Launch through Steam using the Browser Protocol +pub fn launch_northstar_steam( + game_install: &GameInstall, + _bypass_checks: Option<bool>, +) -> Result<String, String> { + if !matches!(game_install.install_type, InstallType::STEAM) { + return Err("Titanfall2 was not installed via Steam".to_string()); } - // Alternatively, check for EA Desktop - for _process in s.processes_by_name("EADesktop.exe") { - // There's at least one EADesktop process, so we can launch - return true; + + match steamlocate::SteamDir::locate() { + Some(mut steamdir) => { + if get_host_os() != "windows" { + let titanfall2_steamid: u32 = TITANFALL2_STEAM_ID.parse().unwrap(); + match steamdir.compat_tool(&titanfall2_steamid) { + Some(compat) => { + if !compat + .name + .clone() + .unwrap() + .to_ascii_lowercase() + .contains("northstarproton") + { + return Err( + "Titanfall2 was not configured to use NorthstarProton".to_string() + ); + } + } + None => { + return Err( + "Titanfall2 was not configured to use a compatibility tool".to_string() + ); + } + } + } + } + None => { + return Err("Couldn't access Titanfall2 directory".to_string()); + } } - false + + // Switch to Titanfall2 directory to set everything up + if std::env::set_current_dir(game_install.game_path.clone()).is_err() { + // We failed to get to Titanfall2 directory + return Err("Couldn't access Titanfall2 directory".to_string()); + } + + let run_northstar = "run_northstar.txt"; + let run_northstar_bak = "run_northstar.txt.bak"; + + if Path::new(run_northstar).exists() { + // rename should ovewrite existing files + fs::rename(run_northstar, run_northstar_bak).unwrap(); + } + + // Passing arguments gives users a prompt, so we use run_northstar.txt + fs::write(run_northstar, b"1").unwrap(); + + let retval = match open::that(format!("steam://run/{}/", TITANFALL2_STEAM_ID)) { + Ok(()) => Ok("Started game".to_string()), + Err(_err) => Err("Failed to launch Titanfall 2 via Steam".to_string()), + }; + + let is_err = retval.is_err(); + + // Handle the rest in the backround + tauri::async_runtime::spawn(async move { + // Starting the EA app and Titanfall might take a good minute or three + let mut wait_countdown = 60 * 3; + while wait_countdown > 0 && !check_northstar_running() && !is_err { + sleep(Duration::from_millis(1000)).await; + wait_countdown -= 1; + } + + // Northstar may be running, but it may not have loaded the file yet + sleep(Duration::from_millis(2000)).await; + + // intentionally ignore Result + let _ = fs::remove_file(run_northstar); + + if Path::new(run_northstar_bak).exists() { + fs::rename(run_northstar_bak, run_northstar).unwrap(); + } + }); + + retval +} + +pub fn check_origin_running() -> bool { + let s = sysinfo::System::new_all(); + let x = s.processes_by_name("Origin.exe").next().is_some() + || s.processes_by_name("EADesktop.exe").next().is_some(); + x } /// Checks if Northstar process is running pub fn check_northstar_running() -> bool { let s = sysinfo::System::new_all(); - for _process in s.processes_by_name("NorthstarLauncher.exe") { - // check here if this is your process - // dbg!(process); - return true; - } - false + let x = s + .processes_by_name("NorthstarLauncher.exe") + .next() + .is_some() + || s.processes_by_name("Titanfall2.exe").next().is_some(); + x } /// Helps with converting release candidate numbers which are different on Thunderstore @@ -364,7 +516,7 @@ pub fn convert_release_candidate_number(version_number: String) -> String { } /// Returns a serde json object of the parsed `enabledmods.json` file -pub fn get_enabled_mods(game_install: GameInstall) -> Result<serde_json::value::Value, String> { +pub fn get_enabled_mods(game_install: &GameInstall) -> Result<serde_json::value::Value, String> { let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); // Check for JSON file @@ -381,7 +533,7 @@ pub fn get_enabled_mods(game_install: GameInstall) -> Result<serde_json::value:: // Parse JSON let res: serde_json::Value = match serde_json::from_str(&data) { Ok(result) => result, - Err(err) => return Err(format!("Failed to read JSON due to: {}", err.to_string())), + Err(err) => return Err(format!("Failed to read JSON due to: {}", err)), }; // Return parsed data diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 50e439c8..3aa17459 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -10,15 +10,18 @@ use std::{ }; use app::{ - constants::{APP_USER_AGENT, MASTER_SERVER_URL, SERVER_BROWSER_ENDPOINT}, + constants::{APP_USER_AGENT, MASTER_SERVER_URL, REFRESH_DELAY, SERVER_BROWSER_ENDPOINT}, *, }; mod github; -use github::pull_requests::{apply_launcher_pr, apply_mods_pr, get_pull_requests_wrapper}; +use github::pull_requests::{ + apply_launcher_pr, apply_mods_pr, get_launcher_download_link, get_pull_requests_wrapper, +}; use github::release_notes::{ check_is_flightcore_outdated, get_newest_flightcore_version, get_northstar_release_notes, }; +use github::{compare_tags, get_list_of_tags}; mod repair_and_verify; use repair_and_verify::{ @@ -38,13 +41,20 @@ mod thunderstore; use thunderstore::query_thunderstore_packages_api; use tauri::{Manager, Runtime}; -use tauri_plugin_store::PluginBuilder; use tokio::time::sleep; #[derive(Default)] struct Counter(Arc<Mutex<i32>>); fn main() { + // Setup logger + let mut log_builder = pretty_env_logger::formatted_builder(); + log_builder.parse_filters("info"); + let logger = sentry_log::SentryLogger::with_dest(log_builder.build()); + + log::set_boxed_logger(Box::new(logger)).unwrap(); + log::set_max_level(log::LevelFilter::Info); + // Only enable Sentry crash logs on release #[cfg(not(debug_assertions))] let _guard = sentry::init(( @@ -57,7 +67,7 @@ fn main() { )); tauri::Builder::default() - .plugin(PluginBuilder::default().build()) + .plugin(tauri_plugin_store::Builder::default().build()) .setup(|app| { let app_handle = app.app_handle(); tauri::async_runtime::spawn(async move { @@ -86,6 +96,17 @@ fn main() { } }); + // Emit updated player and server count to GUI + let app_handle = app.app_handle(); + tauri::async_runtime::spawn(async move { + loop { + sleep(REFRESH_DELAY).await; + app_handle + .emit_all("northstar-statistics", get_server_player_count().await) + .unwrap(); + } + }); + Ok(()) }) .manage(Counter(Default::default())) @@ -100,6 +121,7 @@ fn main() { install_northstar_caller, update_northstar_caller, launch_northstar_caller, + launch_northstar_steam_caller, check_is_flightcore_outdated_caller, get_log_list, verify_game_files, @@ -117,9 +139,12 @@ fn main() { delete_thunderstore_mod, open_repair_window, query_thunderstore_packages_api, + get_list_of_tags, + compare_tags, get_pull_requests_wrapper, apply_launcher_pr, apply_mods_pr, + get_launcher_download_link, close_application, ]) .run(tauri::generate_context!()) @@ -142,7 +167,7 @@ fn force_panic() { #[tauri::command] /// Returns true if built in debug mode async fn is_debug_mode() -> bool { - return cfg!(debug_assertions); + cfg!(debug_assertions) } #[tauri::command] @@ -177,7 +202,7 @@ async fn get_flightcore_version_number() -> String { #[tauri::command] async fn get_northstar_version_number_caller(game_path: String) -> Result<String, String> { - match get_northstar_version_number(game_path) { + match get_northstar_version_number(&game_path) { Ok(version_number) => Ok(version_number), Err(err) => Err(err.to_string()), } @@ -209,10 +234,10 @@ async fn check_is_northstar_outdated( .expect("Couldn't find Northstar on thunderstore???"); // .ok_or_else(|| anyhow!("Couldn't find Northstar on thunderstore???"))?; - let version_number = match get_northstar_version_number(game_path) { + let version_number = match get_northstar_version_number(&game_path) { Ok(version_number) => version_number, Err(err) => { - println!("{}", err); + log::warn!("{}", err); // If we fail to get new version just assume we are up-to-date return Err(err.to_string()); } @@ -222,10 +247,10 @@ async fn check_is_northstar_outdated( let version_number = convert_release_candidate_number(version_number); if version_number != nmod.latest { - println!("Installed Northstar version outdated"); + log::info!("Installed Northstar version outdated"); Ok(true) } else { - println!("Installed Northstar version up-to-date"); + log::info!("Installed Northstar version up-to-date"); Ok(false) } } @@ -244,7 +269,7 @@ async fn verify_install_location(game_path: String) -> bool { match check_is_valid_game_path(&game_path) { Ok(()) => true, Err(err) => { - println!("{}", err); + log::warn!("{}", err); false } } @@ -259,15 +284,16 @@ async fn get_host_os_caller() -> String { #[tauri::command] /// Installs Northstar to the given path async fn install_northstar_caller( + window: tauri::Window, game_path: String, northstar_package_name: Option<String>, ) -> Result<bool, String> { - println!("Running"); - match install_northstar(&game_path, northstar_package_name).await { + log::info!("Running"); + match install_northstar(window, &game_path, northstar_package_name).await { Ok(_) => Ok(true), Err(err) => { - println!("{}", err); - Err(err.to_string()) + log::error!("{}", err); + Err(err) } } } @@ -275,17 +301,18 @@ async fn install_northstar_caller( #[tauri::command] /// Update Northstar install in the given path async fn update_northstar_caller( + window: tauri::Window, game_path: String, northstar_package_name: Option<String>, ) -> Result<bool, String> { - println!("Updating"); + log::info!("Updating Northstar"); // Simply re-run install with up-to-date version for upate - match install_northstar(&game_path, northstar_package_name).await { + match install_northstar(window, &game_path, northstar_package_name).await { Ok(_) => Ok(true), Err(err) => { - println!("{}", err); - Err(err.to_string()) + log::error!("{}", err); + Err(err) } } } @@ -296,7 +323,16 @@ async fn launch_northstar_caller( game_install: GameInstall, bypass_checks: Option<bool>, ) -> Result<String, String> { - launch_northstar(game_install, bypass_checks) + launch_northstar(&game_install, bypass_checks) +} + +#[tauri::command] +/// Launches Northstar +async fn launch_northstar_steam_caller( + game_install: GameInstall, + bypass_checks: Option<bool>, +) -> Result<String, String> { + launch_northstar_steam(&game_install, bypass_checks) } #[tauri::command] @@ -305,11 +341,11 @@ async fn install_mod_caller( game_install: GameInstall, thunderstore_mod_string: String, ) -> Result<(), String> { - fc_download_mod_and_install(game_install.clone(), thunderstore_mod_string).await?; - match clean_up_download_folder(game_install, false) { + fc_download_mod_and_install(&game_install, &thunderstore_mod_string).await?; + match clean_up_download_folder(&game_install, false) { Ok(()) => Ok(()), Err(err) => { - println!("Failed to delete download folder due to {}", err); + log::info!("Failed to delete download folder due to {}", err); // Failure to delete download folder is not an error in mod install // As such ignore. User can still force delete if need be Ok(()) @@ -323,7 +359,7 @@ async fn clean_up_download_folder_caller( game_install: GameInstall, force: bool, ) -> Result<(), String> { - match clean_up_download_folder(game_install, force) { + match clean_up_download_folder(&game_install, force) { Ok(()) => Ok(()), Err(err) => Err(err.to_string()), } @@ -353,7 +389,8 @@ async fn get_server_player_count() -> Result<(i32, usize), String> { // Sum up player count let total_player_count: i32 = ns_servers.iter().map(|server| server.player_count).sum(); - dbg!((total_player_count, server_count)); + log::info!("total_player_count: {}", total_player_count); + log::info!("server_count: {}", server_count); Ok((total_player_count, server_count)) } diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index d2b5040b..728e72c0 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -12,8 +12,6 @@ use std::path::PathBuf; use app::get_enabled_mods; use app::GameInstall; -use json5; - #[derive(Debug, Clone)] struct ParsedThunderstoreModString { author_name: String, @@ -25,7 +23,7 @@ impl std::str::FromStr for ParsedThunderstoreModString { type Err = (); fn from_str(s: &str) -> Result<Self, Self::Err> { - let mut parts = s.split("-"); + let mut parts = s.split('-'); let author_name = parts.next().unwrap().to_string(); let mod_name = parts.next().unwrap().to_string(); @@ -56,9 +54,9 @@ pub struct ModJson { } /// Gets all currently installed and enabled/disabled mods to rebuild `enabledmods.json` -pub fn rebuild_enabled_mods_json(game_install: GameInstall) -> Result<(), String> { +pub fn rebuild_enabled_mods_json(game_install: &GameInstall) -> Result<(), String> { let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); - let mods_and_properties = get_installed_mods_and_properties(game_install)?; + let mods_and_properties = get_installed_mods_and_properties(game_install.clone())?; // Create new mapping let mut my_map = serde_json::Map::new(); @@ -91,28 +89,27 @@ pub fn set_mod_enabled_status( let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); // Parse JSON - let mut res: serde_json::Value = match get_enabled_mods(game_install.clone()) { + let mut res: serde_json::Value = match get_enabled_mods(&game_install) { Ok(res) => res, Err(err) => { - println!("Couldn't parse `enabledmod.json`: {}", err); - println!("Rebuilding file."); + log::warn!("Couldn't parse `enabledmod.json`: {}", err); + log::warn!("Rebuilding file."); - rebuild_enabled_mods_json(game_install.clone())?; + rebuild_enabled_mods_json(&game_install)?; // Then try again - let res = get_enabled_mods(game_install.clone())?; - res + get_enabled_mods(&game_install)? } }; // Check if key exists if res.get(mod_name.clone()).is_none() { // If it doesn't exist, rebuild `enabledmod.json` - println!("Value not found in `enabledmod.json`. Rebuilding file"); - rebuild_enabled_mods_json(game_install.clone())?; + log::info!("Value not found in `enabledmod.json`. Rebuilding file"); + rebuild_enabled_mods_json(&game_install)?; // Then try again - res = get_enabled_mods(game_install)?; + res = get_enabled_mods(&game_install)?; } // Update value @@ -129,7 +126,7 @@ pub fn set_mod_enabled_status( } /// Parses `manifest.json` for Thunderstore mod string -fn parse_for_thunderstore_mod_string(nsmod_path: String) -> Result<String, anyhow::Error> { +fn parse_for_thunderstore_mod_string(nsmod_path: &str) -> Result<String, anyhow::Error> { let manifest_json_path = format!("{}/manifest.json", nsmod_path); let ts_author_txt_path = format!("{}/thunderstore_author.txt", nsmod_path); @@ -152,7 +149,7 @@ fn parse_for_thunderstore_mod_string(nsmod_path: String) -> Result<String, anyho } /// Parse `mods` folder for installed mods. -fn parse_installed_mods(game_install: GameInstall) -> Result<Vec<NorthstarMod>, anyhow::Error> { +fn parse_installed_mods(game_install: &GameInstall) -> Result<Vec<NorthstarMod>, anyhow::Error> { let ns_mods_folder = format!("{}/R2Northstar/mods/", game_install.game_path); let paths = match std::fs::read_dir(ns_mods_folder) { @@ -189,7 +186,7 @@ fn parse_installed_mods(game_install: GameInstall) -> Result<Vec<NorthstarMod>, let parsed_mod_json: ModJson = match json5::from_str(&data) { Ok(parsed_json) => parsed_json, Err(err) => { - println!("Failed parsing {} with {}", mod_json_path, err.to_string()); + log::warn!("Failed parsing {} with {}", mod_json_path, err.to_string()); continue; } }; @@ -198,7 +195,7 @@ fn parse_installed_mods(game_install: GameInstall) -> Result<Vec<NorthstarMod>, // Attempt legacy method for getting Thunderstore string first Some(ts_mod_string) => Some(ts_mod_string), // Legacy method failed - None => match parse_for_thunderstore_mod_string(directory_str) { + None => match parse_for_thunderstore_mod_string(&directory_str) { Ok(thunderstore_mod_string) => Some(thunderstore_mod_string), Err(_err) => None, }, @@ -209,7 +206,7 @@ fn parse_installed_mods(game_install: GameInstall) -> Result<Vec<NorthstarMod>, let ns_mod = NorthstarMod { name: parsed_mod_json.name, version: parsed_mod_json.version, - thunderstore_mod_string: thunderstore_mod_string, + thunderstore_mod_string, enabled: false, // Placeholder directory: mod_directory, }; @@ -229,13 +226,13 @@ pub fn get_installed_mods_and_properties( game_install: GameInstall, ) -> Result<Vec<NorthstarMod>, String> { // Get actually installed mods - let found_installed_mods = match parse_installed_mods(game_install.clone()) { + let found_installed_mods = match parse_installed_mods(&game_install) { Ok(res) => res, Err(err) => return Err(err.to_string()), }; // Get enabled mods as JSON - let enabled_mods: serde_json::Value = match get_enabled_mods(game_install) { + let enabled_mods: serde_json::Value = match get_enabled_mods(&game_install) { Ok(enabled_mods) => enabled_mods, Err(_) => serde_json::from_str("{}").unwrap(), // `enabledmods.json` not found, create empty object }; @@ -256,7 +253,7 @@ pub fn get_installed_mods_and_properties( Ok(installed_mods) } -async fn get_ns_mod_download_url(thunderstore_mod_string: String) -> Result<String, String> { +async fn get_ns_mod_download_url(thunderstore_mod_string: &str) -> Result<String, String> { // TODO: This will crash the thread if not internet connection exist. `match` should be used instead let index = thermite::api::get_package_index().unwrap().to_vec(); @@ -276,7 +273,7 @@ async fn get_ns_mod_download_url(thunderstore_mod_string: String) -> Result<Stri for ns_mod in index { // Iterate over all versions of a given mod - for (_key, ns_mod) in &ns_mod.versions { + for ns_mod in ns_mod.versions.values() { if ns_mod.url.contains(&ts_mod_string_url) { dbg!(ns_mod.clone()); return Ok(ns_mod.url.clone()); @@ -288,21 +285,19 @@ async fn get_ns_mod_download_url(thunderstore_mod_string: String) -> Result<Stri } /// Returns a vector of modstrings containing the dependencies of a given mod -async fn get_mod_dependencies( - thunderstore_mod_string: String, -) -> Result<Vec<String>, anyhow::Error> { - dbg!(thunderstore_mod_string.clone()); +async fn get_mod_dependencies(thunderstore_mod_string: &str) -> Result<Vec<String>, anyhow::Error> { + dbg!(thunderstore_mod_string); // TODO: This will crash the thread if not internet connection exist. `match` should be used instead let index = thermite::api::get_package_index().unwrap().to_vec(); // String replace works but more care should be taken in the future - let ts_mod_string_url = thunderstore_mod_string.replace("-", "/"); + let ts_mod_string_url = thunderstore_mod_string.replace('-', "/"); // Iterate over index for ns_mod in index { // Iterate over all versions of a given mod - for (_key, ns_mod) in &ns_mod.versions { + for ns_mod in ns_mod.versions.values() { if ns_mod.url.contains(&ts_mod_string_url) { dbg!(ns_mod.clone()); return Ok(ns_mod.deps.clone()); @@ -317,8 +312,8 @@ async fn get_mod_dependencies( /// Download and install mod to the specified target. #[async_recursion] pub async fn fc_download_mod_and_install( - game_install: GameInstall, - thunderstore_mod_string: String, + game_install: &GameInstall, + thunderstore_mod_string: &str, ) -> Result<(), String> { // Get mods and download directories let download_directory = format!( @@ -328,11 +323,11 @@ pub async fn fc_download_mod_and_install( let mods_directory = format!("{}/R2Northstar/mods/", game_install.game_path); // Early return on empty string - if thunderstore_mod_string.len() == 0 { + if thunderstore_mod_string.is_empty() { return Err("Passed empty string".to_string()); } - let deps = match get_mod_dependencies(thunderstore_mod_string.clone()).await { + let deps = match get_mod_dependencies(thunderstore_mod_string).await { Ok(deps) => deps, Err(err) => return Err(err.to_string()), }; @@ -340,13 +335,13 @@ pub async fn fc_download_mod_and_install( // Recursively install dependencies for dep in deps { - match fc_download_mod_and_install(game_install.clone(), dep).await { + match fc_download_mod_and_install(game_install, &dep).await { Ok(()) => (), Err(err) => { - if err.to_string() == "Cannot install Northstar as a mod!" { + if err == "Cannot install Northstar as a mod!" { continue; // For Northstar as a dependency, we just skip it } else { - return Err(err.to_string()); + return Err(err); } } }; @@ -361,7 +356,7 @@ pub async fn fc_download_mod_and_install( } // Get download URL for the specified mod - let download_url = get_ns_mod_download_url(thunderstore_mod_string.clone()).await?; + let download_url = get_ns_mod_download_url(thunderstore_mod_string).await?; // Create download directory match std::fs::create_dir_all(download_directory.clone()) { @@ -369,20 +364,19 @@ pub async fn fc_download_mod_and_install( Err(err) => return Err(err.to_string()), }; - let name = thunderstore_mod_string.clone(); let path = format!( - "{}/___flightcore-temp-download-dir/{}.zip", - game_install.game_path, name + "{}/___flightcore-temp-download-dir/{thunderstore_mod_string}.zip", + game_install.game_path ); // Download the mod - let f = match thermite::core::manage::download_file(&download_url, path.clone()) { + let f = match thermite::core::manage::download_file(download_url, path.clone()) { Ok(f) => f, Err(e) => return Err(e.to_string()), }; // Get Thunderstore mod author - let author = thunderstore_mod_string.split("-").next().unwrap(); + let author = thunderstore_mod_string.split('-').next().unwrap(); // Extract the mod to the mods directory match thermite::core::manage::install_mod(author, &f, std::path::Path::new(&mods_directory)) { @@ -397,7 +391,7 @@ pub async fn fc_download_mod_and_install( } /// Deletes a given Northstar mod folder -fn delete_mod_folder(ns_mod_directory: String) -> Result<(), String> { +fn delete_mod_folder(ns_mod_directory: &str) -> Result<(), String> { let ns_mod_dir_path = std::path::Path::new(&ns_mod_directory); // Safety check: Check whether `mod.json` exists and exit early if not @@ -408,7 +402,7 @@ fn delete_mod_folder(ns_mod_directory: String) -> Result<(), String> { return Err(format!("mod.json does not exist in {}", ns_mod_directory)); } - match std::fs::remove_dir_all(&ns_mod_directory) { + match std::fs::remove_dir_all(ns_mod_directory) { Ok(()) => Ok(()), Err(err) => Err(format!("Failed deleting mod: {err}")), } @@ -432,7 +426,7 @@ pub fn delete_northstar_mod(game_install: GameInstall, nsmod_name: String) -> Re // Installed mod matches specified mod if installed_ns_mod.name == nsmod_name { // Delete folder - return delete_mod_folder(installed_ns_mod.directory); + return delete_mod_folder(&installed_ns_mod.directory); } } @@ -483,7 +477,7 @@ pub fn delete_thunderstore_mod( } } - if !(mod_folders_to_remove.len() > 0) { + if mod_folders_to_remove.is_empty() { return Err(format!( "No mods removed as no Northstar mods matching {thunderstore_mod_string} were found to be installed." )); @@ -491,7 +485,7 @@ pub fn delete_thunderstore_mod( // Delete given folders for mod_folder in mod_folders_to_remove { - delete_mod_folder(mod_folder)?; + delete_mod_folder(&mod_folder)?; } Ok(()) diff --git a/src-tauri/src/northstar/mod.rs b/src-tauri/src/northstar/mod.rs index a043632c..7bd0b0a3 100644 --- a/src-tauri/src/northstar/mod.rs +++ b/src-tauri/src/northstar/mod.rs @@ -5,25 +5,23 @@ use crate::{check_mod_version_number, constants::CORE_MODS}; use anyhow::anyhow; /// Returns the current Northstar version number as a string -pub fn get_northstar_version_number(game_path: String) -> Result<String, anyhow::Error> { - println!("{}", game_path); - // println!("{:?}", install_type); +pub fn get_northstar_version_number(game_path: &str) -> Result<String, anyhow::Error> { + log::info!("{}", game_path); // TODO: // Check if NorthstarLauncher.exe exists and check its version number let profile_folder = "R2Northstar"; - let initial_version_number = match check_mod_version_number(format!( - "{}/{}/mods/{}", - game_path, profile_folder, CORE_MODS[0] + let initial_version_number = match check_mod_version_number(&format!( + "{game_path}/{profile_folder}/mods/{}", + CORE_MODS[0] )) { Ok(version_number) => version_number, Err(err) => return Err(err), }; for core_mod in CORE_MODS { - let current_version_number = match check_mod_version_number(format!( - "{}/{}/mods/{}", - game_path, profile_folder, core_mod + let current_version_number = match check_mod_version_number(&format!( + "{game_path}/{profile_folder}/mods/{core_mod}", )) { Ok(version_number) => version_number, Err(err) => return Err(err), @@ -33,7 +31,7 @@ pub fn get_northstar_version_number(game_path: String) -> Result<String, anyhow: return Err(anyhow!("Found version number mismatch")); } } - println!("All mods same version"); + log::info!("All mods same version"); Ok(initial_version_number) } diff --git a/src-tauri/src/platform_specific/linux.rs b/src-tauri/src/platform_specific/linux.rs index eb9fbea6..4b9964e9 100644 --- a/src-tauri/src/platform_specific/linux.rs +++ b/src-tauri/src/platform_specific/linux.rs @@ -14,10 +14,10 @@ pub fn check_glibc_v() -> f32 { let lddvl: Vec<&str> = lddva.split('\n').collect(); let lddvlo = &lddvl[0]; let reg = Regex::new(r"(2.\d{2}$)").unwrap(); - for caps in reg.captures_iter(lddvlo) { + if let Some(caps) = reg.captures_iter(lddvlo).next() { return caps.get(1).unwrap().as_str().parse::<f32>().unwrap(); // theres prolly a better way ijdk how tho } - return 0.0; // this shouldnt ever be reached but it has to be here + 0.0 // this shouldnt ever be reached but it has to be here } /* diff --git a/src-tauri/src/platform_specific/windows.rs b/src-tauri/src/platform_specific/windows.rs index 21772b91..004beb6e 100644 --- a/src-tauri/src/platform_specific/windows.rs +++ b/src-tauri/src/platform_specific/windows.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use crate::{check_is_valid_game_path, constants::TITANFALL2_ORIGIN_IDS}; -/// Runs a powershell command and parses output to get Titanfall2 install location on Origin +/// Gets Titanfall2 install location on Origin pub fn origin_install_location_detection() -> Result<String, anyhow::Error> { // Iterate over known Titanfall2 Origin IDs for origin_id in TITANFALL2_ORIGIN_IDS { @@ -18,14 +18,14 @@ pub fn origin_install_location_detection() -> Result<String, anyhow::Error> { return Ok(game_path_str.to_string()); } Err(err) => { - println!("{}", err.to_string()); + log::warn!("{}", err); continue; // Not a valid game path } } } } Err(err) => { - println!("Couldn't find {origin_id}: {err}") + log::warn!("Couldn't find {origin_id}: {err}") } } } diff --git a/src-tauri/src/repair_and_verify/mod.rs b/src-tauri/src/repair_and_verify/mod.rs index 9d020f15..ecf5e1ec 100644 --- a/src-tauri/src/repair_and_verify/mod.rs +++ b/src-tauri/src/repair_and_verify/mod.rs @@ -15,9 +15,9 @@ pub fn verify_game_files(game_install: GameInstall) -> Result<String, String> { #[tauri::command] pub fn disable_all_but_core(game_install: GameInstall) -> Result<(), String> { // Rebuild `enabledmods.json` first to ensure all mods are added - rebuild_enabled_mods_json(game_install.clone())?; + rebuild_enabled_mods_json(&game_install)?; - let current_mods = get_enabled_mods(game_install.clone())?; + let current_mods = get_enabled_mods(&game_install)?; // Disable all mods, set core mods to enabled for (key, _value) in current_mods.as_object().unwrap() { @@ -37,7 +37,7 @@ pub fn disable_all_but_core(game_install: GameInstall) -> Result<(), String> { /// If `force` is FALSE, bails on non-empty folder /// If `force` is TRUE, deletes folder even if non-empty pub fn clean_up_download_folder( - game_install: GameInstall, + game_install: &GameInstall, force: bool, ) -> Result<(), anyhow::Error> { // Get download directory @@ -51,7 +51,7 @@ pub fn clean_up_download_folder( // dbg!(download_dir_contents); let mut count = 0; - download_dir_contents.inspect(|_| count += 1).for_each(drop); + download_dir_contents.for_each(|_| count += 1); if count > 0 && !force { return Err(anyhow!("Folder not empty, not deleting")); @@ -84,7 +84,7 @@ pub fn get_log_list(game_install: GameInstall) -> Result<Vec<std::path::PathBuf> } } - if log_files.len() > 0 { + if !log_files.is_empty() { Ok(log_files) } else { Err("No logs found".to_string()) diff --git a/src-tauri/src/thunderstore/mod.rs b/src-tauri/src/thunderstore/mod.rs index 9151ba7c..e9eb30d7 100644 --- a/src-tauri/src/thunderstore/mod.rs +++ b/src-tauri/src/thunderstore/mod.rs @@ -43,7 +43,7 @@ pub struct ThunderstoreModVersion { /// Queries Thunderstore packages API #[tauri::command] pub async fn query_thunderstore_packages_api() -> Result<Vec<ThunderstoreMod>, String> { - println!("Fetching Thunderstore API"); + log::info!("Fetching Thunderstore API"); // Fetches let url = "https://northstar.thunderstore.io/api/v1/package/"; @@ -66,7 +66,7 @@ pub async fn query_thunderstore_packages_api() -> Result<Vec<ThunderstoreMod>, S }; // Remove some mods from listing - let to_remove_set: HashSet<&str> = BLACKLISTED_MODS.iter().map(|s| s.as_ref()).collect(); + let to_remove_set: HashSet<&str> = BLACKLISTED_MODS.iter().copied().collect(); let filtered_packages = parsed_json .into_iter() .filter(|package| !to_remove_set.contains(&package.full_name.as_ref())) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 77a10d73..c24251e1 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "FlightCore", - "version": "1.10.2" + "version": "1.13.2" }, "tauri": { "allowlist": { diff --git a/src-vue/package-lock.json b/src-vue/package-lock.json index bf311673..31cee6e0 100644 --- a/src-vue/package-lock.json +++ b/src-vue/package-lock.json @@ -1,7 +1,7 @@ { "name": "src-vue", "version": "0.0.0", - "lockfileVersion": 3, + "lockfileVersion": 2, "requires": true, "packages": { "": { @@ -11,8 +11,9 @@ "@element-plus/icons-vue": "^2.0.9", "element-plus": "^2.2.17", "marked": "^4.1.1", - "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#dev", + "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#9bd993aa67766596638bbfd91e79a1bf8f632014", "vue": "^3.2.37", + "vue-i18n": "^9.2.2", "vue-router": "^4.1.5", "vuex": "^4.0.2" }, @@ -25,9 +26,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", - "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -36,9 +37,9 @@ } }, "node_modules/@ctrl/tinycolor": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz", - "integrity": "sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz", + "integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==", "engines": { "node": ">=10" } @@ -51,49 +52,74 @@ "vue": "^3.2.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], + "node_modules/@floating-ui/core": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.0.2.tgz", + "integrity": "sha512-Skfy0YS3NJ5nV9us0uuPN0HDk1Q4edljaOhRBJGDWs9EBa7ZVMYBHRFlhLvvmwEoaIM9BlH6QJFn9/uZg0bACg==" + }, + "node_modules/@floating-ui/dom": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.7.tgz", + "integrity": "sha512-6RsqvCYe0AYWtsGvuWqCm7mZytnXAZCjWtsWu1Kg8dI3INvj/DbKlDsZO+mKSaQdPT12uxIW9W2dAWJkPx4Y5g==", + "dependencies": { + "@floating-ui/core": "^1.0.2" + } + }, + "node_modules/@intlify/core-base": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz", + "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==", + "dependencies": { + "@intlify/devtools-if": "9.2.2", + "@intlify/message-compiler": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2" + }, "engines": { - "node": ">=12" + "node": ">= 14" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/@intlify/devtools-if": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz", + "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==", + "dependencies": { + "@intlify/shared": "9.2.2" + }, "engines": { - "node": ">=12" + "node": ">= 14" } }, - "node_modules/@floating-ui/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.1.tgz", - "integrity": "sha512-LSqwPZkK3rYfD7GKoIeExXOyYx6Q1O4iqZWwIehDNuv3Dv425FIAE8PRwtAx1imEolFTHgBEcoFHm9MDnYgPCg==" + "node_modules/@intlify/message-compiler": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz", + "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==", + "dependencies": { + "@intlify/shared": "9.2.2", + "source-map": "0.6.1" + }, + "engines": { + "node": ">= 14" + } }, - "node_modules/@floating-ui/dom": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.1.tgz", - "integrity": "sha512-Rt45SmRiV8eU+xXSB9t0uMYiQ/ZWGE/jumse2o3i5RGlyvcbqOF4q+1qBnzLE2kZ5JGhq0iMkcGXUKbFe7MpTA==", + "node_modules/@intlify/shared": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz", + "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@intlify/vue-devtools": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz", + "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==", "dependencies": { - "@floating-ui/core": "^1.2.1" + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2" + }, + "engines": { + "node": ">= 14" } }, "node_modules/@popperjs/core": { @@ -107,11 +133,11 @@ } }, "node_modules/@tauri-apps/api": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.2.0.tgz", - "integrity": "sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.1.0.tgz", + "integrity": "sha512-n13pIqdPd3KtaMmmAcrU7BTfdMtIlGNnfZD0dNX8L4p8dgmuNyikm6JAA+yCpl9gqq6I8x5cV2Y0muqdgD0cWw==", "engines": { - "node": ">= 14.6.0", + "node": ">= 12.22.0", "npm": ">= 6.6.0", "yarn": ">= 1.19.1" }, @@ -134,9 +160,9 @@ } }, "node_modules/@types/marked": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.8.tgz", - "integrity": "sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz", + "integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==", "dev": true }, "node_modules/@types/web-bluetooth": { @@ -158,90 +184,91 @@ } }, "node_modules/@volar/language-core": { - "version": "1.3.0-alpha.0", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.3.0-alpha.0.tgz", - "integrity": "sha512-W3uMzecHPcbwddPu4SJpUcPakRBK/y/BP+U0U6NiPpUX1tONLC4yCawt+QBJqtgJ+sfD6ztf5PyvPL3hQRqfOA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.0.11.tgz", + "integrity": "sha512-YwUYKxIyDc+Fq3kQ6BGGfkrKCG5JzE2Yr6vMxrxEXW2rg/gsq3JgMk/4sI8ybRsaTirhCB4V8+AIVYsvcRxgig==", "dev": true, "dependencies": { - "@volar/source-map": "1.3.0-alpha.0" + "@volar/source-map": "1.0.11", + "@vue/reactivity": "^3.2.45", + "muggle-string": "^0.1.0" } }, "node_modules/@volar/source-map": { - "version": "1.3.0-alpha.0", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.3.0-alpha.0.tgz", - "integrity": "sha512-jSdizxWFvDTvkPYZnO6ew3sBZUnS0abKCbuopkc0JrIlFbznWC/fPH3iPFIMS8/IIkRxq1Jh9VVG60SmtsdaMQ==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.0.11.tgz", + "integrity": "sha512-tkuV9MD+OuiZfHA0qZXrPdW6F7TvnpnuTan6Qe7UGUs9+sflezlMJdjaYdGgQObfP+06pcT1E3xdkOoi08ZyyQ==", "dev": true, "dependencies": { - "muggle-string": "^0.2.2" + "muggle-string": "^0.1.0" } }, "node_modules/@volar/typescript": { - "version": "1.3.0-alpha.0", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.3.0-alpha.0.tgz", - "integrity": "sha512-5UItyW2cdH2mBLu4RrECRNJRgtvvzKrSCn2y3v/D61QwIDkGx4aeil6x8RFuUL5TFtV6QvVHXnsOHxNgd+sCow==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.0.11.tgz", + "integrity": "sha512-mq7wDDAs0Eb43jev2FxbowuiwWqvL3kb+tar1we8VQbdabpyQ5dmbWPwo/IglevMmW3SKo1Et+6rqAeZpXNnPQ==", "dev": true, "dependencies": { - "@volar/language-core": "1.3.0-alpha.0" + "@volar/language-core": "1.0.11" } }, "node_modules/@volar/vue-language-core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@volar/vue-language-core/-/vue-language-core-1.2.0.tgz", - "integrity": "sha512-w7yEiaITh2WzKe6u8ZdeLKCUz43wdmY/OqAmsB/PGDvvhTcVhCJ6f0W/RprZL1IhqH8wALoWiwEh/Wer7ZviMQ==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/vue-language-core/-/vue-language-core-1.0.11.tgz", + "integrity": "sha512-A3ODs0/ua7BcpSSnE7KtO8bzWsYsbOJRyW2Q/2uktxlfHj8srln3JdgK/mNlIgfnWtACbE5K+EfMJOgJKv864A==", "dev": true, "dependencies": { - "@volar/language-core": "1.3.0-alpha.0", - "@volar/source-map": "1.3.0-alpha.0", - "@vue/compiler-dom": "^3.2.47", - "@vue/compiler-sfc": "^3.2.47", - "@vue/reactivity": "^3.2.47", - "@vue/shared": "^3.2.47", - "minimatch": "^6.1.6", - "muggle-string": "^0.2.2", + "@volar/language-core": "1.0.11", + "@volar/source-map": "1.0.11", + "@vue/compiler-dom": "^3.2.45", + "@vue/compiler-sfc": "^3.2.45", + "@vue/reactivity": "^3.2.45", + "@vue/shared": "^3.2.45", + "minimatch": "^5.1.0", "vue-template-compiler": "^2.7.14" } }, "node_modules/@volar/vue-typescript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@volar/vue-typescript/-/vue-typescript-1.2.0.tgz", - "integrity": "sha512-zjmRi9y3J1EkG+pfuHp8IbHmibihrKK485cfzsHjiuvJMGrpkWvlO5WVEk8oslMxxeGC5XwBFE9AOlvh378EPA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/vue-typescript/-/vue-typescript-1.0.11.tgz", + "integrity": "sha512-jlnFPvBcTyPiAbGlgjhKK7fp3Q+Z7Z5eU1NTbTSS0lQC8Gog3sh2UxLAFG5Voe1gHIxasoOEPXzMR0CWF4bKbA==", "dev": true, "dependencies": { - "@volar/typescript": "1.3.0-alpha.0", - "@volar/vue-language-core": "1.2.0" + "@volar/typescript": "1.0.11", + "@volar/vue-language-core": "1.0.11" } }, "node_modules/@vue/compiler-core": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz", - "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", + "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", "dependencies": { "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.47", + "@vue/shared": "3.2.45", "estree-walker": "^2.0.2", "source-map": "^0.6.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz", - "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", + "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", "dependencies": { - "@vue/compiler-core": "3.2.47", - "@vue/shared": "3.2.47" + "@vue/compiler-core": "3.2.45", + "@vue/shared": "3.2.45" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz", - "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", + "integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", "dependencies": { "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.47", - "@vue/compiler-dom": "3.2.47", - "@vue/compiler-ssr": "3.2.47", - "@vue/reactivity-transform": "3.2.47", - "@vue/shared": "3.2.47", + "@vue/compiler-core": "3.2.45", + "@vue/compiler-dom": "3.2.45", + "@vue/compiler-ssr": "3.2.45", + "@vue/reactivity-transform": "3.2.45", + "@vue/shared": "3.2.45", "estree-walker": "^2.0.2", "magic-string": "^0.25.7", "postcss": "^8.1.10", @@ -249,83 +276,83 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz", - "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", + "integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", "dependencies": { - "@vue/compiler-dom": "3.2.47", - "@vue/shared": "3.2.47" + "@vue/compiler-dom": "3.2.45", + "@vue/shared": "3.2.45" } }, "node_modules/@vue/devtools-api": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", - "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz", + "integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==" }, "node_modules/@vue/reactivity": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz", - "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz", + "integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==", "dependencies": { - "@vue/shared": "3.2.47" + "@vue/shared": "3.2.45" } }, "node_modules/@vue/reactivity-transform": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz", - "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", + "integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", "dependencies": { "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.47", - "@vue/shared": "3.2.47", + "@vue/compiler-core": "3.2.45", + "@vue/shared": "3.2.45", "estree-walker": "^2.0.2", "magic-string": "^0.25.7" } }, "node_modules/@vue/runtime-core": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz", - "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz", + "integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==", "dependencies": { - "@vue/reactivity": "3.2.47", - "@vue/shared": "3.2.47" + "@vue/reactivity": "3.2.45", + "@vue/shared": "3.2.45" } }, "node_modules/@vue/runtime-dom": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz", - "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz", + "integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==", "dependencies": { - "@vue/runtime-core": "3.2.47", - "@vue/shared": "3.2.47", + "@vue/runtime-core": "3.2.45", + "@vue/shared": "3.2.45", "csstype": "^2.6.8" } }, "node_modules/@vue/server-renderer": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz", - "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz", + "integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==", "dependencies": { - "@vue/compiler-ssr": "3.2.47", - "@vue/shared": "3.2.47" + "@vue/compiler-ssr": "3.2.45", + "@vue/shared": "3.2.45" }, "peerDependencies": { - "vue": "3.2.47" + "vue": "3.2.45" } }, "node_modules/@vue/shared": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz", - "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==" + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", + "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==" }, "node_modules/@vueuse/core": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz", - "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.6.0.tgz", + "integrity": "sha512-qGUcjKQXHgN+jqXEgpeZGoxdCbIDCdVPz3QiF1uyecVGbMuM63o96I1GjYx5zskKgRI0FKSNsVWM7rwrRMTf6A==", "dependencies": { "@types/web-bluetooth": "^0.0.16", - "@vueuse/metadata": "9.13.0", - "@vueuse/shared": "9.13.0", + "@vueuse/metadata": "9.6.0", + "@vueuse/shared": "9.6.0", "vue-demi": "*" }, "funding": { @@ -358,17 +385,17 @@ } }, "node_modules/@vueuse/metadata": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", - "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.6.0.tgz", + "integrity": "sha512-sIC8R+kWkIdpi5X2z2Gk8TRYzmczDwHRhEFfCu2P+XW2JdPoXrziqsGpDDsN7ykBx4ilwieS7JUIweVGhvZ93w==", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", - "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.6.0.tgz", + "integrity": "sha512-/eDchxYYhkHnFyrb00t90UfjCx94kRHxc7J1GtBCqCG4HyPMX+krV9XJgVtWIsAMaxKVU4fC8NSUviG1JkwhUQ==", "dependencies": { "vue-demi": "*" }, @@ -427,9 +454,9 @@ "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" }, "node_modules/dayjs": { - "version": "1.11.7", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", - "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.6.tgz", + "integrity": "sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==" }, "node_modules/de-indent": { "version": "1.0.2", @@ -438,9 +465,9 @@ "dev": true }, "node_modules/element-plus": { - "version": "2.2.32", - "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.32.tgz", - "integrity": "sha512-DTJMhYOy6MApbmh6z/95hPTK5WrBiNHGzV4IN+uEkup1WoimQ+Qyt8RxKdTe/X1LWEJ8YgWv/Cl8P4ocrt5z5g==", + "version": "2.2.26", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.26.tgz", + "integrity": "sha512-O/rdY5m9DkclpVg8r3GynyqCunm7MxSR142xSsjrZA77bi7bcwA3SIy6SPEDqHi5R4KqgkGYgKSp4Q4e3irbYg==", "dependencies": { "@ctrl/tinycolor": "^3.4.1", "@element-plus/icons-vue": "^2.0.6", @@ -499,294 +526,6 @@ "esbuild-windows-arm64": "0.15.18" } }, - "node_modules/esbuild-android-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", - "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", - "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", - "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", - "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", - "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", - "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", - "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", - "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", - "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", - "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", - "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", - "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", - "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", - "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", - "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", - "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", - "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", - "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/esbuild-windows-64": { "version": "0.15.18", "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", @@ -803,22 +542,6 @@ "node": ">=12" } }, - "node_modules/esbuild-windows-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", - "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -829,20 +552,6 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -911,9 +620,9 @@ } }, "node_modules/marked": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", - "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz", + "integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw==", "bin": { "marked": "bin/marked.js" }, @@ -927,24 +636,21 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" }, "node_modules/minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", + "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/muggle-string": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.2.2.tgz", - "integrity": "sha512-YVE1mIJ4VpUMqZObFndk9CJu6DBJR/GB13p3tXuNbwD4XExaI5EOuRl6BHeIDxIqXZVxSfAC+y6U1Z/IxCfKUg==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.1.0.tgz", + "integrity": "sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==", "dev": true }, "node_modules/nanoid": { @@ -975,9 +681,9 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", + "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", "funding": [ { "type": "opencollective", @@ -1064,17 +770,24 @@ } }, "node_modules/tauri-plugin-store-api": { - "version": "0.0.0", - "resolved": "git+ssh://git@github.com/tauri-apps/tauri-plugin-store.git#916165e4e4ad9821095584dc02ecd9c295cc9971", - "license": "MIT or APACHE-2.0", + "version": "0.1.0", + "resolved": "git+ssh://git@github.com/tauri-apps/tauri-plugin-store.git#9bd993aa67766596638bbfd91e79a1bf8f632014", + "integrity": "sha512-X0cDDcEVLY2X8qCLISgAjzuBKDn7bJkj4S7LnXbEPFbPRe+NzhmFGHSAdFCuQuPzQYjmrVg18mZx9NAg4GBHag==", + "license": "MIT", "dependencies": { - "@tauri-apps/api": "^1.2.0" + "@tauri-apps/api": "1.1.0", + "tslib": "2.4.0" } }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -1134,15 +847,32 @@ } }, "node_modules/vue": { - "version": "3.2.47", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz", - "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz", + "integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==", "dependencies": { - "@vue/compiler-dom": "3.2.47", - "@vue/compiler-sfc": "3.2.47", - "@vue/runtime-dom": "3.2.47", - "@vue/server-renderer": "3.2.47", - "@vue/shared": "3.2.47" + "@vue/compiler-dom": "3.2.45", + "@vue/compiler-sfc": "3.2.45", + "@vue/runtime-dom": "3.2.45", + "@vue/server-renderer": "3.2.45", + "@vue/shared": "3.2.45" + } + }, + "node_modules/vue-i18n": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz", + "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==", + "dependencies": { + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2", + "@vue/devtools-api": "^6.2.1" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "vue": "^3.0.0" } }, "node_modules/vue-router": { @@ -1170,13 +900,13 @@ } }, "node_modules/vue-tsc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.2.0.tgz", - "integrity": "sha512-rIlzqdrhyPYyLG9zxsVRa+JEseeS9s8F2BbVVVWRRsTZvJO2BbhLEb2HW3MY+DFma0378tnIqs+vfTzbcQtRFw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.0.11.tgz", + "integrity": "sha512-lj+6dEroPsE4wmQOPtjCzAf8x363Km5/tuEvMEoQaoRnzs9myBM46FNvCGIIPStYUGuaqF1W1bORmP2KDQEORA==", "dev": true, "dependencies": { - "@volar/vue-language-core": "1.2.0", - "@volar/vue-typescript": "1.2.0" + "@volar/vue-language-core": "1.0.11", + "@volar/vue-typescript": "1.0.11" }, "bin": { "vue-tsc": "bin/vue-tsc.js" @@ -1196,5 +926,666 @@ "vue": "^3.2.0" } } + }, + "dependencies": { + "@babel/parser": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" + }, + "@ctrl/tinycolor": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz", + "integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==" + }, + "@element-plus/icons-vue": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.0.10.tgz", + "integrity": "sha512-ygEZ1mwPjcPo/OulhzLE7mtDrQBWI8vZzEWSNB2W/RNCRjoQGwbaK4N8lV4rid7Ts4qvySU3njMN7YCiSlSaTQ==", + "requires": {} + }, + "@floating-ui/core": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.0.2.tgz", + "integrity": "sha512-Skfy0YS3NJ5nV9us0uuPN0HDk1Q4edljaOhRBJGDWs9EBa7ZVMYBHRFlhLvvmwEoaIM9BlH6QJFn9/uZg0bACg==" + }, + "@floating-ui/dom": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.7.tgz", + "integrity": "sha512-6RsqvCYe0AYWtsGvuWqCm7mZytnXAZCjWtsWu1Kg8dI3INvj/DbKlDsZO+mKSaQdPT12uxIW9W2dAWJkPx4Y5g==", + "requires": { + "@floating-ui/core": "^1.0.2" + } + }, + "@intlify/core-base": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz", + "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==", + "requires": { + "@intlify/devtools-if": "9.2.2", + "@intlify/message-compiler": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2" + } + }, + "@intlify/devtools-if": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz", + "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==", + "requires": { + "@intlify/shared": "9.2.2" + } + }, + "@intlify/message-compiler": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz", + "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==", + "requires": { + "@intlify/shared": "9.2.2", + "source-map": "0.6.1" + } + }, + "@intlify/shared": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz", + "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==" + }, + "@intlify/vue-devtools": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz", + "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==", + "requires": { + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2" + } + }, + "@popperjs/core": { + "version": "npm:@sxzz/popperjs-es@2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==" + }, + "@tauri-apps/api": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.1.0.tgz", + "integrity": "sha512-n13pIqdPd3KtaMmmAcrU7BTfdMtIlGNnfZD0dNX8L4p8dgmuNyikm6JAA+yCpl9gqq6I8x5cV2Y0muqdgD0cWw==" + }, + "@types/lodash": { + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" + }, + "@types/lodash-es": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.6.tgz", + "integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/marked": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz", + "integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==", + "dev": true + }, + "@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==" + }, + "@vitejs/plugin-vue": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz", + "integrity": "sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==", + "dev": true, + "requires": {} + }, + "@volar/language-core": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.0.11.tgz", + "integrity": "sha512-YwUYKxIyDc+Fq3kQ6BGGfkrKCG5JzE2Yr6vMxrxEXW2rg/gsq3JgMk/4sI8ybRsaTirhCB4V8+AIVYsvcRxgig==", + "dev": true, + "requires": { + "@volar/source-map": "1.0.11", + "@vue/reactivity": "^3.2.45", + "muggle-string": "^0.1.0" + } + }, + "@volar/source-map": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.0.11.tgz", + "integrity": "sha512-tkuV9MD+OuiZfHA0qZXrPdW6F7TvnpnuTan6Qe7UGUs9+sflezlMJdjaYdGgQObfP+06pcT1E3xdkOoi08ZyyQ==", + "dev": true, + "requires": { + "muggle-string": "^0.1.0" + } + }, + "@volar/typescript": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.0.11.tgz", + "integrity": "sha512-mq7wDDAs0Eb43jev2FxbowuiwWqvL3kb+tar1we8VQbdabpyQ5dmbWPwo/IglevMmW3SKo1Et+6rqAeZpXNnPQ==", + "dev": true, + "requires": { + "@volar/language-core": "1.0.11" + } + }, + "@volar/vue-language-core": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/vue-language-core/-/vue-language-core-1.0.11.tgz", + "integrity": "sha512-A3ODs0/ua7BcpSSnE7KtO8bzWsYsbOJRyW2Q/2uktxlfHj8srln3JdgK/mNlIgfnWtACbE5K+EfMJOgJKv864A==", + "dev": true, + "requires": { + "@volar/language-core": "1.0.11", + "@volar/source-map": "1.0.11", + "@vue/compiler-dom": "^3.2.45", + "@vue/compiler-sfc": "^3.2.45", + "@vue/reactivity": "^3.2.45", + "@vue/shared": "^3.2.45", + "minimatch": "^5.1.0", + "vue-template-compiler": "^2.7.14" + } + }, + "@volar/vue-typescript": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@volar/vue-typescript/-/vue-typescript-1.0.11.tgz", + "integrity": "sha512-jlnFPvBcTyPiAbGlgjhKK7fp3Q+Z7Z5eU1NTbTSS0lQC8Gog3sh2UxLAFG5Voe1gHIxasoOEPXzMR0CWF4bKbA==", + "dev": true, + "requires": { + "@volar/typescript": "1.0.11", + "@volar/vue-language-core": "1.0.11" + } + }, + "@vue/compiler-core": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", + "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", + "requires": { + "@babel/parser": "^7.16.4", + "@vue/shared": "3.2.45", + "estree-walker": "^2.0.2", + "source-map": "^0.6.1" + } + }, + "@vue/compiler-dom": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", + "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", + "requires": { + "@vue/compiler-core": "3.2.45", + "@vue/shared": "3.2.45" + } + }, + "@vue/compiler-sfc": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", + "integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", + "requires": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.45", + "@vue/compiler-dom": "3.2.45", + "@vue/compiler-ssr": "3.2.45", + "@vue/reactivity-transform": "3.2.45", + "@vue/shared": "3.2.45", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7", + "postcss": "^8.1.10", + "source-map": "^0.6.1" + } + }, + "@vue/compiler-ssr": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", + "integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", + "requires": { + "@vue/compiler-dom": "3.2.45", + "@vue/shared": "3.2.45" + } + }, + "@vue/devtools-api": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz", + "integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==" + }, + "@vue/reactivity": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz", + "integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==", + "requires": { + "@vue/shared": "3.2.45" + } + }, + "@vue/reactivity-transform": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", + "integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", + "requires": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.45", + "@vue/shared": "3.2.45", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7" + } + }, + "@vue/runtime-core": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz", + "integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==", + "requires": { + "@vue/reactivity": "3.2.45", + "@vue/shared": "3.2.45" + } + }, + "@vue/runtime-dom": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz", + "integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==", + "requires": { + "@vue/runtime-core": "3.2.45", + "@vue/shared": "3.2.45", + "csstype": "^2.6.8" + } + }, + "@vue/server-renderer": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz", + "integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==", + "requires": { + "@vue/compiler-ssr": "3.2.45", + "@vue/shared": "3.2.45" + } + }, + "@vue/shared": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", + "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==" + }, + "@vueuse/core": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.6.0.tgz", + "integrity": "sha512-qGUcjKQXHgN+jqXEgpeZGoxdCbIDCdVPz3QiF1uyecVGbMuM63o96I1GjYx5zskKgRI0FKSNsVWM7rwrRMTf6A==", + "requires": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.6.0", + "@vueuse/shared": "9.6.0", + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "requires": {} + } + } + }, + "@vueuse/metadata": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.6.0.tgz", + "integrity": "sha512-sIC8R+kWkIdpi5X2z2Gk8TRYzmczDwHRhEFfCu2P+XW2JdPoXrziqsGpDDsN7ykBx4ilwieS7JUIweVGhvZ93w==" + }, + "@vueuse/shared": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.6.0.tgz", + "integrity": "sha512-/eDchxYYhkHnFyrb00t90UfjCx94kRHxc7J1GtBCqCG4HyPMX+krV9XJgVtWIsAMaxKVU4fC8NSUviG1JkwhUQ==", + "requires": { + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "requires": {} + } + } + }, + "async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + }, + "dayjs": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.6.tgz", + "integrity": "sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==" + }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "element-plus": { + "version": "2.2.26", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.26.tgz", + "integrity": "sha512-O/rdY5m9DkclpVg8r3GynyqCunm7MxSR142xSsjrZA77bi7bcwA3SIy6SPEDqHi5R4KqgkGYgKSp4Q4e3irbYg==", + "requires": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.0.6", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.3", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + } + }, + "esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "dev": true, + "optional": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "requires": {} + }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "marked": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz", + "integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw==" + }, + "memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, + "minimatch": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", + "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "muggle-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.1.0.tgz", + "integrity": "sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==", + "dev": true + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + }, + "normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "postcss": { + "version": "8.4.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", + "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tauri-plugin-store-api": { + "version": "git+ssh://git@github.com/tauri-apps/tauri-plugin-store.git#9bd993aa67766596638bbfd91e79a1bf8f632014", + "integrity": "sha512-X0cDDcEVLY2X8qCLISgAjzuBKDn7bJkj4S7LnXbEPFbPRe+NzhmFGHSAdFCuQuPzQYjmrVg18mZx9NAg4GBHag==", + "from": "tauri-plugin-store-api@github:tauri-apps/tauri-plugin-store#9bd993aa67766596638bbfd91e79a1bf8f632014", + "requires": { + "@tauri-apps/api": "1.1.0", + "tslib": "2.4.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "typescript": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "dev": true + }, + "vite": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", + "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", + "dev": true, + "requires": { + "esbuild": "^0.15.9", + "fsevents": "~2.3.2", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" + } + }, + "vue": { + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz", + "integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==", + "requires": { + "@vue/compiler-dom": "3.2.45", + "@vue/compiler-sfc": "3.2.45", + "@vue/runtime-dom": "3.2.45", + "@vue/server-renderer": "3.2.45", + "@vue/shared": "3.2.45" + } + }, + "vue-i18n": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz", + "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==", + "requires": { + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2", + "@vue/devtools-api": "^6.2.1" + } + }, + "vue-router": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", + "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==", + "requires": { + "@vue/devtools-api": "^6.4.5" + } + }, + "vue-template-compiler": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", + "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "vue-tsc": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.0.11.tgz", + "integrity": "sha512-lj+6dEroPsE4wmQOPtjCzAf8x363Km5/tuEvMEoQaoRnzs9myBM46FNvCGIIPStYUGuaqF1W1bORmP2KDQEORA==", + "dev": true, + "requires": { + "@volar/vue-language-core": "1.0.11", + "@volar/vue-typescript": "1.0.11" + } + }, + "vuex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz", + "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==", + "requires": { + "@vue/devtools-api": "^6.0.0-beta.11" + } + } } } diff --git a/src-vue/package.json b/src-vue/package.json index fbe3fc1a..04c1904b 100644 --- a/src-vue/package.json +++ b/src-vue/package.json @@ -12,8 +12,9 @@ "@element-plus/icons-vue": "^2.0.9", "element-plus": "^2.2.17", "marked": "^4.1.1", - "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#dev", + "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#9bd993aa67766596638bbfd91e79a1bf8f632014", "vue": "^3.2.37", + "vue-i18n": "^9.2.2", "vue-router": "^4.1.5", "vuex": "^4.0.2" }, diff --git a/src-vue/src/App.vue b/src-vue/src/App.vue index f740bd2f..f80e000d 100644 --- a/src-vue/src/App.vue +++ b/src-vue/src/App.vue @@ -6,7 +6,8 @@ import ModsView from './views/ModsView.vue'; import SettingsView from './views/SettingsView.vue'; import { appWindow } from '@tauri-apps/api/window'; import { store } from './plugins/store'; -import { invoke, window as tauriWindow } from "@tauri-apps/api"; +import { Store } from 'tauri-plugin-store-api'; +import { invoke } from "@tauri-apps/api"; export default { components: { @@ -19,8 +20,18 @@ export default { data() { return {} }, - mounted: () => { + mounted: async function() { store.commit('initialize'); + + // Initialize interface language + const persistentStore = new Store('flight-core-settings.json'); + let lang: string | null = await persistentStore.get('lang'); + if (lang === null) { + lang = navigator.language.substring(0, 2); + persistentStore.set('lang', lang); + await persistentStore.save(); + } + this.$root!.$i18n.locale = lang; }, methods: { async toggleMaximize() { @@ -56,11 +67,11 @@ export default { id="fc__menu_items" data-tauri-drag-region > - <el-menu-item index="/">Play</el-menu-item> - <el-menu-item index="/changelog">Changelog</el-menu-item> - <el-menu-item index="/mods">Mods</el-menu-item> - <el-menu-item index="/settings">Settings</el-menu-item> - <el-menu-item index="/dev" v-if="$store.state.developer_mode">Dev</el-menu-item> + <el-menu-item index="/">{{ $t('menu.play') }}</el-menu-item> + <el-menu-item index="/changelog">{{ $t('menu.changelog') }}</el-menu-item> + <el-menu-item index="/mods">{{ $t('menu.mods') }}</el-menu-item> + <el-menu-item index="/settings">{{ $t('menu.settings') }}</el-menu-item> + <el-menu-item index="/dev" v-if="$store.state.developer_mode">{{ $t('menu.dev') }}</el-menu-item> </el-menu> <!-- Window controls --> @@ -82,8 +93,20 @@ export default { top: 0; width: 100%; height: var(--fc-menu_height); - background-image: radial-gradient(transparent 1px); - backdrop-filter: saturate(50%) blur(4px); +} + +#fc__menu_bar::before { + position: absolute; + content: ""; + inset: 0; /* same as { top: 0; right: 0; bottom: 0; left: 0; } */ + background-image: linear-gradient(to bottom, red, orange); + z-index: 1; + opacity: 0; + transition: opacity 1s linear; +} + +#fc__menu_bar:hover::before { + opacity: 1; } /* Borders reset */ diff --git a/src-vue/src/components/InstallProgressBar.vue b/src-vue/src/components/InstallProgressBar.vue new file mode 100644 index 00000000..d0c2047c --- /dev/null +++ b/src-vue/src/components/InstallProgressBar.vue @@ -0,0 +1,102 @@ +<script lang="ts"> +import { defineComponent } from 'vue'; +import { appWindow } from '@tauri-apps/api/window'; +import { InstallProgress } from '../../../src-tauri/bindings/InstallProgress'; + +export default defineComponent({ + name: 'InstallProgressBar', + computed: { + progressBarStyle(): string { + return !this.install_or_update ? 'hide-progress' : ''; + } + }, + data() { + return { + percentage: 0, + color: '#409EFF', + install_or_update: false, + status: "unknown", + current_downloaded: -1, + total_size: -1, + }; + }, + methods: { + formatBytes(bytes: number, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + const k = 1000; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + }, + formatText() { + if (this.status == "DOWNLOADING") { + const current_downloaded_string = this.formatBytes(this.current_downloaded); + const total_size_string = this.formatBytes(this.total_size); + const status = this.$t("generic.downloading"); + return `${status}: ${current_downloaded_string}/${total_size_string}`; + } + if (this.status == "EXTRACTING") { + return this.$t("generic.extracting"); + } + return "Inactive"; // Needed to keep same size format when progress bar is hidden + } + }, + mounted() { + appWindow.listen<InstallProgress>( + 'northstar-install-download-progress', + ({ event, payload }) => { + this.install_or_update = true; + let progress = payload; + this.status = progress.state; + if (progress.state == "DOWNLOADING") { + this.percentage = ((Number(progress.current_downloaded) / Number(progress.total_size)) * 100); + this.color = '#409EFF'; + this.current_downloaded = Number(progress.current_downloaded); + this.total_size = Number(progress.total_size); + } + if (progress.state == "EXTRACTING") { + this.percentage = 100; + this.color = '#67C23A'; + } + if (progress.state == "DONE") { + // Clear state again + this.install_or_update = false + } + } + ); + } +}); +</script> + +<template> + <el-progress + :class="progressBarStyle" + :format="formatText" + :percentage="percentage" + :color="color" + :indeterminate="status === 'EXTRACTING'" + :duration="1" + > + </el-progress> +</template> + +<style scoped> +.el-progress { + margin-top: 10px; +} + +/* Set progress bar width */ +.el-progress:deep(.el-progress-bar) { + width: 200px; + flex-grow: initial; +} + +.el-progress:deep(.el-progress__text) { + line-height: 1.2; +} + +.hide-progress { + opacity: 0; +} +</style> diff --git a/src-vue/src/components/LanguageSelector.vue b/src-vue/src/components/LanguageSelector.vue new file mode 100644 index 00000000..5b1e7ebd --- /dev/null +++ b/src-vue/src/components/LanguageSelector.vue @@ -0,0 +1,51 @@ +<template> + <el-select v-model="value" class="m-2" + :placeholder="$t('settings.language_select')" size="large" + @change="onChange" + > + <el-option + v-for="item in options" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import { Store } from 'tauri-plugin-store-api'; +const persistentStore = new Store('flight-core-settings.json'); + +export default defineComponent({ + name: 'LanguageSelector', + data: () => ({ + value: '', + options: [ + { + value: 'en', + label: 'English' + }, + { + value: 'fr', + label: 'Français' + }, + { + value: 'de', + label: 'Deutsch' + }, + ] + }), + mounted: async function() { + const lang: string = await persistentStore.get('lang') as string; + this.value = lang; + }, + methods: { + async onChange(value: string) { + this.$root!.$i18n.locale = value; + persistentStore.set('lang', value); + await persistentStore.save(); + } + } +}) +</script> diff --git a/src-vue/src/components/ModsMenu.vue b/src-vue/src/components/ModsMenu.vue index 9b62fcfa..656c05a6 100644 --- a/src-vue/src/components/ModsMenu.vue +++ b/src-vue/src/components/ModsMenu.vue @@ -7,20 +7,20 @@ <h5>Mods</h5> <el-menu-item index="1" @click="$emit('showLocalMods', true)"> <el-icon><Folder /></el-icon> - <span>Local</span> + <span>{{ $t('mods.menu.local') }}</span> </el-menu-item> <el-menu-item index="2" @click="$emit('showLocalMods', false)"> <el-icon><Connection /></el-icon> - <span>Online</span> + <span>{{ $t('mods.menu.online') }}</span> </el-menu-item> <!-- Search inputs --> - <h5>Filter</h5> - <el-input v-model="$store.state.search.searchValue" placeholder="Search" clearable /> + <h5>{{ $t('mods.menu.filter') }}</h5> + <el-input v-model="$store.state.search.searchValue" :placeholder="$t('mods.menu.search')" clearable /> <el-select v-if="!showingLocalMods" v-model="$store.state.search.sortValue" - placeholder="Sort mods" + :placeholder="$t('mods.menu.sort_mods')" > <el-option v-for="item of sortValues" @@ -33,7 +33,7 @@ v-if="!showingLocalMods" v-model="$store.state.search.selectedCategories" multiple - placeholder="Select categories" + :placeholder="$t('mods.menu.select_categories')" > <el-option v-for="item in $store.state.thunderstoreModsCategories" @@ -66,7 +66,7 @@ export default defineComponent({ sortValues(): {label: string, value: string}[] { return Object.keys(SortOptions).map((key: string) => ({ value: key, - label: Object.values(SortOptions)[Object.keys(SortOptions).indexOf(key)] + label: this.$t('mods.menu.sort.' + Object.values(SortOptions)[Object.keys(SortOptions).indexOf(key)]) })); } } diff --git a/src-vue/src/components/PlayButton.vue b/src-vue/src/components/PlayButton.vue index 687f12a4..208b4703 100644 --- a/src-vue/src/components/PlayButton.vue +++ b/src-vue/src/components/PlayButton.vue @@ -18,22 +18,22 @@ export default defineComponent({ }, playButtonLabel(): string { if (this.$store.state.northstar_is_running) { - return "Game is running"; + return this.$t("play.button.northstar_is_running"); } switch(this.$store.state.northstar_state) { case NorthstarState.GAME_NOT_FOUND: - return "Select Titanfall2 game folder"; + return this.$t("play.button.select_game_dir"); case NorthstarState.INSTALL: - return "Install"; + return this.$t("play.button.install"); case NorthstarState.INSTALLING: - return "Installing..." + return this.$t("play.button.installing"); case NorthstarState.MUST_UPDATE: - return "Update"; + return this.$t("play.button.update"); case NorthstarState.UPDATING: - return "Updating..."; + return this.$t("play.button.updating"); case NorthstarState.READY_TO_PLAY: - return "Launch game"; + return this.$t("play.button.ready_to_play"); default: return ""; @@ -57,7 +57,7 @@ export default defineComponent({ options: [ { value: ReleaseCanal.RELEASE_CANDIDATE, - label: 'Northstar release candidate', + label: this.$t('channels.names.NorthstarReleaseCandidate'), }, ] }, @@ -83,10 +83,10 @@ export default defineComponent({ return this.showReleaseSwitch ? 'border-radius: 2px 0 0 2px;' : 'border-radius: 2px'; - } + }, }, methods: { - launchGame() { + async launchGame() { this.$store.commit('launchGame'); } } @@ -94,30 +94,31 @@ export default defineComponent({ </script> <template> - <el-button :disabled="northstarIsRunning" - type="primary" size="large" @click="launchGame" - class="fc_launch__button" :style="buttonRadiusStyle"> - {{ playButtonLabel }} - </el-button> - <el-select v-if="showReleaseSwitch" :disabled="northstarIsRunning" - v-model="currentCanal" placeholder="Select"> - <el-option-group - v-for="group in selectOptions" - :key="group.label" - :label="group.label" - > - <el-option - v-for="item in group.options" - :key="item.value" - :label="item.label" - :value="item.value" - /> - </el-option-group> - </el-select> + <nav> + <el-button :disabled="northstarIsRunning" + type="primary" size="large" @click="launchGame" + class="fc_launch__button" :style="buttonRadiusStyle"> + {{ playButtonLabel }} + </el-button> + <el-select v-if="showReleaseSwitch" :disabled="northstarIsRunning" + v-model="currentCanal" placeholder="Select"> + <el-option-group + v-for="group in selectOptions" + :key="group.label" + :label="group.label" + > + <el-option + v-for="item in group.options" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-option-group> + </el-select> + </nav> </template> <style scoped> - button { text-transform: uppercase; padding: 30px; @@ -130,7 +131,6 @@ button { } /* Release canal selector */ - .el-select { width: 0; margin-right: 50px; diff --git a/src-vue/src/components/PullRequestsSelector.vue b/src-vue/src/components/PullRequestsSelector.vue index 58a355f4..ba95624a 100644 --- a/src-vue/src/components/PullRequestsSelector.vue +++ b/src-vue/src/components/PullRequestsSelector.vue @@ -1,7 +1,12 @@ <template> <div> <el-collapse @change="onChange"> - <el-collapse-item title="Launcher PRs" name="1"> + <el-collapse-item name="1"> + <template #title> + Launcher PRs + <el-input class="pr_search_input" v-model="launcherSearch" placeholder="Filter pull requests" @click.stop="() => false"></el-input> + </template> + <p v-if="pull_requests_launcher.length === 0"> <el-progress :show-text="false" @@ -12,16 +17,28 @@ style="margin: 15px" /> </p> - <el-card v-else shadow="hover" v-for="pull_request in pull_requests_launcher" - v-bind:key="pull_request.url"> + <el-card + v-else-if="filtered_launcher_pull_requests.length !== 0" + shadow="hover" + v-for="pull_request in filtered_launcher_pull_requests" + v-bind:key="pull_request.url" + > <el-button type="primary" @click="installLauncherPR(pull_request)">Install</el-button> + <el-button type="primary" @click="downloadLauncherPR(pull_request)">Download</el-button> <a target="_blank" :href="pull_request.html_url"> {{ pull_request.number }}: {{ pull_request.title }} </a> </el-card> + <div v-else class="no_matching_pr"> + No matching PR found. + </div> </el-collapse-item> - <el-collapse-item title="Mods PRs" name="2"> + <el-collapse-item name="2"> + <template #title> + Mods PRs + <el-input class="pr_search_input" v-model="modsSearch" placeholder="Filter pull requests" @click.stop="() => false"></el-input> + </template> <div style="margin: 15px"> <el-alert title="Warning" type="warning" :closable="false" show-icon> Mod PRs are installed into a separate profile. Make sure to launch via @@ -39,12 +56,21 @@ style="margin: 15px" /> </p> - <el-card v-else shadow="hover" v-for="pull_request in pull_requests_mods" v-bind:key="pull_request.url"> + <el-card + v-else-if="filtered_mods_pull_requests.length !== 0" + shadow="hover" + v-for="pull_request in filtered_mods_pull_requests" + v-bind:key="pull_request.url" + > <el-button type="primary" @click="installModsPR(pull_request)">Install</el-button> + <el-button type="primary" @click="downloadModsPR(pull_request)">Download</el-button> <a target="_blank" :href="pull_request.html_url"> {{ pull_request.number }}: {{ pull_request.title }} </a> </el-card> + <div v-else class="no_matching_pr"> + No matching PR found. + </div> </el-collapse-item> </el-collapse> </div> @@ -54,11 +80,13 @@ import { defineComponent } from 'vue' import { PullRequestType } from '../../../src-tauri/bindings/PullRequestType'; import { PullsApiResponseElement } from '../../../src-tauri/bindings/PullsApiResponseElement'; -import { invoke } from "@tauri-apps/api"; -import { ElNotification } from "element-plus"; export default defineComponent({ name: 'PullRequestsSelector', + data: () => ({ + launcherSearch: '', + modsSearch: '' + }), computed: { pull_requests_launcher(): PullsApiResponseElement[] { return this.$store.state.pullrequests.pull_requests_launcher; @@ -66,20 +94,49 @@ export default defineComponent({ pull_requests_mods(): PullsApiResponseElement[] { return this.$store.state.pullrequests.pull_requests_mods; }, + + filtered_launcher_pull_requests(): PullsApiResponseElement[] { + if (this.launcherSearch.length === 0) { + return this.pull_requests_launcher; + } + + return this.pull_requests_launcher.filter(pr => + // Check PR id + pr.number.toString().indexOf(this.launcherSearch) !== -1 + // Check PR title + || pr.title.toLowerCase().indexOf(this.launcherSearch.toLowerCase()) !== -1); + }, + filtered_mods_pull_requests(): PullsApiResponseElement[] { + if (this.modsSearch.length === 0) { + return this.pull_requests_mods; + } + + return this.pull_requests_mods.filter(pr => + // Check PR id + pr.number.toString().indexOf(this.modsSearch) !== -1 + // Check PR title + || pr.title.toLowerCase().indexOf(this.modsSearch.toLowerCase()) !== -1); + }, }, methods: { onChange(e: Object) { const openedCollapseNames = Object.values(e); if (openedCollapseNames.includes('1') && this.pull_requests_launcher.length === 0) { - this.getPullRequests('LAUNCHER'); + this.getPullRequests('Launcher'); } if (openedCollapseNames.includes('2') && this.pull_requests_mods.length === 0) { - this.getPullRequests('MODS'); + this.getPullRequests('Mods'); } }, async getPullRequests(pull_request_type: PullRequestType) { this.$store.commit('getPullRequests', pull_request_type); }, + async downloadLauncherPR(pull_request: PullsApiResponseElement) { + this.$store.commit('downloadLauncherPR', pull_request); + }, + async downloadModsPR(pull_request: PullsApiResponseElement) { + this.$store.commit('downloadModsPR', pull_request); + }, async installLauncherPR(pull_request: PullsApiResponseElement) { this.$store.commit('installLauncherPR', pull_request); }, @@ -100,4 +157,18 @@ export default defineComponent({ padding-left: 10px; font-size: 14px; } + +.el-collapse:deep(.el-collapse-item__arrow) { + margin: 0 8px; +} + +.pr_search_input { + width: 200px; + margin: 0 0 0 auto; +} + +.no_matching_pr { + margin: 0 auto; + width: max-content; +} </style> diff --git a/src-vue/src/components/ThunderstoreModCard.vue b/src-vue/src/components/ThunderstoreModCard.vue index c9f6768c..fec95f14 100644 --- a/src-vue/src/components/ThunderstoreModCard.vue +++ b/src-vue/src/components/ThunderstoreModCard.vue @@ -21,7 +21,7 @@ <br/> <div class="name hide-text-overflow">{{ mod.name }}</div> - <div class="author hide-text-overflow">by {{ mod.owner }}</div> + <div class="author hide-text-overflow">{{ $t('mods.card.by') }} {{ mod.owner }}</div> <div class="desc"> {{ latestVersion.description }} </div> @@ -33,7 +33,7 @@ :loading="isBeingInstalled || isBeingUpdated" @click.stop="installMod(mod)" > - {{ modButtonText }} + {{ $t(modButtonText) }} </el-button> <!-- Information dropdown menu --> @@ -51,10 +51,10 @@ <template #dropdown> <el-dropdown-menu> <el-dropdown-item @click="openURL(mod.package_url)"> - More info + {{ $t('mods.card.more_info') }} </el-dropdown-item> <el-dropdown-item @click="deleteMod(mod)"> - Remove mod + {{ $t('mods.card.remove') }} </el-dropdown-item> </el-dropdown-menu> </template> @@ -72,9 +72,9 @@ import {invoke, shell} from "@tauri-apps/api"; import {ThunderstoreModStatus} from "../utils/thunderstore/ThunderstoreModStatus"; import {NorthstarMod} from "../../../src-tauri/bindings/NorthstarMod"; import {GameInstall} from "../utils/GameInstall"; -import {ElNotification} from "element-plus"; import { NorthstarState } from "../utils/NorthstarState"; import { ElMessageBox } from "element-plus"; +import { showErrorNotification, showNotification } from "../utils/ui"; export default defineComponent({ name: "ThunderstoreModCard", @@ -129,15 +129,15 @@ export default defineComponent({ modButtonText(): string { switch (this.modStatus) { case ThunderstoreModStatus.BEING_INSTALLED: - return "Installing..."; + return "mods.card.button.being_installed"; case ThunderstoreModStatus.BEING_UPDATED: - return "Updating..."; + return "mods.card.button.being_updated"; case ThunderstoreModStatus.INSTALLED: - return "Installed"; + return "mods.card.button.installed"; case ThunderstoreModStatus.NOT_INSTALLED: - return "Install"; + return "mods.card.button.install"; case ThunderstoreModStatus.OUTDATED: - return "Update"; + return "mods.card.button.outdated"; } }, @@ -200,11 +200,11 @@ export default defineComponent({ // Show pop-up to confirm delete ElMessageBox.confirm( - 'Delete Thunderstore mod?', - 'Warning', + this.$t('mods.card.remove_dialog_text'), + this.$t('mods.card.remove_dialog_title'), { - confirmButtonText: 'OK', - cancelButtonText: 'Cancel', + confirmButtonText: this.$t('generic.yes'), + cancelButtonText: this.$t('generic.cancel'), type: 'warning', } ) @@ -214,22 +214,12 @@ export default defineComponent({ install_type: this.$store.state.install_type } as GameInstall; - await invoke("delete_thunderstore_mod", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }) + await invoke<string>("delete_thunderstore_mod", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }) .then((message) => { - ElNotification({ - title: `Removed ${mod.name}`, - message: message as string, - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('mods.card.remove_success', {modName: mod.name}), message); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { this.$store.commit('loadInstalledMods'); @@ -253,21 +243,11 @@ export default defineComponent({ this.isBeingInstalled = true; } - await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }).then((message) => { - ElNotification({ - title: `Installed ${mod.name}`, - message: message as string, - type: 'success', - position: 'bottom-right' - }); + await invoke<string>("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }).then((message) => { + showNotification(this.$t('mods.card.install_success', {modName: mod.name}), message); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { this.isBeingInstalled = false; diff --git a/src-vue/src/i18n/lang/de.json b/src-vue/src/i18n/lang/de.json new file mode 100644 index 00000000..1955b717 --- /dev/null +++ b/src-vue/src/i18n/lang/de.json @@ -0,0 +1,164 @@ +{ + "menu": { + "play": "Spielen", + "changelog": "Versionhistorie", + "mods": "Mods", + "settings": "Einstellungen", + "dev": "Dev" + }, + + "generic": { + "yes": "Ja", + "no": "Nein", + "error": "Fehler", + "cancel": "Abbrechen", + "informationShort": "Info", + "downloading": "Herunterladen", + "extracting": "Extrahieren", + "done": "Fertig", + "success": "Erfolg" + }, + + "play": { + "button": { + "northstar_is_running": "Spiel läuft", + "select_game_dir": "Titanfall2 ordner wählen", + "install": "Installieren", + "installing": "Installiert...", + "update": "Aktualisieren", + "updating": "Aktualisiert...", + "ready_to_play": "Spiel starten" + }, + + "unknown_version": "Unbekannte Version", + "see_patch_notes": "Siehe Patch-Notizen", + "players": "Spieler", + "servers": "Server", + "unable_to_load_playercount": "Spielerzahl konnte nicht geladen werden", + "northstar_running": "Northstar läuft:", + "origin_running": "Origin läuft:" + }, + + "mods": { + "local": { + "no_mods": "Keine Mods gefunden.", + "delete_confirm": "Bist du dir sicher, dass du diesen Mod löschen möchtest?", + "delete": "Löschen", + "part_of_ts_mod": "Dieser Northstar Mod ist teil eines Thunderstore Mods", + "success_deleting": "{modName} erfolgreich gelöscht" + }, + + "online": { + "no_match": "Keine passenden Mods gefunden.", + "try_another_search": "Versuche eine andere Suchanfrage!" + }, + + "menu": { + "local": "Lokal", + "online": "Online", + "filter": "Filter", + "search": "Suche", + "sort_mods": "Mods sortieren", + "select_categories": "Kategorien wählen", + + "sort": { + "name_asc": "Nach Name (A to Z)", + "name_desc": "Nach Name (Z to A)", + "date_asc": "Nach Datum (älteste zuerst)", + "date_desc": "Nach Datum (neuste zuerst)", + "most_downloaded": "Am meisten heruntergeladen", + "top_rated": "Am besten bewerted" + } + }, + + "card": { + "button": { + "being_installed": "Installiert...", + "being_updated": "Aktualisiert...", + "installed": "Installiert", + "install": "Installieren", + "outdated": "Aktualisieren" + }, + + "by": "von", + "more_info": "Mehr Info", + "remove": "Mod entfernen", + "remove_dialog_title": "Warnung", + "remove_dialog_text": "Thunderstore Mod entfernen?", + "remove_success": "{modName} entfernt", + "install_success": "{modName} installiert" + } + }, + + "settings": { + "manage_install": "Installation verwalten", + "choose_folder": "Installationsordner wählen", + "nb_ts_mods_per_page": "Anzahl an Thunderstore Mods pro Seite", + "nb_ts_mods_per_page_desc1": "Ändern dieser Einstellung kann die Leistung beim Suchen von Thunderstore Mods beeinflussen.", + "nb_ts_mods_per_page_desc2": "Setze diesen Wert auf 0 um alle Mods auf einer einzelnen Seite anzuzeigen", + "nb_ts_mods_reset": "Standard wiederherstellen", + "language": "Sprache", + "language_select": "Bevorzugte Sprache wählen", + "about": "Über:", + "flightcore_version": "FlightCore Version:", + "testing": "Testen:", + "enable_test_channels": "Testversionen aktivieren", + "dev_mode_enabled_title": "Vorsicht!", + "dev_mod_enabled_text": "Entwicklermodus aktiviert.", + + "repair": { + "title": "Reparieren", + "open_window": "Reparierfenster öffnen", + + "window": { + "title": "FlightCore Reparierfenster", + "warning": "Dieses Fenster enthält verschiedene Funktionen um gängige Probleme mit Northstar und FlightCore zu beheben.", + "disable_all_but_core": "Alle außer notwendige Mods deaktivieren", + "disable_all_but_core_success": "Alle außer notwendige Mods wurden deaktiviert", + "force_reinstall_ns": "Northstar reinstallieren", + "force_delete_temp_dl": "Temporären FlightCore Downloadordner löschen", + "delete_persistent_store": "FlightCore Einstellungen zurücksetzen", + "reinstall_title": "Northstar wird neu installiert", + "reinstall_text": "Bitte warten", + "reinstall_success": "Northstar erfolgreich neu installiert" + } + } + }, + + "notification": { + "game_folder": { + "new": { + "title": "Neuer Spielordner", + "text": "Spielordner erfolgreich aktualisiert." + }, + + "wrong": { + "title": "Falscher Ordner", + "text": "Der gewählte Ordner enthält keine valide Titanfall2 Installation." + }, + + "not_found": { + "title": "Titanfall2 nicht gefunden!", + "text": "Bitte wähle den Installationsordner manuell aus" + } + }, + + "flightcore_outdated": { + "title": "FlightCore veraltet!", + "text": "Bitte aktualisiere FlightCore.\nDu hast die veraltetet Version {oldVersion}.\nNeuste Version ist {newVersion}!" + } + }, + + "channels": { + "release": { + "switch": { + "text": "Releasekanal zu \"{canal}\" gewechselt." + } + }, + + "names": { + "Northstar": "Northstar", + "NorthstarReleaseCandidate": "Northstar Release Candidate" + } + } +} diff --git a/src-vue/src/i18n/lang/en.json b/src-vue/src/i18n/lang/en.json new file mode 100644 index 00000000..407b69d1 --- /dev/null +++ b/src-vue/src/i18n/lang/en.json @@ -0,0 +1,164 @@ +{ + "menu": { + "play": "Play", + "changelog": "Changelog", + "mods": "Mods", + "settings": "Settings", + "dev": "Dev" + }, + + "generic": { + "yes": "Yes", + "no": "No", + "error": "Error", + "cancel": "Cancel", + "informationShort": "Info", + "downloading": "Downloading", + "extracting": "Extracting", + "done": "Done", + "success": "Success" + }, + + "play": { + "button": { + "northstar_is_running": "Game is running", + "select_game_dir": "Select Titanfall2 game folder", + "install": "Install", + "installing": "Installing...", + "update": "Update", + "updating": "Updating...", + "ready_to_play": "Launch game" + }, + + "unknown_version": "Unknown version", + "see_patch_notes": "see patch notes", + "players": "players", + "servers": "servers", + "unable_to_load_playercount": "Unable to load playercount", + "northstar_running": "Northstar is running:", + "origin_running": "Origin is running:" + }, + + "mods": { + "local": { + "no_mods": "No mods were found.", + "delete_confirm": "Are you sure to delete this mod?", + "delete": "Delete", + "part_of_ts_mod": "This Northstar mod is part of a Thunderstore mod", + "success_deleting": "Success deleting {modName}" + }, + + "online": { + "no_match": "No matching mod has been found.", + "try_another_search": "Try another search!" + }, + + "menu": { + "local": "Local", + "online": "Online", + "filter": "Filter", + "search": "Search", + "sort_mods": "Sort mods", + "select_categories": "Select categories", + + "sort": { + "name_asc": "By name (A to Z)", + "name_desc": "By name (Z to A)", + "date_asc": "By date (from oldest)", + "date_desc": "By date (from newest)", + "most_downloaded": "Most downloaded", + "top_rated": "Top rated" + } + }, + + "card": { + "button": { + "being_installed": "Installing...", + "being_updated": "Updating...", + "installed": "Installed", + "install": "Install", + "outdated": "Update" + }, + + "by": "by", + "more_info": "More info", + "remove": "Remove mod", + "remove_dialog_title": "Warning", + "remove_dialog_text": "Delete Thunderstore mod?", + "remove_success": "Removed {modName}", + "install_success": "Installed {modName}" + } + }, + + "settings": { + "manage_install": "Manage installation", + "choose_folder": "Choose installation folder", + "nb_ts_mods_per_page": "Number of Thunderstore mods per page", + "nb_ts_mods_per_page_desc1": "This has an impact on display performances when browsing Thunderstore mods.", + "nb_ts_mods_per_page_desc2": "Set this value to 0 to disable pagination.", + "nb_ts_mods_reset": "Reset to default", + "language": "Language", + "language_select": "Select your favorite language", + "about": "About:", + "flightcore_version": "FlightCore version:", + "testing": "Testing:", + "enable_test_channels": "Enable testing release channels", + "dev_mode_enabled_title": "Watch out!", + "dev_mode_enabled_text": "Developer mode enabled.", + + "repair": { + "title": "Repair", + "open_window": "Open repair window", + + "window": { + "title": "FlightCore repair window", + "warning": "This window contains various functionality to repair common issues with Northstar and FlightCore.", + "disable_all_but_core": "Disable all but core mods", + "disable_all_but_core_success": "Disabled all mods but core", + "force_reinstall_ns": "Force reinstall Northstar", + "force_delete_temp_dl": "Force delete temp download folder", + "delete_persistent_store": "Delete FlightCore persistent store", + "reinstall_title": "Force reinstalling Northstar", + "reinstall_text": "Please wait", + "reinstall_success": "Successfully reinstalled Northstar" + } + } + }, + + "notification": { + "game_folder": { + "new": { + "title": "New game folder", + "text": "Game folder was successfully updated." + }, + + "wrong": { + "title": "Wrong folder", + "text": "Selected folder is not a valid Titanfall2 install." + }, + + "not_found": { + "title": "Titanfall2 not found!", + "text": "Please manually select install location" + } + }, + + "flightcore_outdated": { + "title": "FlightCore outdated!", + "text": "Please update FlightCore.\nRunning outdated version {oldVersion}.\nNewest is {newVersion}!" + } + }, + + "channels": { + "release": { + "switch": { + "text": "Switched release channel to \"{canal}\"." + } + }, + + "names": { + "Northstar": "Northstar", + "NorthstarReleaseCandidate": "Northstar release candidate" + } + } +} diff --git a/src-vue/src/i18n/lang/fr.json b/src-vue/src/i18n/lang/fr.json new file mode 100644 index 00000000..aae11bb7 --- /dev/null +++ b/src-vue/src/i18n/lang/fr.json @@ -0,0 +1,164 @@ +{ + "menu": { + "play": "Jouer", + "changelog": "Notes", + "mods": "Mods", + "settings": "Paramètres", + "dev": "Dev" + }, + + "generic": { + "yes": "Oui", + "no": "Non", + "error": "Erreur", + "cancel": "Annuler", + "informationShort": "Info", + "downloading": "Téléchargement", + "extracting": "Extraction", + "done": "Fait", + "success": "Succès" + }, + + "play": { + "button": { + "northstar_is_running": "En cours d'utilisation", + "select_game_dir": "Sélectionner le dossier du jeu", + "install": "Installer", + "installing": "Installation...", + "update": "Mettre à jour", + "updating": "Mise à jour...", + "ready_to_play": "Jouer" + }, + + "unknown_version": "Version inconnue", + "see_patch_notes": "voir les notes de version", + "players": "joueurs", + "servers": "serveurs", + "unable_to_load_playercount": "Impossible de charger les statistiques", + "northstar_running": "Northstar est en cours d'exécution :", + "origin_running": "Origin est en cours d'exécution :" + }, + + "mods": { + "local": { + "no_mods": "Aucun mod trouvé.", + "delete_confirm": "Êtes-vous certain de vouloir supprimer ce mod ?", + "delete": "Supprimer", + "part_of_ts_mod": "Ce mod Northstar fait partie d'un mod Thunderstore", + "success_deleting": "Succès de la suppression de {modName}" + }, + + "online": { + "no_match": "Aucun mod correspondant n'a été trouvé.", + "try_another_search": "Essayez une autre recherche !" + }, + + "menu": { + "local": "Local", + "online": "En ligne", + "filter": "Filtrer", + "search": "Chercher", + "sort_mods": "Trier les mods", + "select_categories": "Choisir les catégories", + + "sort": { + "name_asc": "Par nom (de A à Z)", + "name_desc": "Par nom (de Z à A)", + "date_asc": "Par date (du plus vieux)", + "date_desc": "Par date (du plus récent)", + "most_downloaded": "Plus téléchargés", + "top_rated": "Mieux notés" + } + }, + + "card": { + "button": { + "being_installed": "Installation...", + "being_updated": "Mise à jour...", + "installed": "Installé", + "install": "Installer", + "outdated": "Mettre à jour" + }, + + "by": "par", + "more_info": "Plus d'informations", + "remove": "Supprimer le mod", + "remove_dialog_title": "Attention !", + "remove_dialog_text": "Voulez-vous vraiment supprimer ce mod Thunderstore ?", + "remove_success": "{modName} supprimé", + "install_success": "{modName} installé" + } + }, + + "settings": { + "manage_install": "Gérer l'installation", + "choose_folder": "Choisir le dossier d'installation du jeu", + "nb_ts_mods_per_page": "Nombre de mods Thunderstore par page", + "nb_ts_mods_per_page_desc1": "Ce paramètre a un impact sur les performances d'affichage des mods Thunderstore.", + "nb_ts_mods_per_page_desc2": "Réglez-le sur 0 pour désactiver la pagination.", + "nb_ts_mods_reset": "Valeur par défaut", + "language": "Langue", + "language_select": "Sélectionnez votre langue", + "about": "À propos", + "flightcore_version": "Version de FlightCore :", + "testing": "Tests :", + "enable_test_channels": "Activer le test de versions de pré-production", + "dev_mode_enabled_title": "Attention !", + "dev_mode_enabled_text": "Mode développeur activé.", + + "repair": { + "title": "Dépannage", + "open_window": "Ouvrir la fenêtre de dépannage", + + "window": { + "title": "Fenêtre de dépannage FlightCore", + "warning": "Cette fenêtre contient plusieurs fonctionnalité de résolution de problèmes courants avec Northstar et FlightCore.", + "disable_all_but_core": "Désactiver tous les mods (sauf ceux de Northstar)", + "disable_all_but_core_success": "Tous les mods sauf ceux de Northstar ont été désactivés", + "force_reinstall_ns": "Forcer la réinstallation de Northstar", + "force_delete_temp_dl": "Supprimer le dossier de téléchargement temporaire", + "delete_persistent_store": "Supprimer l'espace de stockage local de FlightCore", + "reinstall_title": "Forcer la réinstallation de Northstar", + "reinstall_text": "Veuillez patienter", + "reinstall_success": "Northstar réinstallé avec succès" + } + } + }, + + "notification": { + "game_folder": { + "new": { + "title": "Nouveau dossier", + "text": "Le dossier du jeu a bien été mis à jour." + }, + + "wrong": { + "title": "Mauvais dossier", + "text": "Le dossier sélectionné ne contient pas d'installation de Titanfall2." + }, + + "not_found": { + "title": "Titanfall2 non trouvé", + "text": "Veuillez sélectionner manuellement le dossier du jeu." + } + }, + + "flightcore_outdated": { + "title": "Mise à jour disponible !", + "text": "Veuillez mettre à jour FlightCore.\nVersion actuelle : {oldVersion}.\nNouvelle version : {newVersion}." + } + }, + + "channels": { + "release": { + "switch": { + "text": "Le canal de téléchargement a été réglé sur \"{canal}\"." + } + }, + + "names": { + "Northstar": "Northstar", + "NorthstarReleaseCandidate": "Version de pré-release" + } + } +} diff --git a/src-vue/src/main.ts b/src-vue/src/main.ts index 57d41865..94a0196b 100644 --- a/src-vue/src/main.ts +++ b/src-vue/src/main.ts @@ -1,4 +1,5 @@ import { createApp } from 'vue' +import { createI18n } from "vue-i18n"; import App from './App.vue' import ElementPlus from "element-plus"; import * as ElementPlusIconsVue from '@element-plus/icons-vue' @@ -10,10 +11,22 @@ import SettingsView from "./views/SettingsView.vue"; import DeveloperView from "./views/DeveloperView.vue"; import RepairView from "./views/RepairView.vue"; import {createRouter, createWebHashHistory} from "vue-router"; +import en from "./i18n/lang/en.json"; +import fr from "./i18n/lang/fr.json"; +import de from "./i18n/lang/de.json"; const app = createApp(App); +// internationalization +export const i18n = createI18n({ + locale: 'en', + fallbackLocale: 'en', + messages: { + en, fr, de + } +}); +app.use(i18n); // styles import 'element-plus/theme-chalk/index.css'; diff --git a/src-vue/src/plugins/modules/pull_requests.ts b/src-vue/src/plugins/modules/pull_requests.ts index 573856ad..ff9ec322 100644 --- a/src-vue/src/plugins/modules/pull_requests.ts +++ b/src-vue/src/plugins/modules/pull_requests.ts @@ -1,8 +1,8 @@ -import { ElNotification } from "element-plus"; -import { invoke } from "@tauri-apps/api"; +import { invoke, shell } from "@tauri-apps/api"; import { PullsApiResponseElement } from "../../../../src-tauri/bindings/PullsApiResponseElement"; import { PullRequestType } from '../../../../src-tauri/bindings/PullRequestType'; import { store } from "../store"; +import { showErrorNotification, showNotification } from "../../utils/ui"; interface PullRequestStoreState { searchValue: string, @@ -20,11 +20,11 @@ export const pullRequestModule = { await invoke<PullsApiResponseElement[]>("get_pull_requests_wrapper", { installType: pull_request_type }) .then((message) => { switch (pull_request_type) { - case "MODS": + case "Mods": state.pull_requests_mods = message; break; - case "LAUNCHER": + case "Launcher": state.pull_requests_launcher = message; break; @@ -33,42 +33,35 @@ export const pullRequestModule = { } }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, + async downloadLauncherPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) { + await invoke<string>("get_launcher_download_link", { pullRequest: pull_request }) + .then((url) => { + // Open URL in default HTTPS handler (i.e. default browser) + shell.open(url); + }) + .catch((error) => { + showErrorNotification(error); + }); + }, + async downloadModsPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) { + let url = `https://github.com/${pull_request.head.repo.full_name}/archive/refs/heads/${pull_request.head.ref}.zip` + shell.open(url); + }, async installLauncherPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) { // Send notification telling the user to wait for the process to finish - const notification = ElNotification({ - title: `Installing launcher PR ${pull_request.number}`, - message: 'Please wait', - duration: 0, - type: 'info', - position: 'bottom-right' - }); + const notification = showNotification(`Installing launcher PR ${pull_request.number}`, 'Please wait', 'info', 0); await invoke("apply_launcher_pr", { pullRequest: pull_request, gameInstallPath: store.state.game_path }) .then((message) => { console.log(message); // Show user notification if mod install completed. - ElNotification({ - title: `Done`, - message: `Installed ${pull_request.number}: "${pull_request.title}"`, - type: 'success', - position: 'bottom-right' - }); + showNotification(`Done`, `Installed ${pull_request.number}: "${pull_request.title}"`); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { // Clear old notification @@ -77,32 +70,20 @@ export const pullRequestModule = { }, async installModsPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) { // Send notification telling the user to wait for the process to finish - const notification = ElNotification({ - title: `Installing mods PR ${pull_request.number}`, - message: 'Please wait', - duration: 0, - type: 'info', - position: 'bottom-right' - }); + const notification = showNotification(`Installing mods PR ${pull_request.number}`, 'Please wait', 'info', 0); await invoke("apply_mods_pr", { pullRequest: pull_request, gameInstallPath: store.state.game_path }) .then((message) => { // Show user notification if mod install completed. - ElNotification({ - title: `Done`, - message: `Installed ${pull_request.number}: "${pull_request.title}"\nMake sure to launch via batch file or by specifying correct profile!`, - type: 'success', - duration: 7_000, // in ms - position: 'bottom-right' - }); + showNotification( + `Done`, + `Installed ${pull_request.number}: "${pull_request.title}"\nMake sure to launch via batch file or by specifying correct profile!`, + 'success', + 7000 + ); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { // Clear old notification diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index 8671d12b..00b8f35a 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -6,7 +6,7 @@ import { invoke } from "@tauri-apps/api"; import { GameInstall } from "../utils/GameInstall"; import { ReleaseCanal } from "../utils/ReleaseCanal"; import { FlightCoreVersion } from "../../../src-tauri/bindings/FlightCoreVersion"; -import { ElNotification, NotificationHandle } from 'element-plus'; +import { NotificationHandle } from 'element-plus'; import { NorthstarState } from '../utils/NorthstarState'; import { appDir } from '@tauri-apps/api/path'; import { open } from '@tauri-apps/api/dialog'; @@ -16,8 +16,9 @@ import { ReleaseInfo } from "../../../src-tauri/bindings/ReleaseInfo"; import { ThunderstoreMod } from "../../../src-tauri/bindings/ThunderstoreMod"; import { NorthstarMod } from "../../../src-tauri/bindings/NorthstarMod"; import { searchModule } from './modules/search'; +import { i18n } from '../main'; import { pullRequestModule } from './modules/pull_requests'; -import { PullsApiResponseElement } from "../../../src-tauri/bindings/PullsApiResponseElement"; +import { showErrorNotification, showNotification } from '../utils/ui'; const persistentStore = new Store('flight-core-settings.json'); @@ -51,6 +52,7 @@ export interface FlightCoreStore { let notification_handle: NotificationHandle; + export const store = createStore<FlightCoreStore>({ modules: { search: searchModule, @@ -87,13 +89,23 @@ export const store = createStore<FlightCoreStore>({ checkNorthstarUpdates(state) { _get_northstar_version_number(state); }, - toggleDeveloperMode(state) { + async toggleDebugMode(_state) { + let menu_bar_handle = document.querySelector('#fc_menu-bar'); + if (menu_bar_handle !== null) { + menu_bar_handle.classList.toggle('developer_build'); + } + }, + async toggleDeveloperMode(state) { state.developer_mode = !state.developer_mode; // Reset tab when closing dev mode. if (!state.developer_mode) { store.commit('updateCurrentTab', Tabs.PLAY); } + + // Save dev mode state in persistent store + await persistentStore.set('dev_mode', state.developer_mode); + await persistentStore.save(); }, initialize(state) { _initializeApp(state); @@ -122,12 +134,10 @@ export const store = createStore<FlightCoreStore>({ let is_valid_titanfall2_install = await invoke("verify_install_location", { gamePath: selected }) as boolean; if (is_valid_titanfall2_install) { state.game_path = selected; - ElNotification({ - title: 'New game folder', - message: "Game folder was successfully updated.", - type: 'success', - position: 'bottom-right' - }); + showNotification( + i18n.global.tc('notification.game_folder.new.title'), + i18n.global.tc('notification.game_folder.new.text') + ); try { notification_handle.close(); } @@ -143,18 +153,17 @@ export const store = createStore<FlightCoreStore>({ // Save change in persistent store await persistentStore.set('game-install', { value: game_install }); + await persistentStore.save(); // explicit save to disk // Check for Northstar install store.commit('checkNorthstarUpdates'); } else { // Not valid Titanfall2 install - ElNotification({ - title: 'Wrong folder', - message: "Selected folder is not a valid Titanfall2 install.", - type: 'error', - position: 'bottom-right' - }); + showErrorNotification( + i18n.global.tc('notification.game_folder.wrong.text'), + i18n.global.tc('notification.game_folder.wrong.title') + ); } } }, @@ -222,12 +231,7 @@ export const store = createStore<FlightCoreStore>({ }) .catch((error) => { console.error(error); - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); break; @@ -236,6 +240,23 @@ export const store = createStore<FlightCoreStore>({ break; } }, + async launchGameSteam(state: any, no_checks = false) { + let game_install = { + game_path: state.game_path, + install_type: state.install_type + } as GameInstall; + + await invoke("launch_northstar_steam_caller", { gameInstall: game_install, bypassChecks: no_checks }) + .then((message) => { + showNotification('Success'); + }) + .catch((error) => { + console.error(error); + showErrorNotification(error); + }); + + return; + }, async fetchReleaseNotes(state: FlightCoreStore) { if (state.releaseNotes.length !== 0) return; state.releaseNotes = await invoke("get_northstar_release_notes"); @@ -291,12 +312,7 @@ export const store = createStore<FlightCoreStore>({ }) .catch((error) => { console.error(error); - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, async toggleReleaseCandidate(state: FlightCoreStore) { @@ -307,17 +323,16 @@ export const store = createStore<FlightCoreStore>({ // Save change in persistent store await persistentStore.set('northstar-release-canal', { value: state.northstar_release_canal }); + await persistentStore.save(); // explicit save to disk // Update current state so that update check etc can be performed store.commit("checkNorthstarUpdates"); // Display notification to highlight change - ElNotification({ - title: `${state.northstar_release_canal}`, - message: `Switched release channel to: "${state.northstar_release_canal}"`, - type: 'success', - position: 'bottom-right' - }); + showNotification( + i18n.global.tc(`channels.names.${state.northstar_release_canal}`), + i18n.global.tc('channels.release.switch.text', {canal: state.northstar_release_canal}), + ); } } }); @@ -327,18 +342,18 @@ export const store = createStore<FlightCoreStore>({ * It invokes all Rust methods that are needed to initialize UI. */ async function _initializeApp(state: any) { - // Enable dev mode directly if application is in debug mode - if (await invoke("is_debug_mode")) { - state.developer_mode = true; - - // Make menubar striped if debug build - let menu_bar_handle = document.querySelector('#fc_menu-bar'); - console.log(menu_bar_handle); - if (menu_bar_handle !== null) { - menu_bar_handle.classList.toggle('developer_build'); - } - } else { - // Disable context menu in release build. + // Display dev view if dev mode was previously enabled. + const devModeEnabled: boolean = await persistentStore.get('dev_mode') ?? false; + const debugModeEnabled: boolean = await invoke("is_debug_mode"); + if (devModeEnabled) { + store.commit('toggleDeveloperMode'); + } + if (debugModeEnabled) { + store.commit('toggleDebugMode'); + } + + // Disable context menu in release build. + if (!debugModeEnabled) { document.addEventListener('contextmenu', event => event.preventDefault()); } @@ -391,13 +406,12 @@ async function _initializeApp(state: any) { .catch((err) => { // Gamepath not found or other error console.error(err); - notification_handle = ElNotification({ - title: 'Titanfall2 not found!', - message: "Please manually select install location", - type: 'error', - position: 'bottom-right', - duration: 0 // Duration `0` means the notification will not auto-vanish - }); + notification_handle = showNotification( + i18n.global.tc('notification.game_folder.not_found.title'), + i18n.global.tc('notification.game_folder.not_found.text'), + 'error', + 0 // Duration `0` means the notification will not auto-vanish + ); }); } @@ -407,6 +421,7 @@ async function _initializeApp(state: any) { // Save change in persistent store await persistentStore.set('game-install', { value: typedResult }); + await persistentStore.save(); // explicit save to disk // Update UI store state.game_path = typedResult.game_path; @@ -433,13 +448,12 @@ async function _checkForFlightCoreUpdates(state: FlightCoreStore) { if (flightcore_is_outdated) { let newest_flightcore_version = await invoke("get_newest_flightcore_version") as FlightCoreVersion; - ElNotification({ - title: 'FlightCore outdated!', - message: `Please update FlightCore.\nRunning outdated version ${state.flightcore_version}.\nNewest is ${newest_flightcore_version.tag_name}!`, - type: 'warning', - position: 'bottom-right', - duration: 0 // Duration `0` means the notification will not auto-vanish - }); + showNotification( + i18n.global.tc('notification.flightcore_outdated.title'), + i18n.global.tc('notification.flightcore_outdated.text', {oldVersion: state.flightcore_version, newVersion: newest_flightcore_version.tag_name}), + 'warning', + 0 // Duration `0` means the notification will not auto-vanish + ); } } @@ -455,6 +469,11 @@ function _initializeListeners(state: any) { listen("northstar-running-ping", function (evt: TauriEvent<any>) { state.northstar_is_running = evt.payload as boolean; }); + + listen("northstar-statistics", function (evt: TauriEvent<{Ok: [number, number]}>) { + state.player_count = evt.payload.Ok[0]; + state.server_count = evt.payload.Ok[1]; + }); } /** diff --git a/src-vue/src/style.css b/src-vue/src/style.css index 4401ac96..0366b0ed 100644 --- a/src-vue/src/style.css +++ b/src-vue/src/style.css @@ -41,3 +41,8 @@ body { height: 100%; color: white; } + +.noModMessage { + color: white; + margin: 30px 15px; +} diff --git a/src-vue/src/utils/SortOptions.d.ts b/src-vue/src/utils/SortOptions.d.ts index 6bdd0a4a..b6f180d2 100644 --- a/src-vue/src/utils/SortOptions.d.ts +++ b/src-vue/src/utils/SortOptions.d.ts @@ -1,8 +1,8 @@ export enum SortOptions { - NAME_ASC = 'By name (A to Z)', - NAME_DESC = 'By name (Z to A)', - DATE_ASC = 'By date (from oldest)', - DATE_DESC = 'By date (from newest)', - MOST_DOWNLOADED = "Most downloaded", - TOP_RATED = "Top rated" + NAME_ASC = 'name_asc', + NAME_DESC = 'name_desc', + DATE_ASC = 'date_asc', + DATE_DESC = 'date_desc', + MOST_DOWNLOADED = 'most_downloaded', + TOP_RATED = 'top_rated' } diff --git a/src-vue/src/utils/ui.ts b/src-vue/src/utils/ui.ts new file mode 100644 index 00000000..b84d7666 --- /dev/null +++ b/src-vue/src/utils/ui.ts @@ -0,0 +1,29 @@ +import { ElNotification, NotificationHandle } from "element-plus"; +import { i18n } from "../main"; + +/** + * Displays content to the user in the form of a notification appearing on screen bottom right. + **/ +function showNotification( + title: string, + message: string = '', + type: 'success' | 'warning' | 'error' | 'info' = 'success', + duration: number = 4500 +): NotificationHandle { + return ElNotification({ + title, message, type, duration, + position: 'bottom-right', + }); +} + +/** + * Helper method displaying an error message to the user. + **/ +function showErrorNotification( + error: string, + title: string = i18n.global.tc('generic.error') +): NotificationHandle { + return showNotification(title, error, 'error'); +} + +export {showNotification, showErrorNotification}; diff --git a/src-vue/src/views/ChangelogView.vue b/src-vue/src/views/ChangelogView.vue index 9335220e..e68beded 100644 --- a/src-vue/src/views/ChangelogView.vue +++ b/src-vue/src/views/ChangelogView.vue @@ -46,7 +46,7 @@ export default defineComponent({ let content: string = releaseBody.replaceAll(/\@(\S+)/g, `<a target="_blank" href="https://github.com/$1">@$1</a>`); // PR's links formatting - content = content.replaceAll(/\[\#(\S+)\]\(([^)]+)\)/g, `<a target="_blank" href="$2">#$1</a>`); + content = content.replaceAll(/\[(\S*)\#(\S+)\]\(([^)]+)\)/g, `<a target="_blank" href="$3">$1#$2</a>`); return marked.parse(content, {breaks: true}); }, diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue index 52117b8d..7e11bd11 100644 --- a/src-vue/src/views/DeveloperView.vue +++ b/src-vue/src/views/DeveloperView.vue @@ -27,12 +27,8 @@ Launch Northstar (bypass all checks) </el-button> - <h3>Mod install:</h3> - - <el-input v-model="mod_to_install_field_string" placeholder="Please input Thunderstore dependency string (example: AuthorName-ModName-1.2.3)" clearable /> - - <el-button type="primary" @click="installMod"> - Install mod + <el-button type="primary" @click="launchGameViaSteam"> + Launch Northstar via Steam </el-button> <h3>Repair:</h3> @@ -44,6 +40,48 @@ <h3>Testing</h3> <pull-requests-selector /> + + <h3>Mod install:</h3> + + <el-input v-model="mod_to_install_field_string" placeholder="Please input Thunderstore dependency string (example: AuthorName-ModName-1.2.3)" clearable /> + + <el-button type="primary" @click="installMod"> + Install mod + </el-button> + + <h3>Release management</h3> + + <el-button type="primary" @click="getTags"> + Get tags + </el-button> + + <el-select v-model="firstTag" class="m-2" placeholder="First tag"> + <el-option + v-for="item in ns_release_tags" + :key="item.value" + :label="item.label" + :value="item" + /> + </el-select> + <el-select v-model="secondTag" class="m-2" placeholder="Second tag"> + <el-option + v-for="item in ns_release_tags" + :key="item.value" + :label="item.label" + :value="item" + /> + </el-select> + + <el-button type="primary" @click="compareTags"> + Compare Tags + </el-button> + + <el-input + v-model="release_notes_text" + type="textarea" + :rows="5" + placeholder="Output" + /> </el-scrollbar> </div> </template> @@ -51,9 +89,10 @@ <script lang="ts"> import { defineComponent } from "vue"; import { invoke } from "@tauri-apps/api"; -import { ElNotification } from "element-plus"; import { GameInstall } from "../utils/GameInstall"; +import { TagWrapper } from "../../../src-tauri/bindings/TagWrapper"; import PullRequestsSelector from "../components/PullRequestsSelector.vue"; +import { showErrorNotification, showNotification } from "../utils/ui"; export default defineComponent({ name: "DeveloperView", @@ -63,44 +102,54 @@ export default defineComponent({ data() { return { mod_to_install_field_string : "", + release_notes_text : "", + first_tag: { label: '', value: {name: ''} }, + second_tag: { label: '', value: {name: ''} }, + ns_release_tags: [] as TagWrapper[], } }, + computed: { + firstTag: { + get(): TagWrapper { + return this.first_tag; + }, + set(value: TagWrapper) { + this.first_tag = value; + } + }, + secondTag: { + get(): TagWrapper { + return this.second_tag; + }, + set(value: TagWrapper) { + this.second_tag = value; + } + }, + }, methods: { disableDevMode() { this.$store.commit('toggleDeveloperMode'); }, async crashApplication() { await invoke("force_panic"); - ElNotification({ - title: 'Error', - message: "Never should have been able to get here!", - type: 'error', - position: 'bottom-right' - }); + showErrorNotification("Never should have been able to get here!"); }, async checkLinuxCompatibility() { await invoke("linux_checks") .then(() => { - ElNotification({ - title: 'Linux compatible', - message: 'All checks passed', - type: 'success', - position: 'bottom-right' - }); + showNotification('Linux compatible', 'All checks passed'); }) .catch((error) => { - ElNotification({ - title: 'Not linux compatible', - message: error, - type: 'error', - position: 'bottom-right' - }); + showNotification('Not Linux compatible', error, 'error'); console.error(error); }); }, async launchGameWithoutChecks() { this.$store.commit('launchGame', true); }, + async launchGameViaSteam() { + this.$store.commit('launchGameSteam', true); + }, async getInstalledMods() { let game_install = { game_path: this.$store.state.game_path, @@ -112,20 +161,10 @@ export default defineComponent({ console.log(message); // Just a visual indicator that it worked - ElNotification({ - title: 'Success', - message: "Success", - type: 'success', - position: 'bottom-right' - }); + showNotification('Success'); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, async installMod() { @@ -134,22 +173,32 @@ export default defineComponent({ install_type: this.$store.state.install_type } as GameInstall; let mod_to_install = this.mod_to_install_field_string; - await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install }).then((message) => { + await invoke<string>("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install }).then((message) => { // Show user notification if mod install completed. - ElNotification({ - title: `Installed ${mod_to_install}`, - message: message as string, - type: 'success', - position: 'bottom-right' - }); + showNotification(`Installed ${mod_to_install}`, message); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); + }); + }, + async getTags() { + await invoke<TagWrapper[]>("get_list_of_tags") + .then((message) => { + this.ns_release_tags = message; + showNotification("Done", "Fetched tags"); + }) + .catch((error) => { + showErrorNotification(error); + }); + }, + async compareTags() { + await invoke<string>("compare_tags", {firstTag: this.firstTag.value, secondTag: this.secondTag.value}) + .then((message) => { + this.release_notes_text = message; + showNotification("Done", "Generated release notes"); + }) + .catch((error) => { + showErrorNotification(error); }); }, } diff --git a/src-vue/src/views/PlayView.vue b/src-vue/src/views/PlayView.vue index beca6724..76f4f328 100644 --- a/src-vue/src/views/PlayView.vue +++ b/src-vue/src/views/PlayView.vue @@ -1,12 +1,13 @@ <script lang="ts"> -import { ElNotification } from 'element-plus'; import { Tabs } from "../utils/Tabs"; import PlayButton from '../components/PlayButton.vue'; import { defineComponent } from "vue"; +import InstallProgressBar from "../components/InstallProgressBar.vue"; export default defineComponent({ components: { - PlayButton + PlayButton, + InstallProgressBar }, computed: { northstarIsRunning(): boolean { @@ -34,37 +35,40 @@ export default defineComponent({ <div class="fc_launch__container"> <div class="fc_title">Northstar</div> <div class="fc_northstar__version__container"> - {{ northstarVersion === '' ? 'Unknown version' : `v${northstarVersion}` }} + {{ northstarVersion === '' ? $t('play.unknown_version') : `v${northstarVersion}` }} <div v-if="northstarVersion !== ''" class="fc_changelog__link" @click="showChangelogPage"> - (see patch notes) + ({{ $t('play.see_patch_notes') }}) </div> <div v-if="playerCount >= 0" class="fc-stats__container"> - {{ playerCount }} players, - {{ serverCount }} servers + {{ playerCount }} {{ $t('play.players') }}, + {{ serverCount }} {{ $t('play.servers') }} </div> <div v-else="playerCount >= 0" class="fc-stats__container"> - Unable to load playercount + {{ $t('play.unable_to_load_playercount') }} </div> </div> - <div> + + <!-- Align play button and services state container --> + <div style="display: flex"> <PlayButton /> <div v-if="$store.state.developer_mode" id="fc_services__status"> <div> - <div class="fc_version__line">Northstar is running: </div> + <div class="fc_version__line">{{ $t('play.northstar_running') }}</div> <div class="fc_version__line fc_version__line__boolean"> {{ northstarIsRunning }}</div> </div> <div> - <div class="fc_version__line">Origin is running: </div> + <div class="fc_version__line">{{ $t('play.origin_running') }}</div> <div class="fc_version__line fc_version__line__boolean">{{ $store.state.origin_is_running }}</div> </div> </div> </div> + <InstallProgressBar /> </div> </template> <style scoped> .fc_launch__container { - margin: 50px; + margin: 50px 50px 30px 50px; position: fixed; bottom: 0; } @@ -102,13 +106,9 @@ export default defineComponent({ border-color: var(--el-color-primary); } - #fc_services__status { - display: inline-block; - position: fixed; - padding: 10px 20px; color: #e8edef; - bottom: 43px; + align-self: end; } .fc_version__line { diff --git a/src-vue/src/views/RepairView.vue b/src-vue/src/views/RepairView.vue index e7cd479a..c11518d0 100644 --- a/src-vue/src/views/RepairView.vue +++ b/src-vue/src/views/RepairView.vue @@ -1,30 +1,30 @@ <template> <div class="fc-container"> <el-scrollbar> - <el-alert title="Info" type="info" :closable="false" show-icon> - This window contains various functionality to repair common issues with Northstar and FlightCore. + <el-alert :title="$t('generic.informationShort')" type="info" :closable="false" show-icon> + {{ $t('settings.repair.window.warning') }} </el-alert> - <h1>Repair</h1> + <h1>{{ $t('settings.repair.title') }}</h1> <h2>Northstar</h2> <el-button type="primary" @click="disableAllModsButCore"> - Disable all but core mods + {{ $t('settings.repair.window.disable_all_but_core') }} </el-button> <el-button type="primary" @click="forceInstallNorthstar"> - Force reinstall Northstar + {{ $t('settings.repair.window.force_reinstall_ns') }} </el-button> <h2>FlightCore</h2> <el-button type="primary" @click="cleanUpDownloadFolder"> - Force delete temp download folder + {{ $t('settings.repair.window.force_delete_temp_dl') }} </el-button> <el-button type="primary" @click="clearFlightCorePersistentStore"> - Delete FlightCore persistent store + {{ $t('settings.repair.window.delete_persistent_store') }} </el-button> </el-scrollbar> </div> @@ -32,15 +32,22 @@ <script lang="ts"> import { defineComponent } from "vue"; -import { ElNotification } from "element-plus"; import { GameInstall } from "../utils/GameInstall"; +import { InstallProgress } from "../../../src-tauri/bindings/InstallProgress"; import { invoke } from "@tauri-apps/api"; import { ReleaseCanal } from "../utils/ReleaseCanal"; import { Store } from 'tauri-plugin-store-api'; +import { showErrorNotification, showNotification } from "../utils/ui"; +import { appWindow } from "@tauri-apps/api/window"; const persistentStore = new Store('flight-core-settings.json'); export default defineComponent({ name: "RepairView", + computed: { + lang(): string { + return this.$root!.$i18n.locale; + } + }, methods: { async disableAllModsButCore() { let game_install = { @@ -49,20 +56,10 @@ export default defineComponent({ } as GameInstall; await invoke("disable_all_but_core", { gameInstall: game_install }) .then((message) => { - ElNotification({ - title: 'Success', - message: "Disabled all mods but core", - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('generic.success'), this.$t('settings.repair.window.disable_all_but_core_success')); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, async forceInstallNorthstar() { @@ -72,33 +69,32 @@ export default defineComponent({ } as GameInstall; // Send notification telling the user to wait for the process to finish - const notification = ElNotification({ - title: 'Force reinstalling Northstar', - message: 'Please wait', - duration: 0, - type: 'info', - position: 'bottom-right' - }); + const notification = showNotification( + this.$t('settings.repair.window.reinstall_title'), + this.$t('settings.repair.window.reinstall_text'), + 'info', + 0 + ); let install_northstar_result = invoke("install_northstar_caller", { gamePath: game_install.game_path, northstarPackageName: ReleaseCanal.RELEASE }); + + appWindow.listen<InstallProgress>( + 'northstar-install-download-progress', + ({ event, payload }) => { + let typed_payload = payload; + console.log("current_downloaded:", typed_payload.current_downloaded); + console.log("total_size: ", typed_payload.total_size); + console.log("state: ", typed_payload.state); + } + ); await install_northstar_result .then((message) => { // Send notification - ElNotification({ - title: `Done`, - message: `Successfully reinstalled Northstar`, - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('generic.done'), this.$t('settings.repair.window.reinstall_success')); this.$store.commit('checkNorthstarUpdates'); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); console.error(error); }) .finally(() => { @@ -113,20 +109,10 @@ export default defineComponent({ } as GameInstall; await invoke("clean_up_download_folder_caller", { gameInstall: game_install, force: true }).then((message) => { // Show user notification if task completed. - ElNotification({ - title: `Done`, - message: `Done`, - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('generic.done'), this.$t('generic.done')); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, async clearFlightCorePersistentStore() { @@ -135,6 +121,13 @@ export default defineComponent({ // ...and save await persistentStore.save(); }, + }, + watch: { + // Lang value is propagated to repair view after it's mounted, so we need to watch + // its value, and update window title accordingly. + lang(newv: string) { + appWindow.setTitle( this.$t('settings.repair.window.title') ); + } } }); </script> diff --git a/src-vue/src/views/SettingsView.vue b/src-vue/src/views/SettingsView.vue index c93d69ff..772a4c5c 100644 --- a/src-vue/src/views/SettingsView.vue +++ b/src-vue/src/views/SettingsView.vue @@ -3,53 +3,68 @@ <el-scrollbar> <div class="fc_settings__container"> <!-- Game folder location --> - <h3>Manage installation</h3> - <el-input - v-model="$store.state.game_path" - class="w-50 m-2" - placeholder="Choose installation folder" - @click="updateGamePath" - > - <template #prepend> - <el-button icon="Folder" @click="updateGamePath"/> - </template> - </el-input> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.manage_install') }}</h3> + <el-input + v-model="$store.state.game_path" + :placeholder="$t('settings.choose_folder')" + @click="updateGamePath" + > + <template #prepend> + <el-button icon="Folder" @click="updateGamePath"/> + </template> + </el-input> + </div> <!-- Thunderstore mods per page configuration --> <div class="fc_parameter__panel"> - <h3>Number of Thunderstore mods per page</h3> + <h3>{{ $t('settings.nb_ts_mods_per_page') }}</h3> <h6> - This has an impact on display performances when browsing Thunderstore mods.<br> - Set this value to 0 to disable pagination. + {{ $t('settings.nb_ts_mods_per_page_desc1') }}<br> + {{ $t('settings.nb_ts_mods_per_page_desc2') }} </h6> - <el-input - v-model="modsPerPage" + <el-input + v-model="modsPerPage" type="number" > <template #append> - <el-button @click="modsPerPage = 20">Reset to default</el-button> + <el-button @click="modsPerPage = 20"> + {{ $t('settings.nb_ts_mods_reset') }} + </el-button> </template> </el-input> </div> - <h3>Repair</h3> - <el-button type="primary" @click="openRepairWindow"> - Open Repair window - </el-button> + <!-- Interface localization --> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.language') }}</h3> + <language-selector/> + </div> + + <!-- Repair window --> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.repair.title') }}</h3> + <el-button type="primary" @click="openRepairWindow"> + {{ $t('settings.repair.open_window') }} + </el-button> + </div> + + <!-- About section --> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.about') }}</h3> + <div class="fc_northstar__version" @click="activateDeveloperMode"> + {{ $t('settings.flightcore_version') }} {{ flightcoreVersion === '' ? 'Unknown version' : `${flightcoreVersion}` }} + </div> + </div> - <h3>About:</h3> - <div class="fc_northstar__version" @click="activateDeveloperMode"> - FlightCore Version: {{ flightcoreVersion === '' ? 'Unknown version' : `${flightcoreVersion}` }} + <!-- Testing section --> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.testing') }}</h3> + <span> + {{ $t('settings.enable_test_channels') }} + <el-switch v-model="enableReleasesSwitch"></el-switch> + </span> </div> - <br /> - <br /> - UI design inspired by <el-link :underline="false" target="_blank" href="https://github.com/TFORevive/tforevive_launcher/" type="primary">TFORevive Launcher</el-link> (not yet public) - - <h3>Testing:</h3> - <span> - Enable testing release channels - <el-switch v-model="enableReleasesSwitch"></el-switch> - </span> </div> </el-scrollbar> </div> @@ -57,14 +72,18 @@ <script lang="ts"> import { defineComponent } from "vue"; -import { ElNotification } from 'element-plus'; import { invoke } from "@tauri-apps/api"; import { ReleaseCanal } from "../utils/ReleaseCanal"; import { Store } from 'tauri-plugin-store-api'; +import { showErrorNotification, showNotification } from "../utils/ui"; +import LanguageSelector from "../components/LanguageSelector.vue"; const persistentStore = new Store('flight-core-settings.json'); export default defineComponent({ name: "SettingsView", + components: { + LanguageSelector + }, data() { return { developerModeClicks: 0 @@ -78,9 +97,10 @@ export default defineComponent({ get(): boolean { return this.$store.state.enableReleasesSwitch; }, - set(value: boolean): void { + async set(value: boolean): Promise<void> { this.$store.state.enableReleasesSwitch = value; persistentStore.set('northstar-releases-switching', { value }); + await persistentStore.save(); // explicit save to disk // When disabling switch, we switch release canal to stable release, to avoid users being // stuck with release candidate after disabling release switching. @@ -93,23 +113,23 @@ export default defineComponent({ get(): number { return this.$store.state.mods_per_page; }, - set(value: number) { + async set(value: number) { this.$store.state.mods_per_page = value; persistentStore.set('thunderstore-mods-per-page', { value }); + await persistentStore.save(); // explicit save to disk } } }, methods: { activateDeveloperMode() { this.developerModeClicks += 1; - if (this.developerModeClicks >= 6) { - this.$store.state.developer_mode = true; - ElNotification({ - title: 'Watch out!', - message: 'Developer mode enabled.', - type: 'info', - position: 'bottom-right' - }); + if (this.developerModeClicks >= 6 && !this.$store.state.developer_mode) { + this.$store.commit('toggleDeveloperMode'); + showNotification( + this.$t('settings.dev_mode_enabled_title'), + this.$t('settings.dev_mode_enabled_text'), + 'info' + ); this.developerModeClicks = 0; } }, @@ -120,12 +140,7 @@ export default defineComponent({ await invoke("open_repair_window") .then((message) => { }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, }, @@ -154,7 +169,7 @@ h3:first-of-type { font-weight: unset; } -.el-input { +.el-input, .el-select { width: 50%; } @@ -165,7 +180,7 @@ h3:first-of-type { /* Parameter panel styles */ .fc_parameter__panel { - margin: 30px 0; + margin-bottom: 30px; } .fc_parameter__panel h3 { diff --git a/src-vue/src/views/mods/LocalModsView.vue b/src-vue/src/views/mods/LocalModsView.vue index ed801b7a..3fb90bdc 100644 --- a/src-vue/src/views/mods/LocalModsView.vue +++ b/src-vue/src/views/mods/LocalModsView.vue @@ -1,38 +1,44 @@ <template> - <el-scrollbar> - <div> - <p v-if="mods.length === 0">No mods were found.</p> - <el-card v-else shadow="hover" v-for="mod in mods" v-bind:key="mod.name"> - <el-switch style="--el-switch-on-color: #13ce66; --el-switch-off-color: #8957e5" v-model="mod.enabled" - :before-change="() => updateWhichModsEnabled(mod)" :loading="global_load_indicator" /> - <el-popconfirm - title="Are you sure to delete this mod?" - @confirm="deleteMod(mod)" - > - <template #reference> - <el-button type="danger">Delete</el-button> - </template> - </el-popconfirm> - {{ mod.name }} - <span v-if="mod.version != null">(v{{ mod.version }})</span> - <img - v-if="mod.thunderstore_mod_string != null" - title="This Northstar mod is part of a Thunderstore mod" - src="/src/assets/thunderstore-icon.png" - class="image" - height="16" - /> - </el-card> - </div> + <!-- Message displayed if no mod matched searched words --> + <div v-if="mods.length === 0" class="noModMessage"> + {{ $t('mods.local.no_mods') }} + </div> + + <el-scrollbar v-else> + <el-card shadow="hover" v-for="mod in mods" v-bind:key="mod.name"> + <el-switch style="--el-switch-on-color: #13ce66; --el-switch-off-color: #8957e5" v-model="mod.enabled" + :before-change="() => updateWhichModsEnabled(mod)" :loading="global_load_indicator" /> + <el-popconfirm + :title="$t('mods.local.delete_confirm')" + :confirm-button-text="$t('generic.yes')" + :cancel-button-text="$t('generic.no')" + @confirm="deleteMod(mod)" + > + <template #reference> + <el-button type="danger"> + {{ $t('mods.local.delete') }} + </el-button> + </template> + </el-popconfirm> + {{ mod.name }} + <span v-if="mod.version != null">(v{{ mod.version }})</span> + <img + v-if="mod.thunderstore_mod_string != null" + :title="$t('mods.local.part_of_ts_mod')" + src="/src/assets/thunderstore-icon.png" + class="image" + height="16" + /> + </el-card> </el-scrollbar> </template> <script lang="ts"> import { invoke } from '@tauri-apps/api'; -import { ElNotification } from 'element-plus'; import { defineComponent } from 'vue'; import { GameInstall } from '../../utils/GameInstall'; import { NorthstarMod } from "../../../../src-tauri/bindings/NorthstarMod"; +import { showErrorNotification, showNotification } from '../../utils/ui'; export default defineComponent({ name: 'LocalModsView', @@ -79,12 +85,7 @@ export default defineComponent({ }) } catch (error) { - ElNotification({ - title: 'Error', - message: `${error}`, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(`${error}`); this.global_load_indicator = false; return false; } @@ -100,19 +101,10 @@ export default defineComponent({ await invoke("delete_northstar_mod", { gameInstall: game_install, nsmodName: mod.name }) .then((message) => { // Just a visual indicator that it worked - ElNotification({ - title: `Success deleting ${mod.name}`, - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('mods.local.success_deleting', {modName: mod.name})); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { this.$store.commit('loadInstalledMods'); diff --git a/src-vue/src/views/mods/ThunderstoreModsView.vue b/src-vue/src/views/mods/ThunderstoreModsView.vue index 19809f3e..5a7270df 100644 --- a/src-vue/src/views/mods/ThunderstoreModsView.vue +++ b/src-vue/src/views/mods/ThunderstoreModsView.vue @@ -3,11 +3,17 @@ <div v-if="mods.length === 0" class="fc__changelog__container">
<el-progress :show-text="false" :percentage="50" :indeterminate="true" />
</div>
+
+ <!-- Message displayed if no mod matched searched words -->
+ <div v-else-if="filteredMods.length === 0" class="noModMessage">
+ {{ $t('mods.online.no_match') }}<br/>
+ {{ $t('mods.online.try_another_search') }}
+ </div>
+
<el-scrollbar v-else class="container" ref="scrollbar">
<div class="card-container">
- <div class="pagination_container">
+ <div class="pagination_container" v-if="shouldDisplayPagination">
<el-pagination
- v-if="shouldDisplayPagination"
:currentPage="currentPageIndex + 1"
layout="prev, pager, next"
:page-size="modsPerPage"
@@ -16,12 +22,6 @@ />
</div>
- <!-- Message displayed if no mod matched searched words -->
- <div v-if="filteredMods.length === 0" class="modMessage">
- No matching mod has been found.<br/>
- Try another search!
- </div>
-
<!-- Mod cards -->
<thunderstore-mod-card v-for="mod of currentPageMods" v-bind:key="mod.name" :mod="mod" />
</div>
@@ -199,11 +199,6 @@ export default defineComponent({ margin: 0 0 0 10px !important;
}
-.modMessage {
- color: white;
- margin: 20px 5px;
-}
-
.card-container {
margin: 0 auto;
}
|