<% if(isNightly) { %>
<% } else { %>
diff --git a/package-lock.json b/package-lock.json
index f6bfadfcd..70f035962 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,14 +1,15 @@
{
"name": "bridge",
- "version": "2.4.1",
+ "version": "2.4.15",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bridge",
- "version": "2.4.1",
+ "version": "2.4.15",
"dependencies": {
"@mdi/font": "^6.9.96",
+ "@tauri-apps/api": "^1.2.0",
"@types/lz-string": "^1.3.34",
"bridge-common-utils": "^0.3.3",
"bridge-iframe-api": "^0.4.11",
@@ -28,12 +29,13 @@
"jsonc-parser": "^3.2.0",
"lodash-es": "^4.17.20",
"lz-string": "^1.4.4",
- "mc-project-core": "^0.3.22",
+ "mc-project-core": "^0.3.23",
"molang": "^1.13.11",
"monaco-editor": "^0.33.0",
"path-browserify": "^1.0.1",
"prismarine-nbt": "^1.6.0",
"quick-score": "^0.0.13",
+ "solid-js": "^1.6.2",
"tga-js": "^1.1.1",
"three": "^0.139.2",
"uuid": "^8.3.1",
@@ -62,6 +64,7 @@
"vite": "^3.2.3",
"vite-plugin-ejs": "^1.4.3",
"vite-plugin-pwa": "^0.13.3",
+ "vite-plugin-solid": "^2.4.0",
"vite-plugin-vue2": "^2.0.2"
}
},
@@ -115,20 +118,21 @@
}
},
"node_modules/@babel/core": {
- "version": "7.18.5",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz",
+ "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.1.0",
- "@babel/code-frame": "^7.16.7",
- "@babel/generator": "^7.18.2",
- "@babel/helper-compilation-targets": "^7.18.2",
- "@babel/helper-module-transforms": "^7.18.0",
- "@babel/helpers": "^7.18.2",
- "@babel/parser": "^7.18.5",
- "@babel/template": "^7.16.7",
- "@babel/traverse": "^7.18.5",
- "@babel/types": "^7.18.4",
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.5",
+ "@babel/helper-compilation-targets": "^7.20.0",
+ "@babel/helper-module-transforms": "^7.20.2",
+ "@babel/helpers": "^7.20.5",
+ "@babel/parser": "^7.20.5",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.5",
+ "@babel/types": "^7.20.5",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -144,12 +148,12 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.20.4",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz",
- "integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
+ "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.20.2",
+ "@babel/types": "^7.20.5",
"@jridgewell/gen-mapping": "^0.3.2",
"jsesc": "^2.5.1"
},
@@ -480,13 +484,14 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.18.2",
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz",
+ "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@babel/template": "^7.16.7",
- "@babel/traverse": "^7.18.2",
- "@babel/types": "^7.18.2"
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.5",
+ "@babel/types": "^7.20.5"
},
"engines": {
"node": ">=6.9.0"
@@ -506,9 +511,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.20.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
- "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
+ "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -917,11 +922,12 @@
}
},
"node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.14.5",
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
+ "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
+ "@babel/helper-plugin-utils": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1668,6 +1674,23 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/preset-typescript": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz",
+ "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.18.6",
+ "@babel/helper-validator-option": "^7.18.6",
+ "@babel/plugin-transform-typescript": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.20.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz",
@@ -1695,19 +1718,19 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.20.1",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz",
- "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
+ "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.18.6",
- "@babel/generator": "^7.20.1",
+ "@babel/generator": "^7.20.5",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
- "@babel/parser": "^7.20.1",
- "@babel/types": "^7.20.0",
+ "@babel/parser": "^7.20.5",
+ "@babel/types": "^7.20.5",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -1716,9 +1739,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.20.2",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz",
- "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
+ "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
@@ -2016,6 +2039,20 @@
"version": "1.2.218",
"license": "Apache-2.0"
},
+ "node_modules/@tauri-apps/api": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.2.0.tgz",
+ "integrity": "sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==",
+ "engines": {
+ "node": ">= 14.6.0",
+ "npm": ">= 6.6.0",
+ "yarn": ">= 1.19.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/tauri"
+ }
+ },
"node_modules/@types/chai": {
"version": "4.3.1",
"license": "MIT"
@@ -2408,6 +2445,33 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/babel-plugin-jsx-dom-expressions": {
+ "version": "0.35.6",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.35.6.tgz",
+ "integrity": "sha512-z8VBym+Scol38MiR97iqQGsANjhsDqscRRemk+po+z3TWKV/fb9kux/gdKOJJSC/ARyUL3HExBFVtr+Efd24uw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "7.16.0",
+ "@babel/plugin-syntax-jsx": "^7.16.5",
+ "@babel/types": "^7.16.0",
+ "html-entities": "2.3.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz",
+ "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.16.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz",
@@ -2447,6 +2511,18 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/babel-preset-solid": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.6.2.tgz",
+ "integrity": "sha512-5sFI34g7Jtp4r04YFWkuC1o+gnekBdPXQTJb5/6lmxi5YwzazVgKAXRwEAToC3zRaPyIYJbZUVLpOi5mDzPEuw==",
+ "dev": true,
+ "dependencies": {
+ "babel-plugin-jsx-dom-expressions": "^0.35.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.0",
"dev": true,
@@ -3932,6 +4008,12 @@
"he": "bin/he"
}
},
+ "node_modules/html-entities": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz",
+ "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==",
+ "dev": true
+ },
"node_modules/html-tags": {
"version": "2.0.0",
"dev": true,
@@ -4232,6 +4314,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-what": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.8.tgz",
+ "integrity": "sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/jake": {
"version": "10.8.5",
"dev": true,
@@ -4518,9 +4612,9 @@
}
},
"node_modules/mc-project-core": {
- "version": "0.3.22",
- "resolved": "https://registry.npmjs.org/mc-project-core/-/mc-project-core-0.3.22.tgz",
- "integrity": "sha512-WTjSut6x+f3xTmVr0P75uldgezjTIRO2h/5SQoFBuKZT8XzphEtLBmdiBfNobAz5vsOaCLEuiBkL++DXe3x6Og==",
+ "version": "0.3.23",
+ "resolved": "https://registry.npmjs.org/mc-project-core/-/mc-project-core-0.3.23.tgz",
+ "integrity": "sha512-hy/uVzW0jtL6vdzqwhe5t3aYy/4WsOuGDH7JVESSmeKKgb+jxqz04OhgpWhWj3/86GEdHd1u18Iek1VUG1s6TQ==",
"dependencies": {
"bridge-common-utils": "^0.3.3",
"json5": "^2.2.0",
@@ -4532,6 +4626,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/merge-anything": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.4.tgz",
+ "integrity": "sha512-7PWKwGOs5WWcpw+/OvbiFiAvEP6bv/QHiicigpqMGKIqPPAtGhBLR8LFJW+Zu6m9TXiR/a8+AiPlGG0ko1ruoQ==",
+ "dev": true,
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/merge-source-map": {
"version": "1.1.0",
"dev": true,
@@ -5170,6 +5279,28 @@
"node": ">=8"
}
},
+ "node_modules/solid-js": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.6.2.tgz",
+ "integrity": "sha512-AZBsj+Yn1xliniTTeuQKG9V7VQVkQ8lZmSKvBjpcVSoZeF7nvt/N5f7Kcsx6QSufioa2YgvBjkIiA0cM0qhotw==",
+ "dependencies": {
+ "csstype": "^3.1.0"
+ }
+ },
+ "node_modules/solid-refresh": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.4.1.tgz",
+ "integrity": "sha512-v3tD/OXQcUyXLrWjPW1dXZyeWwP7/+GQNs8YTL09GBq+5FguA6IejJWUvJDrLIA4M0ho9/5zK2e9n+uy+4488g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/generator": "^7.18.2",
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/types": "^7.18.4"
+ },
+ "peerDependencies": {
+ "solid-js": "^1.3"
+ }
+ },
"node_modules/sortablejs": {
"version": "1.10.2",
"license": "MIT"
@@ -5711,6 +5842,24 @@
"workbox-window": "^6.5.4"
}
},
+ "node_modules/vite-plugin-solid": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.4.0.tgz",
+ "integrity": "sha512-Rr+t2sr9TWIvH16yzBZzx6O9YSpYAvcwKUMPqbi/4iU3mRumXQ4O10i1XGtQIynC7U3XwJsMzAJigDFGbiJBiw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.18.6",
+ "@babel/preset-typescript": "^7.18.6",
+ "babel-preset-solid": "^1.4.6",
+ "merge-anything": "^5.0.2",
+ "solid-refresh": "^0.4.1",
+ "vitefu": "^0.1.1"
+ },
+ "peerDependencies": {
+ "solid-js": "^1.3.17",
+ "vite": "^3.0.0"
+ }
+ },
"node_modules/vite-plugin-vue2": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/vite-plugin-vue2/-/vite-plugin-vue2-2.0.2.tgz",
@@ -5861,6 +6010,20 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/vitefu": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.1.1.tgz",
+ "integrity": "sha512-HClD14fjMJ+NQgXBqT3dC3RdO/+Chayil+cCPYZKY3kT+KcJomKzrdgzfCHJkIL2L0OAY+VPvrSW615iPtc7ag==",
+ "dev": true,
+ "peerDependencies": {
+ "vite": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
"node_modules/vitest": {
"version": "0.15.2",
"license": "MIT",
@@ -6353,19 +6516,21 @@
"dev": true
},
"@babel/core": {
- "version": "7.18.5",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz",
+ "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==",
"dev": true,
"requires": {
"@ampproject/remapping": "^2.1.0",
- "@babel/code-frame": "^7.16.7",
- "@babel/generator": "^7.18.2",
- "@babel/helper-compilation-targets": "^7.18.2",
- "@babel/helper-module-transforms": "^7.18.0",
- "@babel/helpers": "^7.18.2",
- "@babel/parser": "^7.18.5",
- "@babel/template": "^7.16.7",
- "@babel/traverse": "^7.18.5",
- "@babel/types": "^7.18.4",
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.5",
+ "@babel/helper-compilation-targets": "^7.20.0",
+ "@babel/helper-module-transforms": "^7.20.2",
+ "@babel/helpers": "^7.20.5",
+ "@babel/parser": "^7.20.5",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.5",
+ "@babel/types": "^7.20.5",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -6374,12 +6539,12 @@
}
},
"@babel/generator": {
- "version": "7.20.4",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz",
- "integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
+ "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
"dev": true,
"requires": {
- "@babel/types": "^7.20.2",
+ "@babel/types": "^7.20.5",
"@jridgewell/gen-mapping": "^0.3.2",
"jsesc": "^2.5.1"
},
@@ -6611,12 +6776,14 @@
}
},
"@babel/helpers": {
- "version": "7.18.2",
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz",
+ "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==",
"dev": true,
"requires": {
- "@babel/template": "^7.16.7",
- "@babel/traverse": "^7.18.2",
- "@babel/types": "^7.18.2"
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.5",
+ "@babel/types": "^7.20.5"
}
},
"@babel/highlight": {
@@ -6629,9 +6796,9 @@
}
},
"@babel/parser": {
- "version": "7.20.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
- "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg=="
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
+ "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA=="
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.18.6",
@@ -6888,10 +7055,12 @@
}
},
"@babel/plugin-syntax-jsx": {
- "version": "7.14.5",
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
+ "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.14.5"
+ "@babel/helper-plugin-utils": "^7.18.6"
}
},
"@babel/plugin-syntax-logical-assignment-operators": {
@@ -7382,6 +7551,17 @@
"esutils": "^2.0.2"
}
},
+ "@babel/preset-typescript": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz",
+ "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.18.6",
+ "@babel/helper-validator-option": "^7.18.6",
+ "@babel/plugin-transform-typescript": "^7.18.6"
+ }
+ },
"@babel/runtime": {
"version": "7.20.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz",
@@ -7403,27 +7583,27 @@
}
},
"@babel/traverse": {
- "version": "7.20.1",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz",
- "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
+ "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.18.6",
- "@babel/generator": "^7.20.1",
+ "@babel/generator": "^7.20.5",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
- "@babel/parser": "^7.20.1",
- "@babel/types": "^7.20.0",
+ "@babel/parser": "^7.20.5",
+ "@babel/types": "^7.20.5",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
- "version": "7.20.2",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz",
- "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==",
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
+ "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
@@ -7633,6 +7813,11 @@
"@swc/wasm-web": {
"version": "1.2.218"
},
+ "@tauri-apps/api": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.2.0.tgz",
+ "integrity": "sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw=="
+ },
"@types/chai": {
"version": "4.3.1"
},
@@ -7922,6 +8107,29 @@
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"dev": true
},
+ "babel-plugin-jsx-dom-expressions": {
+ "version": "0.35.6",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.35.6.tgz",
+ "integrity": "sha512-z8VBym+Scol38MiR97iqQGsANjhsDqscRRemk+po+z3TWKV/fb9kux/gdKOJJSC/ARyUL3HExBFVtr+Efd24uw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "7.16.0",
+ "@babel/plugin-syntax-jsx": "^7.16.5",
+ "@babel/types": "^7.16.0",
+ "html-entities": "2.3.2"
+ },
+ "dependencies": {
+ "@babel/helper-module-imports": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz",
+ "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.16.0"
+ }
+ }
+ }
+ },
"babel-plugin-polyfill-corejs2": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz",
@@ -7952,6 +8160,15 @@
"@babel/helper-define-polyfill-provider": "^0.3.3"
}
},
+ "babel-preset-solid": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.6.2.tgz",
+ "integrity": "sha512-5sFI34g7Jtp4r04YFWkuC1o+gnekBdPXQTJb5/6lmxi5YwzazVgKAXRwEAToC3zRaPyIYJbZUVLpOi5mDzPEuw==",
+ "dev": true,
+ "requires": {
+ "babel-plugin-jsx-dom-expressions": "^0.35.4"
+ }
+ },
"balanced-match": {
"version": "1.0.0",
"dev": true
@@ -8787,6 +9004,12 @@
"he": {
"version": "1.2.0"
},
+ "html-entities": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz",
+ "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==",
+ "dev": true
+ },
"html-tags": {
"version": "2.0.0",
"dev": true
@@ -8974,6 +9197,12 @@
"call-bind": "^1.0.2"
}
},
+ "is-what": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.8.tgz",
+ "integrity": "sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==",
+ "dev": true
+ },
"jake": {
"version": "10.8.5",
"dev": true,
@@ -9177,9 +9406,9 @@
}
},
"mc-project-core": {
- "version": "0.3.22",
- "resolved": "https://registry.npmjs.org/mc-project-core/-/mc-project-core-0.3.22.tgz",
- "integrity": "sha512-WTjSut6x+f3xTmVr0P75uldgezjTIRO2h/5SQoFBuKZT8XzphEtLBmdiBfNobAz5vsOaCLEuiBkL++DXe3x6Og==",
+ "version": "0.3.23",
+ "resolved": "https://registry.npmjs.org/mc-project-core/-/mc-project-core-0.3.23.tgz",
+ "integrity": "sha512-hy/uVzW0jtL6vdzqwhe5t3aYy/4WsOuGDH7JVESSmeKKgb+jxqz04OhgpWhWj3/86GEdHd1u18Iek1VUG1s6TQ==",
"requires": {
"bridge-common-utils": "^0.3.3",
"json5": "^2.2.0",
@@ -9190,6 +9419,15 @@
"version": "1.0.1",
"dev": true
},
+ "merge-anything": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.4.tgz",
+ "integrity": "sha512-7PWKwGOs5WWcpw+/OvbiFiAvEP6bv/QHiicigpqMGKIqPPAtGhBLR8LFJW+Zu6m9TXiR/a8+AiPlGG0ko1ruoQ==",
+ "dev": true,
+ "requires": {
+ "is-what": "^4.1.8"
+ }
+ },
"merge-source-map": {
"version": "1.1.0",
"dev": true,
@@ -9602,6 +9840,25 @@
"version": "3.0.0",
"dev": true
},
+ "solid-js": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.6.2.tgz",
+ "integrity": "sha512-AZBsj+Yn1xliniTTeuQKG9V7VQVkQ8lZmSKvBjpcVSoZeF7nvt/N5f7Kcsx6QSufioa2YgvBjkIiA0cM0qhotw==",
+ "requires": {
+ "csstype": "^3.1.0"
+ }
+ },
+ "solid-refresh": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.4.1.tgz",
+ "integrity": "sha512-v3tD/OXQcUyXLrWjPW1dXZyeWwP7/+GQNs8YTL09GBq+5FguA6IejJWUvJDrLIA4M0ho9/5zK2e9n+uy+4488g==",
+ "dev": true,
+ "requires": {
+ "@babel/generator": "^7.18.2",
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/types": "^7.18.4"
+ }
+ },
"sortablejs": {
"version": "1.10.2"
},
@@ -9996,6 +10253,20 @@
"workbox-window": "^6.5.4"
}
},
+ "vite-plugin-solid": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.4.0.tgz",
+ "integrity": "sha512-Rr+t2sr9TWIvH16yzBZzx6O9YSpYAvcwKUMPqbi/4iU3mRumXQ4O10i1XGtQIynC7U3XwJsMzAJigDFGbiJBiw==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.18.6",
+ "@babel/preset-typescript": "^7.18.6",
+ "babel-preset-solid": "^1.4.6",
+ "merge-anything": "^5.0.2",
+ "solid-refresh": "^0.4.1",
+ "vitefu": "^0.1.1"
+ }
+ },
"vite-plugin-vue2": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/vite-plugin-vue2/-/vite-plugin-vue2-2.0.2.tgz",
@@ -10055,6 +10326,13 @@
}
}
},
+ "vitefu": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.1.1.tgz",
+ "integrity": "sha512-HClD14fjMJ+NQgXBqT3dC3RdO/+Chayil+cCPYZKY3kT+KcJomKzrdgzfCHJkIL2L0OAY+VPvrSW615iPtc7ag==",
+ "dev": true,
+ "requires": {}
+ },
"vitest": {
"version": "0.15.2",
"requires": {
diff --git a/package.json b/package.json
index a62984c38..ba5a6f122 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "bridge",
- "version": "2.4.2",
+ "version": "2.5.0",
"private": true,
"scripts": {
"dev": "vite",
@@ -13,6 +13,7 @@
},
"dependencies": {
"@mdi/font": "^6.9.96",
+ "@tauri-apps/api": "^1.2.0",
"@types/lz-string": "^1.3.34",
"bridge-common-utils": "^0.3.3",
"bridge-iframe-api": "^0.4.11",
@@ -32,12 +33,13 @@
"jsonc-parser": "^3.2.0",
"lodash-es": "^4.17.20",
"lz-string": "^1.4.4",
- "mc-project-core": "^0.3.22",
+ "mc-project-core": "^0.3.23",
"molang": "^1.13.11",
"monaco-editor": "^0.33.0",
"path-browserify": "^1.0.1",
"prismarine-nbt": "^1.6.0",
"quick-score": "^0.0.13",
+ "solid-js": "^1.6.2",
"tga-js": "^1.1.1",
"three": "^0.139.2",
"uuid": "^8.3.1",
@@ -66,6 +68,7 @@
"vite": "^3.2.3",
"vite-plugin-ejs": "^1.4.3",
"vite-plugin-pwa": "^0.13.3",
+ "vite-plugin-solid": "^2.4.0",
"vite-plugin-vue2": "^2.0.2"
}
}
diff --git a/public/changelog.html b/public/changelog.html
index e3ee008f0..6332d5f13 100644
--- a/public/changelog.html
+++ b/public/changelog.html
@@ -1,26 +1,17 @@
Features:
-
-Tree Editor: Added support for "not" and "doNotSuggest" schema properties
-Loading file definitions is now significantly faster
-Loading JSON schemas is now significantly faster
-Loading lightning cache files is now significantly faster
-Improved file system migration process and prompt criteria
-Bone name auto-completions within part_visibility (#468)
-
+
Color Picker & Preview (#614)
+
bridge. now supports previewing colors within JSON files inline and editing them conveniently with a color picker.
+
Changes:
-Updated game test types to latest 1.19.0 release
-Cache event references from new 1.19.0 components
-Initial Setup: Some browsers will now only show one permission prompt per selected folder (instead of previously two for read & write permissions)
-Added "onProjectChanged(...)" extension API
-Updated geode and ore feature schemas (Thanks to @KekeCreations; bridge-core/editor-packages#25)
-Updated data to latest Minecraft Preview
+Ability to configure folder indentation (#730)
Fixes:
-Fixed component data not being cached from entity permutations
-Fixed entity permutations using incorrect component auto-completions
-Fixed tree editor not handling escaped characters correctly (#465)
-Fixed sidebar icons within tasks not scrolling correctly
-Fixed WelcomeScreen top margin for logo without alert
+Fixed transformFiles compilation step overwriting files in rare cases (https://github.com/bridge-core/dash-compiler/commit/8178e1d3700b805e7ab57148b96b6f6bffe05541)
+Fixed "molang" plugin no longer transforming Molang statements (#732)
+Fixed custom item components using the player API (#489)
+Dash now creates a default hooks map upon loading cache file (https://github.com/bridge-core/dash-compiler/commit/4741b9272e9d4148ae29f8403dbadfb62f5e38a5)
+Fixed generator scripts generating files to wrong output location (#735)
+"useTemplate" within generator scripts now works reliably (https://github.com/bridge-core/dash-compiler/commit/7a26aff5b970946507f4194abd16ac68e2ff01b1)
diff --git a/public/packages.zip b/public/packages.zip
index 291ec177a..5f9ccb29d 100644
Binary files a/public/packages.zip and b/public/packages.zip differ
diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore
new file mode 100644
index 000000000..f4dfb82b2
--- /dev/null
+++ b/src-tauri/.gitignore
@@ -0,0 +1,4 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
new file mode 100644
index 000000000..5e841c11e
--- /dev/null
+++ b/src-tauri/Cargo.lock
@@ -0,0 +1,3928 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "adler32"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
+
+[[package]]
+name = "atk"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd"
+dependencies = [
+ "atk-sys",
+ "bitflags",
+ "glib",
+ "libc",
+]
+
+[[package]]
+name = "atk-sys"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps 6.0.3",
+]
+
+[[package]]
+name = "attohttpc"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7"
+dependencies = [
+ "flate2",
+ "http",
+ "log",
+ "native-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "url",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bridge"
+version = "0.0.0"
+dependencies = [
+ "chrono",
+ "discord-rich-presence",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "tokio",
+ "window-shadows",
+]
+
+[[package]]
+name = "brotli"
+version = "3.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
+
+[[package]]
+name = "bytemuck"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
+
+[[package]]
+name = "cairo-rs"
+version = "0.15.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc"
+dependencies = [
+ "bitflags",
+ "cairo-sys-rs",
+ "glib",
+ "libc",
+ "thiserror",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps 6.0.3",
+]
+
+[[package]]
+name = "cargo_toml"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa0e3586af56b3bfa51fca452bd56e8dbbbd5d8d81cbf0b7e4e35b695b537eb8"
+dependencies = [
+ "serde",
+ "toml",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfb"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c"
+dependencies = [
+ "byteorder",
+ "uuid 0.8.2",
+]
+
+[[package]]
+name = "cfg-expr"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "cfg-expr"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-integer",
+ "num-traits",
+ "time 0.1.44",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "cocoa"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
+dependencies = [
+ "bitflags",
+ "block",
+ "cocoa-foundation",
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318"
+dependencies = [
+ "bitflags",
+ "block",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "core-graphics"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cssparser"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a"
+dependencies = [
+ "cssparser-macros",
+ "dtoa-short",
+ "itoa 0.4.8",
+ "matches",
+ "phf 0.8.0",
+ "proc-macro2",
+ "quote",
+ "smallvec",
+ "syn",
+]
+
+[[package]]
+name = "cssparser-macros"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "ctor"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "cty"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
+
+[[package]]
+name = "cxx"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "darling"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dbus"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6"
+dependencies = [
+ "libc",
+ "libdbus-sys",
+ "winapi",
+]
+
+[[package]]
+name = "deflate"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
+dependencies = [
+ "adler32",
+ "byteorder",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version 0.4.0",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "discord-rich-presence"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47fc4beffb85ee1461588499073a4d9c20dcc7728c4b13d6b282ab6c508947e5"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "uuid 0.8.2",
+]
+
+[[package]]
+name = "dispatch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+
+[[package]]
+name = "dtoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
+
+[[package]]
+name = "dtoa-short"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6"
+dependencies = [
+ "dtoa",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
+
+[[package]]
+name = "embed_plist"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "field-offset"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92"
+dependencies = [
+ "memoffset",
+ "rustc_version 0.3.3",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide 0.5.4",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
+dependencies = [
+ "mac",
+ "new_debug_unreachable",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-task"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
+
+[[package]]
+name = "futures-util"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
+dependencies = [
+ "futures-core",
+ "futures-macro",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "gdk"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "gdk-pixbuf",
+ "gdk-sys",
+ "gio",
+ "glib",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gdk-pixbuf"
+version = "0.15.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a"
+dependencies = [
+ "bitflags",
+ "gdk-pixbuf-sys",
+ "gio",
+ "glib",
+ "libc",
+]
+
+[[package]]
+name = "gdk-pixbuf-sys"
+version = "0.15.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7"
+dependencies = [
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps 6.0.3",
+]
+
+[[package]]
+name = "gdk-sys"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "pkg-config",
+ "system-deps 6.0.3",
+]
+
+[[package]]
+name = "gdkx11-sys"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178"
+dependencies = [
+ "gdk-sys",
+ "glib-sys",
+ "libc",
+ "system-deps 6.0.3",
+ "x11",
+]
+
+[[package]]
+name = "generator"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc184cace1cea8335047a471cc1da80f18acf8a76f3bab2028d499e328948ec7"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustversion",
+ "windows 0.32.0",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "gio"
+version = "0.15.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b"
+dependencies = [
+ "bitflags",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "gio-sys",
+ "glib",
+ "libc",
+ "once_cell",
+ "thiserror",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.15.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps 6.0.3",
+ "winapi",
+]
+
+[[package]]
+name = "glib"
+version = "0.15.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d"
+dependencies = [
+ "bitflags",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "glib-macros",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "once_cell",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.15.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64"
+dependencies = [
+ "anyhow",
+ "heck 0.4.0",
+ "proc-macro-crate",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.15.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4"
+dependencies = [
+ "libc",
+ "system-deps 6.0.3",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "globset"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "fnv",
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.15.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps 6.0.3",
+]
+
+[[package]]
+name = "gtk"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0"
+dependencies = [
+ "atk",
+ "bitflags",
+ "cairo-rs",
+ "field-offset",
+ "futures-channel",
+ "gdk",
+ "gdk-pixbuf",
+ "gio",
+ "glib",
+ "gtk-sys",
+ "gtk3-macros",
+ "libc",
+ "once_cell",
+ "pango",
+ "pkg-config",
+]
+
+[[package]]
+name = "gtk-sys"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84"
+dependencies = [
+ "atk-sys",
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "system-deps 6.0.3",
+]
+
+[[package]]
+name = "gtk3-macros"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9"
+dependencies = [
+ "anyhow",
+ "proc-macro-crate",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "html5ever"
+version = "0.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "http"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa 1.0.4",
+]
+
+[[package]]
+name = "http-range"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
+[[package]]
+name = "ico"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a4b3331534254a9b64095ae60d3dc2a8225a7a70229cd5888be127cdc1f6804"
+dependencies = [
+ "byteorder",
+ "png 0.11.0",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
+dependencies = [
+ "crossbeam-utils",
+ "globset",
+ "lazy_static",
+ "log",
+ "memchr",
+ "regex",
+ "same-file",
+ "thread_local",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "image"
+version = "0.24.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "infer"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "inflate"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4"
+dependencies = [
+ "adler32",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "itoa"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
+
+[[package]]
+name = "javascriptcore-rs"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c"
+dependencies = [
+ "bitflags",
+ "glib",
+ "javascriptcore-rs-sys",
+]
+
+[[package]]
+name = "javascriptcore-rs-sys"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps 5.0.0",
+]
+
+[[package]]
+name = "jni"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
+dependencies = [
+ "cesu8",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "js-sys"
+version = "0.3.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-patch"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d"
+dependencies = [
+ "serde",
+ "serde_json",
+ "treediff",
+]
+
+[[package]]
+name = "kuchiki"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
+dependencies = [
+ "cssparser",
+ "html5ever",
+ "matches",
+ "selectors",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
+
+[[package]]
+name = "libdbus-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b"
+dependencies = [
+ "pkg-config",
+]
+
+[[package]]
+name = "line-wrap"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
+dependencies = [
+ "safemem",
+]
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "loom"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
+dependencies = [
+ "cfg-if",
+ "generator",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "mac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+
+[[package]]
+name = "mac-notification-sys"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e72d50edb17756489e79d52eb146927bec8eba9dd48faadf9ef08bca3791ad5"
+dependencies = [
+ "cc",
+ "dirs-next",
+ "objc-foundation",
+ "objc_id",
+ "time 0.3.17",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "markup5ever"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
+dependencies = [
+ "log",
+ "phf 0.8.0",
+ "phf_codegen",
+ "string_cache",
+ "string_cache_codegen",
+ "tendril",
+]
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "minisign-verify"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
+dependencies = [
+ "libc",
+ "log",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "ndk"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4"
+dependencies = [
+ "bitflags",
+ "jni-sys",
+ "ndk-sys",
+ "num_enum",
+ "thiserror",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+
+[[package]]
+name = "nodrop"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
+
+[[package]]
+name = "notify-rust"
+version = "4.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368e89ea58df747ce88be669ae44e79783c1d30bfd540ad0fc520b3f41f0b3b0"
+dependencies = [
+ "dbus",
+ "mac-notification-sys",
+ "tauri-winrt-notification",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+ "objc_exception",
+]
+
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc_exception"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
+
+[[package]]
+name = "open"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4a3100141f1733ea40b53381b0ae3117330735ef22309a190ac57b9576ea716"
+dependencies = [
+ "pathdiff",
+ "windows-sys 0.36.1",
+]
+
+[[package]]
+name = "openssl"
+version = "0.10.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "os_info"
+version = "3.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4750134fb6a5d49afc80777394ad5d95b04bc12068c6abb92fae8f43817270f"
+dependencies = [
+ "log",
+ "serde",
+ "winapi",
+]
+
+[[package]]
+name = "os_pipe"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dceb7e43f59c35ee1548045b2c72945a5a3bb6ce6d6f07cdc13dc8f6bc4930a"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "pango"
+version = "0.15.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f"
+dependencies = [
+ "bitflags",
+ "glib",
+ "libc",
+ "once_cell",
+ "pango-sys",
+]
+
+[[package]]
+name = "pango-sys"
+version = "0.15.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps 6.0.3",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
+
+[[package]]
+name = "pathdiff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+
+[[package]]
+name = "percent-encoding"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+
+[[package]]
+name = "pest"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8"
+dependencies = [
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_macros 0.8.0",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_macros 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
+dependencies = [
+ "phf_generator 0.8.0",
+ "phf_shared 0.8.0",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+dependencies = [
+ "phf_shared 0.8.0",
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared 0.10.0",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
+dependencies = [
+ "phf_generator 0.8.0",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+
+[[package]]
+name = "plist"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225"
+dependencies = [
+ "base64",
+ "indexmap",
+ "line-wrap",
+ "serde",
+ "time 0.3.17",
+ "xml-rs",
+]
+
+[[package]]
+name = "png"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925"
+dependencies = [
+ "bitflags",
+ "deflate",
+ "inflate",
+ "num-iter",
+]
+
+[[package]]
+name = "png"
+version = "0.17.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "flate2",
+ "miniz_oxide 0.6.2",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
+dependencies = [
+ "once_cell",
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.8",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "raw-window-handle"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a"
+dependencies = [
+ "cty",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom 0.2.8",
+ "redox_syscall",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "rfd"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea"
+dependencies = [
+ "block",
+ "dispatch",
+ "glib-sys",
+ "gobject-sys",
+ "gtk-sys",
+ "js-sys",
+ "lazy_static",
+ "log",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "raw-window-handle",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows 0.37.0",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
+dependencies = [
+ "semver 0.11.0",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver 1.0.14",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
+
+[[package]]
+name = "ryu"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.36.1",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "scratch"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
+
+[[package]]
+name = "security-framework"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "selectors"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe"
+dependencies = [
+ "bitflags",
+ "cssparser",
+ "derive_more",
+ "fxhash",
+ "log",
+ "matches",
+ "phf 0.8.0",
+ "phf_codegen",
+ "precomputed-hash",
+ "servo_arc",
+ "smallvec",
+ "thin-slice",
+]
+
+[[package]]
+name = "semver"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
+dependencies = [
+ "pest",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
+dependencies = [
+ "itoa 1.0.4",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa 1.0.4",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
+dependencies = [
+ "serde",
+ "serde_with_macros",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serialize-to-javascript"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb"
+dependencies = [
+ "serde",
+ "serde_json",
+ "serialize-to-javascript-impl",
+]
+
+[[package]]
+name = "serialize-to-javascript-impl"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "servo_arc"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432"
+dependencies = [
+ "nodrop",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shared_child"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "slab"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "soup2"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0"
+dependencies = [
+ "bitflags",
+ "gio",
+ "glib",
+ "libc",
+ "once_cell",
+ "soup2-sys",
+]
+
+[[package]]
+name = "soup2-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf"
+dependencies = [
+ "bitflags",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps 5.0.0",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "state"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b"
+dependencies = [
+ "loom",
+]
+
+[[package]]
+name = "string_cache"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08"
+dependencies = [
+ "new_debug_unreachable",
+ "once_cell",
+ "parking_lot",
+ "phf_shared 0.10.0",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "strum"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb"
+dependencies = [
+ "heck 0.3.3",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "system-deps"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e"
+dependencies = [
+ "cfg-expr 0.9.1",
+ "heck 0.3.3",
+ "pkg-config",
+ "toml",
+ "version-compare 0.0.11",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff"
+dependencies = [
+ "cfg-expr 0.11.0",
+ "heck 0.4.0",
+ "pkg-config",
+ "toml",
+ "version-compare 0.1.1",
+]
+
+[[package]]
+name = "tao"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42c460173627564bde252ca5ebf346ba5b37c5cee1a445782bacc8e9b8d38b5e"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "cc",
+ "cocoa",
+ "core-foundation",
+ "core-graphics",
+ "crossbeam-channel",
+ "dispatch",
+ "gdk",
+ "gdk-pixbuf",
+ "gdk-sys",
+ "gdkx11-sys",
+ "gio",
+ "glib",
+ "glib-sys",
+ "gtk",
+ "image",
+ "instant",
+ "jni",
+ "lazy_static",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "objc",
+ "once_cell",
+ "parking_lot",
+ "paste",
+ "png 0.17.7",
+ "raw-window-handle",
+ "scopeguard",
+ "serde",
+ "unicode-segmentation",
+ "uuid 1.2.1",
+ "windows 0.39.0",
+ "windows-implement",
+ "x11-dl",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "tauri"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac135e45c2923bd91edbb95a0d656f8d025389697e34d6d79166952bfa79c61c"
+dependencies = [
+ "anyhow",
+ "attohttpc",
+ "base64",
+ "cocoa",
+ "dirs-next",
+ "embed_plist",
+ "encoding_rs",
+ "flate2",
+ "futures-util",
+ "glib",
+ "glob",
+ "gtk",
+ "heck 0.4.0",
+ "http",
+ "ignore",
+ "minisign-verify",
+ "notify-rust",
+ "objc",
+ "once_cell",
+ "open",
+ "os_info",
+ "os_pipe",
+ "percent-encoding",
+ "rand 0.8.5",
+ "raw-window-handle",
+ "regex",
+ "rfd",
+ "semver 1.0.14",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "serialize-to-javascript",
+ "shared_child",
+ "state",
+ "tar",
+ "tauri-macros",
+ "tauri-runtime",
+ "tauri-runtime-wry",
+ "tauri-utils",
+ "tempfile",
+ "thiserror",
+ "time 0.3.17",
+ "tokio",
+ "url",
+ "uuid 1.2.1",
+ "webkit2gtk",
+ "webview2-com",
+ "windows 0.39.0",
+ "zip",
+]
+
+[[package]]
+name = "tauri-build"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef796f49abc98e6de0abe1b655120addc9d82363d8fc2304e71a4177c25e783c"
+dependencies = [
+ "anyhow",
+ "cargo_toml",
+ "heck 0.4.0",
+ "json-patch",
+ "semver 1.0.14",
+ "serde_json",
+ "tauri-utils",
+ "winres",
+]
+
+[[package]]
+name = "tauri-codegen"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcb77cf7bfe3d8f886e73a7fa6157587d015c599671180b76595c1aef175ba8"
+dependencies = [
+ "base64",
+ "brotli",
+ "ico",
+ "json-patch",
+ "plist",
+ "png 0.17.7",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "semver 1.0.14",
+ "serde",
+ "serde_json",
+ "sha2",
+ "tauri-utils",
+ "thiserror",
+ "time 0.3.17",
+ "uuid 1.2.1",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-macros"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f24f481b0b2acfc288ac78755f00ebea53992c7365a165af64cb5ae00806edea"
+dependencies = [
+ "heck 0.4.0",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "tauri-codegen",
+ "tauri-utils",
+]
+
+[[package]]
+name = "tauri-runtime"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc5d54c476defa5436e70e0d0a06e3cb0f49b6f863895995d5e3769411769cf"
+dependencies = [
+ "gtk",
+ "http",
+ "http-range",
+ "rand 0.8.5",
+ "raw-window-handle",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "thiserror",
+ "uuid 1.2.1",
+ "webview2-com",
+ "windows 0.39.0",
+]
+
+[[package]]
+name = "tauri-runtime-wry"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d78c55091701426c2519c7e9f1dc2dd33e533af4e75eae89cedc6995409351a2"
+dependencies = [
+ "cocoa",
+ "gtk",
+ "percent-encoding",
+ "rand 0.8.5",
+ "raw-window-handle",
+ "tauri-runtime",
+ "tauri-utils",
+ "uuid 1.2.1",
+ "webkit2gtk",
+ "webview2-com",
+ "windows 0.39.0",
+ "wry",
+]
+
+[[package]]
+name = "tauri-utils"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d64c9a09ba1538b8e67ae8c78c10904f36ce38d364bf7f089ec807032a826b02"
+dependencies = [
+ "brotli",
+ "ctor",
+ "glob",
+ "heck 0.4.0",
+ "html5ever",
+ "infer",
+ "json-patch",
+ "kuchiki",
+ "memchr",
+ "phf 0.10.1",
+ "proc-macro2",
+ "quote",
+ "semver 1.0.14",
+ "serde",
+ "serde_json",
+ "serde_with",
+ "thiserror",
+ "url",
+ "walkdir",
+ "windows 0.39.0",
+]
+
+[[package]]
+name = "tauri-winrt-notification"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b"
+dependencies = [
+ "quick-xml",
+ "strum",
+ "windows 0.39.0",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "tendril"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
+dependencies = [
+ "futf",
+ "mac",
+ "utf-8",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thin-slice"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
+
+[[package]]
+name = "thiserror"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "time"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
+dependencies = [
+ "itoa 1.0.4",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
+[[package]]
+name = "time-macros"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "treediff"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff"
+dependencies = [
+ "serde_json",
+]
+
+[[package]]
+name = "typenum"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "url"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom 0.2.8",
+]
+
+[[package]]
+name = "uuid"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83"
+dependencies = [
+ "getrandom 0.2.8",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version-compare"
+version = "0.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b"
+
+[[package]]
+name = "version-compare"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+
+[[package]]
+name = "web-sys"
+version = "0.3.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webkit2gtk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "gdk",
+ "gdk-sys",
+ "gio",
+ "gio-sys",
+ "glib",
+ "glib-sys",
+ "gobject-sys",
+ "gtk",
+ "gtk-sys",
+ "javascriptcore-rs",
+ "libc",
+ "once_cell",
+ "soup2",
+ "webkit2gtk-sys",
+]
+
+[[package]]
+name = "webkit2gtk-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3"
+dependencies = [
+ "atk-sys",
+ "bitflags",
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "gtk-sys",
+ "javascriptcore-rs-sys",
+ "libc",
+ "pango-sys",
+ "pkg-config",
+ "soup2-sys",
+ "system-deps 6.0.3",
+]
+
+[[package]]
+name = "webview2-com"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178"
+dependencies = [
+ "webview2-com-macros",
+ "webview2-com-sys",
+ "windows 0.39.0",
+ "windows-implement",
+]
+
+[[package]]
+name = "webview2-com-macros"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "webview2-com-sys"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7"
+dependencies = [
+ "regex",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "windows 0.39.0",
+ "windows-bindgen",
+ "windows-metadata",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "window-shadows"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c69eb48aac2da0199ea148c6f2c2610db8a0572b32a3dc857dec40ca22f1cec"
+dependencies = [
+ "cocoa",
+ "objc",
+ "raw-window-handle",
+ "windows-sys 0.36.1",
+]
+
+[[package]]
+name = "windows"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec"
+dependencies = [
+ "windows_aarch64_msvc 0.32.0",
+ "windows_i686_gnu 0.32.0",
+ "windows_i686_msvc 0.32.0",
+ "windows_x86_64_gnu 0.32.0",
+ "windows_x86_64_msvc 0.32.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647"
+dependencies = [
+ "windows_aarch64_msvc 0.37.0",
+ "windows_i686_gnu 0.37.0",
+ "windows_i686_msvc 0.37.0",
+ "windows_x86_64_gnu 0.37.0",
+ "windows_x86_64_msvc 0.37.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a"
+dependencies = [
+ "windows-implement",
+ "windows_aarch64_msvc 0.39.0",
+ "windows_i686_gnu 0.39.0",
+ "windows_i686_msvc 0.39.0",
+ "windows_x86_64_gnu 0.39.0",
+ "windows_x86_64_msvc 0.39.0",
+]
+
+[[package]]
+name = "windows-bindgen"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41"
+dependencies = [
+ "windows-metadata",
+ "windows-tokens",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7"
+dependencies = [
+ "syn",
+ "windows-tokens",
+]
+
+[[package]]
+name = "windows-metadata"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278"
+
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc 0.36.1",
+ "windows_i686_gnu 0.36.1",
+ "windows_i686_msvc 0.36.1",
+ "windows_x86_64_gnu 0.36.1",
+ "windows_x86_64_msvc 0.36.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc 0.42.0",
+ "windows_i686_gnu 0.42.0",
+ "windows_i686_msvc 0.42.0",
+ "windows_x86_64_gnu 0.42.0",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc 0.42.0",
+]
+
+[[package]]
+name = "windows-tokens"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
+
+[[package]]
+name = "winres"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
+dependencies = [
+ "toml",
+]
+
+[[package]]
+name = "wry"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "923d297b203eae65b095af16c02978b7932be1968012b4da7138390edf34dea5"
+dependencies = [
+ "base64",
+ "block",
+ "cocoa",
+ "core-graphics",
+ "crossbeam-channel",
+ "dunce",
+ "gdk",
+ "gio",
+ "glib",
+ "gtk",
+ "html5ever",
+ "http",
+ "kuchiki",
+ "libc",
+ "log",
+ "objc",
+ "objc_id",
+ "once_cell",
+ "serde",
+ "serde_json",
+ "sha2",
+ "soup2",
+ "tao",
+ "thiserror",
+ "url",
+ "webkit2gtk",
+ "webkit2gtk-sys",
+ "webview2-com",
+ "windows 0.39.0",
+ "windows-implement",
+]
+
+[[package]]
+name = "x11"
+version = "2.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7ae97874a928d821b061fce3d1fc52f08071dd53c89a6102bc06efcac3b2908"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11-dl"
+version = "2.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c83627bc137605acc00bb399c7b908ef460b621fc37c953db2b09f88c449ea6"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "xattr"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "xml-rs"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
+
+[[package]]
+name = "zip"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080"
+dependencies = [
+ "byteorder",
+ "crc32fast",
+ "crossbeam-utils",
+]
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
new file mode 100644
index 000000000..d66253de6
--- /dev/null
+++ b/src-tauri/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "bridge"
+version = "0.0.0"
+description = "The IDE for Minecraft Add-Ons"
+authors = ["you"]
+license = ""
+repository = ""
+edition = "2021"
+rust-version = "1.57"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[build-dependencies]
+tauri-build = { version = "1.1", features = [] }
+
+[dependencies]
+serde_json = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+tauri = { version = "1.1", features = ["api-all", "updater"] }
+discord-rich-presence = "0.2.3"
+chrono = "0.4.23"
+window-shadows = "0.2.0"
+tokio = { version = "1.23", features = ["process", "io-util", "sync"] }
+
+[features]
+# by default Tauri runs in production mode
+# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
+default = [ "custom-protocol" ]
+# this feature is used used for production builds where `devPath` points to the filesystem
+# DO NOT remove this
+custom-protocol = [ "tauri/custom-protocol" ]
diff --git a/src-tauri/build.rs b/src-tauri/build.rs
new file mode 100644
index 000000000..795b9b7c8
--- /dev/null
+++ b/src-tauri/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+ tauri_build::build()
+}
diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png
new file mode 100644
index 000000000..068cb65d7
Binary files /dev/null and b/src-tauri/icons/128x128.png differ
diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png
new file mode 100644
index 000000000..34babf993
Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ
diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png
new file mode 100644
index 000000000..68dd39e64
Binary files /dev/null and b/src-tauri/icons/32x32.png differ
diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png
new file mode 100644
index 000000000..6d85c8a24
Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ
diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png
new file mode 100644
index 000000000..f354b454d
Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ
diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png
new file mode 100644
index 000000000..041033a2a
Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ
diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png
new file mode 100644
index 000000000..f0488760a
Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ
diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png
new file mode 100644
index 000000000..5712d4a5a
Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ
diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png
new file mode 100644
index 000000000..d23f38073
Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ
diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png
new file mode 100644
index 000000000..12ae0d0f3
Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ
diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png
new file mode 100644
index 000000000..3655f9c34
Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ
diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png
new file mode 100644
index 000000000..43c4b59d9
Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ
diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png
new file mode 100644
index 000000000..41390191a
Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ
diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns
new file mode 100644
index 000000000..b3275f94f
Binary files /dev/null and b/src-tauri/icons/icon.icns differ
diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico
new file mode 100644
index 000000000..17d23d6d3
Binary files /dev/null and b/src-tauri/icons/icon.ico differ
diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png
new file mode 100644
index 000000000..0875f9de5
Binary files /dev/null and b/src-tauri/icons/icon.png differ
diff --git a/src-tauri/src/fs_extra.rs b/src-tauri/src/fs_extra.rs
new file mode 100644
index 000000000..09e50063a
--- /dev/null
+++ b/src-tauri/src/fs_extra.rs
@@ -0,0 +1,66 @@
+use std::path::Path;
+use std::process::Command;
+use std::{fs, io};
+
+pub fn copy_dir_all(src: impl AsRef
, dest: impl AsRef) -> io::Result<()> {
+ fs::create_dir_all(&dest)?;
+
+ for entry in fs::read_dir(src)? {
+ let entry = entry?;
+ let file_type = entry.file_type()?;
+
+ if file_type.is_dir() {
+ copy_dir_all(entry.path(), dest.as_ref().join(entry.file_name()))?;
+ } else {
+ fs::copy(entry.path(), dest.as_ref().join(entry.file_name()))?;
+ }
+ }
+ Ok(())
+}
+
+/**
+ * Copy a directory from src to dest
+ */
+#[tauri::command]
+pub fn copy_directory(src: String, dest: String) -> Result<(), String> {
+ copy_dir_all(src, dest).map_err(|e| e.to_string())
+}
+
+/**
+ * Opens a file in the file explorer
+ */
+#[tauri::command]
+pub async fn reveal_in_file_explorer(path: &str) -> Result<(), String> {
+ if cfg!(target_os = "windows") {
+ Command::new("explorer")
+ .args(["/select,", path]) // The comma after select is not a typo
+ .spawn()
+ .expect("Failed to open file explorer");
+ } else if cfg!(target_os = "macos") {
+ Command::new("open")
+ .args(["-R", path])
+ .spawn()
+ .expect("Failed to open finder");
+ } else {
+ // TODO: Linux
+ }
+
+ Ok(())
+}
+
+/**
+ * A function that returns when a file was last modified and its file data
+ */
+#[tauri::command]
+pub async fn get_file_data(path: &str) -> Result<(u64, Vec), String> {
+ let metadata = fs::metadata(path).expect("Failed to get file metadata");
+ let modified = metadata
+ .modified()
+ .expect("Failed to get file modified time")
+ .duration_since(std::time::UNIX_EPOCH)
+ .expect("Time went backwards")
+ .as_secs();
+ let data = fs::read(path).expect("Failed to read file");
+
+ Ok((modified, data))
+}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
new file mode 100644
index 000000000..7b43a5d0b
--- /dev/null
+++ b/src-tauri/src/main.rs
@@ -0,0 +1,82 @@
+#![cfg_attr(
+ all(not(debug_assertions), target_os = "windows"),
+ windows_subsystem = "windows"
+)]
+
+use discord_rich_presence::{activity, DiscordIpc, DiscordIpcClient};
+use tauri::{Manager, Menu};
+use terminal::AllTerminals;
+use window_shadows::set_shadow;
+mod fs_extra;
+mod terminal;
+
+fn main() {
+ let mut menu = Menu::os_default(&"bridge. v2");
+
+ if cfg!(target_os = "windows") {
+ menu = Menu::new()
+ }
+
+ tauri::Builder::default()
+ .manage::(AllTerminals(Default::default()))
+ .setup(|app| {
+ // `main` here is the window label; it is defined under `tauri.conf.json`
+ let main_window = app.get_window("main").unwrap();
+
+ if cfg!(target_os = "windows") {
+ set_shadow(&main_window, true).expect("Unable to set window shadow");
+ }
+
+ // Try to set Discord rich presence
+ match set_rich_presence() {
+ Ok(mut discord_client) => {
+ println!("Rich presence set!");
+
+ // listen to "tauri://destroyed" (emitted on the `main` window)
+ main_window.once("tauri://destroyed", move |_| {
+ println!("Window closes!");
+ discord_client.close().expect("Failed to close Discord IPC");
+ });
+ }
+ Err(e) => {
+ println!("Error setting rich presence: {}", e);
+ }
+ };
+
+ Ok(())
+ })
+ .menu(menu)
+ .invoke_handler(tauri::generate_handler![
+ fs_extra::reveal_in_file_explorer,
+ fs_extra::get_file_data,
+ fs_extra::copy_directory,
+ terminal::execute_command,
+ terminal::kill_command,
+ ])
+ .run(tauri::generate_context!())
+ .expect("error while running tauri application");
+}
+
+fn set_rich_presence() -> Result> {
+ let mut client = DiscordIpcClient::new("1045743881393815552")?;
+
+ let state_str = "Developing add-ons...";
+
+ client.connect()?;
+ client.set_activity(
+ activity::Activity::new()
+ .state(state_str)
+ .assets(
+ activity::Assets::new()
+ .large_image("logo_tile")
+ .large_text("bridge. v2"),
+ )
+ .timestamps(activity::Timestamps::new().start(chrono::Utc::now().timestamp_millis()))
+ .buttons(vec![
+ activity::Button::new("Open Editor", "https://editor.bridge-core.app/"),
+ // activity::Button::new("Read More...", "https://bridge-core.app/"),
+ ]),
+ )?;
+
+ Ok(client)
+}
diff --git a/src-tauri/src/terminal.rs b/src-tauri/src/terminal.rs
new file mode 100644
index 000000000..480a5acd5
--- /dev/null
+++ b/src-tauri/src/terminal.rs
@@ -0,0 +1,145 @@
+use std::collections::HashMap;
+/**
+ * Backend for bridge.'s terminal
+ */
+use std::env;
+use std::path::PathBuf;
+use std::process::Stdio;
+use std::sync::Arc;
+use tokio::{
+ io::{AsyncBufReadExt, BufReader},
+ process::Command,
+ sync::Mutex,
+};
+
+// Manager extends Mutex to allow for thread-safe acces
+#[derive(Default)]
+pub struct AllTerminals(pub Arc>>);
+
+#[derive(Clone, serde::Serialize)]
+struct MessagePayload {
+ // Kind is either stdout or stderr
+ message: String,
+}
+
+#[tauri::command]
+pub async fn execute_command(
+ window: tauri::Window,
+ state: tauri::State<'_, AllTerminals>,
+ cwd: String,
+ command: String,
+) -> Result<(), String> {
+ // Set the current working directory
+ let cwd_path = PathBuf::from(cwd);
+ env::set_current_dir(&cwd_path).expect("Failed to set cwd");
+
+ let mut all_terminals = state.0.lock().await;
+ // This can be changed to take a dynamic key in the future in order to support multiple terminals
+ let maybe_child_id = all_terminals.get_mut(&*"current_child");
+
+ // Kill current child
+ if let Some(child_id) = maybe_child_id {
+ kill_process_with_id(child_id.to_string())
+ }
+
+ // Spawn command
+ let mut child = if cfg!(target_os = "windows") {
+ Command::new("cmd")
+ .kill_on_drop(true)
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .args(["/C", &command])
+ .spawn()
+ .expect("Failed to spawn command")
+ } else {
+ Command::new("sh")
+ .kill_on_drop(true)
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .args(["-c", &command])
+ .spawn()
+ .expect("Failed to spawn command")
+ };
+
+ let stdout = child
+ .stdout
+ .take()
+ .expect("Child did not have a handle to stdout");
+ let stderr = child
+ .stderr
+ .take()
+ .expect("Child did not have a handle to stderr");
+
+ let mut stdout_reader = BufReader::new(stdout).lines();
+ let mut stderr_reader = BufReader::new(stderr).lines();
+
+ let window_stderr = window.clone();
+ let window_done = window.clone();
+
+ tokio::spawn(async move {
+ while let Some(line) = stdout_reader
+ .next_line()
+ .await
+ .expect("Failed to read line")
+ {
+ window
+ .emit("onStdoutMessage", MessagePayload { message: line })
+ .expect("Failed to emit message");
+ }
+ });
+ tokio::spawn(async move {
+ while let Some(line) = stderr_reader
+ .next_line()
+ .await
+ .expect("Failed to read line")
+ {
+ window_stderr
+ .emit("onStderrMessage", MessagePayload { message: line })
+ .expect("Failed to emit message");
+ }
+ });
+
+ all_terminals.insert(
+ "current_child".to_string(),
+ child.id().expect("Failed to get child id").to_string(),
+ );
+ tokio::spawn(async move {
+ child.wait().await.expect("Failed to wait on child process");
+ window_done
+ .emit("onCommandDone", ())
+ .expect("Failed to emit message");
+ });
+
+ Ok(())
+}
+
+#[tauri::command]
+pub async fn kill_command(state: tauri::State<'_, AllTerminals>) -> Result<(), String> {
+ let mut all_terminals = state.0.lock().await;
+ // This can be changed to take a dynamic key in the future in order to support multiple terminals
+ let maybe_child_id = all_terminals.get_mut(&*"current_child");
+
+ // Kill current child
+ if let Some(child_id) = maybe_child_id {
+ kill_process_with_id(child_id.to_string())
+ }
+
+ // Remove child from hashmap
+ all_terminals.remove(&*"current_child");
+
+ Ok(())
+}
+
+fn kill_process_with_id(id: String) {
+ if cfg!(target_os = "windows") {
+ std::process::Command::new("taskkill")
+ .args(["/F", "/PID", &id])
+ .output()
+ .expect("Failed to kill process");
+ } else {
+ std::process::Command::new("kill")
+ .args([&id])
+ .output()
+ .expect("Failed to kill process");
+ }
+}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
new file mode 100644
index 000000000..ad59440f9
--- /dev/null
+++ b/src-tauri/tauri.conf.json
@@ -0,0 +1,77 @@
+{
+ "build": {
+ "beforeDevCommand": "VITE_IS_TAURI_APP=true npm run dev",
+ "beforeBuildCommand": "VITE_IS_TAURI_APP=true npm run build",
+ "devPath": "http://localhost:8080/",
+ "distDir": "../dist",
+ "withGlobalTauri": true
+ },
+ "package": {
+ "productName": "bridge",
+ "version": "../package.json"
+ },
+ "tauri": {
+ "allowlist": {
+ "all": true,
+ "fs": {
+ "scope": ["**/*"]
+ }
+ },
+ "bundle": {
+ "active": true,
+ "category": "DeveloperTool",
+ "copyright": "",
+ "deb": {
+ "depends": []
+ },
+ "externalBin": [],
+ "icon": [
+ "icons/32x32.png",
+ "icons/128x128.png",
+ "icons/128x128@2x.png",
+ "icons/icon.icns",
+ "icons/icon.ico"
+ ],
+ "identifier": "com.bridge.dev",
+ "longDescription": "",
+ "macOS": {
+ "entitlements": null,
+ "exceptionDomain": "",
+ "frameworks": [],
+ "providerShortName": null,
+ "signingIdentity": null
+ },
+ "resources": [],
+ "shortDescription": "",
+ "targets": "all",
+ "windows": {
+ "certificateThumbprint": null,
+ "digestAlgorithm": "sha256",
+ "timestampUrl": ""
+ }
+ },
+ "security": {
+ "csp": null
+ },
+ "updater": {
+ "active": true,
+ "dialog": false,
+ "endpoints": [
+ "https://github.com/bridge-core/editor/releases/latest/download/latest.json"
+ ],
+ "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDJFQkNGOUQ2REIzMUU5MDQKUldRRTZUSGIxdm04TGtiQzFEaE1GdVBPbUJYUjJDc25rQjg3RjlyKzdya2RUZVN1RkZ1azVBVmcK"
+ },
+ "windows": [
+ {
+ "label": "main",
+ "fullscreen": false,
+ "height": 900,
+ "resizable": true,
+ "title": "",
+ "width": 1200,
+ "titleBarStyle": "Overlay",
+ "acceptFirstMouse": true
+ }
+ ]
+ }
+}
diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json
new file mode 100644
index 000000000..8d0db1ce0
--- /dev/null
+++ b/src-tauri/tauri.windows.conf.json
@@ -0,0 +1,19 @@
+{
+ "build": {
+ "beforeDevCommand": "SET VITE_IS_TAURI_APP=true && npm run dev",
+ "beforeBuildCommand": "SET VITE_IS_TAURI_APP=true && npm run build"
+ },
+ "tauri": {
+ "windows": [
+ {
+ "label": "main",
+ "decorations": false,
+ "fullscreen": false,
+ "height": 900,
+ "resizable": true,
+ "title": "",
+ "width": 1200
+ }
+ ]
+ }
+}
diff --git a/src/App.ts b/src/App.ts
index 1b8941e4a..f1841ac15 100644
--- a/src/App.ts
+++ b/src/App.ts
@@ -1,6 +1,5 @@
import '/@/components/Notifications/Errors'
import '/@/components/Languages/LanguageManager'
-import '/@/components/App/ServiceWorker'
import Vue from 'vue'
import { EventSystem } from '/@/components/Common/Event/EventSystem'
@@ -39,15 +38,23 @@ import { PackExplorer } from '/@/components/PackExplorer/PackExplorer'
import { PersistentNotification } from '/@/components/Notifications/PersistentNotification'
import { version as appVersion } from '/@/utils/app/version'
import { platform } from '/@/utils/os'
-import { virtualProjectName } from '/@/components/Projects/Project/Project'
import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
import { getStorageDirectory } from '/@/utils/getStorageDirectory'
import { FolderImportManager } from '/@/components/ImportFolder/Manager'
import { StartParamManager } from '/@/components/StartParams/Manager'
import { ViewFolders } from '/@/components/ViewFolders/ViewFolders'
import { SidebarManager } from '/@/components/Sidebar/Manager'
-import { ViewComMojangProject } from './components/OutputFolders/ComMojang/Sidebar/ViewProject'
-import { InformationWindow } from './components/Windows/Common/Information/InformationWindow'
+import { ViewComMojangProject } from '/@/components/OutputFolders/ComMojang/Sidebar/ViewProject'
+import { InformationWindow } from '/@/components/Windows/Common/Information/InformationWindow'
+import { BottomPanel } from '/@/components/BottomPanel/BottomPanel'
+
+if (import.meta.env.VITE_IS_TAURI_APP) {
+ // Import Tauri updater for native builds
+ import('./components/App/Tauri/TauriUpdater')
+} else {
+ // Only import service worker for non-Tauri builds
+ import('/@/components/App/ServiceWorker')
+}
export class App {
public static readonly windowState = new WindowState()
@@ -92,6 +99,7 @@ export class App {
public static readonly fileType = markRaw(new FileTypeLibrary())
public static readonly packType = markRaw(new PackTypeLibrary())
public static readonly sidebar = new SidebarManager()
+ public static readonly bottomPanel = markRaw(new BottomPanel())
public readonly mobile: Mobile
@@ -166,11 +174,7 @@ export class App {
// Only prompt in prod mode so we can use HMR in dev mode
if (import.meta.env.PROD) {
window.addEventListener('beforeunload', (event) => {
- if (
- this.tabSystem?.hasUnsavedTabs ||
- this.taskManager.hasRunningTasks ||
- isUsingFileSystemPolyfill.value
- ) {
+ if (this.shouldWarnBeforeClose) {
event.preventDefault()
event.returnValue = saveWarning
return saveWarning
@@ -179,6 +183,18 @@ export class App {
}
}
+ get shouldWarnBeforeClose() {
+ if (
+ !import.meta.env.VITE_IS_TAURI_APP &&
+ isUsingFileSystemPolyfill.value
+ )
+ return true
+
+ return (
+ this.tabSystem?.hasUnsavedTabs || this.taskManager.hasRunningTasks
+ )
+ }
+
static openUrl(url: string, id?: string, openInBrowser = false) {
if (settingsState?.general?.openLinksInBrowser || openInBrowser)
return window.open(url, '_blank')
@@ -198,6 +214,13 @@ export class App {
this.instance.fileSystem.setup(await getStorageDirectory())
+ if (import.meta.env.VITE_IS_TAURI_APP) {
+ // TauriFs env -> bridge. folder is the same as getStorageDirectory()
+ this.instance.bridgeFolderSetup.dispatch()
+ // Load projects
+ this.instance.projectManager.loadProjects(true)
+ }
+
// Show changelog after an update
if (await get('firstStartAfterUpdate')) {
await set('firstStartAfterUpdate', false)
@@ -271,7 +294,10 @@ export class App {
}
// Warn about saving projects
- if (isUsingFileSystemPolyfill.value) {
+ if (
+ isUsingFileSystemPolyfill.value &&
+ !import.meta.env.VITE_IS_TAURI_APP
+ ) {
const saveWarning = new PersistentNotification({
id: 'bridge-save-warning',
icon: 'mdi-alert-circle-outline',
diff --git a/src/App.vue b/src/App.vue
index 0dced0aa6..0952382c1 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -35,8 +35,6 @@
no-gutters
class="d-flex fill-area"
:class="{
- 'ml-2': !isSidebarContentVisible && isSidebarRight,
- 'mr-2': !isSidebarContentVisible && !isSidebarRight,
'flex-row-reverse': isSidebarRight,
}"
>
@@ -56,7 +54,7 @@
class="d-flex"
:class="{ 'flex-column': $vuetify.breakpoint.mobile }"
:style="{
- height: `calc(${windowSize.currentHeight}px - ${appToolbarHeight})`,
+ height: `calc(${windowSize.currentHeight}px - ${appToolbarHeight} - ${bottomPanelHeight}px)`,
}"
>
-
+
+
+
@@ -120,6 +125,8 @@ import {
setFullscreenElement,
useFullScreen,
} from './components/TabSystem/TabContextMenu/Fullscreen'
+import BottomPanel from './components/BottomPanel/BottomPanel.vue'
+import { useSidebarState } from './components/Composables/Sidebar/useSidebarState'
export default {
name: 'App',
@@ -129,12 +136,17 @@ export default {
const { tabSystem, tabSystems, shouldRenderWelcomeScreen } =
useTabSystem()
const { isInFullScreen } = useFullScreen()
+ const { isNavVisible, isContentVisible, isAttachedRight } =
+ useSidebarState()
return {
tabSystem,
tabSystems,
shouldRenderWelcomeScreen,
isInFullScreen,
+ sidebarNavigationVisible: isNavVisible,
+ isSidebarContentVisible: isContentVisible,
+ isSidebarRight: isAttachedRight,
}
},
@@ -155,6 +167,7 @@ export default {
TabSystem,
WelcomeScreen,
SidebarContent,
+ BottomPanel,
},
data: () => ({
@@ -168,24 +181,6 @@ export default {
}),
computed: {
- isSidebarContentVisible() {
- if (this.isInFullScreen) return false
-
- return (
- this.sidebarNavigationVisible &&
- App.sidebar.isContentVisible.value
- )
- },
- sidebarNavigationVisible() {
- return App.sidebar.isNavigationVisible.value
- },
- isSidebarRight() {
- return (
- this.settingsState &&
- this.settingsState.sidebar &&
- this.settingsState.sidebar.isSidebarRight
- )
- },
sidebarSize() {
let size =
this.settingsState && this.settingsState.sidebar
@@ -212,6 +207,9 @@ export default {
? `${this.settingsState.appearance.font}, system-ui !important`
: `Roboto, system-ui !important`
},
+ bottomPanelHeight() {
+ return App.bottomPanel.currentHeight.value
+ },
},
methods: {
openSidebar() {
@@ -311,6 +309,9 @@ summary::-webkit-details-marker {
.cursor-pointer {
cursor: pointer;
}
+.no-pointer-events {
+ pointer-events: none;
+}
.outlined {
border-width: thin;
border-color: #555555;
diff --git a/src/components/App/ServiceWorker.ts b/src/components/App/ServiceWorker.ts
index 363fbbbb7..45b7aa0f2 100644
--- a/src/components/App/ServiceWorker.ts
+++ b/src/components/App/ServiceWorker.ts
@@ -2,7 +2,6 @@
import { registerSW } from 'virtual:pwa-register'
import { createNotification } from '/@/components/Notifications/create'
-import { App } from '/@/App'
import { set } from 'idb-keyval'
const updateSW = registerSW({
diff --git a/src/components/App/Tauri/TauriUpdater.ts b/src/components/App/Tauri/TauriUpdater.ts
new file mode 100644
index 000000000..c4c4ae203
--- /dev/null
+++ b/src/components/App/Tauri/TauriUpdater.ts
@@ -0,0 +1,27 @@
+import { createNotification } from '../../Notifications/create'
+import { checkUpdate, installUpdate } from '@tauri-apps/api/updater'
+import { relaunch } from '@tauri-apps/api/process'
+
+checkUpdate()
+ .then((update) => {
+ if (update.shouldUpdate) {
+ const notification = createNotification({
+ icon: 'mdi-update',
+ color: 'primary',
+ message: 'sidebar.notifications.updateAvailable.message',
+ textColor: 'white',
+ onClick: async () => {
+ // Dispose the notification
+ notification.dispose()
+ // Install the update
+ await installUpdate()
+ // ...and finally relaunch the app
+ await relaunch()
+ },
+ })
+ }
+ })
+ .catch((err: any) => {
+ console.error(`[TauriUpdater] ${err}`)
+ return null
+ })
diff --git a/src/components/BottomPanel/BottomPanel.css b/src/components/BottomPanel/BottomPanel.css
new file mode 100644
index 000000000..23df6630a
--- /dev/null
+++ b/src/components/BottomPanel/BottomPanel.css
@@ -0,0 +1,50 @@
+.bottom-panel-content {
+ height: 100%;
+}
+
+/* Tab bar */
+.bottom-panel-tab-bar {
+ width: 100%;
+ overflow-x: auto;
+ overflow-y: visible;
+ padding: 4px 0;
+}
+.bottom-panel-tab-bar::-webkit-scrollbar {
+ display: none;
+}
+
+/* Tabs */
+.bottom-panel-tab {
+ display: flex;
+ align-items: center;
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: 1.25px;
+
+ cursor: pointer;
+ padding: 1px 8px;
+ border-radius: 8px;
+ background: var(--v-sidebarSelection-base);
+ text-transform: uppercase;
+
+ transition: transform 0.1s ease-in-out;
+}
+.bottom-panel-tab i {
+ font-size: 18px !important;
+}
+.bottom-panel-tab-active {
+ cursor: default;
+ z-index: 1;
+ background: var(--v-primary-base);
+ transform: scale(1.1);
+}
+
+/* Panel content */
+.bottom-panel-content-container {
+ /* Full height - tab bar height - tab bar padding - vertical padding */
+ height: calc(100% - 39px - 8px);
+ overflow-y: auto;
+}
+.bottom-panel-content-container-full-height {
+ height: 100%;
+}
diff --git a/src/components/BottomPanel/BottomPanel.tsx b/src/components/BottomPanel/BottomPanel.tsx
new file mode 100644
index 000000000..7cceb52c5
--- /dev/null
+++ b/src/components/BottomPanel/BottomPanel.tsx
@@ -0,0 +1,84 @@
+import { computed, ref } from 'vue'
+import { JSX } from 'solid-js/types'
+import './BottomPanel.css'
+import { LogPanel } from '../Compiler/LogPanel/Panel'
+import { App } from '/@/App'
+
+interface ITab {
+ name: string
+ icon: string
+ component: () => JSX.Element
+}
+
+export class BottomPanel {
+ public readonly isVisible = ref(false)
+ public readonly height = ref(400)
+ public readonly tabs = ref([])
+ public readonly activeTab = ref(null)
+ public readonly currentHeight = computed(() =>
+ this.isVisible.value ? this.height.value : 0
+ )
+
+ constructor() {
+ this.setupTerminal()
+
+ this.addTab({
+ icon: 'mdi-bug',
+ name: 'bottomPanel.problems.name',
+ component: () => (
+
+ We are still working on displaying problems with your
+ project here...
+
+ ),
+ })
+
+ setTimeout(() => {
+ App.getApp().then((app) => {
+ this.addTab({
+ icon: 'mdi-cogs',
+ name: 'bottomPanel.compiler.name',
+ component: () =>
+ LogPanel({
+ compilerWindow: app.windows.compilerWindow,
+ }),
+ })
+ })
+ })
+ }
+
+ async setupTerminal() {
+ if (!import.meta.env.VITE_IS_TAURI_APP) return
+ const { Terminal } = await import('./Terminal/Terminal')
+ const { TerminalInput } = await import('./Terminal/Input')
+ const { TerminalOutput } = await import('./Terminal/Output')
+
+ const terminal = new Terminal()
+
+ this.addTab(
+ {
+ icon: 'mdi-console-line',
+ name: 'bottomPanel.terminal.name',
+ component: () => (
+ <>
+
+
+ >
+ ),
+ },
+ true
+ )
+ }
+
+ selectTab(tab: ITab) {
+ this.activeTab.value = tab
+ }
+ addTab(tab: ITab, asFirst = false) {
+ if (asFirst) this.tabs.value.unshift(tab)
+ else this.tabs.value.push(tab)
+
+ if (this.activeTab.value === null || asFirst) {
+ this.activeTab.value = tab
+ }
+ }
+}
diff --git a/src/components/BottomPanel/BottomPanel.vue b/src/components/BottomPanel/BottomPanel.vue
new file mode 100644
index 000000000..945fa8c75
--- /dev/null
+++ b/src/components/BottomPanel/BottomPanel.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+ mdi-chevron-up
+ mdi-chevron-down
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/BottomPanel/PanelContent.tsx b/src/components/BottomPanel/PanelContent.tsx
new file mode 100644
index 000000000..5ae27e1ed
--- /dev/null
+++ b/src/components/BottomPanel/PanelContent.tsx
@@ -0,0 +1,31 @@
+import { Component, Show } from 'solid-js'
+import { Dynamic } from 'solid-js/web'
+import { toSignal } from '../Solid/toSignal'
+import { toVue } from '../Solid/toVue'
+import { TabBar } from './TabBar'
+import { App } from '/@/App'
+
+export const PanelContent: Component = (props) => {
+ const [activeTab] = toSignal(App.bottomPanel.activeTab)
+ const [tabs] = toSignal(App.bottomPanel.tabs)
+
+ return (
+
+ )
+}
+
+export const VuePanelContent = toVue(PanelContent)
diff --git a/src/components/BottomPanel/TabBar.tsx b/src/components/BottomPanel/TabBar.tsx
new file mode 100644
index 000000000..0f914443d
--- /dev/null
+++ b/src/components/BottomPanel/TabBar.tsx
@@ -0,0 +1,47 @@
+import { Component, For } from 'solid-js'
+import { useTranslations } from '../Composables/useTranslations'
+import { useRipple } from '../Solid/Directives/Ripple/Ripple'
+import { SolidIcon } from '../Solid/Icon/SolidIcon'
+import { SolidIconButton } from '../Solid/Inputs/IconButton/IconButton'
+import { SolidSpacer } from '../Solid/SolidSpacer'
+import { toSignal } from '../Solid/toSignal'
+import { App } from '/@/App'
+
+export const TabBar: Component = (props) => {
+ const ripple = useRipple()
+ const { t } = useTranslations()
+ const [tabs] = toSignal(App.bottomPanel.tabs)
+ const [activeTab] = toSignal(App.bottomPanel.activeTab)
+ const [_, setIsVisible] = toSignal(App.bottomPanel.isVisible)
+
+ return (
+
+
+
+ {(tab, i) => (
+ 0,
+ }}
+ onClick={() => App.bottomPanel.selectTab(tab)}
+ >
+
+ {t(tab.name)}
+
+ )}
+
+
+
+
+
+
setIsVisible(false)}
+ />
+
+ )
+}
diff --git a/src/components/BottomPanel/Terminal/Input.tsx b/src/components/BottomPanel/Terminal/Input.tsx
new file mode 100644
index 000000000..93c143ea2
--- /dev/null
+++ b/src/components/BottomPanel/Terminal/Input.tsx
@@ -0,0 +1,47 @@
+import { Component, createSignal, Show } from 'solid-js'
+import { useTranslations } from '../../Composables/useTranslations'
+import { SolidIconButton } from '../../Solid/Inputs/IconButton/IconButton'
+import { TextField } from '../../Solid/Inputs/TextField/TextField'
+import { toSignal } from '../../Solid/toSignal'
+import type { Terminal } from './Terminal'
+
+export const TerminalInput: Component<{
+ terminal: Terminal
+}> = (props) => {
+ const { t } = useTranslations()
+ const [input, setInput] = createSignal('')
+ const [hasRunningTask] = toSignal(props.terminal.hasRunningTask)
+ const [output, setOutput] = toSignal(props.terminal.output)
+
+ const onEnter = () => {
+ props.terminal.executeCommand(input())
+ setInput('')
+ }
+
+ return (
+
+
+
+ props.terminal.killCommand()}
+ />
+
+ setOutput([])}
+ />
+
+ )
+}
diff --git a/src/components/BottomPanel/Terminal/Output.tsx b/src/components/BottomPanel/Terminal/Output.tsx
new file mode 100644
index 000000000..f6b13eb67
--- /dev/null
+++ b/src/components/BottomPanel/Terminal/Output.tsx
@@ -0,0 +1,59 @@
+import { Component, For, Show } from 'solid-js'
+import { SolidIcon } from '../../Solid/Icon/SolidIcon'
+import { toSignal } from '../../Solid/toSignal'
+import type { Terminal } from './Terminal'
+
+export const TerminalOutput: Component<{
+ terminal: Terminal
+}> = (props) => {
+ const [output] = toSignal(props.terminal.output)
+ const [cwd] = toSignal(props.terminal.cwd)
+
+ const prettyCwd = () => {
+ const tmp = cwd()
+ .replace(props.terminal.baseCwd, '')
+ .replace(/\\/g, '/')
+ if (tmp === '') return 'bridge'
+ return `bridge${tmp}`
+ }
+
+ return (
+ <>
+ {/* Show current cwd */}
+
+
+
+
+
+ {prettyCwd()}
+
+
+ {/* Render terminal output */}
+
+
+ {({ kind, time, currentCwdName, msg }, i) => (
+
+ [{time}]
+
+
+ {currentCwdName} >
+
+ {msg}
+
+
+ )}
+
+
+ >
+ )
+}
diff --git a/src/components/BottomPanel/Terminal/Terminal.css b/src/components/BottomPanel/Terminal/Terminal.css
new file mode 100644
index 000000000..b9e13da8d
--- /dev/null
+++ b/src/components/BottomPanel/Terminal/Terminal.css
@@ -0,0 +1,11 @@
+.terminal-line {
+ line-break: normal;
+}
+
+.terminal-output-container {
+ /* full height - terminal input bar height - cwd display height - a few pixels to prevent double scrollbar */
+ height: calc(100% - 58px - 33px - 2px);
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column-reverse;
+}
diff --git a/src/components/BottomPanel/Terminal/Terminal.ts b/src/components/BottomPanel/Terminal/Terminal.ts
new file mode 100644
index 000000000..c9832a536
--- /dev/null
+++ b/src/components/BottomPanel/Terminal/Terminal.ts
@@ -0,0 +1,110 @@
+import { markRaw, ref } from 'vue'
+import { invoke } from '@tauri-apps/api/tauri'
+import { App } from '/@/App'
+import { Signal } from '../../Common/Event/Signal'
+import { appLocalDataDir, isAbsolute, join, sep } from '@tauri-apps/api/path'
+import { exists } from '@tauri-apps/api/fs'
+import { listen, Event } from '@tauri-apps/api/event'
+import './Terminal.css'
+
+type TMessageKind = 'stdout' | 'stderr' | 'stdin'
+
+interface IMessage {
+ // Format HH:MM:SS
+ time: string
+ kind: TMessageKind
+ currentCwdName: string
+ msg: string
+}
+
+interface IMessagePayload {
+ message: string
+}
+
+export class Terminal {
+ output = ref([])
+ hasRunningTask = ref(false)
+ baseCwd = ''
+ cwd = ref('')
+ setupDone = markRaw(new Signal())
+
+ constructor() {
+ setTimeout(() => this.setup())
+
+ listen('onStdoutMessage', ({ payload }: Event) => {
+ this.addToOutput(payload.message, 'stdout')
+ })
+ listen('onStderrMessage', ({ payload }: Event) => {
+ this.addToOutput(payload.message, 'stderr')
+ })
+ listen('onCommandDone', () => {
+ this.hasRunningTask.value = false
+ })
+ }
+
+ async setup() {
+ const app = await App.getApp()
+ await app.projectManager.projectReady.fired
+
+ this.baseCwd = await join(await appLocalDataDir(), 'bridge')
+
+ if (this.cwd.value === '') this.cwd.value = this.baseCwd
+
+ this.setupDone.dispatch()
+ }
+
+ addToOutput(msg: string, kind: TMessageKind) {
+ this.output.value.unshift({
+ time: new Date().toLocaleTimeString(),
+ kind,
+ currentCwdName: this.cwd.value.split(sep).pop()!,
+ // Replace ANSI escape codes
+ msg: msg.replace(/\x1b\[[0-9;]*m/g, ''),
+ })
+ }
+
+ protected async handleCdCommand(command: string) {
+ const path = command.substring(3)
+ const prevCwd = this.cwd.value
+
+ if (await isAbsolute(path)) {
+ this.cwd.value = path
+ } else {
+ this.cwd.value = await join(this.cwd.value, path)
+ }
+
+ // Confirm that the path exists
+ if (!(await exists(this.cwd.value))) {
+ this.cwd.value = prevCwd
+ this.addToOutput(`cd: no such directory`, 'stderr')
+ }
+
+ // Ensure that cwd doesn't leave the baseCwd
+ if (!this.cwd.value.startsWith(this.baseCwd)) {
+ this.cwd.value = prevCwd
+ this.addToOutput(`cd: Permission denied`, 'stderr')
+ }
+ }
+
+ async executeCommand(command: string) {
+ this.addToOutput(command, 'stdin')
+
+ if (command.startsWith('cd ')) {
+ await this.handleCdCommand(command)
+ return
+ }
+
+ this.hasRunningTask.value = true
+
+ await this.setupDone.fired
+
+ await invoke('execute_command', {
+ cwd: this.cwd.value,
+ command,
+ })
+ }
+
+ async killCommand() {
+ await invoke('kill_command')
+ }
+}
diff --git a/src/components/CommandBar/CommandBar.vue b/src/components/CommandBar/CommandBar.vue
index d25fc381e..50fa4d079 100644
--- a/src/components/CommandBar/CommandBar.vue
+++ b/src/components/CommandBar/CommandBar.vue
@@ -14,7 +14,7 @@
'min-width': 'unset',
'max-width': '500px',
transition: 'slide-y-transition',
- contentClass: 'commandbar-menu',
+ contentClass: 'commandbar-menu elevation-4',
}"
:items="actions"
:item-text="(item) => `${t(item.name)}\n${t(item.description)}`"
diff --git a/src/components/Common/GlobalMutex.ts b/src/components/Common/GlobalMutex.ts
index 25f08bddb..208529610 100644
--- a/src/components/Common/GlobalMutex.ts
+++ b/src/components/Common/GlobalMutex.ts
@@ -33,6 +33,14 @@ export class GlobalMutex {
throw new Error('Trying to unlock a mutex that does not exist')
}
+ // Store whether mutex still has listeners
+ const hasListeners = mutex.hasListeners()
+
mutex.unlock()
+
+ // Clean up map if no more listeners
+ if (!hasListeners) {
+ this.mutexMap.delete(key)
+ }
}
}
diff --git a/src/components/Common/Mutex.ts b/src/components/Common/Mutex.ts
index 844a34999..b457a7d8d 100644
--- a/src/components/Common/Mutex.ts
+++ b/src/components/Common/Mutex.ts
@@ -44,4 +44,8 @@ export class Mutex {
listener()
}
}
+
+ hasListeners() {
+ return this.listeners.length > 0
+ }
}
diff --git a/src/components/Common/WindowResize.ts b/src/components/Common/WindowResize.ts
index f2e433266..de20893ff 100644
--- a/src/components/Common/WindowResize.ts
+++ b/src/components/Common/WindowResize.ts
@@ -4,7 +4,7 @@ import { reactive } from 'vue'
import { App } from '/@/App'
export class WindowResize extends EventDispatcher<[number, number]> {
- protected state = reactive({
+ public readonly state = reactive({
currentHeight: window.innerHeight,
currentWidth: window.innerWidth,
})
diff --git a/src/components/Compiler/Compiler.ts b/src/components/Compiler/Compiler.ts
index 2366fdfa1..ac532dd74 100644
--- a/src/components/Compiler/Compiler.ts
+++ b/src/components/Compiler/Compiler.ts
@@ -1,6 +1,9 @@
import type { DashService, ICompilerOptions } from './Worker/Service'
import CompilerWorker from './Worker/Service?worker'
import { wrap } from 'comlink'
+import { setupWorker } from '/@/utils/worker/setup'
const worker = new CompilerWorker()
export const DashCompiler = wrap(worker)
+
+setupWorker(worker)
diff --git a/src/components/Compiler/LogPanel/Panel.tsx b/src/components/Compiler/LogPanel/Panel.tsx
new file mode 100644
index 000000000..6f7624899
--- /dev/null
+++ b/src/components/Compiler/LogPanel/Panel.tsx
@@ -0,0 +1,61 @@
+import { Component, For, Show } from 'solid-js'
+import { useTranslations } from '../../Composables/useTranslations'
+import { SolidIcon } from '../../Solid/Icon/SolidIcon'
+import { toSignal } from '../../Solid/toSignal'
+import { CompilerWindow } from '../Window/Window'
+import { ILogData } from '../Worker/Console'
+
+export const LogPanel: Component<{
+ compilerWindow: CompilerWindow
+}> = (props) => {
+ const { t } = useTranslations()
+ const [log] = toSignal<[string, ILogData][]>(
+ props.compilerWindow.getCategories().logs.data
+ )
+
+ const icon = (type: string | undefined) => {
+ if (type === 'info') return 'mdi-information-outline'
+ if (type === 'warning') return 'mdi-alert-outline'
+ if (type === 'error') return 'mdi-alert-circle-outline'
+
+ return null
+ }
+
+ return (
+ <>
+
+
+ {t('bottomPanel.compiler.noLogs')}
+
+
+
+
+ {([logEntry, { time, type }]) => (
+
+
+ [{time}]
+
+
+
+
+
+
+
+ {logEntry}
+
+
+ )}
+
+ >
+ )
+}
diff --git a/src/components/Compiler/Sidebar/create.ts b/src/components/Compiler/Sidebar/create.ts
index 42f9d647f..b3fcce498 100644
--- a/src/components/Compiler/Sidebar/create.ts
+++ b/src/components/Compiler/Sidebar/create.ts
@@ -32,11 +32,15 @@ export function createCompilerSidebar() {
await project.compilerReady.fired
project.compilerService.onConsoleUpdate(
proxy(async () => {
- let logs = await project.compilerService.getCompilerLogs()
- logs = logs.filter(
+ const allLogs = await project.compilerService.getCompilerLogs()
+ const logs = allLogs.filter(
([_, { type }]) => type === 'error' || type === 'warning'
)
+ const app = await App.getApp()
+ app.windows.compilerWindow.getCategories().logs.data.value =
+ allLogs
+
state.currentCount = logs.length
// User currently still has logs tab selected and therefore sees the new logs
diff --git a/src/components/Compiler/Window/Content.vue b/src/components/Compiler/Window/Content.vue
index 5f23de62c..a2d3bb0e7 100644
--- a/src/components/Compiler/Window/Content.vue
+++ b/src/components/Compiler/Window/Content.vue
@@ -16,6 +16,7 @@
v-if="categories[selectedSidebar]"
:is="categories[selectedSidebar].component"
:data="categories[selectedSidebar].data.value"
+ @closeWindow="onClose"
/>
diff --git a/src/components/Compiler/Window/OutputFolders.vue b/src/components/Compiler/Window/OutputFolders.vue
index 101f7e6d2..5d9736861 100644
--- a/src/components/Compiler/Window/OutputFolders.vue
+++ b/src/components/Compiler/Window/OutputFolders.vue
@@ -1,16 +1,68 @@
-
+
+
+
+
+ mdi-folder-sync-outline
+ {{ t('comMojang.linkFolder') }}
+
+
+
+ {{ t('comMojang.linkedFolder') }}:
+
+
+ {{ path }}
+
+
diff --git a/src/components/Compiler/Window/Window.ts b/src/components/Compiler/Window/Window.ts
index 683400b7e..1f907cc30 100644
--- a/src/components/Compiler/Window/Window.ts
+++ b/src/components/Compiler/Window/Window.ts
@@ -144,6 +144,10 @@ export class CompilerWindow extends NewBaseWindow {
})
}
+ getCategories() {
+ return this.categories
+ }
+
async reload() {
const app = await App.getApp()
@@ -206,7 +210,6 @@ export class CompilerWindow extends NewBaseWindow {
const service = await project.createDashService(
'production'
)
- await service.setup()
await service.build()
},
},
@@ -240,7 +243,6 @@ export class CompilerWindow extends NewBaseWindow {
'production',
`${project.projectPath}/.bridge/compiler/${entry.name}`
)
- await service.setup()
await service.build()
},
})
@@ -256,7 +258,10 @@ export class CompilerWindow extends NewBaseWindow {
const { hasComMojang, didDenyPermission } = comMojang.status
let panelConfig: IPanelOptions
- if (isUsingFileSystemPolyfill.value) {
+ if (
+ !import.meta.env.VITE_IS_TAURI_APP &&
+ isUsingFileSystemPolyfill.value
+ ) {
panelConfig = {
text: 'comMojang.status.notAvailable',
type: 'error',
@@ -276,7 +281,9 @@ export class CompilerWindow extends NewBaseWindow {
}
} else if (!hasComMojang) {
panelConfig = {
- text: 'comMojang.status.notSetup',
+ text: import.meta.env.VITE_IS_TAURI_APP
+ ? 'comMojang.status.notSetupTauri'
+ : 'comMojang.status.notSetup',
type: 'error',
isDismissible: false,
}
diff --git a/src/components/Compiler/Worker/Console.ts b/src/components/Compiler/Worker/Console.ts
index c4a92100d..e5792ddc4 100644
--- a/src/components/Compiler/Worker/Console.ts
+++ b/src/components/Compiler/Worker/Console.ts
@@ -1,6 +1,8 @@
import { Console } from 'dash-compiler'
-interface ILogData {
+export interface ILogData {
+ // Format: HH:MM:SS
+ time: string
type?: 'info' | 'error' | 'warning'
}
@@ -12,7 +14,10 @@ export class ForeignConsole extends Console {
return this.logs
}
- protected basicLog(message: any, { type }: ILogData = {}) {
+ protected basicLog(
+ message: any,
+ { type }: { type?: 'info' | 'error' | 'warning' } = {}
+ ) {
switch (type) {
case 'warning':
console.warn(message)
@@ -31,7 +36,7 @@ export class ForeignConsole extends Console {
this.logs.unshift([
typeof message === 'string' ? message : JSON.stringify(message),
- { type },
+ { time: new Date().toLocaleTimeString(), type },
])
this.logsChanged()
}
diff --git a/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts b/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts
index 288a71469..9f1ddfeb7 100644
--- a/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts
+++ b/src/components/Compiler/Worker/Plugins/CustomComponent/ComponentSchemas.ts
@@ -1,9 +1,12 @@
+import { Remote } from 'comlink'
import { Component, DefaultConsole } from 'dash-compiler'
+import { DashService } from '../../Service'
import { App } from '/@/App'
import { JsRuntime } from '/@/components/Extensions/Scripts/JsRuntime'
import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
+import { Project } from '/@/components/Projects/Project/Project'
import { IDisposable } from '/@/types/disposable'
-import { iterateDir } from '/@/utils/iterateDir'
+import { iterateDir, iterateDirParallel } from '/@/utils/iterateDir'
export const supportsCustomComponents = ['block', 'item', 'entity']
export type TComponentFileType = typeof supportsCustomComponents[number]
@@ -15,14 +18,17 @@ export class ComponentSchemas {
}
protected schemaLookup = new Map()
protected disposables?: IDisposable[]
+ protected dash?: Remote
- constructor() {}
+ constructor(protected project: Project) {}
get(fileType: TComponentFileType) {
return this.schemas[fileType]
}
async activate() {
+ this.dash = await this.project.createDashService('development')
+
this.disposables = [
App.eventSystem.on(
'fileSave',
@@ -44,9 +50,8 @@ export class ComponentSchemas {
const fileType = filePath
.replace(componentPath + '/', '')
.split('/')[0] as TComponentFileType
- const isSupportedFileType = supportsCustomComponents.includes(
- fileType
- )
+ const isSupportedFileType =
+ supportsCustomComponents.includes(fileType)
if (!isSupportedFileType && v1CompatMode) {
// This is not a supported file type but v1CompatMode is enabled,
@@ -102,22 +107,21 @@ export class ComponentSchemas {
jsRuntime: JsRuntime,
fileType: TComponentFileType
) {
- const app = await App.getApp()
- const project = app.project
- await (await project.compilerService.completedStartUp).fired
-
- const v1CompatMode = project.config.get().bridge?.v1CompatMode ?? false
+ const v1CompatMode =
+ this.project.config.get().bridge?.v1CompatMode ?? false
const fromFilePath = v1CompatMode
- ? project.config.resolvePackPath('behaviorPack', 'components')
- : project.config.resolvePackPath(
+ ? this.project.config.resolvePackPath('behaviorPack', 'components')
+ : this.project.config.resolvePackPath(
'behaviorPack',
`components/${fileType}`
)
let baseDir: AnyDirectoryHandle
try {
- baseDir = await app.fileSystem.getDirectoryHandle(fromFilePath)
+ baseDir = await this.project.app.fileSystem.getDirectoryHandle(
+ fromFilePath
+ )
} catch {
return {}
}
@@ -125,7 +129,7 @@ export class ComponentSchemas {
// Reset schemas
this.schemas[fileType] = {}
- await iterateDir(
+ await iterateDirParallel(
baseDir,
async (fileHandle, filePath) => {
await this.evalComponentSchema(
@@ -148,14 +152,14 @@ export class ComponentSchemas {
file: File,
v1CompatMode: boolean
) {
- const app = await App.getApp()
- const project = app.project
- await (await project.compilerService.completedStartUp).fired
+ let fileContent = new Uint8Array(await file.arrayBuffer())
+
+ if (filePath.endsWith('.ts')) {
+ fileContent = (
+ await this.dash!.compileFile(filePath, fileContent)
+ )[1]
+ }
- const [_, fileContent] = await project.compilerService.compileFile(
- filePath,
- new Uint8Array(await file.arrayBuffer())
- )
const transformedFile = new File([fileContent], file.name)
const component = new Component(
new DefaultConsole(),
diff --git a/src/components/Compiler/Worker/Service.ts b/src/components/Compiler/Worker/Service.ts
index bccbf89a3..ee65037e8 100644
--- a/src/components/Compiler/Worker/Service.ts
+++ b/src/components/Compiler/Worker/Service.ts
@@ -1,18 +1,11 @@
-// @ts-ignore Make "path" work on this worker
-globalThis.process = {
- cwd: () => '',
- env: {},
- release: {
- name: 'browser',
- },
-}
+import '/@/utils/worker/inject'
import '/@/components/FileSystem/Virtual/Comlink'
import { expose } from 'comlink'
import { FileTypeLibrary, IFileType } from '/@/components/Data/FileType'
import { DataLoader } from '/@/components/Data/DataLoader'
import type { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
-import { Dash, initRuntimes } from 'dash-compiler'
+import { Dash, initRuntimes, FileSystem } from 'dash-compiler'
import { PackTypeLibrary } from '/@/components/Data/PackType'
import { DashFileSystem } from './FileSystem'
import { Signal } from '/@/components/Common/Event/Signal'
@@ -21,6 +14,8 @@ import { EventDispatcher } from '/@/components/Common/Event/EventDispatcher'
import { ForeignConsole } from './Console'
import { Mutex } from '../../Common/Mutex'
import wasmUrl from '@swc/wasm-web/wasm-web_bg.wasm?url'
+import { VirtualDirectoryHandle } from '../../FileSystem/Virtual/DirectoryHandle'
+import { TauriFsStore } from '../../FileSystem/Virtual/Stores/TauriFs'
initRuntimes(wasmUrl)
@@ -38,48 +33,95 @@ const consoles = new Map()
* Dispatches an event whenever a task starts with progress steps
*/
export class DashService extends EventDispatcher {
- protected fileSystem: DashFileSystem
- public fileType: FileTypeLibrary
- protected readonly dash: Dash
+ protected _fileSystem?: FileSystem
+ public _fileType?: FileTypeLibrary
+ protected _dash?: Dash
public isDashFree = new Mutex()
- protected projectDir: string
+ protected _projectDir?: string
public isSetup = false
public completedStartUp = new Signal()
- protected console: ForeignConsole
+ protected _console?: ForeignConsole
+
+ constructor() {
+ super()
+ }
- constructor(
+ async setup(
baseDirectory: AnyDirectoryHandle,
comMojangDirectory: AnyDirectoryHandle | undefined,
options: ICompilerOptions
) {
- super()
- this.fileSystem = new DashFileSystem(baseDirectory)
+ await this.isDashFree.lock()
+
+ if (!dataLoader.hasFired) await dataLoader.loadData()
+
+ this._fileSystem = await this.createFileSystem(baseDirectory)
const outputFileSystem =
comMojangDirectory && options.mode === 'development'
- ? new DashFileSystem(comMojangDirectory)
+ ? await this.createFileSystem(comMojangDirectory)
: undefined
- this.fileType = new FileTypeLibrary()
- this.fileType.setPluginFileTypes(options.pluginFileTypes)
+ this._fileType = new FileTypeLibrary()
+ this._fileType.setPluginFileTypes(options.pluginFileTypes)
let console = consoles.get(options.projectName)
if (!console) {
console = new ForeignConsole()
consoles.set(options.projectName, console)
}
- this.console = console
+ this._console = console
+
+ this._projectDir = dirname(options.config)
- this.dash = new Dash(this.fileSystem, outputFileSystem, {
+ this._dash = new Dash(this._fileSystem, outputFileSystem, {
config: options.config,
compilerConfig: options.compilerConfig,
console,
mode: options.mode,
- fileType: this.fileType,
+ fileType: this._fileType,
packType: new PackTypeLibrary(),
verbose: true,
requestJsonData: (path) => dataLoader.readJSON(path),
})
+ await this.dash.setup(dataLoader)
+
+ this.isDashFree.unlock()
+ this.isSetup = true
+ }
+ protected async createFileSystem(directoryHandle: AnyDirectoryHandle) {
+ // Default file system on PWA builds
+ if (!import.meta.env.VITE_IS_TAURI_APP)
+ return new DashFileSystem(directoryHandle)
- this.projectDir = dirname(options.config)
+ if (!(directoryHandle instanceof VirtualDirectoryHandle))
+ throw new Error(
+ `Expected directoryHandle to be a virtual directory handle`
+ )
+ const baseStore = directoryHandle.getBaseStore()
+ if (!(baseStore instanceof TauriFsStore))
+ throw new Error(
+ `Expected virtual directory to be backed by TauriFsStore`
+ )
+
+ const { TauriBasedDashFileSystem } = await import('./TauriFs')
+ return new TauriBasedDashFileSystem(baseStore.getBaseDirectory())
+ }
+
+ get dash() {
+ if (!this._dash) throw new Error('Dash not initialized')
+ return this._dash
+ }
+ get fileSystem() {
+ if (!this._fileSystem) throw new Error('File system not initialized')
+ return this._fileSystem
+ }
+ get console() {
+ if (!this._console) throw new Error('Console not initialized')
+ return this._console
+ }
+ get projectDir() {
+ if (!this._projectDir)
+ throw new Error('Project directory not initialized')
+ return this._projectDir
}
getCompilerLogs() {
@@ -106,19 +148,24 @@ export class DashService extends EventDispatcher {
async start(changedFiles: string[], deletedFiles: string[]) {
await this.isDashFree.lock()
- const fs = this.fileSystem.internal
+ const fileExists = (path: string) =>
+ this.fileSystem
+ .readFile(path)
+ .then(() => true)
+ .catch(() => false)
+
if (
- (await fs.fileExists(
+ (await fileExists(
`${this.projectDir}/.bridge/.restartWatchMode`
)) ||
- !(await fs.fileExists(
+ !(await fileExists(
// TODO(Dash): Replace with call to "this.dash.dashFilePath" once the accessor is no longer protected
`${this.projectDir}/.bridge/.dash.${this.dash.getMode()}.json`
))
) {
await Promise.all([
this.build(false).catch((err) => console.error(err)),
- fs
+ this.fileSystem
.unlink(`${this.projectDir}/.bridge/.restartWatchMode`)
.catch((err) => console.error(err)),
])
@@ -184,15 +231,6 @@ export class DashService extends EventDispatcher {
return this.dash.getFileDependencies(filePath)
}
- async setup() {
- await this.isDashFree.lock()
-
- if (!dataLoader.hasFired) await dataLoader.loadData()
- await this.dash.setup(dataLoader)
-
- this.isDashFree.unlock()
- this.isSetup = true
- }
async reloadPlugins() {
await this.isDashFree.lock()
diff --git a/src/components/Compiler/Worker/TauriFs.ts b/src/components/Compiler/Worker/TauriFs.ts
new file mode 100644
index 000000000..efdd24954
--- /dev/null
+++ b/src/components/Compiler/Worker/TauriFs.ts
@@ -0,0 +1,112 @@
+import { FileSystem } from 'dash-compiler'
+import { IDirEntry } from 'dash-compiler/dist/FileSystem/FileSystem'
+import { AnyDirectoryHandle } from '../../FileSystem/Types'
+import {
+ writeFile,
+ writeBinaryFile,
+ createDir,
+ removeDir,
+ removeFile,
+ readBinaryFile,
+ readDir,
+ FileEntry,
+ copyFile,
+} from '@tauri-apps/api/fs'
+import { join, basename, dirname, isAbsolute, sep } from '@tauri-apps/api/path'
+import json5 from 'json5'
+
+export class TauriBasedDashFileSystem extends FileSystem {
+ constructor(protected baseDirectory?: string) {
+ super()
+ }
+
+ async resolvePath(path: string) {
+ path = path.replaceAll(/\\|\//g, sep)
+ if (!this.baseDirectory || (await isAbsolute(path))) return path
+
+ return join(this.baseDirectory, path)
+ }
+
+ async readJson(path: string) {
+ const file = await this.readFile(path)
+
+ return json5.parse(await file.text())
+ }
+ async writeJson(path: string, content: any, beautify?: boolean) {
+ await this.writeFile(
+ path,
+ JSON.stringify(content, null, beautify ? '\t' : undefined)
+ )
+ }
+ async readFile(path: string): Promise {
+ const binaryData = await readBinaryFile(await this.resolvePath(path))
+
+ return new File([binaryData], await basename(path))
+ }
+ async writeFile(path: string, content: string | Uint8Array) {
+ const resolvedPath = await this.resolvePath(path)
+ await createDir(await dirname(resolvedPath), { recursive: true })
+
+ if (typeof content === 'string') await writeFile(resolvedPath, content)
+ else await writeBinaryFile(resolvedPath, content)
+ }
+ async copyFile(from: string, to: string, outputFs = this) {
+ const outputPath = await outputFs.resolvePath(to)
+ await createDir(await dirname(outputPath), { recursive: true }).catch(
+ () => {}
+ )
+
+ await copyFile(await this.resolvePath(from), outputPath)
+ }
+
+ async mkdir(path: string) {
+ await createDir(await this.resolvePath(path), { recursive: true })
+ }
+ async unlink(path: string) {
+ const resolvedPath = await this.resolvePath(path)
+
+ await Promise.all([
+ removeDir(resolvedPath, { recursive: true }).catch(() => {}),
+ removeFile(resolvedPath).catch(() => {}),
+ ])
+ }
+ async allFiles(path: string) {
+ const entries = await readDir(await this.resolvePath(path), {
+ recursive: true,
+ })
+
+ return this.flattenEntries(entries)
+ }
+ protected relative(path: string) {
+ if (!this.baseDirectory) return path
+
+ return path
+ .replace(`${this.baseDirectory}${sep}`, '')
+ .replaceAll('\\', '/') // Dash expects forward slashes
+ }
+ protected flattenEntries(entries: FileEntry[]) {
+ const files: string[] = []
+
+ for (const { path, children } of entries) {
+ if (children) {
+ files.push(...this.flattenEntries(children))
+ continue
+ }
+
+ files.push(this.relative(path))
+ }
+
+ return files
+ }
+ async readdir(path: string): Promise {
+ const entries = await readDir(await this.resolvePath(path))
+
+ return entries.map((entry) => ({
+ name: entry.name!,
+ kind: entry.children ? 'directory' : 'file',
+ }))
+ }
+ async lastModified(filePath: string) {
+ return 0
+ }
+}
diff --git a/src/components/Composables/Sidebar/useSidebarState.ts b/src/components/Composables/Sidebar/useSidebarState.ts
new file mode 100644
index 000000000..27304f9dc
--- /dev/null
+++ b/src/components/Composables/Sidebar/useSidebarState.ts
@@ -0,0 +1,19 @@
+import { computed, watch } from 'vue'
+import { settingsState } from '../../Windows/Settings/SettingsState'
+import { App } from '/@/App'
+
+export function useSidebarState() {
+ const isNavVisible = computed(() => App.sidebar.isNavigationVisible.value)
+ const isContentVisible = computed(
+ () => isNavVisible && App.sidebar.isContentVisible.value
+ )
+ const isAttachedRight = computed(
+ () => settingsState.sidebar && settingsState.sidebar.isSidebarRight
+ )
+
+ return {
+ isNavVisible,
+ isContentVisible,
+ isAttachedRight,
+ }
+}
diff --git a/src/components/Data/DataLoader.ts b/src/components/Data/DataLoader.ts
index 0c6cb2b89..b44fda63b 100644
--- a/src/components/Data/DataLoader.ts
+++ b/src/components/Data/DataLoader.ts
@@ -6,9 +6,10 @@ import { FileSystem } from '../FileSystem/FileSystem'
import { zipSize } from '/@/utils/app/dataPackage'
import { whenIdle } from '/@/utils/whenIdle'
import { get, set } from 'idb-keyval'
-import { IDBWrapper } from '/@/components/FileSystem/Virtual/IDB'
import { compareVersions } from 'bridge-common-utils'
import { version as appVersion } from '/@/utils/app/version'
+import { IndexedDbStore } from '../FileSystem/Virtual/Stores/IndexedDb'
+import { MemoryStore } from '../FileSystem/Virtual/Stores/Memory'
export class DataLoader extends FileSystem {
_virtualFileSystem?: VirtualDirectoryHandle
@@ -50,14 +51,20 @@ export class DataLoader extends FileSystem {
)
console.time('[App] Data')
+
+ const indexedDbStore = new IndexedDbStore(
+ 'data-fs',
+ // Do not allow writes to data-fs
+ true
+ )
+ // Clear data-fs if the version has changed
const mayClearDb = this.isMainLoader && !savedAllDataInIdb
+ if (mayClearDb) await indexedDbStore.clear()
// Create virtual filesystem
this._virtualFileSystem = new VirtualDirectoryHandle(
- new IDBWrapper('data-fs'),
- 'bridgeFolder',
- savedAllDataInIdb ? undefined : new Map(),
- mayClearDb
+ savedAllDataInIdb ? indexedDbStore : new MemoryStore('data-fs'),
+ ''
)
await this._virtualFileSystem.setupDone.fired
@@ -114,14 +121,12 @@ export class DataLoader extends FileSystem {
}
}
- this.setup(this._virtualFileSystem)
- console.timeEnd('[App] Data')
-
if (this.isMainLoader && !forceDataDownload) {
- whenIdle(async () => {
- await this._virtualFileSystem!.moveToIdb()
- await set('savedDataForVersion', appVersion)
- })
+ await this._virtualFileSystem!.moveToIdb()
+ await set('savedDataForVersion', appVersion)
}
+
+ this.setup(this._virtualFileSystem)
+ console.timeEnd('[App] Data')
}
}
diff --git a/src/components/Data/JSONDefaults.ts b/src/components/Data/JSONDefaults.ts
index ea0c69157..52f226c38 100644
--- a/src/components/Data/JSONDefaults.ts
+++ b/src/components/Data/JSONDefaults.ts
@@ -19,11 +19,13 @@ export class JsonDefaults extends EventDispatcher {
protected loadedSchemas = false
protected localSchemas: Record = {}
protected disposables: IDisposable[] = []
- public readonly componentSchemas = new ComponentSchemas()
+ public readonly componentSchemas: ComponentSchemas
protected task: Task | null = null
constructor(protected project: Project) {
super()
+
+ this.componentSchemas = new ComponentSchemas(project)
}
get isReady() {
@@ -34,7 +36,8 @@ export class JsonDefaults extends EventDispatcher {
console.time('[SETUP] JSONDefaults')
await this.project.app.project.packIndexer.fired
- await this.componentSchemas.activate()
+ // Don't await to start loading schemas as soon as possible
+ this.componentSchemas.activate()
this.disposables = [
// Updating currentContext/ references
diff --git a/src/components/Data/SchemaScript.ts b/src/components/Data/SchemaScript.ts
index 910c9351c..c90ca636c 100644
--- a/src/components/Data/SchemaScript.ts
+++ b/src/components/Data/SchemaScript.ts
@@ -100,82 +100,91 @@ export class SchemaScript {
)
}
}
+ protected processScriptResult(
+ scriptPath: string,
+ schemaScript: any,
+ scriptResult: any,
+ localSchemas: any
+ ) {
+ if (!scriptResult) return
+ if (scriptPath.endsWith('.js')) {
+ if (scriptResult.keep) return
+
+ schemaScript = {
+ ...schemaScript,
+ type: scriptResult.type,
+ generateFile: scriptResult.generateFile,
+ }
+ scriptResult = scriptResult.data
+ }
+
+ if (
+ schemaScript.type === 'object' &&
+ !Array.isArray(scriptResult) &&
+ typeof scriptResult === 'object'
+ ) {
+ localSchemas[
+ `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`
+ ] = {
+ uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`,
+ schema: {
+ type: 'object',
+ properties: scriptResult,
+ },
+ }
+ } else if (schemaScript.type === 'custom') {
+ localSchemas[
+ `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`
+ ] = {
+ uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`,
+ schema: scriptResult,
+ }
+ } else {
+ localSchemas[
+ `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`
+ ] = {
+ uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`,
+ schema: {
+ type: schemaScript.type === 'enum' ? 'string' : 'object',
+ enum:
+ schemaScript.type === 'enum' ? scriptResult : undefined,
+ properties:
+ schemaScript.type === 'properties'
+ ? Object.fromEntries(
+ scriptResult.map((res: string) => [res, {}])
+ )
+ : undefined,
+ },
+ }
+ }
+ }
async runSchemaScripts(localSchemas: any) {
const schemaScripts = await this.app.dataLoader.readJSON(
'data/packages/minecraftBedrock/schemaScripts.json'
)
+ const promises = []
+
for (const [scriptPath, script] of Object.entries(schemaScripts)) {
let schemaScript: any
if (scriptPath.endsWith('.js')) schemaScript = { script }
else schemaScript = script
- let scriptResult: any = await this.runScript(
- scriptPath,
- schemaScript.script
- )
-
- if (scriptResult) {
- if (scriptPath.endsWith('.js')) {
- if (scriptResult.keep) continue
-
- schemaScript = {
- ...schemaScript,
- type: scriptResult.type,
- generateFile: scriptResult.generateFile,
- }
- scriptResult = scriptResult.data
- }
-
- if (
- schemaScript.type === 'object' &&
- !Array.isArray(scriptResult) &&
- typeof scriptResult === 'object'
- ) {
- localSchemas[
- `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`
- ] = {
- uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`,
- schema: {
- type: 'object',
- properties: scriptResult,
- },
- }
- } else if (schemaScript.type === 'custom') {
- localSchemas[
- `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`
- ] = {
- uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`,
- schema: scriptResult,
- }
- } else {
- localSchemas[
- `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`
- ] = {
- uri: `file:///data/packages/minecraftBedrock/schema/${schemaScript.generateFile}`,
- schema: {
- type:
- schemaScript.type === 'enum'
- ? 'string'
- : 'object',
- enum:
- schemaScript.type === 'enum'
- ? scriptResult
- : undefined,
- properties:
- schemaScript.type === 'properties'
- ? Object.fromEntries(
- scriptResult.map((res: string) => [
- res,
- {},
- ])
- )
- : undefined,
- },
+ promises.push(
+ this.runScript(scriptPath, schemaScript.script).then(
+ (scriptResult) => {
+ this.processScriptResult(
+ scriptPath,
+ schemaScript,
+ scriptResult,
+ localSchemas
+ )
}
- }
- }
+ )
+ )
}
+
+ await Promise.all(promises)
}
}
diff --git a/src/components/Data/TypeLoader.ts b/src/components/Data/TypeLoader.ts
index 63b03dd25..c7b0efccb 100644
--- a/src/components/Data/TypeLoader.ts
+++ b/src/components/Data/TypeLoader.ts
@@ -1,14 +1,18 @@
import { App } from '/@/App'
import { IDisposable } from '/@/types/disposable'
import { DataLoader } from './DataLoader'
-import { Tab } from '../TabSystem/CommonTab'
-import { FileTab } from '../TabSystem/FileTab'
+import { Tab } from '/@/components/TabSystem/CommonTab'
+import { FileTab } from '/@/components/TabSystem/FileTab'
import {
IRequirements,
RequiresMatcher,
} from './RequiresMatcher/RequiresMatcher'
import { useMonaco } from '/@/utils/libs/useMonaco'
+import { v4 as uuid } from 'uuid'
+/**
+ * A map of type locations to type defintions that have been loaded
+ */
const types = new Map()
export class TypeLoader {
@@ -16,6 +20,7 @@ export class TypeLoader {
protected typeDisposables: IDisposable[] = []
protected userTypeDisposables: IDisposable[] = []
protected currentTypeEnv: string | null = null
+ protected isLoading: boolean = false
constructor(protected dataLoader: DataLoader) {}
@@ -41,21 +46,108 @@ export class TypeLoader {
this.disposables = []
}
- protected async load(typePath: string) {
- // Check whether we have already loaded types
- let src = types.get(typePath)
- if (src) return src
+ protected async load(typeLocations: [string, string?][]) {
+ // Ignore if we are already loading types (e.g. if a tab has been switched while loading)
+ if (this.isLoading) return []
+ this.isLoading = true
- await this.dataLoader.fired
+ const app = await App.getApp()
+
+ // Load the cache index which maps the urls to the location of the files in cache
+ let cacheIndex: Record = {}
+ try {
+ cacheIndex = await app.fileSystem.readJSON(
+ `~local/data/cache/types/index.json`
+ )
+ } catch {}
+
+ // Create promises for loading each type definition. Once this resolves, the types will be cached appropriately
+ const toCache = await Promise.all(
+ typeLocations.map(([typeLocation, moduleName]) => {
+ return new Promise<[string, string, boolean, string?]>(
+ async (resolve) => {
+ // Before we try to load anything, make sure the type definition hasn't already been loaded and set in the type map
+ let src = types.get(typeLocation)
+ if (src) resolve([typeLocation, src, false, moduleName])
+
+ // Decide whether the type is being loaded from data or needs to be fetched externally
+ const isFromData = typeLocation.startsWith('types/')
+ if (isFromData) {
+ // Load types directly from bridge.'s data
+ await this.dataLoader.fired
+
+ const file = await this.dataLoader.readFile(
+ `data/packages/minecraftBedrock/${typeLocation}`
+ )
+ src = await file.text()
+ resolve([typeLocation, src, false, moduleName])
+ return
+ }
+
+ // First check cache to see if we have already cached the file, if so resolve with the file from cache
+ const cacheLocation = cacheIndex[typeLocation]
+ const file = cacheLocation
+ ? await app.fileSystem
+ .readFile(cacheLocation)
+ .catch(() => null)
+ : null
+
+ // File is cached, so resolve with the file from cache
+ if (file) {
+ resolve([
+ typeLocation,
+ await file.text(),
+ false,
+ moduleName,
+ ])
+ return
+ }
+
+ // The file couldn't be fetched from cache (because it is not in index or at the path specified)
+ // So we need to fetch it
+ const res = await fetch(typeLocation).catch(() => null)
+ // TODO: Maybe set a variable (failedToFetchAtLeastOnce) to later open an information window that tells the user that some types couldn't be fetched
+
+ // If the fetch failed, resolve with an empty string but don't cache it
+ const text = res ? await res.text() : ''
+
+ resolve([typeLocation, text, text !== '', moduleName])
+ }
+ )
+ })
+ )
+
+ for (const [typeLocation, definition, updateCache] of toCache) {
+ // First, save types to 'types' map
+ types.set(typeLocation, definition)
+
+ // Then if don't need to update cache, continue processing the next type
+ if (!updateCache) continue
+
+ // Create a random file name for the file to be stored in cache under. We can't use the location since it is a url and contains illegal file name characters
+ const cacheFile = `~local/data/cache/types/${uuid()}.d.ts`
+ cacheIndex = {
+ ...cacheIndex,
+ [typeLocation]: cacheFile,
+ }
+ // Write the actual type definition in cache
+ await app.fileSystem.writeFile(cacheFile, definition)
+ }
- // Load types from file
- const file = await this.dataLoader.readFile(
- `data/packages/minecraftBedrock/${typePath}`
+ // Update the cache index
+ await app.fileSystem.writeJSON(
+ '~local/data/cache/types/index.json',
+ cacheIndex
)
- src = await file.text()
- types.set(typePath, src)
- return src
+ this.isLoading = false
+
+ return toCache.map(
+ ([typeLocation, definition, updateCache, moduleName]) => [
+ typeLocation,
+ this.wrapTypesInModule(definition, moduleName),
+ ]
+ )
}
async setTypeEnv(filePath: string) {
@@ -73,26 +165,22 @@ export class TypeLoader {
const matcher = new RequiresMatcher()
await matcher.setup()
- const libs = await Promise.all(
- types.map(async (type) => {
- if (typeof type === 'string')
- return [type, await this.load(type)]
+ const libs = await this.load(
+ types
+ .map((type) => {
+ if (typeof type === 'string') return [type]
- const { definition, requires } = type
+ const { definition, requires, moduleName } = type
- const valid = !requires
- ? true
- : matcher.isValid(requires as IRequirements)
+ if (!requires || matcher.isValid(requires as IRequirements))
+ return [definition, moduleName]
- if (valid)
- return [definition, await this.load(definition)]
- })
- )
- const filteredLibs = <(readonly [string, string])[]>(
- libs.filter((lib) => lib !== undefined)
+ return []
+ })
+ .filter((type) => type[0]) as [string, string?][]
)
- for (const [typePath, lib] of filteredLibs) {
+ for (const [typePath, lib] of libs) {
const uri = Uri.file(typePath)
this.typeDisposables.push(
languages.typescript.javascriptDefaults.addExtraLib(
@@ -145,4 +233,10 @@ export class TypeLoader {
)
}
}
+
+ wrapTypesInModule(typeSrc: string, moduleName?: string) {
+ if (!moduleName) return typeSrc
+
+ return `declare module '${moduleName}' {\n${typeSrc}\n}`
+ }
}
diff --git a/src/components/Editors/ThreePreview/ThreePreviewTab.ts b/src/components/Editors/ThreePreview/ThreePreviewTab.ts
index 0284b8ea6..5de48e7b5 100644
--- a/src/components/Editors/ThreePreview/ThreePreviewTab.ts
+++ b/src/components/Editors/ThreePreview/ThreePreviewTab.ts
@@ -65,7 +65,7 @@ export abstract class ThreePreviewTab extends PreviewTab {
this.controls = markRaw(new OrbitControls(this.camera, canvas))
this.controls.addEventListener('change', () => {
this.requestRendering()
- if (!this.parent.isActive) this.parent.setActive(true)
+ if (!this.parent.isActive.value) this.parent.setActive(true)
})
if (!this._scene) {
diff --git a/src/components/Extensions/Scripts/Modules/project.ts b/src/components/Extensions/Scripts/Modules/project.ts
index caa1296a8..28754784c 100644
--- a/src/components/Extensions/Scripts/Modules/project.ts
+++ b/src/components/Extensions/Scripts/Modules/project.ts
@@ -41,7 +41,6 @@ export const ProjectModule = async ({
? undefined
: `${app.project.projectPath}/.bridge/compiler/${configFile}`
)
- await service.setup()
await service.build()
},
diff --git a/src/components/FileSystem/Fast/getDirectoryHandle.ts b/src/components/FileSystem/Fast/getDirectoryHandle.ts
new file mode 100644
index 000000000..bf2b9fa3e
--- /dev/null
+++ b/src/components/FileSystem/Fast/getDirectoryHandle.ts
@@ -0,0 +1,80 @@
+import { IGetHandleConfig } from '../Common'
+import { VirtualDirectoryHandle } from '../Virtual/DirectoryHandle'
+import { pathFromHandle } from '../Virtual/pathFromHandle'
+import { IndexedDbStore } from '../Virtual/Stores/IndexedDb'
+import { join } from '/@/utils/path'
+
+export async function getDirectoryHandleTauri(
+ baseDirectory: VirtualDirectoryHandle,
+ pathArr: string[],
+ { create, createOnce }: Partial
+) {
+ // Cannot apply fast path if baseDirectory is not a virtual directory
+ const baseStore = baseDirectory.getBaseStore()
+
+ // Cannot apply fast path if baseStore is not a TauriFsStore
+ const { TauriFsStore } = await import('../Virtual/Stores/TauriFs')
+ if (!(baseStore instanceof TauriFsStore)) return false
+
+ const { createDir, exists } = await import('@tauri-apps/api/fs')
+ const { join, basename, sep } = await import('@tauri-apps/api/path')
+ const fullPath = await join(await pathFromHandle(baseDirectory), ...pathArr)
+
+ if (create) {
+ await createDir(fullPath, { recursive: !createOnce }).catch((err) => {
+ throw new Error(
+ `Failed to access "${fullPath}": Directory does not exist: ${err}`
+ )
+ })
+ } else if (!(await exists(fullPath))) {
+ throw new Error(
+ `Failed to access "${fullPath}": Directory does not exist`
+ )
+ }
+
+ const basePath = baseStore.getBaseDirectory()
+
+ return new VirtualDirectoryHandle(
+ baseStore,
+ await basename(fullPath),
+ basePath
+ ? // pathArr may not contain full path starting from basePath if baseDirectory is not the root directory
+ fullPath.replace(`${basePath}${sep}`, '').split(sep)
+ : pathArr
+ )
+}
+
+export async function getDirectoryHandleIndexedDb(
+ baseDirectory: VirtualDirectoryHandle,
+ pathArr: string[],
+ { create, createOnce }: Partial
+) {
+ // Cannot use fast path if createOnce is true
+ if (createOnce) return false
+
+ const baseStore = baseDirectory.getBaseStore()
+
+ // Cannot apply fast path if baseStore is not a IndexedDbStore
+ if (!(baseStore instanceof IndexedDbStore)) return false
+
+ const fullPath = join(baseDirectory.idbKey, ...pathArr)
+ let directoryData = await baseStore
+ .getDirectoryEntries(fullPath)
+ .catch(() => null)
+
+ if (create) {
+ await baseStore.createDirectory(fullPath)
+ directoryData = []
+ }
+
+ if (!directoryData)
+ throw new Error(
+ `Failed to access "${fullPath}": Directory does not exist`
+ )
+
+ return new VirtualDirectoryHandle(
+ baseStore,
+ pathArr[pathArr.length - 1],
+ fullPath.split(/\\|\//g)
+ )
+}
diff --git a/src/components/FileSystem/FileSystem.ts b/src/components/FileSystem/FileSystem.ts
index d00875e7e..d67ef9ef6 100644
--- a/src/components/FileSystem/FileSystem.ts
+++ b/src/components/FileSystem/FileSystem.ts
@@ -8,6 +8,11 @@ import { AnyDirectoryHandle, AnyFileHandle, AnyHandle } from './Types'
import { getStorageDirectory } from '/@/utils/getStorageDirectory'
import { VirtualFileHandle } from './Virtual/FileHandle'
import { VirtualDirectoryHandle } from './Virtual/DirectoryHandle'
+import {
+ getDirectoryHandleIndexedDb,
+ getDirectoryHandleTauri,
+} from './Fast/getDirectoryHandle'
+import { pathFromHandle } from './Virtual/pathFromHandle'
export class FileSystem extends Signal {
protected _baseDirectory!: AnyDirectoryHandle
@@ -45,14 +50,42 @@ export class FileSystem extends Signal {
pathArr.shift()
}
+ // Cannot apply fast path if baseDirectory is not a virtual directory
+ if (this.baseDirectory instanceof VirtualDirectoryHandle) {
+ /**
+ * Fast path for native app
+ * Every path segment costs at least 1 syscall on the slow path, whereas the fast path costs 1 syscall for the entire path
+ * Therefore, switch to the fast path if the path has more than 1 segment
+ */
+ if (import.meta.env.VITE_IS_TAURI_APP && pathArr.length > 1) {
+ const fastCallResult = await getDirectoryHandleTauri(
+ this.baseDirectory,
+ pathArr,
+ { create, createOnce }
+ )
+ // Returns false if the fast call failed
+ if (fastCallResult) return fastCallResult
+ }
+
+ /**
+ * Fast path for IndexedDB backed file systems
+ */
+ const fastCallResult = await getDirectoryHandleIndexedDb(
+ this.baseDirectory,
+ pathArr,
+ { create, createOnce }
+ )
+ if (fastCallResult) return fastCallResult
+ }
+
for (const folder of pathArr) {
try {
current = await current.getDirectoryHandle(folder, {
create: createOnce || create,
})
- } catch {
+ } catch (err) {
throw new Error(
- `Failed to access "${path}": Directory does not exist`
+ `Failed to access "${path}": Directory does not exist: ${err}`
)
}
@@ -91,7 +124,8 @@ export class FileSystem extends Signal {
.then((path) => path?.join('/'))
if (path) {
- path = '~local/' + path
+ // Local projects don't exist for Tauri builds
+ if (!import.meta.env.VITE_IS_TAURI_APP) path = '~local/' + path
} else {
path = await this.baseDirectory
.resolve(handle)
@@ -268,26 +302,44 @@ export class FileSystem extends Signal {
const originHandle = await this.getDirectoryHandle(originPath, {
create: false,
})
-
- await iterateDirParallel(originHandle, async (fileHandle, filePath) => {
- await this.copyFileHandle(
- fileHandle,
- await this.getFileHandle(join(destPath, filePath), true)
- )
+ const destHandle = await this.getDirectoryHandle(destPath, {
+ create: true,
})
+
+ await this.copyFolderByHandle(originHandle, destHandle)
}
async copyFolderByHandle(
originHandle: AnyDirectoryHandle,
- destHandle: AnyDirectoryHandle
+ destHandle: AnyDirectoryHandle,
+ ignoreFolders?: Set
) {
+ // Tauri build: Both handles are virtual -> Elligible for fast path
+ if (
+ import.meta.env.VITE_IS_TAURI_APP &&
+ originHandle instanceof VirtualDirectoryHandle &&
+ destHandle instanceof VirtualDirectoryHandle
+ ) {
+ const src = await pathFromHandle(originHandle)
+ const dest = await pathFromHandle(destHandle)
+
+ const { invoke } = await import('@tauri-apps/api')
+ await invoke('copy_directory', { src, dest })
+
+ return
+ }
+
const destFs = new FileSystem(destHandle)
- await iterateDirParallel(originHandle, async (fileHandle, filePath) => {
- await this.copyFileHandle(
- fileHandle,
- await destFs.getFileHandle(filePath, true)
- )
- })
+ await iterateDirParallel(
+ originHandle,
+ async (fileHandle, filePath) => {
+ await this.copyFileHandle(
+ fileHandle,
+ await destFs.getFileHandle(filePath, true)
+ )
+ },
+ ignoreFolders
+ )
}
loadFileHandleAsDataUrl(fileHandle: AnyFileHandle) {
diff --git a/src/components/FileSystem/Pickers/showFolderPicker.ts b/src/components/FileSystem/Pickers/showFolderPicker.ts
new file mode 100644
index 000000000..918f4397c
--- /dev/null
+++ b/src/components/FileSystem/Pickers/showFolderPicker.ts
@@ -0,0 +1,45 @@
+import { VirtualDirectoryHandle } from '../Virtual/DirectoryHandle'
+
+interface IOpenFolderOpts {
+ multiple?: boolean
+ defaultPath?: string
+}
+
+export async function showFolderPicker({
+ defaultPath,
+ multiple,
+}: IOpenFolderOpts = {}) {
+ if (!import.meta.env.VITE_IS_TAURI_APP) {
+ if (multiple)
+ console.warn(
+ 'Multiple folder selection is not supported in the browser yet'
+ )
+
+ const handle = await window
+ .showDirectoryPicker({ multiple: false, mode: 'readwrite' })
+ .catch(() => null)
+ return handle ? [handle] : null
+ }
+
+ const { TauriFsStore } = await import('../Virtual/Stores/TauriFs')
+ const { open } = await import('@tauri-apps/api/dialog')
+ const { basename } = await import('@tauri-apps/api/path')
+
+ let selectedPath = await open({
+ directory: true,
+ multiple,
+ defaultPath,
+ }).catch(() => null)
+ if (!selectedPath) return null
+ if (!Array.isArray(selectedPath)) selectedPath = [selectedPath]
+
+ return await Promise.all(
+ selectedPath.map(
+ async (path) =>
+ new VirtualDirectoryHandle(
+ new TauriFsStore(path),
+ await basename(path)
+ )
+ )
+ )
+}
diff --git a/src/components/FileSystem/Polyfill.ts b/src/components/FileSystem/Polyfill.ts
index 90e1b22fc..66c65368b 100644
--- a/src/components/FileSystem/Polyfill.ts
+++ b/src/components/FileSystem/Polyfill.ts
@@ -1,6 +1,7 @@
import { ref, markRaw } from 'vue'
import { VirtualDirectoryHandle } from './Virtual/DirectoryHandle'
import { VirtualFileHandle } from './Virtual/FileHandle'
+import { IndexedDbStore } from './Virtual/Stores/IndexedDb'
/**
* Chrome 93 and 94 crash when we try to call createWritable on a file handle inside of a web worker
@@ -55,7 +56,9 @@ if (
window.showDirectoryPicker = async () =>
// @ts-ignore Typescript doesn't like our polyfill
- markRaw(new VirtualDirectoryHandle(null, 'bridgeFolder', undefined))
+ markRaw(
+ new VirtualDirectoryHandle(new IndexedDbStore(), 'bridgeFolder')
+ )
}
}
@@ -91,10 +94,7 @@ if (isUnsupportedBrowser() || typeof window.showOpenFilePicker !== 'function') {
new VirtualFileHandle(
null,
file.name,
- new Uint8Array(
- await file.arrayBuffer()
- ),
- true
+ new Uint8Array(await file.arrayBuffer())
)
)
)
@@ -135,8 +135,7 @@ if (isUnsupportedBrowser() || typeof window.showSaveFilePicker !== 'function') {
return new VirtualFileHandle(
null,
options.suggestedName ?? 'newFile.txt',
- new Uint8Array(),
- true
+ new Uint8Array()
)
}
}
@@ -155,8 +154,7 @@ if (
return new VirtualFileHandle(
null,
file.name,
- new Uint8Array(await file.arrayBuffer()),
- true
+ new Uint8Array(await file.arrayBuffer())
)
} else if (this.kind === 'directory') {
return markRaw(new VirtualDirectoryHandle(null, 'unknown'))
diff --git a/src/components/FileSystem/Setup.ts b/src/components/FileSystem/Setup.ts
index 807dc44f3..862df15e5 100644
--- a/src/components/FileSystem/Setup.ts
+++ b/src/components/FileSystem/Setup.ts
@@ -13,6 +13,7 @@ import {
import { ConfirmationWindow } from '../Windows/Common/Confirm/ConfirmWindow'
import { FileSystem } from './FileSystem'
import { VirtualFileHandle } from './Virtual/FileHandle'
+import { IndexedDbStore } from './Virtual/Stores/IndexedDb'
type TFileSystemSetupStatus = 'waiting' | 'userInteracted' | 'done'
@@ -73,7 +74,10 @@ export class FileSystemSetup {
// Only create virtual folder if we are not migrating away from the virtual file system
if (!isUpgradingVirtualFs) {
fileHandle = markRaw(
- new VirtualDirectoryHandle(null, 'bridgeFolder', undefined)
+ new VirtualDirectoryHandle(
+ new IndexedDbStore(),
+ 'bridgeFolder'
+ )
)
await fileHandle.setupDone.fired
}
@@ -109,7 +113,7 @@ export class FileSystemSetup {
// Migrate virtual projects over
if (isUpgradingVirtualFs) {
const virtualFolder = markRaw(
- new VirtualDirectoryHandle(null, 'bridgeFolder', undefined)
+ new VirtualDirectoryHandle(null, 'bridgeFolder')
)
await virtualFolder.setupDone.fired
const virtualFs = new FileSystem(virtualFolder)
diff --git a/src/components/FileSystem/Virtual/Comlink.ts b/src/components/FileSystem/Virtual/Comlink.ts
index 164643eac..2650a55c9 100644
--- a/src/components/FileSystem/Virtual/Comlink.ts
+++ b/src/components/FileSystem/Virtual/Comlink.ts
@@ -9,7 +9,7 @@ export interface ISerializedFileHandle {
name: string
path: string[]
fileData?: Uint8Array
- idbWrapper?: string
+ baseStore?: any
}
transferHandlers.set('VirtualFileHandle', <
@@ -27,7 +27,7 @@ export interface ISerializedDirectoryHandle {
kind: 'directory'
name: string
path: string[]
- idbWrapper?: string
+ baseStore?: any
children?: (ISerializedFileHandle | ISerializedDirectoryHandle)[]
}
diff --git a/src/components/FileSystem/Virtual/DirectoryHandle.ts b/src/components/FileSystem/Virtual/DirectoryHandle.ts
index 4d03649fb..2773a8437 100644
--- a/src/components/FileSystem/Virtual/DirectoryHandle.ts
+++ b/src/components/FileSystem/Virtual/DirectoryHandle.ts
@@ -1,10 +1,10 @@
import { VirtualHandle, BaseVirtualHandle } from './Handle'
import { VirtualFileHandle } from './FileHandle'
import { ISerializedDirectoryHandle } from './Comlink'
-import { IDBWrapper } from './IDB'
-import { GlobalMutex } from '/@/components/Common/GlobalMutex'
-
-const globalMutex = new GlobalMutex()
+import { BaseStore, FsKindEnum, IDirEntry } from './Stores/BaseStore'
+import { MemoryStore } from './Stores/Memory'
+import { deserializeStore } from './Stores/Deserialize'
+import { getParent } from './getParent'
/**
* A class that implements a virtual folder
@@ -20,185 +20,99 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle {
*/
public readonly isFile = false
- get isDirectoryInMemory() {
- return this.children !== undefined
- }
async moveToIdb() {
- const children = this.allInMemoryChildren()
-
- this.idbWrapper.setMany(
- children.map((child) => [child.idbKey, child.moveData()])
+ if (!this._baseStore)
+ throw new Error(`Must call method on top-level directory`)
+ if (!(this._baseStore instanceof MemoryStore))
+ throw new Error(`Must call method on memory store`)
+
+ this._baseStore = await this._baseStore.toIdb(
+ // Do not allow writes to data-fs
+ true
)
}
- moveData() {
- if (!this.children)
- throw new Error(
- `No directory data to move to IDB for directory "${this.name}"`
- )
-
- const children = [...this.children.values()].map((child) => child.name)
- this.children = undefined
-
- return children
- }
- protected allInMemoryChildren() {
- if (!this.children) return []
-
- // Recursively get all in memory children
- const children: BaseVirtualHandle[] = [this]
-
- for (const child of this.children.values()) {
- if (child instanceof VirtualDirectoryHandle) {
- if (child.isDirectoryInMemory)
- children.push(...child.allInMemoryChildren())
- } else {
- if (child.isFileStoredInMemory) children.push(child)
- }
- }
-
- return children
- }
constructor(
- parent: VirtualDirectoryHandle | IDBWrapper | null,
+ parent: VirtualDirectoryHandle | BaseStore | null,
name: string,
- protected children?: Map | undefined,
- clearDB = false,
- path: string[] = []
+ path: string[] = [],
+ create = false
) {
super(parent, name, path)
- this.updateIdb(clearDB)
- }
- /**
- * Acquire exclusive access to this directory
- */
- async lockAccess() {
- await globalMutex.lock(this.idbKey)
- }
- /**
- * Release exclusive access to this directory
- */
- unlockAccess() {
- globalMutex.unlock(this.idbKey)
+ this.setup(create)
}
- async updateIdb(clearDB = false) {
- await this.lockAccess()
- if (clearDB) {
- await this.idbWrapper.clear()
- }
+ async setup(create: boolean) {
+ await this.setupStore()
- if (!this.children && !(await this.hasChildren()))
- await this.idbWrapper.set(this.idbKey, [])
+ /**
+ * TauriFsStore should not create the base directory because that'll lead to duplicated directories
+ */
+ if (this.idbKey !== '' && create)
+ await this.baseStore.createDirectory(this.idbKey)
this.setupDone.dispatch()
- this.unlockAccess()
}
- protected async addChild(child: VirtualHandle) {
- if (this.children) this.children.set(child.name, child)
- else
- await this.idbWrapper.set(this.idbKey, [
- ...new Set([...(await this.fromIdb()), child.name]),
- ])
- }
- protected async fromIdb() {
- return (await this.idbWrapper.get(this.idbKey)) ?? []
+ protected async fromStore() {
+ return (await this.baseStore.getDirectoryEntries(this.idbKey)) ?? []
}
protected async getChildren() {
- if (this.children) return [...this.children.values()]
- else
- return (
- (
- await Promise.all(
- (
- await this.fromIdb()
- ).map((name) => this.getChild(name))
- )
- ).filter((child) => child !== undefined)
- )
+ return (
+ (
+ await Promise.all(
+ (await this.fromStore()).map((name) => this.getChild(name))
+ )
+ ).filter((child) => child !== undefined)
+ )
}
- protected getChildPath(childName: string) {
+ protected getChildPath(child: IDirEntry | string) {
+ const childName = typeof child === 'string' ? child : child.name
+
return this.path.concat(childName).join('/')
}
- protected async getChild(childName: string) {
- if (this.children) return this.children.get(childName)
-
- if (await this.has(childName)) {
- const data = await this.idbWrapper.get(this.getChildPath(childName))
-
- if (data instanceof Uint8Array) {
- return new VirtualFileHandle(this, childName)
- } else if (Array.isArray(data)) {
- return new VirtualDirectoryHandle(this, childName)
- } else {
- // File/folder was deleted
- }
+ protected async getChild(child: IDirEntry | string) {
+ const childName = typeof child === 'string' ? child : child.name
+ const type =
+ typeof child === 'string'
+ ? await this.baseStore.typeOf(this.getChildPath(child))
+ : child.kind === FsKindEnum.Directory
+ ? 'directory'
+ : 'file'
+
+ if (type === 'file') {
+ return new VirtualFileHandle(this, childName)
+ } else if (type === 'directory') {
+ return new VirtualDirectoryHandle(this, childName)
+ } else if (type === null) {
+ return undefined
+ } else {
+ throw new Error(`Unknown type ${type}`)
}
}
- /**
- * @deprecated THIS IS NOT A PUBLIC API
- *
- * @param childName
- * @param lockMutex
- */
- async deleteChild(childName: string, lockMutex = true) {
- if (lockMutex) await this.lockAccess()
-
- if (this.children) this.children.delete(childName)
- else
- await this.idbWrapper.set(
- this.idbKey,
- (await this.fromIdb()).filter((name) => name !== childName)
- )
-
- if (lockMutex) this.unlockAccess()
- }
-
protected async hasChildren() {
- if (this.children) return this.children.size > 0
- else return (await this.fromIdb()).length > 0
- }
- protected async has(childName: string) {
- if (this.children) return this.children.has(childName)
- else return (await this.fromIdb()).includes(childName)
+ return (await this.fromStore()).length > 0
}
serialize(): ISerializedDirectoryHandle {
+ let baseStore: BaseStore | undefined = undefined
+ if (this.baseStore) baseStore = this.baseStore.serialize()
+
return {
- idbWrapper: this.idbWrapper.storeName,
+ baseStore,
kind: 'directory',
name: this.name,
path: this.path,
- children: this.children
- ? [...this.children.values()].map((child: VirtualHandle) =>
- child.serialize()
- )
- : undefined,
}
}
- static deserialize(
- data: ISerializedDirectoryHandle,
- parent: VirtualDirectoryHandle | null = null
- ) {
- const dir = new VirtualDirectoryHandle(
- !parent ? new IDBWrapper(data.idbWrapper) : parent,
- data.name,
- undefined,
- false,
- data.path
- )
+ static deserialize(data: ISerializedDirectoryHandle) {
+ let baseStore: BaseStore | null = null
- for (const child of data.children ?? []) {
- if (child.kind === 'directory')
- dir.addChild(VirtualDirectoryHandle.deserialize(child, dir))
- else
- dir.addChild(
- new VirtualFileHandle(dir, child.name, child.fileData)
- )
- }
+ if (data.baseStore) baseStore = deserializeStore(data.baseStore)
+
+ const dir = new VirtualDirectoryHandle(baseStore, data.name, data.path)
return dir
}
@@ -207,35 +121,23 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle {
name: string,
{ create }: { create?: boolean } = {}
) {
- await this.lockAccess()
-
let entry = await this.getChild(name)
if (entry && entry.kind === 'file') {
- this.unlockAccess()
throw new Error(
`TypeMismatch: Expected directory with name "${name}", found file`
)
} else if (!entry) {
if (create) {
- entry = new VirtualDirectoryHandle(
- this,
- name,
- this.children ? new Map() : undefined
- )
+ entry = new VirtualDirectoryHandle(this, name, [], true)
await entry.setupDone.fired
-
- await this.addChild(entry)
} else {
- this.unlockAccess()
throw new Error(
- `No file with the name ${name} exists in this folder`
+ `No directory with the name "${name}" exists in this folder`
)
}
}
- this.unlockAccess()
-
return entry
}
async getFileHandle(
@@ -245,11 +147,9 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle {
initialData,
}: { create?: boolean; initialData?: Uint8Array } = {}
) {
- await this.lockAccess()
let entry = await this.getChild(name)
if (entry && entry.kind === 'directory') {
- this.unlockAccess()
throw new Error(
`TypeMismatch: Expected file with name "${name}", found directory`
)
@@ -261,48 +161,36 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle {
initialData ?? new Uint8Array()
)
await entry.setupDone.fired
-
- await this.addChild(entry)
} else {
- this.unlockAccess()
throw new Error(
- `No file with the name ${name} exists in this folder`
+ `No file with the name "${name}" exists in this folder`
)
}
}
- this.unlockAccess()
-
return entry
}
async removeEntry(
name: string,
{ recursive }: { recursive?: boolean } = {}
) {
- await this.lockAccess()
-
const entry = await this.getChild(name)
if (!entry) {
- this.unlockAccess()
throw new Error(
- `No entry with the name ${name} exists in this folder`
+ `No entry with the name "${name}" exists in this folder`
)
} else if (
entry.kind === 'directory' &&
!recursive &&
(await (entry).hasChildren())
) {
- this.unlockAccess()
throw new Error(
`Cannot remove directory with children without "recursive" option being set to true`
)
}
- await entry.removeSelf(true, false)
-
- await this.deleteChild(name, false)
- this.unlockAccess()
+ await entry.removeSelf()
}
async resolve(possibleDescendant: VirtualHandle) {
const path: string[] = [possibleDescendant.name]
@@ -316,19 +204,21 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle {
if (current === null) return null
return path
}
- async removeSelf(isFirst = true, lockMutex = true) {
- if (lockMutex) await this.lockAccess()
-
+ async removeSelf() {
const children = await this.getChildren()
for (const child of children) {
- await child.removeSelf(false)
+ await child.removeSelf()
}
- if (!this.children) await this.idbWrapper.del(this.idbKey)
- if (this.parent && isFirst) this.parent.deleteChild(this.name)
-
- if (lockMutex) this.unlockAccess()
+ await this.baseStore.unlink(this.idbKey)
+ }
+ getParent() {
+ // We don't have a parent but we do have a base path -> We can traverse path backwards to create parent handle
+ if (this.parent === null && this.basePath.length > 0) {
+ this.parent = getParent(this.baseStore, this.basePath)
+ }
+ return this.parent
}
[Symbol.asyncIterator]() {
@@ -336,10 +226,8 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle {
}
keys() {
- if (this.children) return this.children.keys()
-
return {
- childNamesPromise: this.fromIdb(),
+ childNamesPromise: this.fromStore(),
[Symbol.asyncIterator]() {
return <
@@ -366,8 +254,6 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle {
}
}
entries() {
- if (this.children) return this.children.entries()
-
return {
childrenPromise: this.getChildren(),
@@ -399,8 +285,6 @@ export class VirtualDirectoryHandle extends BaseVirtualHandle {
}
}
values() {
- if (this.children) return this.children.values()
-
return {
childrenPromise: this.getChildren(),
diff --git a/src/components/FileSystem/Virtual/FileHandle.ts b/src/components/FileSystem/Virtual/FileHandle.ts
index e13f73e36..65eb56e48 100644
--- a/src/components/FileSystem/Virtual/FileHandle.ts
+++ b/src/components/FileSystem/Virtual/FileHandle.ts
@@ -1,8 +1,11 @@
import { BaseVirtualHandle } from './Handle'
-import type { VirtualDirectoryHandle } from './DirectoryHandle'
+import { VirtualDirectoryHandle } from './DirectoryHandle'
import { VirtualWritable, writeMethodSymbol } from './VirtualWritable'
import { ISerializedFileHandle } from './Comlink'
-import { IDBWrapper } from './IDB'
+import { BaseStore } from './Stores/BaseStore'
+import { deserializeStore } from './Stores/Deserialize'
+import { MemoryStore } from './Stores/Memory'
+import { getParent } from './getParent'
/**
* A class that implements a virtual file
@@ -18,109 +21,79 @@ export class VirtualFileHandle extends BaseVirtualHandle {
*/
public readonly isDirectory = false
- protected fileData?: Uint8Array
-
- get isFileStoredInMemory() {
- return this.fileData !== undefined
- }
- moveData() {
- if (!this.fileData)
- throw new Error(
- `No file data to move to IDB for file "${this.name}"`
- )
-
- const data = this.fileData
- this.fileData = undefined
- this.inMemory = false
- return data
- }
-
constructor(
- parent: VirtualDirectoryHandle | IDBWrapper | null,
+ parent: VirtualDirectoryHandle | BaseStore | null,
name: string,
data?: Uint8Array,
- protected inMemory = false,
path?: string[]
) {
super(parent, name, path)
- if (data) this.setup(data)
- else this.setupDone.dispatch()
- }
- protected async setup(fileData: Uint8Array) {
- // Composition API isn't available within web workers (and usage of markRaw isn't necessary there) so we can omit the markRaw call
- this.fileData = fileData
- const isDataFile = this.path.join('/').startsWith('data/packages')
+ this.setup(data)
+ }
+ protected async setup(fileData?: Uint8Array) {
+ await this.setupStore()
- // This prevents an IndexedDB overload by saving too many small data files to the DB
- if (this.inMemory || (isDataFile && fileData.length < 50_000)) {
- this.inMemory = true
- return this.setupDone.dispatch()
+ // We only need to write data files from the main thread, web workers can just load the already written data from the main thread
+ // Since MemoryStore extends IndexedDBStore, we cannot use instanceof on the IndexedDBStore directly
+ if (!globalThis.document && !(this.baseStore instanceof MemoryStore)) {
+ this.setupDone.dispatch()
+ return
}
- // We only need to write data files from the main thread, web workers can just load the already written data from the main thread
- if (!isDataFile || globalThis.document) await this.updateIdb(fileData)
- this.setupDone.dispatch()
- }
+ if (fileData) await this.baseStore.writeFile(this.idbKey, fileData)
- protected async updateIdb(data: Uint8Array) {
- await this.idbWrapper.set(this.idbKey, data)
- this.fileData = undefined
+ this.setupDone.dispatch()
}
- protected async loadFromIdb() {
- if (this.fileData) return this.fileData
-
- let storedData = await this.idbWrapper.get(this.idbKey)
- if (storedData === undefined) {
- console.log(this.parent)
- throw new Error(`File not found: "${this.path.join('/')}"`)
- }
-
- return storedData!
- }
serialize(): ISerializedFileHandle {
+ let baseStore: BaseStore | undefined = undefined
+ if (this.baseStore) baseStore = this.baseStore.serialize()
+
return {
- idbWrapper: this.idbWrapper.storeName,
+ baseStore,
kind: 'file',
name: this.name,
path: this.path,
- fileData: this.fileData,
}
}
static deserialize(data: ISerializedFileHandle) {
+ let baseStore: BaseStore | null = null
+ if (data.baseStore) baseStore = deserializeStore(data.baseStore)
+
return new VirtualFileHandle(
- data.idbWrapper ? new IDBWrapper(data.idbWrapper) : null,
+ baseStore,
data.name,
data.fileData,
- data.fileData !== undefined,
data.path
)
}
+ getParent() {
+ // We don't have a parent but we do have a base path -> We can traverse path backwards to create parent handle
+ if (this.parent === null && this.basePath.length > 0) {
+ this.parent = getParent(this.baseStore, this.basePath)
+ }
+ return this.parent
+ }
+
override async isSameEntry(other: BaseVirtualHandle): Promise {
if (this.parent === null) return false
return super.isSameEntry(other)
}
- async removeSelf(isFirst = true) {
- await this.idbWrapper.del(this.idbKey)
- if (this.parent && isFirst) this.parent.deleteChild(this.name)
+ async removeSelf() {
+ await this.baseStore.unlink(this.idbKey)
}
async getFile() {
- const fileData = await this.loadFromIdb()
- // console.log(this.path.join('/'), this.fileData, fileData)
-
- // TODO: Support lastModified timestamp so lightning cache can make use of its optimizations
- return new File([fileData], this.name)
+ return await this.baseStore.readFile(this.idbKey)
}
async createWritable() {
return new VirtualWritable(this)
}
async [writeMethodSymbol](data: Uint8Array) {
- if (this.inMemory) this.fileData = data
- else await this.updateIdb(data)
+ await this.baseStore.writeFile(this.idbKey, data)
}
}
diff --git a/src/components/FileSystem/Virtual/Handle.ts b/src/components/FileSystem/Virtual/Handle.ts
index dbe9631b3..377446c70 100644
--- a/src/components/FileSystem/Virtual/Handle.ts
+++ b/src/components/FileSystem/Virtual/Handle.ts
@@ -2,36 +2,51 @@ import type { VirtualDirectoryHandle } from './DirectoryHandle'
import type { VirtualFileHandle } from './FileHandle'
import { v4 as v4Uuid } from 'uuid'
import { Signal } from '../../Common/Event/Signal'
-import { IDBWrapper } from './IDB'
+import { BaseStore } from './Stores/BaseStore'
+import { MemoryStore } from './Stores/Memory'
+import { TauriFsStore } from './Stores/TauriFs'
export type VirtualHandle = VirtualDirectoryHandle | VirtualFileHandle
export abstract class BaseVirtualHandle {
- protected _idbWrapper: IDBWrapper | null = null
+ protected _baseStore: BaseStore | null = null
protected parent: VirtualDirectoryHandle | null = null
public readonly isVirtual = true
public abstract readonly kind: 'directory' | 'file'
public readonly setupDone = new Signal()
-
- abstract moveData(): any
+ protected includeSelfInPath = true
constructor(
- parent: VirtualDirectoryHandle | IDBWrapper | null,
+ parent: VirtualDirectoryHandle | BaseStore | null,
protected _name: string,
- protected basePath: string[] = [],
- public readonly uuid = v4Uuid()
+ protected basePath: string[] = []
) {
if (parent === null) {
- this._idbWrapper = new IDBWrapper()
- } else if (parent instanceof IDBWrapper) {
- this._idbWrapper = parent
+ this._baseStore = new MemoryStore()
+ } else if (parent instanceof BaseStore) {
+ this._baseStore = parent
} else {
this.parent = parent
}
+
+ /**
+ * TauriFs should not include self in path for top-level directory handle
+ */ if (this._baseStore instanceof TauriFsStore) {
+ this.includeSelfInPath = false
+ }
+ }
+
+ async setupStore() {
+ if (this._baseStore) await this._baseStore.setup()
+ }
+ getBaseStore() {
+ return this.baseStore
}
protected get path(): string[] {
- return this.parent ? this.parent.path.concat(this.name) : this.basePath
+ return this.parent
+ ? [...this.parent.path.concat(this.name)]
+ : [...this.basePath]
}
/**
* Returns whether a handle has parent context
@@ -42,23 +57,22 @@ export abstract class BaseVirtualHandle {
return this.path.length > 1
}
get idbKey() {
- if (this.path.length === 0) return this._name
+ if (this.path.length === 0)
+ return this.includeSelfInPath ? this.name : ''
return this.path.join('/')
}
- protected get idbWrapper(): IDBWrapper {
- if (this._idbWrapper === null) {
- return this.parent!.idbWrapper
+ protected get baseStore(): BaseStore {
+ if (this._baseStore === null) {
+ return this.parent!.baseStore
}
- return this._idbWrapper
+ return this._baseStore
}
abstract removeSelf(): Promise
get name() {
return this._name
}
- getParent() {
- return this.parent
- }
+ abstract getParent(): VirtualDirectoryHandle | null
abstract serialize(): unknown
async isSameEntry(other: BaseVirtualHandle) {
diff --git a/src/components/FileSystem/Virtual/Stores/BaseStore.ts b/src/components/FileSystem/Virtual/Stores/BaseStore.ts
new file mode 100644
index 000000000..828da1f00
--- /dev/null
+++ b/src/components/FileSystem/Virtual/Stores/BaseStore.ts
@@ -0,0 +1,72 @@
+export const FsKindEnum = {
+ Directory: 0,
+ File: 1,
+}
+export type TFsKind = typeof FsKindEnum['Directory' | 'File']
+export interface IFsEntry {
+ kind: TFsKind
+ data: T
+}
+export interface IFileData extends IFsEntry {
+ kind: typeof FsKindEnum.File
+ lastModified: number
+}
+export interface IDirectoryData extends IFsEntry {
+ kind: typeof FsKindEnum.Directory
+}
+export interface IDirEntry {
+ kind: TFsKind
+ name: string
+}
+
+export type TStoreType = 'idbStore' | 'memoryStore' | 'tauriFsStore'
+
+/**
+ * Base implementation of a file system backing store that can be used by our file system access polyfill
+ */
+export abstract class BaseStore {
+ public abstract readonly type: TStoreType
+
+ constructor(protected isReadOnly = false) {}
+
+ /**
+ * Any async setup that needs to be done before the store can be used
+ */
+ async setup() {}
+
+ abstract serialize(): T & { type: TStoreType }
+ static deserialize(data: any & { type: TStoreType }): BaseStore {
+ throw new Error('BaseStore deserialization not implemented')
+ }
+
+ /**
+ * Create directory
+ */
+ abstract createDirectory(path: string): Promise
+
+ /**
+ * Get directory entries
+ */
+ abstract getDirectoryEntries(path: string): Promise<(IDirEntry | string)[]>
+
+ /**
+ * Write file
+ */
+ abstract writeFile(path: string, data: Uint8Array): Promise
+
+ /**
+ * Read file
+ */
+ abstract readFile(path: string): Promise
+
+ /**
+ * Unlink a file or directory
+ */
+ abstract unlink(path: string): Promise
+
+ /**
+ * Return the type of a given path
+ * @returns null if the path does not exist
+ */
+ abstract typeOf(path: string): Promise
+}
diff --git a/src/components/FileSystem/Virtual/Stores/Deserialize.ts b/src/components/FileSystem/Virtual/Stores/Deserialize.ts
new file mode 100644
index 000000000..6b76e7b47
--- /dev/null
+++ b/src/components/FileSystem/Virtual/Stores/Deserialize.ts
@@ -0,0 +1,24 @@
+import type { BaseStore, TStoreType } from './BaseStore'
+import { IndexedDbStore } from './IndexedDb'
+import { MemoryStore } from './Memory'
+import { TauriFsStore } from './TauriFs'
+
+export function deserializeStore(data: any & { type: TStoreType }): BaseStore {
+ if (typeof data !== 'object' || data === null)
+ throw new Error('BaseStore deserialization data must be an object')
+ if (!('type' in data))
+ throw new Error(
+ 'BaseStore deserialization data must have a type property'
+ )
+
+ switch (data.type) {
+ case 'idbStore':
+ return IndexedDbStore.deserialize(data)
+ case 'memoryStore':
+ return MemoryStore.deserialize(data)
+ case 'tauriFsStore':
+ return TauriFsStore.deserialize(data)
+ default:
+ throw new Error(`Unknown base store type: ${data.type}`)
+ }
+}
diff --git a/src/components/FileSystem/Virtual/Stores/IndexedDb.ts b/src/components/FileSystem/Virtual/Stores/IndexedDb.ts
new file mode 100644
index 000000000..f664a85b7
--- /dev/null
+++ b/src/components/FileSystem/Virtual/Stores/IndexedDb.ts
@@ -0,0 +1,207 @@
+import { IDBWrapper } from '../IDB'
+import {
+ BaseStore,
+ FsKindEnum,
+ IDirectoryData,
+ IDirEntry,
+ IFileData,
+ TFsKind,
+ TStoreType,
+} from './BaseStore'
+import { GlobalMutex } from '/@/components/Common/GlobalMutex'
+import { basename, dirname } from '/@/utils/path'
+
+export interface IIndexedDbSerializedData {
+ storeName?: string
+ mapData?: [string, any][]
+}
+
+export class IndexedDbStore extends BaseStore {
+ public readonly type = 'idbStore'
+ protected idb: IDBWrapper
+ protected globalMutex = new GlobalMutex()
+
+ constructor(storeName?: string, isReadOnly = false) {
+ super(isReadOnly)
+ this.idb = new IDBWrapper(storeName)
+ }
+
+ serialize() {
+ return {
+ type: this.type,
+ storeName: this.idb.storeName,
+ }
+ }
+ static deserialize(data: IIndexedDbSerializedData & { type: TStoreType }) {
+ return new IndexedDbStore(data.storeName)
+ }
+
+ lockAccess(path: string) {
+ return this.globalMutex.lock(path)
+ }
+ unlockAccess(path: string) {
+ return this.globalMutex.unlock(path)
+ }
+
+ clear() {
+ if (this.isReadOnly) return
+
+ return this.idb.clear()
+ }
+
+ protected async addChild(
+ parentDir: string,
+ childName: string,
+ childKind: TFsKind
+ ) {
+ // If parent directory is root directory, we don't need to manually add a child entry
+ if (parentDir === '' || parentDir === '.') return
+
+ await this.lockAccess(parentDir)
+
+ let parentChilds = await this.idb.get(
+ parentDir
+ )
+
+ if (parentChilds === undefined) {
+ await this.createDirectory(parentDir)
+ parentChilds = []
+ }
+
+ if (Array.isArray(parentChilds)) {
+ // Old format where we stored dir entries as strings
+
+ // Filter out childName from parentChilds
+ parentChilds = parentChilds
+ .filter((child) => typeof child === 'string') // This filter call is only there to fix already duplicated entries in the database. Can be removed in a future update
+ .filter((child) => child !== childName)
+
+ // Push new child entry
+ parentChilds.push(childName)
+ } else {
+ // New format where we store dir entries as objects
+
+ // Filter out childName from parentChilds
+ parentChilds = {
+ kind: FsKindEnum.Directory,
+ data: parentChilds.data.filter((child) =>
+ typeof child === 'string'
+ ? child !== childName
+ : child.name !== childName
+ ),
+ }
+
+ // Push new child entry
+ parentChilds.data.push({
+ kind: childKind,
+ name: childName,
+ })
+ }
+
+ await this.idb.set(parentDir, parentChilds)
+
+ this.unlockAccess(parentDir)
+ }
+
+ async createDirectory(path: string) {
+ if (this.isReadOnly) return
+ if (path === '' || path === '.') return
+
+ const dirExists = await this.idb.has(path)
+ if (dirExists) return // No work to do, directory already exists
+
+ const parentDir = dirname(path)
+ const dirName = basename(path)
+
+ await this.addChild(parentDir, dirName, FsKindEnum.Directory)
+ await this.idb.set(path, { kind: FsKindEnum.Directory, data: [] })
+ }
+
+ async getDirectoryEntries(path: string) {
+ const entries = await this.idb.get(path)
+ if (entries === undefined) {
+ throw new Error(
+ `Trying to get directory entries for ${path} but it does not exist`
+ )
+ }
+
+ return Array.isArray(entries) ? entries : entries.data
+ }
+
+ async writeFile(path: string, data: Uint8Array) {
+ if (this.isReadOnly) return
+
+ const parentDir = dirname(path)
+ const fileName = basename(path)
+
+ await this.addChild(parentDir, fileName, FsKindEnum.File)
+
+ await this.idb.set(path, {
+ kind: FsKindEnum.File,
+ lastModified: Date.now(),
+ data,
+ })
+ }
+
+ async readFile(path: string) {
+ const rawData = await this.idb.get(path)
+
+ if (rawData === undefined) {
+ throw new Error(`Trying to read file ${path} that does not exist`)
+ }
+
+ let data: Uint8Array
+ let lastModified = Date.now()
+ // Old format where we stored Uint8Array directly
+ if (rawData instanceof Uint8Array) {
+ data = rawData
+ } else {
+ lastModified = rawData.lastModified ?? Date.now()
+ data = rawData.data ?? new Uint8Array()
+ }
+
+ return new File([data], basename(path), { lastModified })
+ }
+
+ async unlink(path: string) {
+ if (this.isReadOnly) return
+
+ const parentDir = dirname(path)
+ const fileName = basename(path)
+
+ await this.lockAccess(parentDir)
+
+ const parentChilds = await this.idb.get(
+ parentDir
+ )
+ if (parentChilds !== undefined) {
+ if (Array.isArray(parentChilds)) {
+ await this.idb.set(
+ parentDir,
+ parentChilds.filter((child) => child !== fileName)
+ )
+ } else {
+ await this.idb.set(parentDir, {
+ kind: FsKindEnum.Directory,
+ data: parentChilds.data.filter(
+ (child) => child.name !== fileName
+ ),
+ })
+ }
+ }
+ // TODO: Fix directory unlinking
+
+ await this.idb.del(path)
+
+ this.unlockAccess(parentDir)
+ }
+
+ async typeOf(path: string) {
+ const data = await this.idb.get(path)
+ if (data === undefined) return null
+
+ if (data instanceof Uint8Array) return 'file'
+ else if (Array.isArray(data)) return 'directory'
+ else return data.kind === FsKindEnum.Directory ? 'directory' : 'file' // New object based file format
+ }
+}
diff --git a/src/components/FileSystem/Virtual/Stores/Memory.ts b/src/components/FileSystem/Virtual/Stores/Memory.ts
new file mode 100644
index 000000000..4a5c78290
--- /dev/null
+++ b/src/components/FileSystem/Virtual/Stores/Memory.ts
@@ -0,0 +1,80 @@
+import { IDBWrapper } from '../IDB'
+import { TFsKind, TStoreType } from './BaseStore'
+import { IIndexedDbSerializedData, IndexedDbStore } from './IndexedDb'
+
+export class MemoryDb extends IDBWrapper {
+ public readonly type = 'memoryStore'
+ protected _store: Map
+
+ constructor(
+ public readonly storeName: string = 'virtual-fs',
+ mapData?: [IDBValidKey, any][]
+ ) {
+ super(storeName)
+
+ this._store = new Map(mapData)
+ }
+
+ async set(key: IDBValidKey, value: any) {
+ this._store.set(key, value)
+ }
+ async get(key: IDBValidKey) {
+ return this._store.get(key)
+ }
+ async del(key: IDBValidKey) {
+ this._store.delete(key)
+ }
+ async setMany(arr: [IDBValidKey, any][]) {
+ for (const [key, value] of arr) {
+ await this.set(key, value)
+ }
+ }
+ async getMany(arr: IDBValidKey[]) {
+ const res: T[] = []
+ for (const key of arr) {
+ res.push(await this.get(key))
+ }
+ return res
+ }
+ async clear() {
+ return this._store.clear()
+ }
+ async has(key: IDBValidKey) {
+ return this._store.has(key)
+ }
+ async keys() {
+ return [...this._store.keys()]
+ }
+ entries() {
+ return this._store.entries()
+ }
+
+ async toIdb(isReadOnly = false) {
+ await super.setMany([...this._store.entries()])
+ return new IndexedDbStore(this.storeName, isReadOnly)
+ }
+}
+
+export class MemoryStore extends IndexedDbStore {
+ protected idb: MemoryDb
+
+ constructor(storeName?: string, mapData?: [IDBValidKey, any][]) {
+ super(storeName)
+ this.idb = new MemoryDb(storeName, mapData)
+ }
+
+ serialize() {
+ return {
+ type: this.type,
+ storeName: this.idb.storeName,
+ mapData: [...this.idb.entries()],
+ }
+ }
+ static deserialize(data: IIndexedDbSerializedData & { type: TStoreType }) {
+ return new MemoryStore(data.storeName, data.mapData)
+ }
+
+ toIdb(isReadOnly = false) {
+ return this.idb.toIdb(isReadOnly)
+ }
+}
diff --git a/src/components/FileSystem/Virtual/Stores/TauriFs.ts b/src/components/FileSystem/Virtual/Stores/TauriFs.ts
new file mode 100644
index 000000000..57667ce22
--- /dev/null
+++ b/src/components/FileSystem/Virtual/Stores/TauriFs.ts
@@ -0,0 +1,116 @@
+import {
+ createDir,
+ readDir,
+ writeBinaryFile,
+ readBinaryFile,
+ removeDir,
+ removeFile,
+} from '@tauri-apps/api/fs'
+import { basename, join, sep } from '@tauri-apps/api/path'
+import { invoke } from '@tauri-apps/api/tauri'
+import { BaseStore, FsKindEnum, type TStoreType } from './BaseStore'
+
+export interface ITauriFsSerializedData {
+ baseDirectory?: string
+}
+
+export class TauriFsStore extends BaseStore {
+ public readonly type = 'tauriFsStore'
+
+ constructor(protected baseDirectory?: string) {
+ super()
+ }
+
+ getBaseDirectory() {
+ return this.baseDirectory
+ }
+
+ serialize() {
+ return {
+ type: this.type,
+ baseDirectory: this.baseDirectory,
+ }
+ }
+ static deserialize(data: ITauriFsSerializedData & { type: TStoreType }) {
+ return new TauriFsStore(data.baseDirectory)
+ }
+
+ async setup() {
+ if (this.isReadOnly) return
+
+ if (this.baseDirectory)
+ await createDir(this.baseDirectory).catch(() => {
+ // Ignore error if directory already exists
+ })
+ }
+
+ async resolvePath(path: string) {
+ path = path.replaceAll(/\\|\//g, sep)
+ if (!this.baseDirectory) return path
+ return await join(this.baseDirectory, path)
+ }
+
+ async createDirectory(path: string) {
+ if (this.isReadOnly) return
+
+ await createDir(await this.resolvePath(path)).catch(() => {
+ // Ignore error if directory already exists
+ })
+ }
+
+ async getDirectoryEntries(path: string) {
+ const entries = await readDir(await this.resolvePath(path))
+
+ return entries.map((entry) => ({
+ kind: entry.children ? FsKindEnum.Directory : FsKindEnum.File,
+ name: entry.name!,
+ }))
+ }
+
+ async writeFile(path: string, data: Uint8Array) {
+ if (this.isReadOnly) return
+
+ await writeBinaryFile(await this.resolvePath(path), data)
+ }
+
+ async readFile(path: string) {
+ const [lastModified, data] = (await invoke('get_file_data', {
+ path: await this.resolvePath(path),
+ })) as [number, number[]]
+
+ return new File([new Uint8Array(data)], await basename(path), {
+ // Rust returns a unix timestamp in seconds but File expects milliseconds
+ lastModified: lastModified * 1000,
+ })
+ }
+
+ async unlink(path: string) {
+ if (this.isReadOnly) return
+
+ const type = await this.typeOf(path)
+ // Path does not exist, nothing to unlink
+ if (type === null) return
+
+ if (type === 'file') {
+ await removeFile(await this.resolvePath(path))
+ } else {
+ await removeDir(await this.resolvePath(path))
+ }
+ }
+
+ async typeOf(path: string) {
+ const resolvedPath = await this.resolvePath(path)
+
+ try {
+ await readDir(resolvedPath)
+ return 'directory'
+ } catch (err) {
+ try {
+ await readBinaryFile(resolvedPath)
+ return 'file'
+ } catch (err) {
+ return null
+ }
+ }
+ }
+}
diff --git a/src/components/FileSystem/Virtual/getParent.ts b/src/components/FileSystem/Virtual/getParent.ts
new file mode 100644
index 000000000..eba429f27
--- /dev/null
+++ b/src/components/FileSystem/Virtual/getParent.ts
@@ -0,0 +1,11 @@
+import { VirtualDirectoryHandle } from './DirectoryHandle'
+import { BaseStore } from './Stores/BaseStore'
+
+export function getParent(baseStore: BaseStore, basePath: string[]) {
+ return new VirtualDirectoryHandle(
+ baseStore,
+ // Base path always contains itself so new directory handle name is at index - 2
+ basePath.length > 1 ? basePath[basePath.length - 2] : '',
+ basePath.slice(0, -1)
+ )
+}
diff --git a/src/components/FileSystem/Virtual/pathFromHandle.ts b/src/components/FileSystem/Virtual/pathFromHandle.ts
new file mode 100644
index 000000000..a3404446e
--- /dev/null
+++ b/src/components/FileSystem/Virtual/pathFromHandle.ts
@@ -0,0 +1,19 @@
+import { AnyHandle } from '../Types'
+import { BaseVirtualHandle } from './Handle'
+
+export async function pathFromHandle(handle: AnyHandle) {
+ if (!import.meta.env.VITE_IS_TAURI_APP)
+ throw new Error('Can only get path from handle in Tauri builds')
+ if (!(handle instanceof BaseVirtualHandle))
+ throw new Error(`Expected a virtual handle`)
+
+ const { TauriFsStore } = await import('./Stores/TauriFs')
+
+ const baseStore = handle.getBaseStore()
+ if (!(baseStore instanceof TauriFsStore))
+ throw new Error(
+ `Expected a TauriFsStore instance to back VirtualHandle`
+ )
+
+ return await baseStore.resolvePath(handle.idbKey)
+}
diff --git a/src/components/FileSystem/saveOrDownload.ts b/src/components/FileSystem/saveOrDownload.ts
index b9e6a38af..189b4b119 100644
--- a/src/components/FileSystem/saveOrDownload.ts
+++ b/src/components/FileSystem/saveOrDownload.ts
@@ -5,12 +5,21 @@ import { FileSystem } from './FileSystem'
import { isUsingFileSystemPolyfill, isUsingOriginPrivateFs } from './Polyfill'
import { App } from '/@/App'
import { basename, extname } from '/@/utils/path'
+import { revealInFileExplorer } from '/@/utils/revealInFileExplorer'
export async function saveOrDownload(
filePath: string,
fileData: Uint8Array,
fileSystem: FileSystem
) {
+ if (
+ import.meta.env.VITE_IS_TAURI_APP ||
+ !isUsingOriginPrivateFs ||
+ isUsingFileSystemPolyfill.value
+ ) {
+ await fileSystem.writeFile(filePath, fileData)
+ }
+
const notification = createNotification({
icon: 'mdi-download',
color: 'success',
@@ -20,7 +29,15 @@ export async function saveOrDownload(
onClick: async () => {
const app = await App.getApp()
- if (
+ if (import.meta.env.VITE_IS_TAURI_APP) {
+ const { join, appLocalDataDir } = await import(
+ '@tauri-apps/api/path'
+ )
+
+ revealInFileExplorer(
+ await join(await appLocalDataDir(), 'bridge', filePath)
+ )
+ } else if (
app.project.isLocal ||
isUsingOriginPrivateFs ||
isUsingFileSystemPolyfill.value
@@ -37,10 +54,6 @@ export async function saveOrDownload(
notification.dispose()
},
})
-
- if (!isUsingOriginPrivateFs || isUsingFileSystemPolyfill.value) {
- await fileSystem.writeFile(filePath, fileData)
- }
}
const knownExtensions = new Set([
@@ -59,6 +72,12 @@ export function download(fileName: string, fileData: Uint8Array) {
const extension = extname(fileName)
let type: string | undefined = undefined
+ // File downloads are not working on Tauri atm
+ if (import.meta.env.VITE_IS_TAURI_APP) {
+ tauriDownload(fileName, fileData)
+ return
+ }
+
// Maintain the extension from the fileName, if the file that is being downloaded has a known extension
if (knownExtensions.has(extension)) type = 'application/file-export'
@@ -70,3 +89,24 @@ export function download(fileName: string, fileData: Uint8Array) {
URL.revokeObjectURL(url)
}
+
+async function tauriDownload(fileName: string, fileData: Uint8Array) {
+ const { writeBinaryFile, exists } = await import('@tauri-apps/api/fs')
+ const { downloadDir, join } = await import('@tauri-apps/api/path')
+
+ let freeFileName = fileName
+ let i = 1
+ while (await exists(await join(await downloadDir(), freeFileName))) {
+ const extension = extname(freeFileName)
+ // Remove extension and counter (i) from the file name
+ const name = basename(freeFileName, extension).replace(/ \(\d+\)$/, '')
+ freeFileName = `${name} (${i})${extension}`
+ i++
+ }
+
+ await writeBinaryFile(
+ await join(await downloadDir(), freeFileName),
+ fileData
+ )
+ await revealInFileExplorer(await join(await downloadDir(), freeFileName))
+}
diff --git a/src/components/FindAndReplace/Tab.ts b/src/components/FindAndReplace/Tab.ts
index d95385646..5e6b620e2 100644
--- a/src/components/FindAndReplace/Tab.ts
+++ b/src/components/FindAndReplace/Tab.ts
@@ -1,6 +1,5 @@
import { reactive, markRaw } from 'vue'
import { Remote, wrap } from 'comlink'
-import { Signal } from '../Common/Event/Signal'
import { searchType } from './Controls/searchType'
import FindAndReplaceComponent from './Tab.vue'
import type {
@@ -15,8 +14,11 @@ import { TabSystem } from '../TabSystem/TabSystem'
import { Mutex } from '../Common/Mutex'
import { AnyFileHandle } from '../FileSystem/Types'
import { translate } from '../Locales/Manager'
+import { setupWorker } from '/@/utils/worker/setup'
-const FindAndReplaceClass = wrap(new Worker())
+const worker = new Worker()
+const FindAndReplaceClass = wrap(worker)
+setupWorker(worker)
interface ITabState {
scrollTop: number
diff --git a/src/components/FindAndReplace/Worker/Worker.ts b/src/components/FindAndReplace/Worker/Worker.ts
index d63f602b8..81efc1486 100644
--- a/src/components/FindAndReplace/Worker/Worker.ts
+++ b/src/components/FindAndReplace/Worker/Worker.ts
@@ -1,11 +1,5 @@
// @ts-ignore Make "path" work on this worker
-globalThis.process = {
- cwd: () => '',
- env: {},
- release: {
- name: 'browser',
- },
-}
+import '/@/utils/worker/inject'
import '/@/components/FileSystem/Virtual/Comlink'
import { expose } from 'comlink'
diff --git a/src/components/ImportFolder/ImportProjects.ts b/src/components/ImportFolder/ImportProjects.ts
new file mode 100644
index 000000000..40f13c1b9
--- /dev/null
+++ b/src/components/ImportFolder/ImportProjects.ts
@@ -0,0 +1,72 @@
+import { FileSystem } from '../FileSystem/FileSystem'
+import { showFolderPicker } from '../FileSystem/Pickers/showFolderPicker'
+import { translate } from '../Locales/Manager'
+import { virtualProjectName } from '../Projects/Project/Project'
+import { ConfirmationWindow } from '../Windows/Common/Confirm/ConfirmWindow'
+import { InformationWindow } from '../Windows/Common/Information/InformationWindow'
+import { App } from '/@/App'
+
+export async function importProjects() {
+ const app = await App.getApp()
+
+ // Show warning if the user has projects already
+ const projects = (await app.fileSystem.readdir('projects')).filter(
+ (projectName) => projectName !== virtualProjectName
+ )
+
+ if (projects.length > 0) {
+ const confirmWindow = new ConfirmationWindow({
+ description:
+ 'packExplorer.noProjectView.importOldProjects.confirmOverwrite',
+ })
+ const choice = await confirmWindow.fired
+
+ if (!choice) return
+ }
+
+ app.windows.loadingWindow.open()
+
+ const bridgeProjects = await showFolderPicker({ multiple: true })
+
+ if (!bridgeProjects) {
+ app.windows.loadingWindow.close()
+ return
+ }
+
+ let didSelectProject = false
+ for (const bridgeProject of bridgeProjects) {
+ const bridgeProjectFs = new FileSystem(bridgeProject)
+ // Ensure that the folder is a bridge project by checking for the config.json fike
+ if (!(await bridgeProjectFs.fileExists('config.json'))) {
+ new InformationWindow({
+ title: `[${translate('toolbar.project.name')}: "${
+ bridgeProject.name
+ }"]`,
+ description:
+ 'packExplorer.noProjectView.importOldProjects.notABridgeProject',
+ })
+ continue
+ }
+
+ const newProjectDir = await app.fileSystem.getDirectoryHandle(
+ `projects/${bridgeProject.name}`,
+ { create: true }
+ )
+ await app.fileSystem.copyFolderByHandle(
+ bridgeProject,
+ newProjectDir,
+ new Set(['builds', '.git'])
+ )
+
+ // Load projects and only select the first one
+ app.projectManager.addProject(
+ bridgeProject,
+ true,
+ true,
+ !didSelectProject
+ )
+ if (!didSelectProject) didSelectProject = true
+ }
+
+ app.windows.loadingWindow.close()
+}
diff --git a/src/components/ImportFolder/Manager.ts b/src/components/ImportFolder/Manager.ts
index 9a1dd546b..92f3cd166 100644
--- a/src/components/ImportFolder/Manager.ts
+++ b/src/components/ImportFolder/Manager.ts
@@ -13,16 +13,24 @@ export class FolderImportManager {
protected readonly folderHandler = new Set()
constructor() {
- this.addImporter({
- icon: 'mdi-minecraft',
- name: 'fileDropper.importMethod.folder.output.name',
- description: 'fileDropper.importMethod.folder.output.description',
- onSelect: async (directoryHandle) => {
- const app = await App.getApp()
+ /**
+ * Setting the output folder isn't possible on Tauri builds
+ * We don't want a browser directory handle, we want our custom virtual directory handle
+ */
+ if (!import.meta.env.VITE_IS_TAURI_APP) {
+ this.addImporter({
+ icon: 'mdi-minecraft',
+ name: 'fileDropper.importMethod.folder.output.name',
+ description:
+ 'fileDropper.importMethod.folder.output.description',
+ onSelect: async (directoryHandle) => {
+ const app = await App.getApp()
+
+ await app.comMojang.handleComMojangDrop(directoryHandle)
+ },
+ })
+ }
- await app.comMojang.handleComMojangDrop(directoryHandle)
- },
- })
this.addImporter({
icon: 'mdi-folder-open-outline',
name: 'fileDropper.importMethod.folder.open.name',
@@ -54,6 +62,16 @@ export class FolderImportManager {
}
async onImportFolder(directoryHandle: AnyDirectoryHandle) {
+ /**
+ * If there is only one handler, use it without prompting the user for a choice
+ */
+ if (this.folderHandler.size === 1) {
+ const handler = [...this.folderHandler][0]
+ await handler.onSelect(directoryHandle)
+
+ return
+ }
+
const informedChoiceWindow = new InformedChoiceWindow(
'fileDropper.importMethod.name',
{
diff --git a/src/components/Mixins/AppToolbarHeight.ts b/src/components/Mixins/AppToolbarHeight.ts
index fe940a343..308cb59ed 100644
--- a/src/components/Mixins/AppToolbarHeight.ts
+++ b/src/components/Mixins/AppToolbarHeight.ts
@@ -2,12 +2,20 @@
import { WindowControlsOverlayMixin } from './WindowControlsOverlay'
import { isInFullScreen } from '/@/components/TabSystem/TabContextMenu/Fullscreen'
+import { platform } from '/@/utils/os'
export const AppToolbarHeightMixin = {
mixins: [WindowControlsOverlayMixin],
+ data: () => ({
+ toolbarPaddingLeft:
+ import.meta.env.VITE_IS_TAURI_APP && platform() === 'darwin'
+ ? `70px`
+ : `0px`,
+ }),
computed: {
appToolbarHeight() {
if (isInFullScreen.value) return `0px`
+ if (import.meta.env.VITE_IS_TAURI_APP) return `28px`
return `env(titlebar-area-height, ${
this.$vuetify.breakpoint.mobile ? 0 : 24
@@ -15,6 +23,7 @@ export const AppToolbarHeightMixin = {
},
appToolbarHeightNumber() {
if (isInFullScreen.value) return 0
+ if (import.meta.env.VITE_IS_TAURI_APP) return 28
if (this.windowControlsOverlay) return 33
return this.$vuetify.breakpoint.mobile ? 0 : 24
diff --git a/src/components/Mixins/WindowControlsOverlay.ts b/src/components/Mixins/WindowControlsOverlay.ts
index a369f1c6c..3e4b6fd88 100644
--- a/src/components/Mixins/WindowControlsOverlay.ts
+++ b/src/components/Mixins/WindowControlsOverlay.ts
@@ -7,7 +7,9 @@ export const WindowControlsOverlayMixin = {
navigator.windowControlsOverlay.visible,
}),
created() {
- if (navigator.windowControlsOverlay) {
+ if (import.meta.env.VITE_IS_TAURI_APP) {
+ this.windowControlsOverlay = true
+ } else if (navigator.windowControlsOverlay) {
navigator.windowControlsOverlay.addEventListener(
'geometrychange',
(event) => {
diff --git a/src/components/OutputFolders/ComMojang/ComMojang.ts b/src/components/OutputFolders/ComMojang/ComMojang.ts
index 0bee10300..c2391a3ca 100644
--- a/src/components/OutputFolders/ComMojang/ComMojang.ts
+++ b/src/components/OutputFolders/ComMojang/ComMojang.ts
@@ -1,10 +1,13 @@
import { del, get, set } from 'idb-keyval'
-import { ConfirmationWindow } from '/@/components/Windows/Common/Confirm/ConfirmWindow'
import { FileSystem } from '/@/components/FileSystem/FileSystem'
import { App } from '/@/App'
import { Signal } from '/@/components/Common/Event/Signal'
import { InformationWindow } from '/@/components/Windows/Common/Information/InformationWindow'
import { AnyDirectoryHandle } from '/@/components/FileSystem/Types'
+import { VirtualDirectoryHandle } from '../../FileSystem/Virtual/DirectoryHandle'
+import { TauriFsStore } from '../../FileSystem/Virtual/Stores/TauriFs'
+import { basename, join } from '/@/utils/path'
+import { platform } from '/@/utils/os'
export const comMojangKey = 'comMojangDirectory'
@@ -37,9 +40,36 @@ export class ComMojang extends Signal {
async setupComMojang() {
if (this._hasComMojang) return false
- const directoryHandle = await get(
- comMojangKey
- )
+ let directoryHandle = await get<
+ AnyDirectoryHandle | string | undefined
+ >(comMojangKey)
+
+ // Auto-infer com.mojang folder for Tauri builds on windows
+ if (
+ import.meta.env.VITE_IS_TAURI_APP &&
+ directoryHandle === undefined &&
+ platform() === 'win32'
+ ) {
+ const { join, localDataDir } = await import('@tauri-apps/api/path')
+
+ directoryHandle = await join(
+ await localDataDir(),
+ 'Packages\\Microsoft.MinecraftUWP_8wekyb3d8bbwe\\LocalState\\games\\com.mojang'
+ )
+ }
+
+ if (typeof directoryHandle === 'string') {
+ if (!import.meta.env.VITE_IS_TAURI_APP)
+ throw new Error(
+ 'Cannot use path reference to com.mojang directoryHandle outside of Tauri builds'
+ )
+
+ const dirName = basename(directoryHandle)
+ directoryHandle = new VirtualDirectoryHandle(
+ new TauriFsStore(directoryHandle),
+ dirName
+ )
+ }
this._hasComMojangHandle = directoryHandle !== undefined
@@ -47,7 +77,9 @@ export class ComMojang extends Signal {
await this.requestPermissions(directoryHandle).catch(async () => {
// Permission request failed because user activation was too long ago
// -> Create window to get new activation
- await this.createPermissionWindow(directoryHandle)
+ await this.createPermissionWindow(
+ directoryHandle
+ )
})
if (this._hasComMojang) {
@@ -90,7 +122,18 @@ export class ComMojang extends Signal {
}
async set(directoryHandle: AnyDirectoryHandle) {
- set(comMojangKey, directoryHandle)
+ if (directoryHandle instanceof VirtualDirectoryHandle) {
+ const store = directoryHandle.getBaseStore()
+ if (!(store instanceof TauriFsStore))
+ throw new Error(
+ 'Cannot set com.mojang directoryHandle to non-tauri-backed store'
+ )
+
+ set(comMojangKey, store.getBaseDirectory())
+ } else {
+ set(comMojangKey, directoryHandle)
+ }
+
await this.requestPermissions(directoryHandle)
if (this._hasComMojang) this.setup.dispatch()
diff --git a/src/components/PackExplorer/HomeView/BridgeFolderBtn.vue b/src/components/PackExplorer/HomeView/BridgeFolderBtn.vue
index 2c1249a33..f4e85b174 100644
--- a/src/components/PackExplorer/HomeView/BridgeFolderBtn.vue
+++ b/src/components/PackExplorer/HomeView/BridgeFolderBtn.vue
@@ -1,6 +1,6 @@
+
@@ -28,6 +29,7 @@ import BridgeFolderBtn from './BridgeFolderBtn.vue'
import SetupView from './SetupView.vue'
import Project from './Project.vue'
import { isUsingFileSystemPolyfill } from '../../FileSystem/Polyfill'
+import ImportOldProjects from './ImportOldProjects.vue'
export default {
components: {
@@ -35,6 +37,7 @@ export default {
BridgeFolderBtn,
SetupView,
Project,
+ ImportOldProjects,
},
setup() {
return {
diff --git a/src/components/PackExplorer/HomeView/ImportOldProjects.vue b/src/components/PackExplorer/HomeView/ImportOldProjects.vue
new file mode 100644
index 000000000..0899ac680
--- /dev/null
+++ b/src/components/PackExplorer/HomeView/ImportOldProjects.vue
@@ -0,0 +1,81 @@
+
+
+
+
+ mdi-import
+
+ {{ t(`packExplorer.noProjectView.importOldProjects.name`) }}
+
+
+
+
+ {{ t(`packExplorer.noProjectView.importOldProjects.name`) }}
+
+
+
+
+
diff --git a/src/components/PackExplorer/HomeView/SetupView.vue b/src/components/PackExplorer/HomeView/SetupView.vue
index 6ab100986..e97dc713a 100644
--- a/src/components/PackExplorer/HomeView/SetupView.vue
+++ b/src/components/PackExplorer/HomeView/SetupView.vue
@@ -1,6 +1,7 @@
+
@@ -24,7 +25,7 @@
@@ -85,6 +86,7 @@ import { TranslationMixin } from '/@/components/Mixins/TranslationMixin'
import BridgeSheet from '/@/components/UIElements/Sheet.vue'
import CreateProjectBtn from './CreateProjectBtn.vue'
import BridgeFolderBtn from './BridgeFolderBtn.vue'
+import ImportOldProjects from './ImportOldProjects.vue'
import { SimpleAction } from '/@/components/Actions/SimpleAction'
import ActionViewer from '/@/components/Actions/ActionViewer.vue'
import { SettingsWindow } from '/@/components/Windows/Settings/SettingsWindow'
@@ -100,6 +102,7 @@ export default {
CreateProjectBtn,
BridgeFolderBtn,
ActionViewer,
+ ImportOldProjects,
},
setup() {
return {
@@ -172,6 +175,15 @@ export default {
],
}
},
+ computed: {
+ mayShowComMojangSetupPanel() {
+ return (
+ !import.meta.env.VITE_IS_TAURI_APP &&
+ !this.hasComMojangSetup &&
+ !this.isUsingFileSystemPolyfill
+ )
+ },
+ },
methods: {
async setJsonEditor(val) {
await SettingsWindow.loadSettings(App.instance)
diff --git a/src/components/PackExplorer/PackExplorer.ts b/src/components/PackExplorer/PackExplorer.ts
index 4caccc45f..25435eac3 100644
--- a/src/components/PackExplorer/PackExplorer.ts
+++ b/src/components/PackExplorer/PackExplorer.ts
@@ -24,6 +24,8 @@ import { IHandleMovedOptions } from '../UIElements/DirectoryViewer/DirectoryStor
import { ViewConnectedFiles } from '../UIElements/DirectoryViewer/ContextMenu/Actions/ConnectedFiles'
import { ToLocalProjectAction } from './Actions/ToLocalProject'
import { ToBridgeFolderProjectAction } from './Actions/ToBridgeFolderProject'
+import { revealInFileExplorer } from '/@/utils/revealInFileExplorer'
+import { pathFromHandle } from '../FileSystem/Virtual/pathFromHandle'
export class PackExplorer extends SidebarContent {
component = markRaw(PackExplorerComponent)
@@ -114,7 +116,10 @@ export class PackExplorer extends SidebarContent {
)
}
- if (isUsingFileSystemPolyfill.value) {
+ if (
+ !import.meta.env.VITE_IS_TAURI_APP &&
+ isUsingFileSystemPolyfill.value
+ ) {
this.actions.push(
new SidebarAction({
icon: 'mdi-content-save-outline',
@@ -211,7 +216,9 @@ export class PackExplorer extends SidebarContent {
},
},
- isUsingFileSystemPolyfill.value ? null : moveAction,
+ import.meta.env.VITE_IS_TAURI_APP || isUsingFileSystemPolyfill.value
+ ? null
+ : moveAction,
{ type: 'divider' },
// Reload project
{
@@ -286,6 +293,13 @@ export class PackExplorer extends SidebarContent {
icon: 'mdi-folder-open-outline',
name: 'packExplorer.openProjectFolder.name',
onTrigger: async () => {
+ if (import.meta.env.VITE_IS_TAURI_APP) {
+ await revealInFileExplorer(
+ await pathFromHandle(app.project.baseDirectory)
+ )
+ return
+ }
+
app.viewFolders.addDirectoryHandle({
directoryHandle: app.project.baseDirectory,
startPath: app.project.projectPath,
diff --git a/src/components/PackIndexer/PackIndexer.ts b/src/components/PackIndexer/PackIndexer.ts
index 2c04f34de..736d493f9 100644
--- a/src/components/PackIndexer/PackIndexer.ts
+++ b/src/components/PackIndexer/PackIndexer.ts
@@ -8,10 +8,11 @@ import type { Project } from '/@/components/Projects/Project/Project'
import { Mutex } from '../Common/Mutex'
import { Signal } from '../Common/Event/Signal'
import { Task } from '../TaskManager/Task'
+import { setupWorker } from '/@/utils/worker/setup'
-const PackIndexerService = wrap(
- new PackIndexerWorker()
-)
+const worker = new PackIndexerWorker()
+const PackIndexerService = wrap(worker)
+setupWorker(worker)
const taskOptions = {
icon: 'mdi-flash-outline',
diff --git a/src/components/PackIndexer/Worker/LightningCache/LightningCache.ts b/src/components/PackIndexer/Worker/LightningCache/LightningCache.ts
index 25ab8000f..4633c6062 100644
--- a/src/components/PackIndexer/Worker/LightningCache/LightningCache.ts
+++ b/src/components/PackIndexer/Worker/LightningCache/LightningCache.ts
@@ -129,6 +129,7 @@ export class LightningCache {
this.lightningStore.visitedFiles !== this.lightningStore.totalFiles
)
deletedFiles = await this.lightningStore.saveStore()
+
return [filePaths, changedFiles, deletedFiles]
}
diff --git a/src/components/PackIndexer/Worker/LightningCache/LightningStore.ts b/src/components/PackIndexer/Worker/LightningCache/LightningStore.ts
index 5610e498d..d55b19b4a 100644
--- a/src/components/PackIndexer/Worker/LightningCache/LightningStore.ts
+++ b/src/components/PackIndexer/Worker/LightningCache/LightningStore.ts
@@ -111,6 +111,7 @@ export class LightningStore {
else saveStore += '\n'
}
}
+
await this.fs.writeFile('.bridge/.lightningCache', saveStore)
return deletedFiles
diff --git a/src/components/PackIndexer/Worker/Main.ts b/src/components/PackIndexer/Worker/Main.ts
index 4e0d6fe10..3a459d9a1 100644
--- a/src/components/PackIndexer/Worker/Main.ts
+++ b/src/components/PackIndexer/Worker/Main.ts
@@ -1,11 +1,4 @@
-// @ts-ignore Make "path" work on this worker
-globalThis.process = {
- cwd: () => '',
- env: {},
- release: {
- name: 'browser',
- },
-}
+import '/@/utils/worker/inject'
import '/@/components/FileSystem/Virtual/Comlink'
import { FileTypeLibrary, IFileType } from '/@/components/Data/FileType'
diff --git a/src/components/Projects/Export/AsBrproject.ts b/src/components/Projects/Export/AsBrproject.ts
index f6d467c5f..bba8a6c3c 100644
--- a/src/components/Projects/Export/AsBrproject.ts
+++ b/src/components/Projects/Export/AsBrproject.ts
@@ -15,19 +15,15 @@ export async function exportAsBrproject(name?: string) {
* .brproject files come in two variants:
* - Complete global package including the data/ & extensions/ folder for browsers using the file system polyfill
* - Package only including the project files (no data/ & extensions/) for other browsers
+ *
+ * The global package variant is deprecated now because we persist settings and extensions in the browser's local storage
*/
- const zipFolder = new ZipDirectory(
- isUsingFileSystemPolyfill.value
- ? app.fileSystem.baseDirectory
- : app.project.baseDirectory
- )
+ const zipFolder = new ZipDirectory(app.project.baseDirectory)
+
+ const zipFile = await zipFolder.package(new Set(['builds']))
try {
- await saveOrDownload(
- savePath,
- await zipFolder.package(new Set(['builds'])),
- app.fileSystem
- )
+ await saveOrDownload(savePath, zipFile, app.fileSystem)
} catch (err) {
console.error(err)
}
diff --git a/src/components/Projects/Export/AsMcaddon.ts b/src/components/Projects/Export/AsMcaddon.ts
index 6b30e9035..2b8903eb5 100644
--- a/src/components/Projects/Export/AsMcaddon.ts
+++ b/src/components/Projects/Export/AsMcaddon.ts
@@ -75,7 +75,6 @@ export async function exportAsMcaddon() {
}
const service = await app.project.createDashService('production')
- await service.setup()
await service.build()
const zipFolder = new ZipDirectory(
diff --git a/src/components/Projects/Export/AsMctemplate.ts b/src/components/Projects/Export/AsMctemplate.ts
index dce847dde..f25bbec4d 100644
--- a/src/components/Projects/Export/AsMctemplate.ts
+++ b/src/components/Projects/Export/AsMctemplate.ts
@@ -12,7 +12,6 @@ export async function exportAsMctemplate(asMcworld = false) {
app.windows.loadingWindow.open()
const service = await app.project.createDashService('production')
- await service.setup()
await service.build()
let baseWorlds: string[] = []
diff --git a/src/components/Projects/Project/Project.ts b/src/components/Projects/Project/Project.ts
index 118548098..7e8cfe979 100644
--- a/src/components/Projects/Project/Project.ts
+++ b/src/components/Projects/Project/Project.ts
@@ -82,12 +82,12 @@ export abstract class Project {
return this.config.get().name ?? this.name
}
get tabSystem() {
- if (this.tabSystems[0].isActive) return this.tabSystems[0]
- if (this.tabSystems[1].isActive) return this.tabSystems[1]
+ if (this.tabSystems[0].isActive.value) return this.tabSystems[0]
+ if (this.tabSystems[1].isActive.value) return this.tabSystems[1]
}
get inactiveTabSystem() {
- if (!this.tabSystems[0].isActive) return this.tabSystems[0]
- if (!this.tabSystems[1].isActive) return this.tabSystems[1]
+ if (!this.tabSystems[0].isActive.value) return this.tabSystems[0]
+ if (!this.tabSystems[1].isActive.value) return this.tabSystems[1]
}
get baseDirectory() {
return this._baseDirectory
@@ -111,7 +111,7 @@ export abstract class Project {
/**
* Only update compilation results if the watch mode setting is active,
* the current project is not a virtual project
- * ...and the filesystem polyfill is not active
+ * ...and the filesystem polyfill is not active (it's also inactive if we're on a Tauri build)
*
* Explanation:
* Devices that need the filesystem polyfill will not be able to export
@@ -121,7 +121,8 @@ export abstract class Project {
return (
(settingsState.compiler?.watchModeActive ?? true) &&
!this.isVirtualProject &&
- !isUsingFileSystemPolyfill.value
+ (!isUsingFileSystemPolyfill.value ||
+ import.meta.env.VITE_IS_TAURI_APP)
)
}
get projectPath() {
@@ -191,7 +192,9 @@ export abstract class Project {
) {
if (!this.isVirtualProject) await this.app.comMojang.fired
- const compiler = await new DashCompiler(
+ const compiler = await new DashCompiler()
+
+ await compiler.setup(
this.app.fileSystem.baseDirectory,
this.app.comMojang.hasComMojang
? this.app.comMojang.fileSystem.baseDirectory
@@ -253,10 +256,7 @@ export abstract class Project {
// Data needs to be loaded into IndexedDB before the PackIndexer can be used
await this.app.dataLoader.fired
- await Promise.all([
- this.packIndexer.activate(isReload),
- this.compilerService.setup(),
- ])
+ await this.packIndexer.activate(isReload)
const [changedFiles, deletedFiles] = await this.packIndexer.fired
// Only recompile changed files if the setting is active and the project is not a virtual project
@@ -581,7 +581,6 @@ export abstract class Project {
this._compilerService = markRaw(
await this.createDashService('development')
)
- await this._compilerService.setup()
if (forceStartIfActive && this.isActiveProject) {
await this.fileSystem.writeFile('.bridge/.restartWatchMode', '')
diff --git a/src/components/Projects/ProjectManager.ts b/src/components/Projects/ProjectManager.ts
index 7b0bdc590..b5a1ecad3 100644
--- a/src/components/Projects/ProjectManager.ts
+++ b/src/components/Projects/ProjectManager.ts
@@ -26,7 +26,8 @@ export class ProjectManager extends Signal {
constructor(protected app: App) {
super()
- this.loadProjects()
+ // Only load local projects on PWA builds
+ if (!import.meta.env.VITE_IS_TAURI_APP) this.loadProjects()
}
get currentProject() {
@@ -50,7 +51,8 @@ export class ProjectManager extends Signal {
async addProject(
projectDir: AnyDirectoryHandle,
isNewProject = true,
- requiresPermissions = this.app.bridgeFolderSetup.hasFired
+ requiresPermissions = this.app.bridgeFolderSetup.hasFired,
+ select = true
) {
const project = new BedrockProject(this, this.app, projectDir, {
requiresPermissions,
@@ -60,7 +62,7 @@ export class ProjectManager extends Signal {
set(this.state, project.name, project)
if (isNewProject) {
- await this.selectProject(project.name)
+ if (select) await this.selectProject(project.name)
this.addedProject.dispatch(project)
}
@@ -108,7 +110,8 @@ export class ProjectManager extends Signal {
promises.push(this.addProject(handle, false, requiresPermissions))
}
- if (isBridgeFolderSetup) {
+ // Only load local projects separately on PWA builds and when the bridge folder was setup
+ if (!import.meta.env.VITE_IS_TAURI_APP && isBridgeFolderSetup) {
const localDirectoryHandle =
await this.app.fileSystem.getDirectoryHandle(
'~local/projects',
@@ -130,7 +133,7 @@ export class ProjectManager extends Signal {
// Update stored projects in the background (don't await it)
if (isBridgeFolderSetup) this.storeProjects(undefined, true)
// Create a placeholder project (virtual project)
- else await this.createVirtualProject()
+ await this.createVirtualProject()
this.dispatch()
}
@@ -217,7 +220,18 @@ export class ProjectManager extends Signal {
}
async selectLastProject() {
await this.fired
- if (isUsingFileSystemPolyfill.value) {
+ /**
+ * We can select the last selected project if the user is...
+ *
+ * 1. Not using a Tauri native build
+ * 2. Using our file system polyfill
+ *
+ * This is because this variant of bridge. only supports loading one project at once
+ */
+ if (
+ !import.meta.env.VITE_IS_TAURI_APP &&
+ isUsingFileSystemPolyfill.value
+ ) {
const selectedProject = await idbGet('selectedProject')
let didSelectProject = false
if (typeof selectedProject === 'string')
diff --git a/src/components/Sidebar/Content/Main.vue b/src/components/Sidebar/Content/Main.vue
index 84317ae5f..83e7c3c12 100644
--- a/src/components/Sidebar/Content/Main.vue
+++ b/src/components/Sidebar/Content/Main.vue
@@ -11,7 +11,7 @@
class="mb-2"
:style="{ height: content.headerHeight, overflow: 'auto' }"
>
-
+
+
onFocus(event)}
+ onBlur={() => setIsFocused(false)}
+ >
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/Solid/DirectoryViewer/DirectoryView.tsx b/src/components/Solid/DirectoryViewer/DirectoryView.tsx
new file mode 100644
index 000000000..9b97d6881
--- /dev/null
+++ b/src/components/Solid/DirectoryViewer/DirectoryView.tsx
@@ -0,0 +1,52 @@
+import { Dynamic, Show, Switch, Match, For } from 'solid-js/web'
+import { toSignal } from '../toSignal'
+import { toVue } from '../toVue'
+import { Name } from './Common/Name'
+import { DirectoryWrapper } from '/@/components/UIElements/DirectoryViewer/DirectoryView/DirectoryWrapper'
+
+interface DirectoryViewProps {
+ hideDirectoryName?: boolean
+ directoryWrapper: DirectoryWrapper
+}
+
+export function DirectoryView(props: DirectoryViewProps) {
+ const [children] = toSignal(props.directoryWrapper.children)
+ const [open] = toSignal(props.directoryWrapper.isOpen)
+
+ return (
+
+
+
+
+
+
+
+ {(child) => (
+
+ Unknown directory entry kind: "{child.kind}"
+
+ }
+ >
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ )
+}
+
+export const VDirectoryView = toVue(DirectoryView)
diff --git a/src/components/Solid/Icon/IconText.tsx b/src/components/Solid/Icon/IconText.tsx
new file mode 100644
index 000000000..4847c7a12
--- /dev/null
+++ b/src/components/Solid/Icon/IconText.tsx
@@ -0,0 +1,34 @@
+import { Component } from 'solid-js'
+import { useRipple } from '../Directives/Ripple/Ripple'
+import { SolidIcon } from './SolidIcon'
+
+interface IconTextProps {
+ icon: string
+ text: string
+ opacity?: number
+ color?: string
+}
+
+/**
+ * A component that displays text with an icon next to it
+ */
+export const IconText: Component = (props) => {
+ const ripple = useRipple()
+ const styles = {
+ 'white-space': 'nowrap',
+ overflow: 'hidden',
+ 'text-overflow': 'ellipsis',
+ width: '100%',
+ } as const
+
+ return (
+
+
+ {props.text}
+
+ )
+}
diff --git a/src/components/Solid/Icon/SolidIcon.tsx b/src/components/Solid/Icon/SolidIcon.tsx
new file mode 100644
index 000000000..869d646f9
--- /dev/null
+++ b/src/components/Solid/Icon/SolidIcon.tsx
@@ -0,0 +1,56 @@
+import { Component } from 'solid-js'
+import { toVue } from '../toVue'
+
+interface SolidIconProps {
+ icon: string
+ class?: string
+ size?: 'small' | 'medium' | 'large' | number
+ offsetY?: number
+ color?: string
+ opacity?: number
+ onClick?: () => unknown
+ children?: string // Compat with Vuetify's v-icon which allows passing icon id as slot content
+}
+
+/**
+ * A solid component which renders a mdi icon
+ */
+export const SolidIcon: Component = (props) => {
+ const icon = () => props.children?.trim() ?? props.icon
+ const iconSize = () => {
+ switch (props.size) {
+ case 'small':
+ return '1rem'
+ case 'medium':
+ return '1.4rem'
+ case 'large':
+ return '2rem'
+ default:
+ return props.size ? `${props.size}rem` : '1.4rem'
+ }
+ }
+
+ const classList = () => ({
+ [`mdi ${icon().startsWith('mdi-') ? icon() : `mdi-${icon()}`}`]: true,
+ 'cursor-pointer': !!props.onClick,
+ [`${props.color}--text`]: !!props.color,
+ })
+
+ return (
+
+ )
+}
+
+export const VIcon = toVue(SolidIcon)
diff --git a/src/components/Solid/Inputs/Button/SolidButton.tsx b/src/components/Solid/Inputs/Button/SolidButton.tsx
new file mode 100644
index 000000000..d9f27de2c
--- /dev/null
+++ b/src/components/Solid/Inputs/Button/SolidButton.tsx
@@ -0,0 +1,11 @@
+import { JSX } from 'solid-js/types'
+
+interface SolidButtonProps {
+ children: JSX.Element[] | JSX.Element
+ color?: string
+ onClick: () => void
+}
+
+export function SolidButton(props: SolidButtonProps) {
+ return {props.children}
+}
diff --git a/src/components/Solid/Inputs/IconButton/IconButton.css b/src/components/Solid/Inputs/IconButton/IconButton.css
new file mode 100644
index 000000000..113e834ed
--- /dev/null
+++ b/src/components/Solid/Inputs/IconButton/IconButton.css
@@ -0,0 +1,19 @@
+.solid-icon-button {
+ width: 2em;
+ height: 2em;
+
+ transition: transform, opacity 0.2s ease-in-out;
+}
+
+.solid-icon-button-disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+/* Hover state */
+.solid-icon-button:hover {
+ transform: scale(1.1);
+}
+.solid-icon-button-disabled:hover {
+ transform: scale(1);
+}
diff --git a/src/components/Solid/Inputs/IconButton/IconButton.tsx b/src/components/Solid/Inputs/IconButton/IconButton.tsx
new file mode 100644
index 000000000..ab2b75f30
--- /dev/null
+++ b/src/components/Solid/Inputs/IconButton/IconButton.tsx
@@ -0,0 +1,26 @@
+import { Component } from 'solid-js'
+import { useRipple } from '../../Directives/Ripple/Ripple'
+import { SolidIcon } from '../../Icon/SolidIcon'
+import './IconButton.css'
+
+interface IconButtonProps {
+ icon: string
+ disabled?: boolean
+ size?: 'small' | 'medium' | 'large' | number
+ onClick: () => void
+}
+
+export const SolidIconButton: Component = (props) => {
+ const ripple = useRipple()
+
+ return (
+
+
+
+ )
+}
diff --git a/src/components/Solid/Inputs/TextField/TextField.css b/src/components/Solid/Inputs/TextField/TextField.css
new file mode 100644
index 000000000..963f9556d
--- /dev/null
+++ b/src/components/Solid/Inputs/TextField/TextField.css
@@ -0,0 +1,29 @@
+.solid-text-field {
+ display: flex;
+ align-items: center;
+ border: 1px solid #bbb;
+ border-radius: 8px;
+ width: auto;
+ caret-color: var(--v-primary-base);
+
+ transition: border, color 0.1s ease-in-out;
+}
+
+.solid-text-field:focus-within {
+ border: 1px solid var(--v-primary-base);
+ /* Box shadow to increase border-radius */
+ box-shadow: 0 0 0 1px var(--v-primary-base);
+ color: var(--v-primary-base);
+}
+
+.solid-text-field input {
+ width: 100%;
+ padding: 8px;
+ border-radius: 8px;
+ outline: none;
+ margin: 0;
+}
+
+.solid-text-field-disabled {
+ opacity: 0.5;
+}
diff --git a/src/components/Solid/Inputs/TextField/TextField.tsx b/src/components/Solid/Inputs/TextField/TextField.tsx
new file mode 100644
index 000000000..a1557d943
--- /dev/null
+++ b/src/components/Solid/Inputs/TextField/TextField.tsx
@@ -0,0 +1,48 @@
+import { Accessor, Component, Setter, Show } from 'solid-js'
+import { useModel } from '../../Directives/Model/Model'
+import { SolidIcon } from '../../Icon/SolidIcon'
+import './TextField.css'
+
+interface TextFieldProps {
+ class?: string
+ classList?: Record
+ disabled?: boolean
+ model?: [Accessor, Setter]
+ prependIcon?: string
+ placeholder?: string
+
+ onEnter?: (input: string) => void
+}
+
+export const TextField: Component = (props) => {
+ const model = useModel()
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.key === 'Enter') {
+ props.onEnter?.((event.target as HTMLInputElement).value)
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/Solid/SolidSpacer.tsx b/src/components/Solid/SolidSpacer.tsx
new file mode 100644
index 000000000..5a20d09ed
--- /dev/null
+++ b/src/components/Solid/SolidSpacer.tsx
@@ -0,0 +1,5 @@
+import { Component } from 'solid-js'
+
+export const SolidSpacer: Component = () => {
+ return
+}
diff --git a/src/components/Solid/toSignal.ts b/src/components/Solid/toSignal.ts
new file mode 100644
index 000000000..3c7d9422e
--- /dev/null
+++ b/src/components/Solid/toSignal.ts
@@ -0,0 +1,31 @@
+import { Ref, watch, watchEffect } from 'vue'
+import { createSignal, onCleanup } from 'solid-js'
+
+export function toSignal(ref: Ref) {
+ const [signal, setSignal] = createSignal(ref.value, { equals: false })
+
+ let userControlledTrigger = false
+ const dispose = watchEffect(() => {
+ if (userControlledTrigger) {
+ userControlledTrigger = false
+ // We need to access the value to keep the dep tracker alive
+ const _ = ref.value
+ return
+ }
+
+ setSignal(() => ref.value)
+ })
+
+ onCleanup(() => {
+ dispose()
+ })
+
+ return [
+ signal,
+ (val: T) => {
+ userControlledTrigger = true
+ ref.value = val
+ return val
+ },
+ ] as const
+}
diff --git a/src/components/Solid/toVue.ts b/src/components/Solid/toVue.ts
new file mode 100644
index 000000000..8ccf4c34f
--- /dev/null
+++ b/src/components/Solid/toVue.ts
@@ -0,0 +1,53 @@
+import { Component } from 'solid-js'
+import { Component as VueComponent, ref, onMounted, onBeforeUnmount } from 'vue'
+import { render } from 'solid-js/web'
+import { SetupContext } from 'vue'
+
+/**
+ * An utility function to embed a Solid component into a Vue component.
+ */
+export function toVue(component: Component): VueComponent {
+ const vueWrapper: VueComponent = {
+ inheritAttrs: false,
+ template: `
`,
+ setup(_: any, { slots, attrs }: SetupContext) {
+ const mountRef = ref(null)
+
+ // Hacky solution for getting compat with Vuetify's icons
+ // Tries to unwrap default slot into normal string
+ let childrenText: string | null = null
+ if (typeof slots.default === 'function') {
+ const [vnode] = slots.default()
+ if (vnode.text) childrenText = vnode.text
+ }
+ if (childrenText) attrs.children = childrenText
+
+ let dispose: (() => void) | null = null
+ onMounted(() => {
+ if (!mountRef.value) return
+
+ dispose = render(
+ () => component(attrs as unknown as T),
+ mountRef.value
+ )
+ })
+
+ onBeforeUnmount(() => {
+ if (!dispose) return
+
+ dispose()
+ dispose = null
+ })
+
+ return {
+ mountRef,
+ }
+ },
+ }
+
+ if (import.meta.env.DEV) {
+ vueWrapper.name = component.name.replace('_Hot$$', '')
+ }
+
+ return vueWrapper
+}
diff --git a/src/components/StartParams/Action/openFileUrl.ts b/src/components/StartParams/Action/openFileUrl.ts
index 977848a2e..4959fe460 100644
--- a/src/components/StartParams/Action/openFileUrl.ts
+++ b/src/components/StartParams/Action/openFileUrl.ts
@@ -14,8 +14,7 @@ export const openFileUrl: IStartAction = {
const file = new VirtualFileHandle(
null,
basename(value),
- new Uint8Array(await resp.arrayBuffer()),
- true
+ new Uint8Array(await resp.arrayBuffer())
)
const app = await App.getApp()
diff --git a/src/components/StartParams/Action/openRawFile.ts b/src/components/StartParams/Action/openRawFile.ts
index 5f346eca1..2aefaa787 100644
--- a/src/components/StartParams/Action/openRawFile.ts
+++ b/src/components/StartParams/Action/openRawFile.ts
@@ -20,8 +20,7 @@ export const openRawFileAction: IStartAction = {
const file = new VirtualFileHandle(
null,
fileName,
- textEncoder.encode(fileData),
- true
+ textEncoder.encode(fileData)
)
const app = await App.getApp()
await app.projectManager.projectReady.fired
@@ -29,33 +28,37 @@ export const openRawFileAction: IStartAction = {
},
}
+/**
+ * Share a file with other users
+ * @param file A file handle representing the file to share
+ */
export async function shareFile(file: AnyFileHandle) {
const fileContent = await file.getFile().then((file) => file.text())
- if (typeof navigator.share === 'function') {
- const url = new URL(window.location.href)
-
- if (!App.sidebar.isContentVisible.value)
- url.searchParams.set('setSidebarState', 'hidden')
-
- url.searchParams.set(
- 'openRawFile',
- btoa(
- strFromU8(
- zlibSync(strToU8(`${file.name}\n${fileContent}`), {
- level: 9,
- }),
- true
- )
+ if (typeof navigator.share !== 'function') return
+
+ const url = new URL(window.location.href)
+
+ if (!App.sidebar.isContentVisible.value)
+ url.searchParams.set('setSidebarState', 'hidden')
+
+ url.searchParams.set(
+ 'openRawFile',
+ btoa(
+ strFromU8(
+ zlibSync(strToU8(`${file.name}\n${fileContent}`), {
+ level: 9,
+ }),
+ true
)
)
+ )
- await navigator
- .share({
- title: `View File: ${file.name}`,
- text: `Edit the file "${file.name}" file with bridge.!`,
- url: url.href,
- })
- .catch(() => {})
- }
+ await navigator
+ .share({
+ title: `View File: ${file.name}`,
+ text: `Edit the file "${file.name}" file with bridge.!`,
+ url: url.href,
+ })
+ .catch(() => {})
}
diff --git a/src/components/TabSystem/TabBar.vue b/src/components/TabSystem/TabBar.vue
index fe0717c03..d7829ae3d 100644
--- a/src/components/TabSystem/TabBar.vue
+++ b/src/components/TabSystem/TabBar.vue
@@ -1,5 +1,12 @@
-
+
@@ -25,7 +32,7 @@
tabSystem.selectedTab &&
tabSystem.selectedTab.actions.length > 0
"
- :class="{ 'inactive-action-bar': !tabSystem.isActive }"
+ :class="{ 'inactive-action-bar': !tabSystem.isActive.value }"
:actions="tabSystem.selectedTab.actions"
@click="tabSystem.setActive(true)"
/>
@@ -39,6 +46,7 @@ import Draggable from 'vuedraggable'
import { pointerDevice } from '/@/utils/pointerDevice'
import { useTabSystem } from '../Composables/UseTabSystem'
import { toRefs } from 'vue'
+import { useSidebarState } from '../Composables/Sidebar/useSidebarState'
export default {
components: {
@@ -53,10 +61,16 @@ export default {
const { id } = toRefs(props)
const { tabSystem } = useTabSystem(id)
+ const {
+ isContentVisible: isSidebarContentVisible,
+ isAttachedRight: isSidebarRight,
+ } = useSidebarState()
return {
tabSystem,
pointerDevice,
+ isSidebarContentVisible,
+ isSidebarRight,
}
},
methods: {
diff --git a/src/components/TabSystem/TabSystem.ts b/src/components/TabSystem/TabSystem.ts
index d4e6f262c..1bd847572 100644
--- a/src/components/TabSystem/TabSystem.ts
+++ b/src/components/TabSystem/TabSystem.ts
@@ -26,7 +26,7 @@ export class TabSystem extends MonacoHolder {
protected get tabTypes() {
return TabProvider.tabs
}
- protected _isActive = true
+ protected _isActive = ref(true)
public readonly openedFiles: OpenedFiles
get isActive() {
@@ -220,7 +220,7 @@ export class TabSystem extends MonacoHolder {
}
async select(tab?: Tab) {
- if (this.isActive !== !!tab) this.setActive(!!tab)
+ if (this.isActive.value !== !!tab) this.setActive(!!tab)
if (this.app.mobile.isCurrentDevice()) App.sidebar.hide()
if (tab?.isSelected) return
@@ -307,9 +307,9 @@ export class TabSystem extends MonacoHolder {
setActive(isActive: boolean, updateProject = true) {
if (updateProject) this.project.setActiveTabSystem(this, !isActive)
- if (isActive === this._isActive) return
+ if (isActive === this._isActive.value) return
- this._isActive = isActive
+ this._isActive.value = isActive
if (isActive && this._selectedTab && this.project.isActiveProject) {
App.eventSystem.dispatch('currentTabSwitched', this._selectedTab)
diff --git a/src/components/TabSystem/TabSystem.vue b/src/components/TabSystem/TabSystem.vue
index 341f7e698..a4098dbf8 100644
--- a/src/components/TabSystem/TabSystem.vue
+++ b/src/components/TabSystem/TabSystem.vue
@@ -13,8 +13,8 @@
:is="tabSystem.currentComponent"
:key="`${tabSystem.uuid}.${
tabSystem.currentComponent.name ||
- tabSystem.selectedTab.type ||
- tabSystem.selectedTab.name
+ (tabSystem.selectedTab || {}).type ||
+ (tabSystem.selectedTab || {}).name
}`"
:style="`height: ${tabHeight}px; width: 100%;`"
:height="tabHeight"
@@ -74,18 +74,17 @@ export default {
},
computed: {
tabBarHeight() {
- return this.tabSystem.selectedTab &&
- this.tabSystem.selectedTab.actions.length > 0
- ? 48 + 25
- : 48
+ if (!this.tabSystem?.selectedTab) return 0
+ return this.tabSystem?.selectedTab.actions.length > 0 ? 48 + 25 : 48
},
tabHeight() {
return (
(this.windowHeight - this.appToolbarHeightNumber) /
- (this.tabSystem.isSharingScreen.value && this.isMobile
+ (this.tabSystem?.isSharingScreen.value && this.isMobile
? 2
: 1) -
- this.tabBarHeight
+ this.tabBarHeight -
+ App.bottomPanel.currentHeight.value
)
},
isMobile() {
diff --git a/src/components/TabSystem/WelcomeScreen.vue b/src/components/TabSystem/WelcomeScreen.vue
index ed9a764f9..70b439b76 100644
--- a/src/components/TabSystem/WelcomeScreen.vue
+++ b/src/components/TabSystem/WelcomeScreen.vue
@@ -1,7 +1,8 @@
@@ -20,22 +21,15 @@
-