Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
138275d752
|
||
|
|
1f035882f7
|
||
|
|
68cd3c891d
|
||
|
|
65bd6cfe82
|
||
|
|
d91314112d
|
||
|
|
da27bcb32c
|
||
|
|
6c8dfd1200
|
||
|
|
e09d125980
|
||
|
|
35dd842576
|
||
|
|
55714c8edb
|
||
|
|
04d1f2822b
|
||
|
|
dfd99ab12e
|
||
|
|
837c4a8863
|
||
|
|
c69060a360
|
||
|
|
f2b0358605
|
||
|
|
57441a8872
|
||
|
|
df522122ac
|
||
|
|
f4e16c12e5
|
||
|
|
e2d98307c0
|
||
|
|
387bd54ce2
|
||
|
|
e7a61872b7
|
||
|
|
ac66c78dbf
|
||
|
|
d9302319fa
|
||
|
|
5206a7f25d
|
||
|
|
3ee371998a
|
||
|
|
5733c21fbb
|
||
|
|
61b9d07fc6
|
||
|
|
59cd625238
|
||
|
|
6bf2733cec
|
||
|
|
60f9f95be4
|
||
|
|
ea1c05d2d9
|
||
|
|
49c03e82b1
|
||
|
|
0c52169533
|
||
|
|
bf6da20fd8
|
||
|
|
c1f87580c7
|
||
|
|
dd7d2b96e3
|
||
|
|
874f0627f7
|
||
|
|
e8dec1a1b2
|
||
|
|
e1f0ce1290
|
||
|
|
bc5ea81170
|
||
|
|
71b998f4bc
|
||
|
|
cbac50753f
|
||
|
|
fb65348ba3
|
||
|
|
f23d1025d2
|
||
|
|
a8573c4b6a
|
||
|
|
4054fdd06b
|
||
|
|
0b4e639630
|
||
|
|
c65fb5e1a5
|
||
|
|
6486498c74
|
||
|
|
cf72d143f3
|
||
|
|
ee7c50c6f5
|
||
|
|
f7317c8232
|
||
|
|
9856cf2426
|
No files matched your search
@@ -8,7 +8,7 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "7.2.0",
|
"@fortawesome/fontawesome-svg-core": "7.2.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "7.2.0",
|
"@fortawesome/free-brands-svg-icons": "7.2.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "7.2.0",
|
"@fortawesome/free-solid-svg-icons": "7.2.0",
|
||||||
"@fortawesome/react-fontawesome": "3.2.0",
|
"@fortawesome/react-fontawesome": "3.3.0",
|
||||||
"@tauri-apps/api": "2.10.1",
|
"@tauri-apps/api": "2.10.1",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
|
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
|
||||||
"@tauri-apps/plugin-dialog": "2.6.0",
|
"@tauri-apps/plugin-dialog": "2.6.0",
|
||||||
@@ -17,25 +17,28 @@
|
|||||||
"@tauri-apps/plugin-notification": "2.3.3",
|
"@tauri-apps/plugin-notification": "2.3.3",
|
||||||
"@tauri-apps/plugin-opener": "2.5.3",
|
"@tauri-apps/plugin-opener": "2.5.3",
|
||||||
"@tauri-apps/plugin-os": "2.3.2",
|
"@tauri-apps/plugin-os": "2.3.2",
|
||||||
|
"@tauri-apps/plugin-store": "2.4.2",
|
||||||
|
"@tauri-apps/plugin-updater": "2.10.0",
|
||||||
"@tauri-apps/plugin-window-state": "2.4.1",
|
"@tauri-apps/plugin-window-state": "2.4.1",
|
||||||
"next": "16.1.6",
|
"dompurify": "3.3.3",
|
||||||
|
"next": "16.2.2",
|
||||||
"pretty-bytes": "7.1.0",
|
"pretty-bytes": "7.1.0",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "3.3.5",
|
"@eslint/eslintrc": "3.3.5",
|
||||||
"@tailwindcss/postcss": "4.2.1",
|
"@tailwindcss/postcss": "4.2.2",
|
||||||
"@tauri-apps/cli": "2.10.1",
|
"@tauri-apps/cli": "2.10.1",
|
||||||
"@types/crypto-js": "4.2.2",
|
"@types/crypto-js": "4.2.2",
|
||||||
"@types/node": "25.3.5",
|
"@types/node": "25.5.2",
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "19.2.14",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
"crypto-js": "4.2.0",
|
"crypto-js": "4.2.0",
|
||||||
"eslint": "9.39.4",
|
"eslint": "9.39.4",
|
||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.2.2",
|
||||||
"tailwindcss": "4.2.1",
|
"tailwindcss": "4.2.2",
|
||||||
"typescript": "5.9.3",
|
"typescript": "6.0.2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -64,9 +67,9 @@
|
|||||||
|
|
||||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||||
|
|
||||||
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||||
|
|
||||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||||
|
|
||||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||||
|
|
||||||
@@ -74,11 +77,11 @@
|
|||||||
|
|
||||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||||
|
|
||||||
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
"@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||||
|
|
||||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||||
|
|
||||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||||
|
|
||||||
@@ -106,7 +109,7 @@
|
|||||||
|
|
||||||
"@fortawesome/free-solid-svg-icons": ["@fortawesome/free-solid-svg-icons@7.2.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.2.0" } }, "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w=="],
|
"@fortawesome/free-solid-svg-icons": ["@fortawesome/free-solid-svg-icons@7.2.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.2.0" } }, "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w=="],
|
||||||
|
|
||||||
"@fortawesome/react-fontawesome": ["@fortawesome/react-fontawesome@3.2.0", "", { "peerDependencies": { "@fortawesome/fontawesome-svg-core": "~6 || ~7", "react": "^18.0.0 || ^19.0.0" } }, "sha512-E9Gu1hqd6JussVO26EC4WqRZssXMnQr2ol7ZNWkkFOH8jZUaxDJ9Z9WF9wIVkC+kJGXUdY3tlffpDwEKfgQrQw=="],
|
"@fortawesome/react-fontawesome": ["@fortawesome/react-fontawesome@3.3.0", "", { "peerDependencies": { "@fortawesome/fontawesome-svg-core": "~6 || ~7", "react": "^18.0.0 || ^19.0.0" } }, "sha512-EHmHeTf8WgO29sdY3iX/7ekE3gNUdlc2RW6mm/FzELlHFKfTrA9S4MlyquRR+RRCRCn8+jXfLFpLGB2l7wCWyw=="],
|
||||||
|
|
||||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||||
|
|
||||||
@@ -178,25 +181,25 @@
|
|||||||
|
|
||||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||||
|
|
||||||
"@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="],
|
"@next/env": ["@next/env@16.2.2", "", {}, "sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ=="],
|
||||||
|
|
||||||
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.1.6", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ=="],
|
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.2", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-IOPbWzDQ+76AtjZioaCjpIY72xNSDMnarZ2GMQ4wjNLvnJEJHqxQwGFhgnIWLV9klb4g/+amg88Tk5OXVpyLTw=="],
|
||||||
|
|
||||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="],
|
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg=="],
|
||||||
|
|
||||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ=="],
|
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw=="],
|
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ=="],
|
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ=="],
|
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg=="],
|
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg=="],
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw=="],
|
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g=="],
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A=="],
|
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
@@ -210,35 +213,35 @@
|
|||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="],
|
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="],
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="],
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="],
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="],
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="],
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="],
|
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="],
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="],
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="],
|
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="],
|
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="],
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="],
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
|
||||||
|
|
||||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.1", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "postcss": "^8.5.6", "tailwindcss": "4.2.1" } }, "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw=="],
|
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="],
|
||||||
|
|
||||||
"@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
"@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
||||||
|
|
||||||
@@ -280,6 +283,10 @@
|
|||||||
|
|
||||||
"@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="],
|
"@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="],
|
||||||
|
|
||||||
|
"@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-0ClHS50Oq9HEvLPhNzTNFxbWVOqoAp3dRvtewQBeqfIQ0z5m3JRnOISIn2ZVPCrQC0MyGyhTS9DWhHjpigQE7A=="],
|
||||||
|
|
||||||
|
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="],
|
"@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="],
|
||||||
|
|
||||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
@@ -292,31 +299,33 @@
|
|||||||
|
|
||||||
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
|
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
|
"@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||||
|
|
||||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="],
|
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="],
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="],
|
||||||
|
|
||||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.1", "@typescript-eslint/types": "^8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ=="],
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="],
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1" } }, "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w=="],
|
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="],
|
||||||
|
|
||||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ=="],
|
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg=="],
|
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="],
|
||||||
|
|
||||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.1", "@typescript-eslint/tsconfig-utils": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg=="],
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA=="],
|
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="],
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="],
|
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="],
|
||||||
|
|
||||||
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
|
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
|
||||||
|
|
||||||
@@ -390,19 +399,19 @@
|
|||||||
|
|
||||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||||
|
|
||||||
"axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="],
|
"axe-core": ["axe-core@4.11.2", "", {}, "sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw=="],
|
||||||
|
|
||||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
|
|
||||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA=="],
|
||||||
|
|
||||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
"brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
|
||||||
|
|
||||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||||
|
|
||||||
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||||
|
|
||||||
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||||
|
|
||||||
@@ -412,7 +421,7 @@
|
|||||||
|
|
||||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="],
|
"caniuse-lite": ["caniuse-lite@1.0.30001784", "", {}, "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw=="],
|
||||||
|
|
||||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
@@ -452,13 +461,15 @@
|
|||||||
|
|
||||||
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||||
|
|
||||||
|
"dompurify": ["dompurify@3.3.3", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA=="],
|
||||||
|
|
||||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||||
|
|
||||||
"electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="],
|
"electron-to-chromium": ["electron-to-chromium@1.5.331", "", {}, "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q=="],
|
||||||
|
|
||||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||||
|
|
||||||
"enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
|
"enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
|
||||||
|
|
||||||
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
||||||
|
|
||||||
@@ -466,7 +477,7 @@
|
|||||||
|
|
||||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||||
|
|
||||||
"es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="],
|
"es-iterator-helpers": ["es-iterator-helpers@1.3.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" } }, "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ=="],
|
||||||
|
|
||||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||||
|
|
||||||
@@ -482,9 +493,9 @@
|
|||||||
|
|
||||||
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
||||||
|
|
||||||
"eslint-config-next": ["eslint-config-next@16.1.6", "", { "dependencies": { "@next/eslint-plugin-next": "16.1.6", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA=="],
|
"eslint-config-next": ["eslint-config-next@16.2.2", "", { "dependencies": { "@next/eslint-plugin-next": "16.2.2", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-6VlvEhwoug2JpVgjZDhyXrJXUEuPY++TddzIpTaIRvlvlXXFgvQUtm3+Zr84IjFm0lXtJt73w19JA08tOaZVwg=="],
|
||||||
|
|
||||||
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
|
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.10", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.16.1", "resolve": "^2.0.0-next.6" } }, "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ=="],
|
||||||
|
|
||||||
"eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="],
|
"eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="],
|
||||||
|
|
||||||
@@ -532,7 +543,7 @@
|
|||||||
|
|
||||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||||
|
|
||||||
"flatted": ["flatted@3.3.4", "", {}, "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA=="],
|
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||||
|
|
||||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||||
|
|
||||||
@@ -552,7 +563,7 @@
|
|||||||
|
|
||||||
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
|
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
|
||||||
|
|
||||||
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
|
"get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="],
|
||||||
|
|
||||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||||
|
|
||||||
@@ -674,29 +685,29 @@
|
|||||||
|
|
||||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||||
|
|
||||||
"lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
|
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||||
|
|
||||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
|
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||||
|
|
||||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
|
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||||
|
|
||||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
|
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||||
|
|
||||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
|
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||||
|
|
||||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
|
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||||
|
|
||||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
|
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||||
|
|
||||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
|
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||||
|
|
||||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
|
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||||
|
|
||||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
|
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||||
|
|
||||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
|
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||||
|
|
||||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||||
|
|
||||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
|
|
||||||
@@ -726,11 +737,11 @@
|
|||||||
|
|
||||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||||
|
|
||||||
"next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="],
|
"next": ["next@16.2.2", "", { "dependencies": { "@next/env": "16.2.2", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.2", "@next/swc-darwin-x64": "16.2.2", "@next/swc-linux-arm64-gnu": "16.2.2", "@next/swc-linux-arm64-musl": "16.2.2", "@next/swc-linux-x64-gnu": "16.2.2", "@next/swc-linux-x64-musl": "16.2.2", "@next/swc-win32-arm64-msvc": "16.2.2", "@next/swc-win32-x64-msvc": "16.2.2", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A=="],
|
||||||
|
|
||||||
"node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="],
|
"node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="],
|
||||||
|
|
||||||
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
|
"node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
|
||||||
|
|
||||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||||
|
|
||||||
@@ -766,7 +777,7 @@
|
|||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||||
|
|
||||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||||
|
|
||||||
@@ -792,7 +803,7 @@
|
|||||||
|
|
||||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||||
|
|
||||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
"resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
|
||||||
|
|
||||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||||
|
|
||||||
@@ -860,15 +871,15 @@
|
|||||||
|
|
||||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
|
"tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
|
||||||
|
|
||||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||||
|
|
||||||
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
|
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||||
|
|
||||||
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
|
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
|
||||||
|
|
||||||
@@ -884,9 +895,9 @@
|
|||||||
|
|
||||||
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
|
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="],
|
||||||
|
|
||||||
"typescript-eslint": ["typescript-eslint@8.56.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ=="],
|
"typescript-eslint": ["typescript-eslint@8.58.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA=="],
|
||||||
|
|
||||||
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
|
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
|
||||||
|
|
||||||
@@ -922,13 +933,13 @@
|
|||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
@@ -936,7 +947,7 @@
|
|||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||||
|
|
||||||
@@ -950,19 +961,17 @@
|
|||||||
|
|
||||||
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||||
|
|
||||||
"eslint-plugin-react/resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
|
|
||||||
|
|
||||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
"is-bun-module/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
"is-bun-module/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||||
|
|
||||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||||
|
|
||||||
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||||
|
|
||||||
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-8
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "lncvrt-games-launcher",
|
"name": "lncvrt-games-launcher",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.6.5",
|
"version": "1.9.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "7.2.0",
|
"@fortawesome/fontawesome-svg-core": "7.2.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "7.2.0",
|
"@fortawesome/free-brands-svg-icons": "7.2.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "7.2.0",
|
"@fortawesome/free-solid-svg-icons": "7.2.0",
|
||||||
"@fortawesome/react-fontawesome": "3.2.0",
|
"@fortawesome/react-fontawesome": "3.3.0",
|
||||||
"@tauri-apps/api": "2.10.1",
|
"@tauri-apps/api": "2.10.1",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
|
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
|
||||||
"@tauri-apps/plugin-dialog": "2.6.0",
|
"@tauri-apps/plugin-dialog": "2.6.0",
|
||||||
@@ -23,24 +23,27 @@
|
|||||||
"@tauri-apps/plugin-notification": "2.3.3",
|
"@tauri-apps/plugin-notification": "2.3.3",
|
||||||
"@tauri-apps/plugin-opener": "2.5.3",
|
"@tauri-apps/plugin-opener": "2.5.3",
|
||||||
"@tauri-apps/plugin-os": "2.3.2",
|
"@tauri-apps/plugin-os": "2.3.2",
|
||||||
|
"@tauri-apps/plugin-store": "2.4.2",
|
||||||
|
"@tauri-apps/plugin-updater": "2.10.0",
|
||||||
"@tauri-apps/plugin-window-state": "2.4.1",
|
"@tauri-apps/plugin-window-state": "2.4.1",
|
||||||
"next": "16.1.6",
|
"dompurify": "3.3.3",
|
||||||
|
"next": "16.2.2",
|
||||||
"pretty-bytes": "7.1.0",
|
"pretty-bytes": "7.1.0",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4"
|
"react-dom": "19.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "3.3.5",
|
"@eslint/eslintrc": "3.3.5",
|
||||||
"@tailwindcss/postcss": "4.2.1",
|
"@tailwindcss/postcss": "4.2.2",
|
||||||
"@tauri-apps/cli": "2.10.1",
|
"@tauri-apps/cli": "2.10.1",
|
||||||
"@types/crypto-js": "4.2.2",
|
"@types/crypto-js": "4.2.2",
|
||||||
"@types/node": "25.3.5",
|
"@types/node": "25.5.2",
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "19.2.14",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
"crypto-js": "4.2.0",
|
"crypto-js": "4.2.0",
|
||||||
"eslint": "9.39.4",
|
"eslint": "9.39.4",
|
||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.2.2",
|
||||||
"tailwindcss": "4.2.1",
|
"tailwindcss": "4.2.2",
|
||||||
"typescript": "5.9.3"
|
"typescript": "6.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+19
-9
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lncvrt-games-launcher"
|
name = "lncvrt-games-launcher"
|
||||||
version = "1.6.5"
|
version = "1.9.0"
|
||||||
authors = ["Lncvrt"]
|
authors = ["Lncvrt"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
@@ -18,27 +18,37 @@ serde = { version = "1.0.228", features = ["derive"] }
|
|||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
tauri-plugin-os = "2.3.2"
|
tauri-plugin-os = "2.3.2"
|
||||||
reqwest = { version = "0.13.2", default-features = false, features = ["stream", "rustls"] }
|
reqwest = { version = "0.13.2", default-features = false, features = ["stream", "rustls"] }
|
||||||
tokio = "1.50.0"
|
tokio = "1.51.0"
|
||||||
futures-util = { version = "0.3.32", features = ["io"] }
|
futures-util = { version = "0.3.32", features = ["io"] }
|
||||||
tauri-plugin-decorum = "1.1.1"
|
tauri-plugin-decorum = "1.1.1"
|
||||||
tauri-plugin-fs = "2.4.5"
|
tauri-plugin-fs = "2.4.5"
|
||||||
zip = "8.2.0"
|
zip = "8.5.0"
|
||||||
libc = "0.2.182"
|
|
||||||
tauri-plugin-dialog = "2.6.0"
|
tauri-plugin-dialog = "2.6.0"
|
||||||
tauri-plugin-notification = "2.3.3"
|
tauri-plugin-notification = "2.3.3"
|
||||||
sysinfo = "0.38.3"
|
sha2 = "0.11.0"
|
||||||
sha2 = "0.10.9"
|
|
||||||
tauri-plugin-clipboard-manager = "2.3.2"
|
tauri-plugin-clipboard-manager = "2.3.2"
|
||||||
tauri-plugin-prevent-default = "4.0.4"
|
tauri-plugin-prevent-default = "4.0.4"
|
||||||
openssl = { version = "0.10.75", features = ["v110", "vendored"] }
|
openssl = { version = "0.10.76", features = ["v110", "vendored"] }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
tauri-plugin-http = "2.5.7"
|
tauri-plugin-http = "2.5.7"
|
||||||
|
dashmap = "6.1.0"
|
||||||
|
tauri-plugin-store = "2.4.2"
|
||||||
|
hex = "0.4.3"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
|
||||||
shlex = "1.3.0"
|
sysinfo = "0.38.4"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-single-instance = "2.4.0"
|
tauri-plugin-single-instance = "2.4.0"
|
||||||
|
tauri-plugin-updater = "2.10.0"
|
||||||
tauri-plugin-window-state = "2.4.1"
|
tauri-plugin-window-state = "2.4.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
tauri-plugin-decorum = "1.1.1"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
lto = "fat"
|
||||||
|
codegen-units = 1
|
||||||
|
strip = true
|
||||||
|
panic = "abort"
|
||||||
@@ -2,9 +2,7 @@
|
|||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "Capability for the main window",
|
"description": "Capability for the main window",
|
||||||
"windows": [
|
"windows": ["main"],
|
||||||
"main"
|
|
||||||
],
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
@@ -20,26 +18,24 @@
|
|||||||
"core:window:allow-start-dragging",
|
"core:window:allow-start-dragging",
|
||||||
"core:window:allow-toggle-maximize",
|
"core:window:allow-toggle-maximize",
|
||||||
"core:window:allow-request-user-attention",
|
"core:window:allow-request-user-attention",
|
||||||
"core:window:allow-set-progress-bar",
|
|
||||||
"decorum:allow-show-snap-overlay",
|
"decorum:allow-show-snap-overlay",
|
||||||
"fs:default",
|
"fs:default",
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
"notification:default",
|
"notification:default",
|
||||||
"clipboard-manager:default",
|
"clipboard-manager:default",
|
||||||
"clipboard-manager:allow-write-text",
|
"clipboard-manager:allow-write-text",
|
||||||
|
"store:default",
|
||||||
|
"store:allow-load",
|
||||||
|
"store:allow-save",
|
||||||
"fs:allow-applocaldata-read",
|
"fs:allow-applocaldata-read",
|
||||||
"fs:allow-applocaldata-write",
|
"fs:allow-applocaldata-write",
|
||||||
{
|
{
|
||||||
"identifier": "fs:scope",
|
"identifier": "fs:scope",
|
||||||
"allow": [
|
"allow": [{ "path": "$APPLOCALDATA/game/**" }, { "path": "$APPCONFIG/versions.json" }]
|
||||||
{ "path": "$APPLOCALDATA/game/**" }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifier": "opener:allow-open-path",
|
"identifier": "opener:allow-open-path",
|
||||||
"allow": [
|
"allow": [{ "path": "$APPLOCALDATA/game/**" }]
|
||||||
{ "path": "$APPLOCALDATA/game/**" }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifier": "http:default",
|
"identifier": "http:default",
|
||||||
@@ -52,7 +48,8 @@
|
|||||||
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/berry" },
|
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/berry" },
|
||||||
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/coin" },
|
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/coin" },
|
||||||
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/legacy" },
|
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/legacy" },
|
||||||
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/total" }
|
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/total" },
|
||||||
|
{ "url": "https://games.lncvrt.xyz/api/launcher/mods" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"main"
|
"main"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"window-state:default"
|
"window-state:default",
|
||||||
|
"updater:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+294
-168
@@ -1,10 +1,12 @@
|
|||||||
use base64::{Engine as _, engine::general_purpose};
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
use dashmap::DashMap;
|
||||||
use futures_util::stream::StreamExt;
|
use futures_util::stream::StreamExt;
|
||||||
use openssl::pkey::PKey;
|
use openssl::pkey::PKey;
|
||||||
use openssl::sign::Verifier;
|
use openssl::sign::Verifier;
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::OnceLock;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{
|
use std::{
|
||||||
fs::{File, create_dir_all},
|
fs::{File, create_dir_all},
|
||||||
@@ -13,21 +15,35 @@ use std::{
|
|||||||
process::Command,
|
process::Command,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use sysinfo::System;
|
use tauri::window::{ProgressBarState, ProgressBarStatus};
|
||||||
use tauri::{AppHandle, Emitter, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
use tauri_plugin_os::platform;
|
use tauri_plugin_os::platform;
|
||||||
use tauri_plugin_prevent_default::Flags;
|
use tauri_plugin_prevent_default::Flags;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::{io::AsyncWriteExt, time::timeout};
|
use tokio::{io::AsyncWriteExt, time::timeout};
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
use zip::result::ZipError;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
use tauri_plugin_decorum::WebviewWindowExt;
|
use sysinfo::System;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
#[allow(unused)]
|
#[cfg(not(debug_assertions))]
|
||||||
|
use tauri_plugin_updater::UpdaterExt;
|
||||||
|
|
||||||
|
static CANCEL_MAP: OnceLock<DashMap<String, bool>> = OnceLock::new();
|
||||||
|
|
||||||
|
fn cancel_map() -> &'static DashMap<String, bool> {
|
||||||
|
CANCEL_MAP.get_or_init(DashMap::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_cancelled(name: &str) -> bool {
|
||||||
|
cancel_map().get(name).map(|v| *v).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
fn is_running_by_path(path: &Path) -> bool {
|
fn is_running_by_path(path: &Path) -> bool {
|
||||||
let sys = System::new_all();
|
let sys = System::new_all();
|
||||||
let target = match path.canonicalize() {
|
let target = match path.canonicalize() {
|
||||||
@@ -48,20 +64,25 @@ fn should_skip(name: &str) -> bool {
|
|||||||
|| name.ends_with(".DS_Store")
|
|| name.ends_with(".DS_Store")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unzip_to_dir(zip_path: PathBuf, out_dir: PathBuf) -> String {
|
async fn unzip_to_dir(app: AppHandle, zip_path: PathBuf, out_dir: PathBuf, name: String) -> String {
|
||||||
|
let _ = app.emit("unzip-start", format!("{}", name));
|
||||||
let res = tauri::async_runtime::spawn_blocking(move || {
|
let res = tauri::async_runtime::spawn_blocking(move || {
|
||||||
let file = File::open(zip_path)?;
|
let file = File::open(zip_path)?;
|
||||||
let mut archive = ZipArchive::new(BufReader::new(file))?;
|
let mut archive = ZipArchive::new(BufReader::new(file))?;
|
||||||
|
let total = archive.len();
|
||||||
|
|
||||||
for i in 0..archive.len() {
|
for i in 0..total {
|
||||||
let mut entry = archive.by_index(i)?;
|
let mut entry = archive.by_index(i)?;
|
||||||
let name = entry.name();
|
let entry_name = entry.name();
|
||||||
|
|
||||||
if should_skip(name) {
|
if should_skip(entry_name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let outpath = out_dir.join(name);
|
let outpath = out_dir.join(entry_name);
|
||||||
|
if !outpath.starts_with(&out_dir) {
|
||||||
|
return Err(ZipError::InvalidArchive("Path traversal detected".into()));
|
||||||
|
}
|
||||||
|
|
||||||
if entry.is_dir() {
|
if entry.is_dir() {
|
||||||
create_dir_all(&outpath)?;
|
create_dir_all(&outpath)?;
|
||||||
@@ -83,6 +104,8 @@ async fn unzip_to_dir(zip_path: PathBuf, out_dir: PathBuf) -> String {
|
|||||||
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?;
|
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = app.emit("unzip-progress", format!("{}:{}:{}", name, i + 1, total));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<(), zip::result::ZipError>(())
|
Ok::<(), zip::result::ZipError>(())
|
||||||
@@ -125,126 +148,135 @@ fn folder_size(app: AppHandle, version: String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn download(
|
async fn download(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
url: String,
|
url: String,
|
||||||
name: String,
|
name: String,
|
||||||
executable: String,
|
|
||||||
hash: String,
|
hash: String,
|
||||||
|
download_type: i8,
|
||||||
|
mod_id: String,
|
||||||
) -> String {
|
) -> String {
|
||||||
println!("[download] Starting download for '{}' from '{}'", name, url);
|
let _ = app.emit("download-start", format!("{}", name));
|
||||||
|
let window = app.get_webview_window("main").expect("main window missing");
|
||||||
let client = reqwest::Client::new();
|
|
||||||
let resp = match client.get(&url).send().await {
|
|
||||||
Ok(r) => {
|
|
||||||
println!("[download] HTTP request succeeded, status: {}", r.status());
|
|
||||||
r
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("[download] ERROR: HTTP request failed: {:?}", e);
|
|
||||||
return "-1".to_string();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let total_size = resp.content_length().unwrap_or(0);
|
|
||||||
println!(
|
|
||||||
"[download] Content-Length: {} bytes ({:.2} MB)",
|
|
||||||
total_size,
|
|
||||||
total_size as f64 / 1_048_576.0
|
|
||||||
);
|
|
||||||
let _ = app.emit("download-size", format!("{}:{}", &name, &total_size));
|
|
||||||
|
|
||||||
let mut downloaded: u64 = 0;
|
|
||||||
let mut stream = resp.bytes_stream();
|
|
||||||
|
|
||||||
let downloads_path = match app.path().app_local_data_dir() {
|
let downloads_path = match app.path().app_local_data_dir() {
|
||||||
Ok(p) => {
|
Ok(p) => p.join("downloads"),
|
||||||
let path = p.join("downloads");
|
Err(_) => {
|
||||||
println!("[download] Downloads path: {:?}", path);
|
let _ = &app.emit("download-stop", format!("{}", name));
|
||||||
path
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!(
|
|
||||||
"[download] ERROR: Failed to resolve app_local_data_dir: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let game_path = match app.path().app_local_data_dir() {
|
let game_path = match app.path().app_local_data_dir() {
|
||||||
Ok(p) => {
|
Ok(p) => p.join("game"),
|
||||||
let path = p.join("game");
|
Err(_) => {
|
||||||
println!("[download] Game path: {:?}", path);
|
let _ = &app.emit("download-stop", format!("{}", name));
|
||||||
path
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("[download] ERROR: Failed to resolve game path: {}", e);
|
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let download_part_path = downloads_path.join(format!("{}.part", name));
|
let download_part_path = downloads_path.join(format!("{}.part", name));
|
||||||
let download_zip_path = downloads_path.join(format!("{}.zip", name));
|
let download_zip_path = downloads_path.join(format!("{}.zip", name));
|
||||||
println!("[download] Part file: {:?}", download_part_path);
|
|
||||||
println!("[download] Zip file: {:?}", download_zip_path);
|
|
||||||
|
|
||||||
if download_part_path.exists() {
|
|
||||||
println!("[download] Stale .part file found, removing...");
|
|
||||||
let _ = tokio::fs::remove_file(&download_part_path).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"[download] Creating downloads dir (if needed): {:?}",
|
|
||||||
downloads_path
|
|
||||||
);
|
|
||||||
let _ = tokio::fs::create_dir_all(&downloads_path).await;
|
let _ = tokio::fs::create_dir_all(&downloads_path).await;
|
||||||
|
|
||||||
if let Ok(true) = tokio::fs::try_exists(&game_path.join(name.clone())).await {
|
let resume_from: u64 = match tokio::fs::metadata(&download_part_path).await {
|
||||||
println!(
|
Ok(meta) => {
|
||||||
"[download] Existing game dir found, removing: {:?}",
|
let size = meta.len();
|
||||||
game_path.join(&name)
|
size
|
||||||
);
|
}
|
||||||
let _ = tokio::fs::remove_dir_all(&game_path.join(name.clone())).await;
|
Err(_) => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut request = client.get(&url);
|
||||||
|
if resume_from > 0 {
|
||||||
|
request = request.header("Range", format!("bytes={}-", resume_from));
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[download] Creating game dir: {:?}", game_path.join(&name));
|
let resp = match request.send().await {
|
||||||
let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await;
|
Ok(r) => r,
|
||||||
|
Err(_) => {
|
||||||
let mut file = match tokio::fs::File::create(download_part_path.clone()).await {
|
let _ = &app.emit("download-stop", format!("{}", name));
|
||||||
Ok(f) => {
|
|
||||||
println!("[download] Created .part file successfully");
|
|
||||||
f
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("[download] ERROR: Failed to create .part file: {}", e);
|
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let status = resp.status();
|
||||||
|
|
||||||
|
let actual_resume_from = if resume_from > 0 && status == reqwest::StatusCode::OK {
|
||||||
|
let _ = tokio::fs::remove_file(&download_part_path).await;
|
||||||
|
0
|
||||||
|
} else if resume_from > 0 && status == reqwest::StatusCode::PARTIAL_CONTENT {
|
||||||
|
resume_from
|
||||||
|
} else if resume_from > 0 {
|
||||||
|
let _ = tokio::fs::remove_file(&download_part_path).await;
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let remaining_size = resp.content_length().unwrap_or(0);
|
||||||
|
let total_size = actual_resume_from + remaining_size;
|
||||||
|
let _ = app.emit("download-size", format!("{}:{}", &name, &total_size));
|
||||||
|
|
||||||
|
let mut file = if actual_resume_from > 0 {
|
||||||
|
match tokio::fs::OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.open(&download_part_path)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => {
|
||||||
|
let _ = &app.emit("download-stop", format!("{}", name));
|
||||||
|
return "-1".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if download_type == 0 {
|
||||||
|
if let Ok(true) = tokio::fs::try_exists(&game_path.join(&name)).await {
|
||||||
|
let _ = tokio::fs::remove_dir_all(&game_path.join(&name)).await;
|
||||||
|
}
|
||||||
|
let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
match tokio::fs::File::create(&download_part_path).await {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => {
|
||||||
|
let _ = &app.emit("download-stop", format!("{}", name));
|
||||||
|
return "-1".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut downloaded: u64 = actual_resume_from;
|
||||||
|
let mut stream = resp.bytes_stream();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut chunk_count: u64 = 0;
|
let mut chunk_count: u64 = 0;
|
||||||
|
|
||||||
while let Ok(Some(chunk_result)) = timeout(Duration::from_secs(5), stream.next()).await {
|
while let Ok(Some(chunk_result)) = timeout(Duration::from_secs(5), stream.next()).await {
|
||||||
|
if is_cancelled(&name) {
|
||||||
|
cancel_map().remove(&name);
|
||||||
|
let _ = window.set_progress_bar(ProgressBarState {
|
||||||
|
status: Some(ProgressBarStatus::None),
|
||||||
|
progress: Some(0),
|
||||||
|
});
|
||||||
|
return "0".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
let chunk = match chunk_result {
|
let chunk = match chunk_result {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(_) => {
|
||||||
println!(
|
let _ = &app.emit("download-stop", format!("{}", name));
|
||||||
"[download] ERROR: Failed to read chunk #{}: {}",
|
|
||||||
chunk_count, e
|
|
||||||
);
|
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
chunk_count += 1;
|
chunk_count += 1;
|
||||||
|
|
||||||
if let Err(e) = file.write_all(&chunk).await {
|
if let Err(_) = file.write_all(&chunk).await {
|
||||||
println!(
|
let _ = &app.emit("download-stop", format!("{}", name));
|
||||||
"[download] ERROR: Failed to write chunk #{} to disk: {}",
|
|
||||||
chunk_count, e
|
|
||||||
);
|
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,24 +288,24 @@ async fn download(
|
|||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let session_bytes = downloaded - actual_resume_from;
|
||||||
let elapsed_secs = start.elapsed().as_secs_f64();
|
let elapsed_secs = start.elapsed().as_secs_f64();
|
||||||
let speed = (downloaded as f64) / elapsed_secs;
|
let speed = if elapsed_secs > 0.0 {
|
||||||
let eta_secs = if total_size > downloaded {
|
session_bytes as f64 / elapsed_secs
|
||||||
((total_size - downloaded) as f64) / speed
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
let eta_secs = if total_size > downloaded && speed > 0.0 {
|
||||||
|
(total_size - downloaded) as f64 / speed
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
|
||||||
if chunk_count % 50 == 0 || downloaded == total_size {
|
if chunk_count % 50 == 0 || downloaded == total_size {
|
||||||
println!(
|
let _ = window.set_progress_bar(ProgressBarState {
|
||||||
"[download] [{:.1}%] {:.2} MB / {:.2} MB | Speed: {:.1} KB/s | ETA: {:.1}s | Chunks: {}",
|
status: Some(ProgressBarStatus::Normal),
|
||||||
progress,
|
progress: Some(progress as u64),
|
||||||
downloaded as f64 / 1_048_576.0,
|
});
|
||||||
total_size as f64 / 1_048_576.0,
|
|
||||||
speed / 1024.0,
|
|
||||||
eta_secs,
|
|
||||||
chunk_count,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = app.emit(
|
let _ = app.emit(
|
||||||
@@ -286,95 +318,90 @@ async fn download(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if total_size > 0 && downloaded < total_size {
|
if total_size > 0 && downloaded < total_size {
|
||||||
println!(
|
let _ = window.set_progress_bar(ProgressBarState {
|
||||||
"[download] ERROR: Download incomplete — got {} of {} bytes ({:.1}% complete)",
|
status: Some(ProgressBarStatus::None),
|
||||||
downloaded,
|
progress: Some(0),
|
||||||
total_size,
|
});
|
||||||
(downloaded as f64 / total_size as f64) * 100.0
|
|
||||||
);
|
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
|
||||||
"[download] Download complete: {} bytes in {:.2}s",
|
|
||||||
downloaded,
|
|
||||||
start.elapsed().as_secs_f64()
|
|
||||||
);
|
|
||||||
println!("[download] Verifying SHA-512 hash...");
|
|
||||||
let _ = app.emit("download-hash-checking", format!("{}", &name));
|
let _ = app.emit("download-hash-checking", format!("{}", &name));
|
||||||
|
|
||||||
let download_hash = {
|
let download_hash = {
|
||||||
let mut file = match tokio::fs::File::open(download_part_path.clone()).await {
|
let mut file = match tokio::fs::File::open(&download_part_path).await {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => {
|
Err(_) => {
|
||||||
println!(
|
|
||||||
"[download] ERROR: Failed to open .part file for hashing: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut hasher = Sha512::new();
|
let mut hasher = Sha512::new();
|
||||||
let mut bytes_hashed: u64 = 0;
|
let mut buffer = [0u8; 8192];
|
||||||
{
|
loop {
|
||||||
let mut buffer = [0; 8192];
|
let bytes_read = match file.read(&mut buffer).await {
|
||||||
loop {
|
Ok(n) => n,
|
||||||
let bytes_read = match file.read(&mut buffer).await {
|
Err(_) => {
|
||||||
Ok(n) => n,
|
return "-1".to_string();
|
||||||
Err(e) => {
|
|
||||||
println!(
|
|
||||||
"[download] ERROR: Failed to read file during hashing: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return "-1".to_string();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if bytes_read == 0 {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
hasher.update(&buffer[..bytes_read]);
|
};
|
||||||
bytes_hashed += bytes_read as u64;
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
hasher.update(&buffer[..bytes_read]);
|
||||||
}
|
}
|
||||||
drop(file);
|
drop(file);
|
||||||
println!("[download] Hashed {} bytes", bytes_hashed);
|
hex::encode(hasher.finalize())
|
||||||
format!("{:x}", hasher.finalize())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("[download] Expected hash: {}", hash);
|
|
||||||
println!("[download] Computed hash: {}", download_hash);
|
|
||||||
|
|
||||||
if hash != download_hash {
|
if hash != download_hash {
|
||||||
println!("[download] ERROR: Hash mismatch! File may be corrupt or tampered with.");
|
let _ = tokio::fs::remove_file(&download_part_path).await;
|
||||||
let _ = tokio::fs::remove_file(download_part_path.clone()).await;
|
let _ = window.set_progress_bar(ProgressBarState {
|
||||||
|
status: Some(ProgressBarStatus::None),
|
||||||
|
progress: Some(0),
|
||||||
|
});
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[download] Hash verified OK");
|
if let Err(_) = tokio::fs::rename(&download_part_path, &download_zip_path).await {
|
||||||
let _ = app.emit("download-finishing", format!("{}", &name));
|
let _ = window.set_progress_bar(ProgressBarState {
|
||||||
|
status: Some(ProgressBarStatus::None),
|
||||||
println!("[download] Renaming .part -> .zip: {:?}", download_zip_path);
|
progress: Some(0),
|
||||||
if let Err(e) = tokio::fs::rename(download_part_path.clone(), download_zip_path.clone()).await {
|
});
|
||||||
println!("[download] ERROR: Failed to rename .part to .zip: {}", e);
|
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[download] Unzipping to: {:?}", game_path.join(&name));
|
let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await;
|
||||||
let unzip_res = unzip_to_dir(download_zip_path.clone(), game_path.join(&name)).await;
|
|
||||||
|
|
||||||
println!("[download] Removing zip file: {:?}", download_zip_path);
|
let unzip_res = unzip_to_dir(
|
||||||
let _ = tokio::fs::remove_file(download_zip_path.clone()).await;
|
app.clone(),
|
||||||
|
download_zip_path.clone(),
|
||||||
|
if download_type == 2 {
|
||||||
|
game_path
|
||||||
|
.join(&name)
|
||||||
|
.join("BepInEx")
|
||||||
|
.join("plugins")
|
||||||
|
.join(&mod_id)
|
||||||
|
} else {
|
||||||
|
game_path.join(&name)
|
||||||
|
},
|
||||||
|
name.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = tokio::fs::remove_file(&download_zip_path).await;
|
||||||
|
|
||||||
if unzip_res == "-1" {
|
if unzip_res == "-1" {
|
||||||
println!("[download] ERROR: Unzip failed for '{}'", name);
|
let _ = window.set_progress_bar(ProgressBarState {
|
||||||
|
status: Some(ProgressBarStatus::None),
|
||||||
|
progress: Some(0),
|
||||||
|
});
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
let _ = window.set_progress_bar(ProgressBarState {
|
||||||
"[download] SUCCESS: '{}' downloaded, verified, and extracted",
|
status: Some(ProgressBarStatus::None),
|
||||||
name
|
progress: Some(0),
|
||||||
);
|
});
|
||||||
return "1".to_string();
|
"1".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
@@ -435,7 +462,12 @@ fn launch_game(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if platform() == "macos" {
|
let bepinex_folder = match app.path().app_local_data_dir() {
|
||||||
|
Ok(p) => p.join("game").join(&name).join("BepInEx"),
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
if platform() == "macos" && !bepinex_folder.exists() {
|
||||||
if let Err(_) = Command::new("open")
|
if let Err(_) = Command::new("open")
|
||||||
.arg(&executable)
|
.arg(&executable)
|
||||||
.current_dir(&game_folder)
|
.current_dir(&game_folder)
|
||||||
@@ -444,8 +476,18 @@ fn launch_game(
|
|||||||
eprintln!("Failed to launch game on macOS");
|
eprintln!("Failed to launch game on macOS");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Err(_) = Command::new(&exe_path).current_dir(&game_folder).spawn() {
|
if (platform() == "macos" || platform() == "linux") && bepinex_folder.exists() {
|
||||||
eprintln!("Failed to launch game");
|
if let Err(_) = Command::new("./run_bepinex.sh")
|
||||||
|
.arg(&executable)
|
||||||
|
.current_dir(&game_folder)
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
eprintln!("Failed to launch game");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Err(_) = Command::new(&exe_path).current_dir(&game_folder).spawn() {
|
||||||
|
eprintln!("Failed to launch game");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,10 +514,17 @@ fn verify_signature(body: String, signature: String, public_key: String) -> bool
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn cancel_download(name: String) {
|
||||||
|
cancel_map().insert(name.clone(), true);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
.plugin(tauri_plugin_http::init())
|
.plugin(tauri_plugin_http::init())
|
||||||
.plugin(
|
.plugin(
|
||||||
tauri_plugin_prevent_default::Builder::new()
|
tauri_plugin_prevent_default::Builder::new()
|
||||||
@@ -510,19 +559,96 @@ pub fn run() {
|
|||||||
download,
|
download,
|
||||||
launch_game,
|
launch_game,
|
||||||
folder_size,
|
folder_size,
|
||||||
verify_signature
|
verify_signature,
|
||||||
|
cancel_download
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
|
let window = app.get_webview_window("main").unwrap();
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
if let Some(main_window) = app.get_webview_window("main") {
|
use tauri_plugin_decorum::WebviewWindowExt;
|
||||||
if let Err(e) = main_window.create_overlay_titlebar() {
|
if let Err(e) = window.create_overlay_titlebar() {
|
||||||
eprintln!("Failed to create overlay titlebar: {:?}", e);
|
eprintln!("Failed to create overlay titlebar: {:?}", e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
let handle = app.handle().clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
update(handle, window.clone()).await.unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
let mut new_url = app.config().build.dev_url.clone().unwrap();
|
||||||
|
new_url.set_path("/main");
|
||||||
|
window.navigate(new_url).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
async fn update(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
window: tauri::WebviewWindow,
|
||||||
|
) -> tauri_plugin_updater::Result<()> {
|
||||||
|
let target = {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
if std::path::Path::new("/etc/debian_version").exists() {
|
||||||
|
"linux-debian"
|
||||||
|
} else if std::path::Path::new("/etc/redhat-release").exists() {
|
||||||
|
"linux-fedora"
|
||||||
|
} else {
|
||||||
|
let mut new_url = window.url().unwrap();
|
||||||
|
new_url.set_path("/update/outdated");
|
||||||
|
window.navigate(new_url).unwrap();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
{
|
||||||
|
tauri_plugin_updater::target().unwrap_or("unknown".to_string())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(update) = app
|
||||||
|
.updater_builder()
|
||||||
|
.target(target)
|
||||||
|
.build()?
|
||||||
|
.check()
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let mut new_url = window.url().unwrap();
|
||||||
|
new_url.set_path("/update/updating");
|
||||||
|
window.navigate(new_url).unwrap();
|
||||||
|
|
||||||
|
let mut downloaded = 0;
|
||||||
|
|
||||||
|
update
|
||||||
|
.download_and_install(
|
||||||
|
|chunk_length, content_length| {
|
||||||
|
downloaded += chunk_length;
|
||||||
|
println!("downloaded {downloaded} from {content_length:?}");
|
||||||
|
},
|
||||||
|
|| {
|
||||||
|
println!("download finished");
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("update installed");
|
||||||
|
app.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_url = window.url().unwrap();
|
||||||
|
new_url.set_path("/main");
|
||||||
|
window.navigate(new_url).unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lncvrt Games Launcher",
|
"productName": "Lncvrt Games Launcher",
|
||||||
"version": "1.6.5",
|
"version": "1.9.0",
|
||||||
"identifier": "xyz.lncvrt.games-launcher",
|
"identifier": "xyz.lncvrt.games-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev --webpack",
|
"beforeDevCommand": "next dev --webpack",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lncvrt Games Launcher",
|
"productName": "Lncvrt Games Launcher",
|
||||||
"version": "1.6.5",
|
"version": "1.9.0",
|
||||||
"identifier": "xyz.lncvrt.games-launcher",
|
"identifier": "xyz.lncvrt.games-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev --webpack",
|
"beforeDevCommand": "next dev --webpack",
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Lncvrt Games Launcher",
|
"title": "Lncvrt Games Launcher",
|
||||||
|
"url": "/update/checking",
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"height": 600,
|
"height": 600,
|
||||||
"minWidth": 875,
|
"minWidth": 875,
|
||||||
@@ -24,6 +25,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": false
|
"active": true,
|
||||||
|
"targets": ["deb", "rpm"],
|
||||||
|
"icon": ["icons/icon.png"],
|
||||||
|
"createUpdaterArtifacts": true
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"updater": {
|
||||||
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERFODI3MTAwQkNBMjE4REEKUldUYUdLSzhBSEdDM3J0N1NvbENsdDQ3c3BOdWlFajErVnlLZitSc2l5WVI0VjQ1ZHNMWjYzaUwK",
|
||||||
|
"endpoints": [
|
||||||
|
"https://games.lncvrt.xyz/api/launcher/update?target={{target}}&arch={{arch}}¤tVersion={{current_version}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lncvrt Games Launcher",
|
"productName": "Lncvrt Games Launcher",
|
||||||
"version": "1.6.5",
|
"version": "1.9.0",
|
||||||
"identifier": "xyz.lncvrt.games-launcher",
|
"identifier": "xyz.lncvrt.games-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev --webpack",
|
"beforeDevCommand": "next dev --webpack",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Lncvrt Games Launcher",
|
"title": "Lncvrt Games Launcher",
|
||||||
|
"url": "/update/checking",
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"height": 600,
|
"height": 600,
|
||||||
"minWidth": 875,
|
"minWidth": 875,
|
||||||
@@ -28,10 +29,19 @@
|
|||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": ["app"],
|
"targets": ["dmg", "app"],
|
||||||
|
"icon": ["icons/icon.icns"],
|
||||||
|
"createUpdaterArtifacts": true,
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"minimumSystemVersion": "13.0"
|
"minimumSystemVersion": "13.0"
|
||||||
},
|
}
|
||||||
"icon": ["icons/icon.icns"]
|
},
|
||||||
|
"plugins": {
|
||||||
|
"updater": {
|
||||||
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERFODI3MTAwQkNBMjE4REEKUldUYUdLSzhBSEdDM3J0N1NvbENsdDQ3c3BOdWlFajErVnlLZitSc2l5WVI0VjQ1ZHNMWjYzaUwK",
|
||||||
|
"endpoints": [
|
||||||
|
"https://games.lncvrt.xyz/api/launcher/update?target={{target}}&arch={{arch}}¤tVersion={{current_version}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lncvrt Games Launcher",
|
"productName": "Lncvrt Games Launcher",
|
||||||
"version": "1.6.5",
|
"version": "1.9.0",
|
||||||
"identifier": "xyz.lncvrt.games-launcher",
|
"identifier": "xyz.lncvrt.games-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "next dev --webpack",
|
"beforeDevCommand": "next dev --webpack",
|
||||||
@@ -14,12 +14,11 @@
|
|||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Lncvrt Games Launcher",
|
"title": "Lncvrt Games Launcher",
|
||||||
|
"url": "/update/checking",
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"height": 632,
|
"height": 632,
|
||||||
"minWidth": 875,
|
"minWidth": 875,
|
||||||
"minHeight": 532,
|
"minHeight": 532,
|
||||||
"titleBarStyle": "Overlay",
|
|
||||||
"hiddenTitle": true,
|
|
||||||
"decorations": false,
|
"decorations": false,
|
||||||
"shadow": true
|
"shadow": true
|
||||||
}
|
}
|
||||||
@@ -29,7 +28,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": false,
|
"active": true,
|
||||||
"icon": ["icons/icon.ico"]
|
"targets": ["msi"],
|
||||||
|
"icon": ["icons/icon.ico"],
|
||||||
|
"createUpdaterArtifacts": true
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"updater": {
|
||||||
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IERFODI3MTAwQkNBMjE4REEKUldUYUdLSzhBSEdDM3J0N1NvbENsdDQ3c3BOdWlFajErVnlLZitSc2l5WVI0VjQ1ZHNMWjYzaUwK",
|
||||||
|
"endpoints": [
|
||||||
|
"https://games.lncvrt.xyz/api/launcher/update?target={{target}}&arch={{arch}}¤tVersion={{current_version}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,667 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
||||||
import Sidebar from '@/componets/Sidebar'
|
|
||||||
import './Globals.css'
|
|
||||||
import { DownloadProgress } from '@/types/DownloadProgress'
|
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
||||||
import { faChevronLeft, faXmark } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import {
|
|
||||||
readNormalConfig,
|
|
||||||
readVersionsConfig,
|
|
||||||
writeVersionsConfig
|
|
||||||
} from '@/lib/BazookaManager'
|
|
||||||
import { VersionsConfig } from '@/types/VersionsConfig'
|
|
||||||
import { NormalConfig } from '@/types/NormalConfig'
|
|
||||||
import { app } from '@tauri-apps/api'
|
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
|
||||||
import { GlobalProvider } from './GlobalProvider'
|
|
||||||
import { Roboto } from 'next/font/google'
|
|
||||||
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
|
|
||||||
import { GameVersion } from '@/types/GameVersion'
|
|
||||||
import { Game } from '@/types/Game'
|
|
||||||
import { listen } from '@tauri-apps/api/event'
|
|
||||||
import { usePathname } from 'next/navigation'
|
|
||||||
import { arch, platform } from '@tauri-apps/plugin-os'
|
|
||||||
import { notifyUser } from '@/lib/Notifications'
|
|
||||||
import {
|
|
||||||
isPermissionGranted,
|
|
||||||
requestPermission
|
|
||||||
} from '@tauri-apps/plugin-notification'
|
|
||||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
|
||||||
|
|
||||||
import VersionsDownloadPopup from '@/componets/popups/VersionsDownload'
|
|
||||||
import GamesDownloadPopup from '@/componets/popups/GamesDownload'
|
|
||||||
import DownloadsPopup from '@/componets/popups/Downloads'
|
|
||||||
import VersionVersionPopup from '@/componets/popups/VersionVersion'
|
|
||||||
import { fetch } from '@tauri-apps/plugin-http'
|
|
||||||
import { verifySignature } from '@/lib/Util'
|
|
||||||
import {
|
|
||||||
getCurrentWindow,
|
|
||||||
ProgressBarStatus,
|
|
||||||
UserAttentionType
|
|
||||||
} from '@tauri-apps/api/window'
|
|
||||||
|
|
||||||
const roboto = Roboto({
|
|
||||||
subsets: ['latin']
|
|
||||||
})
|
|
||||||
|
|
||||||
export default function RootLayout ({
|
|
||||||
children
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [loadingText, setLoadingText] = useState('Loading...')
|
|
||||||
const [outdated, setOutdated] = useState(false)
|
|
||||||
const [version, setVersion] = useState<string | null>(null)
|
|
||||||
const [platformName, setPlatformName] = useState<string | null>(null)
|
|
||||||
|
|
||||||
const [serverVersionList, setServerVersionList] =
|
|
||||||
useState<null | ServerVersionsResponse>(null)
|
|
||||||
const [selectedVersionList, setSelectedVersionList] = useState<string[]>([])
|
|
||||||
|
|
||||||
const [downloadedVersionsConfig, setDownloadedVersionsConfig] =
|
|
||||||
useState<VersionsConfig | null>(null)
|
|
||||||
const [normalConfig, setNormalConfig] = useState<NormalConfig | null>(null)
|
|
||||||
|
|
||||||
const [showPopup, setShowPopup] = useState(false)
|
|
||||||
const [popupMode, setPopupMode] = useState<null | number>(null)
|
|
||||||
const [fadeOut, setFadeOut] = useState(false)
|
|
||||||
|
|
||||||
const [downloadProgress, setDownloadProgress] = useState<DownloadProgress[]>(
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const [downloadQueue, setDownloadQueue] = useState<string[]>([])
|
|
||||||
const [isProcessingQueue, setIsProcessingQueue] = useState<boolean>(false)
|
|
||||||
const [managingVersion, setManagingVersion] = useState<string | null>(null)
|
|
||||||
const [viewingInfoFromDownloads, setViewingInfoFromDownloads] =
|
|
||||||
useState<boolean>(false)
|
|
||||||
const [selectedGame, setSelectedGame] = useState<number | null>(null)
|
|
||||||
|
|
||||||
const [category, setCategory] = useState<number>(-1)
|
|
||||||
|
|
||||||
const pathname = usePathname()
|
|
||||||
const revisionCheck = useRef(false)
|
|
||||||
const previousQueueLength = useRef(0)
|
|
||||||
|
|
||||||
function getSpecialVersionsList (game?: number): GameVersion[] {
|
|
||||||
if (!normalConfig || !serverVersionList) return []
|
|
||||||
|
|
||||||
return serverVersionList.versions
|
|
||||||
.filter(
|
|
||||||
v => !Object.keys(downloadedVersionsConfig?.list ?? []).includes(v.id)
|
|
||||||
)
|
|
||||||
.filter(v => {
|
|
||||||
if (
|
|
||||||
platformName == 'linux' &&
|
|
||||||
v.wine &&
|
|
||||||
!normalConfig.settings.useWineOnUnixWhenNeeded
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (game && v.game != game) return false
|
|
||||||
if (category != -1 && v.category != category) return false
|
|
||||||
if (downloadProgress.length != 0) {
|
|
||||||
return !downloadProgress.some(d => d.version === v.id)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
if (b.game !== a.game) return a.game - b.game
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getVersionInfo = useCallback(
|
|
||||||
(id: string | undefined): GameVersion | undefined => {
|
|
||||||
if (!id) return undefined
|
|
||||||
return serverVersionList?.versions.find(v => v.id === id)
|
|
||||||
},
|
|
||||||
[serverVersionList]
|
|
||||||
)
|
|
||||||
|
|
||||||
const getGameInfo = useCallback(
|
|
||||||
(game: number | undefined): Game | undefined => {
|
|
||||||
if (!game) return undefined
|
|
||||||
return serverVersionList?.games.find(g => g.id === game)
|
|
||||||
},
|
|
||||||
[serverVersionList]
|
|
||||||
)
|
|
||||||
|
|
||||||
function getListOfGames (): Game[] {
|
|
||||||
if (!downloadedVersionsConfig?.list) return []
|
|
||||||
|
|
||||||
const gamesMap = new Map<number, Game>()
|
|
||||||
|
|
||||||
Object.keys(downloadedVersionsConfig.list).forEach(i => {
|
|
||||||
const version = getVersionInfo(i)
|
|
||||||
if (!version) return
|
|
||||||
const game = getGameInfo(version.game)
|
|
||||||
if (!game) return
|
|
||||||
gamesMap.set(game.id, game)
|
|
||||||
})
|
|
||||||
|
|
||||||
return Array.from(gamesMap.values())
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVersionsAmountData (gameId: number): {
|
|
||||||
installed: number
|
|
||||||
total: number
|
|
||||||
} | null {
|
|
||||||
if (!downloadedVersionsConfig || !serverVersionList) return null
|
|
||||||
|
|
||||||
const allowWine =
|
|
||||||
platformName !== 'linux' || normalConfig?.settings.useWineOnUnixWhenNeeded
|
|
||||||
|
|
||||||
const installed = Object.keys(downloadedVersionsConfig.list).filter(v => {
|
|
||||||
const info = getVersionInfo(v)
|
|
||||||
if (!info) return false
|
|
||||||
if (info.wine && !allowWine) return false
|
|
||||||
return getGameInfo(info.game)?.id === gameId
|
|
||||||
}).length
|
|
||||||
|
|
||||||
const total = serverVersionList.versions.filter(v => {
|
|
||||||
if (!v) return false
|
|
||||||
if (v.wine && !allowWine) return false
|
|
||||||
return getGameInfo(v.game)?.id === gameId
|
|
||||||
}).length
|
|
||||||
|
|
||||||
return { installed, total }
|
|
||||||
}
|
|
||||||
|
|
||||||
const closePopup = useCallback(() => {
|
|
||||||
if (popupMode == 0 && selectedGame && pathname === '/') {
|
|
||||||
setSelectedGame(null)
|
|
||||||
setSelectedVersionList([])
|
|
||||||
} else if (viewingInfoFromDownloads) {
|
|
||||||
setViewingInfoFromDownloads(false)
|
|
||||||
setPopupMode(0)
|
|
||||||
} else {
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
}
|
|
||||||
}, [popupMode, selectedGame, pathname, viewingInfoFromDownloads])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let unlistenProgress: (() => void) | null = null
|
|
||||||
|
|
||||||
listen<string>('download-size', event => {
|
|
||||||
const [displayName, sizeStr] = event.payload.split(':')
|
|
||||||
const size = Number(sizeStr)
|
|
||||||
setDownloadProgress(prev => {
|
|
||||||
const i = prev.findIndex(d => d.version === displayName)
|
|
||||||
if (i === -1) return prev
|
|
||||||
const copy = [...prev]
|
|
||||||
copy[i] = {
|
|
||||||
...copy[i],
|
|
||||||
size
|
|
||||||
}
|
|
||||||
return copy
|
|
||||||
})
|
|
||||||
}).then(f => (unlistenProgress = f))
|
|
||||||
|
|
||||||
listen<string>('download-progress', async event => {
|
|
||||||
const [displayName, progStr, totalSizeStr, speedStr, etaSecsStr] =
|
|
||||||
event.payload.split(':')
|
|
||||||
const prog = Number(progStr)
|
|
||||||
const progBytes = Number(totalSizeStr)
|
|
||||||
const speed = Number(speedStr)
|
|
||||||
const etaSecs = Number(etaSecsStr)
|
|
||||||
setDownloadProgress(prev => {
|
|
||||||
const i = prev.findIndex(d => d.version === displayName)
|
|
||||||
if (i === -1) return prev
|
|
||||||
const copy = [...prev]
|
|
||||||
copy[i] = {
|
|
||||||
...copy[i],
|
|
||||||
progress: prog,
|
|
||||||
progressBytes: progBytes,
|
|
||||||
speed,
|
|
||||||
etaSecs
|
|
||||||
}
|
|
||||||
return copy
|
|
||||||
})
|
|
||||||
getCurrentWindow().setProgressBar({
|
|
||||||
status: ProgressBarStatus.Normal,
|
|
||||||
progress: Math.floor(prog)
|
|
||||||
})
|
|
||||||
}).then(f => (unlistenProgress = f))
|
|
||||||
|
|
||||||
listen<string>('download-hash-checking', event => {
|
|
||||||
const displayName = event.payload
|
|
||||||
setDownloadProgress(prev => {
|
|
||||||
const i = prev.findIndex(d => d.version === displayName)
|
|
||||||
if (i === -1) return prev
|
|
||||||
const copy = [...prev]
|
|
||||||
copy[i] = { ...copy[i], hash_checking: true }
|
|
||||||
return copy
|
|
||||||
})
|
|
||||||
}).then(f => (unlistenProgress = f))
|
|
||||||
|
|
||||||
listen<string>('download-finishing', event => {
|
|
||||||
const displayName = event.payload
|
|
||||||
setDownloadProgress(prev => {
|
|
||||||
const i = prev.findIndex(d => d.version === displayName)
|
|
||||||
if (i === -1) return prev
|
|
||||||
const copy = [...prev]
|
|
||||||
copy[i] = { ...copy[i], hash_checking: false, finishing: true }
|
|
||||||
return copy
|
|
||||||
})
|
|
||||||
}).then(f => (unlistenProgress = f))
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unlistenProgress?.()
|
|
||||||
}
|
|
||||||
}, [downloadProgress])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
;(async () => {
|
|
||||||
setPlatformName(platform())
|
|
||||||
const client = await app.getVersion()
|
|
||||||
setVersion(client)
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
'https://games.lncvrt.xyz/api/launcher/latest'
|
|
||||||
)
|
|
||||||
const signature = response.headers.get('x-signature') ?? ''
|
|
||||||
const data = await response.text()
|
|
||||||
if (await verifySignature(data, signature)) {
|
|
||||||
if (data !== client) {
|
|
||||||
setOutdated(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setLoadingText('Failed to check latest version.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setLoadingText('Failed to check latest version.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`https://games.lncvrt.xyz/api/launcher/versions?platform=${platform()}&arch=${arch()}`
|
|
||||||
)
|
|
||||||
const signature = response.headers.get('x-signature') ?? ''
|
|
||||||
const data = await response.json()
|
|
||||||
if (await verifySignature(JSON.stringify(data), signature)) {
|
|
||||||
setServerVersionList(data)
|
|
||||||
} else {
|
|
||||||
setLoadingText('Failed to download versions list.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setLoadingText('Failed to download versions list.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const normalConfig = await readNormalConfig()
|
|
||||||
const versionsConfig = await readVersionsConfig()
|
|
||||||
setDownloadedVersionsConfig(versionsConfig)
|
|
||||||
setNormalConfig(normalConfig)
|
|
||||||
setLoading(false)
|
|
||||||
|
|
||||||
if (!(await isPermissionGranted())) {
|
|
||||||
await requestPermission()
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const downloadVersions = useCallback(
|
|
||||||
async (list: string[]): Promise<void> => {
|
|
||||||
if (list.length === 0) return
|
|
||||||
setSelectedVersionList([])
|
|
||||||
|
|
||||||
const newVersions = list.filter(
|
|
||||||
version =>
|
|
||||||
!downloadQueue.includes(version) &&
|
|
||||||
!downloadProgress.some(d => d.version === version)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (newVersions.length === 0) return
|
|
||||||
|
|
||||||
const newDownloads = newVersions.map(
|
|
||||||
version =>
|
|
||||||
new DownloadProgress(
|
|
||||||
version,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
setDownloadProgress(prev => [...prev, ...newDownloads])
|
|
||||||
setDownloadQueue(prev => [...prev, ...newVersions])
|
|
||||||
},
|
|
||||||
[downloadQueue, downloadProgress]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isProcessingQueue || downloadQueue.length === 0) return
|
|
||||||
|
|
||||||
const processNextDownload = async () => {
|
|
||||||
setIsProcessingQueue(true)
|
|
||||||
|
|
||||||
const versionId = downloadQueue[0]
|
|
||||||
const info = getVersionInfo(versionId)
|
|
||||||
|
|
||||||
if (!info) {
|
|
||||||
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
|
||||||
setDownloadQueue(prev => prev.slice(1))
|
|
||||||
setIsProcessingQueue(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const gameInfo = getGameInfo(info.game)
|
|
||||||
if (!gameInfo) {
|
|
||||||
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
|
||||||
setDownloadQueue(prev => prev.slice(1))
|
|
||||||
setIsProcessingQueue(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setDownloadProgress(prev =>
|
|
||||||
prev.map(d => (d.version === versionId ? { ...d, queued: false } : d))
|
|
||||||
)
|
|
||||||
|
|
||||||
const downloadInfoRequest = await fetch(
|
|
||||||
'https://games.lncvrt.xyz/api/launcher/download?gameId=' +
|
|
||||||
info.id +
|
|
||||||
'&downloadId=' +
|
|
||||||
info.download
|
|
||||||
)
|
|
||||||
const signature = downloadInfoRequest.headers.get('x-signature') ?? ''
|
|
||||||
const data = await downloadInfoRequest.json()
|
|
||||||
if (
|
|
||||||
!(await verifySignature(JSON.stringify(data), signature)) ||
|
|
||||||
!data.success
|
|
||||||
) {
|
|
||||||
setDownloadProgress(prev =>
|
|
||||||
prev.map(d =>
|
|
||||||
d.version === versionId
|
|
||||||
? { ...d, queued: false, failed: true, progress: 0 }
|
|
||||||
: d
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (normalConfig?.settings.allowNotifications)
|
|
||||||
await notifyUser(
|
|
||||||
'Download Failed',
|
|
||||||
`The download for version ${info.displayName} has failed.`
|
|
||||||
)
|
|
||||||
await getCurrentWindow().requestUserAttention(
|
|
||||||
UserAttentionType.Critical
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await invoke<string>('download', {
|
|
||||||
url: data.data.url,
|
|
||||||
name: info.id,
|
|
||||||
executable: info.executable,
|
|
||||||
hash: data.data.hash
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res === '1') {
|
|
||||||
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
|
||||||
setDownloadedVersionsConfig(prev => {
|
|
||||||
if (!prev) return prev
|
|
||||||
|
|
||||||
const updated = {
|
|
||||||
...prev,
|
|
||||||
list: {
|
|
||||||
...prev.list,
|
|
||||||
[versionId]: Date.now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeVersionsConfig(updated)
|
|
||||||
return updated
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
setDownloadProgress(prev =>
|
|
||||||
prev.map(d =>
|
|
||||||
d.version === versionId
|
|
||||||
? { ...d, queued: false, failed: true, progress: 0 }
|
|
||||||
: d
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (normalConfig?.settings.allowNotifications)
|
|
||||||
await notifyUser(
|
|
||||||
'Download Failed',
|
|
||||||
`The download for version ${info.displayName} has failed.`
|
|
||||||
)
|
|
||||||
await getCurrentWindow().requestUserAttention(
|
|
||||||
UserAttentionType.Critical
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setDownloadQueue(prev => prev.slice(1))
|
|
||||||
setIsProcessingQueue(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
processNextDownload()
|
|
||||||
}, [
|
|
||||||
downloadQueue,
|
|
||||||
isProcessingQueue,
|
|
||||||
getVersionInfo,
|
|
||||||
getGameInfo,
|
|
||||||
normalConfig
|
|
||||||
])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
downloadQueue.length === 0 &&
|
|
||||||
downloadProgress.length === 0 &&
|
|
||||||
!isProcessingQueue &&
|
|
||||||
previousQueueLength.current > 0 &&
|
|
||||||
normalConfig?.settings.allowNotifications
|
|
||||||
) {
|
|
||||||
notifyUser('Downloads Finished', 'All downloads have finished.')
|
|
||||||
setTimeout(() => closePopup(), 0)
|
|
||||||
;(async () => {
|
|
||||||
await getCurrentWindow().setProgressBar({
|
|
||||||
status: ProgressBarStatus.None,
|
|
||||||
progress: 0
|
|
||||||
})
|
|
||||||
await getCurrentWindow().requestUserAttention(
|
|
||||||
UserAttentionType.Informational
|
|
||||||
)
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
previousQueueLength.current = downloadQueue.length + downloadProgress.length
|
|
||||||
}, [
|
|
||||||
downloadQueue,
|
|
||||||
downloadProgress,
|
|
||||||
isProcessingQueue,
|
|
||||||
normalConfig,
|
|
||||||
closePopup
|
|
||||||
])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (revisionCheck.current) return
|
|
||||||
if (!serverVersionList || !downloadedVersionsConfig) return
|
|
||||||
revisionCheck.current = true
|
|
||||||
;(async () => {
|
|
||||||
for (const [key, value] of Object.entries(
|
|
||||||
downloadedVersionsConfig.list
|
|
||||||
)) {
|
|
||||||
const verInfo = serverVersionList.versions.find(item => item.id === key)
|
|
||||||
|
|
||||||
if (
|
|
||||||
!verInfo ||
|
|
||||||
(verInfo.lastRevision > 0 && value / 1000 <= verInfo.lastRevision)
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
await exists('game/' + key + '/' + verInfo?.executable, {
|
|
||||||
baseDir: BaseDirectory.AppLocalData
|
|
||||||
})
|
|
||||||
)
|
|
||||||
await remove('game/' + key + '/' + verInfo?.executable, {
|
|
||||||
baseDir: BaseDirectory.AppLocalData,
|
|
||||||
recursive: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}, [serverVersionList, downloadedVersionsConfig, downloadVersions])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<html lang='en' className={roboto.className}>
|
|
||||||
<body
|
|
||||||
className={
|
|
||||||
normalConfig?.settings.theme === 1
|
|
||||||
? 'red-theme'
|
|
||||||
: normalConfig?.settings.theme === 2
|
|
||||||
? 'blue-theme'
|
|
||||||
: normalConfig?.settings.theme === 3
|
|
||||||
? 'purple-theme'
|
|
||||||
: 'dark-theme'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className='relative z-2 w-screen border-b border-b-(--col3) h-8.25 bg-(--col1)'
|
|
||||||
hidden={platformName != 'windows'}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={`w-screen ${
|
|
||||||
platformName == 'windows'
|
|
||||||
? 'h-[calc(100vh-64px)]'
|
|
||||||
: 'h-screen'
|
|
||||||
} flex items-center justify-center`}
|
|
||||||
>
|
|
||||||
{outdated ? (
|
|
||||||
<div className='text-center'>
|
|
||||||
<p className='text-8xl mb-4'>Outdated Launcher!</p>
|
|
||||||
<p className='text-4xl mb-4'>
|
|
||||||
Please update to the latest version to continue.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
className='button'
|
|
||||||
onClick={() =>
|
|
||||||
openUrl('https://games.lncvrt.xyz/berrydash/download')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Download latest version
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className='text-7xl text-center'>{loadingText}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<GlobalProvider
|
|
||||||
value={{
|
|
||||||
serverVersionList,
|
|
||||||
selectedVersionList,
|
|
||||||
setSelectedVersionList,
|
|
||||||
downloadProgress,
|
|
||||||
setDownloadProgress,
|
|
||||||
showPopup,
|
|
||||||
setShowPopup,
|
|
||||||
popupMode,
|
|
||||||
setPopupMode,
|
|
||||||
fadeOut,
|
|
||||||
setFadeOut,
|
|
||||||
downloadedVersionsConfig,
|
|
||||||
setDownloadedVersionsConfig,
|
|
||||||
normalConfig,
|
|
||||||
setNormalConfig,
|
|
||||||
managingVersion,
|
|
||||||
setManagingVersion,
|
|
||||||
getVersionInfo,
|
|
||||||
getGameInfo,
|
|
||||||
getListOfGames,
|
|
||||||
setSelectedGame,
|
|
||||||
getVersionsAmountData,
|
|
||||||
viewingInfoFromDownloads,
|
|
||||||
version,
|
|
||||||
downloadVersions,
|
|
||||||
category,
|
|
||||||
setCategory,
|
|
||||||
downloadQueue,
|
|
||||||
setDownloadQueue,
|
|
||||||
closePopup,
|
|
||||||
getSpecialVersionsList,
|
|
||||||
selectedGame,
|
|
||||||
setViewingInfoFromDownloads
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
tabIndex={0}
|
|
||||||
onKeyDown={e => {
|
|
||||||
if (showPopup && e.key === 'Escape') closePopup()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Sidebar />
|
|
||||||
<div
|
|
||||||
className='relative z-2 ml-59.75 w-[calc(100vw-239px)] border-b border-b-(--col3) h-8.25 bg-(--col1)'
|
|
||||||
hidden={platformName != 'windows'}
|
|
||||||
/>
|
|
||||||
<div className='relative z-0'>
|
|
||||||
<main className='ml-60'>{children}</main>
|
|
||||||
</div>
|
|
||||||
{showPopup && (
|
|
||||||
<div
|
|
||||||
className={`popup-overlay ${fadeOut ? 'fade-out' : ''}`}
|
|
||||||
onClick={(e: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
if (viewingInfoFromDownloads) {
|
|
||||||
setPopupMode(0)
|
|
||||||
setViewingInfoFromDownloads(false)
|
|
||||||
setManagingVersion(null)
|
|
||||||
setSelectedGame(null)
|
|
||||||
}
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className='popup-box'>
|
|
||||||
<button
|
|
||||||
className='close-button btntheme1'
|
|
||||||
onClick={() => closePopup()}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={
|
|
||||||
(popupMode == 0 &&
|
|
||||||
selectedGame &&
|
|
||||||
pathname === '/') ||
|
|
||||||
viewingInfoFromDownloads ||
|
|
||||||
popupMode == 4
|
|
||||||
? faChevronLeft
|
|
||||||
: faXmark
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
{popupMode === 0 && selectedGame ? (
|
|
||||||
<VersionsDownloadPopup />
|
|
||||||
) : popupMode === 0 && !selectedGame ? (
|
|
||||||
<GamesDownloadPopup />
|
|
||||||
) : popupMode === 1 ? (
|
|
||||||
<DownloadsPopup />
|
|
||||||
) : popupMode === 2 ? (
|
|
||||||
<VersionVersionPopup />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</GlobalProvider>
|
|
||||||
)}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
File renamed without changes.
+11
-10
@@ -1,14 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import './styles.css'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { BirdColor } from '@/types/BerryDash/BirdColor'
|
import { BirdColor } from '@/types/BerryDash/BirdColor'
|
||||||
import { GetIconForUser } from '@/lib/BerryDash'
|
import { GetIconForUser } from '@/lib/berrydashutil'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import './styles.css'
|
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import { fetch } from '@tauri-apps/plugin-http'
|
import { fetch } from '@tauri-apps/plugin-http'
|
||||||
import { verifySignature } from '@/lib/Util'
|
import { verifySignature } from '@/lib/util'
|
||||||
|
|
||||||
interface BaseEntry {
|
interface BaseEntry {
|
||||||
id: number
|
id: number
|
||||||
@@ -174,9 +174,10 @@ export default function BerryDashLeaderboards () {
|
|||||||
<button
|
<button
|
||||||
className='button btntheme1'
|
className='button btntheme1'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selected == -1) router.push('/game?id=1')
|
if (selected == -1) router.push('/main/game?id=1')
|
||||||
else {
|
else {
|
||||||
setSelected(-1)
|
setSelected(-1)
|
||||||
|
setSelectedBerryOption(0)
|
||||||
setEntries([])
|
setEntries([])
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -247,8 +248,8 @@ export default function BerryDashLeaderboards () {
|
|||||||
className={`flex flex-col gap-2 overflow-y-auto ${
|
className={`flex flex-col gap-2 overflow-y-auto ${
|
||||||
selected == 1
|
selected == 1
|
||||||
? platform() == 'windows'
|
? platform() == 'windows'
|
||||||
? 'h-[calc(100vh-168px)]'
|
? 'h-[calc(100vh-156px)]'
|
||||||
: 'h-[calc(100vh-136px)]'
|
: 'h-[calc(100vh-124px)]'
|
||||||
: platform() == 'windows'
|
: platform() == 'windows'
|
||||||
? 'h-[calc(100vh-128px)]'
|
? 'h-[calc(100vh-128px)]'
|
||||||
: 'h-[calc(100vh-96px)]'
|
: 'h-[calc(100vh-96px)]'
|
||||||
@@ -297,14 +298,14 @@ export default function BerryDashLeaderboards () {
|
|||||||
: selected === 2
|
: selected === 2
|
||||||
? 'Coins'
|
? 'Coins'
|
||||||
: 'Score'}
|
: 'Score'}
|
||||||
: {item.value.toLocaleString('en-US')}
|
: {item.value.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
) : isAccount ? (
|
) : isAccount ? (
|
||||||
<p>
|
<p>
|
||||||
{selected === 3 ? 'Level' : 'XP'}:{' '}
|
{selected === 3 ? 'Level' : 'XP'}:{' '}
|
||||||
{selected === 3
|
{selected === 3
|
||||||
? calculateLevel(item.xp).toLocaleString('en-US')
|
? calculateLevel(item.xp).toLocaleString()
|
||||||
: item.xp.toLocaleString('en-US')}
|
: item.xp.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -314,9 +315,9 @@ export default function BerryDashLeaderboards () {
|
|||||||
{selected == 1 && (
|
{selected == 1 && (
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
<select
|
<select
|
||||||
|
className='mt-1.25 bg-(--col2) border border-(--col4) rounded-md'
|
||||||
value={selectedBerryOption}
|
value={selectedBerryOption}
|
||||||
onChange={e => setSelectedBerryOption(Number(e.target.value))}
|
onChange={e => setSelectedBerryOption(Number(e.target.value))}
|
||||||
className='leaderboard-select mt-0.75'
|
|
||||||
>
|
>
|
||||||
<option value='0'>Normal Berry</option>
|
<option value='0'>Normal Berry</option>
|
||||||
<option value='1'>Poison Berry</option>
|
<option value='1'>Poison Berry</option>
|
||||||
+1
-2
@@ -4,8 +4,7 @@
|
|||||||
@apply bg-(--col1) border border-(--col3) rounded-lg w-auto p-1;
|
@apply bg-(--col1) border border-(--col3) rounded-lg w-auto p-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaderboard-button,
|
.leaderboard-button {
|
||||||
.leaderboard-select {
|
|
||||||
@apply bg-(--col2) hover:bg-(--col4) border border-(--col4) hover:border-(--col6) rounded-lg px-4 py-2 inline-block transition-all duration-200 cursor-pointer;
|
@apply bg-(--col2) hover:bg-(--col4) border border-(--col4) hover:border-(--col6) rounded-lg px-4 py-2 inline-block transition-all duration-200 cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,36 +1,35 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import '../Installs.css'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import '@/app/Installs.css'
|
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import { useGlobal } from '@/app/GlobalProvider'
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
import { useRouter, useSearchParams } from 'next/navigation'
|
import { useRouter, useSearchParams } from 'next/navigation'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import { faWarning } from '@fortawesome/free-solid-svg-icons'
|
import { faWarning } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { ask } from '@tauri-apps/plugin-dialog'
|
import { ask } from '@tauri-apps/plugin-dialog'
|
||||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||||
import { writeVersionsConfig } from '@/lib/BazookaManager'
|
import { openFolder } from '@/lib/util'
|
||||||
import { openFolder } from '@/lib/Util'
|
|
||||||
|
|
||||||
export default function Installs () {
|
export default function Installs () {
|
||||||
const {
|
const {
|
||||||
|
versionsList,
|
||||||
|
serverVersionList,
|
||||||
showPopup,
|
showPopup,
|
||||||
setShowPopup,
|
setShowPopup,
|
||||||
setPopupMode,
|
setPopupMode,
|
||||||
setFadeOut,
|
setFadeOut,
|
||||||
setSelectedVersionList,
|
setSelectedVersionList,
|
||||||
downloadedVersionsConfig,
|
|
||||||
normalConfig,
|
|
||||||
setManagingVersion,
|
setManagingVersion,
|
||||||
getVersionInfo,
|
|
||||||
getGameInfo,
|
|
||||||
setSelectedGame,
|
setSelectedGame,
|
||||||
serverVersionList,
|
|
||||||
category,
|
category,
|
||||||
setCategory,
|
setCategory,
|
||||||
setDownloadedVersionsConfig,
|
downloadVersions,
|
||||||
downloadVersions
|
downloadProgress,
|
||||||
|
linuxUseWine,
|
||||||
|
linuxWineCommand,
|
||||||
|
versions
|
||||||
} = useGlobal()
|
} = useGlobal()
|
||||||
|
|
||||||
const params = useSearchParams()
|
const params = useSearchParams()
|
||||||
@@ -42,7 +41,7 @@ export default function Installs () {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showPopup) return
|
if (!showPopup) return
|
||||||
setSelectedVersionList([])
|
setSelectedVersionList([])
|
||||||
}, [normalConfig, setSelectedVersionList, showPopup])
|
}, [setSelectedVersionList, showPopup])
|
||||||
|
|
||||||
if (!id || !game) return <p>Invalid game</p>
|
if (!id || !game) return <p>Invalid game</p>
|
||||||
|
|
||||||
@@ -53,10 +52,7 @@ export default function Installs () {
|
|||||||
if (!lastRevision) return false
|
if (!lastRevision) return false
|
||||||
return (
|
return (
|
||||||
lastRevision > 0 &&
|
lastRevision > 0 &&
|
||||||
(downloadedVersionsConfig == undefined
|
(versionsList == undefined ? 0 : versionsList[version]) / 1000 <=
|
||||||
? 0
|
|
||||||
: downloadedVersionsConfig?.list[version]) /
|
|
||||||
1000 <=
|
|
||||||
lastRevision
|
lastRevision
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -79,7 +75,7 @@ export default function Installs () {
|
|||||||
<button
|
<button
|
||||||
className='button btntheme1'
|
className='button btntheme1'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push('/game/berrydash/leaderboards')
|
router.push('/main/game/berrydash/leaderboards')
|
||||||
}}
|
}}
|
||||||
title='View the leaderboards for this game.'
|
title='View the leaderboards for this game.'
|
||||||
hidden={game.id != 1}
|
hidden={game.id != 1}
|
||||||
@@ -120,19 +116,14 @@ export default function Installs () {
|
|||||||
>
|
>
|
||||||
{category == -1 &&
|
{category == -1 &&
|
||||||
Object.entries(game.categoryNames)
|
Object.entries(game.categoryNames)
|
||||||
.sort(([a], [b]) => Number(b) - Number(a))
|
|
||||||
.filter(([key]) => {
|
.filter(([key]) => {
|
||||||
const count = Object.keys(
|
const count = Object.keys(versionsList).filter(v => {
|
||||||
downloadedVersionsConfig?.list ?? {}
|
const info = serverVersionList?.versions.find(
|
||||||
).filter(v => {
|
vf => vf.id == v
|
||||||
const info = getVersionInfo(v)
|
)
|
||||||
if (!info) return false
|
if (!info) return false
|
||||||
|
|
||||||
if (
|
if (platform() == 'linux' && info.wine && !linuxUseWine)
|
||||||
platform() == 'linux' &&
|
|
||||||
info.wine &&
|
|
||||||
!normalConfig?.settings.useWineOnUnixWhenNeeded
|
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return info.game === id && info.category === Number(key)
|
return info.game === id && info.category === Number(key)
|
||||||
@@ -140,6 +131,7 @@ export default function Installs () {
|
|||||||
|
|
||||||
return count >= 1
|
return count >= 1
|
||||||
})
|
})
|
||||||
|
.sort(([a], [b]) => Number(b) - Number(a))
|
||||||
.map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -159,16 +151,15 @@ export default function Installs () {
|
|||||||
<p>
|
<p>
|
||||||
{(() => {
|
{(() => {
|
||||||
const count =
|
const count =
|
||||||
Object.keys(
|
Object.keys(versionsList).filter(v => {
|
||||||
downloadedVersionsConfig?.list ?? []
|
const info = serverVersionList?.versions.find(
|
||||||
).filter(v => {
|
vf => vf.id == v
|
||||||
const info = getVersionInfo(v)
|
)
|
||||||
if (!info) return false
|
if (!info) return false
|
||||||
if (
|
if (
|
||||||
platform() == 'linux' &&
|
platform() == 'linux' &&
|
||||||
info.wine &&
|
info.wine &&
|
||||||
!normalConfig?.settings
|
!linuxUseWine
|
||||||
.useWineOnUnixWhenNeeded
|
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
return (
|
return (
|
||||||
@@ -184,48 +175,40 @@ export default function Installs () {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{Object.keys(downloadedVersionsConfig?.list ?? []).filter(v => {
|
{Object.keys(versionsList)
|
||||||
const info = getVersionInfo(v)
|
.filter(v => {
|
||||||
if (!info) return false
|
const info = serverVersionList?.versions.find(vf => vf.id == v)
|
||||||
return info.game === id
|
if (!info) return false
|
||||||
}).length != 0 ? (
|
if (platform() == 'linux' && info.wine && !linuxUseWine)
|
||||||
Object.keys(downloadedVersionsConfig?.list ?? [])
|
return false
|
||||||
.sort((a, b) => {
|
return (
|
||||||
const infoA = getVersionInfo(a)
|
info.game === id &&
|
||||||
const infoB = getVersionInfo(b)
|
(category == -1
|
||||||
if (!infoA || !infoB) return -1
|
? info.category == -1
|
||||||
return infoB.place - infoA.place
|
: info.category == category)
|
||||||
})
|
)
|
||||||
.filter(v => {
|
})
|
||||||
const info = getVersionInfo(v)
|
.sort((a, b) => {
|
||||||
if (!info) return false
|
const infoA = serverVersionList?.versions.find(vf => vf.id == a)
|
||||||
if (
|
const infoB = serverVersionList?.versions.find(vf => vf.id == b)
|
||||||
platform() == 'linux' &&
|
if (!infoA || !infoB) return 0
|
||||||
info.wine &&
|
return infoB.place - infoA.place
|
||||||
!normalConfig?.settings.useWineOnUnixWhenNeeded
|
})
|
||||||
)
|
.map(v => {
|
||||||
return false
|
const versionInfo = serverVersionList?.versions.find(
|
||||||
return (
|
vf => vf.id == v
|
||||||
info.game === id &&
|
)
|
||||||
(category == -1
|
if (!versionInfo) return
|
||||||
? info.category == -1
|
|
||||||
: info.category == category)
|
return (
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(entry => (
|
|
||||||
<div
|
<div
|
||||||
key={entry}
|
key={v}
|
||||||
className={'downloads-entry'}
|
className={'downloads-entry'}
|
||||||
title={
|
title={
|
||||||
'Click to launch game. Right-click to manage this version install'
|
'Click to launch game. Right-click to manage this version install'
|
||||||
}
|
}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (
|
if (needsRevisionUpdate(versionInfo.lastRevision, v)) {
|
||||||
needsRevisionUpdate(
|
|
||||||
getVersionInfo(entry)?.lastRevision,
|
|
||||||
entry
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const answer = await ask(
|
const answer = await ask(
|
||||||
'Before proceeding, if you do not want your installation directory wiped just yet, please backup the files to another directory. When you click "Yes", it will be wiped. Click "No" if you want to open the installation folder instead.',
|
'Before proceeding, if you do not want your installation directory wiped just yet, please backup the files to another directory. When you click "Yes", it will be wiped. Click "No" if you want to open the installation folder instead.',
|
||||||
{
|
{
|
||||||
@@ -249,78 +232,70 @@ export default function Installs () {
|
|||||||
setFadeOut(false)
|
setFadeOut(false)
|
||||||
|
|
||||||
//uninstall
|
//uninstall
|
||||||
setDownloadedVersionsConfig(prev => {
|
versions?.set(
|
||||||
if (!prev) return prev
|
'list',
|
||||||
const updatedList = Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(prev.list).filter(
|
Object.entries(versionsList).filter(
|
||||||
([k]) => k !== entry
|
([k]) => k !== v
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
const updatedConfig = {
|
)
|
||||||
...prev,
|
|
||||||
list: updatedList
|
|
||||||
}
|
|
||||||
writeVersionsConfig(updatedConfig)
|
|
||||||
return updatedConfig
|
|
||||||
})
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
await exists('game/' + entry, {
|
await exists('game/' + v, {
|
||||||
baseDir: BaseDirectory.AppLocalData
|
baseDir: BaseDirectory.AppLocalData
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
await remove('game/' + entry, {
|
await remove('game/' + v, {
|
||||||
baseDir: BaseDirectory.AppLocalData,
|
baseDir: BaseDirectory.AppLocalData,
|
||||||
recursive: true
|
recursive: true
|
||||||
})
|
})
|
||||||
|
|
||||||
//reinstall
|
//reinstall
|
||||||
setSelectedVersionList([entry])
|
setSelectedVersionList([v])
|
||||||
downloadVersions([entry])
|
downloadVersions([
|
||||||
|
{
|
||||||
|
id: v,
|
||||||
|
type: 0
|
||||||
|
}
|
||||||
|
])
|
||||||
} else {
|
} else {
|
||||||
openFolder(entry)
|
openFolder(v)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const verInfo = getVersionInfo(entry)
|
|
||||||
if (verInfo == undefined) return
|
|
||||||
const gameInfo = getGameInfo(verInfo.game)
|
|
||||||
if (gameInfo == undefined) return
|
|
||||||
invoke('launch_game', {
|
invoke('launch_game', {
|
||||||
name: verInfo.id,
|
name: versionInfo.id,
|
||||||
executable: verInfo.executable,
|
executable: versionInfo.executable,
|
||||||
displayName: verInfo.displayName,
|
displayName: versionInfo.displayName,
|
||||||
useWine: !!(
|
useWine: !!(
|
||||||
platform() == 'linux' &&
|
platform() == 'linux' &&
|
||||||
verInfo.wine &&
|
versionInfo.wine &&
|
||||||
normalConfig?.settings.useWineOnUnixWhenNeeded
|
linuxUseWine
|
||||||
),
|
),
|
||||||
wineCommand: normalConfig?.settings.wineOnUnixCommand
|
wineCommand: linuxWineCommand
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
onContextMenu={e => {
|
onContextMenu={e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setManagingVersion(entry)
|
setManagingVersion(v)
|
||||||
setPopupMode(2)
|
setPopupMode(2)
|
||||||
setShowPopup(true)
|
setShowPopup(true)
|
||||||
setFadeOut(false)
|
setFadeOut(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='h-18 w-screen relative'>
|
<div className='h-18 w-screen relative'>
|
||||||
<p className='text-2xl'>
|
<p className='text-2xl'>{versionInfo.displayName}</p>
|
||||||
{getVersionInfo(entry)?.displayName}{' '}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className='flex gap-2 absolute left-0 bottom-0'>
|
<div className='flex gap-2 absolute left-0 bottom-0'>
|
||||||
<div
|
<div
|
||||||
className='entry-info-item'
|
className='entry-info-item'
|
||||||
title='The date the game was installed.'
|
title='The date the game was installed.'
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Installed{' '}
|
Installed{' '}
|
||||||
{new Intl.DateTimeFormat(undefined).format(
|
{new Intl.DateTimeFormat(undefined).format(
|
||||||
downloadedVersionsConfig?.list[entry]
|
versionsList[v]
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,41 +303,65 @@ export default function Installs () {
|
|||||||
className='entry-info-item'
|
className='entry-info-item'
|
||||||
title='This version is using wine. It cannot be guarenteed to work fully and might not work at all.'
|
title='This version is using wine. It cannot be guarenteed to work fully and might not work at all.'
|
||||||
hidden={
|
hidden={
|
||||||
!(
|
!(platform() == 'linux' && versionInfo.wine) ||
|
||||||
platform() == 'linux' && getVersionInfo(entry)?.wine
|
needsRevisionUpdate(versionInfo.lastRevision, v)
|
||||||
) ||
|
|
||||||
needsRevisionUpdate(
|
|
||||||
getVersionInfo(entry)?.lastRevision,
|
|
||||||
entry
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
|
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
|
||||||
<p>Uses wine</p>
|
<p>Uses wine</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className='entry-info-item'
|
className='entry-info-item'
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
hidden={
|
hidden={
|
||||||
!needsRevisionUpdate(
|
!needsRevisionUpdate(versionInfo.lastRevision, v)
|
||||||
getVersionInfo(entry)?.lastRevision,
|
|
||||||
entry
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
|
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
|
||||||
<p>Needs revision update!</p>
|
<p>Needs revision update!</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
className='absolute right-0 bottom-0 button'
|
||||||
|
title='Click to manage mods for this game!'
|
||||||
|
onClick={async e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await exists('game/' + v + '/BepInEx', {
|
||||||
|
baseDir: BaseDirectory.AppLocalData
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
(await ask(
|
||||||
|
"You don't have BepInEx (the mod loader for Unity Games), would you like to install it now? It is about 1MB in size. If you choose yes, it will download the recommended BepInEx version for " +
|
||||||
|
versionInfo.displayName +
|
||||||
|
'.',
|
||||||
|
{ title: 'BepInEx not found!', kind: 'error' }
|
||||||
|
)) &&
|
||||||
|
!downloadProgress.find(d => d.version == v)
|
||||||
|
) {
|
||||||
|
downloadVersions([
|
||||||
|
{
|
||||||
|
id: v,
|
||||||
|
type: 1
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setManagingVersion(v)
|
||||||
|
setPopupMode(3)
|
||||||
|
setShowPopup(true)
|
||||||
|
setFadeOut(false)
|
||||||
|
}}
|
||||||
|
hidden={!versionInfo.modSupportDownload}
|
||||||
|
>
|
||||||
|
Mod Manager
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
)
|
||||||
) : (
|
})}
|
||||||
<div className='flex justify-center items-center h-full'>
|
|
||||||
<p className='text-3xl'>No versions installed</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,829 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import '@/styles/globals.css'
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import Sidebar from '@/components/Sidebar'
|
||||||
|
import { DownloadProgress } from '@/types/DownloadProgress'
|
||||||
|
import { app } from '@tauri-apps/api'
|
||||||
|
import { GlobalProvider } from '@/providers/GlobalProvider'
|
||||||
|
import { Roboto } from 'next/font/google'
|
||||||
|
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
|
||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
|
import { arch, platform } from '@tauri-apps/plugin-os'
|
||||||
|
import {
|
||||||
|
isPermissionGranted,
|
||||||
|
requestPermission
|
||||||
|
} from '@tauri-apps/plugin-notification'
|
||||||
|
import {
|
||||||
|
BaseDirectory,
|
||||||
|
copyFile,
|
||||||
|
exists,
|
||||||
|
readTextFile,
|
||||||
|
remove
|
||||||
|
} from '@tauri-apps/plugin-fs'
|
||||||
|
import { fetch } from '@tauri-apps/plugin-http'
|
||||||
|
import { verifySignature } from '@/lib/util'
|
||||||
|
import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window'
|
||||||
|
import { Mod } from '@/types/Mod'
|
||||||
|
import { load, Store } from '@tauri-apps/plugin-store'
|
||||||
|
import { notifyUser } from '@/lib/notifications'
|
||||||
|
import { GameVersion } from '@/types/GameVersion'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { faChevronLeft, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
|
import VersionsDownloadPopup from '@/popups/VersionsDownload'
|
||||||
|
import GamesDownloadPopup from '@/popups/GamesDownload'
|
||||||
|
import DownloadsPopup from '@/popups/Downloads'
|
||||||
|
import ManageVersionPopup from '@/popups/ManageVersion'
|
||||||
|
import ModDownloadsPopup from '@/popups/ModDownloads'
|
||||||
|
import VersionChangelogPopup from '@/popups/VersionChangelog'
|
||||||
|
|
||||||
|
const roboto = Roboto({
|
||||||
|
subsets: ['latin']
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function RootLayout ({
|
||||||
|
children
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [loadingText, setLoadingText] = useState('Loading...')
|
||||||
|
const [version, setVersion] = useState<string | null>(null)
|
||||||
|
const [platformName, setPlatformName] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const [serverVersionList, setServerVersionList] =
|
||||||
|
useState<null | ServerVersionsResponse>(null)
|
||||||
|
const [selectedVersionList, setSelectedVersionList] = useState<string[]>([])
|
||||||
|
|
||||||
|
const [showPopup, setShowPopup] = useState(false)
|
||||||
|
const [popupMode, setPopupMode] = useState<null | number>(null)
|
||||||
|
const [fadeOut, setFadeOut] = useState(false)
|
||||||
|
|
||||||
|
const [downloadProgress, setDownloadProgress] = useState<DownloadProgress[]>(
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const [downloadQueue, setDownloadQueue] = useState<string[]>([])
|
||||||
|
const [isProcessingQueue, setIsProcessingQueue] = useState<boolean>(false)
|
||||||
|
const [managingVersion, setManagingVersion] = useState<string | null>(null)
|
||||||
|
const [viewingInfoFromDownloads, setViewingInfoFromDownloads] =
|
||||||
|
useState<boolean>(false)
|
||||||
|
const [selectedGame, setSelectedGame] = useState<number | null>(null)
|
||||||
|
|
||||||
|
const [category, setCategory] = useState<number>(-1)
|
||||||
|
const [showModInfo, setShowModInfo] = useState<Mod | null>(null)
|
||||||
|
|
||||||
|
const pathname = usePathname()
|
||||||
|
const revisionCheck = useRef(false)
|
||||||
|
|
||||||
|
const [settings, setSettings] = useState<Store | null>(null)
|
||||||
|
const [versions, setVersions] = useState<Store | null>(null)
|
||||||
|
|
||||||
|
const [notificationsAllowed, setNotificationsAllowed] =
|
||||||
|
useState<boolean>(false)
|
||||||
|
const [sidebarAlwaysShowGames, setSidebarAlwaysShowGames] =
|
||||||
|
useState<boolean>(false)
|
||||||
|
const [linuxUseWine, setLinuxUseWine] = useState<boolean>(false)
|
||||||
|
const [linuxWineCommand, setLinuxWineCommand] =
|
||||||
|
useState<string>('wine %path%')
|
||||||
|
const [theme, setTheme] = useState<string>('dark')
|
||||||
|
|
||||||
|
const [versionsList, setVersionsList] = useState<Record<string, number>>({})
|
||||||
|
const [modsList, setModsList] = useState<
|
||||||
|
Record<string, Record<string, number>>
|
||||||
|
>({})
|
||||||
|
|
||||||
|
function getSpecialVersionsList (game?: number): GameVersion[] {
|
||||||
|
if (!serverVersionList) return []
|
||||||
|
|
||||||
|
return serverVersionList.versions
|
||||||
|
.filter(v => !Object.keys(versionsList).includes(v.id))
|
||||||
|
.filter(v => {
|
||||||
|
if (platformName == 'linux' && v.wine && linuxUseWine) return false
|
||||||
|
|
||||||
|
if (game && v.game != game) return false
|
||||||
|
if (category != -1 && v.category != category) return false
|
||||||
|
if (downloadProgress.length != 0) {
|
||||||
|
return !downloadProgress.some(d => d.version === v.id)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (b.game !== a.game) return a.game - b.game
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const closePopup = useCallback(() => {
|
||||||
|
if (popupMode == 0 && selectedGame && pathname === '/') {
|
||||||
|
setSelectedGame(null)
|
||||||
|
setSelectedVersionList([])
|
||||||
|
} else if (viewingInfoFromDownloads) {
|
||||||
|
setViewingInfoFromDownloads(false)
|
||||||
|
setPopupMode(0)
|
||||||
|
} else if (popupMode == 3 && showModInfo) {
|
||||||
|
setShowModInfo(null)
|
||||||
|
} else if (popupMode == 4) {
|
||||||
|
setPopupMode(2)
|
||||||
|
} else {
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
}
|
||||||
|
}, [popupMode, selectedGame, pathname, viewingInfoFromDownloads, showModInfo])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisteners: (() => void)[] = []
|
||||||
|
|
||||||
|
const setupListeners = async () => {
|
||||||
|
unlisteners.push(
|
||||||
|
await listen<string>('download-size', event => {
|
||||||
|
const [displayName, sizeStr] = event.payload.split(':')
|
||||||
|
const size = Number(sizeStr)
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === displayName)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = { ...copy[i], size }
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
unlisteners.push(
|
||||||
|
await listen<string>('download-progress', async event => {
|
||||||
|
const [displayName, progStr, totalSizeStr, speedStr, etaSecsStr] =
|
||||||
|
event.payload.split(':')
|
||||||
|
const prog = Number(progStr)
|
||||||
|
const progBytes = Number(totalSizeStr)
|
||||||
|
const speed = Number(speedStr)
|
||||||
|
const etaSecs = Number(etaSecsStr)
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === displayName)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = {
|
||||||
|
...copy[i],
|
||||||
|
progress: prog,
|
||||||
|
progressBytes: progBytes,
|
||||||
|
speed,
|
||||||
|
etaSecs
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
unlisteners.push(
|
||||||
|
await listen<string>('download-hash-checking', event => {
|
||||||
|
const displayName = event.payload
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === displayName)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = { ...copy[i], hash_checking: true, downloading: false }
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
unlisteners.push(
|
||||||
|
await listen<string>('unzip-start', event => {
|
||||||
|
const displayName = event.payload
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === displayName)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = { ...copy[i], hash_checking: false, unzipping: true }
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
unlisteners.push(
|
||||||
|
await listen<string>('unzip-progress', async event => {
|
||||||
|
const [displayName, unzippedStr, unzipTotalStr] =
|
||||||
|
event.payload.split(':')
|
||||||
|
const unzipped = Number(unzippedStr)
|
||||||
|
const unzipTotal = Number(unzipTotalStr)
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === displayName)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = {
|
||||||
|
...copy[i],
|
||||||
|
unzipped,
|
||||||
|
unzipTotal
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
unlisteners.push(
|
||||||
|
await listen<string>('download-start', event => {
|
||||||
|
const displayName = event.payload
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === displayName)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = {
|
||||||
|
...copy[i],
|
||||||
|
downloading: true,
|
||||||
|
paused: false,
|
||||||
|
failed: false
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
unlisteners.push(
|
||||||
|
await listen<string>('download-stop', event => {
|
||||||
|
const displayName = event.payload
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === displayName)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = { ...copy[i], downloading: false }
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setupListeners()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisteners.forEach(unlisten => unlisten())
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
;(async () => {
|
||||||
|
setPlatformName(platform())
|
||||||
|
const client = await app.getVersion()
|
||||||
|
setVersion(client)
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://games.lncvrt.xyz/api/launcher/versions?platform=${platform()}&arch=${arch()}`
|
||||||
|
)
|
||||||
|
const signature = response.headers.get('x-signature') ?? ''
|
||||||
|
const data = await response.json()
|
||||||
|
if (await verifySignature(JSON.stringify(data), signature)) {
|
||||||
|
setServerVersionList(data)
|
||||||
|
} else {
|
||||||
|
setLoadingText('Failed to download versions list.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setLoadingText('Failed to download versions list.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const legacyOptions = {
|
||||||
|
baseDir: BaseDirectory.AppLocalData
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
platform() == 'windows' &&
|
||||||
|
(await exists('versions.json', legacyOptions))
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
await exists('versions.json', {
|
||||||
|
baseDir: BaseDirectory.AppConfig
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await remove('versions.json', {
|
||||||
|
baseDir: BaseDirectory.AppConfig
|
||||||
|
})
|
||||||
|
|
||||||
|
await copyFile('versions.json', 'versions.json', {
|
||||||
|
fromPathBaseDir: BaseDirectory.AppLocalData,
|
||||||
|
toPathBaseDir: BaseDirectory.AppConfig
|
||||||
|
})
|
||||||
|
await remove('versions.json', legacyOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsLocal = await load('settings.json', {
|
||||||
|
autoSave: true,
|
||||||
|
defaults: {
|
||||||
|
version: client,
|
||||||
|
notificationsAllowed: true,
|
||||||
|
sidebarAlwaysShowGames: true,
|
||||||
|
linuxUseWine: false,
|
||||||
|
linuxWineCommand: 'wine %path%',
|
||||||
|
theme: 'dark'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const versionsLocal = await load('versions.json', {
|
||||||
|
autoSave: true,
|
||||||
|
defaults: {
|
||||||
|
version: client,
|
||||||
|
list: {},
|
||||||
|
mods: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
settingsLocal.set('version', client)
|
||||||
|
versionsLocal.set('version', client)
|
||||||
|
if (await exists('config.json', legacyOptions)) {
|
||||||
|
const config = await readTextFile('config.json', legacyOptions)
|
||||||
|
const raw = JSON.parse(config)
|
||||||
|
if (
|
||||||
|
raw.settings &&
|
||||||
|
raw.settings.theme &&
|
||||||
|
(raw.version == '1.0.0' ||
|
||||||
|
raw.version == '1.1.0' ||
|
||||||
|
raw.version == '1.1.1' ||
|
||||||
|
raw.version == '1.2.0' ||
|
||||||
|
raw.version == '1.3.0' ||
|
||||||
|
raw.version == '1.3.1' ||
|
||||||
|
raw.version == '1.4.0' ||
|
||||||
|
raw.version == '1.5.0' ||
|
||||||
|
raw.version == '1.5.1' ||
|
||||||
|
raw.version == '1.5.2' ||
|
||||||
|
raw.version == '1.5.3' ||
|
||||||
|
raw.version == '1.5.4')
|
||||||
|
) {
|
||||||
|
const parsed = Number(raw.settings.theme)
|
||||||
|
if (parsed == 3) raw.settings.theme = 2
|
||||||
|
if (parsed == 4) raw.settings.theme = 3
|
||||||
|
else if (parsed != 0 && parsed != 1) raw.settings.theme = 0
|
||||||
|
}
|
||||||
|
if (raw.settings.allowNotifications)
|
||||||
|
settingsLocal?.set(
|
||||||
|
'notificationsAllowed',
|
||||||
|
Boolean(raw.settings.allowNotifications)
|
||||||
|
)
|
||||||
|
if (raw.settings.alwaysShowGamesInSidebar)
|
||||||
|
settingsLocal?.set(
|
||||||
|
'sidebarAlwaysShowGames',
|
||||||
|
Boolean(raw.settings.alwaysShowGamesInSidebar)
|
||||||
|
)
|
||||||
|
if (raw.settings.useWineOnUnixWhenNeeded)
|
||||||
|
settingsLocal?.set(
|
||||||
|
'linuxUseWine',
|
||||||
|
Boolean(raw.settings.useWineOnUnixWhenNeeded)
|
||||||
|
)
|
||||||
|
if (raw.settings.wineOnUnixCommand)
|
||||||
|
settingsLocal?.set(
|
||||||
|
'linuxWineCommand',
|
||||||
|
String(raw.settings.wineOnUnixCommand)
|
||||||
|
)
|
||||||
|
if (raw.settings.theme) {
|
||||||
|
const tempTheme = (() => {
|
||||||
|
switch (Number(raw.settings.theme)) {
|
||||||
|
case 1:
|
||||||
|
return 'red'
|
||||||
|
case 2:
|
||||||
|
return 'blue'
|
||||||
|
case 3:
|
||||||
|
return 'purple'
|
||||||
|
default:
|
||||||
|
return 'dark'
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
settingsLocal?.set('theme', tempTheme)
|
||||||
|
}
|
||||||
|
await remove('config.json', legacyOptions)
|
||||||
|
}
|
||||||
|
setSettings(settingsLocal)
|
||||||
|
setVersions(versionsLocal)
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
if (!(await isPermissionGranted())) {
|
||||||
|
await requestPermission()
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const downloadVersions = useCallback(
|
||||||
|
async (
|
||||||
|
list: Array<{
|
||||||
|
id: string
|
||||||
|
type: 0 | 1 | 2
|
||||||
|
modDownload?: number
|
||||||
|
gameId?: number
|
||||||
|
modId?: number
|
||||||
|
modVersion?: string
|
||||||
|
}>
|
||||||
|
): Promise<void> => {
|
||||||
|
if (list.length === 0) return
|
||||||
|
setSelectedVersionList([])
|
||||||
|
|
||||||
|
const newVersions = list.filter(
|
||||||
|
item =>
|
||||||
|
!downloadQueue.includes(item.id) &&
|
||||||
|
!downloadProgress.some(d => d.version === item.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (newVersions.length === 0) return
|
||||||
|
|
||||||
|
const newDownloads = newVersions.map(
|
||||||
|
item =>
|
||||||
|
new DownloadProgress(
|
||||||
|
item.id,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
item.type,
|
||||||
|
item.modDownload ?? null,
|
||||||
|
item.gameId ?? null,
|
||||||
|
item.modId ?? null,
|
||||||
|
item.modVersion ?? null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
setDownloadProgress(prev => [...prev, ...newDownloads])
|
||||||
|
setDownloadQueue(prev => [...prev, ...newVersions.map(v => v.id)])
|
||||||
|
},
|
||||||
|
[downloadQueue, downloadProgress]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isProcessingQueue || downloadQueue.length === 0) return
|
||||||
|
|
||||||
|
const processNextDownload = async () => {
|
||||||
|
setIsProcessingQueue(true)
|
||||||
|
|
||||||
|
const versionId = downloadQueue[0]
|
||||||
|
const downloadInfo = downloadProgress.find(d => d.version == versionId)
|
||||||
|
if (!downloadInfo) return
|
||||||
|
const info = serverVersionList?.versions.find(vf => vf.id == versionId)
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
||||||
|
setDownloadQueue(prev => prev.slice(1))
|
||||||
|
setIsProcessingQueue(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const gameInfo = serverVersionList?.games.find(vf => vf.id == info.game)
|
||||||
|
if (!gameInfo) {
|
||||||
|
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
||||||
|
setDownloadQueue(prev => prev.slice(1))
|
||||||
|
setIsProcessingQueue(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.map(d => (d.version === versionId ? { ...d, queued: false } : d))
|
||||||
|
)
|
||||||
|
|
||||||
|
const downloadInfoRequest = await fetch(
|
||||||
|
'https://games.lncvrt.xyz/api/launcher/download' +
|
||||||
|
(downloadInfo.type == 0
|
||||||
|
? '?versionId=' + info.id + '&downloadId=' + info.download
|
||||||
|
: '?downloadId=' +
|
||||||
|
(downloadInfo.type == 1
|
||||||
|
? info.modSupportDownload
|
||||||
|
: downloadInfo.modDownload)) +
|
||||||
|
(downloadInfo.type == 2 ? '&modId=' + downloadInfo.modId : '')
|
||||||
|
)
|
||||||
|
const signature = downloadInfoRequest.headers.get('x-signature') ?? ''
|
||||||
|
const data = await downloadInfoRequest.json()
|
||||||
|
if (
|
||||||
|
!(await verifySignature(JSON.stringify(data), signature)) ||
|
||||||
|
!data.success
|
||||||
|
) {
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.map(d =>
|
||||||
|
d.version === versionId
|
||||||
|
? { ...d, queued: false, failed: true, progress: 0 }
|
||||||
|
: d
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (await settings?.get<boolean>('notifications-allowed'))
|
||||||
|
await notifyUser(
|
||||||
|
'Download Failed',
|
||||||
|
`The download for version ${info.displayName} has failed.`
|
||||||
|
)
|
||||||
|
await getCurrentWindow().requestUserAttention(
|
||||||
|
UserAttentionType.Critical
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.map(d =>
|
||||||
|
d.version === versionId
|
||||||
|
? {
|
||||||
|
...d,
|
||||||
|
url: data.data.url,
|
||||||
|
executable: downloadInfo.type == 0 ? info.executable : null,
|
||||||
|
hash: data.data.hash
|
||||||
|
}
|
||||||
|
: d
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const res = await invoke<string>('download', {
|
||||||
|
url: data.data.url,
|
||||||
|
name: info.id,
|
||||||
|
hash: data.data.hash,
|
||||||
|
downloadType: downloadInfo.type,
|
||||||
|
modId: String(downloadInfo.modId ?? '')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res === '1') {
|
||||||
|
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
||||||
|
if (downloadInfo.type != 1) {
|
||||||
|
if (downloadInfo.type == 2) {
|
||||||
|
versions?.set('mods', {
|
||||||
|
...modsList,
|
||||||
|
[downloadInfo.modGame! + '-' + downloadInfo.modId!]: {
|
||||||
|
[downloadInfo.modVersion!]: Date.now()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
versions?.set('list', {
|
||||||
|
...versionsList,
|
||||||
|
[versionId]: Date.now()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (res == '0') {
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === info.id)
|
||||||
|
if (i === -1) return prev
|
||||||
|
if (prev[i].canceled) {
|
||||||
|
return prev.filter((_, idx) => idx !== i)
|
||||||
|
}
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = {
|
||||||
|
...copy[i],
|
||||||
|
downloading: false,
|
||||||
|
paused: true,
|
||||||
|
failed: false
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
} else if (res == '-1') {
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.map(d =>
|
||||||
|
d.version === versionId
|
||||||
|
? { ...d, queued: false, failed: true, progress: 0 }
|
||||||
|
: d
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (await settings?.get<boolean>('notifications-allowed'))
|
||||||
|
await notifyUser(
|
||||||
|
'Download Failed',
|
||||||
|
`The download for version ${info.displayName} has failed.`
|
||||||
|
)
|
||||||
|
await getCurrentWindow().requestUserAttention(
|
||||||
|
UserAttentionType.Critical
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setDownloadQueue(prev => prev.slice(1))
|
||||||
|
setIsProcessingQueue(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
processNextDownload()
|
||||||
|
}, [
|
||||||
|
downloadQueue,
|
||||||
|
isProcessingQueue,
|
||||||
|
downloadProgress,
|
||||||
|
serverVersionList,
|
||||||
|
settings,
|
||||||
|
versions,
|
||||||
|
versionsList,
|
||||||
|
modsList
|
||||||
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (revisionCheck.current) return
|
||||||
|
if (!serverVersionList) return
|
||||||
|
revisionCheck.current = true
|
||||||
|
;(async () => {
|
||||||
|
for (const [key, value] of Object.entries(versionsList)) {
|
||||||
|
const verInfo = serverVersionList.versions.find(item => item.id === key)
|
||||||
|
|
||||||
|
if (
|
||||||
|
!verInfo ||
|
||||||
|
(verInfo.lastRevision > 0 && value / 1000 <= verInfo.lastRevision)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
await exists('game/' + key + '/' + verInfo?.executable, {
|
||||||
|
baseDir: BaseDirectory.AppLocalData
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await remove('game/' + key + '/' + verInfo?.executable, {
|
||||||
|
baseDir: BaseDirectory.AppLocalData,
|
||||||
|
recursive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}, [serverVersionList, downloadVersions, versionsList])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
downloadProgress.length === 0 &&
|
||||||
|
downloadQueue.length === 0 &&
|
||||||
|
showPopup &&
|
||||||
|
popupMode === 1
|
||||||
|
) {
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (notificationsAllowed) {
|
||||||
|
await notifyUser('Downloads Complete', 'All downloads finished!')
|
||||||
|
}
|
||||||
|
await getCurrentWindow().requestUserAttention(
|
||||||
|
UserAttentionType.Informational
|
||||||
|
)
|
||||||
|
closePopup()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
downloadProgress,
|
||||||
|
downloadQueue,
|
||||||
|
showPopup,
|
||||||
|
popupMode,
|
||||||
|
closePopup,
|
||||||
|
notificationsAllowed
|
||||||
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!settings || !versions) return
|
||||||
|
const unlisteners: Promise<() => void>[] = []
|
||||||
|
|
||||||
|
function watchSettings<T> (key: string, fn: (v: T | undefined) => void) {
|
||||||
|
settings!.get<T>(key).then(fn)
|
||||||
|
unlisteners.push(settings!.onKeyChange<T>(key, fn))
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchVersions<T> (key: string, fn: (v: T | undefined) => void) {
|
||||||
|
versions!.get<T>(key).then(fn)
|
||||||
|
unlisteners.push(versions!.onKeyChange<T>(key, fn))
|
||||||
|
}
|
||||||
|
|
||||||
|
watchSettings<string>('theme', v => setTheme(v ?? 'dark'))
|
||||||
|
watchSettings<boolean>('notificationsAllowed', v =>
|
||||||
|
setNotificationsAllowed(v ?? true)
|
||||||
|
)
|
||||||
|
watchSettings<boolean>('sidebarAlwaysShowGames', v =>
|
||||||
|
setSidebarAlwaysShowGames(v ?? true)
|
||||||
|
)
|
||||||
|
watchSettings<boolean>('linuxUseWine', v => setLinuxUseWine(v ?? false))
|
||||||
|
watchSettings<string>('linuxWineCommand', v =>
|
||||||
|
setLinuxWineCommand(v ?? 'wine %path%')
|
||||||
|
)
|
||||||
|
|
||||||
|
watchVersions<Record<string, number>>('list', v => setVersionsList(v ?? {}))
|
||||||
|
watchVersions<Record<string, Record<string, number>>>('mods', v =>
|
||||||
|
setModsList(v ?? {})
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisteners.forEach(u => u.then(fn => fn()))
|
||||||
|
}
|
||||||
|
}, [settings, versions])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<html lang='en' className={roboto.className}>
|
||||||
|
<body className={theme + '-theme'}>
|
||||||
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className='relative z-2 w-screen border-b border-b-(--col3) h-8.25 bg-(--col1)'
|
||||||
|
hidden={platformName != 'windows'}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`w-screen ${
|
||||||
|
platformName == 'windows'
|
||||||
|
? 'h-[calc(100vh-64px)]'
|
||||||
|
: 'h-screen'
|
||||||
|
} flex items-center justify-center`}
|
||||||
|
>
|
||||||
|
<p className='text-7xl text-center'>{loadingText}</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<GlobalProvider
|
||||||
|
value={{
|
||||||
|
serverVersionList,
|
||||||
|
selectedVersionList,
|
||||||
|
setSelectedVersionList,
|
||||||
|
downloadProgress,
|
||||||
|
setDownloadProgress,
|
||||||
|
showPopup,
|
||||||
|
setShowPopup,
|
||||||
|
popupMode,
|
||||||
|
setPopupMode,
|
||||||
|
fadeOut,
|
||||||
|
setFadeOut,
|
||||||
|
managingVersion,
|
||||||
|
setManagingVersion,
|
||||||
|
setSelectedGame,
|
||||||
|
viewingInfoFromDownloads,
|
||||||
|
version,
|
||||||
|
downloadVersions,
|
||||||
|
category,
|
||||||
|
setCategory,
|
||||||
|
downloadQueue,
|
||||||
|
setDownloadQueue,
|
||||||
|
closePopup,
|
||||||
|
selectedGame,
|
||||||
|
setViewingInfoFromDownloads,
|
||||||
|
showModInfo,
|
||||||
|
setShowModInfo,
|
||||||
|
getSpecialVersionsList,
|
||||||
|
settings,
|
||||||
|
versions,
|
||||||
|
notificationsAllowed,
|
||||||
|
sidebarAlwaysShowGames,
|
||||||
|
linuxUseWine,
|
||||||
|
linuxWineCommand,
|
||||||
|
theme,
|
||||||
|
versionsList,
|
||||||
|
modsList
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (showPopup && e.key === 'Escape') closePopup()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sidebar />
|
||||||
|
<div
|
||||||
|
className='relative z-2 ml-59.75 w-[calc(100vw-239px)] border-b border-b-(--col3) h-8.25 bg-(--col1)'
|
||||||
|
hidden={platformName != 'windows'}
|
||||||
|
/>
|
||||||
|
<div className='relative z-0'>
|
||||||
|
<main className='ml-60'>{children}</main>
|
||||||
|
</div>
|
||||||
|
{showPopup && (
|
||||||
|
<div
|
||||||
|
className={`popup-overlay ${fadeOut ? 'fade-out' : ''}`}
|
||||||
|
onClick={(e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
if (showModInfo) setShowModInfo(null)
|
||||||
|
if (viewingInfoFromDownloads) {
|
||||||
|
setPopupMode(0)
|
||||||
|
setViewingInfoFromDownloads(false)
|
||||||
|
setManagingVersion(null)
|
||||||
|
setSelectedGame(null)
|
||||||
|
}
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='popup-box'>
|
||||||
|
<button
|
||||||
|
className='popup-top-button left-2 btntheme1'
|
||||||
|
onClick={() => closePopup()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={
|
||||||
|
(popupMode == 0 &&
|
||||||
|
selectedGame &&
|
||||||
|
pathname === '/') ||
|
||||||
|
viewingInfoFromDownloads ||
|
||||||
|
(popupMode == 3 && showModInfo) ||
|
||||||
|
popupMode == 4
|
||||||
|
? faChevronLeft
|
||||||
|
: faXmark
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
{popupMode === 0 && selectedGame ? (
|
||||||
|
<VersionsDownloadPopup />
|
||||||
|
) : popupMode === 0 && !selectedGame ? (
|
||||||
|
<GamesDownloadPopup />
|
||||||
|
) : popupMode === 1 ? (
|
||||||
|
<DownloadsPopup />
|
||||||
|
) : popupMode === 2 ? (
|
||||||
|
<ManageVersionPopup />
|
||||||
|
) : popupMode === 3 ? (
|
||||||
|
<ModDownloadsPopup />
|
||||||
|
) : popupMode === 4 ? (
|
||||||
|
<VersionChangelogPopup />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</GlobalProvider>
|
||||||
|
)}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import './Installs.css'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import {
|
||||||
|
faCheck,
|
||||||
|
faShieldHalved,
|
||||||
|
faWarning
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
||||||
|
export default function Installs () {
|
||||||
|
const {
|
||||||
|
versionsList,
|
||||||
|
serverVersionList,
|
||||||
|
showPopup,
|
||||||
|
setShowPopup,
|
||||||
|
setPopupMode,
|
||||||
|
setFadeOut,
|
||||||
|
setSelectedVersionList,
|
||||||
|
setSelectedGame,
|
||||||
|
setCategory
|
||||||
|
} = useGlobal()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showPopup) return
|
||||||
|
setSelectedVersionList([])
|
||||||
|
}, [setSelectedVersionList, showPopup])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mx-4 mt-4'>
|
||||||
|
<div className='flex justify-between items-center mb-4'>
|
||||||
|
<p className='text-3xl'>Games</p>
|
||||||
|
<button
|
||||||
|
className='button btntheme1'
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedGame(null)
|
||||||
|
setPopupMode(0)
|
||||||
|
setShowPopup(true)
|
||||||
|
setFadeOut(false)
|
||||||
|
}}
|
||||||
|
title='Click to download more games.'
|
||||||
|
>
|
||||||
|
Download game
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='downloads-container'>
|
||||||
|
<div
|
||||||
|
className={`downloads-scroll ${
|
||||||
|
platform() == 'windows'
|
||||||
|
? 'h-[calc(100vh-116px)]'
|
||||||
|
: 'h-[calc(100vh-84px)]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{serverVersionList?.games
|
||||||
|
.filter(g =>
|
||||||
|
serverVersionList.versions
|
||||||
|
.filter(v => v.game === g.id)
|
||||||
|
.some(v => Object.keys(versionsList).includes(v.id))
|
||||||
|
)
|
||||||
|
.map(i => (
|
||||||
|
<div
|
||||||
|
key={i.id}
|
||||||
|
className={'downloads-entry'}
|
||||||
|
title={'Click to view game installs'}
|
||||||
|
onClick={() => {
|
||||||
|
setCategory(-1)
|
||||||
|
router.push('/main/game?id=' + i.id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='h-18 w-screen relative'>
|
||||||
|
<p className='text-2xl'>{i.name}</p>
|
||||||
|
|
||||||
|
<div className='flex gap-2 absolute left-0 bottom-0'>
|
||||||
|
<div
|
||||||
|
className='entry-info-item'
|
||||||
|
title='The amount of versions installed of this game in installed/installable format.'
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
{(() => {
|
||||||
|
const gameVersions =
|
||||||
|
serverVersionList.versions.filter(
|
||||||
|
v => v.game === i.id
|
||||||
|
)
|
||||||
|
const installed = gameVersions.filter(v =>
|
||||||
|
Object.keys(versionsList).includes(v.id)
|
||||||
|
).length
|
||||||
|
return gameVersions.length
|
||||||
|
? `${installed}/${gameVersions.length}`
|
||||||
|
: 'N/A'
|
||||||
|
})()}{' '}
|
||||||
|
versions installed
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='entry-info-item'
|
||||||
|
hidden={!i.official}
|
||||||
|
title='This game is official.'
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
|
||||||
|
<p>Official</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='entry-info-item'
|
||||||
|
hidden={i.official}
|
||||||
|
title={
|
||||||
|
i.verified
|
||||||
|
? 'This game is verified to be safe'
|
||||||
|
: 'This game is not verified to be save. Proceed with caution.'
|
||||||
|
}
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={i.verified ? faShieldHalved : faWarning}
|
||||||
|
color={i.verified ? '#19c84b' : '#ffc800'}
|
||||||
|
/>
|
||||||
|
<p>{i.verified ? 'Verified' : 'Unverified'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Setting } from '@/components/Setting'
|
||||||
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
|
import { copyToClipboard } from '@/lib/clipboard'
|
||||||
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
|
|
||||||
|
export default function Settings () {
|
||||||
|
const {
|
||||||
|
settings,
|
||||||
|
version,
|
||||||
|
notificationsAllowed,
|
||||||
|
sidebarAlwaysShowGames,
|
||||||
|
linuxUseWine,
|
||||||
|
linuxWineCommand,
|
||||||
|
theme
|
||||||
|
} = useGlobal()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className='text-3xl ml-4 mt-4'>Settings</p>
|
||||||
|
<div className='ml-4 mt-4 bg-(--col1) border border-(--col3) rounded-lg p-4 w-fit h-fit'>
|
||||||
|
<Setting
|
||||||
|
label='Allow sending notifications'
|
||||||
|
value={notificationsAllowed}
|
||||||
|
onChange={async () => {
|
||||||
|
await settings?.set('notificationsAllowed', !notificationsAllowed)
|
||||||
|
}}
|
||||||
|
title='This setting does as you expect, allow the launcher to send notifications for when stuff like downloading is done.'
|
||||||
|
/>
|
||||||
|
<Setting
|
||||||
|
label='Always show games in sidebar'
|
||||||
|
value={sidebarAlwaysShowGames}
|
||||||
|
onChange={async () => {
|
||||||
|
await settings?.set(
|
||||||
|
'sidebarAlwaysShowGames',
|
||||||
|
!sidebarAlwaysShowGames
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
title="This setting will make it so when you are on a page like this, the games won't disappear."
|
||||||
|
/>
|
||||||
|
<Setting
|
||||||
|
label='Use wine if needed'
|
||||||
|
value={linuxUseWine}
|
||||||
|
onChange={async () => {
|
||||||
|
await settings?.set('linuxUseWine', !linuxUseWine)
|
||||||
|
}}
|
||||||
|
className={platform() == 'linux' ? '' : 'hidden'}
|
||||||
|
/>
|
||||||
|
<p hidden={!(platform() == 'linux' && linuxUseWine)}>Wine Command:</p>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
value={linuxWineCommand}
|
||||||
|
onChange={async e => {
|
||||||
|
await settings?.set('linuxWineCommand', e.target.value)
|
||||||
|
}}
|
||||||
|
className={`input-field my-1 ${
|
||||||
|
platform() == 'linux' && linuxWineCommand ? '' : 'hidden'
|
||||||
|
}`}
|
||||||
|
></input>
|
||||||
|
<div title='The theme you want the launcher to use.'>
|
||||||
|
<label className='text-lg'>Theme:</label>
|
||||||
|
<select
|
||||||
|
className='ml-2 bg-(--col2) border border-(--col4) rounded-md'
|
||||||
|
value={theme}
|
||||||
|
onChange={async e => {
|
||||||
|
await settings?.set('theme', e.target.value)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={'dark'}>Dark (default)</option>
|
||||||
|
<option value={'red'}>Red</option>
|
||||||
|
<option value={'blue'}>Blue</option>
|
||||||
|
<option value={'purple'}>Purple</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className='fixed bottom-1.5 right-1.5 rounded-md cursor-pointer px-1 border z-100 transition-colors btntheme1'
|
||||||
|
onClick={async () =>
|
||||||
|
await copyToClipboard(`v${version}`, notificationsAllowed)
|
||||||
|
}
|
||||||
|
title='The current launcher version.'
|
||||||
|
>
|
||||||
|
v{version}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
import './Installs.css'
|
|
||||||
import { useGlobal } from './GlobalProvider'
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
||||||
import {
|
|
||||||
faCheck,
|
|
||||||
faShieldHalved,
|
|
||||||
faWarning
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
|
||||||
import { useRouter } from 'next/navigation'
|
|
||||||
|
|
||||||
export default function Installs () {
|
|
||||||
const {
|
|
||||||
showPopup,
|
|
||||||
setShowPopup,
|
|
||||||
setPopupMode,
|
|
||||||
setFadeOut,
|
|
||||||
setSelectedVersionList,
|
|
||||||
downloadedVersionsConfig,
|
|
||||||
normalConfig,
|
|
||||||
setSelectedGame,
|
|
||||||
getListOfGames,
|
|
||||||
getVersionsAmountData,
|
|
||||||
setCategory
|
|
||||||
} = useGlobal()
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!showPopup) return
|
|
||||||
setSelectedVersionList([])
|
|
||||||
}, [normalConfig, setSelectedVersionList, showPopup])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='mx-4 mt-4'>
|
|
||||||
<div className='flex justify-between items-center mb-4'>
|
|
||||||
<p className='text-3xl'>Games</p>
|
|
||||||
<button
|
|
||||||
className='button btntheme1'
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedGame(null)
|
|
||||||
setPopupMode(0)
|
|
||||||
setShowPopup(true)
|
|
||||||
setFadeOut(false)
|
|
||||||
}}
|
|
||||||
title='Click to download more games.'
|
|
||||||
>
|
|
||||||
Download game
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className='downloads-container'>
|
|
||||||
<div
|
|
||||||
className={`downloads-scroll ${
|
|
||||||
platform() == 'windows'
|
|
||||||
? 'h-[calc(100vh-116px)]'
|
|
||||||
: 'h-[calc(100vh-84px)]'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{downloadedVersionsConfig &&
|
|
||||||
Object.keys(downloadedVersionsConfig.list).length ? (
|
|
||||||
getListOfGames()
|
|
||||||
.sort((a, b) => {
|
|
||||||
return a.id - b.id
|
|
||||||
})
|
|
||||||
.map(i => (
|
|
||||||
<div
|
|
||||||
key={i.id}
|
|
||||||
className={'downloads-entry'}
|
|
||||||
title={'Click to view game installs'}
|
|
||||||
onClick={() => {
|
|
||||||
setCategory(-1)
|
|
||||||
router.push('/game?id=' + i.id)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className='h-18 w-screen relative'>
|
|
||||||
<p className='text-2xl'>{i.name}</p>
|
|
||||||
|
|
||||||
<div className='flex gap-2 absolute left-0 bottom-0'>
|
|
||||||
<div
|
|
||||||
className='entry-info-item'
|
|
||||||
title='The amount of versions installed of this game in installed/installable format.'
|
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
{(() => {
|
|
||||||
const data = getVersionsAmountData(i.id)
|
|
||||||
if (!data) return 'N/A'
|
|
||||||
return `${data.installed}/${data.total}`
|
|
||||||
})()}{' '}
|
|
||||||
versions installed
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className='entry-info-item'
|
|
||||||
hidden={!i.official}
|
|
||||||
title='This game is official.'
|
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
|
|
||||||
<p>Official</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className='entry-info-item'
|
|
||||||
hidden={i.official}
|
|
||||||
title={
|
|
||||||
i.verified
|
|
||||||
? 'This game is verified to be safe'
|
|
||||||
: 'This game is not verified to be save. Proceed with caution.'
|
|
||||||
}
|
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={i.verified ? faShieldHalved : faWarning}
|
|
||||||
color={i.verified ? '#19c84b' : '#ffc800'}
|
|
||||||
/>
|
|
||||||
<p>{i.verified ? 'Verified' : 'Unverified'}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className='flex justify-center items-center h-full'>
|
|
||||||
<p className='text-3xl'>No games installed</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { Setting } from '@/componets/Setting'
|
|
||||||
import { writeNormalConfig } from '@/lib/BazookaManager'
|
|
||||||
import { useGlobal } from '@/app/GlobalProvider'
|
|
||||||
import { copyToClipboard } from '@/lib/Clipboard'
|
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
|
||||||
|
|
||||||
export default function Settings () {
|
|
||||||
const [allowNotifications, setAllowNotifications] = useState(false)
|
|
||||||
const [alwaysShowGamesInSidebar, setAlwaysShowGamesInSidebar] =
|
|
||||||
useState(false)
|
|
||||||
const [useWineOnUnixWhenNeeded, setUseWineOnUnixWhenNeeded] = useState(false)
|
|
||||||
const [wineOnUnixCommand, setWineOnUnixCommand] = useState('wine %path%')
|
|
||||||
const [theme, setTheme] = useState(0)
|
|
||||||
|
|
||||||
const [loaded, setLoaded] = useState(false)
|
|
||||||
const { normalConfig, setNormalConfig, version } = useGlobal()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
;(async () => {
|
|
||||||
while (normalConfig != null) {
|
|
||||||
setAllowNotifications(normalConfig.settings.allowNotifications)
|
|
||||||
setAlwaysShowGamesInSidebar(
|
|
||||||
normalConfig.settings.alwaysShowGamesInSidebar
|
|
||||||
)
|
|
||||||
setUseWineOnUnixWhenNeeded(
|
|
||||||
normalConfig.settings.useWineOnUnixWhenNeeded
|
|
||||||
)
|
|
||||||
setWineOnUnixCommand(normalConfig.settings.wineOnUnixCommand)
|
|
||||||
setTheme(normalConfig.settings.theme)
|
|
||||||
setLoaded(true)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}, [normalConfig])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p className='text-3xl ml-4 mt-4'>Settings</p>
|
|
||||||
{loaded && (
|
|
||||||
<div className='ml-4 mt-4 bg-(--col1) border border-(--col3) rounded-lg p-4 w-fit h-fit'>
|
|
||||||
<Setting
|
|
||||||
label='Allow sending notifications'
|
|
||||||
value={allowNotifications}
|
|
||||||
onChange={async () => {
|
|
||||||
if (!normalConfig) return
|
|
||||||
setAllowNotifications(!allowNotifications)
|
|
||||||
setNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
allowNotifications: !allowNotifications
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await writeNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
allowNotifications: !allowNotifications
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
title='This setting does as you expect, allow the launcher to send notifications for when stuff like downloading is done.'
|
|
||||||
/>
|
|
||||||
<Setting
|
|
||||||
label='Always show games in sidebar'
|
|
||||||
value={alwaysShowGamesInSidebar}
|
|
||||||
onChange={async () => {
|
|
||||||
if (!normalConfig) return
|
|
||||||
setAlwaysShowGamesInSidebar(!alwaysShowGamesInSidebar)
|
|
||||||
setNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
alwaysShowGamesInSidebar: !alwaysShowGamesInSidebar
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await writeNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
alwaysShowGamesInSidebar: !alwaysShowGamesInSidebar
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
title="This setting will make it so when you are on a page like this, the games won't disappear."
|
|
||||||
/>
|
|
||||||
<Setting
|
|
||||||
label='Use wine when needed to launch games'
|
|
||||||
value={useWineOnUnixWhenNeeded}
|
|
||||||
onChange={async () => {
|
|
||||||
if (!normalConfig) return
|
|
||||||
setUseWineOnUnixWhenNeeded(!useWineOnUnixWhenNeeded)
|
|
||||||
setNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
useWineOnUnixWhenNeeded: !useWineOnUnixWhenNeeded
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await writeNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
useWineOnUnixWhenNeeded: !useWineOnUnixWhenNeeded
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
className={platform() == 'linux' ? '' : 'hidden'}
|
|
||||||
/>
|
|
||||||
<p hidden={!(platform() == 'linux' && useWineOnUnixWhenNeeded)}>
|
|
||||||
Wine Command:
|
|
||||||
</p>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
value={wineOnUnixCommand}
|
|
||||||
onChange={async e => {
|
|
||||||
if (!normalConfig) return
|
|
||||||
setWineOnUnixCommand(e.target.value)
|
|
||||||
setNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
wineOnUnixCommand: e.target.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await writeNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
wineOnUnixCommand: e.target.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
className={`input-field my-1 ${
|
|
||||||
platform() == 'linux' && useWineOnUnixWhenNeeded ? '' : 'hidden'
|
|
||||||
}`}
|
|
||||||
></input>
|
|
||||||
<div title='The theme you want the launcher to use.'>
|
|
||||||
<label className='text-lg'>Theme:</label>
|
|
||||||
<select
|
|
||||||
className='ml-2 bg-(--col2) border border-(--col4) rounded-md'
|
|
||||||
value={theme}
|
|
||||||
onChange={async e => {
|
|
||||||
if (!normalConfig) return
|
|
||||||
const newTheme = parseInt(e.target.value)
|
|
||||||
setTheme(newTheme)
|
|
||||||
setNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
theme: newTheme
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await writeNormalConfig({
|
|
||||||
...normalConfig,
|
|
||||||
settings: {
|
|
||||||
...normalConfig.settings,
|
|
||||||
theme: newTheme
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value={0}>Dark (default)</option>
|
|
||||||
<option value={1}>Red</option>
|
|
||||||
<option value={2}>Blue</option>
|
|
||||||
<option value={3}>Purple</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<p
|
|
||||||
className='fixed bottom-1.5 right-1.5 rounded-md cursor-pointer px-1 border z-100 transition-colors btntheme1'
|
|
||||||
onClick={async () => {
|
|
||||||
await copyToClipboard(`v${version}`, normalConfig)
|
|
||||||
}}
|
|
||||||
title='The current launcher version.'
|
|
||||||
>
|
|
||||||
v{version}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
export default function UpdateCheckingPage () {
|
||||||
|
return <p className='text-5xl text-center'>Checking for updates</p>
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import '@/styles/globals.css'
|
||||||
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
|
import { Roboto } from 'next/font/google'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
const roboto = Roboto({
|
||||||
|
subsets: ['latin']
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function RootLayout ({
|
||||||
|
children
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const [platformName, setPlatformName] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => setPlatformName(platform()), 0)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<html lang='en' className={roboto.className}>
|
||||||
|
<body className='dark-theme'>
|
||||||
|
<div
|
||||||
|
className='relative z-2 w-screen border-b border-b-(--col3) h-8.25 bg-(--col1)'
|
||||||
|
hidden={platformName != 'windows'}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`w-screen ${
|
||||||
|
platformName == 'windows' ? 'h-[calc(100vh-64px)]' : 'h-screen'
|
||||||
|
} flex items-center justify-center`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
|
|
||||||
|
export default function UpdateCheckingPage () {
|
||||||
|
return (
|
||||||
|
<div className='text-center'>
|
||||||
|
<p className='text-6xl mb-4'>Outdated Launcher!</p>
|
||||||
|
<p className='text-2xl mb-4'>
|
||||||
|
Please update to the latest version to continue.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className='button btntheme1'
|
||||||
|
onClick={() => openUrl('https://games.lncvrt.xyz/download')}
|
||||||
|
>
|
||||||
|
Download latest version!
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
export default function UpdateCheckingPage () {
|
||||||
|
return <p className='text-5xl text-center'>Updating launcher</p>
|
||||||
|
}
|
||||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
|||||||
import './Setting.css'
|
import '@/styles/setting.css'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { SettingProps } from '@/types/SettingProps'
|
import { SettingProps } from '@/types/SettingProps'
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import './Sidebar.css'
|
import '@/styles/sidebar.css'
|
||||||
import Icon from '@/assets/Icon.png'
|
import Icon from '@/assets/Icon.png'
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faDiscord } from '@fortawesome/free-brands-svg-icons'
|
import { faDiscord } from '@fortawesome/free-brands-svg-icons'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import { useGlobal } from '@/app/GlobalProvider'
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||||
@@ -27,16 +27,16 @@ const lexend = Lexend({
|
|||||||
|
|
||||||
export default function Sidebar () {
|
export default function Sidebar () {
|
||||||
const {
|
const {
|
||||||
normalConfig,
|
serverVersionList,
|
||||||
getListOfGames,
|
versionsList,
|
||||||
setShowPopup,
|
setShowPopup,
|
||||||
setPopupMode,
|
setPopupMode,
|
||||||
setFadeOut,
|
setFadeOut,
|
||||||
downloadProgress,
|
downloadProgress,
|
||||||
downloadedVersionsConfig,
|
|
||||||
getVersionInfo,
|
|
||||||
category,
|
category,
|
||||||
setCategory
|
setCategory,
|
||||||
|
sidebarAlwaysShowGames,
|
||||||
|
linuxUseWine
|
||||||
} = useGlobal()
|
} = useGlobal()
|
||||||
|
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
@@ -82,37 +82,42 @@ export default function Sidebar () {
|
|||||||
<nav className='nav-links overflow-auto pt-2'>
|
<nav className='nav-links overflow-auto pt-2'>
|
||||||
<Link
|
<Link
|
||||||
draggable={false}
|
draggable={false}
|
||||||
href='/'
|
href='/main'
|
||||||
className={`link relative flex items-center ${
|
className={`link relative flex items-center ${
|
||||||
pathname === '/' || pathname.startsWith('/game') ? 'active' : ''
|
pathname === '/main' || pathname.startsWith('/main/game')
|
||||||
|
? 'active'
|
||||||
|
: ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games
|
<FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games
|
||||||
</Link>
|
</Link>
|
||||||
{getListOfGames()
|
{serverVersionList?.games
|
||||||
.sort((a, b) => {
|
.filter(g =>
|
||||||
return a.id - b.id
|
serverVersionList.versions
|
||||||
})
|
.filter(v => v.game === g.id)
|
||||||
|
.some(v => Object.keys(versionsList).includes(v.id))
|
||||||
|
)
|
||||||
.map(i => (
|
.map(i => (
|
||||||
<React.Fragment key={i.id}>
|
<React.Fragment key={i.id}>
|
||||||
<div
|
<div
|
||||||
draggable={false}
|
draggable={false}
|
||||||
className={`link ${
|
className={`link ${
|
||||||
(pathname === '/game' &&
|
(pathname === '/main/game' &&
|
||||||
Number(params.get('id') || 0) == i.id) ||
|
Number(params.get('id') || 0) == i.id) ||
|
||||||
(i.id == 1 && pathname === '/game/berrydash/leaderboards')
|
(i.id == 1 &&
|
||||||
|
pathname === '/main/game/berrydash/leaderboards')
|
||||||
? 'active'
|
? 'active'
|
||||||
: ''
|
: ''
|
||||||
} ml-auto w-50 ${
|
} ml-auto w-50 ${
|
||||||
normalConfig?.settings.alwaysShowGamesInSidebar ||
|
sidebarAlwaysShowGames ||
|
||||||
pathname === '/' ||
|
pathname === '/' ||
|
||||||
pathname.startsWith('/game')
|
pathname.startsWith('/main/game')
|
||||||
? ''
|
? ''
|
||||||
: 'hidden'
|
: 'hidden'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCategory(-1)
|
setCategory(-1)
|
||||||
router.push('/game?id=' + i.id)
|
router.push('/main/game?id=' + i.id)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
@@ -130,17 +135,13 @@ export default function Sidebar () {
|
|||||||
{Object.entries(i.categoryNames)
|
{Object.entries(i.categoryNames)
|
||||||
.sort(([a], [b]) => Number(b) - Number(a))
|
.sort(([a], [b]) => Number(b) - Number(a))
|
||||||
.filter(([key]) => {
|
.filter(([key]) => {
|
||||||
const count = Object.keys(
|
const count = Object.keys(versionsList).filter(v => {
|
||||||
downloadedVersionsConfig?.list ?? {}
|
const info = serverVersionList.versions.find(
|
||||||
).filter(v => {
|
vf => vf.id == v
|
||||||
const info = getVersionInfo(v)
|
)
|
||||||
if (!info) return false
|
if (!info) return false
|
||||||
|
|
||||||
if (
|
if (platform() == 'linux' && info.wine && !linuxUseWine)
|
||||||
platform() == 'linux' &&
|
|
||||||
info.wine &&
|
|
||||||
!normalConfig?.settings.useWineOnUnixWhenNeeded
|
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return info.game === i.id && info.category === Number(key)
|
return info.game === i.id && info.category === Number(key)
|
||||||
@@ -153,23 +154,23 @@ export default function Sidebar () {
|
|||||||
key={`${i.id}-${key}`}
|
key={`${i.id}-${key}`}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
className={`link ${
|
className={`link ${
|
||||||
(pathname === '/game' &&
|
((pathname === '/main/game' &&
|
||||||
Number(params.get('id') || 0) == i.id) ||
|
Number(params.get('id') || 0) == i.id) ||
|
||||||
(i.id == 1 &&
|
(i.id == 1 &&
|
||||||
pathname === '/game/berrydash/leaderboards' &&
|
pathname === '/main/game/berrydash/leaderboards')) &&
|
||||||
category == Number(key))
|
category == Number(key)
|
||||||
? 'active'
|
? 'active'
|
||||||
: ''
|
: ''
|
||||||
} ml-auto w-47.5 ${
|
} ml-auto w-47.5 ${
|
||||||
normalConfig?.settings.alwaysShowGamesInSidebar ||
|
sidebarAlwaysShowGames ||
|
||||||
pathname === '/' ||
|
pathname === '/' ||
|
||||||
pathname.startsWith('/game')
|
pathname.startsWith('/main/game')
|
||||||
? ''
|
? ''
|
||||||
: 'hidden'
|
: 'hidden'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCategory(Number(key))
|
setCategory(Number(key))
|
||||||
router.push('/game?id=' + i.id)
|
router.push('/main/game?id=' + i.id)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
@@ -182,8 +183,8 @@ export default function Sidebar () {
|
|||||||
))}
|
))}
|
||||||
<Link
|
<Link
|
||||||
draggable={false}
|
draggable={false}
|
||||||
href='/settings'
|
href='/main/settings'
|
||||||
className={`link ${pathname === '/settings' ? 'active' : ''}`}
|
className={`link ${pathname === '/main/settings' ? 'active' : ''}`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
|
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
|
||||||
</Link>
|
</Link>
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { useGlobal } from '@/app/GlobalProvider'
|
|
||||||
import prettyBytes from 'pretty-bytes'
|
|
||||||
import ProgressBar from '../ProgressBar'
|
|
||||||
import { formatEtaSmart } from '@/lib/Util'
|
|
||||||
|
|
||||||
export default function DownloadsPopup () {
|
|
||||||
const {
|
|
||||||
downloadProgress,
|
|
||||||
getVersionInfo,
|
|
||||||
setDownloadProgress,
|
|
||||||
downloadQueue,
|
|
||||||
setDownloadQueue
|
|
||||||
} = useGlobal()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p className='text-xl text-center'>Downloads</p>
|
|
||||||
<div className='popup-content'>
|
|
||||||
{downloadProgress.map((v, i) => {
|
|
||||||
const queuePosition = downloadQueue.indexOf(v.version)
|
|
||||||
return (
|
|
||||||
<div key={i} className='popup-entry flex flex-col justify-between'>
|
|
||||||
<p className='text-2xl text-center'>
|
|
||||||
{getVersionInfo(v.version)?.displayName}
|
|
||||||
</p>
|
|
||||||
<div className='mt-6.25 flex items-center justify-between'>
|
|
||||||
{v.failed || v.queued ? (
|
|
||||||
<div className='flex items-center justify-between w-full'>
|
|
||||||
<span
|
|
||||||
className={`${
|
|
||||||
v.failed ? 'text-red-500' : 'text-yellow-300'
|
|
||||||
} inline-block text-center flex-1`}
|
|
||||||
>
|
|
||||||
{v.failed
|
|
||||||
? 'Download failed'
|
|
||||||
: queuePosition === 0
|
|
||||||
? 'Starting soon...'
|
|
||||||
: `Queued (Position ${queuePosition + 1})`}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
className='button btntheme3 -ml-1.25'
|
|
||||||
onClick={() => {
|
|
||||||
setDownloadQueue(prev =>
|
|
||||||
prev.filter(id => id !== v.version)
|
|
||||||
)
|
|
||||||
setDownloadProgress(prev =>
|
|
||||||
prev.filter(d => d.version !== v.version)
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
title='Click to remove this version from the download queue.'
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : v.hash_checking || v.finishing ? (
|
|
||||||
<span
|
|
||||||
className={`${
|
|
||||||
v.hash_checking ? 'text-blue-300' : 'text-green-300'
|
|
||||||
} inline-block w-full text-center`}
|
|
||||||
>
|
|
||||||
{v.hash_checking ? 'Verifying file integerty' : 'Finishing'}
|
|
||||||
...
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<div className='flex flex-col gap-1 w-full'>
|
|
||||||
<span className='text-center'>
|
|
||||||
Downloaded{' '}
|
|
||||||
{prettyBytes(v.progressBytes, {
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1
|
|
||||||
})}{' '}
|
|
||||||
of{' '}
|
|
||||||
{prettyBytes(v.size, {
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1
|
|
||||||
})}{' '}
|
|
||||||
(ETA: {formatEtaSmart(v.etaSecs)} • Speed:{' '}
|
|
||||||
{prettyBytes(v.speed, {
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1
|
|
||||||
})}
|
|
||||||
/s)
|
|
||||||
</span>
|
|
||||||
<ProgressBar progress={v.progress} className='w-full' />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
import { app } from '@tauri-apps/api'
|
|
||||||
import { NormalConfig } from '@/types/NormalConfig'
|
|
||||||
import {
|
|
||||||
BaseDirectory,
|
|
||||||
create,
|
|
||||||
exists,
|
|
||||||
mkdir,
|
|
||||||
readTextFile,
|
|
||||||
writeFile
|
|
||||||
} from '@tauri-apps/plugin-fs'
|
|
||||||
import { VersionsConfig } from '@/types/VersionsConfig'
|
|
||||||
|
|
||||||
export async function readNormalConfig (): Promise<NormalConfig> {
|
|
||||||
const version = await app.getVersion()
|
|
||||||
try {
|
|
||||||
const options = {
|
|
||||||
baseDir: BaseDirectory.AppLocalData
|
|
||||||
}
|
|
||||||
const doesFolderExist = await exists('', options)
|
|
||||||
const doesConfigExist = await exists('config.json', options)
|
|
||||||
if (!doesFolderExist || !doesConfigExist) {
|
|
||||||
if (!doesFolderExist) {
|
|
||||||
await mkdir('', options)
|
|
||||||
}
|
|
||||||
const file = await create('config.json', options)
|
|
||||||
await file.write(
|
|
||||||
new TextEncoder().encode(
|
|
||||||
JSON.stringify(new NormalConfig(version), null, 2)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
await file.close()
|
|
||||||
return new NormalConfig(version)
|
|
||||||
}
|
|
||||||
const config = await readTextFile('config.json', options)
|
|
||||||
const raw = JSON.parse(config)
|
|
||||||
if (
|
|
||||||
raw.settings &&
|
|
||||||
raw.settings.theme &&
|
|
||||||
(raw.version == '1.0.0' ||
|
|
||||||
raw.version == '1.1.0' ||
|
|
||||||
raw.version == '1.1.1' ||
|
|
||||||
raw.version == '1.2.0' ||
|
|
||||||
raw.version == '1.3.0' ||
|
|
||||||
raw.version == '1.3.1' ||
|
|
||||||
raw.version == '1.4.0' ||
|
|
||||||
raw.version == '1.5.0' ||
|
|
||||||
raw.version == '1.5.1' ||
|
|
||||||
raw.version == '1.5.2' ||
|
|
||||||
raw.version == '1.5.3' ||
|
|
||||||
raw.version == '1.5.4')
|
|
||||||
) {
|
|
||||||
const parsed = Number(raw.settings.theme)
|
|
||||||
if (parsed == 3) raw.settings.theme = 2
|
|
||||||
if (parsed == 4) raw.settings.theme = 3
|
|
||||||
else if (parsed != 0 && parsed != 1) raw.settings.theme = 0
|
|
||||||
}
|
|
||||||
raw.version = version
|
|
||||||
writeNormalConfig(raw)
|
|
||||||
return NormalConfig.import(raw)
|
|
||||||
} catch {
|
|
||||||
return new NormalConfig(version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function writeNormalConfig (data: NormalConfig) {
|
|
||||||
const options = {
|
|
||||||
baseDir: BaseDirectory.AppLocalData
|
|
||||||
}
|
|
||||||
const doesFolderExist = await exists('', options)
|
|
||||||
const doesConfigExist = await exists('config.json', options)
|
|
||||||
if (!doesFolderExist || !doesConfigExist) {
|
|
||||||
if (!doesFolderExist) {
|
|
||||||
await mkdir('', options)
|
|
||||||
}
|
|
||||||
const file = await create('config.json', options)
|
|
||||||
await file.write(new TextEncoder().encode(JSON.stringify(data, null, 2)))
|
|
||||||
await file.close()
|
|
||||||
} else {
|
|
||||||
await writeFile(
|
|
||||||
'config.json',
|
|
||||||
new TextEncoder().encode(JSON.stringify(data, null, 2)),
|
|
||||||
options
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function readVersionsConfig (): Promise<VersionsConfig> {
|
|
||||||
const version = await app.getVersion()
|
|
||||||
try {
|
|
||||||
const options = {
|
|
||||||
baseDir: BaseDirectory.AppLocalData
|
|
||||||
}
|
|
||||||
const doesFolderExist = await exists('', options)
|
|
||||||
const doesConfigExist = await exists('versions.json', options)
|
|
||||||
if (!doesFolderExist || !doesConfigExist) {
|
|
||||||
if (!doesFolderExist) {
|
|
||||||
await mkdir('', options)
|
|
||||||
}
|
|
||||||
const file = await create('versions.json', options)
|
|
||||||
await file.write(
|
|
||||||
new TextEncoder().encode(
|
|
||||||
JSON.stringify(new VersionsConfig(version), null, 2)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
await file.close()
|
|
||||||
return new VersionsConfig(version)
|
|
||||||
}
|
|
||||||
const config = await readTextFile('versions.json', options)
|
|
||||||
const raw = JSON.parse(config)
|
|
||||||
if (raw.list && raw.timestamps) {
|
|
||||||
raw.list = raw.timestamps
|
|
||||||
delete raw.timestamps
|
|
||||||
|
|
||||||
await writeFile(
|
|
||||||
'versions.json',
|
|
||||||
new TextEncoder().encode(JSON.stringify(raw, null, 2)),
|
|
||||||
options
|
|
||||||
)
|
|
||||||
}
|
|
||||||
raw.version = version
|
|
||||||
writeVersionsConfig(raw)
|
|
||||||
return VersionsConfig.import(raw)
|
|
||||||
} catch {
|
|
||||||
return new VersionsConfig(version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function writeVersionsConfig (data: VersionsConfig) {
|
|
||||||
const options = {
|
|
||||||
baseDir: BaseDirectory.AppLocalData
|
|
||||||
}
|
|
||||||
const doesFolderExist = await exists('', options)
|
|
||||||
const doesConfigExist = await exists('versions.json', options)
|
|
||||||
if (!doesFolderExist || !doesConfigExist) {
|
|
||||||
if (!doesFolderExist) {
|
|
||||||
await mkdir('', options)
|
|
||||||
}
|
|
||||||
const file = await create('versions.json', options)
|
|
||||||
await file.write(new TextEncoder().encode(JSON.stringify(data, null, 2)))
|
|
||||||
await file.close()
|
|
||||||
} else {
|
|
||||||
await writeFile(
|
|
||||||
'versions.json',
|
|
||||||
new TextEncoder().encode(JSON.stringify(data, null, 2)),
|
|
||||||
options
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { NormalConfig } from '@/types/NormalConfig'
|
|
||||||
import { notifyUser } from './Notifications'
|
|
||||||
|
|
||||||
export async function copyToClipboard (
|
|
||||||
text: string,
|
|
||||||
normalConfig: NormalConfig | null
|
|
||||||
) {
|
|
||||||
if (normalConfig?.settings.allowNotifications)
|
|
||||||
await notifyUser('Copied', 'Text "' + text + '" copied to clipboard')
|
|
||||||
}
|
|
||||||
File renamed without changes.
@@ -0,0 +1,11 @@
|
|||||||
|
import { writeText } from '@tauri-apps/plugin-clipboard-manager'
|
||||||
|
import { notifyUser } from './notifications'
|
||||||
|
|
||||||
|
export async function copyToClipboard (
|
||||||
|
text: string,
|
||||||
|
notificationsAllowed: boolean
|
||||||
|
) {
|
||||||
|
await writeText(text)
|
||||||
|
if (notificationsAllowed)
|
||||||
|
await notifyUser('Copied', 'Text "' + text + '" copied to clipboard')
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,248 @@
|
|||||||
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
|
import prettyBytes from 'pretty-bytes'
|
||||||
|
import ProgressBar from '@/components/ProgressBar'
|
||||||
|
import { formatEtaSmart } from '@/lib/util'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import {
|
||||||
|
faCancel,
|
||||||
|
faPause,
|
||||||
|
faPlay,
|
||||||
|
faTrash
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
import { notifyUser } from '@/lib/notifications'
|
||||||
|
import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window'
|
||||||
|
|
||||||
|
export default function DownloadsPopup () {
|
||||||
|
const {
|
||||||
|
downloadProgress,
|
||||||
|
setDownloadProgress,
|
||||||
|
downloadQueue,
|
||||||
|
setDownloadQueue,
|
||||||
|
notificationsAllowed,
|
||||||
|
versionsList,
|
||||||
|
modsList,
|
||||||
|
versions,
|
||||||
|
serverVersionList
|
||||||
|
} = useGlobal()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className='text-xl text-center'>Downloads</p>
|
||||||
|
<div className='popup-content'>
|
||||||
|
{downloadProgress.map((v, i) => {
|
||||||
|
const queuePosition = downloadQueue.indexOf(v.version)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className='popup-entry flex flex-col justify-between relative'
|
||||||
|
>
|
||||||
|
<div className='absolute right-2 top-2 flex flex-row gap-2'>
|
||||||
|
<div
|
||||||
|
className='cursor-pointer bg-(--col5) hover:bg-(--col7) border border-(--col7) hover:border-(--col9) transition-colors w-8 h-8 flex items-center justify-center rounded-full'
|
||||||
|
hidden={!v.downloading && !v.paused}
|
||||||
|
onClick={async () => {
|
||||||
|
if (v.downloading) {
|
||||||
|
await invoke('cancel_download', { name: v.version })
|
||||||
|
} else {
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === v.version)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = {
|
||||||
|
...copy[i],
|
||||||
|
paused: false,
|
||||||
|
downloading: true
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
const res = await invoke<string>('download', {
|
||||||
|
url: v.url,
|
||||||
|
name: v.version,
|
||||||
|
hash: v.hash,
|
||||||
|
downloadType: v.type,
|
||||||
|
modId: String(v.modId ?? '')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res === '1') {
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.filter(d => d.version !== v.version)
|
||||||
|
)
|
||||||
|
if (v.type != 1) {
|
||||||
|
if (v.type == 2) {
|
||||||
|
versions?.set('mods', {
|
||||||
|
...modsList,
|
||||||
|
[v.modGame! + '-' + v.modId!]: {
|
||||||
|
[v.modVersion!]: Date.now()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
versions?.set('list', {
|
||||||
|
...versionsList,
|
||||||
|
[v.version]: Date.now()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (res == '0') {
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === v.version)
|
||||||
|
if (i === -1) return prev
|
||||||
|
if (prev[i].canceled) {
|
||||||
|
return prev.filter((_, idx) => idx !== i)
|
||||||
|
}
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = {
|
||||||
|
...copy[i],
|
||||||
|
downloading: false,
|
||||||
|
paused: true,
|
||||||
|
failed: false
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
} else if (res == '-1') {
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.map(d =>
|
||||||
|
d.version === v.version
|
||||||
|
? {
|
||||||
|
...d,
|
||||||
|
queued: false,
|
||||||
|
failed: true,
|
||||||
|
progress: 0
|
||||||
|
}
|
||||||
|
: d
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (notificationsAllowed)
|
||||||
|
await notifyUser(
|
||||||
|
'Download Failed',
|
||||||
|
`The download for version ${
|
||||||
|
serverVersionList?.versions.find(
|
||||||
|
vf => vf.id == v.version
|
||||||
|
)?.displayName
|
||||||
|
} has failed.`
|
||||||
|
)
|
||||||
|
await getCurrentWindow().requestUserAttention(
|
||||||
|
UserAttentionType.Critical
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={v.downloading ? faPause : faPlay}
|
||||||
|
className='w-6 h-6'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='cursor-pointer bg-(--col5) hover:bg-(--col7) border border-(--col7) hover:border-(--col9) transition-colors w-8 h-8 flex items-center justify-center rounded-full'
|
||||||
|
hidden={!v.downloading && !v.paused}
|
||||||
|
onClick={async () => {
|
||||||
|
if (!v.paused) {
|
||||||
|
await invoke('cancel_download', { name: v.version })
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === v.version)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = {
|
||||||
|
...copy[i],
|
||||||
|
canceled: true
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version === v.version)
|
||||||
|
if (i === -1) return prev
|
||||||
|
return prev.filter((_, idx) => idx !== i)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faCancel} className='w-6 h-6' />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='cursor-pointer bg-(--col5) hover:bg-(--col7) border border-(--col7) hover:border-(--col9) transition-colors w-8 h-8 flex items-center justify-center rounded-full'
|
||||||
|
hidden={!v.failed && !v.queued}
|
||||||
|
onClick={() => {
|
||||||
|
setDownloadQueue(prev =>
|
||||||
|
prev.filter(id => id !== v.version)
|
||||||
|
)
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.filter(d => d.version !== v.version)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} className='w-6 h-6' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className='text-2xl text-center'>
|
||||||
|
{v.type == 1
|
||||||
|
? 'Mod loader for '
|
||||||
|
: v.type == 2
|
||||||
|
? 'Mod for '
|
||||||
|
: null}{' '}
|
||||||
|
{
|
||||||
|
serverVersionList?.versions.find(vf => vf.id == v.version)
|
||||||
|
?.displayName
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<div className='mt-6.25 flex items-center justify-between'>
|
||||||
|
{v.failed || v.queued ? (
|
||||||
|
<div className='flex items-center justify-between w-full'>
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
v.failed ? 'text-red-500' : 'text-yellow-300'
|
||||||
|
} inline-block text-center flex-1`}
|
||||||
|
>
|
||||||
|
{v.failed
|
||||||
|
? 'Download failed'
|
||||||
|
: queuePosition === 0
|
||||||
|
? 'Starting soon...'
|
||||||
|
: `Queued (Position ${queuePosition + 1})`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : v.hash_checking ? (
|
||||||
|
<span className='text-blue-300 inline-block w-full text-center'>
|
||||||
|
Verifying file integerty...
|
||||||
|
</span>
|
||||||
|
) : v.unzipping ? (
|
||||||
|
<div className='flex flex-col gap-1 w-full'>
|
||||||
|
<span className='text-center'>
|
||||||
|
Unzipped {v.unzipped} / {v.unzipTotal} files
|
||||||
|
</span>
|
||||||
|
<ProgressBar
|
||||||
|
progress={(v.unzipped / v.unzipTotal) * 100}
|
||||||
|
className='w-full'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='flex flex-col gap-1 w-full'>
|
||||||
|
<span className='text-center'>
|
||||||
|
Downloaded{' '}
|
||||||
|
{prettyBytes(v.progressBytes, {
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1
|
||||||
|
})}{' '}
|
||||||
|
of{' '}
|
||||||
|
{prettyBytes(v.size, {
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1
|
||||||
|
})}{' '}
|
||||||
|
(ETA: {formatEtaSmart(v.etaSecs)} • Speed:{' '}
|
||||||
|
{prettyBytes(v.speed, {
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1
|
||||||
|
})}
|
||||||
|
/s)
|
||||||
|
</span>
|
||||||
|
<ProgressBar progress={v.progress} className='w-full' />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useGlobal } from '@/app/GlobalProvider'
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
import {
|
import {
|
||||||
faCheck,
|
faCheck,
|
||||||
faCode,
|
faCode,
|
||||||
@@ -9,18 +9,18 @@ import {
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
|
||||||
export default function GamesDownloadPopup () {
|
export default function GamesDownloadPopup () {
|
||||||
const { serverVersionList, getVersionsAmountData, setSelectedGame } =
|
const { serverVersionList, setSelectedGame, versionsList } = useGlobal()
|
||||||
useGlobal()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className='text-xl text-center'>Select a game to download</p>
|
<p className='text-xl text-center'>Select a game to download</p>
|
||||||
<div className='popup-content'>
|
<div className='popup-content'>
|
||||||
{serverVersionList?.games
|
{serverVersionList?.games
|
||||||
.filter(v => {
|
.filter(i => {
|
||||||
const data = getVersionsAmountData(v.id)
|
const gameVersions = serverVersionList.versions.filter(
|
||||||
if (!data) return false
|
vf => vf.game === i.id
|
||||||
if (data.total > 0) return true
|
)
|
||||||
|
if (gameVersions.length > 0) return true
|
||||||
})
|
})
|
||||||
.map((v, i) => (
|
.map((v, i) => (
|
||||||
<div key={i} className='popup-entry'>
|
<div key={i} className='popup-entry'>
|
||||||
@@ -32,9 +32,15 @@ export default function GamesDownloadPopup () {
|
|||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
{(() => {
|
{(() => {
|
||||||
const data = getVersionsAmountData(v.id)
|
const gameVersions = serverVersionList.versions.filter(
|
||||||
if (!data) return 'N/A'
|
vf => vf.game === v.id
|
||||||
return `${data.installed}/${data.total}`
|
)
|
||||||
|
const installed = gameVersions.filter(v =>
|
||||||
|
Object.keys(versionsList).includes(v.id)
|
||||||
|
).length
|
||||||
|
return gameVersions.length
|
||||||
|
? `${installed}/${gameVersions.length}`
|
||||||
|
: 'N/A'
|
||||||
})()}{' '}
|
})()}{' '}
|
||||||
versions installed
|
versions installed
|
||||||
</p>
|
</p>
|
||||||
@@ -74,11 +80,7 @@ export default function GamesDownloadPopup () {
|
|||||||
<button
|
<button
|
||||||
className='button btntheme3 right-2 bottom-2'
|
className='button btntheme3 right-2 bottom-2'
|
||||||
onClick={() => setSelectedGame(v.id)}
|
onClick={() => setSelectedGame(v.id)}
|
||||||
title={`Click to download specific versions of the game. You have ${(() => {
|
title='Click to download more versions of the game.'
|
||||||
const data = getVersionsAmountData(v.id)
|
|
||||||
if (!data) return 'N/A'
|
|
||||||
return `${data.installed} of ${data.total}`
|
|
||||||
})()} versions downloaded.`}
|
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<FontAwesomeIcon icon={faDownload} /> Download
|
<FontAwesomeIcon icon={faDownload} /> Download
|
||||||
@@ -9,28 +9,25 @@ import {
|
|||||||
faWarning
|
faWarning
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { useGlobal } from '@/app/GlobalProvider'
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { message } from '@tauri-apps/plugin-dialog'
|
|
||||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||||
import { writeVersionsConfig } from '@/lib/BazookaManager'
|
import { openFolder } from '@/lib/util'
|
||||||
import { openFolder } from '@/lib/Util'
|
|
||||||
|
|
||||||
export default function VersionVersionPopup () {
|
export default function ManageVersionPopup () {
|
||||||
const {
|
const {
|
||||||
getGameInfo,
|
versionsList,
|
||||||
getVersionInfo,
|
serverVersionList,
|
||||||
managingVersion,
|
managingVersion,
|
||||||
downloadedVersionsConfig,
|
|
||||||
viewingInfoFromDownloads,
|
viewingInfoFromDownloads,
|
||||||
setManagingVersion,
|
setManagingVersion,
|
||||||
closePopup,
|
closePopup,
|
||||||
setDownloadedVersionsConfig,
|
|
||||||
setPopupMode,
|
setPopupMode,
|
||||||
setSelectedVersionList,
|
setSelectedVersionList,
|
||||||
downloadVersions
|
downloadVersions,
|
||||||
|
versions
|
||||||
} = useGlobal()
|
} = useGlobal()
|
||||||
const [versionSize, setVersionSize] = useState<number>(0)
|
const [versionSize, setVersionSize] = useState<number>(0)
|
||||||
|
|
||||||
@@ -43,10 +40,14 @@ export default function VersionVersionPopup () {
|
|||||||
})
|
})
|
||||||
}, [managingVersion, setVersionSize, viewingInfoFromDownloads])
|
}, [managingVersion, setVersionSize, viewingInfoFromDownloads])
|
||||||
|
|
||||||
if (!managingVersion || !downloadedVersionsConfig) return <></>
|
if (!managingVersion) return <></>
|
||||||
|
|
||||||
const versionInfo = getVersionInfo(managingVersion)
|
const versionInfo = serverVersionList?.versions.find(
|
||||||
const gameInfo = getGameInfo(versionInfo?.game)
|
vf => vf.id == managingVersion
|
||||||
|
)
|
||||||
|
const gameInfo = serverVersionList?.games.find(
|
||||||
|
vf => vf.id == versionInfo?.game
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -59,7 +60,7 @@ export default function VersionVersionPopup () {
|
|||||||
<p>
|
<p>
|
||||||
Installed{' '}
|
Installed{' '}
|
||||||
{new Intl.DateTimeFormat(undefined).format(
|
{new Intl.DateTimeFormat(undefined).format(
|
||||||
downloadedVersionsConfig.list[managingVersion] ?? 0
|
versionsList[managingVersion] ?? 0
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,13 +110,7 @@ export default function VersionVersionPopup () {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className='entry-info-item btntheme2'
|
className='entry-info-item btntheme2'
|
||||||
onClick={async () => {
|
onClick={() => setPopupMode(4)}
|
||||||
if (!versionInfo) return
|
|
||||||
await message(atob(versionInfo.changelog), {
|
|
||||||
title: 'Changelog for ' + versionInfo.displayName,
|
|
||||||
kind: 'info'
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
hidden={!versionInfo?.changelog}
|
hidden={!versionInfo?.changelog}
|
||||||
>
|
>
|
||||||
<p>View Changelog</p>
|
<p>View Changelog</p>
|
||||||
@@ -135,18 +130,14 @@ export default function VersionVersionPopup () {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
closePopup()
|
closePopup()
|
||||||
|
|
||||||
setDownloadedVersionsConfig(prev => {
|
versions?.set(
|
||||||
if (!prev) return prev
|
'list',
|
||||||
const updatedList = Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(prev.list).filter(([k]) => k !== managingVersion)
|
Object.entries(versionsList).filter(
|
||||||
|
([k]) => k !== managingVersion
|
||||||
|
)
|
||||||
)
|
)
|
||||||
const updatedConfig = {
|
)
|
||||||
...prev,
|
|
||||||
list: updatedList
|
|
||||||
}
|
|
||||||
writeVersionsConfig(updatedConfig)
|
|
||||||
return updatedConfig
|
|
||||||
})
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
await exists('game/' + managingVersion, {
|
await exists('game/' + managingVersion, {
|
||||||
@@ -171,18 +162,14 @@ export default function VersionVersionPopup () {
|
|||||||
setPopupMode(1)
|
setPopupMode(1)
|
||||||
|
|
||||||
//uninstall
|
//uninstall
|
||||||
setDownloadedVersionsConfig(prev => {
|
versions?.set(
|
||||||
if (!prev) return prev
|
'list',
|
||||||
const updatedList = Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(prev.list).filter(([k]) => k !== managingVersion)
|
Object.entries(versionsList).filter(
|
||||||
|
([k]) => k !== managingVersion
|
||||||
|
)
|
||||||
)
|
)
|
||||||
const updatedConfig = {
|
)
|
||||||
...prev,
|
|
||||||
list: updatedList
|
|
||||||
}
|
|
||||||
writeVersionsConfig(updatedConfig)
|
|
||||||
return updatedConfig
|
|
||||||
})
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
await exists('game/' + managingVersion, {
|
await exists('game/' + managingVersion, {
|
||||||
@@ -196,7 +183,12 @@ export default function VersionVersionPopup () {
|
|||||||
|
|
||||||
//reinstall
|
//reinstall
|
||||||
setSelectedVersionList([managingVersion])
|
setSelectedVersionList([managingVersion])
|
||||||
downloadVersions([managingVersion])
|
downloadVersions([
|
||||||
|
{
|
||||||
|
id: managingVersion,
|
||||||
|
type: 0
|
||||||
|
}
|
||||||
|
])
|
||||||
}}
|
}}
|
||||||
title="Click to reinstall this game. This will NOT remove any progress or any save files. This WILL uninstall any modifications to the game's executable files."
|
title="Click to reinstall this game. This will NOT remove any progress or any save files. This WILL uninstall any modifications to the game's executable files."
|
||||||
hidden={viewingInfoFromDownloads}
|
hidden={viewingInfoFromDownloads}
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
|
import { verifySignature } from '@/lib/util'
|
||||||
|
import { Mod } from '@/types/Mod'
|
||||||
|
import {
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faClock,
|
||||||
|
faCodeBranch,
|
||||||
|
faDownload,
|
||||||
|
faGlobe,
|
||||||
|
faPeopleGroup,
|
||||||
|
faRefresh,
|
||||||
|
faTrash
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { message } from '@tauri-apps/plugin-dialog'
|
||||||
|
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||||
|
import { fetch } from '@tauri-apps/plugin-http'
|
||||||
|
import { arch, platform } from '@tauri-apps/plugin-os'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export default function ModDownloadsPopup () {
|
||||||
|
const {
|
||||||
|
modsList,
|
||||||
|
serverVersionList,
|
||||||
|
managingVersion,
|
||||||
|
showModInfo,
|
||||||
|
setShowModInfo,
|
||||||
|
downloadVersions,
|
||||||
|
downloadProgress,
|
||||||
|
versions
|
||||||
|
} = useGlobal()
|
||||||
|
|
||||||
|
const [mods, setMods] = useState<Mod[] | 0 | 1>(0)
|
||||||
|
const [tab, setTab] = useState<number>(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!managingVersion) return
|
||||||
|
;(async () => {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://games.lncvrt.xyz/api/launcher/mods?platform=${platform()}&arch=${arch()}&version=${managingVersion}`
|
||||||
|
)
|
||||||
|
const signature = response.headers.get('x-signature') ?? ''
|
||||||
|
const data = await response.json()
|
||||||
|
if (await verifySignature(JSON.stringify(data), signature)) {
|
||||||
|
setMods(data as Mod[])
|
||||||
|
} else {
|
||||||
|
setMods(1)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}, [managingVersion])
|
||||||
|
|
||||||
|
if (!managingVersion || !serverVersionList) return <></>
|
||||||
|
|
||||||
|
const versionInfo = serverVersionList.versions.find(
|
||||||
|
v => v.id == managingVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className='popup-top-button btntheme1 right-2'
|
||||||
|
onClick={async () => {
|
||||||
|
setMods(0)
|
||||||
|
const response = await fetch(
|
||||||
|
`https://games.lncvrt.xyz/api/launcher/mods?platform=${platform()}&arch=${arch()}&version=${managingVersion}`
|
||||||
|
)
|
||||||
|
const signature = response.headers.get('x-signature') ?? ''
|
||||||
|
const data = await response.json()
|
||||||
|
if (await verifySignature(JSON.stringify(data), signature)) {
|
||||||
|
setMods(data as Mod[])
|
||||||
|
} else {
|
||||||
|
setMods(1)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
hidden={typeof mods == 'number' || showModInfo != null}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faRefresh} />
|
||||||
|
</button>
|
||||||
|
<p className='text-xl text-center'>
|
||||||
|
{!showModInfo
|
||||||
|
? versionInfo?.displayName + ' Mod Manager'
|
||||||
|
: showModInfo.name +
|
||||||
|
' by ' +
|
||||||
|
showModInfo.creators[0] +
|
||||||
|
(showModInfo.creators.length > 1
|
||||||
|
? ' + ' + (showModInfo.creators.length - 1) + ' more'
|
||||||
|
: '')}
|
||||||
|
</p>
|
||||||
|
{typeof mods != 'number' && !showModInfo ? (
|
||||||
|
<div className='flex flex-row justify-center items-center -mb-2 mt-0.5 gap-1.5'>
|
||||||
|
<button
|
||||||
|
className={`button ${tab == 0 ? 'btntheme3' : 'btntheme2'}`}
|
||||||
|
onClick={() => setTab(0)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
|
Installed
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`button ${tab == 1 ? 'btntheme3' : 'btntheme2'}`}
|
||||||
|
onClick={() => setTab(1)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faGlobe} />
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className='popup-content h-full'>
|
||||||
|
{typeof mods == 'number' ||
|
||||||
|
(!showModInfo &&
|
||||||
|
mods.filter(v =>
|
||||||
|
tab == 0
|
||||||
|
? Object.keys(modsList).includes(
|
||||||
|
String(versionInfo?.game + '-' + v.id)
|
||||||
|
)
|
||||||
|
: !Object.keys(modsList).includes(
|
||||||
|
String(versionInfo?.game + '-' + v.id)
|
||||||
|
)
|
||||||
|
).length == 0) ? (
|
||||||
|
<p className='text-2xl flex justify-center items-center h-full'>
|
||||||
|
{typeof mods == 'number'
|
||||||
|
? mods == 0
|
||||||
|
? 'Loading'
|
||||||
|
: 'Failed to load mods'
|
||||||
|
: tab == 0
|
||||||
|
? 'No mods installed'
|
||||||
|
: 'No mods available to download'}
|
||||||
|
</p>
|
||||||
|
) : !showModInfo ? (
|
||||||
|
<div className='flex flex-col items-center justify-center gap-2 p-2'>
|
||||||
|
{mods
|
||||||
|
.filter(v =>
|
||||||
|
tab == 0
|
||||||
|
? Object.keys(modsList).includes(
|
||||||
|
String(versionInfo?.game + '-' + v.id)
|
||||||
|
)
|
||||||
|
: !Object.keys(modsList).includes(
|
||||||
|
String(versionInfo?.game + '-' + v.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map(v => {
|
||||||
|
const localVersion =
|
||||||
|
tab == 0
|
||||||
|
? Object.keys(
|
||||||
|
modsList[versionInfo?.game + '-' + v.id] ?? {}
|
||||||
|
)[0]
|
||||||
|
: null
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={v.id}
|
||||||
|
className='bg-(--col3) border border-(--col5) rounded-lg w-full h-16 flex flex-row'
|
||||||
|
>
|
||||||
|
<div className='flex flex-col justify-center h-full px-3 w-fit'>
|
||||||
|
<p>
|
||||||
|
<span className='text-lg'>{v.name}</span>{' '}
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
tab != 0 || v.latestVersion == localVersion
|
||||||
|
? 'text-green-300'
|
||||||
|
: 'text-red-300'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
v{tab == 0 ? localVersion : v.latestVersion}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className='text-green-300'
|
||||||
|
hidden={tab != 0 || v.latestVersion == localVersion}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
(available: v{v.latestVersion})
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p className='text-yellow-200'>
|
||||||
|
Made by {v.creators[0]}
|
||||||
|
{v.creators.length > 1
|
||||||
|
? ' + ' + (v.creators.length - 1) + ' more'
|
||||||
|
: null}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row items-center h-full gap-2 px-3 ml-auto'>
|
||||||
|
<p className='text-green-400' hidden={tab == 0}>
|
||||||
|
<FontAwesomeIcon icon={faDownload} />{' '}
|
||||||
|
{v.downloads.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className='button btntheme3'
|
||||||
|
onClick={async () => {
|
||||||
|
const path =
|
||||||
|
'game/' +
|
||||||
|
versionInfo?.id +
|
||||||
|
'/BepInEx/plugins/' +
|
||||||
|
v.id
|
||||||
|
if (
|
||||||
|
await exists(path, {
|
||||||
|
baseDir: BaseDirectory.AppLocalData
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await remove(path, {
|
||||||
|
baseDir: BaseDirectory.AppLocalData,
|
||||||
|
recursive: true
|
||||||
|
})
|
||||||
|
versions?.set(
|
||||||
|
'mods',
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(modsList).filter(
|
||||||
|
([k]) => k !== versionInfo?.game + '-' + v.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
downloadVersions([
|
||||||
|
{
|
||||||
|
id: managingVersion,
|
||||||
|
type: 2,
|
||||||
|
modDownload: v.latestDownload,
|
||||||
|
gameId: versionInfo?.game,
|
||||||
|
modId: v.id,
|
||||||
|
modVersion: v.latestVersion
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}}
|
||||||
|
hidden={tab != 0 || v.latestVersion == localVersion}
|
||||||
|
disabled={downloadProgress.length != 0}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='button btntheme3'
|
||||||
|
onClick={() => setShowModInfo(v)}
|
||||||
|
>
|
||||||
|
{tab == 0 ? 'View' : 'Get'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='flex flex-col h-full w-full items-center'>
|
||||||
|
<div className='flex flex-col p-2 h-fit w-fit bg-(--col3) border border-(--col5) rounded-lg m-2 items-center'>
|
||||||
|
<p>
|
||||||
|
<FontAwesomeIcon icon={faDownload} className='text-green-400' />{' '}
|
||||||
|
Downloads {showModInfo.downloads.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<FontAwesomeIcon icon={faClock} /> Released{' '}
|
||||||
|
{new Intl.DateTimeFormat(undefined).format(
|
||||||
|
showModInfo.released * 1000
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<FontAwesomeIcon icon={faClock} /> Updated{' '}
|
||||||
|
{new Intl.DateTimeFormat(undefined).format(
|
||||||
|
showModInfo.updated * 1000
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<FontAwesomeIcon icon={faCodeBranch} /> Version{' '}
|
||||||
|
{showModInfo.latestVersion}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col p-2 bg-(--col3) border border-(--col5) rounded-lg h-full w-[calc(100%-16px)]'>
|
||||||
|
{showModInfo.description
|
||||||
|
? atob(showModInfo.description)
|
||||||
|
: '(No description added)'}
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row h-fit w-full gap-2 justify-center my-2'>
|
||||||
|
{Object.keys(modsList).includes(
|
||||||
|
String(versionInfo?.game + '-' + showModInfo.id)
|
||||||
|
) ? (
|
||||||
|
<button
|
||||||
|
className='button btntheme2 w-fit'
|
||||||
|
disabled={downloadProgress.length != 0}
|
||||||
|
onClick={async () => {
|
||||||
|
const path =
|
||||||
|
'game/' +
|
||||||
|
versionInfo?.id +
|
||||||
|
'/BepInEx/plugins/' +
|
||||||
|
showModInfo.id
|
||||||
|
if (
|
||||||
|
await exists(path, {
|
||||||
|
baseDir: BaseDirectory.AppLocalData
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await remove(path, {
|
||||||
|
baseDir: BaseDirectory.AppLocalData,
|
||||||
|
recursive: true
|
||||||
|
})
|
||||||
|
versions?.set(
|
||||||
|
'mods',
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(modsList).filter(
|
||||||
|
([k]) =>
|
||||||
|
k !== versionInfo?.game + '-' + showModInfo.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} className='text-red-400' />{' '}
|
||||||
|
Uninstall
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className='button btntheme2 w-fit'
|
||||||
|
onClick={() => {
|
||||||
|
downloadVersions([
|
||||||
|
{
|
||||||
|
id: managingVersion,
|
||||||
|
type: 2,
|
||||||
|
modDownload: showModInfo.latestDownload,
|
||||||
|
gameId: versionInfo?.game,
|
||||||
|
modId: showModInfo.id,
|
||||||
|
modVersion: showModInfo.latestVersion
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}}
|
||||||
|
disabled={downloadProgress.length != 0}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faDownload}
|
||||||
|
className='text-green-400'
|
||||||
|
/>{' '}
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className='button btntheme2 w-fit'
|
||||||
|
onClick={async () => {
|
||||||
|
await message(
|
||||||
|
!showModInfo.changelog
|
||||||
|
? '(No changelog provided)'
|
||||||
|
: atob(showModInfo?.changelog ?? ''),
|
||||||
|
{
|
||||||
|
title: 'Changelog for ' + showModInfo.name,
|
||||||
|
kind: 'info'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
color='lightgray'
|
||||||
|
/>{' '}
|
||||||
|
View changelog
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='button btntheme2 w-fit'
|
||||||
|
hidden={showModInfo.creators.length < 2}
|
||||||
|
onClick={async () => {
|
||||||
|
if (!versionInfo) return
|
||||||
|
await message(showModInfo.creators.join(', '), {
|
||||||
|
title: 'Creators for ' + showModInfo.name,
|
||||||
|
kind: 'info'
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPeopleGroup} color='lightgray' /> View
|
||||||
|
all creators
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
|
import DOMPurify from 'dompurify'
|
||||||
|
|
||||||
|
export default function VersionChangelogPopup () {
|
||||||
|
const { serverVersionList, managingVersion } = useGlobal()
|
||||||
|
|
||||||
|
if (!managingVersion) return <></>
|
||||||
|
|
||||||
|
const versionInfo = serverVersionList?.versions.find(
|
||||||
|
vf => vf.id == managingVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className='text-xl text-center'>
|
||||||
|
Viewing changelog {versionInfo?.displayName}
|
||||||
|
</p>
|
||||||
|
<div className='popup-content h-full w-full p-2'>
|
||||||
|
<p
|
||||||
|
className='whitespace-pre-line'
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: DOMPurify.sanitize(
|
||||||
|
atob(versionInfo?.changelog ?? '')
|
||||||
|
.replaceAll('- ', '• ')
|
||||||
|
.replaceAll(/`([^`]+)`/g, '<code>$1</code>')
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
></p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
import { useGlobal } from '@/app/GlobalProvider'
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
import { faAdd, faInfo, faRemove } from '@fortawesome/free-solid-svg-icons'
|
import { faAdd, faInfo, faRemove } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
|
||||||
export default function VersionsDownloadPopup () {
|
export default function VersionsDownloadPopup () {
|
||||||
const {
|
const {
|
||||||
|
serverVersionList,
|
||||||
|
getSpecialVersionsList,
|
||||||
selectedVersionList,
|
selectedVersionList,
|
||||||
setSelectedVersionList,
|
setSelectedVersionList,
|
||||||
setManagingVersion,
|
setManagingVersion,
|
||||||
setPopupMode,
|
setPopupMode,
|
||||||
getSpecialVersionsList,
|
|
||||||
selectedGame,
|
selectedGame,
|
||||||
setViewingInfoFromDownloads,
|
setViewingInfoFromDownloads,
|
||||||
downloadedVersionsConfig,
|
downloadVersions
|
||||||
downloadVersions,
|
|
||||||
getGameInfo
|
|
||||||
} = useGlobal()
|
} = useGlobal()
|
||||||
if (!selectedGame) return <></>
|
|
||||||
|
const game = serverVersionList?.games.find(g => g.id === selectedGame)
|
||||||
|
|
||||||
|
if (!selectedGame || !game) return <></>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -77,19 +79,18 @@ export default function VersionsDownloadPopup () {
|
|||||||
<button
|
<button
|
||||||
className='button btntheme1 w-fit mt-2 -mb-4'
|
className='button btntheme1 w-fit mt-2 -mb-4'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (downloadedVersionsConfig) {
|
downloadVersions(
|
||||||
downloadVersions(selectedVersionList)
|
selectedVersionList.map(versionId => ({
|
||||||
setPopupMode(1)
|
id: versionId,
|
||||||
}
|
type: 0
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
setPopupMode(1)
|
||||||
}}
|
}}
|
||||||
disabled={selectedVersionList.length == 0}
|
disabled={selectedVersionList.length == 0}
|
||||||
title={
|
title={`Download ${selectedVersionList.length} version${
|
||||||
selectedVersionList.length == 0
|
selectedVersionList.length == 1 ? '' : 's'
|
||||||
? 'Select at least one version to download'
|
} of ${game.name}`}
|
||||||
: `Download ${selectedVersionList.length} version${
|
|
||||||
selectedVersionList.length == 1 ? '' : 's'
|
|
||||||
} of ${getGameInfo(selectedGame)?.name}`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Download {selectedVersionList.length} version
|
Download {selectedVersionList.length} version
|
||||||
{selectedVersionList.length == 1 ? '' : 's'}
|
{selectedVersionList.length == 1 ? '' : 's'}
|
||||||
@@ -8,11 +8,10 @@ import {
|
|||||||
SetStateAction
|
SetStateAction
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { DownloadProgress } from '@/types/DownloadProgress'
|
import { DownloadProgress } from '@/types/DownloadProgress'
|
||||||
import { VersionsConfig } from '@/types/VersionsConfig'
|
|
||||||
import { NormalConfig } from '@/types/NormalConfig'
|
|
||||||
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
|
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
|
||||||
|
import { Mod } from '@/types/Mod'
|
||||||
|
import { Store } from '@tauri-apps/plugin-store'
|
||||||
import { GameVersion } from '@/types/GameVersion'
|
import { GameVersion } from '@/types/GameVersion'
|
||||||
import { Game } from '@/types/Game'
|
|
||||||
|
|
||||||
type GlobalCtxType = {
|
type GlobalCtxType = {
|
||||||
serverVersionList: ServerVersionsResponse | null
|
serverVersionList: ServerVersionsResponse | null
|
||||||
@@ -26,31 +25,40 @@ type GlobalCtxType = {
|
|||||||
setPopupMode: Dispatch<SetStateAction<number | null>>
|
setPopupMode: Dispatch<SetStateAction<number | null>>
|
||||||
fadeOut: boolean
|
fadeOut: boolean
|
||||||
setFadeOut: Dispatch<SetStateAction<boolean>>
|
setFadeOut: Dispatch<SetStateAction<boolean>>
|
||||||
downloadedVersionsConfig: VersionsConfig | null
|
|
||||||
setDownloadedVersionsConfig: Dispatch<SetStateAction<VersionsConfig | null>>
|
|
||||||
normalConfig: NormalConfig | null
|
|
||||||
setNormalConfig: Dispatch<SetStateAction<NormalConfig | null>>
|
|
||||||
managingVersion: string | null
|
managingVersion: string | null
|
||||||
setManagingVersion: Dispatch<SetStateAction<string | null>>
|
setManagingVersion: Dispatch<SetStateAction<string | null>>
|
||||||
setSelectedGame: Dispatch<SetStateAction<number | null>>
|
setSelectedGame: Dispatch<SetStateAction<number | null>>
|
||||||
getVersionInfo: (id: string | undefined) => GameVersion | undefined
|
|
||||||
getGameInfo: (game: number | undefined) => Game | undefined
|
|
||||||
getListOfGames(): Game[]
|
|
||||||
getVersionsAmountData: (gameId: number) => {
|
|
||||||
installed: number
|
|
||||||
total: number
|
|
||||||
} | null
|
|
||||||
viewingInfoFromDownloads: boolean
|
viewingInfoFromDownloads: boolean
|
||||||
version: string | null
|
version: string | null
|
||||||
downloadVersions: (list: string[]) => Promise<void>
|
downloadVersions: (
|
||||||
|
list: Array<{
|
||||||
|
id: string
|
||||||
|
type: 0 | 1 | 2
|
||||||
|
modDownload?: number
|
||||||
|
gameId?: number
|
||||||
|
modId?: number
|
||||||
|
modVersion?: string
|
||||||
|
}>
|
||||||
|
) => Promise<void>
|
||||||
category: number
|
category: number
|
||||||
setCategory: Dispatch<SetStateAction<number>>
|
setCategory: Dispatch<SetStateAction<number>>
|
||||||
downloadQueue: string[]
|
downloadQueue: string[]
|
||||||
setDownloadQueue: Dispatch<SetStateAction<string[]>>
|
setDownloadQueue: Dispatch<SetStateAction<string[]>>
|
||||||
closePopup: () => void
|
closePopup: () => void
|
||||||
getSpecialVersionsList(game?: number | undefined): GameVersion[]
|
|
||||||
selectedGame: number | null
|
selectedGame: number | null
|
||||||
setViewingInfoFromDownloads: Dispatch<SetStateAction<boolean>>
|
setViewingInfoFromDownloads: Dispatch<SetStateAction<boolean>>
|
||||||
|
showModInfo: Mod | null
|
||||||
|
setShowModInfo: Dispatch<SetStateAction<Mod | null>>
|
||||||
|
getSpecialVersionsList: (game?: number) => GameVersion[]
|
||||||
|
settings: Store | null
|
||||||
|
versions: Store | null
|
||||||
|
notificationsAllowed: boolean
|
||||||
|
sidebarAlwaysShowGames: boolean
|
||||||
|
linuxUseWine: boolean
|
||||||
|
linuxWineCommand: string
|
||||||
|
theme: string
|
||||||
|
versionsList: Record<string, number>
|
||||||
|
modsList: Record<string, Record<string, number>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const GlobalCtx = createContext<GlobalCtxType | null>(null)
|
const GlobalCtx = createContext<GlobalCtxType | null>(null)
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
html {
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-(--col0) text-white select-none;
|
@apply bg-(--col0) text-white select-none;
|
||||||
}
|
}
|
||||||
@@ -14,6 +18,7 @@ body {
|
|||||||
--col6: rgb(96, 96, 96);
|
--col6: rgb(96, 96, 96);
|
||||||
--col7: rgb(112, 112, 112);
|
--col7: rgb(112, 112, 112);
|
||||||
--col8: rgb(128, 128, 128);
|
--col8: rgb(128, 128, 128);
|
||||||
|
--col9: rgb(144, 144, 144);
|
||||||
}
|
}
|
||||||
|
|
||||||
.red-theme {
|
.red-theme {
|
||||||
@@ -26,6 +31,7 @@ body {
|
|||||||
--col6: rgb(120, 96, 96);
|
--col6: rgb(120, 96, 96);
|
||||||
--col7: rgb(136, 112, 112);
|
--col7: rgb(136, 112, 112);
|
||||||
--col8: rgb(152, 128, 128);
|
--col8: rgb(152, 128, 128);
|
||||||
|
--col9: rgb(168, 144, 144);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blue-theme {
|
.blue-theme {
|
||||||
@@ -38,6 +44,7 @@ body {
|
|||||||
--col6: rgb(96, 96, 120);
|
--col6: rgb(96, 96, 120);
|
||||||
--col7: rgb(112, 112, 136);
|
--col7: rgb(112, 112, 136);
|
||||||
--col8: rgb(128, 128, 152);
|
--col8: rgb(128, 128, 152);
|
||||||
|
--col9: rgb(144, 144, 168);
|
||||||
}
|
}
|
||||||
|
|
||||||
.purple-theme {
|
.purple-theme {
|
||||||
@@ -50,6 +57,7 @@ body {
|
|||||||
--col6: rgb(108, 96, 120);
|
--col6: rgb(108, 96, 120);
|
||||||
--col7: rgb(124, 112, 136);
|
--col7: rgb(124, 112, 136);
|
||||||
--col8: rgb(140, 128, 152);
|
--col8: rgb(140, 128, 152);
|
||||||
|
--col9: rgb(156, 144, 168);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
@@ -126,8 +134,8 @@ body {
|
|||||||
@apply absolute;
|
@apply absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-button {
|
.popup-top-button {
|
||||||
@apply flex justify-center items-center absolute text-2xl text-gray-200 hover:text-white cursor-pointer h-12 w-12 p-3 rounded-lg left-2 top-2 transition-colors border;
|
@apply flex justify-center items-center absolute text-2xl text-gray-200 hover:text-white cursor-pointer h-12 w-12 p-3 rounded-lg top-2 transition-colors border;
|
||||||
}
|
}
|
||||||
|
|
||||||
*:focus {
|
*:focus {
|
||||||
@@ -141,3 +149,7 @@ body {
|
|||||||
.entry-info-item {
|
.entry-info-item {
|
||||||
@apply flex flex-row items-center gap-1 border text-gray-300 py-1 px-2 rounded-md w-fit text-[16px] transition-colors cursor-pointer;
|
@apply flex flex-row items-center gap-1 border text-gray-300 py-1 px-2 rounded-md w-fit text-[16px] transition-colors cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
@apply bg-(--col4) border border-(--col6) rounded-lg px-1 py-0.5 text-sm;
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -4,11 +4,24 @@ export class DownloadProgress {
|
|||||||
public progress: number,
|
public progress: number,
|
||||||
public progressBytes: number,
|
public progressBytes: number,
|
||||||
public failed: boolean,
|
public failed: boolean,
|
||||||
|
public downloading: boolean,
|
||||||
|
public paused: boolean,
|
||||||
|
public canceled: boolean,
|
||||||
public queued: boolean,
|
public queued: boolean,
|
||||||
public hash_checking: boolean,
|
public hash_checking: boolean,
|
||||||
public finishing: boolean,
|
|
||||||
public speed: number,
|
public speed: number,
|
||||||
public size: number,
|
public size: number,
|
||||||
public etaSecs: number
|
public etaSecs: number,
|
||||||
|
public unzipping: boolean,
|
||||||
|
public unzipTotal: number,
|
||||||
|
public unzipped: number,
|
||||||
|
public url: string | null,
|
||||||
|
public executable: string | null,
|
||||||
|
public hash: string | null,
|
||||||
|
public type: 0 | 1 | 2,
|
||||||
|
public modDownload: number | null,
|
||||||
|
public modGame: number | null,
|
||||||
|
public modId: number | null,
|
||||||
|
public modVersion: string | null
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ export interface GameVersion {
|
|||||||
wine: number | undefined
|
wine: number | undefined
|
||||||
category: number
|
category: number
|
||||||
lastRevision: number
|
lastRevision: number
|
||||||
download: number
|
download: number | undefined
|
||||||
executable: string
|
executable: string
|
||||||
|
modSupportDownload: number | undefined
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export interface Mod {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
description: string | null
|
||||||
|
creators: string[]
|
||||||
|
downloads: number
|
||||||
|
released: number
|
||||||
|
updated: number
|
||||||
|
latestVersion: string
|
||||||
|
latestDownload: number
|
||||||
|
changelog: string | null
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { SettingsType } from './SettingsType'
|
|
||||||
|
|
||||||
type NormalConfigData = {
|
|
||||||
version: string
|
|
||||||
settings?: Partial<SettingsType>
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NormalConfig {
|
|
||||||
constructor (
|
|
||||||
public version: string,
|
|
||||||
public settings: SettingsType = new SettingsType()
|
|
||||||
) {}
|
|
||||||
|
|
||||||
static import (data: NormalConfigData) {
|
|
||||||
const cfg = new NormalConfig(data.version)
|
|
||||||
if (data.settings) {
|
|
||||||
cfg.settings = { ...cfg.settings, ...data.settings }
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export class SettingsType {
|
|
||||||
constructor (
|
|
||||||
public allowNotifications: boolean = true,
|
|
||||||
public alwaysShowGamesInSidebar: boolean = true,
|
|
||||||
public useWineOnUnixWhenNeeded: boolean = false,
|
|
||||||
public wineOnUnixCommand: string = 'wine %path%',
|
|
||||||
public theme: number = 0
|
|
||||||
) {}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export type VersionsConfigData = {
|
|
||||||
version: string
|
|
||||||
list: Record<string, number>
|
|
||||||
}
|
|
||||||
|
|
||||||
export class VersionsConfig {
|
|
||||||
constructor (
|
|
||||||
public version: string,
|
|
||||||
public list: Record<string, number> = {}
|
|
||||||
) {}
|
|
||||||
|
|
||||||
static import (data: VersionsConfigData) {
|
|
||||||
const cfg = new VersionsConfig(data.version)
|
|
||||||
cfg.list = { ...data.list }
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user