diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3f9b44f4..1aa8075c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -4,25 +4,39 @@ on:
types: [created]
jobs:
- release:
- name: release ${{ matrix.target }}
- runs-on: ubuntu-latest
+ build-tauri:
strategy:
fail-fast: false
matrix:
- include:
- - target: x86_64-pc-windows-gnu
- archive: zip
- - target: x86_64-unknown-linux-musl
- archive: tar.gz tar.xz
- - target: x86_64-apple-darwin
- archive: zip
+ platform: [macos-latest, ubuntu-20.04, windows-latest]
+
+ runs-on: ${{ matrix.platform }}
steps:
- - uses: actions/checkout@master
- - name: Compile and release
- uses: rust-build/rust-build.action@v1.3.2
+ - uses: actions/checkout@v2
+ - name: setup node
+ uses: actions/setup-node@v1
+ with:
+ node-version: 16
+ - name: install Rust stable
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ - name: install dependencies (ubuntu only)
+ if: matrix.platform == 'ubuntu-20.04'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
+ - name: install app dependencies and build it
+ run: npm i && npm run build
+
+ - name: Get release
+ id: get_release
+ uses: bruceadams/get-release@v1.3.2
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
- RUSTTARGET: ${{ matrix.target }}
- ARCHIVE_TYPES: ${{ matrix.archive }}
+ releaseId: ${{ steps.get_release.outputs.id }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..4ab51f24
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,35 @@
+name: "test-on-pr"
+on: [pull_request]
+
+jobs:
+ test-tauri:
+ strategy:
+ fail-fast: false
+ matrix:
+ platform: [macos-latest, ubuntu-20.04, windows-latest]
+
+ runs-on: ${{ matrix.platform }}
+ steps:
+ - uses: actions/checkout@v2
+ - name: setup node
+ uses: actions/setup-node@v1
+ with:
+ node-version: 16
+ - name: install Rust stable
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ - name: install dependencies (ubuntu only)
+ if: matrix.platform == 'ubuntu-20.04'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
+ - name: install app dependencies and build it
+ run: npm i && npm run build
+ - uses: tauri-apps/tauri-action@v0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - uses: actions/upload-artifact@v3
+ with:
+ name: bundle
+ path: src-tauri/target/release/bundle/
diff --git a/.gitignore b/.gitignore
index 22d35163..b93eddfb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,15 +1,26 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
-Cargo.lock
+node_modules
+dist
+dist-ssr
+*.local
-# These are backup files generated by rustfmt
-**/*.rs.bk
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
-
-# Added by cargo
-
-/target
+target
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 00000000..24d7cc6d
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
+}
diff --git a/Cargo.toml b/Cargo.toml
deleted file mode 100644
index 8fa16ff3..00000000
--- a/Cargo.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-[package]
-name = "pocket-sync"
-version = "0.2.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-chrono = "0.4.22"
-clap = { version = "4.0.18", features = ["derive"] }
-filetime = "0.2.18"
-suppaftp = "4.5.2"
-question = "0.2.2"
-serde = { version = "1.0.147", features = ["derive"] }
-serde_json = "1.0.87"
-walkdir = "2.3.2"
diff --git a/README.md b/README.md
index a4c9ccce..087a6886 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,34 @@
# pocket-sync
-A tool for syncing the Analogue Pocket (initially with MiSTer)
-[![asciicast](https://asciinema.org/a/VnRRsQj8BOikkHi3PKgo4OeWI.svg)](https://asciinema.org/a/VnRRsQj8BOikkHi3PKgo4OeWI?autoplay=1)
+A Windows / Mac / Linx GUI to do _stuff_ with the Analogue Pocket.
+![The Cores List](./readme_images/cores_list.png)
-__WARNING__ this is a _very_ early release - please don't use it without having backed up your saves.
-I recommend backing up via `zip -r saves_backup.zip saves` on the MiSTer & `zip -r saves_backup.zip Saves` on the Pocket.
+Features:
-- There's some inherent issues comparing last modified dates between different systems & with this you've got 3 (the Pocket, the MiSTer, and your PC) - so don't expect this to be accurate within a single day & I'd recommend against running the merge process twice in one day -- this might be fixed later if I can narrow down how the MiSTer, Pocket etc store timestamps.
+- Browse & install cores
+- Export corrected & upscalled screenshots
+- Backup save files
+- Quick links to open game file folders
-- If you see a bunch of Pocket saves with the timestamp `2020-01-01 23:00:00 +00:00` they were probably saved on an older Pocket firmware that wasn't doing timestamps.
+## Roadmap
-- If there's a save which is _only_ on the MiSTer it'll be moved to the _root folder_ on the Pocket, if the rom's in a nested folder this'll mean the save won't get picked up.
+### Soon
-- Some cores do double duty on the MiSTer (e.g. `GAMEBOY` does GB & GBC), for these cores if there isn't a Pocket save already they'll be put somewhere (hopefully) sensible (`GameGear` might be an issue though since it's in with `SMS` on the MiSTer)
+- Installing / Checking for firmware updates
+- Hopefully get the saves backups working how I'd planned
-- The ROM name must match _exactly_ for it to be picked up as a save, this might cause issues for NEOGEO.
+### Longer term
-## Usage
+- Add back MiSTer save file sync (don't really want to touch it if there's a chance I'll clopper MiSTer files with a bunch of incompatiable Genesis saves)
-- Grab the latest release for your OS from the releases section ->
-- You'll probably need to jump through the "Unidentified Developer" thing (well, on Mac anyway)
-- Your MiSTer will need to be on and have FTP enabled (on the default port, 21)
-- (Probably a good time to back up your saves just incase)
-- Run `./pocket-sync [path-to-pocket-SD-card] --host-mister [mister IP or hostname]`
-- If you've changed the user / password on your MiSTer you'll need to use `--user-mister [name]` / `--password-mister [password]`
-- It'll add a `pocket_sync.json` that (currently) just contains the timestamp of the last run so the program knows not to bother with things that've been synced already
-- It'll merge the saves by these rules:
- - If both devices have the save & they're both older than the last-run timestamp **skip**
- - If only one device has the save, **copies it to both**
- - If one device has a save newer than the last-run time & the other is older **copies newer over older**
- - If both devices have saves newer than the last-run time **allows you to pick the Pocket save, the MiSTer save, or to skip**
-## Roadmap
+## FAQs
+
+### Donations?
+
+Nah, I'm alright - you should donate to the folks porting / building cores over though.
+
+## Thanks to
-- [x] CLI for syncing saves with a MiSTer over FTP
-- [ ] GUI (Windows / Mac / Linux)
-- [ ] Library browser / editor in GUI
-- [ ] Auto-updating GUI
-- [ ] Core Updater in GUI (maybe)
-- [ ] GUI for core updator showing release notes etc
+- [The OpenFPGA Cores Inventory supplies a bunch of the data used in the app](https://github.com/joshcampbell191/openfpga-cores-inventory)
+- [This recreation of the Analogue OS font](https://github.com/AbFarid/analogue-os-font)
diff --git a/app-icon.png b/app-icon.png
new file mode 100644
index 00000000..a697e533
Binary files /dev/null and b/app-icon.png differ
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..5d85875c
--- /dev/null
+++ b/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Pocket Sync
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..aee35089
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,5820 @@
+{
+ "name": "pocket-sync",
+ "version": "2.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "pocket-sync",
+ "version": "2.0.0",
+ "dependencies": {
+ "@react-three/drei": "^9.40.0",
+ "@react-three/fiber": "^8.9.1",
+ "@tauri-apps/api": "^1.1.0",
+ "@types/three": "^0.146.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-markdown": "^8.0.3",
+ "recoil": "^0.7.6",
+ "remark-gfm": "^3.0.1",
+ "three": "^0.146.0"
+ },
+ "devDependencies": {
+ "@tauri-apps/cli": "^1.1.0",
+ "@types/node": "^18.7.10",
+ "@types/react": "^18.0.15",
+ "@types/react-dom": "^18.0.6",
+ "@vitejs/plugin-react": "^2.0.0",
+ "postcss-nesting": "^10.2.0",
+ "typescript": "^4.6.4",
+ "vite": "^3.0.2"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
+ "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.1.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
+ "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz",
+ "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz",
+ "integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.1.0",
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.2",
+ "@babel/helper-compilation-targets": "^7.20.0",
+ "@babel/helper-module-transforms": "^7.20.2",
+ "@babel/helpers": "^7.20.1",
+ "@babel/parser": "^7.20.2",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.1",
+ "@babel/types": "^7.20.2",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.1",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.20.2",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+ "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
+ "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.20.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz",
+ "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.20.0",
+ "@babel/helper-validator-option": "^7.18.6",
+ "browserslist": "^4.21.3",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.18.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
+ "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.19.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
+ "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.18.10",
+ "@babel/types": "^7.19.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
+ "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
+ "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz",
+ "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/helper-simple-access": "^7.20.2",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.1",
+ "@babel/types": "^7.20.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz",
+ "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
+ "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.20.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
+ "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.19.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
+ "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
+ "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
+ "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz",
+ "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.1",
+ "@babel/types": "^7.20.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+ "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "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,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx": {
+ "version": "7.19.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz",
+ "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.18.6",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.19.0",
+ "@babel/plugin-syntax-jsx": "^7.18.6",
+ "@babel/types": "^7.19.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-development": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz",
+ "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/plugin-transform-react-jsx": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz",
+ "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.19.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz",
+ "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.19.0"
+ },
+ "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",
+ "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==",
+ "dependencies": {
+ "regenerator-runtime": "^0.13.10"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.18.10",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
+ "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/parser": "^7.18.10",
+ "@babel/types": "^7.18.10"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz",
+ "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.1",
+ "@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",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.19.4",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@chevrotain/cst-dts-gen": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.4.1.tgz",
+ "integrity": "sha512-wNDw9Rh6dPJKH275er8nijuDIpTcG2GjQANjnG8RaeGkZ3JN99+u6HRtnjKhjoi4NY9rg+udHChHQSskZtlkPw==",
+ "dependencies": {
+ "@chevrotain/gast": "10.4.1",
+ "@chevrotain/types": "10.4.1",
+ "lodash": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/gast": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.4.1.tgz",
+ "integrity": "sha512-HRv66QVbmC7eb/ppwsPCfNH4oZ/VV+thuMZILm7A7W6Q5M0tqiZv0ecdiB8hydmPO8je0aSrXEOCcaA6fuXc3Q==",
+ "dependencies": {
+ "@chevrotain/types": "10.4.1",
+ "lodash": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/types": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.4.1.tgz",
+ "integrity": "sha512-J8iyZNn/RGYWSyNJdGd3QI01gKFUx4mCSM0+vEqmIw9TXFlxj1IsHteXFahtezSHjgMtBTqWn6hb2YxCLjpHVg=="
+ },
+ "node_modules/@chevrotain/utils": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.4.1.tgz",
+ "integrity": "sha512-vPIgzES8QhHMchb5UaQ4V/c9xmoaECN+4EXpuhWE+pu3LXJUUtAwDn/SEKFgtyiRo269Hxv3b0NbPlQfH0jeVA=="
+ },
+ "node_modules/@csstools/selector-specificity": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz",
+ "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==",
+ "dev": true,
+ "engines": {
+ "node": "^12 || ^14 || >=16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2",
+ "postcss-selector-parser": "^6.0.10"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
+ "integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
+ "integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
+ "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.0",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
+ "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "node_modules/@react-spring/animated": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.5.5.tgz",
+ "integrity": "sha512-glzViz7syQ3CE6BQOwAyr75cgh0qsihm5lkaf24I0DfU63cMm/3+br299UEYkuaHNmfDfM414uktiPlZCNJbQA==",
+ "dependencies": {
+ "@react-spring/shared": "~9.5.5",
+ "@react-spring/types": "~9.5.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/core": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.5.5.tgz",
+ "integrity": "sha512-shaJYb3iX18Au6gkk8ahaF0qx0LpS0Yd+ajb4asBaAQf6WPGuEdJsbsNSgei1/O13JyEATsJl20lkjeslJPMYA==",
+ "dependencies": {
+ "@react-spring/animated": "~9.5.5",
+ "@react-spring/rafz": "~9.5.5",
+ "@react-spring/shared": "~9.5.5",
+ "@react-spring/types": "~9.5.5"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-spring/donate"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/rafz": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.5.5.tgz",
+ "integrity": "sha512-F/CLwB0d10jL6My5vgzRQxCNY2RNyDJZedRBK7FsngdCmzoq3V4OqqNc/9voJb9qRC2wd55oGXUeXv2eIaFmsw=="
+ },
+ "node_modules/@react-spring/shared": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.5.5.tgz",
+ "integrity": "sha512-YwW70Pa/YXPOwTutExHZmMQSHcNC90kJOnNR4G4mCDNV99hE98jWkIPDOsgqbYx3amIglcFPiYKMaQuGdr8dyQ==",
+ "dependencies": {
+ "@react-spring/rafz": "~9.5.5",
+ "@react-spring/types": "~9.5.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@react-spring/three": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.5.5.tgz",
+ "integrity": "sha512-9kTIaSceqFIl5EIrdwM7Z53o5I+9BGNVzbp4oZZYMao+GMAWOosnlQdDG5GeqNsIqfW9fZCEquGqagfKAxftcA==",
+ "dependencies": {
+ "@react-spring/animated": "~9.5.5",
+ "@react-spring/core": "~9.5.5",
+ "@react-spring/shared": "~9.5.5",
+ "@react-spring/types": "~9.5.5"
+ },
+ "peerDependencies": {
+ "@react-three/fiber": ">=6.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "three": ">=0.126"
+ }
+ },
+ "node_modules/@react-spring/types": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.5.5.tgz",
+ "integrity": "sha512-7I/qY8H7Enwasxr4jU6WmtNK+RZ4Z/XvSlDvjXFVe7ii1x0MoSlkw6pD7xuac8qrHQRm9BTcbZNyeeKApYsvCg=="
+ },
+ "node_modules/@react-three/drei": {
+ "version": "9.40.0",
+ "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.40.0.tgz",
+ "integrity": "sha512-v+7l62c7BIXS172E1TiEOeXXpZnfoJNsdjZK5h0TxKVXsbhs5t6rMAlDKhgWbZ96rDzgNrSIPv+GMzlG/rkFdQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "@react-spring/three": "^9.3.1",
+ "@use-gesture/react": "^10.2.0",
+ "detect-gpu": "^4.0.36",
+ "glsl-noise": "^0.0.0",
+ "lodash.clamp": "^4.0.3",
+ "lodash.omit": "^4.5.0",
+ "lodash.pick": "^4.4.0",
+ "meshline": "^2.0.4",
+ "react-composer": "^5.0.3",
+ "react-merge-refs": "^1.1.0",
+ "stats.js": "^0.17.0",
+ "suspend-react": "^0.0.8",
+ "three-mesh-bvh": "^0.5.15",
+ "three-stdlib": "^2.17.3",
+ "troika-three-text": "^0.46.4",
+ "utility-types": "^3.10.0",
+ "zustand": "^3.5.13"
+ },
+ "peerDependencies": {
+ "@react-three/fiber": ">=8.0",
+ "react": ">=18.0",
+ "react-dom": ">=18.0",
+ "three": ">=0.137"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/fiber": {
+ "version": "8.9.1",
+ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.9.1.tgz",
+ "integrity": "sha512-xRMO9RGp0DkxSFu5BmmkjCxJ4r0dEpLobtxXdZwI0h2rZZaCnkPM5zThRN8xaZNbZhzRSVICeNOFaZltr9xFyQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.17.8",
+ "@types/react-reconciler": "^0.26.7",
+ "its-fine": "^1.0.6",
+ "react-reconciler": "^0.27.0",
+ "react-use-measure": "^2.1.1",
+ "scheduler": "^0.21.0",
+ "suspend-react": "^0.0.8",
+ "zustand": "^3.7.1"
+ },
+ "peerDependencies": {
+ "expo": ">=43.0",
+ "expo-asset": ">=8.4",
+ "expo-gl": ">=11.0",
+ "react": ">=18.0",
+ "react-dom": ">=18.0",
+ "react-native": ">=0.64",
+ "three": ">=0.133"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ },
+ "expo-asset": {
+ "optional": true
+ },
+ "expo-gl": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/fiber/node_modules/scheduler": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
+ "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.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/@tauri-apps/cli": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.2.0.tgz",
+ "integrity": "sha512-DgUnk4p/atWHq2HUx9Vt+/LuRsx4iFlkzdZIUxtFWvpcZih2k0TzmHJbrhM1evh1/7a+SqiwDawmyf3Hz1HxXA==",
+ "dev": true,
+ "bin": {
+ "tauri": "tauri.js"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/tauri"
+ },
+ "optionalDependencies": {
+ "@tauri-apps/cli-darwin-arm64": "1.2.0",
+ "@tauri-apps/cli-darwin-x64": "1.2.0",
+ "@tauri-apps/cli-linux-arm-gnueabihf": "1.2.0",
+ "@tauri-apps/cli-linux-arm64-gnu": "1.2.0",
+ "@tauri-apps/cli-linux-arm64-musl": "1.2.0",
+ "@tauri-apps/cli-linux-x64-gnu": "1.2.0",
+ "@tauri-apps/cli-linux-x64-musl": "1.2.0",
+ "@tauri-apps/cli-win32-ia32-msvc": "1.2.0",
+ "@tauri-apps/cli-win32-x64-msvc": "1.2.0"
+ }
+ },
+ "node_modules/@tauri-apps/cli-darwin-arm64": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.2.0.tgz",
+ "integrity": "sha512-f3LR2RvTU2ulxYdK9Nc3vKaSpDChu52pz0BMWNrSs3dxs4WTVioie98Ufz+GorifkUp3sYXcJte3HzX6wH/QxQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-darwin-x64": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.2.0.tgz",
+ "integrity": "sha512-m07QZaAZCtyobrjddfz/Rxf9GGutnBOpRMbNqVqCk0qKRJzHG1fIsLqkgZh6+qPv0zHpu7xi/FPcqTec72Cp8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.2.0.tgz",
+ "integrity": "sha512-Id9eF1JtthZRFVtXAAVtSlI3uMT8cJ7LYmCSIl3mAXEUeaPBxnUs1i9X6/J+2Ho3yLEuuOxJ7PaJd+4v8wnEeg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm64-gnu": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.2.0.tgz",
+ "integrity": "sha512-NtfPkkpeMPl+i/tB/Fc8ST2rKO2vV8int/RkOvNGLCkhWcl4sbzKBol7tc4q8c8h0X7FXDcF1l/EOuGsZUAA5Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm64-musl": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.2.0.tgz",
+ "integrity": "sha512-tz+mOOVsy/TMdq2WJVIJl/iwW3OCWCyD5Fls3fhyJ4XpLfjn4G+C+oU0awXD/0se0ko81aq4D+r8eDx6oBRi0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-x64-gnu": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.2.0.tgz",
+ "integrity": "sha512-FH/wU+OWZjRQvrq/oequScr72I84XgOuRuMEpt/GqGD341cBJ8ithpoyzuiKsvjS6K0qMyRFzy3eyhQ7gwX+4Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-x64-musl": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.2.0.tgz",
+ "integrity": "sha512-nLg30aBT9fI83sjIqaGPN7twbtE5LJy2DbKzxIlw59F+GT8HBdiM/2mZdTLB3AQb52yVHuGB1TVtWDsl0JHqCA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-ia32-msvc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.2.0.tgz",
+ "integrity": "sha512-eXtgIgY0fawgcOuUjH8Y6PxwPxbK87Zl9XmA7Q0m58T7pIz+gcbgvtH8Bb+liYHoRYItIhQxVm+ui7Y59rI7Cg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-x64-msvc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.2.0.tgz",
+ "integrity": "sha512-egyM66R05AIbkaUDptpHurFTIYp3VM4H5OrRd3O2b0oXf8SoiXiyrHbQsHVHHDYyytKmwkdNqjdy+Vev/Vq25Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
+ "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/hast": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
+ "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
+ "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "0.7.31",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
+ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
+ },
+ "node_modules/@types/node": {
+ "version": "18.11.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
+ "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
+ "dev": true
+ },
+ "node_modules/@types/offscreencanvas": {
+ "version": "2019.7.0",
+ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz",
+ "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg=="
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.5",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
+ },
+ "node_modules/@types/react": {
+ "version": "18.0.25",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.25.tgz",
+ "integrity": "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g==",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.0.8",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz",
+ "integrity": "sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-reconciler": {
+ "version": "0.26.7",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz",
+ "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
+ },
+ "node_modules/@types/three": {
+ "version": "0.146.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.146.0.tgz",
+ "integrity": "sha512-75AgysUrIvTCB054eQa2pDVFurfeFW8CrMQjpzjt3yHBfuuknoSvvsESd/3EhQxPrz9si3+P0wiDUVsWUlljfA==",
+ "dependencies": {
+ "@types/webxr": "*"
+ }
+ },
+ "node_modules/@types/unist": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
+ "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
+ },
+ "node_modules/@types/webxr": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
+ "integrity": "sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA=="
+ },
+ "node_modules/@use-gesture/core": {
+ "version": "10.2.22",
+ "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.2.22.tgz",
+ "integrity": "sha512-Ek0JZFYfk+hicLmoG094gm3YOuDMBNckHb988e59YOZoAkETT8dQSzT+g3QkSHSiP1m5wFXAGPSgxvOuwvGKHQ=="
+ },
+ "node_modules/@use-gesture/react": {
+ "version": "10.2.22",
+ "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.2.22.tgz",
+ "integrity": "sha512-ECo7ig16SxBE06ENIURO1woKEB6TC8qY3a0rugJjQ2f1o0Tj28xS/eYNyJuqzQB5YT0q5IrF7ZFpbx1p/5ohYA==",
+ "dependencies": {
+ "@use-gesture/core": "10.2.22"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz",
+ "integrity": "sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.19.6",
+ "@babel/plugin-transform-react-jsx": "^7.19.0",
+ "@babel/plugin-transform-react-jsx-development": "^7.18.6",
+ "@babel/plugin-transform-react-jsx-self": "^7.18.6",
+ "@babel/plugin-transform-react-jsx-source": "^7.19.6",
+ "magic-string": "^0.26.7",
+ "react-refresh": "^0.14.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^3.0.0"
+ }
+ },
+ "node_modules/@webgpu/glslang": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/@webgpu/glslang/-/glslang-0.0.15.tgz",
+ "integrity": "sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q=="
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.2.tgz",
+ "integrity": "sha512-rzSy/k7WdX5zOyeHHCOixGXbCHkyogkxPKL2r8QtzHmVQDiWCXUWa18bLdMWT9CYMLOYTjWpTHawuev2ouYJVw==",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.21.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
+ "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001400",
+ "electron-to-chromium": "^1.4.251",
+ "node-releases": "^2.0.6",
+ "update-browserslist-db": "^1.0.9"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001431",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz",
+ "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ }
+ ]
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chevrotain": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.4.1.tgz",
+ "integrity": "sha512-1y4vnssauVmrrP5MBaJ6DZvsv3BpXLlKVNK5S52fTGQHqg09qxMDBAz0wZbb04Ovc1pBCA4obcCjOlRioIV+cA==",
+ "dependencies": {
+ "@chevrotain/cst-dts-gen": "10.4.1",
+ "@chevrotain/gast": "10.4.1",
+ "@chevrotain/types": "10.4.1",
+ "@chevrotain/utils": "10.4.1",
+ "lodash": "4.17.21",
+ "regexp-to-ast": "0.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "dev": true
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
+ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
+ },
+ "node_modules/debounce": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
+ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
+ "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-gpu": {
+ "version": "4.0.48",
+ "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-4.0.48.tgz",
+ "integrity": "sha512-qrJTrCeYG/5aaMaqHlbTZu3cTZB/TZJ/ZbDDEb712FMe5irqjGwp2fyG4ag+y19f4QGKg55dQXf8IuKo+oCmRw==",
+ "dependencies": {
+ "webgl-constants": "^1.1.1"
+ }
+ },
+ "node_modules/diff": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
+ "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/draco3d": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.5.tgz",
+ "integrity": "sha512-JVuNV0EJzD3LBYhGyIXJLeBID/EVtmFO1ZNhAYflTgiMiAJlbhXQmRRda/azjc8MRVMHh0gqGhiqHUo5dIXM8Q=="
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.284",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
+ "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
+ "integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.15.13",
+ "@esbuild/linux-loong64": "0.15.13",
+ "esbuild-android-64": "0.15.13",
+ "esbuild-android-arm64": "0.15.13",
+ "esbuild-darwin-64": "0.15.13",
+ "esbuild-darwin-arm64": "0.15.13",
+ "esbuild-freebsd-64": "0.15.13",
+ "esbuild-freebsd-arm64": "0.15.13",
+ "esbuild-linux-32": "0.15.13",
+ "esbuild-linux-64": "0.15.13",
+ "esbuild-linux-arm": "0.15.13",
+ "esbuild-linux-arm64": "0.15.13",
+ "esbuild-linux-mips64le": "0.15.13",
+ "esbuild-linux-ppc64le": "0.15.13",
+ "esbuild-linux-riscv64": "0.15.13",
+ "esbuild-linux-s390x": "0.15.13",
+ "esbuild-netbsd-64": "0.15.13",
+ "esbuild-openbsd-64": "0.15.13",
+ "esbuild-sunos-64": "0.15.13",
+ "esbuild-windows-32": "0.15.13",
+ "esbuild-windows-64": "0.15.13",
+ "esbuild-windows-arm64": "0.15.13"
+ }
+ },
+ "node_modules/esbuild-android-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
+ "integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-android-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
+ "integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
+ "integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
+ "integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
+ "integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
+ "integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-32": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
+ "integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
+ "integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
+ "integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
+ "integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-mips64le": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
+ "integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-ppc64le": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
+ "integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-riscv64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
+ "integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-s390x": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
+ "integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-netbsd-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
+ "integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-openbsd-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
+ "integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-sunos-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
+ "integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-32": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
+ "integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
+ "integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
+ "integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "node_modules/fflate": {
+ "version": "0.6.10",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
+ "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/glsl-noise": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
+ "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w=="
+ },
+ "node_modules/hamt_plus": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
+ "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA=="
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz",
+ "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
+ "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
+ },
+ "node_modules/is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/its-fine": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.0.6.tgz",
+ "integrity": "sha512-VZJZPwVT2kxe5KQv+TxCjojfLiUIut8zXDNLTxcM7gJ/xQ/bSPk5M0neZ+j3myy45KKkltY1mm1jyJgx3Fxsdg==",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.0"
+ },
+ "peerDependencies": {
+ "react": ">=18.0"
+ }
+ },
+ "node_modules/its-fine/node_modules/@types/react-reconciler": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.0.tgz",
+ "integrity": "sha512-5cjk9ottZAj7eaTsqzPUIlrVbh3hBAO2YaEL1rkjHKB3xNAId7oU8GhzvAX+gfmlfoxTwJnBjPxEHyxkEA1Ffg==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
+ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ktx-parse": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.4.5.tgz",
+ "integrity": "sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg=="
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash.clamp": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz",
+ "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg=="
+ },
+ "node_modules/lodash.omit": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
+ "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg=="
+ },
+ "node_modules/lodash.pick": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
+ "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q=="
+ },
+ "node_modules/longest-streak": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz",
+ "integrity": "sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.26.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz",
+ "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==",
+ "dev": true,
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/markdown-table": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.2.tgz",
+ "integrity": "sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-definitions": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz",
+ "integrity": "sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "unist-util-visit": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz",
+ "integrity": "sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==",
+ "dependencies": {
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz",
+ "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "mdast-util-to-string": "^3.1.0",
+ "micromark": "^3.0.0",
+ "micromark-util-decode-numeric-character-reference": "^1.0.0",
+ "micromark-util-decode-string": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "unist-util-stringify-position": "^3.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz",
+ "integrity": "sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==",
+ "dependencies": {
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-gfm-autolink-literal": "^1.0.0",
+ "mdast-util-gfm-footnote": "^1.0.0",
+ "mdast-util-gfm-strikethrough": "^1.0.0",
+ "mdast-util-gfm-table": "^1.0.0",
+ "mdast-util-gfm-task-list-item": "^1.0.0",
+ "mdast-util-to-markdown": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz",
+ "integrity": "sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "ccount": "^2.0.0",
+ "mdast-util-find-and-replace": "^2.0.0",
+ "micromark-util-character": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz",
+ "integrity": "sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0",
+ "micromark-util-normalize-identifier": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.2.tgz",
+ "integrity": "sha512-T/4DVHXcujH6jx1yqpcAYYwd+z5lAYMw4Ls6yhTfbMMtCt0PHY4gEfhW9+lKsLBtyhUGKRIzcUA2FATVqnvPDA==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.6.tgz",
+ "integrity": "sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz",
+ "integrity": "sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "12.2.4",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.4.tgz",
+ "integrity": "sha512-a21xoxSef1l8VhHxS1Dnyioz6grrJkoaCUgGzMD/7dWHvboYX3VW53esRUfB5tgTyz4Yos1n25SPcj35dJqmAg==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/mdast": "^3.0.0",
+ "mdast-util-definitions": "^5.0.0",
+ "micromark-util-sanitize-uri": "^1.1.0",
+ "trim-lines": "^3.0.0",
+ "unist-builder": "^3.0.0",
+ "unist-util-generated": "^2.0.0",
+ "unist-util-position": "^4.0.0",
+ "unist-util-visit": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.3.0.tgz",
+ "integrity": "sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-to-string": "^3.0.0",
+ "micromark-util-decode-string": "^1.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz",
+ "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/meshline": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/meshline/-/meshline-2.0.4.tgz",
+ "integrity": "sha512-Jh6DJl/zLqA4xsKvGv5950jr2ukyXQE1wgxs8u94cImHrvL6soVIggqjP+2hVHZXGYaKnWszhtjuCbKNeQyYiw==",
+ "peerDependencies": {
+ "three": ">=0.137"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz",
+ "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-core-commonmark": "^1.0.1",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-combine-extensions": "^1.0.0",
+ "micromark-util-decode-numeric-character-reference": "^1.0.0",
+ "micromark-util-encode": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-subtokenize": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.1",
+ "uvu": "^0.5.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz",
+ "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-factory-destination": "^1.0.0",
+ "micromark-factory-label": "^1.0.0",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-factory-title": "^1.0.0",
+ "micromark-factory-whitespace": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-classify-character": "^1.0.0",
+ "micromark-util-html-tag-name": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-subtokenize": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.1",
+ "uvu": "^0.5.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz",
+ "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^1.0.0",
+ "micromark-extension-gfm-footnote": "^1.0.0",
+ "micromark-extension-gfm-strikethrough": "^1.0.0",
+ "micromark-extension-gfm-table": "^1.0.0",
+ "micromark-extension-gfm-tagfilter": "^1.0.0",
+ "micromark-extension-gfm-task-list-item": "^1.0.0",
+ "micromark-util-combine-extensions": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz",
+ "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==",
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz",
+ "integrity": "sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==",
+ "dependencies": {
+ "micromark-core-commonmark": "^1.0.0",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz",
+ "integrity": "sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==",
+ "dependencies": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-classify-character": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz",
+ "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==",
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz",
+ "integrity": "sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==",
+ "dependencies": {
+ "micromark-util-types": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz",
+ "integrity": "sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==",
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
+ "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz",
+ "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz",
+ "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz",
+ "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz",
+ "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz",
+ "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz",
+ "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz",
+ "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz",
+ "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz",
+ "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz",
+ "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-decode-numeric-character-reference": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz",
+ "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz",
+ "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz",
+ "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz",
+ "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz",
+ "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-encode": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz",
+ "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz",
+ "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-types": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz",
+ "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/mmd-parser": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mmd-parser/-/mmd-parser-1.0.4.tgz",
+ "integrity": "sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg=="
+ },
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
+ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
+ "dev": true
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/opentype.js": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz",
+ "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==",
+ "dependencies": {
+ "string.prototype.codepointat": "^0.2.1",
+ "tiny-inflate": "^1.0.3"
+ },
+ "bin": {
+ "ot": "bin/ot"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/postcss": {
+ "version": "8.4.19",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
+ "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-nesting": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz",
+ "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==",
+ "dev": true,
+ "dependencies": {
+ "@csstools/selector-specificity": "^2.0.0",
+ "postcss-selector-parser": "^6.0.10"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >=16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/potpack": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
+ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/property-information": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz",
+ "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-composer": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz",
+ "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==",
+ "dependencies": {
+ "prop-types": "^15.6.0"
+ },
+ "peerDependencies": {
+ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ },
+ "node_modules/react-markdown": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.3.tgz",
+ "integrity": "sha512-We36SfqaKoVNpN1QqsZwWSv/OZt5J15LNgTLWynwAN5b265hrQrsjMtlRNwUvS+YyR3yDM8HpTNc4pK9H/Gc0A==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/prop-types": "^15.0.0",
+ "@types/unist": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^2.0.0",
+ "prop-types": "^15.0.0",
+ "property-information": "^6.0.0",
+ "react-is": "^18.0.0",
+ "remark-parse": "^10.0.0",
+ "remark-rehype": "^10.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-object": "^0.3.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0",
+ "vfile": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16",
+ "react": ">=16"
+ }
+ },
+ "node_modules/react-merge-refs": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",
+ "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/react-reconciler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz",
+ "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.21.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0"
+ }
+ },
+ "node_modules/react-reconciler/node_modules/scheduler": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
+ "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
+ "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-use-measure": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz",
+ "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==",
+ "dependencies": {
+ "debounce": "^1.2.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.13",
+ "react-dom": ">=16.13"
+ }
+ },
+ "node_modules/recoil": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.6.tgz",
+ "integrity": "sha512-hsBEw7jFdpBCY/tu2GweiyaqHKxVj6EqF2/SfrglbKvJHhpN57SANWvPW+gE90i3Awi+A5gssOd3u+vWlT+g7g==",
+ "dependencies": {
+ "hamt_plus": "1.0.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.13.1"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.10",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz",
+ "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw=="
+ },
+ "node_modules/regexp-to-ast": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz",
+ "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw=="
+ },
+ "node_modules/remark-gfm": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz",
+ "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-gfm": "^2.0.0",
+ "micromark-extension-gfm": "^2.0.0",
+ "unified": "^10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz",
+ "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-from-markdown": "^1.0.0",
+ "unified": "^10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz",
+ "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-hast": "^12.1.0",
+ "unified": "^10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "2.79.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
+ "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "dependencies": {
+ "mri": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/stats.js": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
+ "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw=="
+ },
+ "node_modules/string.prototype.codepointat": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
+ "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
+ },
+ "node_modules/style-to-object": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",
+ "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==",
+ "dependencies": {
+ "inline-style-parser": "0.1.1"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/suspend-react": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.0.8.tgz",
+ "integrity": "sha512-ZC3r8Hu1y0dIThzsGw0RLZplnX9yXwfItcvaIzJc2VQVi8TGyGDlu92syMB5ulybfvGLHAI5Ghzlk23UBPF8xg==",
+ "peerDependencies": {
+ "react": ">=17.0"
+ }
+ },
+ "node_modules/three": {
+ "version": "0.146.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.146.0.tgz",
+ "integrity": "sha512-1lvNfLezN6OJ9NaFAhfX4sm5e9YCzHtaRgZ1+B4C+Hv6TibRMsuBAM5/wVKzxjpYIlMymvgsHEFrrigEfXnb2A=="
+ },
+ "node_modules/three-mesh-bvh": {
+ "version": "0.5.18",
+ "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.5.18.tgz",
+ "integrity": "sha512-lJQkt4A+pfHMf8Pbyqm5UiIBoVtp3cuy5rrTpuhIaJlbAobJW3/uQxJVZKiHaGi1Bs+5Svb+T8xIS17EqjG2ZA==",
+ "peerDependencies": {
+ "three": ">= 0.123.0"
+ }
+ },
+ "node_modules/three-stdlib": {
+ "version": "2.17.4",
+ "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.17.4.tgz",
+ "integrity": "sha512-Xh9+B/1RL0YXRjWgLilbK/5qL7hVOWMF5sQ1seKqGvR4svNvQPU+vCZkL2sUaZ0usc/C3FpyGTuwbqo+eW444Q==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.7",
+ "@types/offscreencanvas": "^2019.6.4",
+ "@webgpu/glslang": "^0.0.15",
+ "chevrotain": "^10.1.2",
+ "draco3d": "^1.4.1",
+ "fflate": "^0.6.9",
+ "ktx-parse": "^0.4.5",
+ "mmd-parser": "^1.0.4",
+ "opentype.js": "^1.3.3",
+ "potpack": "^1.0.1",
+ "zstddec": "^0.0.2"
+ },
+ "peerDependencies": {
+ "three": ">=0.122.0"
+ }
+ },
+ "node_modules/tiny-inflate": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/troika-three-text": {
+ "version": "0.46.4",
+ "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.46.4.tgz",
+ "integrity": "sha512-Qsv0HhUKTZgSmAJs5wvO7YlBoJSP9TGPLmrg+K9pbQq4lseQdcevbno/WI38bwJBZ/qS56hvfqEzY0zUEFzDIw==",
+ "dependencies": {
+ "bidi-js": "^1.0.2",
+ "troika-three-utils": "^0.46.0",
+ "troika-worker-utils": "^0.46.0",
+ "webgl-sdf-generator": "1.1.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.103.0"
+ }
+ },
+ "node_modules/troika-three-utils": {
+ "version": "0.46.0",
+ "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.46.0.tgz",
+ "integrity": "sha512-llHyrXAcwzr0bpg80GxsIp73N7FuImm4WCrKDJkAqcAsWmE5pfP9+Qzw+oMWK1P/AdHQ79eOrOl9NjyW4aOw0w==",
+ "peerDependencies": {
+ "three": ">=0.103.0"
+ }
+ },
+ "node_modules/troika-worker-utils": {
+ "version": "0.46.0",
+ "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.46.0.tgz",
+ "integrity": "sha512-bzOx5f2ZBxkFhXtIvDJlLn2AI3bzCkGVbCndl/2dL5QZrwHEKl45OEIilCxYQQWJG1rEbOD9O80tMjoYjw19OA=="
+ },
+ "node_modules/trough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz",
+ "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "4.8.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
+ "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/unified": {
+ "version": "10.1.2",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz",
+ "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "bail": "^2.0.0",
+ "extend": "^3.0.0",
+ "is-buffer": "^2.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-builder": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz",
+ "integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==",
+ "dependencies": {
+ "@types/unist": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-generated": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz",
+ "integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz",
+ "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz",
+ "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==",
+ "dependencies": {
+ "@types/unist": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz",
+ "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==",
+ "dependencies": {
+ "@types/unist": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz",
+ "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.1.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz",
+ "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "browserslist-lint": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/utility-types": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
+ "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/uvu": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
+ "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
+ "dependencies": {
+ "dequal": "^2.0.0",
+ "diff": "^5.0.0",
+ "kleur": "^4.0.3",
+ "sade": "^1.7.3"
+ },
+ "bin": {
+ "uvu": "bin.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/vfile": {
+ "version": "5.3.5",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.5.tgz",
+ "integrity": "sha512-U1ho2ga33eZ8y8pkbQLH54uKqGhFJ6GYIHnnG5AhRpAh3OWjkrRHKa/KogbmQn8We+c0KVV3rTOgR9V/WowbXQ==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "is-buffer": "^2.0.0",
+ "unist-util-stringify-position": "^3.0.0",
+ "vfile-message": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz",
+ "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-stringify-position": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vite": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.3.tgz",
+ "integrity": "sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.15.9",
+ "postcss": "^8.4.18",
+ "resolve": "^1.22.1",
+ "rollup": "^2.79.1"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webgl-constants": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
+ "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
+ },
+ "node_modules/webgl-sdf-generator": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
+ "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="
+ },
+ "node_modules/zstddec": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz",
+ "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA=="
+ },
+ "node_modules/zustand": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz",
+ "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==",
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.3.tgz",
+ "integrity": "sha512-dn/sDAIuRCsXGnBD4P+SA6nv7Y54HQZjC4SPL8PToU3714zu7wSEc1129D/i0+vvjRfOlFo4Zqrpwj+Zhcykhw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ },
+ "dependencies": {
+ "@ampproject/remapping": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
+ "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.1.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@babel/code-frame": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
+ "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.18.6"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz",
+ "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==",
+ "dev": true
+ },
+ "@babel/core": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz",
+ "integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==",
+ "dev": true,
+ "requires": {
+ "@ampproject/remapping": "^2.1.0",
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.2",
+ "@babel/helper-compilation-targets": "^7.20.0",
+ "@babel/helper-module-transforms": "^7.20.2",
+ "@babel/helpers": "^7.20.1",
+ "@babel/parser": "^7.20.2",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.1",
+ "@babel/types": "^7.20.2",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.1",
+ "semver": "^6.3.0"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.20.4",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz",
+ "integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.20.2",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "jsesc": "^2.5.1"
+ },
+ "dependencies": {
+ "@jridgewell/gen-mapping": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+ "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ }
+ }
+ },
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
+ "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-compilation-targets": {
+ "version": "7.20.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz",
+ "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.20.0",
+ "@babel/helper-validator-option": "^7.18.6",
+ "browserslist": "^4.21.3",
+ "semver": "^6.3.0"
+ }
+ },
+ "@babel/helper-environment-visitor": {
+ "version": "7.18.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
+ "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
+ "dev": true
+ },
+ "@babel/helper-function-name": {
+ "version": "7.19.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
+ "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.18.10",
+ "@babel/types": "^7.19.0"
+ }
+ },
+ "@babel/helper-hoist-variables": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
+ "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
+ "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz",
+ "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/helper-simple-access": "^7.20.2",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.1",
+ "@babel/types": "^7.20.2"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz",
+ "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==",
+ "dev": true
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
+ "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.20.2"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
+ "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-string-parser": {
+ "version": "7.19.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
+ "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
+ "dev": true
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
+ "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+ "dev": true
+ },
+ "@babel/helper-validator-option": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
+ "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==",
+ "dev": true
+ },
+ "@babel/helpers": {
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz",
+ "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.1",
+ "@babel/types": "^7.20.0"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+ "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.20.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
+ "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==",
+ "dev": true
+ },
+ "@babel/plugin-syntax-jsx": {
+ "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.18.6"
+ }
+ },
+ "@babel/plugin-transform-react-jsx": {
+ "version": "7.19.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz",
+ "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.18.6",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.19.0",
+ "@babel/plugin-syntax-jsx": "^7.18.6",
+ "@babel/types": "^7.19.0"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-development": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz",
+ "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==",
+ "dev": true,
+ "requires": {
+ "@babel/plugin-transform-react-jsx": "^7.18.6"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-self": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz",
+ "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.18.6"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-source": {
+ "version": "7.19.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz",
+ "integrity": "sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.19.0"
+ }
+ },
+ "@babel/runtime": {
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz",
+ "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.10"
+ }
+ },
+ "@babel/template": {
+ "version": "7.18.10",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
+ "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/parser": "^7.18.10",
+ "@babel/types": "^7.18.10"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.20.1",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz",
+ "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.1",
+ "@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",
+ "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==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-string-parser": "^7.19.4",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "@chevrotain/cst-dts-gen": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.4.1.tgz",
+ "integrity": "sha512-wNDw9Rh6dPJKH275er8nijuDIpTcG2GjQANjnG8RaeGkZ3JN99+u6HRtnjKhjoi4NY9rg+udHChHQSskZtlkPw==",
+ "requires": {
+ "@chevrotain/gast": "10.4.1",
+ "@chevrotain/types": "10.4.1",
+ "lodash": "4.17.21"
+ }
+ },
+ "@chevrotain/gast": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.4.1.tgz",
+ "integrity": "sha512-HRv66QVbmC7eb/ppwsPCfNH4oZ/VV+thuMZILm7A7W6Q5M0tqiZv0ecdiB8hydmPO8je0aSrXEOCcaA6fuXc3Q==",
+ "requires": {
+ "@chevrotain/types": "10.4.1",
+ "lodash": "4.17.21"
+ }
+ },
+ "@chevrotain/types": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.4.1.tgz",
+ "integrity": "sha512-J8iyZNn/RGYWSyNJdGd3QI01gKFUx4mCSM0+vEqmIw9TXFlxj1IsHteXFahtezSHjgMtBTqWn6hb2YxCLjpHVg=="
+ },
+ "@chevrotain/utils": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.4.1.tgz",
+ "integrity": "sha512-vPIgzES8QhHMchb5UaQ4V/c9xmoaECN+4EXpuhWE+pu3LXJUUtAwDn/SEKFgtyiRo269Hxv3b0NbPlQfH0jeVA=="
+ },
+ "@csstools/selector-specificity": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz",
+ "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==",
+ "dev": true,
+ "requires": {}
+ },
+ "@esbuild/android-arm": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
+ "integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-loong64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
+ "integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
+ "dev": true,
+ "optional": true
+ },
+ "@jridgewell/gen-mapping": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
+ "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/set-array": "^1.0.0",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "dev": true
+ },
+ "@jridgewell/set-array": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "dev": true
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+ "dev": true
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
+ "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "@react-spring/animated": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.5.5.tgz",
+ "integrity": "sha512-glzViz7syQ3CE6BQOwAyr75cgh0qsihm5lkaf24I0DfU63cMm/3+br299UEYkuaHNmfDfM414uktiPlZCNJbQA==",
+ "requires": {
+ "@react-spring/shared": "~9.5.5",
+ "@react-spring/types": "~9.5.5"
+ }
+ },
+ "@react-spring/core": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.5.5.tgz",
+ "integrity": "sha512-shaJYb3iX18Au6gkk8ahaF0qx0LpS0Yd+ajb4asBaAQf6WPGuEdJsbsNSgei1/O13JyEATsJl20lkjeslJPMYA==",
+ "requires": {
+ "@react-spring/animated": "~9.5.5",
+ "@react-spring/rafz": "~9.5.5",
+ "@react-spring/shared": "~9.5.5",
+ "@react-spring/types": "~9.5.5"
+ }
+ },
+ "@react-spring/rafz": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.5.5.tgz",
+ "integrity": "sha512-F/CLwB0d10jL6My5vgzRQxCNY2RNyDJZedRBK7FsngdCmzoq3V4OqqNc/9voJb9qRC2wd55oGXUeXv2eIaFmsw=="
+ },
+ "@react-spring/shared": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.5.5.tgz",
+ "integrity": "sha512-YwW70Pa/YXPOwTutExHZmMQSHcNC90kJOnNR4G4mCDNV99hE98jWkIPDOsgqbYx3amIglcFPiYKMaQuGdr8dyQ==",
+ "requires": {
+ "@react-spring/rafz": "~9.5.5",
+ "@react-spring/types": "~9.5.5"
+ }
+ },
+ "@react-spring/three": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.5.5.tgz",
+ "integrity": "sha512-9kTIaSceqFIl5EIrdwM7Z53o5I+9BGNVzbp4oZZYMao+GMAWOosnlQdDG5GeqNsIqfW9fZCEquGqagfKAxftcA==",
+ "requires": {
+ "@react-spring/animated": "~9.5.5",
+ "@react-spring/core": "~9.5.5",
+ "@react-spring/shared": "~9.5.5",
+ "@react-spring/types": "~9.5.5"
+ }
+ },
+ "@react-spring/types": {
+ "version": "9.5.5",
+ "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.5.5.tgz",
+ "integrity": "sha512-7I/qY8H7Enwasxr4jU6WmtNK+RZ4Z/XvSlDvjXFVe7ii1x0MoSlkw6pD7xuac8qrHQRm9BTcbZNyeeKApYsvCg=="
+ },
+ "@react-three/drei": {
+ "version": "9.40.0",
+ "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.40.0.tgz",
+ "integrity": "sha512-v+7l62c7BIXS172E1TiEOeXXpZnfoJNsdjZK5h0TxKVXsbhs5t6rMAlDKhgWbZ96rDzgNrSIPv+GMzlG/rkFdQ==",
+ "requires": {
+ "@babel/runtime": "^7.11.2",
+ "@react-spring/three": "^9.3.1",
+ "@use-gesture/react": "^10.2.0",
+ "detect-gpu": "^4.0.36",
+ "glsl-noise": "^0.0.0",
+ "lodash.clamp": "^4.0.3",
+ "lodash.omit": "^4.5.0",
+ "lodash.pick": "^4.4.0",
+ "meshline": "^2.0.4",
+ "react-composer": "^5.0.3",
+ "react-merge-refs": "^1.1.0",
+ "stats.js": "^0.17.0",
+ "suspend-react": "^0.0.8",
+ "three-mesh-bvh": "^0.5.15",
+ "three-stdlib": "^2.17.3",
+ "troika-three-text": "^0.46.4",
+ "utility-types": "^3.10.0",
+ "zustand": "^3.5.13"
+ }
+ },
+ "@react-three/fiber": {
+ "version": "8.9.1",
+ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.9.1.tgz",
+ "integrity": "sha512-xRMO9RGp0DkxSFu5BmmkjCxJ4r0dEpLobtxXdZwI0h2rZZaCnkPM5zThRN8xaZNbZhzRSVICeNOFaZltr9xFyQ==",
+ "requires": {
+ "@babel/runtime": "^7.17.8",
+ "@types/react-reconciler": "^0.26.7",
+ "its-fine": "^1.0.6",
+ "react-reconciler": "^0.27.0",
+ "react-use-measure": "^2.1.1",
+ "scheduler": "^0.21.0",
+ "suspend-react": "^0.0.8",
+ "zustand": "^3.7.1"
+ },
+ "dependencies": {
+ "scheduler": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
+ "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==",
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ }
+ }
+ },
+ "@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=="
+ },
+ "@tauri-apps/cli": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.2.0.tgz",
+ "integrity": "sha512-DgUnk4p/atWHq2HUx9Vt+/LuRsx4iFlkzdZIUxtFWvpcZih2k0TzmHJbrhM1evh1/7a+SqiwDawmyf3Hz1HxXA==",
+ "dev": true,
+ "requires": {
+ "@tauri-apps/cli-darwin-arm64": "1.2.0",
+ "@tauri-apps/cli-darwin-x64": "1.2.0",
+ "@tauri-apps/cli-linux-arm-gnueabihf": "1.2.0",
+ "@tauri-apps/cli-linux-arm64-gnu": "1.2.0",
+ "@tauri-apps/cli-linux-arm64-musl": "1.2.0",
+ "@tauri-apps/cli-linux-x64-gnu": "1.2.0",
+ "@tauri-apps/cli-linux-x64-musl": "1.2.0",
+ "@tauri-apps/cli-win32-ia32-msvc": "1.2.0",
+ "@tauri-apps/cli-win32-x64-msvc": "1.2.0"
+ }
+ },
+ "@tauri-apps/cli-darwin-arm64": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.2.0.tgz",
+ "integrity": "sha512-f3LR2RvTU2ulxYdK9Nc3vKaSpDChu52pz0BMWNrSs3dxs4WTVioie98Ufz+GorifkUp3sYXcJte3HzX6wH/QxQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@tauri-apps/cli-darwin-x64": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.2.0.tgz",
+ "integrity": "sha512-m07QZaAZCtyobrjddfz/Rxf9GGutnBOpRMbNqVqCk0qKRJzHG1fIsLqkgZh6+qPv0zHpu7xi/FPcqTec72Cp8w==",
+ "dev": true,
+ "optional": true
+ },
+ "@tauri-apps/cli-linux-arm-gnueabihf": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.2.0.tgz",
+ "integrity": "sha512-Id9eF1JtthZRFVtXAAVtSlI3uMT8cJ7LYmCSIl3mAXEUeaPBxnUs1i9X6/J+2Ho3yLEuuOxJ7PaJd+4v8wnEeg==",
+ "dev": true,
+ "optional": true
+ },
+ "@tauri-apps/cli-linux-arm64-gnu": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.2.0.tgz",
+ "integrity": "sha512-NtfPkkpeMPl+i/tB/Fc8ST2rKO2vV8int/RkOvNGLCkhWcl4sbzKBol7tc4q8c8h0X7FXDcF1l/EOuGsZUAA5Q==",
+ "dev": true,
+ "optional": true
+ },
+ "@tauri-apps/cli-linux-arm64-musl": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.2.0.tgz",
+ "integrity": "sha512-tz+mOOVsy/TMdq2WJVIJl/iwW3OCWCyD5Fls3fhyJ4XpLfjn4G+C+oU0awXD/0se0ko81aq4D+r8eDx6oBRi0A==",
+ "dev": true,
+ "optional": true
+ },
+ "@tauri-apps/cli-linux-x64-gnu": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.2.0.tgz",
+ "integrity": "sha512-FH/wU+OWZjRQvrq/oequScr72I84XgOuRuMEpt/GqGD341cBJ8ithpoyzuiKsvjS6K0qMyRFzy3eyhQ7gwX+4Q==",
+ "dev": true,
+ "optional": true
+ },
+ "@tauri-apps/cli-linux-x64-musl": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.2.0.tgz",
+ "integrity": "sha512-nLg30aBT9fI83sjIqaGPN7twbtE5LJy2DbKzxIlw59F+GT8HBdiM/2mZdTLB3AQb52yVHuGB1TVtWDsl0JHqCA==",
+ "dev": true,
+ "optional": true
+ },
+ "@tauri-apps/cli-win32-ia32-msvc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.2.0.tgz",
+ "integrity": "sha512-eXtgIgY0fawgcOuUjH8Y6PxwPxbK87Zl9XmA7Q0m58T7pIz+gcbgvtH8Bb+liYHoRYItIhQxVm+ui7Y59rI7Cg==",
+ "dev": true,
+ "optional": true
+ },
+ "@tauri-apps/cli-win32-x64-msvc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.2.0.tgz",
+ "integrity": "sha512-egyM66R05AIbkaUDptpHurFTIYp3VM4H5OrRd3O2b0oXf8SoiXiyrHbQsHVHHDYyytKmwkdNqjdy+Vev/Vq25Q==",
+ "dev": true,
+ "optional": true
+ },
+ "@types/debug": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
+ "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==",
+ "requires": {
+ "@types/ms": "*"
+ }
+ },
+ "@types/hast": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
+ "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==",
+ "requires": {
+ "@types/unist": "*"
+ }
+ },
+ "@types/mdast": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
+ "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==",
+ "requires": {
+ "@types/unist": "*"
+ }
+ },
+ "@types/ms": {
+ "version": "0.7.31",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
+ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
+ },
+ "@types/node": {
+ "version": "18.11.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
+ "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
+ "dev": true
+ },
+ "@types/offscreencanvas": {
+ "version": "2019.7.0",
+ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz",
+ "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg=="
+ },
+ "@types/prop-types": {
+ "version": "15.7.5",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
+ },
+ "@types/react": {
+ "version": "18.0.25",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.25.tgz",
+ "integrity": "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g==",
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@types/react-dom": {
+ "version": "18.0.8",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz",
+ "integrity": "sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-reconciler": {
+ "version": "0.26.7",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz",
+ "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/scheduler": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
+ },
+ "@types/three": {
+ "version": "0.146.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.146.0.tgz",
+ "integrity": "sha512-75AgysUrIvTCB054eQa2pDVFurfeFW8CrMQjpzjt3yHBfuuknoSvvsESd/3EhQxPrz9si3+P0wiDUVsWUlljfA==",
+ "requires": {
+ "@types/webxr": "*"
+ }
+ },
+ "@types/unist": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
+ "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
+ },
+ "@types/webxr": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
+ "integrity": "sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA=="
+ },
+ "@use-gesture/core": {
+ "version": "10.2.22",
+ "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.2.22.tgz",
+ "integrity": "sha512-Ek0JZFYfk+hicLmoG094gm3YOuDMBNckHb988e59YOZoAkETT8dQSzT+g3QkSHSiP1m5wFXAGPSgxvOuwvGKHQ=="
+ },
+ "@use-gesture/react": {
+ "version": "10.2.22",
+ "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.2.22.tgz",
+ "integrity": "sha512-ECo7ig16SxBE06ENIURO1woKEB6TC8qY3a0rugJjQ2f1o0Tj28xS/eYNyJuqzQB5YT0q5IrF7ZFpbx1p/5ohYA==",
+ "requires": {
+ "@use-gesture/core": "10.2.22"
+ }
+ },
+ "@vitejs/plugin-react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz",
+ "integrity": "sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.19.6",
+ "@babel/plugin-transform-react-jsx": "^7.19.0",
+ "@babel/plugin-transform-react-jsx-development": "^7.18.6",
+ "@babel/plugin-transform-react-jsx-self": "^7.18.6",
+ "@babel/plugin-transform-react-jsx-source": "^7.19.6",
+ "magic-string": "^0.26.7",
+ "react-refresh": "^0.14.0"
+ }
+ },
+ "@webgpu/glslang": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/@webgpu/glslang/-/glslang-0.0.15.tgz",
+ "integrity": "sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q=="
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="
+ },
+ "bidi-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.2.tgz",
+ "integrity": "sha512-rzSy/k7WdX5zOyeHHCOixGXbCHkyogkxPKL2r8QtzHmVQDiWCXUWa18bLdMWT9CYMLOYTjWpTHawuev2ouYJVw==",
+ "requires": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "browserslist": {
+ "version": "4.21.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
+ "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001400",
+ "electron-to-chromium": "^1.4.251",
+ "node-releases": "^2.0.6",
+ "update-browserslist-db": "^1.0.9"
+ }
+ },
+ "caniuse-lite": {
+ "version": "1.0.30001431",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz",
+ "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==",
+ "dev": true
+ },
+ "ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="
+ },
+ "chevrotain": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.4.1.tgz",
+ "integrity": "sha512-1y4vnssauVmrrP5MBaJ6DZvsv3BpXLlKVNK5S52fTGQHqg09qxMDBAz0wZbb04Ovc1pBCA4obcCjOlRioIV+cA==",
+ "requires": {
+ "@chevrotain/cst-dts-gen": "10.4.1",
+ "@chevrotain/gast": "10.4.1",
+ "@chevrotain/types": "10.4.1",
+ "@chevrotain/utils": "10.4.1",
+ "lodash": "4.17.21",
+ "regexp-to-ast": "0.5.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="
+ },
+ "convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "dev": true
+ },
+ "cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true
+ },
+ "csstype": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
+ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
+ },
+ "debounce": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
+ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "decode-named-character-reference": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
+ "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
+ "requires": {
+ "character-entities": "^2.0.0"
+ }
+ },
+ "dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="
+ },
+ "detect-gpu": {
+ "version": "4.0.48",
+ "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-4.0.48.tgz",
+ "integrity": "sha512-qrJTrCeYG/5aaMaqHlbTZu3cTZB/TZJ/ZbDDEb712FMe5irqjGwp2fyG4ag+y19f4QGKg55dQXf8IuKo+oCmRw==",
+ "requires": {
+ "webgl-constants": "^1.1.1"
+ }
+ },
+ "diff": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
+ "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw=="
+ },
+ "draco3d": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.5.tgz",
+ "integrity": "sha512-JVuNV0EJzD3LBYhGyIXJLeBID/EVtmFO1ZNhAYflTgiMiAJlbhXQmRRda/azjc8MRVMHh0gqGhiqHUo5dIXM8Q=="
+ },
+ "electron-to-chromium": {
+ "version": "1.4.284",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
+ "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
+ "dev": true
+ },
+ "esbuild": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
+ "integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
+ "dev": true,
+ "requires": {
+ "@esbuild/android-arm": "0.15.13",
+ "@esbuild/linux-loong64": "0.15.13",
+ "esbuild-android-64": "0.15.13",
+ "esbuild-android-arm64": "0.15.13",
+ "esbuild-darwin-64": "0.15.13",
+ "esbuild-darwin-arm64": "0.15.13",
+ "esbuild-freebsd-64": "0.15.13",
+ "esbuild-freebsd-arm64": "0.15.13",
+ "esbuild-linux-32": "0.15.13",
+ "esbuild-linux-64": "0.15.13",
+ "esbuild-linux-arm": "0.15.13",
+ "esbuild-linux-arm64": "0.15.13",
+ "esbuild-linux-mips64le": "0.15.13",
+ "esbuild-linux-ppc64le": "0.15.13",
+ "esbuild-linux-riscv64": "0.15.13",
+ "esbuild-linux-s390x": "0.15.13",
+ "esbuild-netbsd-64": "0.15.13",
+ "esbuild-openbsd-64": "0.15.13",
+ "esbuild-sunos-64": "0.15.13",
+ "esbuild-windows-32": "0.15.13",
+ "esbuild-windows-64": "0.15.13",
+ "esbuild-windows-arm64": "0.15.13"
+ }
+ },
+ "esbuild-android-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
+ "integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-android-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
+ "integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-darwin-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
+ "integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-darwin-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
+ "integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-freebsd-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
+ "integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-freebsd-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
+ "integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-32": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
+ "integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
+ "integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-arm": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
+ "integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
+ "integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-mips64le": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
+ "integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-ppc64le": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
+ "integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-riscv64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
+ "integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-s390x": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
+ "integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-netbsd-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
+ "integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-openbsd-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
+ "integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-sunos-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
+ "integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-32": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
+ "integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
+ "integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-arm64": {
+ "version": "0.15.13",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
+ "integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
+ "dev": true,
+ "optional": true
+ },
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "fflate": {
+ "version": "0.6.10",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
+ "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true
+ },
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ },
+ "glsl-noise": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
+ "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w=="
+ },
+ "hamt_plus": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
+ "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA=="
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true
+ },
+ "hast-util-whitespace": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz",
+ "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg=="
+ },
+ "inline-style-parser": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
+ "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
+ },
+ "is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
+ },
+ "is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="
+ },
+ "its-fine": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.0.6.tgz",
+ "integrity": "sha512-VZJZPwVT2kxe5KQv+TxCjojfLiUIut8zXDNLTxcM7gJ/xQ/bSPk5M0neZ+j3myy45KKkltY1mm1jyJgx3Fxsdg==",
+ "requires": {
+ "@types/react-reconciler": "^0.28.0"
+ },
+ "dependencies": {
+ "@types/react-reconciler": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.0.tgz",
+ "integrity": "sha512-5cjk9ottZAj7eaTsqzPUIlrVbh3hBAO2YaEL1rkjHKB3xNAId7oU8GhzvAX+gfmlfoxTwJnBjPxEHyxkEA1Ffg==",
+ "requires": {
+ "@types/react": "*"
+ }
+ }
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true
+ },
+ "json5": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
+ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+ "dev": true
+ },
+ "kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
+ },
+ "ktx-parse": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.4.5.tgz",
+ "integrity": "sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg=="
+ },
+ "lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "lodash.clamp": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz",
+ "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg=="
+ },
+ "lodash.omit": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
+ "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg=="
+ },
+ "lodash.pick": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
+ "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q=="
+ },
+ "longest-streak": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz",
+ "integrity": "sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg=="
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "magic-string": {
+ "version": "0.26.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz",
+ "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==",
+ "dev": true,
+ "requires": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "markdown-table": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.2.tgz",
+ "integrity": "sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA=="
+ },
+ "mdast-util-definitions": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz",
+ "integrity": "sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "unist-util-visit": "^4.0.0"
+ }
+ },
+ "mdast-util-find-and-replace": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz",
+ "integrity": "sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==",
+ "requires": {
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
+ }
+ }
+ },
+ "mdast-util-from-markdown": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz",
+ "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "mdast-util-to-string": "^3.1.0",
+ "micromark": "^3.0.0",
+ "micromark-util-decode-numeric-character-reference": "^1.0.0",
+ "micromark-util-decode-string": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "unist-util-stringify-position": "^3.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "mdast-util-gfm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz",
+ "integrity": "sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==",
+ "requires": {
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-gfm-autolink-literal": "^1.0.0",
+ "mdast-util-gfm-footnote": "^1.0.0",
+ "mdast-util-gfm-strikethrough": "^1.0.0",
+ "mdast-util-gfm-table": "^1.0.0",
+ "mdast-util-gfm-task-list-item": "^1.0.0",
+ "mdast-util-to-markdown": "^1.0.0"
+ }
+ },
+ "mdast-util-gfm-autolink-literal": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz",
+ "integrity": "sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "ccount": "^2.0.0",
+ "mdast-util-find-and-replace": "^2.0.0",
+ "micromark-util-character": "^1.0.0"
+ }
+ },
+ "mdast-util-gfm-footnote": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz",
+ "integrity": "sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0",
+ "micromark-util-normalize-identifier": "^1.0.0"
+ }
+ },
+ "mdast-util-gfm-strikethrough": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.2.tgz",
+ "integrity": "sha512-T/4DVHXcujH6jx1yqpcAYYwd+z5lAYMw4Ls6yhTfbMMtCt0PHY4gEfhW9+lKsLBtyhUGKRIzcUA2FATVqnvPDA==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ }
+ },
+ "mdast-util-gfm-table": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.6.tgz",
+ "integrity": "sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ }
+ },
+ "mdast-util-gfm-task-list-item": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz",
+ "integrity": "sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ }
+ },
+ "mdast-util-to-hast": {
+ "version": "12.2.4",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.4.tgz",
+ "integrity": "sha512-a21xoxSef1l8VhHxS1Dnyioz6grrJkoaCUgGzMD/7dWHvboYX3VW53esRUfB5tgTyz4Yos1n25SPcj35dJqmAg==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/mdast": "^3.0.0",
+ "mdast-util-definitions": "^5.0.0",
+ "micromark-util-sanitize-uri": "^1.1.0",
+ "trim-lines": "^3.0.0",
+ "unist-builder": "^3.0.0",
+ "unist-util-generated": "^2.0.0",
+ "unist-util-position": "^4.0.0",
+ "unist-util-visit": "^4.0.0"
+ }
+ },
+ "mdast-util-to-markdown": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.3.0.tgz",
+ "integrity": "sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-to-string": "^3.0.0",
+ "micromark-util-decode-string": "^1.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ }
+ },
+ "mdast-util-to-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz",
+ "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA=="
+ },
+ "meshline": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/meshline/-/meshline-2.0.4.tgz",
+ "integrity": "sha512-Jh6DJl/zLqA4xsKvGv5950jr2ukyXQE1wgxs8u94cImHrvL6soVIggqjP+2hVHZXGYaKnWszhtjuCbKNeQyYiw==",
+ "requires": {}
+ },
+ "micromark": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz",
+ "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==",
+ "requires": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-core-commonmark": "^1.0.1",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-combine-extensions": "^1.0.0",
+ "micromark-util-decode-numeric-character-reference": "^1.0.0",
+ "micromark-util-encode": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-subtokenize": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.1",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-core-commonmark": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz",
+ "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==",
+ "requires": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-factory-destination": "^1.0.0",
+ "micromark-factory-label": "^1.0.0",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-factory-title": "^1.0.0",
+ "micromark-factory-whitespace": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-classify-character": "^1.0.0",
+ "micromark-util-html-tag-name": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-subtokenize": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.1",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz",
+ "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==",
+ "requires": {
+ "micromark-extension-gfm-autolink-literal": "^1.0.0",
+ "micromark-extension-gfm-footnote": "^1.0.0",
+ "micromark-extension-gfm-strikethrough": "^1.0.0",
+ "micromark-extension-gfm-table": "^1.0.0",
+ "micromark-extension-gfm-tagfilter": "^1.0.0",
+ "micromark-extension-gfm-task-list-item": "^1.0.0",
+ "micromark-util-combine-extensions": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-extension-gfm-autolink-literal": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz",
+ "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==",
+ "requires": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-footnote": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz",
+ "integrity": "sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==",
+ "requires": {
+ "micromark-core-commonmark": "^1.0.0",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-strikethrough": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz",
+ "integrity": "sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==",
+ "requires": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-classify-character": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-table": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz",
+ "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==",
+ "requires": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-tagfilter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz",
+ "integrity": "sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==",
+ "requires": {
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-extension-gfm-task-list-item": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz",
+ "integrity": "sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==",
+ "requires": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-factory-destination": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
+ "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==",
+ "requires": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-factory-label": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz",
+ "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==",
+ "requires": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-factory-space": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz",
+ "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==",
+ "requires": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-factory-title": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz",
+ "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==",
+ "requires": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-factory-whitespace": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz",
+ "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==",
+ "requires": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-util-character": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz",
+ "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==",
+ "requires": {
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-util-chunked": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz",
+ "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==",
+ "requires": {
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "micromark-util-classify-character": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz",
+ "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==",
+ "requires": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-util-combine-extensions": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz",
+ "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==",
+ "requires": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-util-decode-numeric-character-reference": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz",
+ "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==",
+ "requires": {
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "micromark-util-decode-string": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz",
+ "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==",
+ "requires": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-decode-numeric-character-reference": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "micromark-util-encode": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz",
+ "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA=="
+ },
+ "micromark-util-html-tag-name": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz",
+ "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA=="
+ },
+ "micromark-util-normalize-identifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz",
+ "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==",
+ "requires": {
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "micromark-util-resolve-all": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz",
+ "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==",
+ "requires": {
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-util-sanitize-uri": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz",
+ "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==",
+ "requires": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-encode": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0"
+ }
+ },
+ "micromark-util-subtokenize": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz",
+ "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==",
+ "requires": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-util-symbol": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz",
+ "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ=="
+ },
+ "micromark-util-types": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz",
+ "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w=="
+ },
+ "mmd-parser": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mmd-parser/-/mmd-parser-1.0.4.tgz",
+ "integrity": "sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg=="
+ },
+ "mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "dev": true
+ },
+ "node-releases": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
+ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
+ },
+ "opentype.js": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz",
+ "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==",
+ "requires": {
+ "string.prototype.codepointat": "^0.2.1",
+ "tiny-inflate": "^1.0.3"
+ }
+ },
+ "path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "postcss": {
+ "version": "8.4.19",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
+ "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
+ "dev": true,
+ "requires": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "postcss-nesting": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz",
+ "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==",
+ "dev": true,
+ "requires": {
+ "@csstools/selector-specificity": "^2.0.0",
+ "postcss-selector-parser": "^6.0.10"
+ }
+ },
+ "postcss-selector-parser": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "dev": true,
+ "requires": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ }
+ },
+ "potpack": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
+ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="
+ },
+ "prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ },
+ "dependencies": {
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ }
+ }
+ },
+ "property-information": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz",
+ "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w=="
+ },
+ "react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "react-composer": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz",
+ "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==",
+ "requires": {
+ "prop-types": "^15.6.0"
+ }
+ },
+ "react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ }
+ },
+ "react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ },
+ "react-markdown": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.3.tgz",
+ "integrity": "sha512-We36SfqaKoVNpN1QqsZwWSv/OZt5J15LNgTLWynwAN5b265hrQrsjMtlRNwUvS+YyR3yDM8HpTNc4pK9H/Gc0A==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/prop-types": "^15.0.0",
+ "@types/unist": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^2.0.0",
+ "prop-types": "^15.0.0",
+ "property-information": "^6.0.0",
+ "react-is": "^18.0.0",
+ "remark-parse": "^10.0.0",
+ "remark-rehype": "^10.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-object": "^0.3.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0",
+ "vfile": "^5.0.0"
+ }
+ },
+ "react-merge-refs": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",
+ "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ=="
+ },
+ "react-reconciler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz",
+ "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.21.0"
+ },
+ "dependencies": {
+ "scheduler": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz",
+ "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==",
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ }
+ }
+ },
+ "react-refresh": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
+ "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
+ "dev": true
+ },
+ "react-use-measure": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz",
+ "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==",
+ "requires": {
+ "debounce": "^1.2.1"
+ }
+ },
+ "recoil": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.6.tgz",
+ "integrity": "sha512-hsBEw7jFdpBCY/tu2GweiyaqHKxVj6EqF2/SfrglbKvJHhpN57SANWvPW+gE90i3Awi+A5gssOd3u+vWlT+g7g==",
+ "requires": {
+ "hamt_plus": "1.0.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.10",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz",
+ "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw=="
+ },
+ "regexp-to-ast": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz",
+ "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw=="
+ },
+ "remark-gfm": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz",
+ "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-gfm": "^2.0.0",
+ "micromark-extension-gfm": "^2.0.0",
+ "unified": "^10.0.0"
+ }
+ },
+ "remark-parse": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz",
+ "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-from-markdown": "^1.0.0",
+ "unified": "^10.0.0"
+ }
+ },
+ "remark-rehype": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz",
+ "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-hast": "^12.1.0",
+ "unified": "^10.0.0"
+ }
+ },
+ "require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
+ },
+ "resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "rollup": {
+ "version": "2.79.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
+ "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
+ "dev": true,
+ "requires": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "requires": {
+ "mri": "^1.1.0"
+ }
+ },
+ "scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true
+ },
+ "sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="
+ },
+ "stats.js": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
+ "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw=="
+ },
+ "string.prototype.codepointat": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
+ "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
+ },
+ "style-to-object": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",
+ "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==",
+ "requires": {
+ "inline-style-parser": "0.1.1"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true
+ },
+ "suspend-react": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.0.8.tgz",
+ "integrity": "sha512-ZC3r8Hu1y0dIThzsGw0RLZplnX9yXwfItcvaIzJc2VQVi8TGyGDlu92syMB5ulybfvGLHAI5Ghzlk23UBPF8xg==",
+ "requires": {}
+ },
+ "three": {
+ "version": "0.146.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.146.0.tgz",
+ "integrity": "sha512-1lvNfLezN6OJ9NaFAhfX4sm5e9YCzHtaRgZ1+B4C+Hv6TibRMsuBAM5/wVKzxjpYIlMymvgsHEFrrigEfXnb2A=="
+ },
+ "three-mesh-bvh": {
+ "version": "0.5.18",
+ "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.5.18.tgz",
+ "integrity": "sha512-lJQkt4A+pfHMf8Pbyqm5UiIBoVtp3cuy5rrTpuhIaJlbAobJW3/uQxJVZKiHaGi1Bs+5Svb+T8xIS17EqjG2ZA==",
+ "requires": {}
+ },
+ "three-stdlib": {
+ "version": "2.17.4",
+ "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.17.4.tgz",
+ "integrity": "sha512-Xh9+B/1RL0YXRjWgLilbK/5qL7hVOWMF5sQ1seKqGvR4svNvQPU+vCZkL2sUaZ0usc/C3FpyGTuwbqo+eW444Q==",
+ "requires": {
+ "@babel/runtime": "^7.16.7",
+ "@types/offscreencanvas": "^2019.6.4",
+ "@webgpu/glslang": "^0.0.15",
+ "chevrotain": "^10.1.2",
+ "draco3d": "^1.4.1",
+ "fflate": "^0.6.9",
+ "ktx-parse": "^0.4.5",
+ "mmd-parser": "^1.0.4",
+ "opentype.js": "^1.3.3",
+ "potpack": "^1.0.1",
+ "zstddec": "^0.0.2"
+ }
+ },
+ "tiny-inflate": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "dev": true
+ },
+ "trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="
+ },
+ "troika-three-text": {
+ "version": "0.46.4",
+ "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.46.4.tgz",
+ "integrity": "sha512-Qsv0HhUKTZgSmAJs5wvO7YlBoJSP9TGPLmrg+K9pbQq4lseQdcevbno/WI38bwJBZ/qS56hvfqEzY0zUEFzDIw==",
+ "requires": {
+ "bidi-js": "^1.0.2",
+ "troika-three-utils": "^0.46.0",
+ "troika-worker-utils": "^0.46.0",
+ "webgl-sdf-generator": "1.1.1"
+ }
+ },
+ "troika-three-utils": {
+ "version": "0.46.0",
+ "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.46.0.tgz",
+ "integrity": "sha512-llHyrXAcwzr0bpg80GxsIp73N7FuImm4WCrKDJkAqcAsWmE5pfP9+Qzw+oMWK1P/AdHQ79eOrOl9NjyW4aOw0w==",
+ "requires": {}
+ },
+ "troika-worker-utils": {
+ "version": "0.46.0",
+ "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.46.0.tgz",
+ "integrity": "sha512-bzOx5f2ZBxkFhXtIvDJlLn2AI3bzCkGVbCndl/2dL5QZrwHEKl45OEIilCxYQQWJG1rEbOD9O80tMjoYjw19OA=="
+ },
+ "trough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz",
+ "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g=="
+ },
+ "typescript": {
+ "version": "4.8.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
+ "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
+ "dev": true
+ },
+ "unified": {
+ "version": "10.1.2",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz",
+ "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "bail": "^2.0.0",
+ "extend": "^3.0.0",
+ "is-buffer": "^2.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^5.0.0"
+ }
+ },
+ "unist-builder": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz",
+ "integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==",
+ "requires": {
+ "@types/unist": "^2.0.0"
+ }
+ },
+ "unist-util-generated": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz",
+ "integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw=="
+ },
+ "unist-util-is": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz",
+ "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ=="
+ },
+ "unist-util-position": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz",
+ "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==",
+ "requires": {
+ "@types/unist": "^2.0.0"
+ }
+ },
+ "unist-util-stringify-position": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz",
+ "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==",
+ "requires": {
+ "@types/unist": "^2.0.0"
+ }
+ },
+ "unist-util-visit": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz",
+ "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.1.1"
+ }
+ },
+ "unist-util-visit-parents": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz",
+ "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^5.0.0"
+ }
+ },
+ "update-browserslist-db": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "dev": true,
+ "requires": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "utility-types": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
+ "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg=="
+ },
+ "uvu": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
+ "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
+ "requires": {
+ "dequal": "^2.0.0",
+ "diff": "^5.0.0",
+ "kleur": "^4.0.3",
+ "sade": "^1.7.3"
+ }
+ },
+ "vfile": {
+ "version": "5.3.5",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.5.tgz",
+ "integrity": "sha512-U1ho2ga33eZ8y8pkbQLH54uKqGhFJ6GYIHnnG5AhRpAh3OWjkrRHKa/KogbmQn8We+c0KVV3rTOgR9V/WowbXQ==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "is-buffer": "^2.0.0",
+ "unist-util-stringify-position": "^3.0.0",
+ "vfile-message": "^3.0.0"
+ }
+ },
+ "vfile-message": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz",
+ "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "unist-util-stringify-position": "^3.0.0"
+ }
+ },
+ "vite": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.3.tgz",
+ "integrity": "sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==",
+ "dev": true,
+ "requires": {
+ "esbuild": "^0.15.9",
+ "fsevents": "~2.3.2",
+ "postcss": "^8.4.18",
+ "resolve": "^1.22.1",
+ "rollup": "^2.79.1"
+ }
+ },
+ "webgl-constants": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
+ "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
+ },
+ "webgl-sdf-generator": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
+ "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="
+ },
+ "zstddec": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz",
+ "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA=="
+ },
+ "zustand": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz",
+ "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==",
+ "requires": {}
+ },
+ "zwitch": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.3.tgz",
+ "integrity": "sha512-dn/sDAIuRCsXGnBD4P+SA6nv7Y54HQZjC4SPL8PToU3714zu7wSEc1129D/i0+vvjRfOlFo4Zqrpwj+Zhcykhw=="
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..aeb13580
--- /dev/null
+++ b/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "pocket-sync",
+ "private": true,
+ "version": "2.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "tauri": "tauri"
+ },
+ "dependencies": {
+ "@react-three/drei": "^9.40.0",
+ "@react-three/fiber": "^8.9.1",
+ "@tauri-apps/api": "^1.1.0",
+ "@types/three": "^0.146.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-markdown": "^8.0.3",
+ "recoil": "^0.7.6",
+ "remark-gfm": "^3.0.1",
+ "three": "^0.146.0"
+ },
+ "devDependencies": {
+ "@tauri-apps/cli": "^1.1.0",
+ "@types/node": "^18.7.10",
+ "@types/react": "^18.0.15",
+ "@types/react-dom": "^18.0.6",
+ "@vitejs/plugin-react": "^2.0.0",
+ "postcss-nesting": "^10.2.0",
+ "typescript": "^4.6.4",
+ "vite": "^3.0.2"
+ },
+ "prettier": {
+ "trailingComma": "es5",
+ "tabWidth": 2,
+ "semi": false,
+ "singleQuote": false
+ }
+}
diff --git a/public/tauri.svg b/public/tauri.svg
new file mode 100644
index 00000000..31b62c92
--- /dev/null
+++ b/public/tauri.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/public/vite.svg b/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/readme_images/cores_list.png b/readme_images/cores_list.png
new file mode 100644
index 00000000..c5de7f65
Binary files /dev/null and b/readme_images/cores_list.png differ
diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore
new file mode 100644
index 00000000..f4dfb82b
--- /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 00000000..1c49b1fc
--- /dev/null
+++ b/src-tauri/Cargo.lock
@@ -0,0 +1,4181 @@
+# 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 = "aes"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+ "opaque-debug",
+]
+
+[[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 = "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 = "base64ct"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
+
+[[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 = "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 = "bzip2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[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"
+dependencies = [
+ "jobserver",
+]
+
+[[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 = "cipher"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
+dependencies = [
+ "generic-array",
+]
+
+[[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 = "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 = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[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 = "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",
+ "subtle",
+]
+
+[[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 = "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 = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+
+[[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"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[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-locks"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eb42d4fb72227be5778429f9ef5240a38a358925a49f05b5cf702ce7c7e558a"
+dependencies = [
+ "futures-channel",
+ "futures-task",
+ "tokio",
+]
+
+[[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-sink"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
+
+[[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-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "gdk"
+version = "0.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 = "h2"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[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 = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[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-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-range"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
+[[package]]
+name = "hyper"
+version = "0.14.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa 1.0.4",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+dependencies = [
+ "bytes",
+ "hyper",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+]
+
+[[package]]
+name = "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 = "ipnet"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
+
+[[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 = "jobserver"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
+dependencies = [
+ "libc",
+]
+
+[[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 = "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",
+]
+
+[[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 = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[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 = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[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 = "password-hash"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
+dependencies = [
+ "base64ct",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[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 = "pbkdf2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+dependencies = [
+ "digest",
+ "hmac",
+ "password-hash",
+ "sha2",
+]
+
+[[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",
+ "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 = "pocket-sync"
+version = "0.0.0"
+dependencies = [
+ "bytes",
+ "futures",
+ "futures-locks",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "tempdir",
+ "time",
+ "tokio",
+ "walkdir",
+ "zip",
+]
+
+[[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.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
+dependencies = [
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.3.1",
+ "rdrand",
+ "winapi",
+]
+
+[[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.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+dependencies = [
+ "rand_core 0.4.2",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+
+[[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 = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[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 = "reqwest"
+version = "0.11.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-tls",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "winreg",
+]
+
+[[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 = "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 = "sha1"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[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 = "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 = "socket2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[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 = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[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",
+ "cocoa",
+ "dirs-next",
+ "embed_plist",
+ "encoding_rs",
+ "flate2",
+ "futures-util",
+ "glib",
+ "glob",
+ "gtk",
+ "heck 0.4.0",
+ "http",
+ "ignore",
+ "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",
+ "tokio",
+ "url",
+ "uuid 1.2.1",
+ "webkit2gtk",
+ "webview2-com",
+ "windows 0.39.0",
+]
+
+[[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",
+ "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 = "tempdir"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
+dependencies = [
+ "rand 0.4.6",
+ "remove_dir_all",
+]
+
+[[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 = "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.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.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "socket2",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[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 = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[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 = "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"
+
+[[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 = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.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 = "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 = "winreg"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+dependencies = [
+ "winapi",
+]
+
+[[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 = [
+ "aes",
+ "byteorder",
+ "bzip2",
+ "constant_time_eq",
+ "crc32fast",
+ "crossbeam-utils",
+ "flate2",
+ "hmac",
+ "pbkdf2",
+ "sha1",
+ "time",
+ "zstd",
+]
+
+[[package]]
+name = "zstd"
+version = "0.11.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "5.0.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.1+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b"
+dependencies = [
+ "cc",
+ "libc",
+]
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
new file mode 100644
index 00000000..3826f31d
--- /dev/null
+++ b/src-tauri/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "pocket-sync"
+version = "0.0.0"
+description = "A Tauri App"
+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"] }
+zip = { version = "0.6.3", feature = ["deflate", "time"] }
+reqwest = "0.11.12"
+futures = "0.3.25"
+futures-locks = "0.7.0"
+tokio = "1.21.2"
+tempdir = "0.3.7"
+walkdir = "2.3.2"
+bytes = "1.2.1"
+time = "0.3.17"
+
+[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 00000000..795b9b7c
--- /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 00000000..1c123957
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 00000000..7db2924a
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 00000000..b2ea7060
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 00000000..812ee8f2
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 00000000..7142207a
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 00000000..b9d6fe11
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 00000000..680cc3ce
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 00000000..58b8b656
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 00000000..a1f2864f
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 00000000..5e3a88c7
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 00000000..969aa7ad
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 00000000..218f9c4f
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 00000000..6e1d3cdc
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 00000000..b662b1fc
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 00000000..7f248d78
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 00000000..c41ab689
Binary files /dev/null and b/src-tauri/icons/icon.png differ
diff --git a/src-tauri/src/checks.rs b/src-tauri/src/checks.rs
new file mode 100644
index 00000000..0990876b
--- /dev/null
+++ b/src-tauri/src/checks.rs
@@ -0,0 +1,69 @@
+use std::{
+ path::PathBuf,
+ thread::{self, sleep},
+ time::Duration,
+};
+
+use serde::{Deserialize, Serialize};
+use tauri::{App, Manager};
+
+use crate::PocketSyncState;
+
+pub fn check_if_folder_looks_like_pocket(path: &PathBuf) -> bool {
+ let json_path = path.join("Analogue_Pocket.json");
+
+ if !json_path.exists() {
+ return false;
+ }
+
+ let assets_path = path.join("Assets");
+
+ if !assets_path.exists() {
+ return false;
+ }
+
+ let cores_path = path.join("Cores");
+
+ if !cores_path.exists() {
+ return false;
+ }
+
+ // yeah, looks enough like a Pocket
+ return true;
+}
+
+pub fn start_connection_thread(app: &App) -> Result<(), Box<(dyn std::error::Error + 'static)>> {
+ let app_handle = app.handle();
+
+ thread::spawn(move || {
+ let main_window = app_handle.get_window("main").unwrap();
+ let mut was_connected: bool = false;
+ loop {
+ tauri::async_runtime::block_on(async {
+ let state: tauri::State = tauri::Manager::state(&app_handle);
+ let pocket_path = state.0.read().await;
+ // println!("checking if still connected {}", pocket_path.exists());
+ if !pocket_path.exists() && was_connected {
+ was_connected = false;
+ main_window
+ .emit(
+ "pocket-connection",
+ ConnectionEventPayload { connected: false },
+ )
+ .unwrap();
+ } else if pocket_path.exists() && !was_connected {
+ was_connected = true;
+ }
+ });
+
+ sleep(Duration::from_secs(5));
+ }
+ });
+
+ Ok(())
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct ConnectionEventPayload {
+ connected: bool,
+}
diff --git a/src-tauri/src/install_zip.rs b/src-tauri/src/install_zip.rs
new file mode 100644
index 00000000..4eb26e20
--- /dev/null
+++ b/src-tauri/src/install_zip.rs
@@ -0,0 +1,258 @@
+use reqwest::StatusCode;
+use serde::{Deserialize, Serialize};
+use std::{fs, io::Cursor, path::PathBuf, thread};
+use tauri::{App, Manager, Window};
+use tempdir::TempDir;
+use zip::ZipArchive;
+
+use crate::PocketSyncState;
+
+#[derive(Serialize, Deserialize)]
+struct InstallInfo {
+ core_name: String,
+ zip_url: String,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct PathStatus {
+ path: String,
+ exists: bool,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct InstallConfirmation {
+ paths: Vec,
+ allow: bool,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct InstallZipEventPayload {
+ title: String,
+ files: Option>,
+ progress: Option,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct ZipInstallFinishedPayload {
+ error: Option,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct ZipInstallProgress {
+ max: usize,
+ value: usize,
+}
+
+struct Titles {
+ title: String,
+ installing_title: String,
+}
+
+pub fn start_zip_thread(app: &App) -> Result<(), Box<(dyn std::error::Error + 'static)>> {
+ let app_handle = app.handle();
+ let app_handle_b = app.handle();
+
+ thread::spawn(move || {
+ let main_window = app_handle.get_window("main").unwrap();
+ let main_window_b = app_handle.get_window("main").unwrap();
+ let main_window_c = app_handle.get_window("main").unwrap();
+
+ main_window.on_window_event(move |event| {
+ if let tauri::WindowEvent::FileDrop(e) = event {
+ if let tauri::FileDropEvent::Dropped(paths) = e {
+ tokio::task::block_in_place(|| {
+ tauri::async_runtime::block_on(async {
+ let state: tauri::State = app_handle_b.state();
+ let pocket_path = state.0.read().await;
+
+ if !pocket_path.exists() || paths.len() != 1 {
+ return;
+ }
+
+ for path in paths {
+ if !path
+ .file_name()
+ .and_then(|f| f.to_str())
+ .unwrap()
+ .ends_with(".zip")
+ {
+ continue;
+ }
+ let zip_file = fs::read(path).unwrap();
+ let cursor = Cursor::new(zip_file);
+ let archive = zip::ZipArchive::new(cursor).unwrap();
+
+ start_zip_install_flow(
+ archive,
+ Titles {
+ title: String::from("Install Zip"),
+ installing_title: (String::from("Installing Zip...")),
+ },
+ pocket_path.clone(),
+ &main_window_c,
+ )
+ .await
+ .unwrap();
+ }
+ })
+ });
+ }
+ }
+ });
+
+ main_window.listen("install-core", move |event| {
+ emit_progress("Install Core", None, None, &main_window_b);
+
+ tokio::task::block_in_place(|| {
+ tauri::async_runtime::block_on(async {
+ let state: tauri::State = app_handle.state();
+ let pocket_path = state.0.read().await;
+
+ let install: InstallInfo =
+ serde_json::from_str(event.payload().unwrap()).unwrap();
+ let response = reqwest::get(install.zip_url).await.unwrap();
+
+ // dbg!(&response);
+
+ match response.status() {
+ StatusCode::OK => {
+ let zip_file = response.bytes().await.unwrap();
+ let cursor = Cursor::new(zip_file);
+ let archive = zip::ZipArchive::new(cursor).unwrap();
+
+ start_zip_install_flow(
+ archive,
+ Titles {
+ title: String::from("Install Core"),
+ installing_title: (String::from("Installing Core...")),
+ },
+ pocket_path.clone(),
+ &main_window_b,
+ )
+ .await
+ .unwrap();
+ }
+ _s => {
+ emit_finished(
+ Some(String::from("Unable to download ZIP")),
+ &main_window_b,
+ );
+ }
+ }
+ });
+ })
+ })
+ });
+
+ Ok(())
+}
+
+fn emit_progress(
+ title: &str,
+ files: Option>,
+ progress: Option,
+ window: &Window,
+) -> () {
+ window
+ .emit(
+ "install-zip-event",
+ InstallZipEventPayload {
+ title: String::from(title),
+ files: files,
+ progress,
+ },
+ )
+ .unwrap();
+}
+
+fn emit_finished(error: Option, window: &Window) -> () {
+ window
+ .emit("install-zip-finished", ZipInstallFinishedPayload { error })
+ .unwrap();
+}
+
+async fn start_zip_install_flow(
+ mut archive: ZipArchive,
+ titles: Titles,
+ pocket_path: PathBuf,
+ window: &Window,
+) -> Result<(), ()> {
+ emit_progress(
+ &titles.title,
+ Some(get_file_names(&archive, &pocket_path)),
+ None,
+ &window,
+ );
+
+ let main_window_c = window.clone();
+
+ window.once("install-confirmation", move |event| {
+ let install_confirm: InstallConfirmation =
+ serde_json::from_str(event.payload().unwrap()).unwrap();
+
+ if !install_confirm.allow {
+ emit_finished(None, &main_window_c);
+ return ();
+ }
+
+ emit_progress(
+ &titles.installing_title,
+ None,
+ Some(ZipInstallProgress {
+ value: 0,
+ max: install_confirm.paths.len(),
+ }),
+ &main_window_c,
+ );
+
+ let tmp_dir = TempDir::new("zip_install_tmp").unwrap();
+ let tmp_path = tmp_dir.into_path();
+ archive.extract(&tmp_path).unwrap();
+
+ for (index, path) in install_confirm.paths.iter().enumerate() {
+ let destination = pocket_path.join(&path);
+ let source = tmp_path.join(&path);
+
+ if destination.is_dir() && destination.exists() {
+ continue;
+ }
+
+ // println!("copy from {:?} to {:?}", &source, &destination);
+
+ fs::create_dir_all(destination.parent().unwrap()).unwrap();
+ if !source.is_dir() {
+ fs::copy(&source, &destination).unwrap();
+ }
+
+ emit_progress(
+ &titles.installing_title,
+ None,
+ Some(ZipInstallProgress {
+ value: index + 1,
+ max: install_confirm.paths.len(),
+ }),
+ &main_window_c,
+ );
+ }
+ emit_finished(None, &main_window_c);
+ });
+
+ Ok(())
+}
+
+fn get_file_names(
+ zip: &ZipArchive,
+ pocket_path: &PathBuf,
+) -> Vec {
+ let files = zip.file_names();
+ files
+ .into_iter()
+ .map(|f| {
+ let path = pocket_path.join(f);
+ PathStatus {
+ exists: path.exists(),
+ path: String::from(f),
+ }
+ })
+ .collect()
+}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
new file mode 100644
index 00000000..ad478aef
--- /dev/null
+++ b/src-tauri/src/main.rs
@@ -0,0 +1,346 @@
+#![cfg_attr(
+ all(not(debug_assertions), target_os = "windows"),
+ windows_subsystem = "windows"
+)]
+
+use checks::{check_if_folder_looks_like_pocket, start_connection_thread};
+use futures_locks::RwLock;
+use install_zip::start_zip_thread;
+use saves_zip::{
+ build_save_zip, read_save_zip_list, read_saves_in_zip, restore_save_from_zip, SaveZipFile,
+};
+use serde::{Deserialize, Serialize};
+use std::collections::HashSet;
+use std::fs::{self};
+use std::io::Read;
+use std::io::Write;
+use std::path::PathBuf;
+use tauri::api::dialog;
+use tauri::{App, Window};
+use walkdir::{DirEntry, WalkDir};
+mod checks;
+mod install_zip;
+mod saves_zip;
+struct PocketSyncState(RwLock);
+
+// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
+#[tauri::command(async)]
+async fn open_pocket(state: tauri::State<'_, PocketSyncState>) -> Result, ()> {
+ if let Some(pocket_path) = dialog::blocking::FileDialogBuilder::new().pick_folder() {
+ if !check_if_folder_looks_like_pocket(&pocket_path) {
+ return Ok(None);
+ }
+
+ let mut path_state = state.0.write().await;
+ *path_state = pocket_path;
+
+ Ok(Some(String::from(path_state.to_str().unwrap())))
+ } else {
+ Err(())
+ }
+}
+
+#[tauri::command(async)]
+async fn read_binary_file(
+ state: tauri::State<'_, PocketSyncState>,
+ path: &str,
+) -> Result, ()> {
+ let pocket_path = state.0.read().await;
+ let path = pocket_path.join(path);
+
+ let mut f = fs::File::open(&path).expect(&format!("no file found: {:?}", path));
+ let metadata = fs::metadata(&path).expect("unable to read metadata");
+ let mut buffer = vec![0; metadata.len() as usize];
+
+ f.read(&mut buffer).expect("buffer overflow");
+
+ Ok(buffer)
+}
+
+#[tauri::command(async)]
+async fn read_text_file(
+ state: tauri::State<'_, PocketSyncState>,
+ path: &str,
+) -> Result {
+ let pocket_path = state.0.read().await;
+ let path = pocket_path.join(path);
+ // println!("reading text file: {:?}", &path);
+ let video_json = fs::read_to_string(path).unwrap();
+ Ok(video_json)
+}
+
+#[tauri::command(async)]
+async fn file_exists(state: tauri::State<'_, PocketSyncState>, path: &str) -> Result {
+ let pocket_path = state.0.read().await;
+ let path = pocket_path.join(path);
+ // println!("checking if file exists @{:?}", &path);
+ Ok(path.exists())
+}
+
+#[tauri::command(async)]
+fn save_file(path: &str, buffer: Vec) -> Result {
+ let file_path = PathBuf::from(path);
+ // println!("Saving file {:?}", &file_path);
+ let mut file = fs::File::create(file_path).unwrap();
+ file.write_all(&buffer).unwrap();
+ Ok(true)
+}
+
+#[tauri::command(async)]
+async fn list_files(
+ path: &str,
+ state: tauri::State<'_, PocketSyncState>,
+) -> Result, ()> {
+ let pocket_path = state.0.read().await;
+ let dir_path = pocket_path.join(path);
+
+ if !dir_path.exists() {
+ return Ok(vec![]);
+ }
+
+ let paths = fs::read_dir(dir_path).unwrap();
+
+ Ok(paths
+ .into_iter()
+ .filter(Result::is_ok)
+ .map(|p| p.unwrap())
+ .map(|p| p.file_name().into_string().unwrap())
+ .filter(|s| !s.starts_with("."))
+ .collect())
+}
+
+#[tauri::command(async)]
+async fn walkdir_list_files(
+ path: &str,
+ extensions: Vec<&str>,
+ state: tauri::State<'_, PocketSyncState>,
+) -> Result, ()> {
+ let pocket_path = state.0.read().await;
+ let dir_path = pocket_path.join(path);
+
+ if !dir_path.exists() {
+ return Ok(vec![]);
+ }
+
+ fn is_hidden(entry: &DirEntry) -> bool {
+ entry
+ .file_name()
+ .to_str()
+ .map(|s| s.starts_with("."))
+ .unwrap_or(false)
+ }
+
+ let walker = WalkDir::new(&dir_path).into_iter();
+ let dir_path_str = &dir_path.to_str().unwrap();
+ Ok(walker
+ .filter_entry(|e| !is_hidden(e))
+ .into_iter()
+ .filter_map(|x| x.ok())
+ .map(|e| String::from(e.path().to_str().unwrap()))
+ .filter(|s| extensions.iter().any(|e| s.ends_with(e)))
+ .map(|s| s.replace(dir_path_str, ""))
+ .collect())
+}
+
+#[tauri::command(async)]
+async fn uninstall_core(
+ core_name: &str,
+ state: tauri::State<'_, PocketSyncState>,
+) -> Result {
+ let pocket_path = state.0.write().await;
+ let core_path = pocket_path.join("Cores").join(core_name);
+ // println!("I will remove {:?}", &core_path);
+ if core_path.exists() && core_path.is_dir() {
+ // not sure why this doesn't work
+ // fs::remove_dir_all(core_path).unwrap();
+
+ if let Ok(entries) = std::fs::read_dir(&core_path) {
+ for entry in entries {
+ let path = entry.unwrap().path();
+ // println!("{:?}", path);
+
+ if path.exists() {
+ std::fs::remove_file(path).unwrap();
+ }
+ }
+ };
+
+ fs::remove_dir(&core_path).unwrap();
+ } else {
+ println!("Weird, it's gone already");
+ }
+
+ Ok(true)
+}
+
+#[tauri::command(async)]
+async fn install_archive_files(
+ files: Vec,
+ archive_url: &str,
+ state: tauri::State<'_, PocketSyncState>,
+ window: Window,
+) -> Result {
+ // println!("installing archive files");
+ let pocket_path = state.0.read().await;
+ let file_count = files.len();
+
+ let mut failed_already = HashSet::new();
+
+ window
+ .emit(
+ "file-progress",
+ FileProgressPayload {
+ value: 0,
+ max: file_count,
+ },
+ )
+ .unwrap();
+
+ for (index, file) in files.into_iter().enumerate() {
+ let full_url = format!("{}/{}", archive_url, file.filename);
+
+ // println!("Downloading from {full_url}");
+
+ if !failed_already.contains(&file.filename) {
+ let response = reqwest::get(&full_url).await;
+
+ match response {
+ Err(e) => {
+ println!("Error downloading from {full_url}: ({e})");
+ failed_already.insert(file.filename);
+ }
+ Ok(r) => {
+ if r.status() != 200 {
+ println!("Unable to find {full_url}, skipping");
+ failed_already.insert(file.filename);
+ } else {
+ let folder = pocket_path.join(file.path);
+
+ if !folder.exists() {
+ fs::create_dir_all(&folder).unwrap();
+ }
+
+ let new_file_path = folder.join(file.filename);
+ let mut dest = fs::File::create(&new_file_path).unwrap();
+ let content = r.bytes().await.unwrap();
+ let mut content_cusror = std::io::Cursor::new(content);
+ std::io::copy(&mut content_cusror, &mut dest).unwrap();
+ }
+ }
+ }
+ }
+
+ window
+ .emit(
+ "file-progress",
+ FileProgressPayload {
+ value: index + 1,
+ max: file_count,
+ },
+ )
+ .unwrap();
+ }
+
+ Ok(true)
+}
+
+#[tauri::command(async)]
+async fn backup_saves(
+ save_paths: Vec<&str>,
+ zip_path: &str,
+ max_count: usize,
+ state: tauri::State<'_, PocketSyncState>,
+) -> Result {
+ let pocket_path = state.0.read().await;
+ build_save_zip(&pocket_path, save_paths, zip_path, max_count).unwrap();
+
+ Ok(true)
+}
+
+#[tauri::command(async)]
+async fn list_backup_saves(backup_path: &str) -> Result {
+ let path = PathBuf::from(backup_path);
+ if !path.exists() {
+ return Ok(BackupSavesResponse {
+ files: vec![],
+ exists: false,
+ });
+ }
+
+ let files = read_save_zip_list(&path).unwrap();
+
+ Ok(BackupSavesResponse {
+ files,
+ exists: true,
+ })
+}
+
+#[tauri::command(async)]
+async fn list_saves_in_zip(zip_path: &str) -> Result, ()> {
+ let path = PathBuf::from(zip_path);
+ if !path.exists() {
+ return Ok(vec![]);
+ }
+
+ read_saves_in_zip(&path)
+}
+
+#[tauri::command(async)]
+async fn restore_save(
+ zip_path: &str,
+ file_path: &str,
+ state: tauri::State<'_, PocketSyncState>,
+) -> Result<(), ()> {
+ let pocket_path = state.0.read().await;
+ let path = PathBuf::from(zip_path);
+ restore_save_from_zip(&path, file_path, &pocket_path);
+
+ Ok(())
+}
+
+fn main() {
+ tauri::Builder::default()
+ .manage(PocketSyncState(Default::default()))
+ .invoke_handler(tauri::generate_handler![
+ open_pocket,
+ list_files,
+ walkdir_list_files,
+ read_binary_file,
+ read_text_file,
+ save_file,
+ uninstall_core,
+ install_archive_files,
+ file_exists,
+ backup_saves,
+ list_backup_saves,
+ list_saves_in_zip,
+ restore_save
+ ])
+ .setup(|app| start_threads(&app))
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
+
+fn start_threads(app: &App) -> Result<(), Box<(dyn std::error::Error + 'static)>> {
+ start_connection_thread(&app).unwrap();
+ start_zip_thread(&app).unwrap();
+ Ok(())
+}
+
+#[derive(Serialize, Deserialize)]
+struct DownloadFile {
+ filename: String,
+ path: String,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+struct FileProgressPayload {
+ value: usize,
+ max: usize,
+}
+
+#[derive(Serialize, Deserialize)]
+struct BackupSavesResponse {
+ files: Vec,
+ exists: bool,
+}
diff --git a/src-tauri/src/saves_zip.rs b/src-tauri/src/saves_zip.rs
new file mode 100644
index 00000000..ff790e57
--- /dev/null
+++ b/src-tauri/src/saves_zip.rs
@@ -0,0 +1,168 @@
+use serde::{Deserialize, Serialize};
+use std::{
+ cmp::{Ord, Ordering},
+ fs::{self, File},
+ io::{Cursor, Read, Write},
+ path::{Path, PathBuf},
+ time::SystemTime,
+};
+use tempdir::TempDir;
+use walkdir::WalkDir;
+use zip::{write::FileOptions, DateTime};
+
+#[derive(Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
+pub struct SaveZipFile {
+ last_modified: u32,
+ filename: String,
+}
+
+impl Ord for SaveZipFile {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.last_modified.cmp(&other.last_modified)
+ }
+}
+
+static FILE_PREFIX: &str = "pocket-sync-save-backup__";
+
+pub fn restore_save_from_zip(zip_path: &PathBuf, file_path: &str, pocket_path: &PathBuf) -> () {
+ let zip_file = fs::read(zip_path).unwrap();
+ let cursor = Cursor::new(zip_file);
+ let mut archive = zip::ZipArchive::new(cursor).unwrap();
+
+ let tmp_dir = TempDir::new("zip_saves_tmp").unwrap();
+ let tmp_path = tmp_dir.into_path();
+ archive.extract(&tmp_path).unwrap();
+
+ let src_file_path = tmp_path.join(remove_leading_slash(file_path));
+ let dest_file_path = pocket_path
+ .join("Saves")
+ .join(remove_leading_slash(file_path));
+
+ // println!("from {:?} to {:?}", src_file_path, dest_file_path);
+
+ fs::create_dir_all(dest_file_path.parent().unwrap()).unwrap();
+ fs::copy(&src_file_path, &dest_file_path).unwrap();
+}
+
+pub fn read_saves_in_zip(zip_path: &PathBuf) -> Result, ()> {
+ let zip_file = fs::read(zip_path).unwrap();
+ let cursor = Cursor::new(zip_file);
+ let mut archive = zip::ZipArchive::new(cursor).unwrap();
+
+ let tmp_dir = TempDir::new("zip_saves_tmp").unwrap();
+ let tmp_path = tmp_dir.into_path();
+ archive.extract(&tmp_path).unwrap();
+
+ let walker = WalkDir::new(&tmp_path).into_iter();
+ let dir_path_str = &tmp_path.to_str().unwrap();
+ Ok(walker
+ .into_iter()
+ .filter_map(|x| x.ok())
+ .map(|e| {
+ let file_path = e.path();
+ let metadata = file_path.metadata().unwrap();
+ let last_modified = time::OffsetDateTime::from(metadata.created().unwrap());
+
+ SaveZipFile {
+ filename: String::from(e.path().to_str().unwrap()).replace(dir_path_str, ""),
+ last_modified: last_modified.unix_timestamp().try_into().unwrap(),
+ }
+ })
+ .collect())
+}
+
+pub fn read_save_zip_list(dir_path: &PathBuf) -> Result, ()> {
+ if !dir_path.exists() {
+ return Ok(vec![]);
+ }
+ let paths = fs::read_dir(dir_path).unwrap();
+ Ok(paths
+ .into_iter()
+ .filter(Result::is_ok)
+ .map(|p| p.unwrap())
+ .map(|p| p.file_name().into_string().unwrap())
+ .filter(|s| s.starts_with(FILE_PREFIX))
+ .map(|filename| {
+ let file_path = dir_path.join(&filename);
+ let metadata = file_path.metadata().unwrap();
+ let last_modified = time::OffsetDateTime::from(metadata.modified().unwrap());
+
+ SaveZipFile {
+ filename,
+ last_modified: last_modified.unix_timestamp().try_into().unwrap(),
+ }
+ })
+ .collect())
+}
+
+pub fn build_save_zip(
+ pocket_path: &PathBuf,
+ save_paths: Vec<&str>,
+ dir_path: &str,
+ max_count: usize,
+) -> Result<(), ()> {
+ let zip_path = Path::new(dir_path);
+ let timestamp = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs();
+ let filename = format!("{FILE_PREFIX}{timestamp}.zip");
+ let zip_file_path = zip_path.join(filename);
+ let zip_file = File::create(zip_file_path).unwrap();
+
+ let mut zip = zip::ZipWriter::new(zip_file);
+ let options = FileOptions::default()
+ .compression_method(zip::CompressionMethod::Deflated)
+ .unix_permissions(0o755);
+
+ let saves_path = pocket_path.join("Saves");
+
+ let mut buffer = Vec::new();
+ for name in save_paths {
+ let path = saves_path.join(name);
+ let metadata = path.metadata().unwrap();
+ let last_modified = time::OffsetDateTime::from(metadata.modified().unwrap());
+
+ let file_options = options.last_modified_time(
+ DateTime::from_date_and_time(
+ last_modified.year().try_into().unwrap(),
+ last_modified.month().try_into().unwrap(),
+ last_modified.day(),
+ last_modified.hour(),
+ last_modified.minute(),
+ last_modified.second(),
+ )
+ .unwrap(),
+ );
+
+ if path.is_file() {
+ zip.start_file(name, file_options).unwrap();
+ let mut f = File::open(path).unwrap();
+ f.read_to_end(&mut buffer).unwrap();
+ zip.write_all(&*buffer).unwrap();
+ buffer.clear();
+ } else {
+ zip.add_directory(name, options).unwrap();
+ }
+ }
+ zip.finish().unwrap();
+
+ let files = read_save_zip_list(&PathBuf::from(zip_path)).unwrap();
+
+ if files.len() > max_count {
+ let oldest_file = files.iter().min().unwrap();
+ let last_file_path = zip_path.join(&oldest_file.filename);
+ fs::remove_file(last_file_path).unwrap();
+ }
+ Ok(())
+}
+
+fn remove_leading_slash(value: &str) -> &str {
+ if !value.starts_with("/") {
+ return value;
+ }
+
+ let mut chars = value.chars();
+ chars.next();
+ chars.as_str()
+}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
new file mode 100644
index 00000000..506321b9
--- /dev/null
+++ b/src-tauri/tauri.conf.json
@@ -0,0 +1,65 @@
+{
+ "build": {
+ "beforeDevCommand": "npm run dev",
+ "beforeBuildCommand": "npm run build",
+ "devPath": "http://localhost:1420",
+ "distDir": "../dist"
+ },
+ "package": {
+ "productName": "Pocket Sync",
+ "version": "1.0.0"
+ },
+ "tauri": {
+ "allowlist": {
+ "all": true
+ },
+ "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": "today.neil.pocket-sync",
+ "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": false
+ },
+ "windows": [
+ {
+ "fullscreen": false,
+ "height": 600,
+ "resizable": true,
+ "title": "Pocket Sync",
+ "width": 800
+ }
+ ]
+ }
+}
diff --git a/src/app.css b/src/app.css
new file mode 100644
index 00000000..a89ebd15
--- /dev/null
+++ b/src/app.css
@@ -0,0 +1,7 @@
+.logo.vite:hover {
+ filter: drop-shadow(0 0 2em #747bff);
+}
+
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafb);
+}
diff --git a/src/app.tsx b/src/app.tsx
new file mode 100644
index 00000000..a7164208
--- /dev/null
+++ b/src/app.tsx
@@ -0,0 +1,52 @@
+import "./font.css"
+import "./app.css"
+import { useRecoilState } from "recoil"
+import { pocketPathAtom } from "./recoil/atoms"
+import { Layout } from "./components/layout"
+import React, { Suspense, useCallback, useState } from "react"
+import { invokeOpenPocket } from "./utils/invokes"
+
+const Pocket = React.lazy(() =>
+ import("./components/three/pocket").then((m) => ({ default: m.Pocket }))
+)
+
+export const App = () => {
+ const [pocketPath, setPocketPath] = useRecoilState(pocketPathAtom)
+ const [attempts, setAttempts] = useState(0)
+
+ const onOpenPocket = useCallback(async () => {
+ const result = await invokeOpenPocket()
+ setPocketPath(result)
+ if (result === null) {
+ setAttempts((a) => a + 1)
+ } else {
+ setAttempts(0)
+ }
+ }, [setPocketPath, setAttempts])
+
+ if (pocketPath) {
+ return
+ }
+
+ return (
+
+
Pocket Sync
+
+ }>
+
+
+
+ {attempts > 0 && (
+
+ {"That folder doesn't look like the Pocket's file system. Try again"}
+
+ )}
+
+
+ onOpenPocket()}>
+ Connect to Pocket
+
+
+
+ )
+}
diff --git a/src/assets/AnalogueOS-Regular.woff b/src/assets/AnalogueOS-Regular.woff
new file mode 100644
index 00000000..27ddec7f
Binary files /dev/null and b/src/assets/AnalogueOS-Regular.woff differ
diff --git a/src/assets/AnalogueOS-Regular.woff2 b/src/assets/AnalogueOS-Regular.woff2
new file mode 100644
index 00000000..fcc89c1b
Binary files /dev/null and b/src/assets/AnalogueOS-Regular.woff2 differ
diff --git a/src/components/about/index.css b/src/components/about/index.css
new file mode 100644
index 00000000..218c781e
--- /dev/null
+++ b/src/components/about/index.css
@@ -0,0 +1,40 @@
+.about {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ height: 100vh;
+ text-align: center;
+}
+
+
+.about__update-link{
+ font-size: 2rem;
+ background-color: var(--green-colour);
+ padding: 20px;
+ border-radius: 25px;
+ white-space: nowrap;
+}
+
+.about__top{
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ /* width: 100%; */
+ width: max-content;
+ text-align: center;
+ align-items: center;
+ gap: 10px;
+}
+
+.about__info{
+ background-color: var(--info-colour);
+ width: 100%;
+ max-height: fit-content;
+ flex-grow: 1;
+ justify-self: flex-end;
+ text-align: left;
+ padding: 0px 20px;
+ box-sizing: border-box;
+}
diff --git a/src/components/about/index.tsx b/src/components/about/index.tsx
new file mode 100644
index 00000000..f58670fc
--- /dev/null
+++ b/src/components/about/index.tsx
@@ -0,0 +1,79 @@
+import { Suspense, useMemo } from "react"
+import { useRecoilValue } from "recoil"
+import { GithubReleasesSelectorFamily } from "../../recoil/github/selectors"
+import {
+ AppVersionSelector,
+ PocketSyncConfigSelector,
+} from "../../recoil/selectors"
+import { Link } from "../link"
+import { Pocket } from "../three/pocket"
+import { ProgressScreen } from "../three/progressScreen"
+import { RandomScreenshotScreen } from "../three/randomScreenshotScreen"
+
+import "./index.css"
+
+export const About = () => {
+ const selfReleases = useRecoilValue(
+ GithubReleasesSelectorFamily({
+ owner: "neil-morrison44",
+ repo: "pocket-sync",
+ })
+ )
+
+ const AppVersion = useRecoilValue(AppVersionSelector)
+
+ const updateAvailable = useMemo(() => {
+ return selfReleases[0].tag_name !== `v${AppVersion}`
+ }, [selfReleases, AppVersion])
+
+ return (
+
+
+
+
Pocket Sync
+ {`v${AppVersion}`}
+
+
+ {updateAvailable && (
+
{`Update Available! ${selfReleases[0].tag_name}`}
+ )}
+
+
}
+ >
+
+
+ }
+ />
+
+
+
+
Thanks to:
+
+
+
+
+ {"https://github.com/joshcampbell191/openfpga-cores-inventory"}
+
+
+
+
+
+ {"https://github.com/AbFarid/analogue-os-font"}
+
+
+
+
+
+ )
+}
diff --git a/src/components/controls/index.css b/src/components/controls/index.css
new file mode 100644
index 00000000..f7160cf2
--- /dev/null
+++ b/src/components/controls/index.css
@@ -0,0 +1,65 @@
+.controls{
+ display: flex;
+ justify-content: space-between;
+ font-size: 1.125rem;
+ background-color: var(--info-colour);
+ position: sticky;
+ top: 0;
+ z-index: 100;
+}
+
+.controls__item{
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10px 20px;
+ accent-color: white;
+
+ &:hover{
+ background-color: var(--hover-colour);
+ }
+}
+
+
+.controls__item--back::before{
+ content: "";
+ display: block;
+ width: 10px;
+ height: 10px;
+ aspect-ratio: 1 / 1;
+ border: 1px solid white;
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+ transform: rotate(-45deg);
+ margin-right: 5px;
+}
+
+.controls__item--checkbox{
+ display: flex;
+ gap: 0.2em;
+ align-items: center;
+}
+
+
+.controls__search-input{
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 2px 10px;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ width: 200px;
+ transition: border-color 0.25s;
+}
+
+.controls__item--select{
+ display: flex;
+ gap: 5px;
+ align-items: center;
+ font-size: 1.125rem;
+
+ & > select {
+ font-size: 1.125rem;
+ }
+}
diff --git a/src/components/controls/index.tsx b/src/components/controls/index.tsx
new file mode 100644
index 00000000..17c01315
--- /dev/null
+++ b/src/components/controls/index.tsx
@@ -0,0 +1,115 @@
+import "./index.css"
+
+type Control = {
+ text: string
+ type: "button" | "back-button" | "checkbox" | "select" | "search"
+} & (
+ | {
+ type: "button"
+ onClick: () => void
+ }
+ | {
+ type: "back-button"
+ onClick: () => void
+ }
+ | {
+ type: "checkbox"
+ checked: boolean
+ onChange: (checked: boolean) => void
+ }
+ | {
+ type: "select"
+ options: string[]
+ selected: string
+ onChange: (value: string) => void
+ }
+ | {
+ type: "search"
+ value: string
+ onChange: (value: string) => void
+ }
+)
+
+type ControlProps = {
+ controls: (Control | null | undefined | "")[]
+}
+
+export const Controls = ({ controls }: ControlProps) => {
+ return (
+
+ {controls.map((control) => {
+ if (!control) return null
+ switch (control.type) {
+ case "search":
+ return (
+
+ control.onChange(target.value)}
+ autoComplete="off"
+ value={control.value}
+ spellCheck={false}
+ />
+
+ )
+ case "select":
+ return (
+
+ {control.text}
+ control.onChange(target.value)}
+ >
+ {control.options.map((v) => (
+
+ {v}
+
+ ))}
+
+
+ )
+ case "button":
+ return (
+
+ {control.text}
+
+ )
+ case "back-button":
+ return (
+
+ {"Back to List"}
+
+ )
+ case "checkbox":
+ return (
+
+ {control.text}
+ control.onChange(target.checked)}
+ key={control.text}
+ />
+
+ )
+ }
+ })}
+
+ )
+}
diff --git a/src/components/cores/index.css b/src/components/cores/index.css
new file mode 100644
index 00000000..91cd13f0
--- /dev/null
+++ b/src/components/cores/index.css
@@ -0,0 +1,51 @@
+.cores__platform-image {
+ width: 100%;
+}
+
+.cores__info-blurb{
+ background-color: var(--info-colour);
+ padding: 10px;
+ display: flex;
+ gap: 5px;
+ flex-direction: column;
+ justify-content: space-between;
+ flex-grow: 1;
+}
+
+.cores__info-blurb-name{
+ font-weight: bold;
+}
+
+.cores__author-tag{
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.cores__author-tag-image{
+ width: 24px;
+ height: 24px;
+}
+
+.cores__item{
+ display: flex;
+ flex-direction: column;
+ cursor: pointer;
+
+ &:hover, &:focus {
+ transform: scale(1.1);
+ }
+}
+
+.cores__item--not-installed{
+ background-color: var(--info-colour);
+ justify-content: space-between;
+ padding: 10px;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+.cores__not-installed-item-id{
+ font-weight: normal;
+ font-size: 0.825rem;
+}
diff --git a/src/components/cores/index.tsx b/src/components/cores/index.tsx
new file mode 100644
index 00000000..747b0005
--- /dev/null
+++ b/src/components/cores/index.tsx
@@ -0,0 +1,148 @@
+import { Suspense, useMemo, useState } from "react"
+import { useRecoilCallback, useRecoilValue } from "recoil"
+import { useCategoryLookup } from "../../hooks/useCategoryLookup"
+import { useSaveScroll } from "../../hooks/useSaveScroll"
+import {
+ fileSystemInvalidationAtom,
+ inventoryInvalidationAtom,
+} from "../../recoil/atoms"
+import { CateogryListselector } from "../../recoil/inventory/selectors"
+import { CoreInventorySelector } from "../../recoil/inventory/selectors"
+import { coresListSelector } from "../../recoil/selectors"
+import { Controls } from "../controls"
+import { Grid } from "../grid"
+import { Loader } from "../loader"
+import { Tip } from "../tip"
+import { CoreInfo } from "./info"
+import { CoreItem } from "./item"
+
+export const Cores = () => {
+ const [selectedCore, setSelectedCore] = useState(null)
+ const coresList = useRecoilValue(coresListSelector)
+ const coreInventory = useRecoilValue(CoreInventorySelector)
+ const { pushScroll, popScroll } = useSaveScroll()
+ const [searchQuery, setSearchQuery] = useState("")
+ const [filterCategory, setFilterCategory] = useState("All")
+ const lookupCategory = useCategoryLookup()
+
+ const refresh = useRecoilCallback(({ set }) => () => {
+ set(inventoryInvalidationAtom, Date.now())
+ set(fileSystemInvalidationAtom, Date.now())
+ })
+
+ const notInstalledCores = useMemo(() => {
+ return coreInventory.data
+ .filter(({ identifier }) => !coresList.includes(identifier))
+ .filter(({ release, prerelease }) => {
+ if (filterCategory === "All") return true
+
+ if (release?.platform.category === filterCategory) return true
+ if (prerelease?.platform.category === filterCategory) return true
+ return false
+ })
+ .filter((core) =>
+ core.identifier.toLowerCase().includes(searchQuery.toLowerCase())
+ )
+ }, [searchQuery, filterCategory, coresList, coreInventory])
+
+ const sortedList = useMemo(
+ () =>
+ [...coresList]
+ .sort((a, b) => {
+ const [authorA, coreA] = a.split(".")
+ const switchedA = `${coreA}.${authorA}`
+
+ const [authorB, coreB] = b.split(".")
+ const switchedB = `${coreB}.${authorB}`
+
+ return switchedA.localeCompare(switchedB)
+ })
+ .filter((core) => {
+ if (filterCategory === "All") return true
+ return lookupCategory(core) === filterCategory
+ })
+ .filter((core) =>
+ core.toLowerCase().includes(searchQuery.toLowerCase())
+ ),
+ [searchQuery, filterCategory, coresList]
+ )
+
+ const categoryList = useRecoilValue(CateogryListselector)
+
+ if (selectedCore) {
+ return (
+ {
+ setSelectedCore(null)
+ popScroll()
+ }}
+ />
+ )
+ }
+
+ return (
+
+
setSearchQuery(v),
+ },
+ {
+ type: "button",
+ text: "Refresh",
+ onClick: refresh,
+ },
+ {
+ type: "select",
+ options: categoryList,
+ selected: filterCategory,
+ text: "Category",
+ onChange: (v) => setFilterCategory(v),
+ },
+ ]}
+ />
+ {`Installed (${sortedList.length})`}
+
+ {sortedList.map((core) => (
+ } key={core}>
+ {
+ pushScroll()
+ setSelectedCore(core)
+ }}
+ />
+
+ ))}
+
+
+ {`Available (${notInstalledCores.length})`}
+
+ {notInstalledCores.map(({ identifier: core, platform }) => (
+ } key={core}>
+ {
+ pushScroll()
+ setSelectedCore(core)
+ }}
+ >
+
{platform}
+
{core}
+
+
+ ))}
+
+
+
+ {
+ "You can also install cores (or anything else in a zip) by dragging the .zip into this window"
+ }
+
+
+ )
+}
diff --git a/src/components/cores/info/index.css b/src/components/cores/info/index.css
new file mode 100644
index 00000000..04b35e03
--- /dev/null
+++ b/src/components/cores/info/index.css
@@ -0,0 +1,81 @@
+.core-info{
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+}
+
+.core-info__image{
+ width: 100%;
+ image-rendering: pixelated;
+}
+
+
+.core-info__title{
+ padding: 0px 20px;
+ font-size: 2rem;
+}
+
+.core-info__info{
+ background-color: var(--info-colour);
+ padding: 10px;
+ display: flex;
+ gap: 5px;
+ flex-direction: column;
+ flex-grow: 1;
+}
+
+.core-info__info-row{
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ grid-column: 1;
+}
+
+.core-info__info-row--right{
+ grid-column: 2;
+}
+
+.core-info__info-grid{
+ display: grid;
+ gap: inherit;
+ grid-template-columns: 1fr auto;
+}
+
+.core-info__author-tag{
+ display: inline-flex;
+ gap: 10px;
+ justify-content: center;
+ align-items: center;
+
+ image-rendering: pixelated;
+}
+
+.core-info__supports-bubbles{
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.core-info__supports {
+ white-space: nowrap;
+ background-color: var(--light-colour);
+ padding: 0 5px;
+ border-radius: 5px;
+}
+
+.core-info__platforms{
+ padding: 10px 0;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.core-info__platform-loader{
+ width: 100px;
+ aspect-ratio: 4 / 2;
+ border-radius: 15px;
+}
+
+.core-info__supports--false{
+ opacity: 0.2;
+}
diff --git a/src/components/cores/info/index.tsx b/src/components/cores/info/index.tsx
new file mode 100644
index 00000000..0f0b1c2e
--- /dev/null
+++ b/src/components/cores/info/index.tsx
@@ -0,0 +1,19 @@
+import { useRecoilValue } from "recoil"
+import { coresListSelector } from "../../../recoil/selectors"
+import { InstalledCoreInfo } from "./installed"
+import { NotInstalledCoreInfo } from "./notInstalled"
+
+type CoreInfoProps = {
+ coreName: string
+ onBack: () => void
+}
+
+export const CoreInfo = (props: CoreInfoProps) => {
+ const coresList = useRecoilValue(coresListSelector)
+
+ if (coresList.includes(props.coreName)) {
+ return
+ } else {
+ return
+ }
+}
diff --git a/src/components/cores/info/installOptions/index.css b/src/components/cores/info/installOptions/index.css
new file mode 100644
index 00000000..489a0c64
--- /dev/null
+++ b/src/components/cores/info/installOptions/index.css
@@ -0,0 +1,49 @@
+.install-options__paths{
+ flex-grow: 1;
+ overflow: auto;
+ background-color: var(--info-colour);
+ border-radius: 10px;
+ box-sizing: border-box;
+ padding: 10px;
+}
+
+.install-options__controls {
+ display: flex;
+ justify-content: space-around;
+}
+
+.install-options__tree-node{
+ margin-left: 20px;
+ display: flex;
+ flex-direction: column;
+}
+
+.install-options__tree-node-info{
+ display: flex;
+ gap: 5px;
+ align-items: center;
+}
+
+.install-options__progress{
+ width: 100%;
+ accent-color: unset;
+}
+
+.install-options__tree-node-name{
+ display: flex;
+ gap: inherit;
+ cursor: pointer;
+ align-items: center;
+}
+
+.install-options__tree-arrow{
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ background-color: white;
+ clip-path: polygon(0% 0%, 50% 100%, 100% 0%);
+}
+
+.install-options__tree-arrow--collapsed {
+ transform: rotate(-90deg);
+}
diff --git a/src/components/cores/info/installOptions/index.tsx b/src/components/cores/info/installOptions/index.tsx
new file mode 100644
index 00000000..93536c97
--- /dev/null
+++ b/src/components/cores/info/installOptions/index.tsx
@@ -0,0 +1,187 @@
+import { emit, listen } from "@tauri-apps/api/event"
+import { useCallback, useEffect, useMemo, useState } from "react"
+import { InstallDetails } from "../../../../types"
+import { Modal } from "../../../modal"
+
+import "./index.css"
+
+type InstallOptionsProps = {
+ details: InstallDetails
+}
+
+export const InstallOptions = ({ details }: InstallOptionsProps) => {
+ const [allowedFiles, setAllowedFiles] = useState(
+ details.files.map(({ path }) => path)
+ )
+
+ const toggleFile = useCallback(
+ (path: string) => {
+ setAllowedFiles((f) => {
+ if (f.includes(path)) {
+ return f.filter((p) => p !== path)
+ } else {
+ return [...f, path]
+ }
+ })
+ },
+ [setAllowedFiles]
+ )
+
+ const [progress, setProgress] = useState<{
+ max: number
+ value: number
+ } | null>(null)
+
+ useEffect(() => {
+ const unlisten = listen<{ max: number; value: number }>(
+ "core-progress",
+ ({ payload }) => {
+ setProgress(payload)
+ }
+ )
+
+ return () => {
+ unlisten.then((l) => l())
+ }
+ }, [])
+
+ const confirm = useCallback(() => {
+ emit("install-confirmation", {
+ paths: allowedFiles,
+ allow: true,
+ })
+ }, [allowedFiles])
+
+ const cancel = useCallback(() => {
+ emit("install-confirmation", {
+ paths: [],
+ allow: false,
+ })
+ }, [])
+
+ const tree = useMemo(() => {
+ const sortedFiles = [...details.files].sort(
+ ({ path: pathA }, { path: pathB }) => pathA.localeCompare(pathB)
+ )
+
+ return sortedFiles.reduce((prev, curr) => {
+ const newTree: FileTreeNode[] = [...prev]
+ const fileBits = curr.path.split("/")
+ let treeNode = newTree
+
+ for (let index = 0; index < fileBits.length; index++) {
+ const element = fileBits[index]
+ if (element === "") continue
+ if (!treeNode.find(({ name }) => name === element)) {
+ treeNode.push({
+ name: element,
+ full: curr.path,
+ exists: curr.exists,
+ is_dir: index !== fileBits.length - 1,
+ children: [],
+ })
+ }
+
+ treeNode = treeNode.find(({ name }) => name === element)?.children || []
+ }
+
+ return newTree
+ }, [] as FileTreeNode[])
+ }, [details.files])
+
+ return (
+
+ {"Install Core"}
+ {!progress && (
+
+ {tree.map((node) => (
+
+ ))}
+
+ )}
+
+ {progress && (
+ <>
+ {`${((progress.value / progress.max) * 100).toFixed(0)}%`}
+
+ >
+ )}
+
+ {!progress && (
+
+ Cancel
+ Confirm
+
+ )}
+
+ )
+}
+
+type FileTreeNode = {
+ name: string
+ full: string
+ exists: boolean
+ is_dir: boolean
+ children: FileTreeNode[]
+}
+
+const TreeNode = ({
+ node,
+ allowed,
+ defaultExpanded = false,
+ toggleFile,
+}: {
+ node: FileTreeNode
+ defaultExpanded?: boolean
+ allowed: string[]
+ toggleFile: (path: string) => void
+}) => {
+ const [expanded, setExpanded] = useState(defaultExpanded)
+
+ return (
+
+
+ {!node.is_dir && (
+
toggleFile(node.full)}
+ >
+ )}
+
setExpanded((e) => !e)}
+ >
+ {node.is_dir && (
+
+ )}
+ {node.name}
+ {node.is_dir ? "/" : ""}
+
+
+
+ {expanded &&
+ node.children.map((n) => (
+
+ ))}
+
+ )
+}
diff --git a/src/components/cores/info/installed.tsx b/src/components/cores/info/installed.tsx
new file mode 100644
index 00000000..c4aa1f3a
--- /dev/null
+++ b/src/components/cores/info/installed.tsx
@@ -0,0 +1,192 @@
+import { platform } from "os"
+import { useRecoilValue } from "recoil"
+import {
+ CoreAuthorImageSelectorFamily,
+ CoreInfoSelectorFamily,
+} from "../../../recoil/selectors"
+import {
+ CoreInventorySelector,
+ DownloadURLSelectorFamily,
+} from "../../../recoil/inventory/selectors"
+import { Controls } from "../../controls"
+import { Link } from "../../link"
+
+import "./index.css"
+import { PlatformImage } from "../platformImage"
+import { useInventoryItem } from "../../../hooks/useInventoryItem"
+import { Releases } from "./releases"
+import { Version } from "../version"
+import { useUninstallCore } from "../../../hooks/useUninstallCore"
+import { useInstallCore } from "../../../hooks/useInstallCore"
+import { ReactNode, Suspense, useState } from "react"
+import { CorePlatformInfo } from "./platform"
+import { Loader } from "../../loader"
+import { SponsorLinks } from "./sponsorLinks"
+import { RequiredFiles } from "./requiredFiles"
+import { LoadRequiredFiles } from "./loadRequiredFiles"
+
+type CoreInfoProps = {
+ coreName: string
+ onBack: () => void
+}
+
+export const InstalledCoreInfo = ({ coreName, onBack }: CoreInfoProps) => {
+ const coreInfo = useRecoilValue(CoreInfoSelectorFamily(coreName))
+ const authorImageSrc = useRecoilValue(CoreAuthorImageSelectorFamily(coreName))
+ const uninstall = useUninstallCore()
+ const { installCore } = useInstallCore()
+ const inventoryItem = useInventoryItem(coreName)
+ const downloadUrl = useRecoilValue(DownloadURLSelectorFamily(coreName))
+
+ const [requiredFilesOpen, setRequiredFilesOpen] = useState(false)
+
+ return (
+
+
uninstall(coreName),
+ },
+ {
+ type: "button",
+ text: "Required Files",
+ onClick: () => setRequiredFilesOpen(true),
+ },
+ downloadUrl && {
+ type: "button",
+ text: "Update",
+ onClick: () => installCore(coreName, downloadUrl),
+ },
+ ]}
+ />
+
+ {requiredFilesOpen && (
+ setRequiredFilesOpen(false)}
+ />
+ )}
+
+ {coreInfo.core.metadata.shortname}
+ {coreInfo.core.metadata.platform_ids.map((platformId) => (
+
+ ))}
+
+
+ {coreInfo.core.metadata.description}
+
+
+
+ {"Version:"}
+
+
+
+
+
{"Author:"}
+
+
+ {coreInfo.core.metadata.author}
+
+
+
+ {inventoryItem?.sponsor && (
+
+ {"Sponsor:"}
+
+
+ )}
+
+ {coreInfo.core.metadata.url && (
+
+ {"URL:"}
+
+
+ {coreInfo.core.metadata.url}
+
+
+ )}
+
+
+
+ {coreInfo.core.metadata.date_release && (
+
+ {"Release Date:"}
+
+ {coreInfo.core.metadata.date_release}
+
+ )}
+
+
+
+
{"Supports:"}
+
+
+ {"Save States / Sleep"}
+
+
+
+ {"Dock"}
+
+
+
+ {"Dock (Analog)"}
+
+
+
+ {"Cartridges"}
+
+
+
+
+
+
{"Platforms:"}
+
}
+ >
+
+ {coreInfo.core.metadata.platform_ids.map((id) => (
+
+ ))}
+
+
+
+ {inventoryItem && inventoryItem.repository.platform === "github" && (
+
+ )}
+
+
+ )
+}
+
+type SupportsBubbleProps = {
+ children: ReactNode
+ supports: boolean
+}
+
+const SupportsBubble = ({ supports, children }: SupportsBubbleProps) => (
+
+ {children}
+
+)
diff --git a/src/components/cores/info/loadRequiredFiles/index.css b/src/components/cores/info/loadRequiredFiles/index.css
new file mode 100644
index 00000000..f5f83e13
--- /dev/null
+++ b/src/components/cores/info/loadRequiredFiles/index.css
@@ -0,0 +1,40 @@
+.load-required-files{
+ width: max-content;
+ height: max-content;
+ max-height: 80vh;
+
+ min-width: 50vw;
+ min-height: 50vw;
+}
+
+.load-required-files__files {
+ background-color: var(--info-colour);
+ padding: 10px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ border-radius: 10px;
+ overflow: auto;
+}
+
+.load-required-files__row{
+ display: flex;
+
+ align-items: center;
+ gap: 0.5em;
+
+ & > pre {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+}
+
+.load-required-files__row--exists{
+ opacity: 0.5;
+}
+
+.load-required-files__buttons{
+ display: flex;
+ justify-content: space-between;
+ gap: 20px;
+}
diff --git a/src/components/cores/info/loadRequiredFiles/index.tsx b/src/components/cores/info/loadRequiredFiles/index.tsx
new file mode 100644
index 00000000..b03c957d
--- /dev/null
+++ b/src/components/cores/info/loadRequiredFiles/index.tsx
@@ -0,0 +1,81 @@
+import { useRecoilValue } from "recoil"
+import { useInstallRequiredFiles } from "../../../../hooks/useInstallRequiredFiles"
+import {
+ PocketSyncConfigSelector,
+ RequiredFileInfoSelectorFamily,
+} from "../../../../recoil/selectors"
+import { Modal } from "../../../modal"
+import { Progress } from "../../../progress"
+
+import "./index.css"
+
+type LoadRequiredFilesProps = {
+ coreName: string
+ onClose: () => void
+}
+
+export const LoadRequiredFiles = ({
+ coreName,
+ onClose,
+}: LoadRequiredFilesProps) => {
+ const requiredFiles = useRecoilValue(RequiredFileInfoSelectorFamily(coreName))
+ const pocketSyncConfig = useRecoilValue(PocketSyncConfigSelector)
+ const { installRequiredFiles, progress } = useInstallRequiredFiles(onClose)
+
+ return (
+
+ {"Required Files"}
+
+ {progress && }
+
+ {!progress && (
+ <>
+
+ {requiredFiles.map((r) => {
+ return (
+
+ Put
{`"${r.filename}"`} in{" "}
+
{`"${r.path}"`}
+
+ )
+ })}
+
+
+ {!pocketSyncConfig.archive_url && (
+
+ {
+ "Please view the `Settings` pane for more options with required files"
+ }
+
+ )}
+
+ {"Cancel"}
+
+ {pocketSyncConfig.archive_url && (
+ installRequiredFiles(requiredFiles)}>
+ {"Download All"}
+
+ )}
+
+ {pocketSyncConfig.archive_url && (
+
+ installRequiredFiles(
+ requiredFiles.filter(({ exists }) => !exists)
+ )
+ }
+ >
+ {"Download Missing"}
+
+ )}
+
+ >
+ )}
+
+ )
+}
diff --git a/src/components/cores/info/notInstalled.tsx b/src/components/cores/info/notInstalled.tsx
new file mode 100644
index 00000000..9d92a649
--- /dev/null
+++ b/src/components/cores/info/notInstalled.tsx
@@ -0,0 +1,73 @@
+import { useMemo } from "react"
+import { useRecoilValue } from "recoil"
+import { useInstallCore } from "../../../hooks/useInstallCore"
+import { useInventoryItem } from "../../../hooks/useInventoryItem"
+import { DownloadURLSelectorFamily } from "../../../recoil/inventory/selectors"
+import { Controls } from "../../controls"
+import { Link } from "../../link"
+import { Releases } from "./releases"
+
+type NotInstalledCoreInfoProps = {
+ onBack: () => void
+ coreName: string
+}
+
+export const NotInstalledCoreInfo = ({
+ coreName,
+ onBack,
+}: NotInstalledCoreInfoProps) => {
+ const inventoryItem = useInventoryItem(coreName)
+
+ const url = useMemo(() => {
+ if (inventoryItem?.repository.platform !== "github") return null
+ return `https://github.com/${inventoryItem.repository.owner}/${inventoryItem.repository.name}`
+ }, [inventoryItem])
+
+ const download_url = useRecoilValue(DownloadURLSelectorFamily(coreName))
+ const { installCore } = useInstallCore()
+
+ return (
+
+
{
+ installCore(coreName, download_url)
+ },
+ },
+ ]}
+ />
+
+ {!inventoryItem && {`${coreName} not in cores inventory`}
}
+
+ {inventoryItem && (
+ <>
+ {inventoryItem.platform}
+
+
+ {inventoryItem.identifier}
+
+
+ {url && (
+
+ {"URL:"}
+ {url}
+
+ )}
+
+ {inventoryItem.repository.platform === "github" && (
+
+ )}
+
+ >
+ )}
+
+ )
+}
diff --git a/src/components/cores/info/platform/index.css b/src/components/cores/info/platform/index.css
new file mode 100644
index 00000000..8bb004da
--- /dev/null
+++ b/src/components/cores/info/platform/index.css
@@ -0,0 +1,33 @@
+.cores__platform-info{
+ border-radius: 12px;
+ background-color: var(--background-colour);
+ display: flex;
+ grid-template-columns: max-content min-content;
+ gap: 15px;
+ overflow: hidden;
+
+ & > *{
+ padding: 10px 0;
+ }
+
+ & > *:first-child{
+ padding-left: 15px;
+ }
+
+ & > *:last-child{
+ padding-right: 15px;
+ }
+}
+
+.cores__platform-info-name{
+ font-size: 1.25rem;
+}
+
+.cores__platform-info-cateogry {
+ border-right: 1px solid white;
+ padding-right: 15px;
+ box-sizing: border-box;
+ background-color: var(--light-colour);
+ display: flex;
+ align-items: center;
+}
diff --git a/src/components/cores/info/platform/index.tsx b/src/components/cores/info/platform/index.tsx
new file mode 100644
index 00000000..8834ad5c
--- /dev/null
+++ b/src/components/cores/info/platform/index.tsx
@@ -0,0 +1,39 @@
+import { useMemo } from "react"
+import { useRecoilValue } from "recoil"
+import { PlatformInfoSelectorFamily } from "../../../../recoil/selectors"
+import { PlatformId } from "../../../../types"
+import { Link } from "../../../link"
+
+import "./index.css"
+
+const HARD_TO_FIND_THINGS = ["Genesis", "Dominos", "Green Beret"]
+
+type CorePlatformInfoProps = {
+ platformId: PlatformId
+}
+
+export const CorePlatformInfo = ({ platformId }: CorePlatformInfoProps) => {
+ const { platform } = useRecoilValue(PlatformInfoSelectorFamily(platformId))
+
+ const wikiLink = useMemo(() => {
+ const searchTerm = HARD_TO_FIND_THINGS.includes(platform.name)
+ ? `${platform.manufacturer} ${platform.name}`
+ : platform.name
+
+ return `https://en.wikipedia.org/wiki/Special:Search?go=Go&search=${window.encodeURIComponent(
+ searchTerm
+ )}`
+ }, [platform])
+
+ return (
+
+
{platform.category}
+
{platform.name}
+
+
{`${platform.manufacturer}, ${platform.year}`}
+
+ {"Wikipedia"}
+
+
+ )
+}
diff --git a/src/components/cores/info/releases.css b/src/components/cores/info/releases.css
new file mode 100644
index 00000000..f96944a3
--- /dev/null
+++ b/src/components/cores/info/releases.css
@@ -0,0 +1,48 @@
+.releases__title{
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px;
+ font-weight: bold;
+ font-size: 1.25rem;
+}
+
+
+.releases__body{
+ border-left: 2px solid white;
+ padding-left: 10px;
+ box-sizing: border-box;
+ margin-left: 10px;
+}
+
+
+.releases__body--collapsed {
+ max-height: 110px;
+ overflow: hidden;
+ position: relative;
+ cursor: pointer;
+
+ &::after{
+ content: "";
+ position: absolute;
+ display: block;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+
+ background: linear-gradient(transparent, var(--info-colour));
+ }
+
+ & * {
+ pointer-events: none;
+ }
+}
+
+.releases__more-button{
+ cursor: pointer;
+ font-size: 1.25rem;
+ padding: 10px;
+ &:hover{
+ text-decoration: underline;
+ }
+}
diff --git a/src/components/cores/info/releases.tsx b/src/components/cores/info/releases.tsx
new file mode 100644
index 00000000..e68c8348
--- /dev/null
+++ b/src/components/cores/info/releases.tsx
@@ -0,0 +1,89 @@
+import { useRecoilValue } from "recoil"
+import { InventoryItem } from "../../../types"
+import ReactMarkdown from "react-markdown"
+import remarkGfm from "remark-gfm"
+import { useState } from "react"
+import { Link } from "../../link"
+import { GithubReleasesSelectorFamily } from "../../../recoil/github/selectors"
+
+import "./releases.css"
+
+type ReleasesProps = {
+ inventoryItem: InventoryItem
+}
+
+const INITAL_COUNT = 1
+
+export const Releases = ({ inventoryItem }: ReleasesProps) => {
+ if (inventoryItem.repository.platform !== "github") {
+ throw new Error("Can't show non-github releases")
+ }
+
+ const [showAll, setShowAll] = useState(false)
+
+ const githubReleases = useRecoilValue(
+ GithubReleasesSelectorFamily({
+ owner: inventoryItem.repository.owner,
+ repo: inventoryItem.repository.name,
+ })
+ )
+
+ return (
+
+
Release History:
+ {githubReleases
+ .slice(0, showAll ? Infinity : INITAL_COUNT)
+ .map(({ id, name, body, tag_name, created_at }) => (
+
+
+ {tag_name} - {name}
+
{new Date(created_at).toLocaleString()}
+
+
+
+
+ ))}
+
+ {!showAll && githubReleases.length > INITAL_COUNT && (
+
setShowAll(true)}
+ >
+ {"Show previous releases"}
+
+ )}
+
+ )
+}
+
+type ReleaseBodyProps = {
+ body: string
+}
+
+const ReleaseBody = ({ body }: ReleaseBodyProps) => {
+ const [collapsed, setCollapsed] = useState(true)
+
+ return (
+ setCollapsed(false)}
+ >
+ {children},
+ }}
+ >
+ {body}
+
+
+ )
+}
diff --git a/src/components/cores/info/requiredFiles/index.css b/src/components/cores/info/requiredFiles/index.css
new file mode 100644
index 00000000..67f91d03
--- /dev/null
+++ b/src/components/cores/info/requiredFiles/index.css
@@ -0,0 +1,10 @@
+.required-files{
+ white-space: nowrap;
+ background-color: var(--light-colour);
+ padding: 0 5px;
+ border-radius: 5px;
+}
+
+.required-files--missing {
+ background-color: var(--red-colour);
+}
diff --git a/src/components/cores/info/requiredFiles/index.tsx b/src/components/cores/info/requiredFiles/index.tsx
new file mode 100644
index 00000000..78d6fe90
--- /dev/null
+++ b/src/components/cores/info/requiredFiles/index.tsx
@@ -0,0 +1,46 @@
+import { useMemo } from "react"
+import { useRecoilValue } from "recoil"
+import { RequiredFileInfoSelectorFamily } from "../../../../recoil/selectors"
+
+import "./index.css"
+
+type RequiredFilesProps = {
+ coreName: string
+ ignoreInstance?: boolean
+}
+
+export const RequiredFiles = ({
+ coreName,
+ ignoreInstance,
+}: RequiredFilesProps) => {
+ const requiredFiles = useRecoilValue(RequiredFileInfoSelectorFamily(coreName))
+
+ const filteredRequiredFiles = useMemo(() => {
+ if (!ignoreInstance) return requiredFiles
+
+ return requiredFiles.filter(({ type }) => type !== "instance")
+ }, [requiredFiles])
+
+ const foundFiles = useMemo(
+ () => filteredRequiredFiles.filter(({ exists }) => exists),
+ [filteredRequiredFiles]
+ )
+
+ const full = useMemo(
+ () => filteredRequiredFiles.every(({ exists }) => exists),
+ [filteredRequiredFiles]
+ )
+
+ if (filteredRequiredFiles.length === 0) return null
+
+ return (
+
+
{"Required Files:"}
+
{`${foundFiles.length} / ${filteredRequiredFiles.length} Files`}
+
+ )
+}
diff --git a/src/components/cores/info/sponsorLinks/index.css b/src/components/cores/info/sponsorLinks/index.css
new file mode 100644
index 00000000..0a307691
--- /dev/null
+++ b/src/components/cores/info/sponsorLinks/index.css
@@ -0,0 +1,9 @@
+.sponsor-links{
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.sponsor-links__link{
+ word-break: break-all;
+}
diff --git a/src/components/cores/info/sponsorLinks/index.tsx b/src/components/cores/info/sponsorLinks/index.tsx
new file mode 100644
index 00000000..2e43547c
--- /dev/null
+++ b/src/components/cores/info/sponsorLinks/index.tsx
@@ -0,0 +1,27 @@
+import { useMemo } from "react"
+import { InventoryItem } from "../../../../types"
+import { Link } from "../../../link"
+
+import "./index.css"
+
+type SponsorLinkProps = {
+ links: Required["sponsor"]
+}
+
+export const SponsorLinks = ({ links }: SponsorLinkProps) => {
+ const sponsorTypes = useMemo(() => Object.keys(links || {}), [links])
+
+ return (
+
+ {sponsorTypes.map((sponsorKey) => (
+
+ {links[sponsorKey]}
+
+ ))}
+
+ )
+}
diff --git a/src/components/cores/item.tsx b/src/components/cores/item.tsx
new file mode 100644
index 00000000..c77d415e
--- /dev/null
+++ b/src/components/cores/item.tsx
@@ -0,0 +1,44 @@
+import { useRecoilValue } from "recoil"
+import {
+ CoreAuthorImageSelectorFamily,
+ CoreInfoSelectorFamily,
+} from "../../recoil/selectors"
+import { PlatformImage } from "./platformImage"
+
+import "./index.css"
+import { Version } from "./version"
+
+type CoreItemProps = {
+ coreName: string
+ onClick: () => void
+}
+
+export const CoreItem = ({ coreName, onClick }: CoreItemProps) => {
+ const coreInfo = useRecoilValue(CoreInfoSelectorFamily(coreName))
+ const imageSrc = useRecoilValue(CoreAuthorImageSelectorFamily(coreName))
+
+ return (
+
+ {coreInfo.core.metadata.platform_ids.map((platformId) => (
+
+ ))}
+
+
+
+ {coreInfo.core.metadata.shortname}
+
+
+
+
+
+
+ {coreInfo.core.metadata.author}
+
+
+
+ )
+}
diff --git a/src/components/cores/platformImage.tsx b/src/components/cores/platformImage.tsx
new file mode 100644
index 00000000..471f258f
--- /dev/null
+++ b/src/components/cores/platformImage.tsx
@@ -0,0 +1,15 @@
+import { useRecoilValue } from "recoil"
+import { PlatformImageSelectorFamily } from "../../recoil/selectors"
+import { PlatformId } from "../../types"
+
+type PlatformImageProps = {
+ platformId: PlatformId
+ className?: string
+}
+export const PlatformImage = ({
+ platformId,
+ className,
+}: PlatformImageProps) => {
+ const imageSrc = useRecoilValue(PlatformImageSelectorFamily(platformId))
+ return
+}
diff --git a/src/components/cores/version/index.css b/src/components/cores/version/index.css
new file mode 100644
index 00000000..3bfcc84a
--- /dev/null
+++ b/src/components/cores/version/index.css
@@ -0,0 +1,12 @@
+.version {
+ display: flex;
+ gap: 5px;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.version__update{
+ background-color: var(--green-colour);
+ padding: 0px 10px;
+ border-radius: 10px;
+}
diff --git a/src/components/cores/version/index.tsx b/src/components/cores/version/index.tsx
new file mode 100644
index 00000000..cbb8f366
--- /dev/null
+++ b/src/components/cores/version/index.tsx
@@ -0,0 +1,45 @@
+import { useMemo } from "react"
+import { useRecoilValue } from "recoil"
+import { CoreInfoSelectorFamily } from "../../../recoil/selectors"
+import { CoreInventorySelector } from "../../../recoil/inventory/selectors"
+
+import "./index.css"
+
+type VersionProps = {
+ coreName: string
+}
+
+export const Version = ({ coreName }: VersionProps) => {
+ const coreInfo = useRecoilValue(CoreInfoSelectorFamily(coreName))
+ const coreInventory = useRecoilValue(CoreInventorySelector)
+
+ const updateAvailable = useMemo(() => {
+ // this isn't very good yet
+ const inventoryCore = coreInventory.data.find(
+ ({ identifier }) => identifier === coreName
+ )
+
+ if (!inventoryCore?.release) return null
+
+ const { tag_name } = inventoryCore.release
+ const metadataVersion = coreInfo.core.metadata.version
+
+ if (tag_name !== metadataVersion) {
+ if (tag_name.includes(metadataVersion)) {
+ return null
+ }
+ return inventoryCore.release.tag_name
+ }
+
+ return null
+ }, [coreInfo.core.metadata.version, coreInventory])
+
+ return (
+
+ {coreInfo.core.metadata.version}
+ {updateAvailable && (
+
{updateAvailable}
+ )}
+
+ )
+}
diff --git a/src/components/disconnections/index.tsx b/src/components/disconnections/index.tsx
new file mode 100644
index 00000000..5e613a3e
--- /dev/null
+++ b/src/components/disconnections/index.tsx
@@ -0,0 +1,31 @@
+import { listen } from "@tauri-apps/api/event"
+import { useEffect } from "react"
+import { useSetRecoilState } from "recoil"
+import { fileSystemInvalidationAtom, pocketPathAtom } from "../../recoil/atoms"
+
+export const Disconnections = () => {
+ const setPocketPath = useSetRecoilState(pocketPathAtom)
+ const setFileSystemInvalidation = useSetRecoilState(
+ fileSystemInvalidationAtom
+ )
+
+ useEffect(() => {
+ const unlisten = listen<{ connected: boolean }>(
+ "pocket-connection",
+ ({ payload }) => {
+ if (!payload.connected) {
+ setPocketPath(null)
+ setTimeout(() => {
+ setFileSystemInvalidation(Date.now())
+ }, 50)
+ }
+ }
+ )
+
+ return () => {
+ unlisten.then((l) => l())
+ }
+ }, [])
+
+ return null
+}
diff --git a/src/components/games/gameCount.tsx b/src/components/games/gameCount.tsx
new file mode 100644
index 00000000..dc907792
--- /dev/null
+++ b/src/components/games/gameCount.tsx
@@ -0,0 +1,16 @@
+import { useRecoilValue } from "recoil"
+import { FileCountSelectorFamily } from "../../recoil/selectors"
+
+export const GameCount = ({
+ platformId,
+ extensions,
+}: {
+ platformId: string
+ extensions: string[]
+}) => {
+ const count = useRecoilValue(
+ FileCountSelectorFamily({ path: `Assets/${platformId}`, extensions })
+ )
+
+ return {`${count.toLocaleString()} Games`}
+}
diff --git a/src/components/games/index.tsx b/src/components/games/index.tsx
new file mode 100644
index 00000000..c20934a5
--- /dev/null
+++ b/src/components/games/index.tsx
@@ -0,0 +1,84 @@
+import { Suspense, useMemo, useState } from "react"
+import { useRecoilCallback, useRecoilValue } from "recoil"
+import { useCategoryLookup } from "../../hooks/useCategoryLookup"
+import { useSaveScroll } from "../../hooks/useSaveScroll"
+import {
+ fileSystemInvalidationAtom,
+ inventoryInvalidationAtom,
+} from "../../recoil/atoms"
+import { CateogryListselector } from "../../recoil/inventory/selectors"
+import { coresListSelector } from "../../recoil/selectors"
+import { Controls } from "../controls"
+import { Grid } from "../grid"
+import { Loader } from "../loader"
+import { CoreFolderItem } from "./item"
+
+export const Games = () => {
+ const coresList = useRecoilValue(coresListSelector)
+ const [searchQuery, setSearchQuery] = useState("")
+ const [filterCategory, setFilterCategory] = useState("All")
+ const lookupCategory = useCategoryLookup()
+
+ const refresh = useRecoilCallback(({ set }) => () => {
+ set(inventoryInvalidationAtom, Date.now())
+ set(fileSystemInvalidationAtom, Date.now())
+ })
+
+ const sortedList = useMemo(
+ () =>
+ [...coresList]
+ .sort((a, b) => {
+ const [authorA, coreA] = a.split(".")
+ const switchedA = `${coreA}.${authorA}`
+
+ const [authorB, coreB] = b.split(".")
+ const switchedB = `${coreB}.${authorB}`
+
+ return switchedA.localeCompare(switchedB)
+ })
+ .filter((core) => {
+ if (filterCategory === "All") return true
+ return lookupCategory(core) === filterCategory
+ })
+ .filter((core) =>
+ core.toLowerCase().includes(searchQuery.toLowerCase())
+ ),
+ [searchQuery, filterCategory, coresList]
+ )
+
+ const categoryList = useRecoilValue(CateogryListselector)
+
+ return (
+
+ setSearchQuery(v),
+ },
+ {
+ type: "button",
+ text: "Refresh",
+ onClick: refresh,
+ },
+ {
+ type: "select",
+ options: categoryList,
+ selected: filterCategory,
+ text: "Category",
+ onChange: (v) => setFilterCategory(v),
+ },
+ ]}
+ />
+
+ {sortedList.map((core) => (
+ } key={core}>
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/games/item.tsx b/src/components/games/item.tsx
new file mode 100644
index 00000000..ed217475
--- /dev/null
+++ b/src/components/games/item.tsx
@@ -0,0 +1,62 @@
+import { useMemo } from "react"
+import { useRecoilValue } from "recoil"
+import { pocketPathAtom } from "../../recoil/atoms"
+import {
+ CoreInfoSelectorFamily,
+ DataJSONSelectorFamily,
+} from "../../recoil/selectors"
+import { decodeDataParams } from "../../utils/decodeDataParams"
+import { PlatformImage } from "../cores/platformImage"
+import { Link } from "../link"
+import { GameCount } from "./gameCount"
+
+export const CoreFolderItem = ({ coreName }: { coreName: string }) => {
+ const info = useRecoilValue(CoreInfoSelectorFamily(coreName))
+ const data = useRecoilValue(DataJSONSelectorFamily(coreName))
+ const pocketPath = useRecoilValue(pocketPathAtom)
+
+ const romsSlot = useMemo(
+ () =>
+ data.data.data_slots
+ .filter(({ required, extensions }) => required && extensions)
+ .at(0),
+ [data]
+ )
+
+ const platformIds = useMemo(() => info.core.metadata.platform_ids, [info])
+
+ const paths = useMemo(() => {
+ if (!romsSlot || !platformIds) return []
+ const coreSpecific = decodeDataParams(romsSlot.parameters)?.coreSpecific
+
+ return platformIds.map(
+ (pId) =>
+ `${pocketPath}/Assets/${pId}/${coreSpecific ? coreName : "common"}`
+ )
+ }, [romsSlot, platformIds])
+
+ if (paths.length === 0) return null
+
+ console.log({ paths })
+
+ return (
+ <>
+ {info.core.metadata.platform_ids.map((platformId, index) => (
+
+
+
+ {coreName}
+
+
+
+ ))}
+ >
+ )
+}
diff --git a/src/components/grid/index.css b/src/components/grid/index.css
new file mode 100644
index 00000000..a7ea3ccb
--- /dev/null
+++ b/src/components/grid/index.css
@@ -0,0 +1,22 @@
+.grid {
+ display: grid;
+ grid-auto-flow: row;
+ grid-template-columns: repeat(2, minmax(100px, 1fr));
+ gap: 15px;
+ padding: 10px;
+ align-items: stretch;
+ justify-items: stretch;
+ grid-template-rows: repeat(auto-fill, minmax(100px, 1fr));
+
+ @media (min-width: 640px) {
+ grid-template-columns: repeat(3, minmax(100px, 1fr));
+ }
+
+ @media (min-width: 860px) {
+ grid-template-columns: repeat(4, minmax(100px, 1fr));
+ }
+
+ @media (min-width: 1200px) {
+ grid-template-columns: repeat(5, minmax(100px, 1fr));
+ }
+}
diff --git a/src/components/grid/index.tsx b/src/components/grid/index.tsx
new file mode 100644
index 00000000..bdcdb6a9
--- /dev/null
+++ b/src/components/grid/index.tsx
@@ -0,0 +1,11 @@
+import { ReactNode } from "react"
+import "./index.css"
+
+type GridProps = {
+ children: ReactNode
+ className?: string
+}
+
+export const Grid = ({ children, className }: GridProps) => {
+ return {children}
+}
diff --git a/src/components/layout/index.css b/src/components/layout/index.css
new file mode 100644
index 00000000..87ab0b89
--- /dev/null
+++ b/src/components/layout/index.css
@@ -0,0 +1,40 @@
+.layout{
+ display: flex;
+ align-items: flex-start;
+}
+
+.layout__sidebar-menu{
+ display: flex;
+ flex-direction: column;
+ position: sticky;
+ top: 0px;
+ height: 100vh;
+ background-color: var(--light-colour);
+ flex-basis: max-content;
+ flex-shrink: 0;
+ z-index: 100;
+}
+
+.layout__sidebar-menu-item{
+ padding: 20px;
+ font-size: 1.5rem;
+ cursor: pointer;
+ -webkit-user-select: none;
+ user-select: none;
+
+ &:hover{
+ background-color: var(--hover-colour);
+ }
+}
+
+.layout__sidebar-menu-item--active{
+ background-color: var(--background-colour);
+}
+
+.layout__sidebar-menu-item--disabled{
+ cursor: unset;
+}
+
+.layout__content {
+ flex-grow: 1;
+}
diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx
new file mode 100644
index 00000000..f45736af
--- /dev/null
+++ b/src/components/layout/index.tsx
@@ -0,0 +1,53 @@
+import React, { Suspense, useState } from "react"
+import { About } from "../about"
+import { Cores } from "../cores"
+import { Disconnections } from "../disconnections"
+import { Games } from "../games"
+import { Loader } from "../loader"
+import { Saves } from "../saves"
+import { Screenshots } from "../screenshots"
+import { Settings } from "../settings"
+import { ZipInstall } from "../zipInstall"
+import "./index.css"
+
+export const Layout = () => {
+ const views = [
+ "Pocket Sync",
+ "Games",
+ "Cores",
+ "Screenshots",
+ "Saves",
+ "Settings",
+ ] as const
+ const [viewName, setViewName] = useState("Pocket Sync")
+
+ return (
+
+
+
+
+ {views.map((v) => (
+
setViewName(v)}
+ >
+ {v}
+
+ ))}
+
+
+
}>
+ {viewName === "Screenshots" &&
}
+ {viewName === "Cores" &&
}
+ {viewName === "Pocket Sync" &&
}
+ {viewName === "Settings" &&
}
+ {viewName === "Games" &&
}
+ {viewName === "Saves" &&
}
+
+
+
+ )
+}
diff --git a/src/components/link/index.css b/src/components/link/index.css
new file mode 100644
index 00000000..a64ab90f
--- /dev/null
+++ b/src/components/link/index.css
@@ -0,0 +1,9 @@
+.link{
+ text-decoration: underline;
+ cursor: pointer;
+ display: inline-block;
+
+ &:hover {
+ opacity: 0.8;
+ }
+}
diff --git a/src/components/link/index.tsx b/src/components/link/index.tsx
new file mode 100644
index 00000000..109cb4a8
--- /dev/null
+++ b/src/components/link/index.tsx
@@ -0,0 +1,23 @@
+import { ReactNode } from "react"
+import { open } from "@tauri-apps/api/shell"
+
+import "./index.css"
+
+type LinkProps = {
+ href?: string
+ className?: string
+ children: ReactNode
+}
+
+export const Link = ({ children, className, href }: LinkProps) => {
+ return (
+ {
+ if (href) open(window.encodeURI(href))
+ }}
+ >
+ {children}
+
+ )
+}
diff --git a/src/components/loader/index.css b/src/components/loader/index.css
new file mode 100644
index 00000000..7143ded3
--- /dev/null
+++ b/src/components/loader/index.css
@@ -0,0 +1,30 @@
+.loader {
+ height: 100%;
+ width: 100%;
+ background-color: #333;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.loader--full{
+ height: 100vh;
+}
+
+.loader::before{
+ content: "";
+ display: block;
+ border: 2px solid white;
+ border-radius: 50%;
+ border-left-color: transparent;
+ width: 25%;
+ aspect-ratio: 1 / 1;
+
+ animation: spin 2s infinite linear;
+ will-change: transform;
+}
+
+@keyframes spin {
+ from {transform:rotate(0deg);}
+ to {transform:rotate(360deg);}
+}
diff --git a/src/components/loader/index.tsx b/src/components/loader/index.tsx
new file mode 100644
index 00000000..d8ad5378
--- /dev/null
+++ b/src/components/loader/index.tsx
@@ -0,0 +1,15 @@
+import React from "react"
+import "./index.css"
+
+type LoaderProps = {
+ className?: string
+ fullHeight?: boolean
+ title?: string
+}
+
+export const Loader = ({ className, fullHeight, title = "" }: LoaderProps) => (
+
+)
diff --git a/src/components/modal/index.css b/src/components/modal/index.css
new file mode 100644
index 00000000..e8e0224f
--- /dev/null
+++ b/src/components/modal/index.css
@@ -0,0 +1,27 @@
+.modal__wrapper{
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background-color: rgba(0,0,0,0.8);
+ z-index: 1000;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.modal{
+ width: calc(100vw - 100px);
+ height: calc(100vh - 100px);
+ background-color: var(--background-colour);
+ border-radius: 10px;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ box-sizing: border-box;
+ padding: 20px;
+ gap: 10px;
+}
diff --git a/src/components/modal/index.tsx b/src/components/modal/index.tsx
new file mode 100644
index 00000000..2e40cbdd
--- /dev/null
+++ b/src/components/modal/index.tsx
@@ -0,0 +1,16 @@
+import { ReactNode } from "react"
+
+import "./index.css"
+
+type ModalProps = {
+ children: ReactNode
+ className?: string
+}
+
+export const Modal = ({ children, className }: ModalProps) => {
+ return (
+
+ )
+}
diff --git a/src/components/progress/index.tsx b/src/components/progress/index.tsx
new file mode 100644
index 00000000..ef528988
--- /dev/null
+++ b/src/components/progress/index.tsx
@@ -0,0 +1,14 @@
+import { Pocket } from "../three/pocket"
+import { ProgressScreen } from "../three/progressScreen"
+
+type ProgressProps = {
+ value: number
+ max: number
+}
+
+export const Progress = ({ value, max }: ProgressProps) => (
+ }
+ />
+)
diff --git a/src/components/saves/hooks/useBuildsaveZip.ts b/src/components/saves/hooks/useBuildsaveZip.ts
new file mode 100644
index 00000000..aa3d3c2e
--- /dev/null
+++ b/src/components/saves/hooks/useBuildsaveZip.ts
@@ -0,0 +1,18 @@
+import { useCallback } from "react"
+import { useRecoilValue, useSetRecoilState } from "recoil"
+import { fileSystemInvalidationAtom } from "../../../recoil/atoms"
+import { AllSavesSelector } from "../../../recoil/selectors"
+import { invokeBackupSaves } from "../../../utils/invokes"
+
+export const useBuildSaveZip = () => {
+ const allSaves = useRecoilValue(AllSavesSelector)
+ const invalidateFS = useSetRecoilState(fileSystemInvalidationAtom)
+
+ return useCallback(
+ async (savePath: string, maxCount: number) => {
+ await invokeBackupSaves(allSaves, savePath, maxCount)
+ invalidateFS(Date.now())
+ },
+ [allSaves]
+ )
+}
diff --git a/src/components/saves/index.css b/src/components/saves/index.css
new file mode 100644
index 00000000..2774ae8d
--- /dev/null
+++ b/src/components/saves/index.css
@@ -0,0 +1,129 @@
+.saves {
+ box-sizing: border-box;
+}
+
+.saves__none{
+ width: 100%;
+ height: calc(100vh - 60px);
+ font-size: 3rem;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ font-family: "Analogue";
+ font-smooth: never;
+ -webkit-font-smoothing : none;
+ text-align: center;
+ line-height: 45px;
+}
+
+.saves__list{
+ padding: 20px;
+ overflow: auto;
+
+ display: flex;
+ gap: 10px;
+ flex-direction: column;
+}
+
+.saves__item{
+ padding: 20px;
+ border-radius: 15px;
+ background-color: var(--info-colour);
+
+ cursor: pointer;
+
+ display: grid;
+ gap: 20px;
+ grid-template-columns: min-content 1fr min-content;
+ align-items: center;
+ justify-content: center;
+ grid-template-areas:
+ "a b c"
+ "a d c";
+}
+
+.saves__item--not-found{
+ cursor: unset;
+}
+
+.saves__item-path {
+ grid-area: b;
+
+ word-break: break-all;
+}
+
+.saves__info{
+ grid-area: d;
+}
+
+.saves__item-sync-button {
+ grid-area: a;
+ text-align: center;
+
+ background-color: var(--green-colour);
+ padding: 10px;
+ aspect-ratio: 1 / 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+
+ &:hover{
+ transform: scale(1.05);
+ }
+
+}
+
+.saves__item-restore-button {
+ grid-area: c;
+ padding: 10px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ font-size: 1.25rem;
+
+ &::after {
+ content: "";
+ display: block;
+ width: 40px;
+ height: 40px;
+
+ border-right: 2px solid white;
+ border-top: 2px solid white;
+ flex-grow: 0;
+ flex-shrink: 0;
+ transform: rotate(45deg);
+ }
+
+ &:hover{
+ transform: scale(1.1);
+ }
+}
+
+
+.saves__info-save-files{
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.saves__info-save-file-versions {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.saves__info-save-file-version {
+ padding: 5px 10px;
+ background-color: var(--light-colour);
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+}
diff --git a/src/components/saves/index.tsx b/src/components/saves/index.tsx
new file mode 100644
index 00000000..4a9c8103
--- /dev/null
+++ b/src/components/saves/index.tsx
@@ -0,0 +1,74 @@
+import { useCallback, useState } from "react"
+import { useRecoilValue } from "recoil"
+import { PocketSyncConfigSelector } from "../../recoil/selectors"
+import { Controls } from "../controls"
+import { useUpdateConfig } from "../settings/hooks/useUpdateConfig"
+import { open } from "@tauri-apps/api/dialog"
+
+import "./index.css"
+import { SavesItem } from "./item"
+import { SaveInfo } from "./info"
+import { Link } from "../link"
+import { Tip } from "../tip"
+
+export const Saves = () => {
+ const [selectedSaveBackup, setSelectedSavebackup] = useState(
+ null
+ )
+
+ const updateConfig = useUpdateConfig()
+ const { saves } = useRecoilValue(PocketSyncConfigSelector)
+ const addBackupLocation = useCallback(async () => {
+ const directory = await open({
+ directory: true,
+ })
+
+ if (!directory) return
+
+ updateConfig("saves", [
+ ...saves,
+ {
+ type: "zip",
+ backup_location: directory as string,
+ backup_count: 10,
+ },
+ ])
+ }, [saves])
+
+ if (selectedSaveBackup !== null)
+ return (
+ setSelectedSavebackup(null)}
+ />
+ )
+
+ return (
+
+
+
+ {saves.length === 0 && (
+
{"Add a backup location above"}
+ )}
+ {saves.length > 0 && (
+
+ {saves.map((s, index) => (
+ setSelectedSavebackup(index)}
+ />
+ ))}
+
+ )}
+
+ )
+}
diff --git a/src/components/saves/info/index.tsx b/src/components/saves/info/index.tsx
new file mode 100644
index 00000000..50bea592
--- /dev/null
+++ b/src/components/saves/info/index.tsx
@@ -0,0 +1,149 @@
+import { save } from "@tauri-apps/api/dialog"
+import { useCallback, useMemo, useState } from "react"
+import { useRecoilValue } from "recoil"
+import { AllBackupZipsFilesSelectorFamily } from "../../../recoil/selectors"
+import { SaveBackupPathTime } from "../../../types"
+import { invokeRestoreZip } from "../../../utils/invokes"
+import { Controls } from "../../controls"
+import { Link } from "../../link"
+import { Tip } from "../../tip"
+
+export const SaveInfo = ({
+ backupPath,
+ onBack,
+}: {
+ backupPath: string
+ onBack: () => void
+}) => {
+ const [searchQuery, setSearchQuery] = useState("")
+
+ const zipFilesInfo = useRecoilValue(
+ AllBackupZipsFilesSelectorFamily(backupPath)
+ )
+
+ const perSaveFormat = useMemo(() => {
+ const perSave: {
+ [filename: string]: { zip: SaveBackupPathTime; last_modified: number }[]
+ } = {}
+
+ zipFilesInfo.forEach(({ zip, files }) => {
+ files
+ .filter((f) => f.filename.endsWith(".sav"))
+ .forEach(({ filename, last_modified }) => {
+ const existing = perSave[filename]
+ if (existing) {
+ if (
+ !existing.find(
+ ({ last_modified: stored_last_modified }) =>
+ last_modified === zip.last_modified
+ )
+ ) {
+ existing.push({ zip, last_modified })
+ }
+ } else {
+ perSave[filename] = [{ zip, last_modified }]
+ }
+ })
+ })
+
+ return perSave
+ }, [zipFilesInfo])
+
+ const filteredSaves = useMemo(
+ () =>
+ Object.fromEntries(
+ Object.entries(perSaveFormat).filter(([filename]) =>
+ filename.toLowerCase().includes(searchQuery.toLowerCase())
+ )
+ ),
+
+ [perSaveFormat, searchQuery]
+ )
+
+ return (
+
+
{
+ setSearchQuery(v)
+ },
+ },
+ ]}
+ >
+
+
+ {"This save backup system won't be as good as I'd like until "}
+
+ {"this issue"}
+
+ {" is fixed"}
+
+
+
+ {Object.entries(filteredSaves).map(([savefile, versions]) => {
+ return (
+
+ )
+ })}
+
+
+ )
+}
+
+const SaveVersions = ({
+ backupPath,
+ savefile,
+ versions,
+}: {
+ backupPath: string
+ savefile: string
+ versions: { zip: SaveBackupPathTime; last_modified: number }[]
+}) => {
+ const [isOpen, setIsOpen] = useState(true)
+
+ const restore = useCallback(
+ async (zip: string) => {
+ const zipPath = `${backupPath}/${zip}`
+ await invokeRestoreZip(zipPath, savefile)
+ },
+ [backupPath, savefile]
+ )
+
+ return (
+
+
setIsOpen((o) => !o)}
+ >{`${savefile} (${versions.length})`}
+
+ {isOpen && (
+
+ {versions.map(({ zip }) => (
+
{
+ restore(zip.filename)
+ }}
+ >
+ {new Date(zip.last_modified * 1000).toLocaleString()}
+
+ ))}
+
+ )}
+
+ )
+}
diff --git a/src/components/saves/item.tsx b/src/components/saves/item.tsx
new file mode 100644
index 00000000..ab393a2c
--- /dev/null
+++ b/src/components/saves/item.tsx
@@ -0,0 +1,55 @@
+import { useRecoilValue } from "recoil"
+import { BackupZipsSelectorFamily } from "../../recoil/selectors"
+import { SaveConfig } from "../../types"
+import { useBuildSaveZip } from "./hooks/useBuildsaveZip"
+
+type SavesItemProps = {
+ config: SaveConfig
+ onClickRestore: () => void
+}
+
+export const SavesItem = ({ config, onClickRestore }: SavesItemProps) => {
+ const saveZip = useBuildSaveZip()
+
+ const { files, exists } = useRecoilValue(
+ BackupZipsSelectorFamily(config.backup_location)
+ )
+
+ if (!exists) {
+ return (
+
+
{config.backup_location}
+
{"Not found"}
+
+ )
+ }
+
+ return (
+
+
saveZip(config.backup_location, config.backup_count)}
+ >
+ {"Backup Now"}
+
+
{config.backup_location}
+
+
{`Backups: ${files.length}`}
+ {files.length > 0 && (
+ <>
+
{`Last Backup: ${new Date(
+ (files.at(-1)?.last_modified || 0) * 1000
+ ).toLocaleString()}`}
+
{`Oldest Backup: ${new Date(
+ (files.at(0)?.last_modified || 0) * 1000
+ ).toLocaleString()}`}
+ >
+ )}
+
+
+
+ {"View Saves"}
+
+
+ )
+}
diff --git a/src/components/screenshots/hooks/useScreenshot.ts b/src/components/screenshots/hooks/useScreenshot.ts
new file mode 100644
index 00000000..e2ac1b01
--- /dev/null
+++ b/src/components/screenshots/hooks/useScreenshot.ts
@@ -0,0 +1,67 @@
+import { useMemo, useState } from "react"
+import { useRecoilValue } from "recoil"
+import { SingleScreenshotSelectorFamily } from "../../../recoil/screenshots/selectors"
+import { VideoJSONSelectorFamily } from "../../../recoil/screenshots/selectors"
+
+const POCKET_WIDTH = 1600
+const POCKET_HEIGHT = 1440
+
+export const useScreenshot = (
+ imageMode: "raw" | "upscaled",
+ fileName: string
+) => {
+ const screenshot = useRecoilValue(SingleScreenshotSelectorFamily(fileName))
+ if (screenshot === null) throw new Error(`Null file ${fileName}`)
+
+ const videoJson = useRecoilValue(
+ VideoJSONSelectorFamily(`${screenshot.author}.${screenshot.core}`)
+ )
+
+ const image = useMemo(() => {
+ const image = new Image()
+ image.src = URL.createObjectURL(screenshot.file)
+ image.onload = () => setImageLoaded(true)
+ return image
+ }, [screenshot.file])
+
+ const [imageLoaded, setImageLoaded] = useState(false)
+
+ const imageSrc = useMemo(() => {
+ switch (imageMode) {
+ case "raw":
+ return URL.createObjectURL(screenshot.file)
+ case "upscaled":
+ const canvas = document.createElement("canvas")
+ const context = canvas.getContext("2d")
+
+ if (!context) throw new Error("Unable to get canvas context")
+
+ const scalarMode = videoJson.video.scaler_modes.find(
+ ({ width, height }) =>
+ width === image.width && height === image.height
+ )
+
+ if (scalarMode) {
+ if (scalarMode.aspect_h > scalarMode.aspect_w) {
+ canvas.height = POCKET_HEIGHT
+ canvas.width =
+ POCKET_HEIGHT * (scalarMode.aspect_w / scalarMode.aspect_h)
+ } else {
+ canvas.width = POCKET_WIDTH
+ canvas.height =
+ POCKET_WIDTH * (scalarMode.aspect_h / scalarMode.aspect_w)
+ }
+ } else {
+ canvas.height = image.height * 10
+ canvas.width = image.width * 10
+ }
+
+ context.imageSmoothingEnabled = false
+ context?.drawImage(image, 0, 0, canvas.width, canvas.height)
+
+ return canvas.toDataURL()
+ }
+ }, [imageMode, imageLoaded])
+
+ return imageSrc
+}
diff --git a/src/components/screenshots/index.css b/src/components/screenshots/index.css
new file mode 100644
index 00000000..55f5acc4
--- /dev/null
+++ b/src/components/screenshots/index.css
@@ -0,0 +1,63 @@
+.screenshots--none{
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ font-size: 3rem;
+ padding: 20px;
+ box-sizing: border-box;
+
+ font-family: "Analogue";
+ font-smooth: never;
+ -webkit-font-smoothing : none;
+ text-align: center;
+ line-height: 45px;
+}
+
+
+.screenshots__item{
+ overflow: hidden;
+ position: relative;
+ aspect-ratio: 160 / 144;
+ cursor: pointer;
+}
+
+.screenshots__item-image{
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+
+ background-color: #111;
+ image-rendering: pixelated;
+}
+
+
+.screenshots__loading-item {
+ height: 190px !important;
+ aspect-ratio: 160 / 144;
+}
+
+.screenshots__item-info{
+ background-color: var(--info-colour);
+ padding: 5px;
+ font-size: 0.875rem;
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+
+ opacity: 0;
+ transform: translateY(80%);
+ transition: opacity 0.25s linear, transform 0.2s ease-out;
+}
+
+.screenshots__item:hover .screenshots__item-info{
+ opacity: 0.8;
+ transform: translateY(0);
+}
+
+.screenshots__item-info-line{
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
diff --git a/src/components/screenshots/index.tsx b/src/components/screenshots/index.tsx
new file mode 100644
index 00000000..d648ac8e
--- /dev/null
+++ b/src/components/screenshots/index.tsx
@@ -0,0 +1,60 @@
+import React, { Suspense, useMemo, useState } from "react"
+import { useRecoilValue } from "recoil"
+import { screenshotsListSelector } from "../../recoil/screenshots/selectors"
+import { Screenshot } from "./item"
+
+import "./index.css"
+import { Loader } from "../loader"
+import { ScreenshotInfo } from "./info"
+import { Grid } from "../grid"
+import { useSaveScroll } from "../../hooks/useSaveScroll"
+
+export const Screenshots = () => {
+ const [selected, setSelected] = useState(null)
+ const screenshots = useRecoilValue(screenshotsListSelector)
+
+ const { pushScroll, popScroll } = useSaveScroll()
+
+ const sortedScreenshots = useMemo(() => {
+ return [...screenshots].sort((a, b) => b.localeCompare(a))
+ }, [screenshots])
+
+ if (selected) {
+ return (
+ {
+ setSelected(null)
+ popScroll()
+ }}
+ />
+ )
+ }
+
+ if (sortedScreenshots.length === 0) {
+ return (
+
+
Once you take some screenshots they'll appear here
+
+ )
+ }
+
+ return (
+
+ {sortedScreenshots.map((fileName) => (
+ }
+ key={fileName}
+ >
+ {
+ pushScroll()
+ setSelected(fileName)
+ }}
+ />
+
+ ))}
+
+ )
+}
diff --git a/src/components/screenshots/info.css b/src/components/screenshots/info.css
new file mode 100644
index 00000000..f0948888
--- /dev/null
+++ b/src/components/screenshots/info.css
@@ -0,0 +1,60 @@
+.screenshot-info{
+ height: 100vh;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ overflow: hidden;
+}
+
+.screenshot-info__controls{
+ display: flex;
+ justify-content: space-between;
+ font-size: 1.125rem;
+ background-color: var(--info-colour);
+}
+
+.screenshot-info__controls-item {
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10px 20px;
+ accent-color: white;
+
+ &:hover{
+ background-color: var(--hover-colour);
+ }
+}
+
+.screenshot-info__controls-item--back-button::before{
+ content: "";
+ display: block;
+ width: 10px;
+ height: 10px;
+ aspect-ratio: 1 / 1;
+ border: 1px solid white;
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+ transform: rotate(-45deg);
+ margin-right: 5px;
+}
+
+.screenshot-info__controls-checkbox{
+ display: flex;
+ gap: 0.2em;
+ align-items: center;
+}
+
+.screenshot-info__raw-image{
+ flex-grow: 1;
+ flex-shrink: 1;
+ object-fit: contain;
+ image-rendering: pixelated;
+ height: 0;
+}
+
+.screenshot-info__info{
+ background-color: var(--info-colour);
+ padding: 10px;
+}
diff --git a/src/components/screenshots/info.tsx b/src/components/screenshots/info.tsx
new file mode 100644
index 00000000..50e1b79d
--- /dev/null
+++ b/src/components/screenshots/info.tsx
@@ -0,0 +1,118 @@
+import React, { useCallback, useMemo, useState } from "react"
+import { useRecoilValue } from "recoil"
+import { SingleScreenshotSelectorFamily } from "../../recoil/screenshots/selectors"
+import { VideoJSONSelectorFamily } from "../../recoil/screenshots/selectors"
+import "./info.css"
+
+import { useSaveFile } from "../../hooks/saveFile"
+import { Controls } from "../controls"
+
+type ScreenshotInfo = {
+ fileName: string
+ onBack: () => void
+}
+
+const POCKET_WIDTH = 1600
+const POCKET_HEIGHT = 1440
+
+export const ScreenshotInfo = ({ fileName, onBack }: ScreenshotInfo) => {
+ const [imageMode, setImageMode] = useState<"raw" | "upscaled">("upscaled")
+ const screenshot = useRecoilValue(SingleScreenshotSelectorFamily(fileName))
+ if (screenshot === null) throw new Error(`Null file ${fileName}`)
+
+ const videoJson = useRecoilValue(
+ VideoJSONSelectorFamily(`${screenshot.author}.${screenshot.core}`)
+ )
+
+ const image = useMemo(() => {
+ const image = new Image()
+ image.src = URL.createObjectURL(screenshot.file)
+ image.onload = () => setImageLoaded(true)
+ return image
+ }, [screenshot.file])
+
+ const [imageLoaded, setImageLoaded] = useState(false)
+
+ const imageSrc = useMemo(() => {
+ switch (imageMode) {
+ case "raw":
+ return URL.createObjectURL(screenshot.file)
+ case "upscaled":
+ const canvas = document.createElement("canvas")
+ const context = canvas.getContext("2d")
+
+ if (!context) throw new Error("Unable to get canvas context")
+
+ const scalarMode = videoJson.video.scaler_modes.find(
+ ({ width, height }) =>
+ width === image.width && height === image.height
+ )
+
+ if (scalarMode) {
+ if (scalarMode.aspect_h > scalarMode.aspect_w) {
+ canvas.height = POCKET_HEIGHT
+ canvas.width =
+ POCKET_HEIGHT * (scalarMode.aspect_w / scalarMode.aspect_h)
+ } else {
+ canvas.width = POCKET_WIDTH
+ canvas.height =
+ POCKET_WIDTH * (scalarMode.aspect_h / scalarMode.aspect_w)
+ }
+ } else {
+ canvas.height = image.height * 10
+ canvas.width = image.width * 10
+ }
+
+ context.imageSmoothingEnabled = false
+ context?.drawImage(image, 0, 0, canvas.width, canvas.height)
+
+ return canvas.toDataURL()
+ }
+ }, [imageMode, imageLoaded])
+
+ const saveFile = useSaveFile()
+
+ const openShareSheet = useCallback(async () => {
+ const file = await fetch(imageSrc)
+ .then((res) => res.arrayBuffer())
+ .then(
+ (buf) => new File([buf], screenshot.file_name, { type: "image/png" })
+ )
+
+ navigator.share({ files: [file] })
+ }, [imageSrc])
+
+ return (
+
+
saveFile(screenshot.file_name, imageSrc),
+ },
+ { type: "button", text: "Share", onClick: openShareSheet },
+ {
+ type: "checkbox",
+ text: "Upscaled",
+ checked: imageMode === "upscaled",
+ onChange: (checked) =>
+ checked ? setImageMode("upscaled") : setImageMode("raw"),
+ },
+ ]}
+ />
+
+
+
+
{screenshot.game}
+
{screenshot.platform}
+
{screenshot.file_name}
+
+
+ )
+}
diff --git a/src/components/screenshots/item.tsx b/src/components/screenshots/item.tsx
new file mode 100644
index 00000000..dd61def3
--- /dev/null
+++ b/src/components/screenshots/item.tsx
@@ -0,0 +1,29 @@
+import React, { ReactElement, useCallback, useMemo } from "react"
+import { useRecoilValue } from "recoil"
+import { SingleScreenshotSelectorFamily } from "../../recoil/screenshots/selectors"
+
+type ScreenshotProps = {
+ fileName: string
+ onClick: () => void
+}
+
+export const Screenshot = ({
+ fileName,
+ onClick,
+}: ScreenshotProps): ReactElement => {
+ const screenshot = useRecoilValue(SingleScreenshotSelectorFamily(fileName))
+ if (screenshot === null) throw new Error(`Null file ${fileName}`)
+
+ const blob = useMemo(() => URL.createObjectURL(screenshot.file), [screenshot])
+
+ return (
+
+
+
+
+
{screenshot.game}
+
{screenshot.platform}
+
+
+ )
+}
diff --git a/src/components/settings/hooks/useUpdateConfig.ts b/src/components/settings/hooks/useUpdateConfig.ts
new file mode 100644
index 00000000..67f44399
--- /dev/null
+++ b/src/components/settings/hooks/useUpdateConfig.ts
@@ -0,0 +1,29 @@
+import { useCallback } from "react"
+import { useRecoilValue, useSetRecoilState } from "recoil"
+import { configInvalidationAtom, pocketPathAtom } from "../../../recoil/atoms"
+import { PocketSyncConfigSelector } from "../../../recoil/selectors"
+import { PocketSyncConfig } from "../../../types"
+import { invokeSaveFile } from "../../../utils/invokes"
+
+export const useUpdateConfig = () => {
+ const currentConfig = useRecoilValue(PocketSyncConfigSelector)
+ const pocketPath = useRecoilValue(pocketPathAtom)
+ const invalidateConfigSelector = useSetRecoilState(configInvalidationAtom)
+
+ return useCallback(
+ async (
+ key: T,
+ value: PocketSyncConfig[T]
+ ) => {
+ const newConfig = { ...currentConfig, [key]: value } as PocketSyncConfig
+ const encoder = new TextEncoder()
+ await invokeSaveFile(
+ `${pocketPath}/pocket-sync.json`,
+ encoder.encode(JSON.stringify(newConfig, null, 2))
+ )
+
+ setTimeout(() => invalidateConfigSelector(Date.now()), 500)
+ },
+ [currentConfig]
+ )
+}
diff --git a/src/components/settings/index.css b/src/components/settings/index.css
new file mode 100644
index 00000000..89bd2193
--- /dev/null
+++ b/src/components/settings/index.css
@@ -0,0 +1,25 @@
+.settings{
+ padding: 10px;
+}
+
+.settings__items{
+ padding: 20px;
+}
+
+.settings__ramble{
+ margin: 0;
+ padding: 0;
+ white-space: pre-wrap;
+}
+
+.settings__row{
+ & input, & select {
+ font-size: 1.4rem;
+ width: 80%;
+ }
+}
+
+.settings__text-input-and-save{
+ display: flex;
+ gap: 10px;
+}
diff --git a/src/components/settings/index.tsx b/src/components/settings/index.tsx
new file mode 100644
index 00000000..9012a68b
--- /dev/null
+++ b/src/components/settings/index.tsx
@@ -0,0 +1,55 @@
+import { useState } from "react"
+import { useRecoilValue } from "recoil"
+import { PocketSyncConfigSelector } from "../../recoil/selectors"
+import { useUpdateConfig } from "./hooks/useUpdateConfig"
+
+import "./index.css"
+
+const ARCHIVE_URL_TEXT = `Please check with your local laws around the downloading of potentially copyrighted (arcade) ROM & BIOS files.
+If you are comfortable with this, copy the following url into the input and hit "save".`
+
+export const Settings = () => {
+ const config = useRecoilValue(PocketSyncConfigSelector)
+ const [archiveUrlInput, setArchiveUrl] = useState(config.archive_url || "")
+ const updateConfig = useUpdateConfig()
+
+ return (
+
+
{"Settings"}
+
+
+
{"Pocket Colour:"}
+
+
+ updateConfig("colour", target.value as "black" | "white")
+ }
+ >
+ {"Black"}
+ {"White"}
+
+
+
+
+
{"ROM & BIOS archive:"}
+
{ARCHIVE_URL_TEXT}
+
{"https://archive.org/download/openFPGA-Files"}
+
+ setArchiveUrl(target.value)}
+ />
+ updateConfig("archive_url", archiveUrlInput)}
+ >
+ {"Save"}
+
+
+
+
+
+ )
+}
diff --git a/src/components/three/index.css b/src/components/three/index.css
new file mode 100644
index 00000000..fac38eb7
--- /dev/null
+++ b/src/components/three/index.css
@@ -0,0 +1,5 @@
+.three-pocket {
+ flex-shrink: 1;
+ flex-grow: 1;
+ flex-basis: 200px;
+}
diff --git a/src/components/three/pocket.tsx b/src/components/three/pocket.tsx
new file mode 100644
index 00000000..cf86777c
--- /dev/null
+++ b/src/components/three/pocket.tsx
@@ -0,0 +1,225 @@
+import { Canvas, useFrame } from "@react-three/fiber"
+import { OrbitControls, Stats, RoundedBox } from "@react-three/drei"
+import { ReactNode, useRef } from "react"
+import { useRecoilValue, useSetRecoilState } from "recoil"
+import { PocketModelColourAtom } from "../../recoil/atoms"
+
+import "./index.css"
+import { PocketSyncConfigSelector } from "../../recoil/selectors"
+
+type PocketProps = {
+ move?: "none" | "spin" | "back-and-forth"
+ screenMaterial?: ReactNode
+}
+
+const BLACK_COLOUR = "rgb(1,1,1)"
+const WHITE_COLOUR = "rgb(90,90,90)"
+
+export const Pocket = ({ move = "none", screenMaterial }: PocketProps) => {
+ return (
+
+
+
+
+
+
+
+
+ {/* */}
+ {/* */}
+
+ )
+}
+
+const Body = ({
+ move,
+ screenMaterial,
+}: Pick) => {
+ const groupRef = useRef(null)
+ const speedRef = useRef(1)
+ useFrame((_, delta) => {
+ if (groupRef.current && speedRef.current) {
+ const speed = speedRef.current
+ switch (move) {
+ case "spin":
+ groupRef.current.rotateY(-0.6 * speed * delta)
+ break
+ case "back-and-forth":
+ groupRef.current.rotateY(-0.6 * speed * delta)
+ if (groupRef.current.rotation.y > 0.4) {
+ speedRef.current = 0.2
+ } else if (groupRef.current.rotation.y < -0.4) {
+ speedRef.current = -0.2
+ }
+ break
+ default:
+ break
+ }
+ }
+ })
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const Screen = ({ screenMaterial }: PocketProps) => {
+ const { colour } = useRecoilValue(PocketSyncConfigSelector)
+ return (
+ <>
+ {/* colour */}
+
+
+
+
+
+ {/* LCD */}
+
+
+ {screenMaterial || (
+
+ )}
+
+ {/* Glass */}
+
+
+
+
+ >
+ )
+}
+
+const BUTTON_GAP = 1.25 as const
+
+const Buttons = () => {
+ const positions = [
+ [BUTTON_GAP, 0, BUTTON_GAP],
+ [-BUTTON_GAP, 0, BUTTON_GAP],
+ [-BUTTON_GAP, 0, -BUTTON_GAP],
+ [BUTTON_GAP, 0, -BUTTON_GAP],
+ ] as const
+
+ return (
+
+ {positions.map((p, index) => (
+
+
+
+
+ ))}
+
+ )
+}
+
+const BottomButtons = () => {
+ const positions = [
+ [BUTTON_GAP, 0, BUTTON_GAP],
+ [0, 0, 0],
+ [-BUTTON_GAP, 0, -BUTTON_GAP],
+ ] as const
+
+ return (
+
+ {positions.map((p, index) => (
+
+
+
+
+ ))}
+
+ )
+}
+
+const DPAD = () => {
+ const args = [
+ [BUTTON_GAP, 1, BUTTON_GAP * 3.5],
+ [BUTTON_GAP * 3.5, 1, BUTTON_GAP],
+ ] as const
+
+ return (
+
+ {args.map((a, index) => (
+
+ {/* @ts-ignore */}
+
+
+
+ ))}
+
+ )
+}
+
+const Material = ({ isButton = false }: { isButton?: boolean }) => {
+ const { colour } = useRecoilValue(PocketSyncConfigSelector)
+ return (
+
+ )
+}
diff --git a/src/components/three/progressScreen.tsx b/src/components/three/progressScreen.tsx
new file mode 100644
index 00000000..646ce7e4
--- /dev/null
+++ b/src/components/three/progressScreen.tsx
@@ -0,0 +1,66 @@
+import React, { useEffect, useState } from "react"
+import { NearestFilter, Texture } from "three"
+
+type ProgressScreenProps = {
+ value: number
+ max: number
+}
+
+const SCALE = 4
+
+const DARKEST_GREEN = "#0f380f"
+const DARK_GREEN = "#306230"
+const LIGHT_GREEN = "#8bac0f"
+const LIGHTEST_GREEN = "#9bbc0f"
+
+export const ProgressScreen = ({
+ value = 33,
+ max = 100,
+}: ProgressScreenProps) => {
+ const [texture, setTexture] = useState(null)
+
+ useEffect(() => {
+ const canvas = document.createElement("canvas")
+ canvas.width = 160 * SCALE
+ canvas.height = 140 * SCALE
+
+ const context = canvas.getContext("2d")
+ if (!context) return
+
+ context.fillStyle = DARKEST_GREEN
+ context.fillRect(0, 0, canvas.width, canvas.height)
+
+ context.fillStyle = DARK_GREEN
+ console.log({ value, max })
+ context.fillRect(0, 0, canvas.width * (value / max), canvas.height)
+
+ context.fillStyle = LIGHTEST_GREEN
+ context.font = `${85 * SCALE}px Analogue`
+ const text = `${((value / max) * 100).toFixed(0)}%`
+ context.textAlign = "center"
+ context.textBaseline = "middle"
+ context.fillText(text, canvas.width / 2, canvas.height / 2)
+
+ canvas.toBlob((b) => {
+ if (!b) return
+ const image = new Image()
+ image.src = URL.createObjectURL(b)
+ image.onload = () => {
+ const newTexture = new Texture()
+ newTexture.image = image
+ newTexture.needsUpdate = true
+ newTexture.anisotropy = 4
+ newTexture.minFilter = NearestFilter
+ newTexture.magFilter = NearestFilter
+ setTexture(newTexture)
+ }
+ })
+ }, [value, max])
+
+ return (
+
+ )
+}
diff --git a/src/components/three/randomScreenshotScreen.tsx b/src/components/three/randomScreenshotScreen.tsx
new file mode 100644
index 00000000..9ff1456a
--- /dev/null
+++ b/src/components/three/randomScreenshotScreen.tsx
@@ -0,0 +1,76 @@
+import React, { useEffect, useMemo, useRef, useState } from "react"
+import { useRecoilCallback, useRecoilValue } from "recoil"
+import {
+ screenshotsListSelector,
+ SingleScreenshotSelectorFamily,
+} from "../../recoil/screenshots/selectors"
+import { Texture } from "three"
+
+type RandomScreenshotScreenProps = {
+ interval?: number
+}
+
+export const RandomScreenshotScreen = ({
+ interval = 2000,
+}: RandomScreenshotScreenProps) => {
+ const screenshotList = useRecoilValue(screenshotsListSelector)
+ const [randomNudge, setRandomNudge] = useState(0)
+
+ const screenshotName = useMemo(() => {
+ return screenshotList[Math.floor(Math.random() * screenshotList.length)]
+ }, [screenshotList, randomNudge])
+
+ useEffect(() => {
+ const changeImageInterval = setInterval(
+ () => setRandomNudge(Date.now()),
+ interval
+ )
+
+ return () => {
+ clearInterval(changeImageInterval)
+ }
+ })
+
+ if (screenshotList.length === 0)
+ return
+
+ return
+}
+
+const ScreenshotScreen = ({ name }: { name: string }) => {
+ const [texture, setTexture] = useState(null)
+
+ const loadImageFile = useRecoilCallback(
+ ({ snapshot }) =>
+ async (imageName: string) => {
+ const screenshot = await snapshot.getPromise(
+ SingleScreenshotSelectorFamily(imageName)
+ )
+ if (!screenshot) return
+ const image = new Image()
+ image.src = URL.createObjectURL(screenshot.file)
+ return image
+ }
+ )
+
+ useEffect(() => {
+ ;(async () => {
+ const image = await loadImageFile(name)
+ if (!image) return
+ image.onload = () => {
+ const newTexture = new Texture()
+ newTexture.image = image
+ newTexture.needsUpdate = true
+ newTexture.anisotropy = 4
+ setTexture(newTexture)
+ }
+ })()
+ }, [name])
+
+ return (
+
+ )
+}
diff --git a/src/components/tip/index.css b/src/components/tip/index.css
new file mode 100644
index 00000000..6f85ba36
--- /dev/null
+++ b/src/components/tip/index.css
@@ -0,0 +1,23 @@
+.tip{
+ width: 100%;
+ text-align: center;
+ box-sizing: border-box;
+ padding: 20px 10px;
+ justify-content: center;
+ align-items: center;
+
+
+ &::before {
+ content: "i";
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ margin-right: 10px;
+ border-radius: 50%;
+
+ aspect-ratio: 1 / 1;
+ border: 1px solid white;
+ width: 1.1em;
+ height: 1.1em;
+ }
+}
diff --git a/src/components/tip/index.tsx b/src/components/tip/index.tsx
new file mode 100644
index 00000000..da61d656
--- /dev/null
+++ b/src/components/tip/index.tsx
@@ -0,0 +1,6 @@
+import { ReactNode } from "react"
+import "./index.css"
+
+export const Tip = ({ children }: { children: ReactNode }) => (
+ {children}
+)
diff --git a/src/components/zipInstall/hooks.ts b/src/components/zipInstall/hooks.ts
new file mode 100644
index 00000000..f1b09400
--- /dev/null
+++ b/src/components/zipInstall/hooks.ts
@@ -0,0 +1,117 @@
+import { emit, listen } from "@tauri-apps/api/event"
+import { useCallback, useEffect, useMemo, useState } from "react"
+import { useSetRecoilState } from "recoil"
+import { fileSystemInvalidationAtom } from "../../recoil/atoms"
+import { FileTreeNode, InstallZipEventPayload } from "./types"
+
+export const useListenForZipInstall = () => {
+ const [installState, setInstallState] =
+ useState(null)
+
+ const updateFSInvalidationAtom = useSetRecoilState(fileSystemInvalidationAtom)
+
+ useEffect(() => {
+ const unlisten = listen(
+ "install-zip-event",
+ ({ payload }) => setInstallState(payload)
+ )
+ return () => {
+ unlisten.then((l) => l())
+ }
+ }, [setInstallState])
+
+ useEffect(() => {
+ const unlisten = listen<{ error?: string }>(
+ "install-zip-finished",
+ ({ payload }) => {
+ setInstallState(null)
+ updateFSInvalidationAtom(Date.now())
+ }
+ )
+ return () => {
+ unlisten.then((l) => l())
+ }
+ }, [setInstallState])
+
+ return { installState }
+}
+
+export const useTree = (files: InstallZipEventPayload["files"]) => {
+ return useMemo(() => {
+ if (!files) return null
+
+ const sortedFiles = [...files].sort(({ path: pathA }, { path: pathB }) =>
+ pathA.localeCompare(pathB)
+ )
+
+ return sortedFiles.reduce((prev, curr) => {
+ const newTree: FileTreeNode[] = [...prev]
+ const fileBits = curr.path.split("/")
+ let treeNode = newTree
+
+ for (let index = 0; index < fileBits.length; index++) {
+ const element = fileBits[index]
+ if (element === "") continue
+ if (!treeNode.find(({ name }) => name === element)) {
+ treeNode.push({
+ name: element,
+ full: curr.path,
+ exists: curr.exists,
+ is_dir: index !== fileBits.length - 1,
+ children: [],
+ })
+ }
+
+ treeNode = treeNode.find(({ name }) => name === element)?.children || []
+ }
+
+ return newTree
+ }, [] as FileTreeNode[])
+ }, [files])
+}
+
+export const useAllowedFiles = (files: InstallZipEventPayload["files"]) => {
+ const [allowedFiles, setAllowedFiles] = useState(null)
+
+ useEffect(() => {
+ setAllowedFiles((f) => {
+ if (f === null && files) return files.map(({ path }) => path)
+ return f
+ })
+ }, [files])
+
+ const toggleFile = useCallback(
+ (path: string) => {
+ setAllowedFiles((f) => {
+ if (!f) return f
+
+ if (f.includes(path)) {
+ return f.filter((p) => p !== path)
+ } else {
+ return [...f, path]
+ }
+ })
+ },
+ [setAllowedFiles]
+ )
+
+ return { allowedFiles, toggleFile }
+}
+
+export const useZipInstallButtons = (allowedFiles: string[] | null) => {
+ const confirm = useCallback(() => {
+ emit("install-confirmation", {
+ paths: allowedFiles,
+ allow: true,
+ })
+ }, [allowedFiles])
+
+ const cancel = useCallback(() => {
+ emit("install-confirmation", {
+ paths: [],
+ allow: false,
+ })
+ }, [])
+
+ return { confirm, cancel }
+}
diff --git a/src/components/zipInstall/index.css b/src/components/zipInstall/index.css
new file mode 100644
index 00000000..e1dcb8c4
--- /dev/null
+++ b/src/components/zipInstall/index.css
@@ -0,0 +1,49 @@
+.zip-install__paths{
+ flex-grow: 1;
+ overflow: auto;
+ background-color: var(--info-colour);
+ border-radius: 10px;
+ box-sizing: border-box;
+ padding: 10px;
+}
+
+.zip-install__controls {
+ display: flex;
+ justify-content: space-around;
+}
+
+.zip-install__tree-node{
+ margin-left: 20px;
+ display: flex;
+ flex-direction: column;
+}
+
+.zip-install__tree-node-info{
+ display: flex;
+ gap: 5px;
+ align-items: center;
+}
+
+.zip-install__progress{
+ width: 100%;
+ accent-color: unset;
+}
+
+.zip-install__tree-node-name{
+ display: flex;
+ gap: inherit;
+ cursor: pointer;
+ align-items: center;
+}
+
+.zip-install__tree-arrow{
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ background-color: white;
+ clip-path: polygon(0% 0%, 50% 100%, 100% 0%);
+}
+
+.zip-install__tree-arrow--collapsed {
+ transform: rotate(-90deg);
+}
diff --git a/src/components/zipInstall/index.tsx b/src/components/zipInstall/index.tsx
new file mode 100644
index 00000000..05db977e
--- /dev/null
+++ b/src/components/zipInstall/index.tsx
@@ -0,0 +1,62 @@
+import { Modal } from "../modal"
+import {
+ useAllowedFiles,
+ useListenForZipInstall,
+ useTree,
+ useZipInstallButtons,
+} from "./hooks"
+import { TreeNode } from "./treeNode"
+import { InstallZipEventPayload } from "./types"
+import { Progress } from "../progress"
+import "./index.css"
+
+export const ZipInstall = () => {
+ const { installState } = useListenForZipInstall()
+
+ if (!installState) return null
+ return
+}
+
+export const ZipInstallInner = ({
+ files,
+ progress,
+ title,
+}: InstallZipEventPayload) => {
+ const tree = useTree(files)
+ const { allowedFiles, toggleFile } = useAllowedFiles(files)
+ const { confirm, cancel } = useZipInstallButtons(allowedFiles)
+
+ if (progress) {
+ return (
+
+ {title}
+
+
+ )
+ }
+
+ return (
+
+ {title}
+
+ {!tree &&
{`Scanning files...`}
}
+ {tree &&
+ allowedFiles &&
+ tree.map((node) => (
+
+ ))}
+
+
+
+ Cancel
+ Confirm
+
+
+ )
+}
diff --git a/src/components/zipInstall/treeNode.tsx b/src/components/zipInstall/treeNode.tsx
new file mode 100644
index 00000000..38e19fd6
--- /dev/null
+++ b/src/components/zipInstall/treeNode.tsx
@@ -0,0 +1,54 @@
+import { useState } from "react"
+import { FileTreeNode } from "./types"
+
+export const TreeNode = ({
+ node,
+ allowed,
+ defaultExpanded = false,
+ toggleFile,
+}: {
+ node: FileTreeNode
+ defaultExpanded?: boolean
+ allowed: string[]
+ toggleFile: (path: string) => void
+}) => {
+ const [expanded, setExpanded] = useState(defaultExpanded)
+
+ return (
+
+
+ {!node.is_dir && (
+
toggleFile(node.full)}
+ >
+ )}
+
setExpanded((e) => !e)}
+ >
+ {node.is_dir && (
+
+ )}
+ {node.name}
+ {node.is_dir ? "/" : ""}
+
+
+
+ {expanded &&
+ node.children.map((n) => (
+
+ ))}
+
+ )
+}
diff --git a/src/components/zipInstall/types.ts b/src/components/zipInstall/types.ts
new file mode 100644
index 00000000..91d48aef
--- /dev/null
+++ b/src/components/zipInstall/types.ts
@@ -0,0 +1,13 @@
+export type FileTreeNode = {
+ name: string
+ full: string
+ exists: boolean
+ is_dir: boolean
+ children: FileTreeNode[]
+}
+
+export type InstallZipEventPayload = {
+ title: string
+ files?: { path: string; exists: boolean }[]
+ progress?: { value: number; max: number }
+}
diff --git a/src/config_file.rs b/src/config_file.rs
deleted file mode 100644
index 51e4e59a..00000000
--- a/src/config_file.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use serde::{Deserialize, Serialize};
-use std::{fs, io::ErrorKind, path::PathBuf};
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct PocketSyncConfig {
- pub last_run_timestamp: i64,
-}
-
-impl PocketSyncConfig {
- pub fn read(pocket_path: &PathBuf) -> PocketSyncConfig {
- let json_path = pocket_path.join("pocket_sync.json");
- if let Ok(data) = fs::read_to_string(&json_path).or_else(|err| match err.kind() {
- ErrorKind::NotFound => {
- let default_struct = PocketSyncConfig {
- last_run_timestamp: 0,
- };
-
- default_struct.write(pocket_path);
- let json = serde_json::to_string(&default_struct);
- json
- }
- _ => panic!("Error trying to read config {}", err),
- }) {
- if let Ok(result) = serde_json::from_str(&data) {
- return result;
- }
- }
-
- panic!("Failed to read or create pocket_sync.json")
- }
-
- pub fn write(&self, pocket_path: &PathBuf) -> () {
- let json_path = pocket_path.join("pocket_sync.json");
- if let Ok(json) = serde_json::to_string(&self) {
- fs::write(&json_path, &json).expect("Failed to write pocket_sync.json");
- }
- }
-}
diff --git a/src/cores.rs b/src/cores.rs
deleted file mode 100644
index 6cc088d0..00000000
--- a/src/cores.rs
+++ /dev/null
@@ -1,160 +0,0 @@
-#[derive(Debug, PartialEq)]
-pub enum SupportedCore {
- GB,
- GBC,
- GBA,
- TGFX16,
- NES,
- SNES,
- GameGear,
- MasterSystem,
- Genesis,
- NEOGEO,
- Arduboy,
- SuperVision,
-}
-
-pub trait TransformCore {
- fn to_pocket(&self) -> String;
- fn to_mister(&self) -> String;
-
- fn from_pocket(name: &str) -> Option;
- fn from_mister(name: &str) -> Option;
-
- fn pocket_folder(&self) -> String;
-
- fn rom_filetypes(&self) -> Vec;
-}
-
-impl TransformCore for SupportedCore {
- fn to_pocket(&self) -> String {
- String::from(match self {
- SupportedCore::Arduboy => "arduboy",
- SupportedCore::GameGear => "gg",
- SupportedCore::GB => "gb",
- SupportedCore::GBA => "gba",
- SupportedCore::GBC => "gbc",
- SupportedCore::Genesis => "genesis",
- SupportedCore::MasterSystem => "sms",
- SupportedCore::NEOGEO => "ng",
- SupportedCore::NES => "nes",
- SupportedCore::SNES => "snes",
- SupportedCore::SuperVision => "supervision",
- SupportedCore::TGFX16 => "pce",
- })
- }
-
- fn to_mister(&self) -> String {
- String::from(match self {
- SupportedCore::Arduboy => "Arduboy",
- SupportedCore::GameGear => "SMS",
- SupportedCore::GB => "GAMEBOY",
- SupportedCore::GBA => "GBA",
- SupportedCore::GBC => "GAMEBOY",
- SupportedCore::Genesis => "Genesis",
- SupportedCore::MasterSystem => "SMS",
- SupportedCore::NEOGEO => "NEOGEO",
- SupportedCore::NES => "NES",
- SupportedCore::SNES => "SNES",
- SupportedCore::SuperVision => "SuperVision",
- SupportedCore::TGFX16 => "TGFX16",
- })
- }
-
- fn from_pocket(name: &str) -> Option {
- match name {
- "arduboy" => Some(SupportedCore::Arduboy),
- "gb" => Some(SupportedCore::GB),
- "gba" => Some(SupportedCore::GBA),
- "gbc" => Some(SupportedCore::GBC),
- "genesis" => Some(SupportedCore::Genesis),
- "gg" => Some(SupportedCore::GameGear),
- "nes" => Some(SupportedCore::NES),
- "ng" => Some(SupportedCore::NEOGEO),
- "pce" => Some(SupportedCore::TGFX16),
- "sms" => Some(SupportedCore::MasterSystem),
- "snes" => Some(SupportedCore::SNES),
- "supervision" => Some(SupportedCore::SuperVision),
- _ => None,
- }
- }
-
- fn from_mister(name: &str) -> Option {
- // Some cores on the MiSTer do double duty (GAMEBOY, SMS) will need to work out
- // How to deal with that (save file header? try to find the full rom name?)
- match name {
- "Arduboy" => Some(SupportedCore::Arduboy),
- "NES" => Some(SupportedCore::NES),
- "SNES" => Some(SupportedCore::SNES),
- "GAMEBOY" => Some(SupportedCore::GBC),
- "GBA" => Some(SupportedCore::GBA),
- "SMS" => Some(SupportedCore::MasterSystem),
- "TGFX16" => Some(SupportedCore::TGFX16),
- "NEOGEO" => Some(SupportedCore::NEOGEO),
- "Genesis" => Some(SupportedCore::Genesis),
- "SuperVision" => Some(SupportedCore::SuperVision),
- _ => None,
- }
- }
-
- fn pocket_folder(&self) -> String {
- String::from(match self {
- Self::NEOGEO => "Mazamars312.NeoGeo",
- _ => "common",
- })
- }
-
- fn rom_filetypes(&self) -> Vec {
- (match self {
- SupportedCore::Arduboy => vec!["hex"],
- SupportedCore::GameGear => vec!["gg"],
- SupportedCore::GB => vec!["gb"],
- SupportedCore::GBA => vec!["gba"],
- SupportedCore::GBC => vec!["gbc"],
- SupportedCore::Genesis => vec!["md", "bin", "gen", "smd"],
- SupportedCore::MasterSystem => vec!["sms"],
- SupportedCore::NEOGEO => vec!["json"],
- SupportedCore::NES => vec!["nes"],
- SupportedCore::SNES => vec!["smc", "sfc"],
- SupportedCore::SuperVision => vec!["sv", "bin"],
- SupportedCore::TGFX16 => vec!["pce"],
- _ => vec![],
- })
- .iter()
- .map(|s| s.to_string())
- .collect()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- #[test]
- fn self_compare() {
- let mister_core_a = SupportedCore::from_mister("SNES").unwrap();
- let mister_core_b = SupportedCore::from_mister("SNES").unwrap();
- assert_eq!(mister_core_a, mister_core_b);
-
- let mister_core_a = SupportedCore::from_mister("NES").unwrap();
- assert_ne!(mister_core_a, mister_core_b);
- }
-
- #[test]
- fn things_that_should_match() {
- let mister_core = SupportedCore::from_mister("SNES").unwrap();
- let pocket_core = SupportedCore::from_pocket("snes").unwrap();
- assert_eq!(mister_core, pocket_core);
-
- let mister_core = SupportedCore::from_mister("GAMEBOY").unwrap();
- let pocket_core = SupportedCore::from_pocket("gbc").unwrap();
- assert_eq!(pocket_core.to_mister(), mister_core.to_mister());
- }
-
- #[test]
- fn things_that_should_return_none() {
- let mister_core = SupportedCore::from_mister("PSX");
- let pocket_core = SupportedCore::from_pocket("galaga");
- assert_eq!(mister_core, None);
- assert_eq!(pocket_core, None);
- }
-}
diff --git a/src/font.css b/src/font.css
new file mode 100644
index 00000000..e05526a0
--- /dev/null
+++ b/src/font.css
@@ -0,0 +1,7 @@
+@font-face {
+ font-family: 'Analogue';
+ src: url('assets/AnalogueOS-Regular.woff2') format('woff2'),
+ url('assets/AnalogueOS-Regular.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+}
diff --git a/src/hooks/saveFile.ts b/src/hooks/saveFile.ts
new file mode 100644
index 00000000..2910072a
--- /dev/null
+++ b/src/hooks/saveFile.ts
@@ -0,0 +1,33 @@
+import { save } from "@tauri-apps/api/dialog"
+import { useCallback } from "react"
+import { invokeSaveFile } from "../utils/invokes"
+
+export const useSaveFile = () => {
+ const onSaveFile = useCallback(async (name: string, file: File | string) => {
+ const filePath = await save({
+ title: "Save screenshot",
+ filters: [
+ {
+ name: "image",
+ extensions: ["png"],
+ },
+ ],
+ defaultPath: name,
+ })
+
+ if (!filePath) return
+
+ const buffer = await fileToBuffer(file)
+ await invokeSaveFile(filePath, new Uint8Array(buffer))
+ }, [])
+
+ return onSaveFile
+}
+
+const fileToBuffer = async (file: File | string) => {
+ if (typeof file === "string") {
+ return fetch(file).then((res) => res.arrayBuffer())
+ } else {
+ return file.arrayBuffer()
+ }
+}
diff --git a/src/hooks/useCategoryLookup.ts b/src/hooks/useCategoryLookup.ts
new file mode 100644
index 00000000..574d21f7
--- /dev/null
+++ b/src/hooks/useCategoryLookup.ts
@@ -0,0 +1,18 @@
+import { useRecoilCallback } from "recoil"
+import { CoreInventorySelector } from "../recoil/inventory/selectors"
+
+export const useCategoryLookup = () => {
+ // might change this to query the filesystem instead but that'll
+ // probably take _a while_ to load
+ return useRecoilCallback(({ snapshot }) => (coreName: string) => {
+ const coreInventory = snapshot.getLoadable(CoreInventorySelector)
+
+ if (coreInventory.state !== "hasValue") return "Unknown"
+ const { data } = coreInventory.getValue()
+ const inventoryItem = data.find(({ identifier }) => identifier === coreName)
+ const aRelease = inventoryItem?.release || inventoryItem?.prerelease
+ if (!aRelease) return "Unknown"
+
+ return aRelease.platform.category ?? "Uncategorized"
+ })
+}
diff --git a/src/hooks/useInstallCore.ts b/src/hooks/useInstallCore.ts
new file mode 100644
index 00000000..3a99ecdb
--- /dev/null
+++ b/src/hooks/useInstallCore.ts
@@ -0,0 +1,13 @@
+import { useCallback } from "react"
+import { emit } from "@tauri-apps/api/event"
+
+export const useInstallCore = () => {
+ const installCore = useCallback(async (coreName: string, zipUrl: string) => {
+ emit("install-core", {
+ core_name: coreName,
+ zip_url: zipUrl,
+ })
+ }, [])
+
+ return { installCore }
+}
diff --git a/src/hooks/useInstallRequiredFiles.ts b/src/hooks/useInstallRequiredFiles.ts
new file mode 100644
index 00000000..e8c7f23f
--- /dev/null
+++ b/src/hooks/useInstallRequiredFiles.ts
@@ -0,0 +1,49 @@
+import { invoke } from "@tauri-apps/api"
+import { listen } from "@tauri-apps/api/event"
+import { useCallback, useEffect, useState } from "react"
+import { useRecoilValue, useSetRecoilState } from "recoil"
+import { fileSystemInvalidationAtom } from "../recoil/atoms"
+import { PocketSyncConfigSelector } from "../recoil/selectors"
+import { RequiredFileInfo } from "../types"
+
+export const useInstallRequiredFiles = (closeModal: () => void) => {
+ const { archive_url } = useRecoilValue(PocketSyncConfigSelector)
+ const invalidateFS = useSetRecoilState(fileSystemInvalidationAtom)
+
+ const [progress, setProgress] = useState<{
+ value: number
+ max: number
+ } | null>(null)
+
+ useEffect(() => {
+ const unlisten = listen<{ max: number; value: number }>(
+ "file-progress",
+ ({ payload }) => {
+ setProgress(payload)
+ if (payload.max === payload.value) {
+ invalidateFS(Date.now())
+ closeModal()
+ }
+ }
+ )
+
+ return () => {
+ unlisten.then((l) => l())
+ }
+ }, [])
+
+ const installRequiredFiles = useCallback(
+ async (files: RequiredFileInfo[]) => {
+ if (!archive_url)
+ throw new Error("Attempt to download without an `archive_url` set")
+
+ const response = await invoke("install_archive_files", {
+ files,
+ archiveUrl: archive_url,
+ })
+ },
+ [archive_url]
+ )
+
+ return { installRequiredFiles, progress }
+}
diff --git a/src/hooks/useInventoryItem.ts b/src/hooks/useInventoryItem.ts
new file mode 100644
index 00000000..bef57313
--- /dev/null
+++ b/src/hooks/useInventoryItem.ts
@@ -0,0 +1,12 @@
+import { useRecoilValue } from "recoil"
+import { CoreInventorySelector } from "../recoil/inventory/selectors"
+
+export const useInventoryItem = (coreName: string) => {
+ const coreInventory = useRecoilValue(CoreInventorySelector)
+ const inventoryItem = coreInventory.data.find(
+ ({ identifier }) => identifier === coreName
+ )
+
+ if (!inventoryItem) return null
+ return inventoryItem
+}
diff --git a/src/hooks/useSaveScroll.ts b/src/hooks/useSaveScroll.ts
new file mode 100644
index 00000000..be6bbeb2
--- /dev/null
+++ b/src/hooks/useSaveScroll.ts
@@ -0,0 +1,19 @@
+import { useCallback, useState } from "react"
+
+export const useSaveScroll = () => {
+ const [savedScroll, setSavedScroll] = useState(null)
+
+ const pushScroll = useCallback(() => {
+ setSavedScroll(window.pageYOffset)
+ window.scrollTo({ top: 0 })
+ }, [])
+
+ const popScroll = useCallback(() => {
+ requestAnimationFrame(() => {
+ if (savedScroll) window.scrollTo({ top: savedScroll })
+ setSavedScroll(null)
+ })
+ }, [savedScroll])
+
+ return { pushScroll, popScroll }
+}
diff --git a/src/hooks/useUninstallCore.ts b/src/hooks/useUninstallCore.ts
new file mode 100644
index 00000000..8d32ee11
--- /dev/null
+++ b/src/hooks/useUninstallCore.ts
@@ -0,0 +1,25 @@
+import { useCallback } from "react"
+import { useSetRecoilState } from "recoil"
+import { fileSystemInvalidationAtom } from "../recoil/atoms"
+import { ask } from "@tauri-apps/api/dialog"
+import { invokeUninstallCore } from "../utils/invokes"
+
+export const useUninstallCore = () => {
+ const updateFSInvalidationAtom = useSetRecoilState(fileSystemInvalidationAtom)
+
+ return useCallback(async (coreName: string) => {
+ const sure = await ask(
+ "This will remove the core, but not any games, saves, library data, or platform information. Are you sure?",
+ { title: "Uninstall Core", type: "warning" }
+ )
+
+ if (!sure) return
+
+ const success = await invokeUninstallCore(coreName)
+
+ if (success) {
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+ updateFSInvalidationAtom(Date.now())
+ }
+ }, [])
+}
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index 87f40d43..00000000
--- a/src/main.rs
+++ /dev/null
@@ -1,248 +0,0 @@
-mod config_file;
-mod cores;
-mod mister_ftp;
-mod pocket_files;
-mod save_compare;
-mod user_input;
-
-use chrono::TimeZone;
-use clap::Parser;
-use cores::{SupportedCore, TransformCore};
-use std::{
- fmt, fs,
- path::{Path, PathBuf},
- time::{Duration, SystemTime, UNIX_EPOCH},
-};
-use walkdir::{DirEntry, WalkDir};
-
-use crate::{
- config_file::PocketSyncConfig,
- mister_ftp::logged_in_ftp,
- user_input::{report_status, UserInput},
-};
-use crate::{
- save_compare::{remove_duplicates, SaveComparison},
- user_input::choose_save,
-};
-
-#[derive(Debug, PartialEq)]
-pub struct SaveInfo {
- pub game: String,
- pub path: PathBuf,
- pub date_modified: i64,
- pub core: SupportedCore,
-}
-
-impl fmt::Display for SaveInfo {
- // This trait requires `fmt` with this exact signature.
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let time = chrono::Local.timestamp(self.date_modified.try_into().unwrap(), 0);
- write!(
- f,
- "Name: {}\nLast Modified: {}\nCore: {}",
- self.game,
- time,
- self.core.to_pocket()
- )
- }
-}
-
-#[derive(Debug)]
-pub enum PlatformSave {
- PocketSave(SaveInfo),
- MiSTerSave(SaveInfo),
-}
-
-#[derive(Parser, Debug)]
-#[command(author, version, about, long_about = None)]
-struct Args {
- path: PathBuf,
- #[arg(long)]
- host_mister: String,
- #[arg(long, default_value = "root")]
- user_mister: String,
- #[arg(long, default_value = "1")]
- password_mister: String,
-}
-
-fn main() {
- let args = Args::parse();
- let mut config = PocketSyncConfig::read(&args.path);
-
- if let Ok(pocket_saves) = find_pocket_saves(&args.path) {
- if let Ok(mister_saves) =
- find_mister_saves(&args.host_mister, &args.user_mister, &args.password_mister)
- {
- let mut save_comparisons: Vec = Vec::new();
-
- for pocket_save in &pocket_saves {
- save_comparisons.push(save_compare::check_save(
- &pocket_save,
- &pocket_saves,
- &mister_saves,
- config.last_run_timestamp,
- ))
- }
-
- for mister_save in &mister_saves {
- save_comparisons.push(save_compare::check_save(
- &mister_save,
- &pocket_saves,
- &mister_saves,
- config.last_run_timestamp,
- ))
- }
-
- let save_comparisons = remove_duplicates(save_comparisons);
- let user_choice = report_status(&save_comparisons);
-
- match user_choice {
- UserInput::Cancel => {
- println!("Ok, exiting!");
- std::process::exit(0);
- }
- _ => {}
- }
-
- for save_comp in save_comparisons {
- let choice = match &save_comp {
- SaveComparison::Conflict(save_pair) => choose_save(&save_pair),
- SaveComparison::MiSTerNewer(_) | SaveComparison::MiSTerOnly(_) => {
- UserInput::UseMister
- }
- SaveComparison::PocketNewer(_) | SaveComparison::PocketOnly(_) => {
- UserInput::UsePocket
- }
- SaveComparison::NoSyncNeeded => UserInput::Skip,
- };
-
- if choice == UserInput::Skip {
- continue;
- }
-
- if let Ok(mut ftp_stream) =
- logged_in_ftp(&args.host_mister, &args.user_mister, &args.password_mister)
- {
- match choice {
- UserInput::UseMister => {
- save_comp
- .use_mister(&mut ftp_stream, &args.path)
- .expect("Failed to copy save from MiSTer");
- }
- UserInput::UsePocket => {
- save_comp
- .use_pocket(&mut ftp_stream, &args.path)
- .expect("Failed to copy save from Pocket");
- }
- _ => {}
- }
- }
- }
-
- let start = SystemTime::now();
- config.last_run_timestamp = start
- .duration_since(UNIX_EPOCH)
- .expect("Time went backwards")
- .as_secs() as i64;
-
- // Push a little bit into the future so an immediate re-run finds nothing
- config.last_run_timestamp += 60;
- config.write(&args.path);
-
- println!("All done!");
- std::process::exit(0);
- } else {
- println!("Failed to get MiSTer saves");
- std::process::exit(1);
- }
- } else {
- println!("Failed to get Pocket saves");
- std::process::exit(1);
- }
-}
-
-fn find_pocket_saves(path: &PathBuf) -> Result, String> {
- println!("Scanning Pocket files");
- let mut saves: Vec = Vec::new();
- let saves_path = Path::new(path).join("Saves");
-
- for entry in WalkDir::new(saves_path).into_iter().filter_map(|e| e.ok()) {
- if !entry.file_type().is_file() {
- continue;
- }
-
- if let Some(extension) = entry.path().extension() {
- if extension == "sav" {
- if let Ok(time) = fs::metadata(entry.path()).and_then(|md| md.modified()) {
- if let Some(core) = SupportedCore::from_pocket(&get_pocket_system_name(&entry))
- {
- saves.push(PlatformSave::PocketSave(SaveInfo {
- game: String::from(entry.file_name().to_str().unwrap_or("unknown")),
- path: PathBuf::from(entry.path()),
- date_modified: time
- .duration_since(SystemTime::UNIX_EPOCH)
- .unwrap_or(Duration::from_secs(0))
- .as_secs() as i64,
- core,
- }))
- }
- }
- }
- }
- }
-
- return Ok(saves);
-}
-
-fn get_pocket_system_name(entry: &DirEntry) -> String {
- let mut current = entry.path();
-
- while let Some(parent) = current.parent() {
- if parent.ends_with("Saves") {
- return String::from(
- current
- .file_stem()
- .and_then(|s| s.to_str())
- .unwrap_or("unknown"),
- );
- }
-
- current = parent;
- }
-
- return String::from("unknown");
-}
-
-fn find_mister_saves(
- host: &str,
- user: &str,
- password: &str,
-) -> Result, suppaftp::FtpError> {
- println!("Connecting to MiSTer");
- let mut ftp_stream = logged_in_ftp(host, user, password)?;
- let mut saves: Vec = Vec::new();
- let _ = ftp_stream.cwd("/media/fat/saves")?;
- let systems = ftp_stream.nlst(None)?;
-
- for system in systems {
- print!(".");
- let _ = ftp_stream.cwd(&system)?;
- let system_saves = ftp_stream.nlst(None)?;
- for system_save in system_saves {
- let modtime = ftp_stream.mdtm(&system_save)?;
- if let Some(core) = SupportedCore::from_mister(&system) {
- saves.push(PlatformSave::MiSTerSave(SaveInfo {
- game: String::from(&system_save),
- path: PathBuf::from(format!("/media/fat/saves/{system}/{system_save}")),
- date_modified: modtime.timestamp(),
- core,
- }))
- }
- }
-
- let _ = ftp_stream.cwd("..")?;
- }
- let _ = ftp_stream.quit()?;
- print!("\n");
- return Ok(saves);
-}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 00000000..7acb6b74
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,13 @@
+import React from "react"
+import ReactDOM from "react-dom/client"
+import { RecoilRoot } from "recoil"
+import { App } from "./app"
+import "./style.css"
+
+ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
+
+
+
+
+
+)
diff --git a/src/mister_ftp.rs b/src/mister_ftp.rs
deleted file mode 100644
index 439cd8a3..00000000
--- a/src/mister_ftp.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-pub fn logged_in_ftp(
- host: &str,
- user: &str,
- password: &str,
-) -> Result {
- let mut ftp_stream = suppaftp::FtpStream::connect(format!("{host}:21"))?;
- ftp_stream.login(user, password)?;
- Ok(ftp_stream)
-}
diff --git a/src/pocket_files.rs b/src/pocket_files.rs
deleted file mode 100644
index 4452306c..00000000
--- a/src/pocket_files.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-use std::{
- ffi::OsStr,
- path::{Component, PathBuf},
-};
-use walkdir::WalkDir;
-
-pub fn find_roms_for_save(
- save_name: &str,
- file_types: &Vec,
- pocket_path: &PathBuf,
-) -> Vec {
- let mut found_paths = vec![];
- let assets_path = pocket_path.join("Assets");
-
- let potential_rom_names: Vec = file_types
- .iter()
- .map(|f| save_name.replace(".sav", format!(".{}", f.as_str()).as_str()))
- .collect();
-
- for entry in WalkDir::new(assets_path).into_iter().filter_map(|e| e.ok()) {
- if !entry.file_type().is_file() {
- continue;
- }
-
- if let Some(file_name) = entry
- .file_name()
- .to_str()
- .and_then(|s| Some(String::from(s)))
- {
- if potential_rom_names.contains(&file_name) {
- found_paths.push(entry.path().to_path_buf());
- }
- }
- }
-
- found_paths
-}
-
-pub fn convert_rom_path_to_save_path(rom_path: &PathBuf) -> PathBuf {
- let file_name = format!("{}.sav", rom_path.file_stem().unwrap().to_str().unwrap());
-
- let mut save_path: PathBuf = PathBuf::new();
- let components = rom_path.components();
- for component in components {
- if component.as_os_str().to_string_lossy() == String::from("Assets") {
- save_path = save_path.join("Saves");
- } else {
- save_path = save_path.join(component);
- }
- }
-
- save_path.pop();
- save_path = save_path.join(file_name);
-
- save_path
-}
diff --git a/src/recoil/atoms.ts b/src/recoil/atoms.ts
new file mode 100644
index 00000000..944a9854
--- /dev/null
+++ b/src/recoil/atoms.ts
@@ -0,0 +1,26 @@
+import { atom } from "recoil"
+
+export const pocketPathAtom = atom({
+ key: "pocketPathAtom",
+ default: null,
+})
+
+export const fileSystemInvalidationAtom = atom({
+ key: "fileSystemInvalidationAtom",
+ default: Date.now(),
+})
+
+export const PocketModelColourAtom = atom<"black" | "white">({
+ key: "PocketModelColourAtom",
+ default: "black",
+})
+
+export const inventoryInvalidationAtom = atom({
+ key: "inventoryInvalidationAtom",
+ default: Date.now(),
+})
+
+export const configInvalidationAtom = atom({
+ key: "configInvalidationAtom",
+ default: Date.now(),
+})
diff --git a/src/recoil/github/selectors.ts b/src/recoil/github/selectors.ts
new file mode 100644
index 00000000..37819c42
--- /dev/null
+++ b/src/recoil/github/selectors.ts
@@ -0,0 +1,18 @@
+import { GithubRelease } from "../../types"
+import { selectorFamily } from "recoil"
+
+export const GithubReleasesSelectorFamily = selectorFamily<
+ GithubRelease[],
+ { owner: string; repo: string }
+>({
+ key: "GithubReleasesSelectorFamily",
+ get:
+ ({ owner, repo }) =>
+ async () => {
+ const response = await fetch(
+ `https://api.github.com/repos/${owner}/${repo}/releases`
+ )
+
+ return (await response.json()) as GithubRelease[]
+ },
+})
diff --git a/src/recoil/inventory/selectors.ts b/src/recoil/inventory/selectors.ts
new file mode 100644
index 00000000..f7d0e85f
--- /dev/null
+++ b/src/recoil/inventory/selectors.ts
@@ -0,0 +1,72 @@
+import { selector, selectorFamily } from "recoil"
+import { Category, InventoryJSON } from "../../types"
+import { inventoryInvalidationAtom } from "../atoms"
+import { GithubReleasesSelectorFamily } from "../github/selectors"
+
+export const DownloadURLSelectorFamily = selectorFamily({
+ key: "DownloadURLSelectorFamily",
+ get:
+ (coreName: string) =>
+ async ({ get }) => {
+ const inventory = get(CoreInventorySelector)
+ const inventoryItem = inventory.data.find(
+ ({ identifier }) => coreName === identifier
+ )
+ if (!inventoryItem || inventoryItem.repository.platform !== "github")
+ return null
+
+ const { owner, name: repo } = inventoryItem.repository
+
+ const githubReleaseList = get(
+ GithubReleasesSelectorFamily({ owner, repo })
+ )
+
+ const zips = githubReleaseList[0].assets.filter(({ name }) =>
+ name.endsWith(".zip")
+ )
+
+ if (zips.length === 1) return zips[0].browser_download_url
+ const coreZip = githubReleaseList[0].assets.find(({ name }) => {
+ // hopefully this doesn't get used much
+ const [_, core] = coreName.split(".")
+ const simpleCore = core.replace(/[^\x00-\x7F]/g, "").toLowerCase()
+ const simpleName = name.replace(/[^\x00-\x7F]/g, "").toLowerCase()
+
+ const regex = new RegExp(`[^a-zA-Z0-9]${simpleCore}[^a-zA-Z0-9]`)
+ return name.endsWith(".zip") && regex.test(simpleName)
+ })
+
+ if (!coreZip) return null
+
+ return coreZip.browser_download_url
+ },
+})
+
+export const CoreInventorySelector = selector({
+ key: "CoreInventorySelector",
+ get: async ({ get }) => {
+ get(inventoryInvalidationAtom)
+ const response = await fetch(
+ "https://joshcampbell191.github.io/openfpga-cores-inventory/api/v1/analogue-pocket/cores.json"
+ )
+ return await response.json()
+ },
+})
+
+export const CateogryListselector = selector({
+ key: "CateogryListselector",
+ get: ({ get }) => {
+ const inventory = get(CoreInventorySelector)
+
+ const cateogrySet = new Set(
+ inventory.data.map(({ release, prerelease }) => {
+ const releaseDetails = release ?? prerelease
+ if (!releaseDetails) return "Uncategorized"
+ const { platform } = releaseDetails
+ return platform.category ?? "Uncategorized"
+ })
+ )
+
+ return ["All", ...Array.from(cateogrySet)]
+ },
+})
diff --git a/src/recoil/screenshots/selectors.ts b/src/recoil/screenshots/selectors.ts
new file mode 100644
index 00000000..28a2f6cc
--- /dev/null
+++ b/src/recoil/screenshots/selectors.ts
@@ -0,0 +1,77 @@
+import { selector, selectorFamily } from "recoil"
+import { Screenshot, VideoJSON } from "../../types"
+import { fileSystemInvalidationAtom } from "../atoms"
+import {
+ invokeListFiles,
+ invokeReadBinaryFile,
+ invokeReadTextFile,
+} from "../../utils/invokes"
+
+export const VideoJSONSelectorFamily = selectorFamily({
+ key: "VideoJSONSelectorFamily",
+ get:
+ (coreName) =>
+ async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ const jsonText = await invokeReadTextFile(`Cores/${coreName}/video.json`)
+ return JSON.parse(jsonText) as VideoJSON
+ },
+})
+
+export const screenshotsListSelector = selector({
+ key: "screenshotsListSelector",
+ get: async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ return await invokeListFiles("Memories/Screenshots")
+ },
+})
+
+export const SingleScreenshotSelectorFamily = selectorFamily<
+ Screenshot | null,
+ string
+>({
+ key: "SingleScreenshotSelectorFamily",
+ get:
+ (fileName) =>
+ async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+
+ const data = await invokeReadBinaryFile(
+ `Memories/Screenshots/${fileName}`
+ )
+ const buf = new Uint8Array(data)
+ const file = new File([buf], fileName, { type: "image/png" })
+ const metadataBuffer = buf.slice(buf.length - 528)
+
+ let utf8decoder = new TextDecoder()
+ // The unpacking here might not be right if there's unused ranges
+
+ let authorName = utf8decoder
+ .decode(metadataBuffer.slice(0, 16 * 2))
+ .replaceAll("\u0000", "")
+
+ let coreName = utf8decoder
+ .decode(metadataBuffer.slice(16 * 2, 16 * 4))
+ .trim()
+ .replaceAll("\u0000", "")
+
+ let gameName = utf8decoder
+ .decode(metadataBuffer.slice(16 * 6, 16 * 20))
+ .trim()
+ .replaceAll("\u0000", "")
+
+ let platformName = utf8decoder
+ .decode(metadataBuffer.slice(metadataBuffer.length - 16 * 10))
+ .replaceAll("\u0000", "")
+
+ return {
+ file_name: fileName,
+ file,
+ game: gameName,
+ platform: platformName,
+ timestamp: new Date(file.lastModified),
+ author: authorName,
+ core: coreName,
+ }
+ },
+})
diff --git a/src/recoil/selectors.ts b/src/recoil/selectors.ts
new file mode 100644
index 00000000..bd2fcd28
--- /dev/null
+++ b/src/recoil/selectors.ts
@@ -0,0 +1,332 @@
+import { selector, selectorFamily } from "recoil"
+import {
+ CoreInfoJSON,
+ DataJSON,
+ InstanceDataJSON,
+ PlatformId,
+ PlatformInfoJSON,
+ PocketSyncConfig,
+ RequiredFileInfo,
+ SaveBackupPathTime,
+} from "../types"
+import { renderBinImage } from "../utils/renderBinImage"
+import {
+ configInvalidationAtom,
+ fileSystemInvalidationAtom,
+ pocketPathAtom,
+} from "./atoms"
+import { getVersion } from "@tauri-apps/api/app"
+import { decodeDataParams } from "../utils/decodeDataParams"
+import {
+ invokeFileExists,
+ invokeListBackupSaves,
+ invokeListFiles,
+ invokeListSavesInZip,
+ invokeReadBinaryFile,
+ invokeReadTextFile,
+ invokeSaveFile,
+ invokeWalkDirListFiles,
+} from "../utils/invokes"
+
+export const DataJSONSelectorFamily = selectorFamily({
+ key: "DataJSONSelectorFamily",
+ get:
+ (coreName) =>
+ async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ const jsonText = await invokeReadTextFile(`Cores/${coreName}/data.json`)
+ return JSON.parse(jsonText) as DataJSON
+ },
+})
+
+export const RequiredFileInfoSelectorFamily = selectorFamily<
+ RequiredFileInfo[],
+ string
+>({
+ key: "DataJSONSelectorFamily",
+ get:
+ (coreName) =>
+ async ({ get }) => {
+ const dataJSON = get(DataJSONSelectorFamily(coreName))
+ const coreJSON = get(CoreInfoSelectorFamily(coreName))
+ const [platform_id] = coreJSON.core.metadata.platform_ids
+
+ const requiredCoreFiles = dataJSON.data.data_slots.filter(
+ ({ name, required, filename }) => {
+ return (
+ // not sure why some bioses aren't required
+ (required || name?.toLowerCase().includes("bios")) &&
+ filename &&
+ coreJSON.core.metadata.platform_ids.length === 1
+ )
+ }
+ )
+
+ const fileInfo = await Promise.all(
+ requiredCoreFiles.map(async ({ filename, parameters }) => {
+ const path = decodeDataParams(parameters).coreSpecific
+ ? `Assets/${platform_id}/${coreName}`
+ : `Assets/${platform_id}/common`
+
+ return {
+ filename: filename as string,
+ path,
+ exists: await invokeFileExists(`${path}/${filename}`),
+ type: "core",
+ } as RequiredFileInfo
+ })
+ )
+
+ const instanceFileInfo = await Promise.all(
+ dataJSON.data.data_slots
+ .filter(({ required, parameters }) => {
+ return (
+ required &&
+ decodeDataParams(parameters).instanceJSON &&
+ coreJSON.core.metadata.platform_ids.length === 1
+ )
+ })
+ .map(async ({ filename, parameters }) => {
+ if (filename) {
+ // can't handle this yet
+ console.log("is a single filename")
+ }
+
+ const path = decodeDataParams(parameters).coreSpecific
+ ? `Assets/${platform_id}/${coreName}/`
+ : `Assets/${platform_id}/common/`
+
+ const files = await invokeWalkDirListFiles(path, [".json"])
+
+ return await Promise.all(
+ files.map(async (f) => {
+ const response = await invokeReadTextFile(`${path}/${f}`)
+ const instanceFile = JSON.parse(response) as InstanceDataJSON
+ const dataPath = instanceFile.instance.data_path
+
+ return await Promise.all(
+ instanceFile.instance.data_slots.map(
+ async ({ filename, parameters }) => {
+ const path = decodeDataParams(parameters).coreSpecific
+ ? `Assets/${platform_id}/${coreName}${
+ dataPath ? `/${dataPath}` : ""
+ }`
+ : `Assets/${platform_id}/common${
+ dataPath ? `/${dataPath}` : ""
+ }`
+
+ return {
+ filename: filename as string,
+ path,
+ exists: await invokeFileExists(`${path}/${filename}`),
+ type: "instance",
+ } as RequiredFileInfo
+ }
+ )
+ )
+ })
+ )
+ })
+ )
+
+ return [...fileInfo, ...instanceFileInfo.flat(3)]
+ },
+})
+
+export const coresListSelector = selector({
+ key: "coresListSelector",
+ get: async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ return await invokeListFiles("Cores")
+ },
+})
+
+export const CoreInfoSelectorFamily = selectorFamily({
+ key: "CoreInfoSelectorFamily",
+ get:
+ (coreName: string) =>
+ async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ const response = await invokeReadTextFile(`Cores/${coreName}/core.json`)
+ return JSON.parse(response) as CoreInfoJSON
+ },
+})
+
+export const CoreAuthorImageSelectorFamily = selectorFamily({
+ key: "CoreAuthorImageSelectorFamily",
+ get:
+ (coreName: string) =>
+ async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ const response = await invokeReadBinaryFile(`Cores/${coreName}/icon.bin`)
+ return await new Promise((resolve) => {
+ // @ts-ignore not supported in safari
+ if (window.requestIdleCallback) {
+ requestIdleCallback(
+ () => {
+ resolve(renderBinImage(response, 36, 36, true))
+ },
+ { timeout: 1000 }
+ )
+ } else {
+ resolve(renderBinImage(response, 36, 36, true))
+ }
+ })
+ },
+})
+
+export const PlatformImageSelectorFamily = selectorFamily({
+ key: "PlatformImageSelectorFamily",
+ get:
+ (platformId: PlatformId) =>
+ async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ const response = await invokeReadBinaryFile(
+ `Platforms/_images/${platformId}.bin`
+ )
+
+ return await new Promise((resolve) => {
+ // @ts-ignore not supported in safari
+ if (window.requestIdleCallback) {
+ requestIdleCallback(
+ () => {
+ resolve(renderBinImage(response, 521, 165, true))
+ },
+ { timeout: 1000 }
+ )
+ } else {
+ resolve(renderBinImage(response, 521, 165, true))
+ }
+ })
+ },
+})
+
+export const platformsListSelector = selector({
+ key: "platformsListSelector",
+ get: async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ return await invokeListFiles("Platforms")
+ },
+})
+
+export const PlatformInfoSelectorFamily = selectorFamily<
+ PlatformInfoJSON,
+ PlatformId
+>({
+ key: "PlatformInfoSelectorFamily",
+ get:
+ (platformId: PlatformId) =>
+ async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ const response = await invokeReadTextFile(`Platforms/${platformId}.json`)
+ return JSON.parse(response) as PlatformInfoJSON
+ },
+})
+
+export const AppVersionSelector = selector({
+ key: "AppVersionSelector",
+ get: async () => await getVersion(),
+})
+
+export const PocketSyncConfigSelector = selector({
+ key: "PocketSyncConfigSelector",
+ get: async ({ get }) => {
+ get(configInvalidationAtom)
+ const pocketPath = get(pocketPathAtom)
+
+ if (!pocketPath) {
+ return {
+ version: get(AppVersionSelector),
+ colour: Math.random() > 0.5 ? "white" : "black",
+ archive_url: null,
+ saves: [],
+ } as PocketSyncConfig
+ }
+
+ const exists = await invokeFileExists("pocket-sync.json")
+ if (!exists) {
+ const defaultConfig = {
+ version: get(AppVersionSelector),
+ colour: "black",
+ archive_url: null,
+ saves: [],
+ } as PocketSyncConfig
+
+ const encoder = new TextEncoder()
+
+ await invokeSaveFile(
+ `${pocketPath}/pocket-sync.json`,
+ encoder.encode(JSON.stringify(defaultConfig, null, 2))
+ )
+ }
+
+ const response = await invokeReadTextFile("pocket-sync.json")
+ return JSON.parse(response) as PocketSyncConfig
+ },
+})
+
+export const FileCountSelectorFamily = selectorFamily<
+ number,
+ { path: string; extensions: string[] }
+>({
+ key: "FileCountSelectorFamily",
+ get:
+ ({ path, extensions }) =>
+ async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ const files = await invokeWalkDirListFiles(path, extensions)
+ return files.length
+ },
+})
+
+export const AllSavesSelector = selector({
+ key: "AllSavesSelector",
+ get: async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ const saves = await invokeWalkDirListFiles(`Saves`, [".sav"])
+ return saves.map((f) => f.replace(/^\//g, ""))
+ },
+})
+
+export const BackupZipsSelectorFamily = selectorFamily<
+ { files: SaveBackupPathTime[]; exists: boolean },
+ string
+>({
+ key: "BackupZipsSelectorFamily",
+ get:
+ (backupPath) =>
+ async ({ get }) => {
+ get(fileSystemInvalidationAtom)
+ const backups = await invokeListBackupSaves(backupPath)
+ return backups
+ },
+})
+
+export const SaveZipFilesListSelectorFamily = selectorFamily<
+ SaveBackupPathTime[],
+ string
+>({
+ key: "SaveZipFilesListSelectorFamily",
+ get: (zipPath) => async () => {
+ const backups = await invokeListSavesInZip(zipPath)
+ return backups
+ },
+})
+
+export const AllBackupZipsFilesSelectorFamily = selectorFamily<
+ { zip: SaveBackupPathTime; files: SaveBackupPathTime[] }[],
+ string
+>({
+ key: "AllBackupZipsFilesSelectorFamily",
+ get:
+ (backupPath) =>
+ async ({ get }) => {
+ const { files } = get(BackupZipsSelectorFamily(backupPath))
+ return files.map((zip) => ({
+ zip,
+ files: get(
+ SaveZipFilesListSelectorFamily(`${backupPath}/${zip.filename}`)
+ ),
+ }))
+ },
+})
diff --git a/src/save_compare.rs b/src/save_compare.rs
deleted file mode 100644
index 60e63e77..00000000
--- a/src/save_compare.rs
+++ /dev/null
@@ -1,296 +0,0 @@
-use crate::{
- cores::TransformCore,
- pocket_files::{convert_rom_path_to_save_path, find_roms_for_save},
- PlatformSave, SaveInfo,
-};
-use std::{
- fmt,
- fs::File,
- io::{Read, Write},
- path::PathBuf,
-};
-
-#[derive(Debug, PartialEq)]
-pub struct SavePair<'a> {
- pocket: &'a SaveInfo,
- mister: &'a SaveInfo,
-}
-
-impl SavePair<'_> {
- pub fn is_pocket_newer(&self) -> bool {
- self.pocket.date_modified > self.mister.date_modified
- }
-
- pub fn newer_save(&self) -> &SaveInfo {
- if self.pocket.date_modified > self.mister.date_modified {
- self.pocket
- } else {
- self.mister
- }
- }
-
- pub fn older_save(&self) -> &SaveInfo {
- if self.pocket.date_modified > self.mister.date_modified {
- self.mister
- } else {
- self.pocket
- }
- }
-}
-
-impl<'a> fmt::Display for SavePair<'a> {
- // This trait requires `fmt` with this exact signature.
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let titles = match self.is_pocket_newer() {
- true => ("-- Pocket (newer)", "-- MiSTer (older)"),
- false => ("-- MiSTer (newer)", "-- Pocket (older)"),
- };
-
- write!(
- f,
- "{}\n{} \n\n--- VS ---\n\n{}\n{}",
- titles.0,
- self.newer_save(),
- titles.1,
- self.older_save()
- )
- }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum SaveComparison<'a> {
- PocketOnly(&'a SaveInfo),
- MiSTerOnly(&'a SaveInfo),
- PocketNewer(SavePair<'a>),
- MiSTerNewer(SavePair<'a>),
- Conflict(SavePair<'a>),
- NoSyncNeeded,
-}
-
-impl SaveComparison<'_> {
- pub fn use_mister(
- &self,
- ftp_stream: &mut suppaftp::FtpStream,
- pocket_path: &PathBuf,
- ) -> Result<(), suppaftp::FtpError> {
- let mister_save_info = match self {
- SaveComparison::MiSTerOnly(save_info) => &save_info,
- Self::PocketNewer(save_pair)
- | Self::MiSTerNewer(save_pair)
- | Self::Conflict(save_pair) => &save_pair.mister,
- _ => {
- panic!("Attempt to use a non-existent MiSTer save");
- }
- };
- let path = &mister_save_info.path;
- let _ = ftp_stream.cwd(path.parent().unwrap().to_path_buf().to_str().unwrap())?;
- let file_name = path.file_name().unwrap();
- let mut save_file = ftp_stream.retr_as_buffer(file_name.to_str().unwrap())?;
-
- let pocket_save_paths: Vec = match self {
- SaveComparison::MiSTerOnly(save_info) => {
- let found = find_roms_for_save(
- &save_info.game,
- &save_info.core.rom_filetypes(),
- &pocket_path,
- )
- .iter()
- .map(|p| convert_rom_path_to_save_path(p))
- .collect();
-
- found
- }
- Self::PocketNewer(save_pair)
- | Self::MiSTerNewer(save_pair)
- | Self::Conflict(save_pair) => vec![pocket_path.join(save_pair.pocket.path.clone())],
- Self::PocketOnly(_) => panic!("Attempt to use a non-existent MiSTer save"),
- Self::NoSyncNeeded => panic!("Attempt to sync when NoSyncNeeded"),
- };
-
- if pocket_save_paths.len() == 0 {
- println!(
- "Couldn't find \"{}\" on the pocket, skipping",
- mister_save_info.game.replace(".sav", "")
- );
- return Ok(());
- }
-
- println!(
- "Copying {} ({}) \nMiSTer -> Pocket",
- mister_save_info.game,
- mister_save_info.core.to_pocket()
- );
-
- for pocket_save_path in pocket_save_paths {
- let prefix = pocket_save_path.parent().unwrap();
- std::fs::create_dir_all(prefix).unwrap();
-
- let mut file = File::create(pocket_save_path).unwrap();
- let mut buf: Vec = Vec::new();
- save_file.read_to_end(&mut buf).unwrap();
- file.write(&buf).unwrap();
- }
-
- return Ok(());
- }
-
- pub fn use_pocket(
- &self,
- ftp_stream: &mut suppaftp::FtpStream,
- pocket_path: &PathBuf,
- ) -> Result<(), suppaftp::FtpError> {
- let pocket_save_info = match self {
- SaveComparison::PocketOnly(save_info) => &save_info,
- Self::PocketNewer(save_pair)
- | Self::MiSTerNewer(save_pair)
- | Self::Conflict(save_pair) => &save_pair.pocket,
- _ => {
- panic!("Attempt to use a non-existent Pocket save");
- }
- };
- let path = &pocket_save_info.path;
- let file_name = path.file_name().unwrap();
- let mister_save_path = match self {
- SaveComparison::PocketOnly(save_info) => {
- pocket_path.join(format!("/media/fat/saves/{}", save_info.core.to_mister()))
- }
- Self::PocketNewer(save_pair)
- | Self::MiSTerNewer(save_pair)
- | Self::Conflict(save_pair) => save_pair
- .mister
- .path
- .clone()
- .parent()
- .unwrap()
- .to_path_buf(),
- Self::MiSTerOnly(_) => panic!("Attempt to use a non-existent MiSTer save"),
- Self::NoSyncNeeded => panic!("Attempt to sync when NoSyncNeeded"),
- };
-
- let mut file = File::open(path).unwrap();
-
- let mister_path_buf = &mister_save_path.to_path_buf();
- let mister_path = mister_path_buf.to_str().unwrap();
-
- println!(
- "Copying {} ({}) \nPocket -> MiSTer\n---",
- pocket_save_info.game,
- pocket_save_info.core.to_mister()
- );
-
- ftp_stream.cwd(mister_path)?;
- ftp_stream.put_file(file_name.to_str().unwrap(), &mut file)?;
-
- return Ok(());
- }
-}
-
-pub fn check_save<'a>(
- save: &'a PlatformSave,
- pocket_saves: &'a Vec,
- mister_saves: &'a Vec,
- last_merge: i64,
-) -> SaveComparison<'a> {
- match save {
- PlatformSave::PocketSave(pocket_save_info) => {
- if let Some(mister_save_info) =
- find_matching_mister_save(pocket_save_info, mister_saves)
- {
- return get_comparison(pocket_save_info, mister_save_info, last_merge);
- } else {
- return SaveComparison::PocketOnly(pocket_save_info);
- }
- }
- PlatformSave::MiSTerSave(mister_save_info) => {
- if let Some(pocket_save_info) =
- find_matching_pocket_save(mister_save_info, pocket_saves)
- {
- return get_comparison(pocket_save_info, mister_save_info, last_merge);
- } else {
- return SaveComparison::MiSTerOnly(mister_save_info);
- }
- }
- }
-}
-
-fn get_comparison<'a>(
- pocket_save_info: &'a SaveInfo,
- mister_save_info: &'a SaveInfo,
- last_merge: i64,
-) -> SaveComparison<'a> {
- if mister_save_info.date_modified < 86400 {
- // MiSTer save was updated while the RTC wasn't running - raise as a conflict to be safe
- return SaveComparison::Conflict(SavePair {
- pocket: pocket_save_info,
- mister: mister_save_info,
- });
- }
-
- if pocket_save_info.date_modified < last_merge && mister_save_info.date_modified < last_merge {
- return SaveComparison::NoSyncNeeded;
- }
-
- if pocket_save_info.date_modified > last_merge && mister_save_info.date_modified > last_merge {
- return SaveComparison::Conflict(SavePair {
- pocket: pocket_save_info,
- mister: mister_save_info,
- });
- }
-
- if mister_save_info.date_modified > pocket_save_info.date_modified {
- return SaveComparison::MiSTerNewer(SavePair {
- pocket: pocket_save_info,
- mister: &mister_save_info,
- });
- } else {
- return SaveComparison::PocketNewer(SavePair {
- pocket: pocket_save_info,
- mister: mister_save_info,
- });
- }
-}
-
-fn find_matching_mister_save<'a>(
- save: &SaveInfo,
- saves: &'a Vec,
-) -> Option<&'a SaveInfo> {
- for mister_save in saves {
- if let PlatformSave::MiSTerSave(mister_save) = mister_save {
- if mister_save.core == save.core && mister_save.game == save.game {
- return Some(&mister_save);
- }
- }
- }
- return None;
-}
-
-fn find_matching_pocket_save<'a>(
- save: &SaveInfo,
- saves: &'a Vec,
-) -> Option<&'a SaveInfo> {
- for pocket_save in saves {
- if let PlatformSave::PocketSave(pocket_save) = pocket_save {
- if pocket_save.core == save.core && pocket_save.game == save.game {
- return Some(&pocket_save);
- }
- }
- }
- return None;
-}
-
-pub fn remove_duplicates<'a>(save_comparisons: Vec>) -> Vec> {
- let mut singles: Vec = Vec::new();
-
- for save_comparison in save_comparisons {
- match &save_comparison {
- SaveComparison::NoSyncNeeded => singles.push(save_comparison),
- _ => {
- if !singles.contains(&save_comparison) {
- singles.push(save_comparison)
- }
- }
- }
- }
-
- singles
-}
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 00000000..5901d9ed
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,109 @@
+:root {
+ font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
+ font-size: 16px;
+ line-height: 24px;
+ font-weight: 400;
+
+ color: #f6f6f6;
+ --background-colour: #2f2f2f;
+ --hover-colour: #222;
+ --info-colour: #111;
+ --light-colour: #555;
+ --green-colour: rgb(88, 144, 80);
+ --red-colour: rgb(200, 80, 88);
+ background-color: var(--background-colour);
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+ accent-color: white;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ overflow-x: hidden;
+}
+
+.container {
+ margin: 0;
+ padding: 10vh 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+ height: 100vh;
+ box-sizing: border-box;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: 0.75s;
+}
+
+.logo.tauri:hover {
+ filter: drop-shadow(0 0 2em #24c8db);
+}
+
+.row {
+ display: flex;
+ justify-content: center;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+
+a:hover {
+ color: #535bf2;
+}
+
+h1 {
+ text-align: center;
+ font-family: "Analogue";
+ font-size: 4rem;
+ font-smooth: never;
+ -webkit-font-smoothing : none;
+}
+
+h2 {
+ text-align: left;
+ font-family: "Analogue";
+ font-size: 3rem;
+ font-smooth: never;
+ -webkit-font-smoothing : none;
+ padding: 0 10px;
+ margin-bottom: 15px;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ color: #ffffff;
+ background-color: #0f0f0f98;
+ transition: border-color 0.25s;
+ box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
+}
+
+button {
+ cursor: pointer;
+}
+
+button:hover {
+ border-color: #396cd8;
+}
+
+input,
+button {
+ outline: none;
+}
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 00000000..4742b702
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,178 @@
+export type Screenshot = {
+ file_name: string
+ file: File
+ platform: string
+ game: string
+ author: string
+ core: string
+ timestamp: Date
+}
+
+export type VideoJSON = {
+ video: {
+ magic: "APF_VER_1"
+ scaler_modes: {
+ width: number
+ height: number
+ aspect_w: number
+ aspect_h: number
+ rotation: number
+ mirror: 0 | 1
+ }[]
+ }
+}
+
+export type DataJSON = {
+ data: {
+ magic: "APF_VER_1"
+ data_slots: DataSlotJSON[]
+ }
+}
+
+export type InstanceDataJSON = {
+ instance: {
+ data_path?: string
+ data_slots: DataSlotJSON[]
+ }
+}
+
+type DataSlotJSON = {
+ name?: string
+ required?: boolean
+ parameters: number | string
+ extensions?: string[]
+ filename?: string
+}
+
+export type RequiredFileInfo = {
+ filename: string
+ path: string
+ exists: boolean
+ type: "core" | "instance"
+}
+
+export type PlatformId = string
+export type Category = string
+export type AuthorName = string
+export type Semver = `${number}.${number}.${number}`
+
+export type PlatformInfoJSON = {
+ platform: {
+ category: Category
+ name: string
+ year: number
+ manufacturer: string
+ }
+}
+
+export type CoreInfoJSON = {
+ core: {
+ magic: "APF_VER_1"
+ metadata: {
+ platform_ids: PlatformId[]
+ shortname: string
+ description: string
+ author: AuthorName
+ url?: string
+ version: Semver | string
+ date_release: string
+ }
+ framework: {
+ target_product: "Analogue Pocket"
+ version_required: string
+ sleep_supported: boolean
+ dock: {
+ supported: boolean
+ analog_output: boolean
+ }
+ hardware: {
+ link_port: boolean
+ cartridge_adapter: -1 | 0
+ }
+ }
+ cores: {
+ name: string
+ id: number | string
+ filename: string
+ }[]
+ }
+}
+
+export type InventoryJSON = {
+ data: InventoryItem[]
+}
+
+export type InventoryItem = {
+ identifier: string
+ platform: PlatformId
+ repository: {
+ platform: "github" | string
+ owner: string
+ name: string
+ }
+ release?: InventoryItemRelease
+ prerelease?: InventoryItemRelease
+ sponsor?: {
+ [k: string]: string
+ }
+}
+
+type InventoryItemRelease = {
+ tag_name: Semver | string
+ release_date: string
+ platform: {
+ category: string
+ name: string
+ manufacturer: string
+ year: number
+ }
+ assets: [
+ {
+ platform: PlatformId
+ filename?: string
+ extensions?: string[]
+ }
+ ]
+}
+
+export type GithubRelease = {
+ html_url: string
+ id: number
+ tag_name: string
+ name: string
+ body: string
+ draft: boolean
+ prerelease: boolean
+ created_at: string
+ published_at: string
+ assets: {
+ browser_download_url: string
+ name: string
+ label: string
+ content_type: string
+ size: 1024
+ }[]
+}
+
+export type InstallDetails = {
+ success: boolean
+ files: { path: string; exists: boolean }[]
+}
+
+export type PocketSyncConfig = {
+ version: string
+ colour: "white" | "black"
+ archive_url: string | null
+ saves: SaveConfig[]
+}
+
+export type SaveConfig = {
+ type: "zip"
+ backup_location: string
+ backup_count: number
+}
+
+export type SaveBackupPathTime = {
+ filename: string
+ last_modified: number
+}
diff --git a/src/user_input.rs b/src/user_input.rs
deleted file mode 100644
index 28435805..00000000
--- a/src/user_input.rs
+++ /dev/null
@@ -1,96 +0,0 @@
-use question::{Answer, Question};
-
-use crate::save_compare::{SaveComparison, SavePair};
-
-#[derive(PartialEq)]
-pub enum UserInput {
- Ok,
- Cancel,
- UseMister,
- UsePocket,
- Skip,
-}
-
-pub fn choose_save(save_pair: &SavePair) -> UserInput {
- println!("{}", save_pair);
- let answer = Question::new("Use MiSTer or Pocket save?\n([m]ister/[p]ocket/[s]kip):")
- .acceptable(vec!["pocket", "mister", "p", "m", "skip", "s"])
- .until_acceptable()
- .ask();
-
- match answer {
- Some(Answer::RESPONSE(response)) => match response.as_str() {
- "mister" | "m" => UserInput::UseMister,
- "pocket" | "p" => UserInput::UsePocket,
- _ => UserInput::Cancel,
- },
- _ => UserInput::Cancel,
- }
-}
-
-pub fn report_status(saves: &Vec) -> UserInput {
- let (mister_count, pocket_count, conflict_count, no_sync_needed_count) =
- count_save_types(&saves);
- println!(
- "Found {} Saves\nWith {} conflicts, {} newer MiSTer saves, {} newer Pocket saves, and {} which don't need synced",
- saves.len(),
- conflict_count,
- mister_count,
- pocket_count,
- no_sync_needed_count
- );
-
- if saves.len() == no_sync_needed_count as usize {
- println!("No saves to sync! Exiting...");
- std::process::exit(0);
- }
-
- let answer = Question::new("Do you want to continue the sync?")
- .yes_no()
- .default(Answer::YES)
- .show_defaults()
- .tries(3)
- .ask();
-
- match answer {
- Some(Answer::YES) => UserInput::Ok,
- _ => UserInput::Cancel,
- }
-}
-
-fn count_save_types(saves: &Vec) -> (u32, u32, u32, u32) {
- let mut mister_count: u32 = 0;
- let mut pocket_count: u32 = 0;
- let mut conflict_count: u32 = 0;
- let mut no_sync_needed_count: u32 = 0;
-
- for save in saves {
- match save {
- SaveComparison::MiSTerOnly(_) => {
- mister_count += 1;
- }
- SaveComparison::MiSTerNewer(_) => {
- mister_count += 1;
- }
- SaveComparison::PocketOnly(_) => {
- pocket_count += 1;
- }
- SaveComparison::PocketNewer(_) => {
- pocket_count += 1;
- }
- SaveComparison::Conflict(_) => {
- conflict_count += 1;
- }
- _ => {
- no_sync_needed_count += 1;
- }
- }
- }
-
- (
- mister_count,
- pocket_count,
- conflict_count,
- no_sync_needed_count,
- )
-}
diff --git a/src/utils/decodeDataParams.ts b/src/utils/decodeDataParams.ts
new file mode 100644
index 00000000..c701b69f
--- /dev/null
+++ b/src/utils/decodeDataParams.ts
@@ -0,0 +1,11 @@
+export const decodeDataParams = (rawParams: string | number) => {
+ const parms = typeof rawParams === "string" ? Number(rawParams) : rawParams
+
+ return {
+ userReloadable: (parms & 1) === 1,
+ coreSpecific: (parms & 2) === 2,
+ nonVolitileFilename: (parms & 0b000000100) === 0b000000100,
+ readOnly: (parms & 0b000001000) === 0b000001000,
+ instanceJSON: (parms & 0b000010000) === 0b000010000,
+ }
+}
diff --git a/src/utils/invokes.ts b/src/utils/invokes.ts
new file mode 100644
index 00000000..e1db33d6
--- /dev/null
+++ b/src/utils/invokes.ts
@@ -0,0 +1,71 @@
+import { invoke } from "@tauri-apps/api"
+import { SaveBackupPathTime } from "../types"
+
+export const invokeOpenPocket = async () => invoke("open_pocket")
+
+export const invokeListFiles = async (path: string) =>
+ invoke("list_files", {
+ path,
+ })
+
+export const invokeReadTextFile = async (path: string) =>
+ invoke("read_text_file", {
+ path,
+ })
+
+export const invokeReadBinaryFile = async (path: string) =>
+ invoke("read_binary_file", {
+ path,
+ })
+
+export const invokeWalkDirListFiles = async (
+ path: string,
+ extensions: string[]
+) =>
+ invoke("walkdir_list_files", {
+ path,
+ extensions,
+ })
+
+export const invokeSaveFile = async (path: string, buffer: Uint8Array) =>
+ invoke("save_file", {
+ path,
+ buffer: Array.prototype.slice.call(buffer),
+ })
+
+export const invokeFileExists = async (path: string) =>
+ invoke("file_exists", {
+ path,
+ })
+
+export const invokeUninstallCore = async (coreName: string) =>
+ invoke("uninstall_core", {
+ coreName,
+ })
+
+export const invokeBackupSaves = async (
+ savePaths: string[],
+ zipPath: string,
+ maxCount: number
+) => invoke("backup_saves", { savePaths, zipPath, maxCount })
+
+export const invokeListBackupSaves = async (backupPath: string) => {
+ const { files, exists } = await invoke<{
+ files: SaveBackupPathTime[]
+ exists: boolean
+ }>("list_backup_saves", {
+ backupPath,
+ })
+ return {
+ files: [...files].sort((a, b) => a.last_modified - b.last_modified),
+ exists,
+ }
+}
+
+export const invokeListSavesInZip = async (zipPath: string) => {
+ return invoke("list_saves_in_zip", { zipPath })
+}
+
+export const invokeRestoreZip = async (zipPath: string, filePath: string) => {
+ return invoke("restore_save", { zipPath, filePath })
+}
diff --git a/src/utils/renderBinImage.ts b/src/utils/renderBinImage.ts
new file mode 100644
index 00000000..5f4b4492
--- /dev/null
+++ b/src/utils/renderBinImage.ts
@@ -0,0 +1,34 @@
+export const renderBinImage = (
+ image: Uint8Array,
+ width: number,
+ height: number,
+ invert: boolean
+): string => {
+ const canvas = document.createElement("canvas")
+ const context = canvas.getContext("2d")
+ if (!context) throw new Error("Failed to get canvas context")
+
+ canvas.width = width
+ canvas.height = height
+
+ const halfWidth = Math.ceil(width / 2)
+ const halfHeight = Math.ceil(height / 2)
+
+ context.translate(halfWidth, halfHeight)
+ context.rotate(Math.PI / 2)
+ context.translate(-halfHeight, -halfWidth)
+
+ let index = 0
+ for (let y = 0; y < width; y++) {
+ for (let x = 0; x < height; x++) {
+ const value = image[index]
+ const brightness = invert ? 255 - value : value
+ context.fillStyle = `rgb(${brightness}, ${brightness}, ${brightness})`
+ context.fillRect(x, y, 1, 1)
+
+ index = index + 2
+ }
+ }
+
+ return canvas.toDataURL()
+}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..3d0a51a8
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 00000000..9d31e2ae
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 00000000..851d6945
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,33 @@
+import { defineConfig } from "vite"
+import react from "@vitejs/plugin-react"
+import postcssNesting from "postcss-nesting"
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ css: {
+ postcss: {
+ plugins: [postcssNesting],
+ },
+ },
+
+ // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
+ // prevent vite from obscuring rust errors
+ clearScreen: false,
+ // tauri expects a fixed port, fail if that port is not available
+ server: {
+ port: 1420,
+ strictPort: true,
+ },
+ // to make use of `TAURI_DEBUG` and other env variables
+ // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
+ envPrefix: ["VITE_", "TAURI_"],
+ build: {
+ // Tauri supports es2021
+ target: ["es2021", "chrome100", "safari13"],
+ // don't minify for debug builds
+ minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
+ // produce sourcemaps for debug builds
+ sourcemap: !!process.env.TAURI_DEBUG,
+ },
+})