diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b24d805fa..dc0c0a516 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,21 +1,5 @@ - - ## Description - - -## Motivation and Context - - - -## Screenshots (if appropriate): -## Types of changes - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) +## Motivation -## Checklist: - - -- [ ] My code follows the code style of this project. -- [ ] I have tested my changes and confirmed that they are working +## Additional Context diff --git a/.github/workflows/build-tauri-app.yml b/.github/workflows/build-tauri-app.yml new file mode 100644 index 000000000..730392123 --- /dev/null +++ b/.github/workflows/build-tauri-app.yml @@ -0,0 +1,72 @@ +name: Build Tauri App +on: + release: + types: [published] + workflow_dispatch: + +jobs: + release: + strategy: + fail-fast: false + matrix: + platform: [macos-latest, ubuntu-latest, windows-latest] + + runs-on: ${{ matrix.platform }} + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v3 + + - name: Install Node 🧰 + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + + - name: Install Rust 🦀 + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Install dependencies (ubuntu only) 📦 + if: matrix.platform == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf + + - name: Install npm packages 🔧 + run: npm i + + # - name: Add aarch64 target + # if: matrix.platform == 'macos-latest' + # run: rustup target add aarch64-apple-darwin + + # - name: Build the app (Apple Silicon) + # uses: solvedDev/tauri-action@dev + # if: matrix.platform == 'macos-latest' + + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} + # TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} + + # with: + # tagName: v__VERSION__ # tauri-action replaces \_\_VERSION\_\_ with the app version + # releaseName: 'v__VERSION__' + # releaseDraft: true + # prerelease: false + # args: --target aarch64-apple-darwin + + - name: Build the app 💻 + uses: solvedDev/tauri-action@dev + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} + TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} + VITE_IS_TAURI_APP: true + + with: + tagName: v__VERSION__ # tauri-action replaces \_\_VERSION\_\_ with the app version + releaseName: 'v__VERSION__' + releaseDraft: false + prerelease: false diff --git a/.github/workflows/deploy.yml b/.github/workflows/build-web-app.yml similarity index 98% rename from .github/workflows/deploy.yml rename to .github/workflows/build-web-app.yml index ee7ad9e1e..36e8aa4b1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/build-web-app.yml @@ -1,4 +1,4 @@ -name: Build and Deploy +name: Build Web App on: release: types: [published] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a74f591d8..f23df6315 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,11 @@ In order to get started testing the application, you need to install [NodeJS](ht Open the terminal, cd to the directory you have cloned "bridge." to and run `npm i`. Afterwards, you can use `npm run dev` and `npm run build` to start the development environment and build versions of "bridge.". +### Native Builds + +We use [Tauri](https://tauri.app/) to build native versions of bridge. v2. If you want to run the native app in development mode, follow the steps described [here](https://tauri.app/v1/guides/getting-started/prerequisites). Afterwards, run `cargo install tauri-cli` to install the Tauri CLI. +You can now run `cargo tauri dev` to start the native app in development mode. + ## Code Rules ### Verified as working diff --git a/README.md b/README.md index 370dfef2d..fde9915e3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Open in Codeflow - + diff --git a/index.html b/index.html index de3eacf71..e64a57969 100644 --- a/index.html +++ b/index.html @@ -133,6 +133,8 @@ width: 100vw; height: calc(100vh - 50px); + app-region: drag; + -webkit-app-region: drag; } .initial-boot img { place-self: center; @@ -192,16 +194,18 @@
-
+
<% if(isNightly) { %> bridge Nightly <% } else { %> bridge diff --git a/package-lock.json b/package-lock.json index f6bfadfcd..70f035962 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "bridge", - "version": "2.4.1", + "version": "2.4.15", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bridge", - "version": "2.4.1", + "version": "2.4.15", "dependencies": { "@mdi/font": "^6.9.96", + "@tauri-apps/api": "^1.2.0", "@types/lz-string": "^1.3.34", "bridge-common-utils": "^0.3.3", "bridge-iframe-api": "^0.4.11", @@ -28,12 +29,13 @@ "jsonc-parser": "^3.2.0", "lodash-es": "^4.17.20", "lz-string": "^1.4.4", - "mc-project-core": "^0.3.22", + "mc-project-core": "^0.3.23", "molang": "^1.13.11", "monaco-editor": "^0.33.0", "path-browserify": "^1.0.1", "prismarine-nbt": "^1.6.0", "quick-score": "^0.0.13", + "solid-js": "^1.6.2", "tga-js": "^1.1.1", "three": "^0.139.2", "uuid": "^8.3.1", @@ -62,6 +64,7 @@ "vite": "^3.2.3", "vite-plugin-ejs": "^1.4.3", "vite-plugin-pwa": "^0.13.3", + "vite-plugin-solid": "^2.4.0", "vite-plugin-vue2": "^2.0.2" } }, @@ -115,20 +118,21 @@ } }, "node_modules/@babel/core": { - "version": "7.18.5", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", "dev": true, - "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.2", - "@babel/helper-compilation-targets": "^7.18.2", - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helpers": "^7.18.2", - "@babel/parser": "^7.18.5", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.5", - "@babel/types": "^7.18.4", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -144,12 +148,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz", - "integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", "dev": true, "dependencies": { - "@babel/types": "^7.20.2", + "@babel/types": "^7.20.5", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, @@ -480,13 +484,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.18.2", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2" + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" }, "engines": { "node": ">=6.9.0" @@ -506,9 +511,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", - "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==", + "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" }, @@ -917,11 +922,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.14.5", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -1668,6 +1674,23 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", + "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-typescript": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.20.1", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", @@ -1695,19 +1718,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", - "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.1", + "@babel/generator": "^7.20.5", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.19.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.1", - "@babel/types": "^7.20.0", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1716,9 +1739,9 @@ } }, "node_modules/@babel/types": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", - "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.19.4", @@ -2016,6 +2039,20 @@ "version": "1.2.218", "license": "Apache-2.0" }, + "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==", + "engines": { + "node": ">= 14.6.0", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, "node_modules/@types/chai": { "version": "4.3.1", "license": "MIT" @@ -2408,6 +2445,33 @@ "node": ">= 4.0.0" } }, + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.35.6", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.35.6.tgz", + "integrity": "sha512-z8VBym+Scol38MiR97iqQGsANjhsDqscRRemk+po+z3TWKV/fb9kux/gdKOJJSC/ARyUL3HExBFVtr+Efd24uw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "7.16.0", + "@babel/plugin-syntax-jsx": "^7.16.5", + "@babel/types": "^7.16.0", + "html-entities": "2.3.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", + "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", @@ -2447,6 +2511,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-preset-solid": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.6.2.tgz", + "integrity": "sha512-5sFI34g7Jtp4r04YFWkuC1o+gnekBdPXQTJb5/6lmxi5YwzazVgKAXRwEAToC3zRaPyIYJbZUVLpOi5mDzPEuw==", + "dev": true, + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.35.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.0", "dev": true, @@ -3932,6 +4008,12 @@ "he": "bin/he" } }, + "node_modules/html-entities": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", + "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==", + "dev": true + }, "node_modules/html-tags": { "version": "2.0.0", "dev": true, @@ -4232,6 +4314,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-what": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.8.tgz", + "integrity": "sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==", + "dev": true, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/jake": { "version": "10.8.5", "dev": true, @@ -4518,9 +4612,9 @@ } }, "node_modules/mc-project-core": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/mc-project-core/-/mc-project-core-0.3.22.tgz", - "integrity": "sha512-WTjSut6x+f3xTmVr0P75uldgezjTIRO2h/5SQoFBuKZT8XzphEtLBmdiBfNobAz5vsOaCLEuiBkL++DXe3x6Og==", + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/mc-project-core/-/mc-project-core-0.3.23.tgz", + "integrity": "sha512-hy/uVzW0jtL6vdzqwhe5t3aYy/4WsOuGDH7JVESSmeKKgb+jxqz04OhgpWhWj3/86GEdHd1u18Iek1VUG1s6TQ==", "dependencies": { "bridge-common-utils": "^0.3.3", "json5": "^2.2.0", @@ -4532,6 +4626,21 @@ "dev": true, "license": "MIT" }, + "node_modules/merge-anything": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.4.tgz", + "integrity": "sha512-7PWKwGOs5WWcpw+/OvbiFiAvEP6bv/QHiicigpqMGKIqPPAtGhBLR8LFJW+Zu6m9TXiR/a8+AiPlGG0ko1ruoQ==", + "dev": true, + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/merge-source-map": { "version": "1.1.0", "dev": true, @@ -5170,6 +5279,28 @@ "node": ">=8" } }, + "node_modules/solid-js": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.6.2.tgz", + "integrity": "sha512-AZBsj+Yn1xliniTTeuQKG9V7VQVkQ8lZmSKvBjpcVSoZeF7nvt/N5f7Kcsx6QSufioa2YgvBjkIiA0cM0qhotw==", + "dependencies": { + "csstype": "^3.1.0" + } + }, + "node_modules/solid-refresh": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.4.1.tgz", + "integrity": "sha512-v3tD/OXQcUyXLrWjPW1dXZyeWwP7/+GQNs8YTL09GBq+5FguA6IejJWUvJDrLIA4M0ho9/5zK2e9n+uy+4488g==", + "dev": true, + "dependencies": { + "@babel/generator": "^7.18.2", + "@babel/helper-module-imports": "^7.16.7", + "@babel/types": "^7.18.4" + }, + "peerDependencies": { + "solid-js": "^1.3" + } + }, "node_modules/sortablejs": { "version": "1.10.2", "license": "MIT" @@ -5711,6 +5842,24 @@ "workbox-window": "^6.5.4" } }, + "node_modules/vite-plugin-solid": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.4.0.tgz", + "integrity": "sha512-Rr+t2sr9TWIvH16yzBZzx6O9YSpYAvcwKUMPqbi/4iU3mRumXQ4O10i1XGtQIynC7U3XwJsMzAJigDFGbiJBiw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.18.6", + "@babel/preset-typescript": "^7.18.6", + "babel-preset-solid": "^1.4.6", + "merge-anything": "^5.0.2", + "solid-refresh": "^0.4.1", + "vitefu": "^0.1.1" + }, + "peerDependencies": { + "solid-js": "^1.3.17", + "vite": "^3.0.0" + } + }, "node_modules/vite-plugin-vue2": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/vite-plugin-vue2/-/vite-plugin-vue2-2.0.2.tgz", @@ -5861,6 +6010,20 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/vitefu": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.1.1.tgz", + "integrity": "sha512-HClD14fjMJ+NQgXBqT3dC3RdO/+Chayil+cCPYZKY3kT+KcJomKzrdgzfCHJkIL2L0OAY+VPvrSW615iPtc7ag==", + "dev": true, + "peerDependencies": { + "vite": "^3.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/vitest": { "version": "0.15.2", "license": "MIT", @@ -6353,19 +6516,21 @@ "dev": true }, "@babel/core": { - "version": "7.18.5", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.2", - "@babel/helper-compilation-targets": "^7.18.2", - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helpers": "^7.18.2", - "@babel/parser": "^7.18.5", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.5", - "@babel/types": "^7.18.4", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -6374,12 +6539,12 @@ } }, "@babel/generator": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz", - "integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", "dev": true, "requires": { - "@babel/types": "^7.20.2", + "@babel/types": "^7.20.5", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, @@ -6611,12 +6776,14 @@ } }, "@babel/helpers": { - "version": "7.18.2", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", "dev": true, "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.2", - "@babel/types": "^7.18.2" + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" } }, "@babel/highlight": { @@ -6629,9 +6796,9 @@ } }, "@babel/parser": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", - "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.18.6", @@ -6888,10 +7055,12 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.14.5", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -7382,6 +7551,17 @@ "esutils": "^2.0.2" } }, + "@babel/preset-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", + "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-typescript": "^7.18.6" + } + }, "@babel/runtime": { "version": "7.20.1", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", @@ -7403,27 +7583,27 @@ } }, "@babel/traverse": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", - "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", "dev": true, "requires": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.1", + "@babel/generator": "^7.20.5", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.19.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.1", - "@babel/types": "^7.20.0", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", - "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.19.4", @@ -7633,6 +7813,11 @@ "@swc/wasm-web": { "version": "1.2.218" }, + "@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==" + }, "@types/chai": { "version": "4.3.1" }, @@ -7922,6 +8107,29 @@ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true }, + "babel-plugin-jsx-dom-expressions": { + "version": "0.35.6", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.35.6.tgz", + "integrity": "sha512-z8VBym+Scol38MiR97iqQGsANjhsDqscRRemk+po+z3TWKV/fb9kux/gdKOJJSC/ARyUL3HExBFVtr+Efd24uw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "7.16.0", + "@babel/plugin-syntax-jsx": "^7.16.5", + "@babel/types": "^7.16.0", + "html-entities": "2.3.2" + }, + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", + "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + } + } + }, "babel-plugin-polyfill-corejs2": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", @@ -7952,6 +8160,15 @@ "@babel/helper-define-polyfill-provider": "^0.3.3" } }, + "babel-preset-solid": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.6.2.tgz", + "integrity": "sha512-5sFI34g7Jtp4r04YFWkuC1o+gnekBdPXQTJb5/6lmxi5YwzazVgKAXRwEAToC3zRaPyIYJbZUVLpOi5mDzPEuw==", + "dev": true, + "requires": { + "babel-plugin-jsx-dom-expressions": "^0.35.4" + } + }, "balanced-match": { "version": "1.0.0", "dev": true @@ -8787,6 +9004,12 @@ "he": { "version": "1.2.0" }, + "html-entities": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", + "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==", + "dev": true + }, "html-tags": { "version": "2.0.0", "dev": true @@ -8974,6 +9197,12 @@ "call-bind": "^1.0.2" } }, + "is-what": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.8.tgz", + "integrity": "sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==", + "dev": true + }, "jake": { "version": "10.8.5", "dev": true, @@ -9177,9 +9406,9 @@ } }, "mc-project-core": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/mc-project-core/-/mc-project-core-0.3.22.tgz", - "integrity": "sha512-WTjSut6x+f3xTmVr0P75uldgezjTIRO2h/5SQoFBuKZT8XzphEtLBmdiBfNobAz5vsOaCLEuiBkL++DXe3x6Og==", + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/mc-project-core/-/mc-project-core-0.3.23.tgz", + "integrity": "sha512-hy/uVzW0jtL6vdzqwhe5t3aYy/4WsOuGDH7JVESSmeKKgb+jxqz04OhgpWhWj3/86GEdHd1u18Iek1VUG1s6TQ==", "requires": { "bridge-common-utils": "^0.3.3", "json5": "^2.2.0", @@ -9190,6 +9419,15 @@ "version": "1.0.1", "dev": true }, + "merge-anything": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.4.tgz", + "integrity": "sha512-7PWKwGOs5WWcpw+/OvbiFiAvEP6bv/QHiicigpqMGKIqPPAtGhBLR8LFJW+Zu6m9TXiR/a8+AiPlGG0ko1ruoQ==", + "dev": true, + "requires": { + "is-what": "^4.1.8" + } + }, "merge-source-map": { "version": "1.1.0", "dev": true, @@ -9602,6 +9840,25 @@ "version": "3.0.0", "dev": true }, + "solid-js": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.6.2.tgz", + "integrity": "sha512-AZBsj+Yn1xliniTTeuQKG9V7VQVkQ8lZmSKvBjpcVSoZeF7nvt/N5f7Kcsx6QSufioa2YgvBjkIiA0cM0qhotw==", + "requires": { + "csstype": "^3.1.0" + } + }, + "solid-refresh": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.4.1.tgz", + "integrity": "sha512-v3tD/OXQcUyXLrWjPW1dXZyeWwP7/+GQNs8YTL09GBq+5FguA6IejJWUvJDrLIA4M0ho9/5zK2e9n+uy+4488g==", + "dev": true, + "requires": { + "@babel/generator": "^7.18.2", + "@babel/helper-module-imports": "^7.16.7", + "@babel/types": "^7.18.4" + } + }, "sortablejs": { "version": "1.10.2" }, @@ -9996,6 +10253,20 @@ "workbox-window": "^6.5.4" } }, + "vite-plugin-solid": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.4.0.tgz", + "integrity": "sha512-Rr+t2sr9TWIvH16yzBZzx6O9YSpYAvcwKUMPqbi/4iU3mRumXQ4O10i1XGtQIynC7U3XwJsMzAJigDFGbiJBiw==", + "dev": true, + "requires": { + "@babel/core": "^7.18.6", + "@babel/preset-typescript": "^7.18.6", + "babel-preset-solid": "^1.4.6", + "merge-anything": "^5.0.2", + "solid-refresh": "^0.4.1", + "vitefu": "^0.1.1" + } + }, "vite-plugin-vue2": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/vite-plugin-vue2/-/vite-plugin-vue2-2.0.2.tgz", @@ -10055,6 +10326,13 @@ } } }, + "vitefu": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.1.1.tgz", + "integrity": "sha512-HClD14fjMJ+NQgXBqT3dC3RdO/+Chayil+cCPYZKY3kT+KcJomKzrdgzfCHJkIL2L0OAY+VPvrSW615iPtc7ag==", + "dev": true, + "requires": {} + }, "vitest": { "version": "0.15.2", "requires": { diff --git a/package.json b/package.json index a62984c38..ba5a6f122 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bridge", - "version": "2.4.2", + "version": "2.5.0", "private": true, "scripts": { "dev": "vite", @@ -13,6 +13,7 @@ }, "dependencies": { "@mdi/font": "^6.9.96", + "@tauri-apps/api": "^1.2.0", "@types/lz-string": "^1.3.34", "bridge-common-utils": "^0.3.3", "bridge-iframe-api": "^0.4.11", @@ -32,12 +33,13 @@ "jsonc-parser": "^3.2.0", "lodash-es": "^4.17.20", "lz-string": "^1.4.4", - "mc-project-core": "^0.3.22", + "mc-project-core": "^0.3.23", "molang": "^1.13.11", "monaco-editor": "^0.33.0", "path-browserify": "^1.0.1", "prismarine-nbt": "^1.6.0", "quick-score": "^0.0.13", + "solid-js": "^1.6.2", "tga-js": "^1.1.1", "three": "^0.139.2", "uuid": "^8.3.1", @@ -66,6 +68,7 @@ "vite": "^3.2.3", "vite-plugin-ejs": "^1.4.3", "vite-plugin-pwa": "^0.13.3", + "vite-plugin-solid": "^2.4.0", "vite-plugin-vue2": "^2.0.2" } } diff --git a/public/changelog.html b/public/changelog.html index e3ee008f0..6332d5f13 100644 --- a/public/changelog.html +++ b/public/changelog.html @@ -1,26 +1,17 @@

Features:

- +

Color Picker & Preview (#614)

+

bridge. now supports previewing colors within JSON files inline and editing them conveniently with a color picker.

+

image

Changes:

Fixes:

diff --git a/public/packages.zip b/public/packages.zip index 291ec177a..5f9ccb29d 100644 Binary files a/public/packages.zip and b/public/packages.zip differ diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore new file mode 100644 index 000000000..f4dfb82b2 --- /dev/null +++ b/src-tauri/.gitignore @@ -0,0 +1,4 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 000000000..5e841c11e --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,3928 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + +[[package]] +name = "atk" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" +dependencies = [ + "atk-sys", + "bitflags", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "attohttpc" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7" +dependencies = [ + "flate2", + "http", + "log", + "native-tls", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bridge" +version = "0.0.0" +dependencies = [ + "chrono", + "discord-rich-presence", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tokio", + "window-shadows", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "bytemuck" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cairo-rs" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "cargo_toml" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa0e3586af56b3bfa51fca452bd56e8dbbbd5d8d81cbf0b7e4e35b695b537eb8" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "cc" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c" +dependencies = [ + "byteorder", + "uuid 0.8.2", +] + +[[package]] +name = "cfg-expr" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-expr" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.44", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "cxx" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dbus" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "deflate" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "discord-rich-presence" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47fc4beffb85ee1461588499073a4d9c20dcc7728c4b13d6b282ab6c508947e5" +dependencies = [ + "serde", + "serde_derive", + "serde_json", + "uuid 0.8.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "dtoa-short" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "field-offset" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +dependencies = [ + "memoffset", + "rustc_version 0.3.3", +] + +[[package]] +name = "filetime" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.42.0", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide 0.5.4", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +dependencies = [ + "bitflags", + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "gdk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps 6.0.3", +] + +[[package]] +name = "gdkx11-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps 6.0.3", + "x11", +] + +[[package]] +name = "generator" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc184cace1cea8335047a471cc1da80f18acf8a76f3bab2028d499e328948ec7" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows 0.32.0", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gio" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-io", + "gio-sys", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.3", + "winapi", +] + +[[package]] +name = "glib" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +dependencies = [ + "anyhow", + "heck 0.4.0", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +dependencies = [ + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "gobject-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "gtk" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +dependencies = [ + "atk", + "bitflags", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "once_cell", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps 6.0.3", +] + +[[package]] +name = "gtk3-macros" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" +dependencies = [ + "anyhow", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "html5ever" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.4", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "ico" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a4b3331534254a9b64095ae60d3dc2a8225a7a70229cd5888be127cdc1f6804" +dependencies = [ + "byteorder", + "png 0.11.0", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "infer" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" +dependencies = [ + "cfb", +] + +[[package]] +name = "inflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4" +dependencies = [ + "adler32", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "javascriptcore-rs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" +dependencies = [ + "bitflags", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d" +dependencies = [ + "serde", + "serde_json", + "treediff", +] + +[[package]] +name = "kuchiki" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" +dependencies = [ + "cssparser", + "html5ever", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "libdbus-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mac-notification-sys" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e72d50edb17756489e79d52eb146927bec8eba9dd48faadf9ef08bca3791ad5" +dependencies = [ + "cc", + "dirs-next", + "objc-foundation", + "objc_id", + "time 0.3.17", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" +dependencies = [ + "log", + "phf 0.8.0", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minisign-verify" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.42.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "notify-rust" +version = "4.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368e89ea58df747ce88be669ae44e79783c1d30bfd540ad0fc520b3f41f0b3b0" +dependencies = [ + "dbus", + "mac-notification-sys", + "tauri-winrt-notification", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "open" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a3100141f1733ea40b53381b0ae3117330735ef22309a190ac57b9576ea716" +dependencies = [ + "pathdiff", + "windows-sys 0.36.1", +] + +[[package]] +name = "openssl" +version = "0.10.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_info" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4750134fb6a5d49afc80777394ad5d95b04bc12068c6abb92fae8f43817270f" +dependencies = [ + "log", + "serde", + "winapi", +] + +[[package]] +name = "os_pipe" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dceb7e43f59c35ee1548045b2c72945a5a3bb6ce6d6f07cdc13dc8f6bc4930a" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pango" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +dependencies = [ + "bitflags", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.42.0", +] + +[[package]] +name = "paste" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pest" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "plist" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +dependencies = [ + "base64", + "indexmap", + "line-wrap", + "serde", + "time 0.3.17", + "xml-rs", +] + +[[package]] +name = "png" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925" +dependencies = [ + "bitflags", + "deflate", + "inflate", + "num-iter", +] + +[[package]] +name = "png" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +dependencies = [ + "bitflags", + "crc32fast", + "flate2", + "miniz_oxide 0.6.2", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +dependencies = [ + "cty", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.8", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rfd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.37.0", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.14", +] + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +dependencies = [ + "itoa 1.0.4", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.4", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared_child" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "soup2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" +dependencies = [ + "bitflags", + "gio", + "glib", + "libc", + "once_cell", + "soup2-sys", +] + +[[package]] +name = "soup2-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" +dependencies = [ + "bitflags", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" +dependencies = [ + "cfg-expr 0.9.1", + "heck 0.3.3", + "pkg-config", + "toml", + "version-compare 0.0.11", +] + +[[package]] +name = "system-deps" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" +dependencies = [ + "cfg-expr 0.11.0", + "heck 0.4.0", + "pkg-config", + "toml", + "version-compare 0.1.1", +] + +[[package]] +name = "tao" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42c460173627564bde252ca5ebf346ba5b37c5cee1a445782bacc8e9b8d38b5e" +dependencies = [ + "bitflags", + "cairo-rs", + "cc", + "cocoa", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "gdk", + "gdk-pixbuf", + "gdk-sys", + "gdkx11-sys", + "gio", + "glib", + "glib-sys", + "gtk", + "image", + "instant", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "paste", + "png 0.17.7", + "raw-window-handle", + "scopeguard", + "serde", + "unicode-segmentation", + "uuid 1.2.1", + "windows 0.39.0", + "windows-implement", + "x11-dl", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tauri" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac135e45c2923bd91edbb95a0d656f8d025389697e34d6d79166952bfa79c61c" +dependencies = [ + "anyhow", + "attohttpc", + "base64", + "cocoa", + "dirs-next", + "embed_plist", + "encoding_rs", + "flate2", + "futures-util", + "glib", + "glob", + "gtk", + "heck 0.4.0", + "http", + "ignore", + "minisign-verify", + "notify-rust", + "objc", + "once_cell", + "open", + "os_info", + "os_pipe", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", + "regex", + "rfd", + "semver 1.0.14", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "shared_child", + "state", + "tar", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "tempfile", + "thiserror", + "time 0.3.17", + "tokio", + "url", + "uuid 1.2.1", + "webkit2gtk", + "webview2-com", + "windows 0.39.0", + "zip", +] + +[[package]] +name = "tauri-build" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef796f49abc98e6de0abe1b655120addc9d82363d8fc2304e71a4177c25e783c" +dependencies = [ + "anyhow", + "cargo_toml", + "heck 0.4.0", + "json-patch", + "semver 1.0.14", + "serde_json", + "tauri-utils", + "winres", +] + +[[package]] +name = "tauri-codegen" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb77cf7bfe3d8f886e73a7fa6157587d015c599671180b76595c1aef175ba8" +dependencies = [ + "base64", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.7", + "proc-macro2", + "quote", + "regex", + "semver 1.0.14", + "serde", + "serde_json", + "sha2", + "tauri-utils", + "thiserror", + "time 0.3.17", + "uuid 1.2.1", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24f481b0b2acfc288ac78755f00ebea53992c7365a165af64cb5ae00806edea" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "syn", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-runtime" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc5d54c476defa5436e70e0d0a06e3cb0f49b6f863895995d5e3769411769cf" +dependencies = [ + "gtk", + "http", + "http-range", + "rand 0.8.5", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror", + "uuid 1.2.1", + "webview2-com", + "windows 0.39.0", +] + +[[package]] +name = "tauri-runtime-wry" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c55091701426c2519c7e9f1dc2dd33e533af4e75eae89cedc6995409351a2" +dependencies = [ + "cocoa", + "gtk", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", + "tauri-runtime", + "tauri-utils", + "uuid 1.2.1", + "webkit2gtk", + "webview2-com", + "windows 0.39.0", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64c9a09ba1538b8e67ae8c78c10904f36ce38d364bf7f089ec807032a826b02" +dependencies = [ + "brotli", + "ctor", + "glob", + "heck 0.4.0", + "html5ever", + "infer", + "json-patch", + "kuchiki", + "memchr", + "phf 0.10.1", + "proc-macro2", + "quote", + "semver 1.0.14", + "serde", + "serde_json", + "serde_with", + "thiserror", + "url", + "walkdir", + "windows 0.39.0", +] + +[[package]] +name = "tauri-winrt-notification" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b" +dependencies = [ + "quick-xml", + "strum", + "windows 0.39.0", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa 1.0.4", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "windows-sys 0.42.0", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "treediff" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +dependencies = [ + "serde_json", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "uuid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup2", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +dependencies = [ + "atk-sys", + "bitflags", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pango-sys", + "pkg-config", + "soup2-sys", + "system-deps 6.0.3", +] + +[[package]] +name = "webview2-com" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.39.0", + "windows-implement", +] + +[[package]] +name = "webview2-com-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "webview2-com-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" +dependencies = [ + "regex", + "serde", + "serde_json", + "thiserror", + "windows 0.39.0", + "windows-bindgen", + "windows-metadata", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-shadows" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c69eb48aac2da0199ea148c6f2c2610db8a0572b32a3dc857dec40ca22f1cec" +dependencies = [ + "cocoa", + "objc", + "raw-window-handle", + "windows-sys 0.36.1", +] + +[[package]] +name = "windows" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" +dependencies = [ + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", +] + +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + +[[package]] +name = "windows" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +dependencies = [ + "windows-implement", + "windows_aarch64_msvc 0.39.0", + "windows_i686_gnu 0.39.0", + "windows_i686_msvc 0.39.0", + "windows_x86_64_gnu 0.39.0", + "windows_x86_64_msvc 0.39.0", +] + +[[package]] +name = "windows-bindgen" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" +dependencies = [ + "windows-metadata", + "windows-tokens", +] + +[[package]] +name = "windows-implement" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" +dependencies = [ + "syn", + "windows-tokens", +] + +[[package]] +name = "windows-metadata" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows-tokens" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + +[[package]] +name = "windows_i686_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + +[[package]] +name = "windows_i686_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winres" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" +dependencies = [ + "toml", +] + +[[package]] +name = "wry" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d297b203eae65b095af16c02978b7932be1968012b4da7138390edf34dea5" +dependencies = [ + "base64", + "block", + "cocoa", + "core-graphics", + "crossbeam-channel", + "dunce", + "gdk", + "gio", + "glib", + "gtk", + "html5ever", + "http", + "kuchiki", + "libc", + "log", + "objc", + "objc_id", + "once_cell", + "serde", + "serde_json", + "sha2", + "soup2", + "tao", + "thiserror", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.39.0", + "windows-implement", +] + +[[package]] +name = "x11" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7ae97874a928d821b061fce3d1fc52f08071dd53c89a6102bc06efcac3b2908" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c83627bc137605acc00bb399c7b908ef460b621fc37c953db2b09f88c449ea6" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "zip" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 000000000..d66253de6 --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bridge" +version = "0.0.0" +description = "The IDE for Minecraft Add-Ons" +authors = ["you"] +license = "" +repository = "" +edition = "2021" +rust-version = "1.57" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +tauri-build = { version = "1.1", features = [] } + +[dependencies] +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +tauri = { version = "1.1", features = ["api-all", "updater"] } +discord-rich-presence = "0.2.3" +chrono = "0.4.23" +window-shadows = "0.2.0" +tokio = { version = "1.23", features = ["process", "io-util", "sync"] } + +[features] +# by default Tauri runs in production mode +# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL +default = [ "custom-protocol" ] +# this feature is used used for production builds where `devPath` points to the filesystem +# DO NOT remove this +custom-protocol = [ "tauri/custom-protocol" ] diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 000000000..795b9b7c8 --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 000000000..068cb65d7 Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 000000000..34babf993 Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 000000000..68dd39e64 Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 000000000..6d85c8a24 Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 000000000..f354b454d Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 000000000..041033a2a Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 000000000..f0488760a Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 000000000..5712d4a5a Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 000000000..d23f38073 Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 000000000..12ae0d0f3 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 000000000..3655f9c34 Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 000000000..43c4b59d9 Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 000000000..41390191a Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 000000000..b3275f94f Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 000000000..17d23d6d3 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 000000000..0875f9de5 Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/src/fs_extra.rs b/src-tauri/src/fs_extra.rs new file mode 100644 index 000000000..09e50063a --- /dev/null +++ b/src-tauri/src/fs_extra.rs @@ -0,0 +1,66 @@ +use std::path::Path; +use std::process::Command; +use std::{fs, io}; + +pub fn copy_dir_all(src: impl AsRef, dest: impl AsRef) -> io::Result<()> { + fs::create_dir_all(&dest)?; + + for entry in fs::read_dir(src)? { + let entry = entry?; + let file_type = entry.file_type()?; + + if file_type.is_dir() { + copy_dir_all(entry.path(), dest.as_ref().join(entry.file_name()))?; + } else { + fs::copy(entry.path(), dest.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} + +/** + * Copy a directory from src to dest + */ +#[tauri::command] +pub fn copy_directory(src: String, dest: String) -> Result<(), String> { + copy_dir_all(src, dest).map_err(|e| e.to_string()) +} + +/** + * Opens a file in the file explorer + */ +#[tauri::command] +pub async fn reveal_in_file_explorer(path: &str) -> Result<(), String> { + if cfg!(target_os = "windows") { + Command::new("explorer") + .args(["/select,", path]) // The comma after select is not a typo + .spawn() + .expect("Failed to open file explorer"); + } else if cfg!(target_os = "macos") { + Command::new("open") + .args(["-R", path]) + .spawn() + .expect("Failed to open finder"); + } else { + // TODO: Linux + } + + Ok(()) +} + +/** + * A function that returns when a file was last modified and its file data + */ +#[tauri::command] +pub async fn get_file_data(path: &str) -> Result<(u64, Vec), String> { + let metadata = fs::metadata(path).expect("Failed to get file metadata"); + let modified = metadata + .modified() + .expect("Failed to get file modified time") + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + let data = fs::read(path).expect("Failed to read file"); + + Ok((modified, data)) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 000000000..7b43a5d0b --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,82 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use discord_rich_presence::{activity, DiscordIpc, DiscordIpcClient}; +use tauri::{Manager, Menu}; +use terminal::AllTerminals; +use window_shadows::set_shadow; +mod fs_extra; +mod terminal; + +fn main() { + let mut menu = Menu::os_default(&"bridge. v2"); + + if cfg!(target_os = "windows") { + menu = Menu::new() + } + + tauri::Builder::default() + .manage::(AllTerminals(Default::default())) + .setup(|app| { + // `main` here is the window label; it is defined under `tauri.conf.json` + let main_window = app.get_window("main").unwrap(); + + if cfg!(target_os = "windows") { + set_shadow(&main_window, true).expect("Unable to set window shadow"); + } + + // Try to set Discord rich presence + match set_rich_presence() { + Ok(mut discord_client) => { + println!("Rich presence set!"); + + // listen to "tauri://destroyed" (emitted on the `main` window) + main_window.once("tauri://destroyed", move |_| { + println!("Window closes!"); + discord_client.close().expect("Failed to close Discord IPC"); + }); + } + Err(e) => { + println!("Error setting rich presence: {}", e); + } + }; + + Ok(()) + }) + .menu(menu) + .invoke_handler(tauri::generate_handler![ + fs_extra::reveal_in_file_explorer, + fs_extra::get_file_data, + fs_extra::copy_directory, + terminal::execute_command, + terminal::kill_command, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} + +fn set_rich_presence() -> Result> { + let mut client = DiscordIpcClient::new("1045743881393815552")?; + + let state_str = "Developing add-ons..."; + + client.connect()?; + client.set_activity( + activity::Activity::new() + .state(state_str) + .assets( + activity::Assets::new() + .large_image("logo_tile") + .large_text("bridge. v2"), + ) + .timestamps(activity::Timestamps::new().start(chrono::Utc::now().timestamp_millis())) + .buttons(vec![ + activity::Button::new("Open Editor", "https://editor.bridge-core.app/"), + // activity::Button::new("Read More...", "https://bridge-core.app/"), + ]), + )?; + + Ok(client) +} diff --git a/src-tauri/src/terminal.rs b/src-tauri/src/terminal.rs new file mode 100644 index 000000000..480a5acd5 --- /dev/null +++ b/src-tauri/src/terminal.rs @@ -0,0 +1,145 @@ +use std::collections::HashMap; +/** + * Backend for bridge.'s terminal + */ +use std::env; +use std::path::PathBuf; +use std::process::Stdio; +use std::sync::Arc; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + process::Command, + sync::Mutex, +}; + +// Manager extends Mutex to allow for thread-safe acces +#[derive(Default)] +pub struct AllTerminals(pub Arc>>); + +#[derive(Clone, serde::Serialize)] +struct MessagePayload { + // Kind is either stdout or stderr + message: String, +} + +#[tauri::command] +pub async fn execute_command( + window: tauri::Window, + state: tauri::State<'_, AllTerminals>, + cwd: String, + command: String, +) -> Result<(), String> { + // Set the current working directory + let cwd_path = PathBuf::from(cwd); + env::set_current_dir(&cwd_path).expect("Failed to set cwd"); + + let mut all_terminals = state.0.lock().await; + // This can be changed to take a dynamic key in the future in order to support multiple terminals + let maybe_child_id = all_terminals.get_mut(&*"current_child"); + + // Kill current child + if let Some(child_id) = maybe_child_id { + kill_process_with_id(child_id.to_string()) + } + + // Spawn command + let mut child = if cfg!(target_os = "windows") { + Command::new("cmd") + .kill_on_drop(true) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .args(["/C", &command]) + .spawn() + .expect("Failed to spawn command") + } else { + Command::new("sh") + .kill_on_drop(true) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .args(["-c", &command]) + .spawn() + .expect("Failed to spawn command") + }; + + let stdout = child + .stdout + .take() + .expect("Child did not have a handle to stdout"); + let stderr = child + .stderr + .take() + .expect("Child did not have a handle to stderr"); + + let mut stdout_reader = BufReader::new(stdout).lines(); + let mut stderr_reader = BufReader::new(stderr).lines(); + + let window_stderr = window.clone(); + let window_done = window.clone(); + + tokio::spawn(async move { + while let Some(line) = stdout_reader + .next_line() + .await + .expect("Failed to read line") + { + window + .emit("onStdoutMessage", MessagePayload { message: line }) + .expect("Failed to emit message"); + } + }); + tokio::spawn(async move { + while let Some(line) = stderr_reader + .next_line() + .await + .expect("Failed to read line") + { + window_stderr + .emit("onStderrMessage", MessagePayload { message: line }) + .expect("Failed to emit message"); + } + }); + + all_terminals.insert( + "current_child".to_string(), + child.id().expect("Failed to get child id").to_string(), + ); + tokio::spawn(async move { + child.wait().await.expect("Failed to wait on child process"); + window_done + .emit("onCommandDone", ()) + .expect("Failed to emit message"); + }); + + Ok(()) +} + +#[tauri::command] +pub async fn kill_command(state: tauri::State<'_, AllTerminals>) -> Result<(), String> { + let mut all_terminals = state.0.lock().await; + // This can be changed to take a dynamic key in the future in order to support multiple terminals + let maybe_child_id = all_terminals.get_mut(&*"current_child"); + + // Kill current child + if let Some(child_id) = maybe_child_id { + kill_process_with_id(child_id.to_string()) + } + + // Remove child from hashmap + all_terminals.remove(&*"current_child"); + + Ok(()) +} + +fn kill_process_with_id(id: String) { + if cfg!(target_os = "windows") { + std::process::Command::new("taskkill") + .args(["/F", "/PID", &id]) + .output() + .expect("Failed to kill process"); + } else { + std::process::Command::new("kill") + .args([&id]) + .output() + .expect("Failed to kill process"); + } +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 000000000..ad59440f9 --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,77 @@ +{ + "build": { + "beforeDevCommand": "VITE_IS_TAURI_APP=true npm run dev", + "beforeBuildCommand": "VITE_IS_TAURI_APP=true npm run build", + "devPath": "http://localhost:8080/", + "distDir": "../dist", + "withGlobalTauri": true + }, + "package": { + "productName": "bridge", + "version": "../package.json" + }, + "tauri": { + "allowlist": { + "all": true, + "fs": { + "scope": ["**/*"] + } + }, + "bundle": { + "active": true, + "category": "DeveloperTool", + "copyright": "", + "deb": { + "depends": [] + }, + "externalBin": [], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "identifier": "com.bridge.dev", + "longDescription": "", + "macOS": { + "entitlements": null, + "exceptionDomain": "", + "frameworks": [], + "providerShortName": null, + "signingIdentity": null + }, + "resources": [], + "shortDescription": "", + "targets": "all", + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + } + }, + "security": { + "csp": null + }, + "updater": { + "active": true, + "dialog": false, + "endpoints": [ + "https://github.com/bridge-core/editor/releases/latest/download/latest.json" + ], + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDJFQkNGOUQ2REIzMUU5MDQKUldRRTZUSGIxdm04TGtiQzFEaE1GdVBPbUJYUjJDc25rQjg3RjlyKzdya2RUZVN1RkZ1azVBVmcK" + }, + "windows": [ + { + "label": "main", + "fullscreen": false, + "height": 900, + "resizable": true, + "title": "", + "width": 1200, + "titleBarStyle": "Overlay", + "acceptFirstMouse": true + } + ] + } +} diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json new file mode 100644 index 000000000..8d0db1ce0 --- /dev/null +++ b/src-tauri/tauri.windows.conf.json @@ -0,0 +1,19 @@ +{ + "build": { + "beforeDevCommand": "SET VITE_IS_TAURI_APP=true && npm run dev", + "beforeBuildCommand": "SET VITE_IS_TAURI_APP=true && npm run build" + }, + "tauri": { + "windows": [ + { + "label": "main", + "decorations": false, + "fullscreen": false, + "height": 900, + "resizable": true, + "title": "", + "width": 1200 + } + ] + } +} diff --git a/src/App.ts b/src/App.ts index 1b8941e4a..f1841ac15 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,6 +1,5 @@ import '/@/components/Notifications/Errors' import '/@/components/Languages/LanguageManager' -import '/@/components/App/ServiceWorker' import Vue from 'vue' import { EventSystem } from '/@/components/Common/Event/EventSystem' @@ -39,15 +38,23 @@ import { PackExplorer } from '/@/components/PackExplorer/PackExplorer' import { PersistentNotification } from '/@/components/Notifications/PersistentNotification' import { version as appVersion } from '/@/utils/app/version' import { platform } from '/@/utils/os' -import { virtualProjectName } from '/@/components/Projects/Project/Project' import { AnyDirectoryHandle } from '/@/components/FileSystem/Types' import { getStorageDirectory } from '/@/utils/getStorageDirectory' import { FolderImportManager } from '/@/components/ImportFolder/Manager' import { StartParamManager } from '/@/components/StartParams/Manager' import { ViewFolders } from '/@/components/ViewFolders/ViewFolders' import { SidebarManager } from '/@/components/Sidebar/Manager' -import { ViewComMojangProject } from './components/OutputFolders/ComMojang/Sidebar/ViewProject' -import { InformationWindow } from './components/Windows/Common/Information/InformationWindow' +import { ViewComMojangProject } from '/@/components/OutputFolders/ComMojang/Sidebar/ViewProject' +import { InformationWindow } from '/@/components/Windows/Common/Information/InformationWindow' +import { BottomPanel } from '/@/components/BottomPanel/BottomPanel' + +if (import.meta.env.VITE_IS_TAURI_APP) { + // Import Tauri updater for native builds + import('./components/App/Tauri/TauriUpdater') +} else { + // Only import service worker for non-Tauri builds + import('/@/components/App/ServiceWorker') +} export class App { public static readonly windowState = new WindowState() @@ -92,6 +99,7 @@ export class App { public static readonly fileType = markRaw(new FileTypeLibrary()) public static readonly packType = markRaw(new PackTypeLibrary()) public static readonly sidebar = new SidebarManager() + public static readonly bottomPanel = markRaw(new BottomPanel()) public readonly mobile: Mobile @@ -166,11 +174,7 @@ export class App { // Only prompt in prod mode so we can use HMR in dev mode if (import.meta.env.PROD) { window.addEventListener('beforeunload', (event) => { - if ( - this.tabSystem?.hasUnsavedTabs || - this.taskManager.hasRunningTasks || - isUsingFileSystemPolyfill.value - ) { + if (this.shouldWarnBeforeClose) { event.preventDefault() event.returnValue = saveWarning return saveWarning @@ -179,6 +183,18 @@ export class App { } } + get shouldWarnBeforeClose() { + if ( + !import.meta.env.VITE_IS_TAURI_APP && + isUsingFileSystemPolyfill.value + ) + return true + + return ( + this.tabSystem?.hasUnsavedTabs || this.taskManager.hasRunningTasks + ) + } + static openUrl(url: string, id?: string, openInBrowser = false) { if (settingsState?.general?.openLinksInBrowser || openInBrowser) return window.open(url, '_blank') @@ -198,6 +214,13 @@ export class App { this.instance.fileSystem.setup(await getStorageDirectory()) + if (import.meta.env.VITE_IS_TAURI_APP) { + // TauriFs env -> bridge. folder is the same as getStorageDirectory() + this.instance.bridgeFolderSetup.dispatch() + // Load projects + this.instance.projectManager.loadProjects(true) + } + // Show changelog after an update if (await get('firstStartAfterUpdate')) { await set('firstStartAfterUpdate', false) @@ -271,7 +294,10 @@ export class App { } // Warn about saving projects - if (isUsingFileSystemPolyfill.value) { + if ( + isUsingFileSystemPolyfill.value && + !import.meta.env.VITE_IS_TAURI_APP + ) { const saveWarning = new PersistentNotification({ id: 'bridge-save-warning', icon: 'mdi-alert-circle-outline', diff --git a/src/App.vue b/src/App.vue index 0dced0aa6..0952382c1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -35,8 +35,6 @@ no-gutters class="d-flex fill-area" :class="{ - 'ml-2': !isSidebarContentVisible && isSidebarRight, - 'mr-2': !isSidebarContentVisible && !isSidebarRight, 'flex-row-reverse': isSidebarRight, }" > @@ -56,7 +54,7 @@ class="d-flex" :class="{ 'flex-column': $vuetify.breakpoint.mobile }" :style="{ - height: `calc(${windowSize.currentHeight}px - ${appToolbarHeight})`, + height: `calc(${windowSize.currentHeight}px - ${appToolbarHeight} - ${bottomPanelHeight}px)`, }" > - + + + @@ -120,6 +125,8 @@ import { setFullscreenElement, useFullScreen, } from './components/TabSystem/TabContextMenu/Fullscreen' +import BottomPanel from './components/BottomPanel/BottomPanel.vue' +import { useSidebarState } from './components/Composables/Sidebar/useSidebarState' export default { name: 'App', @@ -129,12 +136,17 @@ export default { const { tabSystem, tabSystems, shouldRenderWelcomeScreen } = useTabSystem() const { isInFullScreen } = useFullScreen() + const { isNavVisible, isContentVisible, isAttachedRight } = + useSidebarState() return { tabSystem, tabSystems, shouldRenderWelcomeScreen, isInFullScreen, + sidebarNavigationVisible: isNavVisible, + isSidebarContentVisible: isContentVisible, + isSidebarRight: isAttachedRight, } }, @@ -155,6 +167,7 @@ export default { TabSystem, WelcomeScreen, SidebarContent, + BottomPanel, }, data: () => ({ @@ -168,24 +181,6 @@ export default { }), computed: { - isSidebarContentVisible() { - if (this.isInFullScreen) return false - - return ( - this.sidebarNavigationVisible && - App.sidebar.isContentVisible.value - ) - }, - sidebarNavigationVisible() { - return App.sidebar.isNavigationVisible.value - }, - isSidebarRight() { - return ( - this.settingsState && - this.settingsState.sidebar && - this.settingsState.sidebar.isSidebarRight - ) - }, sidebarSize() { let size = this.settingsState && this.settingsState.sidebar @@ -212,6 +207,9 @@ export default { ? `${this.settingsState.appearance.font}, system-ui !important` : `Roboto, system-ui !important` }, + bottomPanelHeight() { + return App.bottomPanel.currentHeight.value + }, }, methods: { openSidebar() { @@ -311,6 +309,9 @@ summary::-webkit-details-marker { .cursor-pointer { cursor: pointer; } +.no-pointer-events { + pointer-events: none; +} .outlined { border-width: thin; border-color: #555555; diff --git a/src/components/App/ServiceWorker.ts b/src/components/App/ServiceWorker.ts index 363fbbbb7..45b7aa0f2 100644 --- a/src/components/App/ServiceWorker.ts +++ b/src/components/App/ServiceWorker.ts @@ -2,7 +2,6 @@ import { registerSW } from 'virtual:pwa-register' import { createNotification } from '/@/components/Notifications/create' -import { App } from '/@/App' import { set } from 'idb-keyval' const updateSW = registerSW({ diff --git a/src/components/App/Tauri/TauriUpdater.ts b/src/components/App/Tauri/TauriUpdater.ts new file mode 100644 index 000000000..c4c4ae203 --- /dev/null +++ b/src/components/App/Tauri/TauriUpdater.ts @@ -0,0 +1,27 @@ +import { createNotification } from '../../Notifications/create' +import { checkUpdate, installUpdate } from '@tauri-apps/api/updater' +import { relaunch } from '@tauri-apps/api/process' + +checkUpdate() + .then((update) => { + if (update.shouldUpdate) { + const notification = createNotification({ + icon: 'mdi-update', + color: 'primary', + message: 'sidebar.notifications.updateAvailable.message', + textColor: 'white', + onClick: async () => { + // Dispose the notification + notification.dispose() + // Install the update + await installUpdate() + // ...and finally relaunch the app + await relaunch() + }, + }) + } + }) + .catch((err: any) => { + console.error(`[TauriUpdater] ${err}`) + return null + }) diff --git a/src/components/BottomPanel/BottomPanel.css b/src/components/BottomPanel/BottomPanel.css new file mode 100644 index 000000000..23df6630a --- /dev/null +++ b/src/components/BottomPanel/BottomPanel.css @@ -0,0 +1,50 @@ +.bottom-panel-content { + height: 100%; +} + +/* Tab bar */ +.bottom-panel-tab-bar { + width: 100%; + overflow-x: auto; + overflow-y: visible; + padding: 4px 0; +} +.bottom-panel-tab-bar::-webkit-scrollbar { + display: none; +} + +/* Tabs */ +.bottom-panel-tab { + display: flex; + align-items: center; + font-size: 12px; + font-weight: 500; + letter-spacing: 1.25px; + + cursor: pointer; + padding: 1px 8px; + border-radius: 8px; + background: var(--v-sidebarSelection-base); + text-transform: uppercase; + + transition: transform 0.1s ease-in-out; +} +.bottom-panel-tab i { + font-size: 18px !important; +} +.bottom-panel-tab-active { + cursor: default; + z-index: 1; + background: var(--v-primary-base); + transform: scale(1.1); +} + +/* Panel content */ +.bottom-panel-content-container { + /* Full height - tab bar height - tab bar padding - vertical padding */ + height: calc(100% - 39px - 8px); + overflow-y: auto; +} +.bottom-panel-content-container-full-height { + height: 100%; +} diff --git a/src/components/BottomPanel/BottomPanel.tsx b/src/components/BottomPanel/BottomPanel.tsx new file mode 100644 index 000000000..7cceb52c5 --- /dev/null +++ b/src/components/BottomPanel/BottomPanel.tsx @@ -0,0 +1,84 @@ +import { computed, ref } from 'vue' +import { JSX } from 'solid-js/types' +import './BottomPanel.css' +import { LogPanel } from '../Compiler/LogPanel/Panel' +import { App } from '/@/App' + +interface ITab { + name: string + icon: string + component: () => JSX.Element +} + +export class BottomPanel { + public readonly isVisible = ref(false) + public readonly height = ref(400) + public readonly tabs = ref([]) + public readonly activeTab = ref(null) + public readonly currentHeight = computed(() => + this.isVisible.value ? this.height.value : 0 + ) + + constructor() { + this.setupTerminal() + + this.addTab({ + icon: 'mdi-bug', + name: 'bottomPanel.problems.name', + component: () => ( +
+ We are still working on displaying problems with your + project here... +
+ ), + }) + + setTimeout(() => { + App.getApp().then((app) => { + this.addTab({ + icon: 'mdi-cogs', + name: 'bottomPanel.compiler.name', + component: () => + LogPanel({ + compilerWindow: app.windows.compilerWindow, + }), + }) + }) + }) + } + + async setupTerminal() { + if (!import.meta.env.VITE_IS_TAURI_APP) return + const { Terminal } = await import('./Terminal/Terminal') + const { TerminalInput } = await import('./Terminal/Input') + const { TerminalOutput } = await import('./Terminal/Output') + + const terminal = new Terminal() + + this.addTab( + { + icon: 'mdi-console-line', + name: 'bottomPanel.terminal.name', + component: () => ( + <> + + + + ), + }, + true + ) + } + + selectTab(tab: ITab) { + this.activeTab.value = tab + } + addTab(tab: ITab, asFirst = false) { + if (asFirst) this.tabs.value.unshift(tab) + else this.tabs.value.push(tab) + + if (this.activeTab.value === null || asFirst) { + this.activeTab.value = tab + } + } +} diff --git a/src/components/BottomPanel/BottomPanel.vue b/src/components/BottomPanel/BottomPanel.vue new file mode 100644 index 000000000..945fa8c75 --- /dev/null +++ b/src/components/BottomPanel/BottomPanel.vue @@ -0,0 +1,64 @@ + + + diff --git a/src/components/BottomPanel/PanelContent.tsx b/src/components/BottomPanel/PanelContent.tsx new file mode 100644 index 000000000..5ae27e1ed --- /dev/null +++ b/src/components/BottomPanel/PanelContent.tsx @@ -0,0 +1,31 @@ +import { Component, Show } from 'solid-js' +import { Dynamic } from 'solid-js/web' +import { toSignal } from '../Solid/toSignal' +import { toVue } from '../Solid/toVue' +import { TabBar } from './TabBar' +import { App } from '/@/App' + +export const PanelContent: Component = (props) => { + const [activeTab] = toSignal(App.bottomPanel.activeTab) + const [tabs] = toSignal(App.bottomPanel.tabs) + + return ( +
+ 1}> + + + +
+ +
+
+ ) +} + +export const VuePanelContent = toVue(PanelContent) diff --git a/src/components/BottomPanel/TabBar.tsx b/src/components/BottomPanel/TabBar.tsx new file mode 100644 index 000000000..0f914443d --- /dev/null +++ b/src/components/BottomPanel/TabBar.tsx @@ -0,0 +1,47 @@ +import { Component, For } from 'solid-js' +import { useTranslations } from '../Composables/useTranslations' +import { useRipple } from '../Solid/Directives/Ripple/Ripple' +import { SolidIcon } from '../Solid/Icon/SolidIcon' +import { SolidIconButton } from '../Solid/Inputs/IconButton/IconButton' +import { SolidSpacer } from '../Solid/SolidSpacer' +import { toSignal } from '../Solid/toSignal' +import { App } from '/@/App' + +export const TabBar: Component = (props) => { + const ripple = useRipple() + const { t } = useTranslations() + const [tabs] = toSignal(App.bottomPanel.tabs) + const [activeTab] = toSignal(App.bottomPanel.activeTab) + const [_, setIsVisible] = toSignal(App.bottomPanel.isVisible) + + return ( +
+
+ + {(tab, i) => ( +
0, + }} + onClick={() => App.bottomPanel.selectTab(tab)} + > + + {t(tab.name)} +
+ )} +
+
+ + + + setIsVisible(false)} + /> +
+ ) +} diff --git a/src/components/BottomPanel/Terminal/Input.tsx b/src/components/BottomPanel/Terminal/Input.tsx new file mode 100644 index 000000000..93c143ea2 --- /dev/null +++ b/src/components/BottomPanel/Terminal/Input.tsx @@ -0,0 +1,47 @@ +import { Component, createSignal, Show } from 'solid-js' +import { useTranslations } from '../../Composables/useTranslations' +import { SolidIconButton } from '../../Solid/Inputs/IconButton/IconButton' +import { TextField } from '../../Solid/Inputs/TextField/TextField' +import { toSignal } from '../../Solid/toSignal' +import type { Terminal } from './Terminal' + +export const TerminalInput: Component<{ + terminal: Terminal +}> = (props) => { + const { t } = useTranslations() + const [input, setInput] = createSignal('') + const [hasRunningTask] = toSignal(props.terminal.hasRunningTask) + const [output, setOutput] = toSignal(props.terminal.output) + + const onEnter = () => { + props.terminal.executeCommand(input()) + setInput('') + } + + return ( +
+ + + props.terminal.killCommand()} + /> + + setOutput([])} + /> +
+ ) +} diff --git a/src/components/BottomPanel/Terminal/Output.tsx b/src/components/BottomPanel/Terminal/Output.tsx new file mode 100644 index 000000000..f6b13eb67 --- /dev/null +++ b/src/components/BottomPanel/Terminal/Output.tsx @@ -0,0 +1,59 @@ +import { Component, For, Show } from 'solid-js' +import { SolidIcon } from '../../Solid/Icon/SolidIcon' +import { toSignal } from '../../Solid/toSignal' +import type { Terminal } from './Terminal' + +export const TerminalOutput: Component<{ + terminal: Terminal +}> = (props) => { + const [output] = toSignal(props.terminal.output) + const [cwd] = toSignal(props.terminal.cwd) + + const prettyCwd = () => { + const tmp = cwd() + .replace(props.terminal.baseCwd, '') + .replace(/\\/g, '/') + if (tmp === '') return 'bridge' + return `bridge${tmp}` + } + + return ( + <> + {/* Show current cwd */} + + + + + + {prettyCwd()} + + + {/* Render terminal output */} +
+ + {({ kind, time, currentCwdName, msg }, i) => ( +
+ [{time}] + + + {currentCwdName} >  + + {msg} + +
+ )} +
+
+ + ) +} diff --git a/src/components/BottomPanel/Terminal/Terminal.css b/src/components/BottomPanel/Terminal/Terminal.css new file mode 100644 index 000000000..b9e13da8d --- /dev/null +++ b/src/components/BottomPanel/Terminal/Terminal.css @@ -0,0 +1,11 @@ +.terminal-line { + line-break: normal; +} + +.terminal-output-container { + /* full height - terminal input bar height - cwd display height - a few pixels to prevent double scrollbar */ + height: calc(100% - 58px - 33px - 2px); + overflow-y: auto; + display: flex; + flex-direction: column-reverse; +} diff --git a/src/components/BottomPanel/Terminal/Terminal.ts b/src/components/BottomPanel/Terminal/Terminal.ts new file mode 100644 index 000000000..c9832a536 --- /dev/null +++ b/src/components/BottomPanel/Terminal/Terminal.ts @@ -0,0 +1,110 @@ +import { markRaw, ref } from 'vue' +import { invoke } from '@tauri-apps/api/tauri' +import { App } from '/@/App' +import { Signal } from '../../Common/Event/Signal' +import { appLocalDataDir, isAbsolute, join, sep } from '@tauri-apps/api/path' +import { exists } from '@tauri-apps/api/fs' +import { listen, Event } from '@tauri-apps/api/event' +import './Terminal.css' + +type TMessageKind = 'stdout' | 'stderr' | 'stdin' + +interface IMessage { + // Format HH:MM:SS + time: string + kind: TMessageKind + currentCwdName: string + msg: string +} + +interface IMessagePayload { + message: string +} + +export class Terminal { + output = ref([]) + hasRunningTask = ref(false) + baseCwd = '' + cwd = ref('') + setupDone = markRaw(new Signal()) + + constructor() { + setTimeout(() => this.setup()) + + listen('onStdoutMessage', ({ payload }: Event) => { + this.addToOutput(payload.message, 'stdout') + }) + listen('onStderrMessage', ({ payload }: Event) => { + this.addToOutput(payload.message, 'stderr') + }) + listen('onCommandDone', () => { + this.hasRunningTask.value = false + }) + } + + async setup() { + const app = await App.getApp() + await app.projectManager.projectReady.fired + + this.baseCwd = await join(await appLocalDataDir(), 'bridge') + + if (this.cwd.value === '') this.cwd.value = this.baseCwd + + this.setupDone.dispatch() + } + + addToOutput(msg: string, kind: TMessageKind) { + this.output.value.unshift({ + time: new Date().toLocaleTimeString(), + kind, + currentCwdName: this.cwd.value.split(sep).pop()!, + // Replace ANSI escape codes + msg: msg.replace(/\x1b\[[0-9;]*m/g, ''), + }) + } + + protected async handleCdCommand(command: string) { + const path = command.substring(3) + const prevCwd = this.cwd.value + + if (await isAbsolute(path)) { + this.cwd.value = path + } else { + this.cwd.value = await join(this.cwd.value, path) + } + + // Confirm that the path exists + if (!(await exists(this.cwd.value))) { + this.cwd.value = prevCwd + this.addToOutput(`cd: no such directory`, 'stderr') + } + + // Ensure that cwd doesn't leave the baseCwd + if (!this.cwd.value.startsWith(this.baseCwd)) { + this.cwd.value = prevCwd + this.addToOutput(`cd: Permission denied`, 'stderr') + } + } + + async executeCommand(command: string) { + this.addToOutput(command, 'stdin') + + if (command.startsWith('cd ')) { + await this.handleCdCommand(command) + return + } + + this.hasRunningTask.value = true + + await this.setupDone.fired + + await invoke('execute_command', { + cwd: this.cwd.value, + command, + }) + } + + async killCommand() { + await invoke('kill_command') + } +} diff --git a/src/components/CommandBar/CommandBar.vue b/src/components/CommandBar/CommandBar.vue index d25fc381e..50fa4d079 100644 --- a/src/components/CommandBar/CommandBar.vue +++ b/src/components/CommandBar/CommandBar.vue @@ -14,7 +14,7 @@ 'min-width': 'unset', 'max-width': '500px', transition: 'slide-y-transition', - contentClass: 'commandbar-menu', + contentClass: 'commandbar-menu elevation-4', }" :items="actions" :item-text="(item) => `${t(item.name)}\n${t(item.description)}`" diff --git a/src/components/Common/GlobalMutex.ts b/src/components/Common/GlobalMutex.ts index 25f08bddb..208529610 100644 --- a/src/components/Common/GlobalMutex.ts +++ b/src/components/Common/GlobalMutex.ts @@ -33,6 +33,14 @@ export class GlobalMutex { throw new Error('Trying to unlock a mutex that does not exist') } + // Store whether mutex still has listeners + const hasListeners = mutex.hasListeners() + mutex.unlock() + + // Clean up map if no more listeners + if (!hasListeners) { + this.mutexMap.delete(key) + } } } diff --git a/src/components/Common/Mutex.ts b/src/components/Common/Mutex.ts index 844a34999..b457a7d8d 100644 --- a/src/components/Common/Mutex.ts +++ b/src/components/Common/Mutex.ts @@ -44,4 +44,8 @@ export class Mutex { listener() } } + + hasListeners() { + return this.listeners.length > 0 + } } diff --git a/src/components/Common/WindowResize.ts b/src/components/Common/WindowResize.ts index f2e433266..de20893ff 100644 --- a/src/components/Common/WindowResize.ts +++ b/src/components/Common/WindowResize.ts @@ -4,7 +4,7 @@ import { reactive } from 'vue' import { App } from '/@/App' export class WindowResize extends EventDispatcher<[number, number]> { - protected state = reactive({ + public readonly state = reactive({ currentHeight: window.innerHeight, currentWidth: window.innerWidth, }) diff --git a/src/components/Compiler/Compiler.ts b/src/components/Compiler/Compiler.ts index 2366fdfa1..ac532dd74 100644 --- a/src/components/Compiler/Compiler.ts +++ b/src/components/Compiler/Compiler.ts @@ -1,6 +1,9 @@ import type { DashService, ICompilerOptions } from './Worker/Service' import CompilerWorker from './Worker/Service?worker' import { wrap } from 'comlink' +import { setupWorker } from '/@/utils/worker/setup' const worker = new CompilerWorker() export const DashCompiler = wrap(worker) + +setupWorker(worker) diff --git a/src/components/Compiler/LogPanel/Panel.tsx b/src/components/Compiler/LogPanel/Panel.tsx new file mode 100644 index 000000000..6f7624899 --- /dev/null +++ b/src/components/Compiler/LogPanel/Panel.tsx @@ -0,0 +1,61 @@ +import { Component, For, Show } from 'solid-js' +import { useTranslations } from '../../Composables/useTranslations' +import { SolidIcon } from '../../Solid/Icon/SolidIcon' +import { toSignal } from '../../Solid/toSignal' +import { CompilerWindow } from '../Window/Window' +import { ILogData } from '../Worker/Console' + +export const LogPanel: Component<{ + compilerWindow: CompilerWindow +}> = (props) => { + const { t } = useTranslations() + const [log] = toSignal<[string, ILogData][]>( + props.compilerWindow.getCategories().logs.data + ) + + const icon = (type: string | undefined) => { + if (type === 'info') return 'mdi-information-outline' + if (type === 'warning') return 'mdi-alert-outline' + if (type === 'error') return 'mdi-alert-circle-outline' + + return null + } + + return ( + <> + +
+ {t('bottomPanel.compiler.noLogs')} +
+
+ + + {([logEntry, { time, type }]) => ( +
+ + [{time}] + + + + + + + + {logEntry} + +
+ )} +
+ + ) +} diff --git a/src/components/Compiler/Sidebar/create.ts b/src/components/Compiler/Sidebar/create.ts index 42f9d647f..b3fcce498 100644 --- a/src/components/Compiler/Sidebar/create.ts +++ b/src/components/Compiler/Sidebar/create.ts @@ -32,11 +32,15 @@ export function createCompilerSidebar() { await project.compilerReady.fired project.compilerService.onConsoleUpdate( proxy(async () => { - let logs = await project.compilerService.getCompilerLogs() - logs = logs.filter( + const allLogs = await project.compilerService.getCompilerLogs() + const logs = allLogs.filter( ([_, { type }]) => type === 'error' || type === 'warning' ) + const app = await App.getApp() + app.windows.compilerWindow.getCategories().logs.data.value = + allLogs + state.currentCount = logs.length // User currently still has logs tab selected and therefore sees the new logs diff --git a/src/components/Compiler/Window/Content.vue b/src/components/Compiler/Window/Content.vue index 5f23de62c..a2d3bb0e7 100644 --- a/src/components/Compiler/Window/Content.vue +++ b/src/components/Compiler/Window/Content.vue @@ -16,6 +16,7 @@ v-if="categories[selectedSidebar]" :is="categories[selectedSidebar].component" :data="categories[selectedSidebar].data.value" + @closeWindow="onClose" /> diff --git a/src/components/Compiler/Window/OutputFolders.vue b/src/components/Compiler/Window/OutputFolders.vue index 101f7e6d2..5d9736861 100644 --- a/src/components/Compiler/Window/OutputFolders.vue +++ b/src/components/Compiler/Window/OutputFolders.vue @@ -1,16 +1,68 @@ diff --git a/src/components/Compiler/Window/Window.ts b/src/components/Compiler/Window/Window.ts index 683400b7e..1f907cc30 100644 --- a/src/components/Compiler/Window/Window.ts +++ b/src/components/Compiler/Window/Window.ts @@ -144,6 +144,10 @@ export class CompilerWindow extends NewBaseWindow { }) } + getCategories() { + return this.categories + } + async reload() { const app = await App.getApp() @@ -206,7 +210,6 @@ export class CompilerWindow extends NewBaseWindow { const service = await project.createDashService( 'production' ) - await service.setup() await service.build() }, }, @@ -240,7 +243,6 @@ export class CompilerWindow extends NewBaseWindow { 'production', `${project.projectPath}/.bridge/compiler/${entry.name}` ) - await service.setup() await service.build() }, }) @@ -256,7 +258,10 @@ export class CompilerWindow extends NewBaseWindow { const { hasComMojang, didDenyPermission } = comMojang.status let panelConfig: IPanelOptions - if (isUsingFileSystemPolyfill.value) { + if ( + !import.meta.env.VITE_IS_TAURI_APP && + isUsingFileSystemPolyfill.value + ) { panelConfig = { text: 'comMojang.status.notAvailable', type: 'error', @@ -276,7 +281,9 @@ export class CompilerWindow extends NewBaseWindow { } } else if (!hasComMojang) { panelConfig = { - text: 'comMojang.status.notSetup', + text: import.meta.env.VITE_IS_TAURI_APP + ? 'comMojang.status.notSetupTauri' + : 'comMojang.status.notSetup', type: 'error', isDismissible: false, } diff --git a/src/components/Compiler/Worker/Console.ts b/src/components/Compiler/Worker/Console.ts index c4a92100d..e5792ddc4 100644 --- a/src/components/Compiler/Worker/Console.ts +++ b/src/components/Compiler/Worker/Console.ts @@ -1,6 +1,8 @@ import { Console } from 'dash-compiler' -interface ILogData { +export interface ILogData { + // Format: HH:MM:SS + time: string type?: 'info' | 'error' | 'warning' } @@ -12,7 +14,10 @@ export class ForeignConsole extends Console { return this.logs } - protected basicLog(message: any, { type }: ILogData = {}) { + protected basicLog( + message: any, + { type }: { type?: 'info' | 'error' | 'warning' } = {} + ) { switch (type) { case 'warning': console.warn(message) @@ -31,7 +36,7 @@ export class ForeignConsole extends Console { this.logs.unshift([ typeof message === 'string' ? message : JSON.stringify(message), - { type }, + { time: new Date().toLocaleTimeString(), type }, ]) this.logsChanged() } diff --git a/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts b/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts index 288a71469..9f1ddfeb7 100644 --- a/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts +++ b/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts @@ -1,9 +1,12 @@ +import { Remote } from 'comlink' import { Component, DefaultConsole } from 'dash-compiler' +import { DashService } from '../../Service' import { App } from '/@/App' import { JsRuntime } from '/@/components/Extensions/Scripts/JsRuntime' import { AnyDirectoryHandle } from '/@/components/FileSystem/Types' +import { Project } from '/@/components/Projects/Project/Project' import { IDisposable } from '/@/types/disposable' -import { iterateDir } from '/@/utils/iterateDir' +import { iterateDir, iterateDirParallel } from '/@/utils/iterateDir' export const supportsCustomComponents = ['block', 'item', 'entity'] export type TComponentFileType = typeof supportsCustomComponents[number] @@ -15,14 +18,17 @@ export class ComponentSchemas { } protected schemaLookup = new Map() protected disposables?: IDisposable[] + protected dash?: Remote - constructor() {} + constructor(protected project: Project) {} get(fileType: TComponentFileType) { return this.schemas[fileType] } async activate() { + this.dash = await this.project.createDashService('development') + this.disposables = [ App.eventSystem.on( 'fileSave', @@ -44,9 +50,8 @@ export class ComponentSchemas { const fileType = filePath .replace(componentPath + '/', '') .split('/')[0] as TComponentFileType - const isSupportedFileType = supportsCustomComponents.includes( - fileType - ) + const isSupportedFileType = + supportsCustomComponents.includes(fileType) if (!isSupportedFileType && v1CompatMode) { // This is not a supported file type but v1CompatMode is enabled, @@ -102,22 +107,21 @@ export class ComponentSchemas { jsRuntime: JsRuntime, fileType: TComponentFileType ) { - const app = await App.getApp() - const project = app.project - await (await project.compilerService.completedStartUp).fired - - const v1CompatMode = project.config.get().bridge?.v1CompatMode ?? false + const v1CompatMode = + this.project.config.get().bridge?.v1CompatMode ?? false const fromFilePath = v1CompatMode - ? project.config.resolvePackPath('behaviorPack', 'components') - : project.config.resolvePackPath( + ? this.project.config.resolvePackPath('behaviorPack', 'components') + : this.project.config.resolvePackPath( 'behaviorPack', `components/${fileType}` ) let baseDir: AnyDirectoryHandle try { - baseDir = await app.fileSystem.getDirectoryHandle(fromFilePath) + baseDir = await this.project.app.fileSystem.getDirectoryHandle( + fromFilePath + ) } catch { return {} } @@ -125,7 +129,7 @@ export class ComponentSchemas { // Reset schemas this.schemas[fileType] = {} - await iterateDir( + await iterateDirParallel( baseDir, async (fileHandle, filePath) => { await this.evalComponentSchema( @@ -148,14 +152,14 @@ export class ComponentSchemas { file: File, v1CompatMode: boolean ) { - const app = await App.getApp() - const project = app.project - await (await project.compilerService.completedStartUp).fired + let fileContent = new Uint8Array(await file.arrayBuffer()) + + if (filePath.endsWith('.ts')) { + fileContent = ( + await this.dash!.compileFile(filePath, fileContent) + )[1] + } - const [_, fileContent] = await project.compilerService.compileFile( - filePath, - new Uint8Array(await file.arrayBuffer()) - ) const transformedFile = new File([fileContent], file.name) const component = new Component( new DefaultConsole(), diff --git a/src/components/Compiler/Worker/Service.ts b/src/components/Compiler/Worker/Service.ts index bccbf89a3..ee65037e8 100644 --- a/src/components/Compiler/Worker/Service.ts +++ b/src/components/Compiler/Worker/Service.ts @@ -1,18 +1,11 @@ -// @ts-ignore Make "path" work on this worker -globalThis.process = { - cwd: () => '', - env: {}, - release: { - name: 'browser', - }, -} +import '/@/utils/worker/inject' import '/@/components/FileSystem/Virtual/Comlink' import { expose } from 'comlink' import { FileTypeLibrary, IFileType } from '/@/components/Data/FileType' import { DataLoader } from '/@/components/Data/DataLoader' import type { AnyDirectoryHandle } from '/@/components/FileSystem/Types' -import { Dash, initRuntimes } from 'dash-compiler' +import { Dash, initRuntimes, FileSystem } from 'dash-compiler' import { PackTypeLibrary } from '/@/components/Data/PackType' import { DashFileSystem } from './FileSystem' import { Signal } from '/@/components/Common/Event/Signal' @@ -21,6 +14,8 @@ import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher' import { ForeignConsole } from './Console' import { Mutex } from '../../Common/Mutex' import wasmUrl from '@swc/wasm-web/wasm-web_bg.wasm?url' +import { VirtualDirectoryHandle } from '../../FileSystem/Virtual/DirectoryHandle' +import { TauriFsStore } from '../../FileSystem/Virtual/Stores/TauriFs' initRuntimes(wasmUrl) @@ -38,48 +33,95 @@ const consoles = new Map() * Dispatches an event whenever a task starts with progress steps */ export class DashService extends EventDispatcher { - protected fileSystem: DashFileSystem - public fileType: FileTypeLibrary - protected readonly dash: Dash + protected _fileSystem?: FileSystem + public _fileType?: FileTypeLibrary + protected _dash?: Dash public isDashFree = new Mutex() - protected projectDir: string + protected _projectDir?: string public isSetup = false public completedStartUp = new Signal() - protected console: ForeignConsole + protected _console?: ForeignConsole + + constructor() { + super() + } - constructor( + async setup( baseDirectory: AnyDirectoryHandle, comMojangDirectory: AnyDirectoryHandle | undefined, options: ICompilerOptions ) { - super() - this.fileSystem = new DashFileSystem(baseDirectory) + await this.isDashFree.lock() + + if (!dataLoader.hasFired) await dataLoader.loadData() + + this._fileSystem = await this.createFileSystem(baseDirectory) const outputFileSystem = comMojangDirectory && options.mode === 'development' - ? new DashFileSystem(comMojangDirectory) + ? await this.createFileSystem(comMojangDirectory) : undefined - this.fileType = new FileTypeLibrary() - this.fileType.setPluginFileTypes(options.pluginFileTypes) + this._fileType = new FileTypeLibrary() + this._fileType.setPluginFileTypes(options.pluginFileTypes) let console = consoles.get(options.projectName) if (!console) { console = new ForeignConsole() consoles.set(options.projectName, console) } - this.console = console + this._console = console + + this._projectDir = dirname(options.config) - this.dash = new Dash(this.fileSystem, outputFileSystem, { + this._dash = new Dash(this._fileSystem, outputFileSystem, { config: options.config, compilerConfig: options.compilerConfig, console, mode: options.mode, - fileType: this.fileType, + fileType: this._fileType, packType: new PackTypeLibrary(), verbose: true, requestJsonData: (path) => dataLoader.readJSON(path), }) + await this.dash.setup(dataLoader) + + this.isDashFree.unlock() + this.isSetup = true + } + protected async createFileSystem(directoryHandle: AnyDirectoryHandle) { + // Default file system on PWA builds + if (!import.meta.env.VITE_IS_TAURI_APP) + return new DashFileSystem(directoryHandle) - this.projectDir = dirname(options.config) + if (!(directoryHandle instanceof VirtualDirectoryHandle)) + throw new Error( + `Expected directoryHandle to be a virtual directory handle` + ) + const baseStore = directoryHandle.getBaseStore() + if (!(baseStore instanceof TauriFsStore)) + throw new Error( + `Expected virtual directory to be backed by TauriFsStore` + ) + + const { TauriBasedDashFileSystem } = await import('./TauriFs') + return new TauriBasedDashFileSystem(baseStore.getBaseDirectory()) + } + + get dash() { + if (!this._dash) throw new Error('Dash not initialized') + return this._dash + } + get fileSystem() { + if (!this._fileSystem) throw new Error('File system not initialized') + return this._fileSystem + } + get console() { + if (!this._console) throw new Error('Console not initialized') + return this._console + } + get projectDir() { + if (!this._projectDir) + throw new Error('Project directory not initialized') + return this._projectDir } getCompilerLogs() { @@ -106,19 +148,24 @@ export class DashService extends EventDispatcher { async start(changedFiles: string[], deletedFiles: string[]) { await this.isDashFree.lock() - const fs = this.fileSystem.internal + const fileExists = (path: string) => + this.fileSystem + .readFile(path) + .then(() => true) + .catch(() => false) + if ( - (await fs.fileExists( + (await fileExists( `${this.projectDir}/.bridge/.restartWatchMode` )) || - !(await fs.fileExists( + !(await fileExists( // TODO(Dash): Replace with call to "this.dash.dashFilePath" once the accessor is no longer protected `${this.projectDir}/.bridge/.dash.${this.dash.getMode()}.json` )) ) { await Promise.all([ this.build(false).catch((err) => console.error(err)), - fs + this.fileSystem .unlink(`${this.projectDir}/.bridge/.restartWatchMode`) .catch((err) => console.error(err)), ]) @@ -184,15 +231,6 @@ export class DashService extends EventDispatcher { return this.dash.getFileDependencies(filePath) } - async setup() { - await this.isDashFree.lock() - - if (!dataLoader.hasFired) await dataLoader.loadData() - await this.dash.setup(dataLoader) - - this.isDashFree.unlock() - this.isSetup = true - } async reloadPlugins() { await this.isDashFree.lock() diff --git a/src/components/Compiler/Worker/TauriFs.ts b/src/components/Compiler/Worker/TauriFs.ts new file mode 100644 index 000000000..efdd24954 --- /dev/null +++ b/src/components/Compiler/Worker/TauriFs.ts @@ -0,0 +1,112 @@ +import { FileSystem } from 'dash-compiler' +import { IDirEntry } from 'dash-compiler/dist/FileSystem/FileSystem' +import { AnyDirectoryHandle } from '../../FileSystem/Types' +import { + writeFile, + writeBinaryFile, + createDir, + removeDir, + removeFile, + readBinaryFile, + readDir, + FileEntry, + copyFile, +} from '@tauri-apps/api/fs' +import { join, basename, dirname, isAbsolute, sep } from '@tauri-apps/api/path' +import json5 from 'json5' + +export class TauriBasedDashFileSystem extends FileSystem { + constructor(protected baseDirectory?: string) { + super() + } + + async resolvePath(path: string) { + path = path.replaceAll(/\\|\//g, sep) + if (!this.baseDirectory || (await isAbsolute(path))) return path + + return join(this.baseDirectory, path) + } + + async readJson(path: string) { + const file = await this.readFile(path) + + return json5.parse(await file.text()) + } + async writeJson(path: string, content: any, beautify?: boolean) { + await this.writeFile( + path, + JSON.stringify(content, null, beautify ? '\t' : undefined) + ) + } + async readFile(path: string): Promise { + const binaryData = await readBinaryFile(await this.resolvePath(path)) + + return new File([binaryData], await basename(path)) + } + async writeFile(path: string, content: string | Uint8Array) { + const resolvedPath = await this.resolvePath(path) + await createDir(await dirname(resolvedPath), { recursive: true }) + + if (typeof content === 'string') await writeFile(resolvedPath, content) + else await writeBinaryFile(resolvedPath, content) + } + async copyFile(from: string, to: string, outputFs = this) { + const outputPath = await outputFs.resolvePath(to) + await createDir(await dirname(outputPath), { recursive: true }).catch( + () => {} + ) + + await copyFile(await this.resolvePath(from), outputPath) + } + + async mkdir(path: string) { + await createDir(await this.resolvePath(path), { recursive: true }) + } + async unlink(path: string) { + const resolvedPath = await this.resolvePath(path) + + await Promise.all([ + removeDir(resolvedPath, { recursive: true }).catch(() => {}), + removeFile(resolvedPath).catch(() => {}), + ]) + } + async allFiles(path: string) { + const entries = await readDir(await this.resolvePath(path), { + recursive: true, + }) + + return this.flattenEntries(entries) + } + protected relative(path: string) { + if (!this.baseDirectory) return path + + return path + .replace(`${this.baseDirectory}${sep}`, '') + .replaceAll('\\', '/') // Dash expects forward slashes + } + protected flattenEntries(entries: FileEntry[]) { + const files: string[] = [] + + for (const { path, children } of entries) { + if (children) { + files.push(...this.flattenEntries(children)) + continue + } + + files.push(this.relative(path)) + } + + return files + } + async readdir(path: string): Promise { + const entries = await readDir(await this.resolvePath(path)) + + return entries.map((entry) => ({ + name: entry.name!, + kind: entry.children ? 'directory' : 'file', + })) + } + async lastModified(filePath: string) { + return 0 + } +} diff --git a/src/components/Composables/Sidebar/useSidebarState.ts b/src/components/Composables/Sidebar/useSidebarState.ts new file mode 100644 index 000000000..27304f9dc --- /dev/null +++ b/src/components/Composables/Sidebar/useSidebarState.ts @@ -0,0 +1,19 @@ +import { computed, watch } from 'vue' +import { settingsState } from '../../Windows/Settings/SettingsState' +import { App } from '/@/App' + +export function useSidebarState() { + const isNavVisible = computed(() => App.sidebar.isNavigationVisible.value) + const isContentVisible = computed( + () => isNavVisible && App.sidebar.isContentVisible.value + ) + const isAttachedRight = computed( + () => settingsState.sidebar && settingsState.sidebar.isSidebarRight + ) + + return { + isNavVisible, + isContentVisible, + isAttachedRight, + } +} diff --git a/src/components/Data/DataLoader.ts b/src/components/Data/DataLoader.ts index 0c6cb2b89..b44fda63b 100644 --- a/src/components/Data/DataLoader.ts +++ b/src/components/Data/DataLoader.ts @@ -6,9 +6,10 @@ import { FileSystem } from '../FileSystem/FileSystem' import { zipSize } from '/@/utils/app/dataPackage' import { whenIdle } from '/@/utils/whenIdle' import { get, set } from 'idb-keyval' -import { IDBWrapper } from '/@/components/FileSystem/Virtual/IDB' import { compareVersions } from 'bridge-common-utils' import { version as appVersion } from '/@/utils/app/version' +import { IndexedDbStore } from '../FileSystem/Virtual/Stores/IndexedDb' +import { MemoryStore } from '../FileSystem/Virtual/Stores/Memory' export class DataLoader extends FileSystem { _virtualFileSystem?: VirtualDirectoryHandle @@ -50,14 +51,20 @@ export class DataLoader extends FileSystem { ) console.time('[App] Data') + + const indexedDbStore = new IndexedDbStore( + 'data-fs', + // Do not allow writes to data-fs + true + ) + // Clear data-fs if the version has changed const mayClearDb = this.isMainLoader && !savedAllDataInIdb + if (mayClearDb) await indexedDbStore.clear() // Create virtual filesystem this._virtualFileSystem = new VirtualDirectoryHandle( - new IDBWrapper('data-fs'), - 'bridgeFolder', - savedAllDataInIdb ? undefined : new Map(), - mayClearDb + savedAllDataInIdb ? indexedDbStore : new MemoryStore('data-fs'), + '' ) await this._virtualFileSystem.setupDone.fired @@ -114,14 +121,12 @@ export class DataLoader extends FileSystem { } } - this.setup(this._virtualFileSystem) - console.timeEnd('[App] Data') - if (this.isMainLoader && !forceDataDownload) { - whenIdle(async () => { - await this._virtualFileSystem!.moveToIdb() - await set('savedDataForVersion', appVersion) - }) + await this._virtualFileSystem!.moveToIdb() + await set('savedDataForVersion', appVersion) } + + this.setup(this._virtualFileSystem) + console.timeEnd('[App] Data') } } diff --git a/src/components/Data/JSONDefaults.ts b/src/components/Data/JSONDefaults.ts index ea0c69157..52f226c38 100644 --- a/src/components/Data/JSONDefaults.ts +++ b/src/components/Data/JSONDefaults.ts @@ -19,11 +19,13 @@ export class JsonDefaults extends EventDispatcher { protected loadedSchemas = false protected localSchemas: Record = {} protected disposables: IDisposable[] = [] - public readonly componentSchemas = new ComponentSchemas() + public readonly componentSchemas: ComponentSchemas protected task: Task | null = null constructor(protected project: Project) { super() + + this.componentSchemas = new ComponentSchemas(project) } get isReady() { @@ -34,7 +36,8 @@ export class JsonDefaults extends EventDispatcher { console.time('[SETUP] JSONDefaults') await this.project.app.project.packIndexer.fired - await this.componentSchemas.activate() + // Don't await to start loading schemas as soon as possible + this.componentSchemas.activate() this.disposables = [ // Updating currentContext/ references diff --git a/src/components/Data/SchemaScript.ts b/src/components/Data/SchemaScript.ts index 910c9351c..c90ca636c 100644 --- a/src/components/Data/SchemaScript.ts +++ b/src/components/Data/SchemaScript.ts @@ -100,82 +100,91 @@ export class SchemaScript { ) } } + protected processScriptResult( + scriptPath: string, + schemaScript: any, + scriptResult: any, + localSchemas: any + ) { + if (!scriptResult) return + if (scriptPath.endsWith('.js')) { + if (scriptResult.keep) return + + schemaScript = { + ...schemaScript, + type: scriptResult.type, + generateFile: scriptResult.generateFile, + } + scriptResult = scriptResult.data + } + + if ( + schemaScript.type === 'object' && + !Array.isArray(scriptResult) && + typeof scriptResult === 'object' + ) { + localSchemas[ + `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}` + ] = { + uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`, + schema: { + type: 'object', + properties: scriptResult, + }, + } + } else if (schemaScript.type === 'custom') { + localSchemas[ + `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}` + ] = { + uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`, + schema: scriptResult, + } + } else { + localSchemas[ + `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}` + ] = { + uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`, + schema: { + type: schemaScript.type === 'enum' ? 'string' : 'object', + enum: + schemaScript.type === 'enum' ? scriptResult : undefined, + properties: + schemaScript.type === 'properties' + ? Object.fromEntries( + scriptResult.map((res: string) => [res, {}]) + ) + : undefined, + }, + } + } + } async runSchemaScripts(localSchemas: any) { const schemaScripts = await this.app.dataLoader.readJSON( 'data/packages/minecraftBedrock/schemaScripts.json' ) + const promises = [] + for (const [scriptPath, script] of Object.entries(schemaScripts)) { let schemaScript: any if (scriptPath.endsWith('.js')) schemaScript = { script } else schemaScript = script - let scriptResult: any = await this.runScript( - scriptPath, - schemaScript.script - ) - - if (scriptResult) { - if (scriptPath.endsWith('.js')) { - if (scriptResult.keep) continue - - schemaScript = { - ...schemaScript, - type: scriptResult.type, - generateFile: scriptResult.generateFile, - } - scriptResult = scriptResult.data - } - - if ( - schemaScript.type === 'object' && - !Array.isArray(scriptResult) && - typeof scriptResult === 'object' - ) { - localSchemas[ - `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}` - ] = { - uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`, - schema: { - type: 'object', - properties: scriptResult, - }, - } - } else if (schemaScript.type === 'custom') { - localSchemas[ - `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}` - ] = { - uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`, - schema: scriptResult, - } - } else { - localSchemas[ - `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}` - ] = { - uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`, - schema: { - type: - schemaScript.type === 'enum' - ? 'string' - : 'object', - enum: - schemaScript.type === 'enum' - ? scriptResult - : undefined, - properties: - schemaScript.type === 'properties' - ? Object.fromEntries( - scriptResult.map((res: string) => [ - res, - {}, - ]) - ) - : undefined, - }, + promises.push( + this.runScript(scriptPath, schemaScript.script).then( + (scriptResult) => { + this.processScriptResult( + scriptPath, + schemaScript, + scriptResult, + localSchemas + ) } - } - } + ) + ) } + + await Promise.all(promises) } } diff --git a/src/components/Data/TypeLoader.ts b/src/components/Data/TypeLoader.ts index 63b03dd25..c7b0efccb 100644 --- a/src/components/Data/TypeLoader.ts +++ b/src/components/Data/TypeLoader.ts @@ -1,14 +1,18 @@ import { App } from '/@/App' import { IDisposable } from '/@/types/disposable' import { DataLoader } from './DataLoader' -import { Tab } from '../TabSystem/CommonTab' -import { FileTab } from '../TabSystem/FileTab' +import { Tab } from '/@/components/TabSystem/CommonTab' +import { FileTab } from '/@/components/TabSystem/FileTab' import { IRequirements, RequiresMatcher, } from './RequiresMatcher/RequiresMatcher' import { useMonaco } from '/@/utils/libs/useMonaco' +import { v4 as uuid } from 'uuid' +/** + * A map of type locations to type defintions that have been loaded + */ const types = new Map() export class TypeLoader { @@ -16,6 +20,7 @@ export class TypeLoader { protected typeDisposables: IDisposable[] = [] protected userTypeDisposables: IDisposable[] = [] protected currentTypeEnv: string | null = null + protected isLoading: boolean = false constructor(protected dataLoader: DataLoader) {} @@ -41,21 +46,108 @@ export class TypeLoader { this.disposables = [] } - protected async load(typePath: string) { - // Check whether we have already loaded types - let src = types.get(typePath) - if (src) return src + protected async load(typeLocations: [string, string?][]) { + // Ignore if we are already loading types (e.g. if a tab has been switched while loading) + if (this.isLoading) return [] + this.isLoading = true - await this.dataLoader.fired + const app = await App.getApp() + + // Load the cache index which maps the urls to the location of the files in cache + let cacheIndex: Record = {} + try { + cacheIndex = await app.fileSystem.readJSON( + `~local/data/cache/types/index.json` + ) + } catch {} + + // Create promises for loading each type definition. Once this resolves, the types will be cached appropriately + const toCache = await Promise.all( + typeLocations.map(([typeLocation, moduleName]) => { + return new Promise<[string, string, boolean, string?]>( + async (resolve) => { + // Before we try to load anything, make sure the type definition hasn't already been loaded and set in the type map + let src = types.get(typeLocation) + if (src) resolve([typeLocation, src, false, moduleName]) + + // Decide whether the type is being loaded from data or needs to be fetched externally + const isFromData = typeLocation.startsWith('types/') + if (isFromData) { + // Load types directly from bridge.'s data + await this.dataLoader.fired + + const file = await this.dataLoader.readFile( + `data/packages/minecraftBedrock/${typeLocation}` + ) + src = await file.text() + resolve([typeLocation, src, false, moduleName]) + return + } + + // First check cache to see if we have already cached the file, if so resolve with the file from cache + const cacheLocation = cacheIndex[typeLocation] + const file = cacheLocation + ? await app.fileSystem + .readFile(cacheLocation) + .catch(() => null) + : null + + // File is cached, so resolve with the file from cache + if (file) { + resolve([ + typeLocation, + await file.text(), + false, + moduleName, + ]) + return + } + + // The file couldn't be fetched from cache (because it is not in index or at the path specified) + // So we need to fetch it + const res = await fetch(typeLocation).catch(() => null) + // TODO: Maybe set a variable (failedToFetchAtLeastOnce) to later open an information window that tells the user that some types couldn't be fetched + + // If the fetch failed, resolve with an empty string but don't cache it + const text = res ? await res.text() : '' + + resolve([typeLocation, text, text !== '', moduleName]) + } + ) + }) + ) + + for (const [typeLocation, definition, updateCache] of toCache) { + // First, save types to 'types' map + types.set(typeLocation, definition) + + // Then if don't need to update cache, continue processing the next type + if (!updateCache) continue + + // Create a random file name for the file to be stored in cache under. We can't use the location since it is a url and contains illegal file name characters + const cacheFile = `~local/data/cache/types/${uuid()}.d.ts` + cacheIndex = { + ...cacheIndex, + [typeLocation]: cacheFile, + } + // Write the actual type definition in cache + await app.fileSystem.writeFile(cacheFile, definition) + } - // Load types from file - const file = await this.dataLoader.readFile( - `data/packages/minecraftBedrock/${typePath}` + // Update the cache index + await app.fileSystem.writeJSON( + '~local/data/cache/types/index.json', + cacheIndex ) - src = await file.text() - types.set(typePath, src) - return src + this.isLoading = false + + return toCache.map( + ([typeLocation, definition, updateCache, moduleName]) => [ + typeLocation, + this.wrapTypesInModule(definition, moduleName), + ] + ) } async setTypeEnv(filePath: string) { @@ -73,26 +165,22 @@ export class TypeLoader { const matcher = new RequiresMatcher() await matcher.setup() - const libs = await Promise.all( - types.map(async (type) => { - if (typeof type === 'string') - return [type, await this.load(type)] + const libs = await this.load( + types + .map((type) => { + if (typeof type === 'string') return [type] - const { definition, requires } = type + const { definition, requires, moduleName } = type - const valid = !requires - ? true - : matcher.isValid(requires as IRequirements) + if (!requires || matcher.isValid(requires as IRequirements)) + return [definition, moduleName] - if (valid) - return [definition, await this.load(definition)] - }) - ) - const filteredLibs = <(readonly [string, string])[]>( - libs.filter((lib) => lib !== undefined) + return [] + }) + .filter((type) => type[0]) as [string, string?][] ) - for (const [typePath, lib] of filteredLibs) { + for (const [typePath, lib] of libs) { const uri = Uri.file(typePath) this.typeDisposables.push( languages.typescript.javascriptDefaults.addExtraLib( @@ -145,4 +233,10 @@ export class TypeLoader { ) } } + + wrapTypesInModule(typeSrc: string, moduleName?: string) { + if (!moduleName) return typeSrc + + return `declare module '${moduleName}' {\n${typeSrc}\n}` + } } diff --git a/src/components/Editors/ThreePreview/ThreePreviewTab.ts b/src/components/Editors/ThreePreview/ThreePreviewTab.ts index 0284b8ea6..5de48e7b5 100644 --- a/src/components/Editors/ThreePreview/ThreePreviewTab.ts +++ b/src/components/Editors/ThreePreview/ThreePreviewTab.ts @@ -65,7 +65,7 @@ export abstract class ThreePreviewTab extends PreviewTab { this.controls = markRaw(new OrbitControls(this.camera, canvas)) this.controls.addEventListener('change', () => { this.requestRendering() - if (!this.parent.isActive) this.parent.setActive(true) + if (!this.parent.isActive.value) this.parent.setActive(true) }) if (!this._scene) { diff --git a/src/components/Extensions/Scripts/Modules/project.ts b/src/components/Extensions/Scripts/Modules/project.ts index caa1296a8..28754784c 100644 --- a/src/components/Extensions/Scripts/Modules/project.ts +++ b/src/components/Extensions/Scripts/Modules/project.ts @@ -41,7 +41,6 @@ export const ProjectModule = async ({ ? undefined : `${app.project.projectPath}/.bridge/compiler/${configFile}` ) - await service.setup() await service.build() }, diff --git a/src/components/FileSystem/Fast/getDirectoryHandle.ts b/src/components/FileSystem/Fast/getDirectoryHandle.ts new file mode 100644 index 000000000..bf2b9fa3e --- /dev/null +++ b/src/components/FileSystem/Fast/getDirectoryHandle.ts @@ -0,0 +1,80 @@ +import { IGetHandleConfig } from '../Common' +import { VirtualDirectoryHandle } from '../Virtual/DirectoryHandle' +import { pathFromHandle } from '../Virtual/pathFromHandle' +import { IndexedDbStore } from '../Virtual/Stores/IndexedDb' +import { join } from '/@/utils/path' + +export async function getDirectoryHandleTauri( + baseDirectory: VirtualDirectoryHandle, + pathArr: string[], + { create, createOnce }: Partial +) { + // Cannot apply fast path if baseDirectory is not a virtual directory + const baseStore = baseDirectory.getBaseStore() + + // Cannot apply fast path if baseStore is not a TauriFsStore + const { TauriFsStore } = await import('../Virtual/Stores/TauriFs') + if (!(baseStore instanceof TauriFsStore)) return false + + const { createDir, exists } = await import('@tauri-apps/api/fs') + const { join, basename, sep } = await import('@tauri-apps/api/path') + const fullPath = await join(await pathFromHandle(baseDirectory), ...pathArr) + + if (create) { + await createDir(fullPath, { recursive: !createOnce }).catch((err) => { + throw new Error( + `Failed to access "${fullPath}": Directory does not exist: ${err}` + ) + }) + } else if (!(await exists(fullPath))) { + throw new Error( + `Failed to access "${fullPath}": Directory does not exist` + ) + } + + const basePath = baseStore.getBaseDirectory() + + return new VirtualDirectoryHandle( + baseStore, + await basename(fullPath), + basePath + ? // pathArr may not contain full path starting from basePath if baseDirectory is not the root directory + fullPath.replace(`${basePath}${sep}`, '').split(sep) + : pathArr + ) +} + +export async function getDirectoryHandleIndexedDb( + baseDirectory: VirtualDirectoryHandle, + pathArr: string[], + { create, createOnce }: Partial +) { + // Cannot use fast path if createOnce is true + if (createOnce) return false + + const baseStore = baseDirectory.getBaseStore() + + // Cannot apply fast path if baseStore is not a IndexedDbStore + if (!(baseStore instanceof IndexedDbStore)) return false + + const fullPath = join(baseDirectory.idbKey, ...pathArr) + let directoryData = await baseStore + .getDirectoryEntries(fullPath) + .catch(() => null) + + if (create) { + await baseStore.createDirectory(fullPath) + directoryData = [] + } + + if (!directoryData) + throw new Error( + `Failed to access "${fullPath}": Directory does not exist` + ) + + return new VirtualDirectoryHandle( + baseStore, + pathArr[pathArr.length - 1], + fullPath.split(/\\|\//g) + ) +} diff --git a/src/components/FileSystem/FileSystem.ts b/src/components/FileSystem/FileSystem.ts index d00875e7e..d67ef9ef6 100644 --- a/src/components/FileSystem/FileSystem.ts +++ b/src/components/FileSystem/FileSystem.ts @@ -8,6 +8,11 @@ import { AnyDirectoryHandle, AnyFileHandle, AnyHandle } from './Types' import { getStorageDirectory } from '/@/utils/getStorageDirectory' import { VirtualFileHandle } from './Virtual/FileHandle' import { VirtualDirectoryHandle } from './Virtual/DirectoryHandle' +import { + getDirectoryHandleIndexedDb, + getDirectoryHandleTauri, +} from './Fast/getDirectoryHandle' +import { pathFromHandle } from './Virtual/pathFromHandle' export class FileSystem extends Signal { protected _baseDirectory!: AnyDirectoryHandle @@ -45,14 +50,42 @@ export class FileSystem extends Signal { pathArr.shift() } + // Cannot apply fast path if baseDirectory is not a virtual directory + if (this.baseDirectory instanceof VirtualDirectoryHandle) { + /** + * Fast path for native app + * Every path segment costs at least 1 syscall on the slow path, whereas the fast path costs 1 syscall for the entire path + * Therefore, switch to the fast path if the path has more than 1 segment + */ + if (import.meta.env.VITE_IS_TAURI_APP && pathArr.length > 1) { + const fastCallResult = await getDirectoryHandleTauri( + this.baseDirectory, + pathArr, + { create, createOnce } + ) + // Returns false if the fast call failed + if (fastCallResult) return fastCallResult + } + + /** + * Fast path for IndexedDB backed file systems + */ + const fastCallResult = await getDirectoryHandleIndexedDb( + this.baseDirectory, + pathArr, + { create, createOnce } + ) + if (fastCallResult) return fastCallResult + } + for (const folder of pathArr) { try { current = await current.getDirectoryHandle(folder, { create: createOnce || create, }) - } catch { + } catch (err) { throw new Error( - `Failed to access "${path}": Directory does not exist` + `Failed to access "${path}": Directory does not exist: ${err}` ) } @@ -91,7 +124,8 @@ export class FileSystem extends Signal { .then((path) => path?.join('/')) if (path) { - path = '~local/' + path + // Local projects don't exist for Tauri builds + if (!import.meta.env.VITE_IS_TAURI_APP) path = '~local/' + path } else { path = await this.baseDirectory .resolve(handle) @@ -268,26 +302,44 @@ export class FileSystem extends Signal { const originHandle = await this.getDirectoryHandle(originPath, { create: false, }) - - await iterateDirParallel(originHandle, async (fileHandle, filePath) => { - await this.copyFileHandle( - fileHandle, - await this.getFileHandle(join(destPath, filePath), true) - ) + const destHandle = await this.getDirectoryHandle(destPath, { + create: true, }) + + await this.copyFolderByHandle(originHandle, destHandle) } async copyFolderByHandle( originHandle: AnyDirectoryHandle, - destHandle: AnyDirectoryHandle + destHandle: AnyDirectoryHandle, + ignoreFolders?: Set ) { + // Tauri build: Both handles are virtual -> Elligible for fast path + if ( + import.meta.env.VITE_IS_TAURI_APP && + originHandle instanceof VirtualDirectoryHandle && + destHandle instanceof VirtualDirectoryHandle + ) { + const src = await pathFromHandle(originHandle) + const dest = await pathFromHandle(destHandle) + + const { invoke } = await import('@tauri-apps/api') + await invoke('copy_directory', { src, dest }) + + return + } + const destFs = new FileSystem(destHandle) - await iterateDirParallel(originHandle, async (fileHandle, filePath) => { - await this.copyFileHandle( - fileHandle, - await destFs.getFileHandle(filePath, true) - ) - }) + await iterateDirParallel( + originHandle, + async (fileHandle, filePath) => { + await this.copyFileHandle( + fileHandle, + await destFs.getFileHandle(filePath, true) + ) + }, + ignoreFolders + ) } loadFileHandleAsDataUrl(fileHandle: AnyFileHandle) { diff --git a/src/components/FileSystem/Pickers/showFolderPicker.ts b/src/components/FileSystem/Pickers/showFolderPicker.ts new file mode 100644 index 000000000..918f4397c --- /dev/null +++ b/src/components/FileSystem/Pickers/showFolderPicker.ts @@ -0,0 +1,45 @@ +import { VirtualDirectoryHandle } from '../Virtual/DirectoryHandle' + +interface IOpenFolderOpts { + multiple?: boolean + defaultPath?: string +} + +export async function showFolderPicker({ + defaultPath, + multiple, +}: IOpenFolderOpts = {}) { + if (!import.meta.env.VITE_IS_TAURI_APP) { + if (multiple) + console.warn( + 'Multiple folder selection is not supported in the browser yet' + ) + + const handle = await window + .showDirectoryPicker({ multiple: false, mode: 'readwrite' }) + .catch(() => null) + return handle ? [handle] : null + } + + const { TauriFsStore } = await import('../Virtual/Stores/TauriFs') + const { open } = await import('@tauri-apps/api/dialog') + const { basename } = await import('@tauri-apps/api/path') + + let selectedPath = await open({ + directory: true, + multiple, + defaultPath, + }).catch(() => null) + if (!selectedPath) return null + if (!Array.isArray(selectedPath)) selectedPath = [selectedPath] + + return await Promise.all( + selectedPath.map( + async (path) => + new VirtualDirectoryHandle( + new TauriFsStore(path), + await basename(path) + ) + ) + ) +} diff --git a/src/components/FileSystem/Polyfill.ts b/src/components/FileSystem/Polyfill.ts index 90e1b22fc..66c65368b 100644 --- a/src/components/FileSystem/Polyfill.ts +++ b/src/components/FileSystem/Polyfill.ts @@ -1,6 +1,7 @@ import { ref, markRaw } from 'vue' import { VirtualDirectoryHandle } from './Virtual/DirectoryHandle' import { VirtualFileHandle } from './Virtual/FileHandle' +import { IndexedDbStore } from './Virtual/Stores/IndexedDb' /** * Chrome 93 and 94 crash when we try to call createWritable on a file handle inside of a web worker @@ -55,7 +56,9 @@ if ( window.showDirectoryPicker = async () => // @ts-ignore Typescript doesn't like our polyfill - markRaw(new VirtualDirectoryHandle(null, 'bridgeFolder', undefined)) + markRaw( + new VirtualDirectoryHandle(new IndexedDbStore(), 'bridgeFolder') + ) } } @@ -91,10 +94,7 @@ if (isUnsupportedBrowser() || typeof window.showOpenFilePicker !== 'function') { new VirtualFileHandle( null, file.name, - new Uint8Array( - await file.arrayBuffer() - ), - true + new Uint8Array(await file.arrayBuffer()) ) ) ) @@ -135,8 +135,7 @@ if (isUnsupportedBrowser() || typeof window.showSaveFilePicker !== 'function') { return new VirtualFileHandle( null, options.suggestedName ?? 'newFile.txt', - new Uint8Array(), - true + new Uint8Array() ) } } @@ -155,8 +154,7 @@ if ( return new VirtualFileHandle( null, file.name, - new Uint8Array(await file.arrayBuffer()), - true + new Uint8Array(await file.arrayBuffer()) ) } else if (this.kind === 'directory') { return markRaw(new VirtualDirectoryHandle(null, 'unknown')) diff --git a/src/components/FileSystem/Setup.ts b/src/components/FileSystem/Setup.ts index 807dc44f3..862df15e5 100644 --- a/src/components/FileSystem/Setup.ts +++ b/src/components/FileSystem/Setup.ts @@ -13,6 +13,7 @@ import { import { ConfirmationWindow } from '../Windows/Common/Confirm/ConfirmWindow' import { FileSystem } from './FileSystem' import { VirtualFileHandle } from './Virtual/FileHandle' +import { IndexedDbStore } from './Virtual/Stores/IndexedDb' type TFileSystemSetupStatus = 'waiting' | 'userInteracted' | 'done' @@ -73,7 +74,10 @@ export class FileSystemSetup { // Only create virtual folder if we are not migrating away from the virtual file system if (!isUpgradingVirtualFs) { fileHandle = markRaw( - new VirtualDirectoryHandle(null, 'bridgeFolder', undefined) + new VirtualDirectoryHandle( + new IndexedDbStore(), + 'bridgeFolder' + ) ) await fileHandle.setupDone.fired } @@ -109,7 +113,7 @@ export class FileSystemSetup { // Migrate virtual projects over if (isUpgradingVirtualFs) { const virtualFolder = markRaw( - new VirtualDirectoryHandle(null, 'bridgeFolder', undefined) + new VirtualDirectoryHandle(null, 'bridgeFolder') ) await virtualFolder.setupDone.fired const virtualFs = new FileSystem(virtualFolder) diff --git a/src/components/FileSystem/Virtual/Comlink.ts b/src/components/FileSystem/Virtual/Comlink.ts index 164643eac..2650a55c9 100644 --- a/src/components/FileSystem/Virtual/Comlink.ts +++ b/src/components/FileSystem/Virtual/Comlink.ts @@ -9,7 +9,7 @@ export interface ISerializedFileHandle { name: string path: string[] fileData?: Uint8Array - idbWrapper?: string + baseStore?: any } transferHandlers.set('VirtualFileHandle', < @@ -27,7 +27,7 @@ export interface ISerializedDirectoryHandle { kind: 'directory' name: string path: string[] - idbWrapper?: string + baseStore?: any children?: (ISerializedFileHandle | ISerializedDirectoryHandle)[] } diff --git a/src/components/FileSystem/Virtual/DirectoryHandle.ts b/src/components/FileSystem/Virtual/DirectoryHandle.ts index 4d03649fb..2773a8437 100644 --- a/src/components/FileSystem/Virtual/DirectoryHandle.ts +++ b/src/components/FileSystem/Virtual/DirectoryHandle.ts @@ -1,10 +1,10 @@ import { VirtualHandle, BaseVirtualHandle } from './Handle' import { VirtualFileHandle } from './FileHandle' import { ISerializedDirectoryHandle } from './Comlink' -import { IDBWrapper } from './IDB' -import { GlobalMutex } from '/@/components/Common/GlobalMutex' - -const globalMutex = new GlobalMutex() +import { BaseStore, FsKindEnum, IDirEntry } from './Stores/BaseStore' +import { MemoryStore } from './Stores/Memory' +import { deserializeStore } from './Stores/Deserialize' +import { getParent } from './getParent' /** * A class that implements a virtual folder @@ -20,185 +20,99 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle { */ public readonly isFile = false - get isDirectoryInMemory() { - return this.children !== undefined - } async moveToIdb() { - const children = this.allInMemoryChildren() - - this.idbWrapper.setMany( - children.map((child) => [child.idbKey, child.moveData()]) + if (!this._baseStore) + throw new Error(`Must call method on top-level directory`) + if (!(this._baseStore instanceof MemoryStore)) + throw new Error(`Must call method on memory store`) + + this._baseStore = await this._baseStore.toIdb( + // Do not allow writes to data-fs + true ) } - moveData() { - if (!this.children) - throw new Error( - `No directory data to move to IDB for directory "${this.name}"` - ) - - const children = [...this.children.values()].map((child) => child.name) - this.children = undefined - - return children - } - protected allInMemoryChildren() { - if (!this.children) return [] - - // Recursively get all in memory children - const children: BaseVirtualHandle[] = [this] - - for (const child of this.children.values()) { - if (child instanceof VirtualDirectoryHandle) { - if (child.isDirectoryInMemory) - children.push(...child.allInMemoryChildren()) - } else { - if (child.isFileStoredInMemory) children.push(child) - } - } - - return children - } constructor( - parent: VirtualDirectoryHandle | IDBWrapper | null, + parent: VirtualDirectoryHandle | BaseStore | null, name: string, - protected children?: Map | undefined, - clearDB = false, - path: string[] = [] + path: string[] = [], + create = false ) { super(parent, name, path) - this.updateIdb(clearDB) - } - /** - * Acquire exclusive access to this directory - */ - async lockAccess() { - await globalMutex.lock(this.idbKey) - } - /** - * Release exclusive access to this directory - */ - unlockAccess() { - globalMutex.unlock(this.idbKey) + this.setup(create) } - async updateIdb(clearDB = false) { - await this.lockAccess() - if (clearDB) { - await this.idbWrapper.clear() - } + async setup(create: boolean) { + await this.setupStore() - if (!this.children && !(await this.hasChildren())) - await this.idbWrapper.set(this.idbKey, []) + /** + * TauriFsStore should not create the base directory because that'll lead to duplicated directories + */ + if (this.idbKey !== '' && create) + await this.baseStore.createDirectory(this.idbKey) this.setupDone.dispatch() - this.unlockAccess() } - protected async addChild(child: VirtualHandle) { - if (this.children) this.children.set(child.name, child) - else - await this.idbWrapper.set(this.idbKey, [ - ...new Set([...(await this.fromIdb()), child.name]), - ]) - } - protected async fromIdb() { - return (await this.idbWrapper.get(this.idbKey)) ?? [] + protected async fromStore() { + return (await this.baseStore.getDirectoryEntries(this.idbKey)) ?? [] } protected async getChildren() { - if (this.children) return [...this.children.values()] - else - return ( - ( - await Promise.all( - ( - await this.fromIdb() - ).map((name) => this.getChild(name)) - ) - ).filter((child) => child !== undefined) - ) + return ( + ( + await Promise.all( + (await this.fromStore()).map((name) => this.getChild(name)) + ) + ).filter((child) => child !== undefined) + ) } - protected getChildPath(childName: string) { + protected getChildPath(child: IDirEntry | string) { + const childName = typeof child === 'string' ? child : child.name + return this.path.concat(childName).join('/') } - protected async getChild(childName: string) { - if (this.children) return this.children.get(childName) - - if (await this.has(childName)) { - const data = await this.idbWrapper.get(this.getChildPath(childName)) - - if (data instanceof Uint8Array) { - return new VirtualFileHandle(this, childName) - } else if (Array.isArray(data)) { - return new VirtualDirectoryHandle(this, childName) - } else { - // File/folder was deleted - } + protected async getChild(child: IDirEntry | string) { + const childName = typeof child === 'string' ? child : child.name + const type = + typeof child === 'string' + ? await this.baseStore.typeOf(this.getChildPath(child)) + : child.kind === FsKindEnum.Directory + ? 'directory' + : 'file' + + if (type === 'file') { + return new VirtualFileHandle(this, childName) + } else if (type === 'directory') { + return new VirtualDirectoryHandle(this, childName) + } else if (type === null) { + return undefined + } else { + throw new Error(`Unknown type ${type}`) } } - /** - * @deprecated THIS IS NOT A PUBLIC API - * - * @param childName - * @param lockMutex - */ - async deleteChild(childName: string, lockMutex = true) { - if (lockMutex) await this.lockAccess() - - if (this.children) this.children.delete(childName) - else - await this.idbWrapper.set( - this.idbKey, - (await this.fromIdb()).filter((name) => name !== childName) - ) - - if (lockMutex) this.unlockAccess() - } - protected async hasChildren() { - if (this.children) return this.children.size > 0 - else return (await this.fromIdb()).length > 0 - } - protected async has(childName: string) { - if (this.children) return this.children.has(childName) - else return (await this.fromIdb()).includes(childName) + return (await this.fromStore()).length > 0 } serialize(): ISerializedDirectoryHandle { + let baseStore: BaseStore | undefined = undefined + if (this.baseStore) baseStore = this.baseStore.serialize() + return { - idbWrapper: this.idbWrapper.storeName, + baseStore, kind: 'directory', name: this.name, path: this.path, - children: this.children - ? [...this.children.values()].map((child: VirtualHandle) => - child.serialize() - ) - : undefined, } } - static deserialize( - data: ISerializedDirectoryHandle, - parent: VirtualDirectoryHandle | null = null - ) { - const dir = new VirtualDirectoryHandle( - !parent ? new IDBWrapper(data.idbWrapper) : parent, - data.name, - undefined, - false, - data.path - ) + static deserialize(data: ISerializedDirectoryHandle) { + let baseStore: BaseStore | null = null - for (const child of data.children ?? []) { - if (child.kind === 'directory') - dir.addChild(VirtualDirectoryHandle.deserialize(child, dir)) - else - dir.addChild( - new VirtualFileHandle(dir, child.name, child.fileData) - ) - } + if (data.baseStore) baseStore = deserializeStore(data.baseStore) + + const dir = new VirtualDirectoryHandle(baseStore, data.name, data.path) return dir } @@ -207,35 +121,23 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle { name: string, { create }: { create?: boolean } = {} ) { - await this.lockAccess() - let entry = await this.getChild(name) if (entry && entry.kind === 'file') { - this.unlockAccess() throw new Error( `TypeMismatch: Expected directory with name "${name}", found file` ) } else if (!entry) { if (create) { - entry = new VirtualDirectoryHandle( - this, - name, - this.children ? new Map() : undefined - ) + entry = new VirtualDirectoryHandle(this, name, [], true) await entry.setupDone.fired - - await this.addChild(entry) } else { - this.unlockAccess() throw new Error( - `No file with the name ${name} exists in this folder` + `No directory with the name "${name}" exists in this folder` ) } } - this.unlockAccess() - return entry } async getFileHandle( @@ -245,11 +147,9 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle { initialData, }: { create?: boolean; initialData?: Uint8Array } = {} ) { - await this.lockAccess() let entry = await this.getChild(name) if (entry && entry.kind === 'directory') { - this.unlockAccess() throw new Error( `TypeMismatch: Expected file with name "${name}", found directory` ) @@ -261,48 +161,36 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle { initialData ?? new Uint8Array() ) await entry.setupDone.fired - - await this.addChild(entry) } else { - this.unlockAccess() throw new Error( - `No file with the name ${name} exists in this folder` + `No file with the name "${name}" exists in this folder` ) } } - this.unlockAccess() - return entry } async removeEntry( name: string, { recursive }: { recursive?: boolean } = {} ) { - await this.lockAccess() - const entry = await this.getChild(name) if (!entry) { - this.unlockAccess() throw new Error( - `No entry with the name ${name} exists in this folder` + `No entry with the name "${name}" exists in this folder` ) } else if ( entry.kind === 'directory' && !recursive && (await (entry).hasChildren()) ) { - this.unlockAccess() throw new Error( `Cannot remove directory with children without "recursive" option being set to true` ) } - await entry.removeSelf(true, false) - - await this.deleteChild(name, false) - this.unlockAccess() + await entry.removeSelf() } async resolve(possibleDescendant: VirtualHandle) { const path: string[] = [possibleDescendant.name] @@ -316,19 +204,21 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle { if (current === null) return null return path } - async removeSelf(isFirst = true, lockMutex = true) { - if (lockMutex) await this.lockAccess() - + async removeSelf() { const children = await this.getChildren() for (const child of children) { - await child.removeSelf(false) + await child.removeSelf() } - if (!this.children) await this.idbWrapper.del(this.idbKey) - if (this.parent && isFirst) this.parent.deleteChild(this.name) - - if (lockMutex) this.unlockAccess() + await this.baseStore.unlink(this.idbKey) + } + getParent() { + // We don't have a parent but we do have a base path -> We can traverse path backwards to create parent handle + if (this.parent === null && this.basePath.length > 0) { + this.parent = getParent(this.baseStore, this.basePath) + } + return this.parent } [Symbol.asyncIterator]() { @@ -336,10 +226,8 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle { } keys() { - if (this.children) return this.children.keys() - return { - childNamesPromise: this.fromIdb(), + childNamesPromise: this.fromStore(), [Symbol.asyncIterator]() { return < @@ -366,8 +254,6 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle { } } entries() { - if (this.children) return this.children.entries() - return { childrenPromise: this.getChildren(), @@ -399,8 +285,6 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle { } } values() { - if (this.children) return this.children.values() - return { childrenPromise: this.getChildren(), diff --git a/src/components/FileSystem/Virtual/FileHandle.ts b/src/components/FileSystem/Virtual/FileHandle.ts index e13f73e36..65eb56e48 100644 --- a/src/components/FileSystem/Virtual/FileHandle.ts +++ b/src/components/FileSystem/Virtual/FileHandle.ts @@ -1,8 +1,11 @@ import { BaseVirtualHandle } from './Handle' -import type { VirtualDirectoryHandle } from './DirectoryHandle' +import { VirtualDirectoryHandle } from './DirectoryHandle' import { VirtualWritable, writeMethodSymbol } from './VirtualWritable' import { ISerializedFileHandle } from './Comlink' -import { IDBWrapper } from './IDB' +import { BaseStore } from './Stores/BaseStore' +import { deserializeStore } from './Stores/Deserialize' +import { MemoryStore } from './Stores/Memory' +import { getParent } from './getParent' /** * A class that implements a virtual file @@ -18,109 +21,79 @@ export class VirtualFileHandle extends BaseVirtualHandle { */ public readonly isDirectory = false - protected fileData?: Uint8Array - - get isFileStoredInMemory() { - return this.fileData !== undefined - } - moveData() { - if (!this.fileData) - throw new Error( - `No file data to move to IDB for file "${this.name}"` - ) - - const data = this.fileData - this.fileData = undefined - this.inMemory = false - return data - } - constructor( - parent: VirtualDirectoryHandle | IDBWrapper | null, + parent: VirtualDirectoryHandle | BaseStore | null, name: string, data?: Uint8Array, - protected inMemory = false, path?: string[] ) { super(parent, name, path) - if (data) this.setup(data) - else this.setupDone.dispatch() - } - protected async setup(fileData: Uint8Array) { - // Composition API isn't available within web workers (and usage of markRaw isn't necessary there) so we can omit the markRaw call - this.fileData = fileData - const isDataFile = this.path.join('/').startsWith('data/packages') + this.setup(data) + } + protected async setup(fileData?: Uint8Array) { + await this.setupStore() - // This prevents an IndexedDB overload by saving too many small data files to the DB - if (this.inMemory || (isDataFile && fileData.length < 50_000)) { - this.inMemory = true - return this.setupDone.dispatch() + // We only need to write data files from the main thread, web workers can just load the already written data from the main thread + // Since MemoryStore extends IndexedDBStore, we cannot use instanceof on the IndexedDBStore directly + if (!globalThis.document && !(this.baseStore instanceof MemoryStore)) { + this.setupDone.dispatch() + return } - // We only need to write data files from the main thread, web workers can just load the already written data from the main thread - if (!isDataFile || globalThis.document) await this.updateIdb(fileData) - this.setupDone.dispatch() - } + if (fileData) await this.baseStore.writeFile(this.idbKey, fileData) - protected async updateIdb(data: Uint8Array) { - await this.idbWrapper.set(this.idbKey, data) - this.fileData = undefined + this.setupDone.dispatch() } - protected async loadFromIdb() { - if (this.fileData) return this.fileData - - let storedData = await this.idbWrapper.get(this.idbKey) - if (storedData === undefined) { - console.log(this.parent) - throw new Error(`File not found: "${this.path.join('/')}"`) - } - - return storedData! - } serialize(): ISerializedFileHandle { + let baseStore: BaseStore | undefined = undefined + if (this.baseStore) baseStore = this.baseStore.serialize() + return { - idbWrapper: this.idbWrapper.storeName, + baseStore, kind: 'file', name: this.name, path: this.path, - fileData: this.fileData, } } static deserialize(data: ISerializedFileHandle) { + let baseStore: BaseStore | null = null + if (data.baseStore) baseStore = deserializeStore(data.baseStore) + return new VirtualFileHandle( - data.idbWrapper ? new IDBWrapper(data.idbWrapper) : null, + baseStore, data.name, data.fileData, - data.fileData !== undefined, data.path ) } + getParent() { + // We don't have a parent but we do have a base path -> We can traverse path backwards to create parent handle + if (this.parent === null && this.basePath.length > 0) { + this.parent = getParent(this.baseStore, this.basePath) + } + return this.parent + } + override async isSameEntry(other: BaseVirtualHandle): Promise { if (this.parent === null) return false return super.isSameEntry(other) } - async removeSelf(isFirst = true) { - await this.idbWrapper.del(this.idbKey) - if (this.parent && isFirst) this.parent.deleteChild(this.name) + async removeSelf() { + await this.baseStore.unlink(this.idbKey) } async getFile() { - const fileData = await this.loadFromIdb() - // console.log(this.path.join('/'), this.fileData, fileData) - - // TODO: Support lastModified timestamp so lightning cache can make use of its optimizations - return new File([fileData], this.name) + return await this.baseStore.readFile(this.idbKey) } async createWritable() { return new VirtualWritable(this) } async [writeMethodSymbol](data: Uint8Array) { - if (this.inMemory) this.fileData = data - else await this.updateIdb(data) + await this.baseStore.writeFile(this.idbKey, data) } } diff --git a/src/components/FileSystem/Virtual/Handle.ts b/src/components/FileSystem/Virtual/Handle.ts index dbe9631b3..377446c70 100644 --- a/src/components/FileSystem/Virtual/Handle.ts +++ b/src/components/FileSystem/Virtual/Handle.ts @@ -2,36 +2,51 @@ import type { VirtualDirectoryHandle } from './DirectoryHandle' import type { VirtualFileHandle } from './FileHandle' import { v4 as v4Uuid } from 'uuid' import { Signal } from '../../Common/Event/Signal' -import { IDBWrapper } from './IDB' +import { BaseStore } from './Stores/BaseStore' +import { MemoryStore } from './Stores/Memory' +import { TauriFsStore } from './Stores/TauriFs' export type VirtualHandle = VirtualDirectoryHandle | VirtualFileHandle export abstract class BaseVirtualHandle { - protected _idbWrapper: IDBWrapper | null = null + protected _baseStore: BaseStore | null = null protected parent: VirtualDirectoryHandle | null = null public readonly isVirtual = true public abstract readonly kind: 'directory' | 'file' public readonly setupDone = new Signal() - - abstract moveData(): any + protected includeSelfInPath = true constructor( - parent: VirtualDirectoryHandle | IDBWrapper | null, + parent: VirtualDirectoryHandle | BaseStore | null, protected _name: string, - protected basePath: string[] = [], - public readonly uuid = v4Uuid() + protected basePath: string[] = [] ) { if (parent === null) { - this._idbWrapper = new IDBWrapper() - } else if (parent instanceof IDBWrapper) { - this._idbWrapper = parent + this._baseStore = new MemoryStore() + } else if (parent instanceof BaseStore) { + this._baseStore = parent } else { this.parent = parent } + + /** + * TauriFs should not include self in path for top-level directory handle + */ if (this._baseStore instanceof TauriFsStore) { + this.includeSelfInPath = false + } + } + + async setupStore() { + if (this._baseStore) await this._baseStore.setup() + } + getBaseStore() { + return this.baseStore } protected get path(): string[] { - return this.parent ? this.parent.path.concat(this.name) : this.basePath + return this.parent + ? [...this.parent.path.concat(this.name)] + : [...this.basePath] } /** * Returns whether a handle has parent context @@ -42,23 +57,22 @@ export abstract class BaseVirtualHandle { return this.path.length > 1 } get idbKey() { - if (this.path.length === 0) return this._name + if (this.path.length === 0) + return this.includeSelfInPath ? this.name : '' return this.path.join('/') } - protected get idbWrapper(): IDBWrapper { - if (this._idbWrapper === null) { - return this.parent!.idbWrapper + protected get baseStore(): BaseStore { + if (this._baseStore === null) { + return this.parent!.baseStore } - return this._idbWrapper + return this._baseStore } abstract removeSelf(): Promise get name() { return this._name } - getParent() { - return this.parent - } + abstract getParent(): VirtualDirectoryHandle | null abstract serialize(): unknown async isSameEntry(other: BaseVirtualHandle) { diff --git a/src/components/FileSystem/Virtual/Stores/BaseStore.ts b/src/components/FileSystem/Virtual/Stores/BaseStore.ts new file mode 100644 index 000000000..828da1f00 --- /dev/null +++ b/src/components/FileSystem/Virtual/Stores/BaseStore.ts @@ -0,0 +1,72 @@ +export const FsKindEnum = { + Directory: 0, + File: 1, +} +export type TFsKind = typeof FsKindEnum['Directory' | 'File'] +export interface IFsEntry { + kind: TFsKind + data: T +} +export interface IFileData extends IFsEntry { + kind: typeof FsKindEnum.File + lastModified: number +} +export interface IDirectoryData extends IFsEntry { + kind: typeof FsKindEnum.Directory +} +export interface IDirEntry { + kind: TFsKind + name: string +} + +export type TStoreType = 'idbStore' | 'memoryStore' | 'tauriFsStore' + +/** + * Base implementation of a file system backing store that can be used by our file system access polyfill + */ +export abstract class BaseStore { + public abstract readonly type: TStoreType + + constructor(protected isReadOnly = false) {} + + /** + * Any async setup that needs to be done before the store can be used + */ + async setup() {} + + abstract serialize(): T & { type: TStoreType } + static deserialize(data: any & { type: TStoreType }): BaseStore { + throw new Error('BaseStore deserialization not implemented') + } + + /** + * Create directory + */ + abstract createDirectory(path: string): Promise + + /** + * Get directory entries + */ + abstract getDirectoryEntries(path: string): Promise<(IDirEntry | string)[]> + + /** + * Write file + */ + abstract writeFile(path: string, data: Uint8Array): Promise + + /** + * Read file + */ + abstract readFile(path: string): Promise + + /** + * Unlink a file or directory + */ + abstract unlink(path: string): Promise + + /** + * Return the type of a given path + * @returns null if the path does not exist + */ + abstract typeOf(path: string): Promise +} diff --git a/src/components/FileSystem/Virtual/Stores/Deserialize.ts b/src/components/FileSystem/Virtual/Stores/Deserialize.ts new file mode 100644 index 000000000..6b76e7b47 --- /dev/null +++ b/src/components/FileSystem/Virtual/Stores/Deserialize.ts @@ -0,0 +1,24 @@ +import type { BaseStore, TStoreType } from './BaseStore' +import { IndexedDbStore } from './IndexedDb' +import { MemoryStore } from './Memory' +import { TauriFsStore } from './TauriFs' + +export function deserializeStore(data: any & { type: TStoreType }): BaseStore { + if (typeof data !== 'object' || data === null) + throw new Error('BaseStore deserialization data must be an object') + if (!('type' in data)) + throw new Error( + 'BaseStore deserialization data must have a type property' + ) + + switch (data.type) { + case 'idbStore': + return IndexedDbStore.deserialize(data) + case 'memoryStore': + return MemoryStore.deserialize(data) + case 'tauriFsStore': + return TauriFsStore.deserialize(data) + default: + throw new Error(`Unknown base store type: ${data.type}`) + } +} diff --git a/src/components/FileSystem/Virtual/Stores/IndexedDb.ts b/src/components/FileSystem/Virtual/Stores/IndexedDb.ts new file mode 100644 index 000000000..f664a85b7 --- /dev/null +++ b/src/components/FileSystem/Virtual/Stores/IndexedDb.ts @@ -0,0 +1,207 @@ +import { IDBWrapper } from '../IDB' +import { + BaseStore, + FsKindEnum, + IDirectoryData, + IDirEntry, + IFileData, + TFsKind, + TStoreType, +} from './BaseStore' +import { GlobalMutex } from '/@/components/Common/GlobalMutex' +import { basename, dirname } from '/@/utils/path' + +export interface IIndexedDbSerializedData { + storeName?: string + mapData?: [string, any][] +} + +export class IndexedDbStore extends BaseStore { + public readonly type = 'idbStore' + protected idb: IDBWrapper + protected globalMutex = new GlobalMutex() + + constructor(storeName?: string, isReadOnly = false) { + super(isReadOnly) + this.idb = new IDBWrapper(storeName) + } + + serialize() { + return { + type: this.type, + storeName: this.idb.storeName, + } + } + static deserialize(data: IIndexedDbSerializedData & { type: TStoreType }) { + return new IndexedDbStore(data.storeName) + } + + lockAccess(path: string) { + return this.globalMutex.lock(path) + } + unlockAccess(path: string) { + return this.globalMutex.unlock(path) + } + + clear() { + if (this.isReadOnly) return + + return this.idb.clear() + } + + protected async addChild( + parentDir: string, + childName: string, + childKind: TFsKind + ) { + // If parent directory is root directory, we don't need to manually add a child entry + if (parentDir === '' || parentDir === '.') return + + await this.lockAccess(parentDir) + + let parentChilds = await this.idb.get( + parentDir + ) + + if (parentChilds === undefined) { + await this.createDirectory(parentDir) + parentChilds = [] + } + + if (Array.isArray(parentChilds)) { + // Old format where we stored dir entries as strings + + // Filter out childName from parentChilds + parentChilds = parentChilds + .filter((child) => typeof child === 'string') // This filter call is only there to fix already duplicated entries in the database. Can be removed in a future update + .filter((child) => child !== childName) + + // Push new child entry + parentChilds.push(childName) + } else { + // New format where we store dir entries as objects + + // Filter out childName from parentChilds + parentChilds = { + kind: FsKindEnum.Directory, + data: parentChilds.data.filter((child) => + typeof child === 'string' + ? child !== childName + : child.name !== childName + ), + } + + // Push new child entry + parentChilds.data.push({ + kind: childKind, + name: childName, + }) + } + + await this.idb.set(parentDir, parentChilds) + + this.unlockAccess(parentDir) + } + + async createDirectory(path: string) { + if (this.isReadOnly) return + if (path === '' || path === '.') return + + const dirExists = await this.idb.has(path) + if (dirExists) return // No work to do, directory already exists + + const parentDir = dirname(path) + const dirName = basename(path) + + await this.addChild(parentDir, dirName, FsKindEnum.Directory) + await this.idb.set(path, { kind: FsKindEnum.Directory, data: [] }) + } + + async getDirectoryEntries(path: string) { + const entries = await this.idb.get(path) + if (entries === undefined) { + throw new Error( + `Trying to get directory entries for ${path} but it does not exist` + ) + } + + return Array.isArray(entries) ? entries : entries.data + } + + async writeFile(path: string, data: Uint8Array) { + if (this.isReadOnly) return + + const parentDir = dirname(path) + const fileName = basename(path) + + await this.addChild(parentDir, fileName, FsKindEnum.File) + + await this.idb.set(path, { + kind: FsKindEnum.File, + lastModified: Date.now(), + data, + }) + } + + async readFile(path: string) { + const rawData = await this.idb.get(path) + + if (rawData === undefined) { + throw new Error(`Trying to read file ${path} that does not exist`) + } + + let data: Uint8Array + let lastModified = Date.now() + // Old format where we stored Uint8Array directly + if (rawData instanceof Uint8Array) { + data = rawData + } else { + lastModified = rawData.lastModified ?? Date.now() + data = rawData.data ?? new Uint8Array() + } + + return new File([data], basename(path), { lastModified }) + } + + async unlink(path: string) { + if (this.isReadOnly) return + + const parentDir = dirname(path) + const fileName = basename(path) + + await this.lockAccess(parentDir) + + const parentChilds = await this.idb.get( + parentDir + ) + if (parentChilds !== undefined) { + if (Array.isArray(parentChilds)) { + await this.idb.set( + parentDir, + parentChilds.filter((child) => child !== fileName) + ) + } else { + await this.idb.set(parentDir, { + kind: FsKindEnum.Directory, + data: parentChilds.data.filter( + (child) => child.name !== fileName + ), + }) + } + } + // TODO: Fix directory unlinking + + await this.idb.del(path) + + this.unlockAccess(parentDir) + } + + async typeOf(path: string) { + const data = await this.idb.get(path) + if (data === undefined) return null + + if (data instanceof Uint8Array) return 'file' + else if (Array.isArray(data)) return 'directory' + else return data.kind === FsKindEnum.Directory ? 'directory' : 'file' // New object based file format + } +} diff --git a/src/components/FileSystem/Virtual/Stores/Memory.ts b/src/components/FileSystem/Virtual/Stores/Memory.ts new file mode 100644 index 000000000..4a5c78290 --- /dev/null +++ b/src/components/FileSystem/Virtual/Stores/Memory.ts @@ -0,0 +1,80 @@ +import { IDBWrapper } from '../IDB' +import { TFsKind, TStoreType } from './BaseStore' +import { IIndexedDbSerializedData, IndexedDbStore } from './IndexedDb' + +export class MemoryDb extends IDBWrapper { + public readonly type = 'memoryStore' + protected _store: Map + + constructor( + public readonly storeName: string = 'virtual-fs', + mapData?: [IDBValidKey, any][] + ) { + super(storeName) + + this._store = new Map(mapData) + } + + async set(key: IDBValidKey, value: any) { + this._store.set(key, value) + } + async get(key: IDBValidKey) { + return this._store.get(key) + } + async del(key: IDBValidKey) { + this._store.delete(key) + } + async setMany(arr: [IDBValidKey, any][]) { + for (const [key, value] of arr) { + await this.set(key, value) + } + } + async getMany(arr: IDBValidKey[]) { + const res: T[] = [] + for (const key of arr) { + res.push(await this.get(key)) + } + return res + } + async clear() { + return this._store.clear() + } + async has(key: IDBValidKey) { + return this._store.has(key) + } + async keys() { + return [...this._store.keys()] + } + entries() { + return this._store.entries() + } + + async toIdb(isReadOnly = false) { + await super.setMany([...this._store.entries()]) + return new IndexedDbStore(this.storeName, isReadOnly) + } +} + +export class MemoryStore extends IndexedDbStore { + protected idb: MemoryDb + + constructor(storeName?: string, mapData?: [IDBValidKey, any][]) { + super(storeName) + this.idb = new MemoryDb(storeName, mapData) + } + + serialize() { + return { + type: this.type, + storeName: this.idb.storeName, + mapData: [...this.idb.entries()], + } + } + static deserialize(data: IIndexedDbSerializedData & { type: TStoreType }) { + return new MemoryStore(data.storeName, data.mapData) + } + + toIdb(isReadOnly = false) { + return this.idb.toIdb(isReadOnly) + } +} diff --git a/src/components/FileSystem/Virtual/Stores/TauriFs.ts b/src/components/FileSystem/Virtual/Stores/TauriFs.ts new file mode 100644 index 000000000..57667ce22 --- /dev/null +++ b/src/components/FileSystem/Virtual/Stores/TauriFs.ts @@ -0,0 +1,116 @@ +import { + createDir, + readDir, + writeBinaryFile, + readBinaryFile, + removeDir, + removeFile, +} from '@tauri-apps/api/fs' +import { basename, join, sep } from '@tauri-apps/api/path' +import { invoke } from '@tauri-apps/api/tauri' +import { BaseStore, FsKindEnum, type TStoreType } from './BaseStore' + +export interface ITauriFsSerializedData { + baseDirectory?: string +} + +export class TauriFsStore extends BaseStore { + public readonly type = 'tauriFsStore' + + constructor(protected baseDirectory?: string) { + super() + } + + getBaseDirectory() { + return this.baseDirectory + } + + serialize() { + return { + type: this.type, + baseDirectory: this.baseDirectory, + } + } + static deserialize(data: ITauriFsSerializedData & { type: TStoreType }) { + return new TauriFsStore(data.baseDirectory) + } + + async setup() { + if (this.isReadOnly) return + + if (this.baseDirectory) + await createDir(this.baseDirectory).catch(() => { + // Ignore error if directory already exists + }) + } + + async resolvePath(path: string) { + path = path.replaceAll(/\\|\//g, sep) + if (!this.baseDirectory) return path + return await join(this.baseDirectory, path) + } + + async createDirectory(path: string) { + if (this.isReadOnly) return + + await createDir(await this.resolvePath(path)).catch(() => { + // Ignore error if directory already exists + }) + } + + async getDirectoryEntries(path: string) { + const entries = await readDir(await this.resolvePath(path)) + + return entries.map((entry) => ({ + kind: entry.children ? FsKindEnum.Directory : FsKindEnum.File, + name: entry.name!, + })) + } + + async writeFile(path: string, data: Uint8Array) { + if (this.isReadOnly) return + + await writeBinaryFile(await this.resolvePath(path), data) + } + + async readFile(path: string) { + const [lastModified, data] = (await invoke('get_file_data', { + path: await this.resolvePath(path), + })) as [number, number[]] + + return new File([new Uint8Array(data)], await basename(path), { + // Rust returns a unix timestamp in seconds but File expects milliseconds + lastModified: lastModified * 1000, + }) + } + + async unlink(path: string) { + if (this.isReadOnly) return + + const type = await this.typeOf(path) + // Path does not exist, nothing to unlink + if (type === null) return + + if (type === 'file') { + await removeFile(await this.resolvePath(path)) + } else { + await removeDir(await this.resolvePath(path)) + } + } + + async typeOf(path: string) { + const resolvedPath = await this.resolvePath(path) + + try { + await readDir(resolvedPath) + return 'directory' + } catch (err) { + try { + await readBinaryFile(resolvedPath) + return 'file' + } catch (err) { + return null + } + } + } +} diff --git a/src/components/FileSystem/Virtual/getParent.ts b/src/components/FileSystem/Virtual/getParent.ts new file mode 100644 index 000000000..eba429f27 --- /dev/null +++ b/src/components/FileSystem/Virtual/getParent.ts @@ -0,0 +1,11 @@ +import { VirtualDirectoryHandle } from './DirectoryHandle' +import { BaseStore } from './Stores/BaseStore' + +export function getParent(baseStore: BaseStore, basePath: string[]) { + return new VirtualDirectoryHandle( + baseStore, + // Base path always contains itself so new directory handle name is at index - 2 + basePath.length > 1 ? basePath[basePath.length - 2] : '', + basePath.slice(0, -1) + ) +} diff --git a/src/components/FileSystem/Virtual/pathFromHandle.ts b/src/components/FileSystem/Virtual/pathFromHandle.ts new file mode 100644 index 000000000..a3404446e --- /dev/null +++ b/src/components/FileSystem/Virtual/pathFromHandle.ts @@ -0,0 +1,19 @@ +import { AnyHandle } from '../Types' +import { BaseVirtualHandle } from './Handle' + +export async function pathFromHandle(handle: AnyHandle) { + if (!import.meta.env.VITE_IS_TAURI_APP) + throw new Error('Can only get path from handle in Tauri builds') + if (!(handle instanceof BaseVirtualHandle)) + throw new Error(`Expected a virtual handle`) + + const { TauriFsStore } = await import('./Stores/TauriFs') + + const baseStore = handle.getBaseStore() + if (!(baseStore instanceof TauriFsStore)) + throw new Error( + `Expected a TauriFsStore instance to back VirtualHandle` + ) + + return await baseStore.resolvePath(handle.idbKey) +} diff --git a/src/components/FileSystem/saveOrDownload.ts b/src/components/FileSystem/saveOrDownload.ts index b9e6a38af..189b4b119 100644 --- a/src/components/FileSystem/saveOrDownload.ts +++ b/src/components/FileSystem/saveOrDownload.ts @@ -5,12 +5,21 @@ import { FileSystem } from './FileSystem' import { isUsingFileSystemPolyfill, isUsingOriginPrivateFs } from './Polyfill' import { App } from '/@/App' import { basename, extname } from '/@/utils/path' +import { revealInFileExplorer } from '/@/utils/revealInFileExplorer' export async function saveOrDownload( filePath: string, fileData: Uint8Array, fileSystem: FileSystem ) { + if ( + import.meta.env.VITE_IS_TAURI_APP || + !isUsingOriginPrivateFs || + isUsingFileSystemPolyfill.value + ) { + await fileSystem.writeFile(filePath, fileData) + } + const notification = createNotification({ icon: 'mdi-download', color: 'success', @@ -20,7 +29,15 @@ export async function saveOrDownload( onClick: async () => { const app = await App.getApp() - if ( + if (import.meta.env.VITE_IS_TAURI_APP) { + const { join, appLocalDataDir } = await import( + '@tauri-apps/api/path' + ) + + revealInFileExplorer( + await join(await appLocalDataDir(), 'bridge', filePath) + ) + } else if ( app.project.isLocal || isUsingOriginPrivateFs || isUsingFileSystemPolyfill.value @@ -37,10 +54,6 @@ export async function saveOrDownload( notification.dispose() }, }) - - if (!isUsingOriginPrivateFs || isUsingFileSystemPolyfill.value) { - await fileSystem.writeFile(filePath, fileData) - } } const knownExtensions = new Set([ @@ -59,6 +72,12 @@ export function download(fileName: string, fileData: Uint8Array) { const extension = extname(fileName) let type: string | undefined = undefined + // File downloads are not working on Tauri atm + if (import.meta.env.VITE_IS_TAURI_APP) { + tauriDownload(fileName, fileData) + return + } + // Maintain the extension from the fileName, if the file that is being downloaded has a known extension if (knownExtensions.has(extension)) type = 'application/file-export' @@ -70,3 +89,24 @@ export function download(fileName: string, fileData: Uint8Array) { URL.revokeObjectURL(url) } + +async function tauriDownload(fileName: string, fileData: Uint8Array) { + const { writeBinaryFile, exists } = await import('@tauri-apps/api/fs') + const { downloadDir, join } = await import('@tauri-apps/api/path') + + let freeFileName = fileName + let i = 1 + while (await exists(await join(await downloadDir(), freeFileName))) { + const extension = extname(freeFileName) + // Remove extension and counter (i) from the file name + const name = basename(freeFileName, extension).replace(/ \(\d+\)$/, '') + freeFileName = `${name} (${i})${extension}` + i++ + } + + await writeBinaryFile( + await join(await downloadDir(), freeFileName), + fileData + ) + await revealInFileExplorer(await join(await downloadDir(), freeFileName)) +} diff --git a/src/components/FindAndReplace/Tab.ts b/src/components/FindAndReplace/Tab.ts index d95385646..5e6b620e2 100644 --- a/src/components/FindAndReplace/Tab.ts +++ b/src/components/FindAndReplace/Tab.ts @@ -1,6 +1,5 @@ import { reactive, markRaw } from 'vue' import { Remote, wrap } from 'comlink' -import { Signal } from '../Common/Event/Signal' import { searchType } from './Controls/searchType' import FindAndReplaceComponent from './Tab.vue' import type { @@ -15,8 +14,11 @@ import { TabSystem } from '../TabSystem/TabSystem' import { Mutex } from '../Common/Mutex' import { AnyFileHandle } from '../FileSystem/Types' import { translate } from '../Locales/Manager' +import { setupWorker } from '/@/utils/worker/setup' -const FindAndReplaceClass = wrap(new Worker()) +const worker = new Worker() +const FindAndReplaceClass = wrap(worker) +setupWorker(worker) interface ITabState { scrollTop: number diff --git a/src/components/FindAndReplace/Worker/Worker.ts b/src/components/FindAndReplace/Worker/Worker.ts index d63f602b8..81efc1486 100644 --- a/src/components/FindAndReplace/Worker/Worker.ts +++ b/src/components/FindAndReplace/Worker/Worker.ts @@ -1,11 +1,5 @@ // @ts-ignore Make "path" work on this worker -globalThis.process = { - cwd: () => '', - env: {}, - release: { - name: 'browser', - }, -} +import '/@/utils/worker/inject' import '/@/components/FileSystem/Virtual/Comlink' import { expose } from 'comlink' diff --git a/src/components/ImportFolder/ImportProjects.ts b/src/components/ImportFolder/ImportProjects.ts new file mode 100644 index 000000000..40f13c1b9 --- /dev/null +++ b/src/components/ImportFolder/ImportProjects.ts @@ -0,0 +1,72 @@ +import { FileSystem } from '../FileSystem/FileSystem' +import { showFolderPicker } from '../FileSystem/Pickers/showFolderPicker' +import { translate } from '../Locales/Manager' +import { virtualProjectName } from '../Projects/Project/Project' +import { ConfirmationWindow } from '../Windows/Common/Confirm/ConfirmWindow' +import { InformationWindow } from '../Windows/Common/Information/InformationWindow' +import { App } from '/@/App' + +export async function importProjects() { + const app = await App.getApp() + + // Show warning if the user has projects already + const projects = (await app.fileSystem.readdir('projects')).filter( + (projectName) => projectName !== virtualProjectName + ) + + if (projects.length > 0) { + const confirmWindow = new ConfirmationWindow({ + description: + 'packExplorer.noProjectView.importOldProjects.confirmOverwrite', + }) + const choice = await confirmWindow.fired + + if (!choice) return + } + + app.windows.loadingWindow.open() + + const bridgeProjects = await showFolderPicker({ multiple: true }) + + if (!bridgeProjects) { + app.windows.loadingWindow.close() + return + } + + let didSelectProject = false + for (const bridgeProject of bridgeProjects) { + const bridgeProjectFs = new FileSystem(bridgeProject) + // Ensure that the folder is a bridge project by checking for the config.json fike + if (!(await bridgeProjectFs.fileExists('config.json'))) { + new InformationWindow({ + title: `[${translate('toolbar.project.name')}: "${ + bridgeProject.name + }"]`, + description: + 'packExplorer.noProjectView.importOldProjects.notABridgeProject', + }) + continue + } + + const newProjectDir = await app.fileSystem.getDirectoryHandle( + `projects/${bridgeProject.name}`, + { create: true } + ) + await app.fileSystem.copyFolderByHandle( + bridgeProject, + newProjectDir, + new Set(['builds', '.git']) + ) + + // Load projects and only select the first one + app.projectManager.addProject( + bridgeProject, + true, + true, + !didSelectProject + ) + if (!didSelectProject) didSelectProject = true + } + + app.windows.loadingWindow.close() +} diff --git a/src/components/ImportFolder/Manager.ts b/src/components/ImportFolder/Manager.ts index 9a1dd546b..92f3cd166 100644 --- a/src/components/ImportFolder/Manager.ts +++ b/src/components/ImportFolder/Manager.ts @@ -13,16 +13,24 @@ export class FolderImportManager { protected readonly folderHandler = new Set() constructor() { - this.addImporter({ - icon: 'mdi-minecraft', - name: 'fileDropper.importMethod.folder.output.name', - description: 'fileDropper.importMethod.folder.output.description', - onSelect: async (directoryHandle) => { - const app = await App.getApp() + /** + * Setting the output folder isn't possible on Tauri builds + * We don't want a browser directory handle, we want our custom virtual directory handle + */ + if (!import.meta.env.VITE_IS_TAURI_APP) { + this.addImporter({ + icon: 'mdi-minecraft', + name: 'fileDropper.importMethod.folder.output.name', + description: + 'fileDropper.importMethod.folder.output.description', + onSelect: async (directoryHandle) => { + const app = await App.getApp() + + await app.comMojang.handleComMojangDrop(directoryHandle) + }, + }) + } - await app.comMojang.handleComMojangDrop(directoryHandle) - }, - }) this.addImporter({ icon: 'mdi-folder-open-outline', name: 'fileDropper.importMethod.folder.open.name', @@ -54,6 +62,16 @@ export class FolderImportManager { } async onImportFolder(directoryHandle: AnyDirectoryHandle) { + /** + * If there is only one handler, use it without prompting the user for a choice + */ + if (this.folderHandler.size === 1) { + const handler = [...this.folderHandler][0] + await handler.onSelect(directoryHandle) + + return + } + const informedChoiceWindow = new InformedChoiceWindow( 'fileDropper.importMethod.name', { diff --git a/src/components/Mixins/AppToolbarHeight.ts b/src/components/Mixins/AppToolbarHeight.ts index fe940a343..308cb59ed 100644 --- a/src/components/Mixins/AppToolbarHeight.ts +++ b/src/components/Mixins/AppToolbarHeight.ts @@ -2,12 +2,20 @@ import { WindowControlsOverlayMixin } from './WindowControlsOverlay' import { isInFullScreen } from '/@/components/TabSystem/TabContextMenu/Fullscreen' +import { platform } from '/@/utils/os' export const AppToolbarHeightMixin = { mixins: [WindowControlsOverlayMixin], + data: () => ({ + toolbarPaddingLeft: + import.meta.env.VITE_IS_TAURI_APP && platform() === 'darwin' + ? `70px` + : `0px`, + }), computed: { appToolbarHeight() { if (isInFullScreen.value) return `0px` + if (import.meta.env.VITE_IS_TAURI_APP) return `28px` return `env(titlebar-area-height, ${ this.$vuetify.breakpoint.mobile ? 0 : 24 @@ -15,6 +23,7 @@ export const AppToolbarHeightMixin = { }, appToolbarHeightNumber() { if (isInFullScreen.value) return 0 + if (import.meta.env.VITE_IS_TAURI_APP) return 28 if (this.windowControlsOverlay) return 33 return this.$vuetify.breakpoint.mobile ? 0 : 24 diff --git a/src/components/Mixins/WindowControlsOverlay.ts b/src/components/Mixins/WindowControlsOverlay.ts index a369f1c6c..3e4b6fd88 100644 --- a/src/components/Mixins/WindowControlsOverlay.ts +++ b/src/components/Mixins/WindowControlsOverlay.ts @@ -7,7 +7,9 @@ export const WindowControlsOverlayMixin = { navigator.windowControlsOverlay.visible, }), created() { - if (navigator.windowControlsOverlay) { + if (import.meta.env.VITE_IS_TAURI_APP) { + this.windowControlsOverlay = true + } else if (navigator.windowControlsOverlay) { navigator.windowControlsOverlay.addEventListener( 'geometrychange', (event) => { diff --git a/src/components/OutputFolders/ComMojang/ComMojang.ts b/src/components/OutputFolders/ComMojang/ComMojang.ts index 0bee10300..c2391a3ca 100644 --- a/src/components/OutputFolders/ComMojang/ComMojang.ts +++ b/src/components/OutputFolders/ComMojang/ComMojang.ts @@ -1,10 +1,13 @@ import { del, get, set } from 'idb-keyval' -import { ConfirmationWindow } from '/@/components/Windows/Common/Confirm/ConfirmWindow' import { FileSystem } from '/@/components/FileSystem/FileSystem' import { App } from '/@/App' import { Signal } from '/@/components/Common/Event/Signal' import { InformationWindow } from '/@/components/Windows/Common/Information/InformationWindow' import { AnyDirectoryHandle } from '/@/components/FileSystem/Types' +import { VirtualDirectoryHandle } from '../../FileSystem/Virtual/DirectoryHandle' +import { TauriFsStore } from '../../FileSystem/Virtual/Stores/TauriFs' +import { basename, join } from '/@/utils/path' +import { platform } from '/@/utils/os' export const comMojangKey = 'comMojangDirectory' @@ -37,9 +40,36 @@ export class ComMojang extends Signal { async setupComMojang() { if (this._hasComMojang) return false - const directoryHandle = await get( - comMojangKey - ) + let directoryHandle = await get< + AnyDirectoryHandle | string | undefined + >(comMojangKey) + + // Auto-infer com.mojang folder for Tauri builds on windows + if ( + import.meta.env.VITE_IS_TAURI_APP && + directoryHandle === undefined && + platform() === 'win32' + ) { + const { join, localDataDir } = await import('@tauri-apps/api/path') + + directoryHandle = await join( + await localDataDir(), + 'Packages\\Microsoft.MinecraftUWP_8wekyb3d8bbwe\\LocalState\\games\\com.mojang' + ) + } + + if (typeof directoryHandle === 'string') { + if (!import.meta.env.VITE_IS_TAURI_APP) + throw new Error( + 'Cannot use path reference to com.mojang directoryHandle outside of Tauri builds' + ) + + const dirName = basename(directoryHandle) + directoryHandle = new VirtualDirectoryHandle( + new TauriFsStore(directoryHandle), + dirName + ) + } this._hasComMojangHandle = directoryHandle !== undefined @@ -47,7 +77,9 @@ export class ComMojang extends Signal { await this.requestPermissions(directoryHandle).catch(async () => { // Permission request failed because user activation was too long ago // -> Create window to get new activation - await this.createPermissionWindow(directoryHandle) + await this.createPermissionWindow( + directoryHandle + ) }) if (this._hasComMojang) { @@ -90,7 +122,18 @@ export class ComMojang extends Signal { } async set(directoryHandle: AnyDirectoryHandle) { - set(comMojangKey, directoryHandle) + if (directoryHandle instanceof VirtualDirectoryHandle) { + const store = directoryHandle.getBaseStore() + if (!(store instanceof TauriFsStore)) + throw new Error( + 'Cannot set com.mojang directoryHandle to non-tauri-backed store' + ) + + set(comMojangKey, store.getBaseDirectory()) + } else { + set(comMojangKey, directoryHandle) + } + await this.requestPermissions(directoryHandle) if (this._hasComMojang) this.setup.dispatch() diff --git a/src/components/PackExplorer/HomeView/BridgeFolderBtn.vue b/src/components/PackExplorer/HomeView/BridgeFolderBtn.vue index 2c1249a33..f4e85b174 100644 --- a/src/components/PackExplorer/HomeView/BridgeFolderBtn.vue +++ b/src/components/PackExplorer/HomeView/BridgeFolderBtn.vue @@ -1,6 +1,6 @@