Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d9c6147c5
|
||
|
|
48c8365b7c
|
||
|
|
8884d08c7c
|
||
|
|
b516a9cc43
|
||
|
|
6b7b2b198a
|
||
|
|
fbf87dceba
|
||
|
|
9ae28690a7
|
||
|
|
f83cc118fe
|
||
|
|
19788a8a3c
|
||
|
|
e066dd0681
|
||
|
|
29b3c3c58b
|
||
|
|
a8390216d0
|
||
|
|
513f1a0c16
|
||
|
|
e7e624f042
|
||
|
|
654405e016
|
||
|
|
c461a0a459
|
||
|
|
16ce04ba7a
|
||
|
|
1700d127c3
|
||
|
|
7c47b32c68
|
||
|
|
56781b5d1a
|
||
|
|
6ce7c2a8ae
|
||
|
|
15dd9ad013
|
||
|
|
217222a22b
|
||
|
|
39da88a856
|
||
|
|
29c90d2f06
|
||
|
|
467dd1b0a1
|
||
|
|
a1a69c9ac5
|
||
|
|
0163e09b02
|
||
|
|
fbb4b15971
|
||
|
|
77933a2298
|
||
|
|
7209a36752
|
||
|
|
f00b78996a
|
||
|
|
38779c1715
|
||
|
|
fa67d26fe9
|
||
|
|
81d47ea71d
|
||
|
|
70433b76ab
|
||
|
|
105913503c
|
||
|
|
2ad54f976b
|
||
|
|
d8d59febbb
|
||
|
|
a618c76292
|
||
|
|
118fad49d8
|
||
|
|
62c0f84d14
|
||
|
|
4a96911885
|
||
|
|
51cd3c3620
|
||
|
|
dfbfac33d3
|
||
|
|
03ec5a220a
|
||
|
|
abebc0bb08
|
||
|
|
3c7487f20f
|
||
|
|
97231c6b38
|
||
|
|
a93e4ab521
|
||
|
|
e7c48b182f
|
||
|
|
16dd3fd613
|
||
|
|
c91303d194
|
||
|
|
a76e81789b
|
||
|
|
c909973aab
|
||
|
|
28f694e4e7
|
||
|
|
508e4d1640
|
||
|
|
41a66eaf86
|
||
|
|
ac4b76fa46
|
||
|
|
2e02275c29
|
||
|
|
dbaac6a6a6
|
||
|
|
68525f161e
|
||
|
|
d386454e91
|
||
|
|
6e05aac871
|
||
|
|
fbe3feec9d
|
||
|
|
1c540ad35b
|
No files matched your search
@@ -0,0 +1 @@
|
||||
NEXT_PUBLIC_PUBLIC_SIGNING_KEY=
|
||||
@@ -32,6 +32,7 @@ yarn-error.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
@@ -5,36 +5,36 @@
|
||||
"": {
|
||||
"name": "lncvrt-games-launcher",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "7.1.0",
|
||||
"@fortawesome/free-brands-svg-icons": "7.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "7.1.0",
|
||||
"@fortawesome/react-fontawesome": "3.1.1",
|
||||
"@fortawesome/fontawesome-svg-core": "7.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "7.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "7.2.0",
|
||||
"@fortawesome/react-fontawesome": "3.2.0",
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "2.6.0",
|
||||
"@tauri-apps/plugin-fs": "2.4.5",
|
||||
"@tauri-apps/plugin-http": "2.5.7",
|
||||
"@tauri-apps/plugin-notification": "2.3.3",
|
||||
"@tauri-apps/plugin-opener": "2.5.3",
|
||||
"@tauri-apps/plugin-os": "2.3.2",
|
||||
"@tauri-apps/plugin-window-state": "2.4.1",
|
||||
"axios": "1.13.4",
|
||||
"next": "16.1.6",
|
||||
"pretty-bytes": "7.1.0",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.3.3",
|
||||
"@tailwindcss/postcss": "4.1.18",
|
||||
"@eslint/eslintrc": "3.3.4",
|
||||
"@tailwindcss/postcss": "4.2.1",
|
||||
"@tauri-apps/cli": "2.10.0",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/node": "25.2.0",
|
||||
"@types/react": "19.2.10",
|
||||
"@types/node": "25.3.2",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"crypto-js": "4.2.0",
|
||||
"eslint": "9.39.2",
|
||||
"eslint": "9.39.3",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"tailwindcss": "4.1.18",
|
||||
"tailwindcss": "4.2.1",
|
||||
"typescript": "5.9.3",
|
||||
},
|
||||
},
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.0", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ=="],
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
|
||||
@@ -90,23 +90,23 @@
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.4", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.3", "strip-json-comments": "^3.1.1" } }, "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="],
|
||||
"@eslint/js": ["@eslint/js@9.39.3", "", {}, "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@7.1.0", "", {}, "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA=="],
|
||||
"@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@7.2.0", "", {}, "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw=="],
|
||||
|
||||
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA=="],
|
||||
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@7.2.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.2.0" } }, "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q=="],
|
||||
|
||||
"@fortawesome/free-brands-svg-icons": ["@fortawesome/free-brands-svg-icons@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-9byUd9bgNfthsZAjBl6GxOu1VPHgBuRUP9juI7ZoM98h8xNPTCTagfwUFyYscdZq4Hr7gD1azMfM9s5tIWKZZA=="],
|
||||
"@fortawesome/free-brands-svg-icons": ["@fortawesome/free-brands-svg-icons@7.2.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.2.0" } }, "sha512-VNG8xqOip1JuJcC3zsVsKRQ60oXG9+oYNDCosjoU/H9pgYmLTEwWw8pE0jhPz/JWdHeUuK6+NQ3qsM4gIbdbYQ=="],
|
||||
|
||||
"@fortawesome/free-solid-svg-icons": ["@fortawesome/free-solid-svg-icons@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA=="],
|
||||
"@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.1.1", "", { "peerDependencies": { "@fortawesome/fontawesome-svg-core": "~6 || ~7", "react": "^18.0.0 || ^19.0.0" } }, "sha512-EDllr9hpodc21odmUywHS1alXNiCd4E8sp5GJ5s7wYINz8vSmMiNWpALTiuYODb865YyQ/NlyiN4mbXp7HCNqg=="],
|
||||
"@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=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
@@ -210,35 +210,35 @@
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
||||
"@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/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
|
||||
"@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-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
|
||||
"@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-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
|
||||
"@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-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
|
||||
"@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-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
|
||||
"@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-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
|
||||
"@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-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="],
|
||||
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="],
|
||||
"@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=="],
|
||||
|
||||
"@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
||||
|
||||
@@ -272,6 +272,8 @@
|
||||
|
||||
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.5", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA=="],
|
||||
|
||||
"@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.7", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-+F2lEH/c9b0zSsOXKq+5hZNcd9F4IIKCK1T17RqMwpCmVnx2aoqY8yIBccCd25HTYUb3j6NPVbRax/m00hKG8A=="],
|
||||
|
||||
"@tauri-apps/plugin-notification": ["@tauri-apps/plugin-notification@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg=="],
|
||||
|
||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ=="],
|
||||
@@ -290,31 +292,31 @@
|
||||
|
||||
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
|
||||
|
||||
"@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="],
|
||||
"@types/node": ["@types/node@25.3.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.10", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw=="],
|
||||
"@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=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.54.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/type-utils": "8.54.0", "@typescript-eslint/utils": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ=="],
|
||||
"@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=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.54.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA=="],
|
||||
"@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/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="],
|
||||
"@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/scope-manager": ["@typescript-eslint/scope-manager@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0" } }, "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg=="],
|
||||
"@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/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.54.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw=="],
|
||||
"@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/type-utils": ["@typescript-eslint/type-utils@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA=="],
|
||||
"@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/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="],
|
||||
"@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/utils": ["@typescript-eslint/utils@8.54.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA=="],
|
||||
"@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/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
"@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=="],
|
||||
|
||||
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
|
||||
|
||||
@@ -354,11 +356,11 @@
|
||||
|
||||
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
@@ -386,19 +388,15 @@
|
||||
|
||||
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"axios": ["axios@1.13.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="],
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
@@ -414,7 +412,7 @@
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001767", "", {}, "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
@@ -424,8 +422,6 @@
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
@@ -452,19 +448,17 @@
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||
|
||||
"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.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
||||
"enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -486,7 +480,7 @@
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@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.12.4", "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.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="],
|
||||
"eslint": ["eslint@9.39.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.3", "@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.12.4", "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.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -540,12 +534,8 @@
|
||||
|
||||
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
|
||||
|
||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||
|
||||
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
|
||||
@@ -562,7 +552,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-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="],
|
||||
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
@@ -684,29 +674,29 @@
|
||||
|
||||
"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.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
"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-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
|
||||
"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-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
@@ -724,11 +714,7 @@
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
"minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
@@ -742,6 +728,8 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"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.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
@@ -790,8 +778,6 @@
|
||||
|
||||
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
@@ -874,7 +860,7 @@
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
|
||||
"tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
|
||||
@@ -900,11 +886,11 @@
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.54.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ=="],
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
|
||||
"unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="],
|
||||
|
||||
@@ -950,9 +936,11 @@
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"eslint-config-next/globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
|
||||
|
||||
@@ -962,18 +950,20 @@
|
||||
|
||||
"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.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
|
||||
"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=="],
|
||||
|
||||
"is-bun-module/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
"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=="],
|
||||
|
||||
"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.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
"@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/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ const eslintConfig = defineConfig([
|
||||
'.next/**',
|
||||
'out/**',
|
||||
'build/**',
|
||||
'next-env.d.ts'
|
||||
'next-env.d.ts',
|
||||
'src-tauri/**'
|
||||
])
|
||||
])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "lncvrt-games-launcher",
|
||||
"private": true,
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -11,36 +11,36 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "7.1.0",
|
||||
"@fortawesome/free-brands-svg-icons": "7.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "7.1.0",
|
||||
"@fortawesome/react-fontawesome": "3.1.1",
|
||||
"@fortawesome/fontawesome-svg-core": "7.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "7.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "7.2.0",
|
||||
"@fortawesome/react-fontawesome": "3.2.0",
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "2.6.0",
|
||||
"@tauri-apps/plugin-fs": "2.4.5",
|
||||
"@tauri-apps/plugin-http": "2.5.7",
|
||||
"@tauri-apps/plugin-notification": "2.3.3",
|
||||
"@tauri-apps/plugin-opener": "2.5.3",
|
||||
"@tauri-apps/plugin-os": "2.3.2",
|
||||
"@tauri-apps/plugin-window-state": "2.4.1",
|
||||
"axios": "1.13.4",
|
||||
"next": "16.1.6",
|
||||
"pretty-bytes": "7.1.0",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.3.3",
|
||||
"@tailwindcss/postcss": "4.1.18",
|
||||
"@eslint/eslintrc": "3.3.4",
|
||||
"@tailwindcss/postcss": "4.2.1",
|
||||
"@tauri-apps/cli": "2.10.0",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/node": "25.2.0",
|
||||
"@types/react": "19.2.10",
|
||||
"@types/node": "25.3.2",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"crypto-js": "4.2.0",
|
||||
"eslint": "9.39.2",
|
||||
"eslint": "9.39.3",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"tailwindcss": "4.1.18",
|
||||
"tailwindcss": "4.2.1",
|
||||
"typescript": "5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lncvrt-games-launcher"
|
||||
version = "1.5.4"
|
||||
version = "1.6.4"
|
||||
authors = ["Lncvrt"]
|
||||
edition = "2024"
|
||||
|
||||
@@ -9,32 +9,36 @@ name = "lncvrt_games_launcher_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.5.4", features = [] }
|
||||
tauri-build = { version = "2.5.5", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.10.1", features = ["macos-private-api"] }
|
||||
tauri = { version = "2.10.2", features = ["macos-private-api"] }
|
||||
tauri-plugin-opener = "2.5.3"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
tauri-plugin-os = "2.3.2"
|
||||
reqwest = { version = "0.13.1", default-features = false, features = ["stream", "rustls"] }
|
||||
reqwest = { version = "0.13.2", default-features = false, features = ["stream", "rustls"] }
|
||||
tokio = "1.49.0"
|
||||
futures-util = { version = "0.3.31", features = ["io"] }
|
||||
futures-util = { version = "0.3.32", features = ["io"] }
|
||||
tauri-plugin-decorum = "1.1.1"
|
||||
tauri-plugin-fs = "2.4.5"
|
||||
zip = "7.2.0"
|
||||
libc = "0.2.180"
|
||||
zip = "8.1.0"
|
||||
libc = "0.2.182"
|
||||
tauri-plugin-dialog = "2.6.0"
|
||||
tauri-plugin-notification = "2.3.3"
|
||||
sysinfo = "0.38.0"
|
||||
sysinfo = "0.38.2"
|
||||
sha2 = "0.10.9"
|
||||
tauri-plugin-clipboard-manager = "2.3.2"
|
||||
tauri-plugin-prevent-default = "4.0.4"
|
||||
openssl = { version = "0.10.75", features = ["v110", "vendored"] }
|
||||
base64 = "0.22.1"
|
||||
tauri-plugin-http = "2.5.7"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
shlex = "1.3.0"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-single-instance = "2.3.7"
|
||||
tauri-plugin-single-instance = "2.4.0"
|
||||
tauri-plugin-window-state = "2.4.1"
|
||||
|
||||
|
||||
@@ -32,6 +32,26 @@
|
||||
"allow": [
|
||||
{ "path": "$APPLOCALDATA/game/**" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "opener:allow-open-path",
|
||||
"allow": [
|
||||
{ "path": "$APPLOCALDATA/game/**" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [
|
||||
{ "url": "https://games.lncvrt.xyz/api/launcher/latest" },
|
||||
{ "url": "https://games.lncvrt.xyz/api/launcher/versions" },
|
||||
{ "url": "https://games.lncvrt.xyz/api/launcher/download" },
|
||||
{ "url": "https://games.lncvrt.xyz/api/berrydash/account" },
|
||||
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/score" },
|
||||
{ "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/legacy" },
|
||||
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/total" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 248 KiB |
@@ -1,4 +1,7 @@
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use futures_util::stream::StreamExt;
|
||||
use openssl::pkey::PKey;
|
||||
use openssl::sign::Verifier;
|
||||
use sha2::{Digest, Sha512};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -12,9 +15,8 @@ use std::{
|
||||
};
|
||||
use sysinfo::System;
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use tauri_plugin_os::platform;
|
||||
use tauri_plugin_prevent_default::Flags;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::{io::AsyncWriteExt, time::timeout};
|
||||
use zip::ZipArchive;
|
||||
@@ -22,14 +24,16 @@ use zip::ZipArchive;
|
||||
#[cfg(target_os = "windows")]
|
||||
use tauri_plugin_decorum::WebviewWindowExt;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
#[allow(unused)]
|
||||
fn is_running_by_path(path: &Path) -> bool {
|
||||
let sys = System::new_all();
|
||||
let target = path.canonicalize().ok();
|
||||
if target.is_none() {
|
||||
return false;
|
||||
}
|
||||
let target = target.unwrap();
|
||||
let target = match path.canonicalize() {
|
||||
Ok(t) => t,
|
||||
Err(_) => return false,
|
||||
};
|
||||
sys.processes().values().any(|proc| {
|
||||
proc.exe()
|
||||
.and_then(|exe| exe.canonicalize().ok())
|
||||
@@ -37,23 +41,47 @@ fn is_running_by_path(path: &Path) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn should_skip(name: &str) -> bool {
|
||||
name.starts_with("__MACOSX/")
|
||||
|| name == "__MACOSX"
|
||||
|| name.ends_with("/.DS_Store")
|
||||
|| name.ends_with(".DS_Store")
|
||||
}
|
||||
|
||||
async fn unzip_to_dir(zip_path: PathBuf, out_dir: PathBuf) -> String {
|
||||
let res = tauri::async_runtime::spawn_blocking(move || {
|
||||
let file = File::open(zip_path)?;
|
||||
let mut archive = ZipArchive::new(BufReader::new(file))?;
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i)?;
|
||||
let outpath = out_dir.join(file.name());
|
||||
let mut entry = archive.by_index(i)?;
|
||||
let name = entry.name();
|
||||
|
||||
if file.is_dir() {
|
||||
if should_skip(name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let outpath = out_dir.join(name);
|
||||
|
||||
if entry.is_dir() {
|
||||
create_dir_all(&outpath)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
if let Some(mode) = entry.unix_mode() {
|
||||
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?;
|
||||
}
|
||||
} else {
|
||||
if let Some(parent) = outpath.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let mut outfile = File::create(&outpath)?;
|
||||
copy(&mut file, &mut outfile)?;
|
||||
copy(&mut entry, &mut outfile)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
if let Some(mode) = entry.unix_mode() {
|
||||
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,12 +97,10 @@ async fn unzip_to_dir(zip_path: PathBuf, out_dir: PathBuf) -> String {
|
||||
|
||||
#[tauri::command]
|
||||
fn folder_size(app: AppHandle, version: String) -> String {
|
||||
let path = app
|
||||
.path()
|
||||
.app_local_data_dir()
|
||||
.unwrap()
|
||||
.join("game")
|
||||
.join(&version);
|
||||
let path = match app.path().app_local_data_dir() {
|
||||
Ok(p) => p.join("game").join(&version),
|
||||
Err(_) => return "-1".to_string(),
|
||||
};
|
||||
fn inner(path: &Path) -> u64 {
|
||||
let mut size = 0;
|
||||
if let Ok(entries) = fs::read_dir(path) {
|
||||
@@ -108,48 +134,117 @@ async fn download(
|
||||
executable: String,
|
||||
hash: String,
|
||||
) -> String {
|
||||
println!("[download] Starting download for '{}' from '{}'", name, url);
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let resp = match client.get(&url).send().await {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
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 = app.path().app_local_data_dir().unwrap().join("downloads");
|
||||
let game_path = app.path().app_local_data_dir().unwrap().join("game");
|
||||
let downloads_path = match app.path().app_local_data_dir() {
|
||||
Ok(p) => {
|
||||
let path = p.join("downloads");
|
||||
println!("[download] Downloads path: {:?}", path);
|
||||
path
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"[download] ERROR: Failed to resolve app_local_data_dir: {}",
|
||||
e
|
||||
);
|
||||
return "-1".to_string();
|
||||
}
|
||||
};
|
||||
let game_path = match app.path().app_local_data_dir() {
|
||||
Ok(p) => {
|
||||
let path = p.join("game");
|
||||
println!("[download] Game path: {:?}", path);
|
||||
path
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[download] ERROR: Failed to resolve game path: {}", e);
|
||||
return "-1".to_string();
|
||||
}
|
||||
};
|
||||
|
||||
let download_part_path = downloads_path.join(format!("{}.part", 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;
|
||||
|
||||
if let Ok(true) = tokio::fs::try_exists(&game_path.join(name.clone())).await {
|
||||
println!(
|
||||
"[download] Existing game dir found, removing: {:?}",
|
||||
game_path.join(&name)
|
||||
);
|
||||
let _ = tokio::fs::remove_dir_all(&game_path.join(name.clone())).await;
|
||||
}
|
||||
|
||||
println!("[download] Creating game dir: {:?}", game_path.join(&name));
|
||||
let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await;
|
||||
let mut file = tokio::fs::File::create(download_part_path.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut file = match tokio::fs::File::create(download_part_path.clone()).await {
|
||||
Ok(f) => {
|
||||
println!("[download] Created .part file successfully");
|
||||
f
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[download] ERROR: Failed to create .part file: {}", e);
|
||||
return "-1".to_string();
|
||||
}
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
let mut chunk_count: u64 = 0;
|
||||
|
||||
while let Ok(Some(chunk_result)) = timeout(Duration::from_secs(5), stream.next()).await {
|
||||
let chunk = match chunk_result {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
println!(
|
||||
"[download] ERROR: Failed to read chunk #{}: {}",
|
||||
chunk_count, e
|
||||
);
|
||||
return "-1".to_string();
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(_) = file.write_all(&chunk).await {
|
||||
chunk_count += 1;
|
||||
|
||||
if let Err(e) = file.write_all(&chunk).await {
|
||||
println!(
|
||||
"[download] ERROR: Failed to write chunk #{} to disk: {}",
|
||||
chunk_count, e
|
||||
);
|
||||
return "-1".to_string();
|
||||
}
|
||||
|
||||
@@ -169,87 +264,116 @@ async fn download(
|
||||
0.0
|
||||
};
|
||||
|
||||
app.emit(
|
||||
if chunk_count % 50 == 0 || downloaded == total_size {
|
||||
println!(
|
||||
"[download] [{:.1}%] {:.2} MB / {:.2} MB | Speed: {:.1} KB/s | ETA: {:.1}s | Chunks: {}",
|
||||
progress,
|
||||
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(
|
||||
"download-progress",
|
||||
format!(
|
||||
"{}:{:.8}:{}:{}:{:.2}",
|
||||
&name, progress, downloaded, speed, eta_secs
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
}
|
||||
|
||||
if total_size > 0 && downloaded < total_size {
|
||||
println!(
|
||||
"[download] ERROR: Download incomplete — got {} of {} bytes ({:.1}% complete)",
|
||||
downloaded,
|
||||
total_size,
|
||||
(downloaded as f64 / total_size as f64) * 100.0
|
||||
);
|
||||
return "-1".to_string();
|
||||
}
|
||||
|
||||
app.emit("download-hash-checking", format!("{}", &name))
|
||||
.unwrap();
|
||||
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 download_hash = {
|
||||
let mut file = tokio::fs::File::open(download_part_path.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
let mut file = match tokio::fs::File::open(download_part_path.clone()).await {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
println!(
|
||||
"[download] ERROR: Failed to open .part file for hashing: {}",
|
||||
e
|
||||
);
|
||||
return "-1".to_string();
|
||||
}
|
||||
};
|
||||
let mut hasher = Sha512::new();
|
||||
let mut bytes_hashed: u64 = 0;
|
||||
{
|
||||
let mut buffer = [0; 8192];
|
||||
loop {
|
||||
let bytes_read = file.read(&mut buffer).await.unwrap();
|
||||
let bytes_read = match file.read(&mut buffer).await {
|
||||
Ok(n) => n,
|
||||
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;
|
||||
}
|
||||
}
|
||||
drop(file);
|
||||
println!("[download] Hashed {} bytes", bytes_hashed);
|
||||
format!("{:x}", hasher.finalize())
|
||||
};
|
||||
|
||||
println!("[download] Expected hash: {}", hash);
|
||||
println!("[download] Computed hash: {}", download_hash);
|
||||
|
||||
if hash != download_hash {
|
||||
tokio::fs::remove_file(download_part_path.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("[download] ERROR: Hash mismatch! File may be corrupt or tampered with.");
|
||||
let _ = tokio::fs::remove_file(download_part_path.clone()).await;
|
||||
return "-1".to_string();
|
||||
}
|
||||
app.emit("download-finishing", format!("{}", &name))
|
||||
.unwrap();
|
||||
|
||||
tokio::fs::rename(download_part_path.clone(), download_zip_path.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("[download] Hash verified OK");
|
||||
let _ = app.emit("download-finishing", format!("{}", &name));
|
||||
|
||||
println!("[download] Renaming .part -> .zip: {:?}", download_zip_path);
|
||||
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();
|
||||
}
|
||||
|
||||
println!("[download] Unzipping to: {:?}", game_path.join(&name));
|
||||
let unzip_res = unzip_to_dir(download_zip_path.clone(), game_path.join(&name)).await;
|
||||
tokio::fs::remove_file(download_zip_path.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("[download] Removing zip file: {:?}", download_zip_path);
|
||||
let _ = tokio::fs::remove_file(download_zip_path.clone()).await;
|
||||
|
||||
if unzip_res == "-1" {
|
||||
println!("[download] ERROR: Unzip failed for '{}'", name);
|
||||
return "-1".to_string();
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
use std::{fs, os::unix::fs::PermissionsExt};
|
||||
|
||||
let executable_path = if cfg!(target_os = "linux") {
|
||||
game_path.join(&name).join(&executable)
|
||||
} else {
|
||||
game_path
|
||||
.join(&name)
|
||||
.join(&executable)
|
||||
.join("Contents")
|
||||
.join("MacOS")
|
||||
.join(
|
||||
&executable
|
||||
.chars()
|
||||
.take(executable.chars().count() - 4)
|
||||
.collect::<String>(),
|
||||
)
|
||||
};
|
||||
|
||||
let mut perms = fs::metadata(&executable_path).unwrap().permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&executable_path, perms).unwrap();
|
||||
}
|
||||
|
||||
println!(
|
||||
"[download] SUCCESS: '{}' downloaded, verified, and extracted",
|
||||
name
|
||||
);
|
||||
return "1".to_string();
|
||||
}
|
||||
|
||||
@@ -263,12 +387,10 @@ fn launch_game(
|
||||
use_wine: bool,
|
||||
wine_command: String,
|
||||
) {
|
||||
let game_folder = app
|
||||
.path()
|
||||
.app_local_data_dir()
|
||||
.unwrap()
|
||||
.join("game")
|
||||
.join(&name);
|
||||
let game_folder = match app.path().app_local_data_dir() {
|
||||
Ok(p) => p.join("game").join(&name),
|
||||
Err(_) => return,
|
||||
};
|
||||
if !game_folder.exists() {
|
||||
return;
|
||||
}
|
||||
@@ -278,6 +400,9 @@ fn launch_game(
|
||||
//if already running on macos, it'll auto take the user to that proccess
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
{
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
use tauri_plugin_dialog::MessageDialogKind;
|
||||
|
||||
if !use_wine && is_running_by_path(&exe_path) {
|
||||
app.dialog()
|
||||
.message(format!(
|
||||
@@ -297,67 +422,84 @@ fn launch_game(
|
||||
let quoted_path = format!("\"{}\"", exe_path.to_string_lossy());
|
||||
let cmd = wine_command.replace("%path%", "ed_path);
|
||||
|
||||
Command::new("bash")
|
||||
if let Err(_) = Command::new("bash")
|
||||
.arg("-c")
|
||||
.arg(cmd)
|
||||
.current_dir(&game_folder)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
{
|
||||
eprintln!("Failed to launch game with Wine");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if platform() == "macos" {
|
||||
Command::new("open")
|
||||
if let Err(_) = Command::new("open")
|
||||
.arg(&executable)
|
||||
.current_dir(&game_folder)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
{
|
||||
eprintln!("Failed to launch game on macOS");
|
||||
}
|
||||
} else {
|
||||
Command::new(&exe_path)
|
||||
.current_dir(&game_folder)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
if let Err(_) = Command::new(&exe_path).current_dir(&game_folder).spawn() {
|
||||
eprintln!("Failed to launch game");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn open_folder(app: AppHandle, name: String) {
|
||||
let game_path = app
|
||||
.path()
|
||||
.app_local_data_dir()
|
||||
.unwrap()
|
||||
.join("game")
|
||||
.join(&name);
|
||||
if game_path.exists() {
|
||||
app.opener()
|
||||
.open_path(game_path.to_string_lossy(), None::<&str>)
|
||||
.unwrap();
|
||||
} else {
|
||||
app.dialog()
|
||||
.message(format!(
|
||||
"Game folder \"{}\" not found.",
|
||||
game_path.display()
|
||||
))
|
||||
.kind(MessageDialogKind::Error)
|
||||
.title("Folder not found")
|
||||
.show(|_| {});
|
||||
}
|
||||
fn verify_signature(body: String, signature: String, public_key: String) -> bool {
|
||||
let Ok(pem) = general_purpose::STANDARD.decode(public_key) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(pubkey) = PKey::public_key_from_pem(&pem) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(sig) = general_purpose::STANDARD.decode(signature) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let mut verifier = match Verifier::new_without_digest(&pubkey) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
verifier
|
||||
.verify_oneshot(&sig, body.as_bytes())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
#[allow(unused_variables)]
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(
|
||||
tauri_plugin_prevent_default::Builder::new()
|
||||
.with_flags(
|
||||
Flags::FIND
|
||||
| Flags::CARET_BROWSING
|
||||
| Flags::DEV_TOOLS
|
||||
| Flags::DOWNLOADS
|
||||
| Flags::FOCUS_MOVE
|
||||
| Flags::RELOAD
|
||||
| Flags::SOURCE
|
||||
| Flags::OPEN
|
||||
| Flags::PRINT
|
||||
| Flags::CONTEXT_MENU,
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_window_state::Builder::new().build())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
||||
let _ = app
|
||||
.get_webview_window("main")
|
||||
.expect("no main window")
|
||||
.set_focus();
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}))
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
@@ -367,14 +509,17 @@ pub fn run() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
download,
|
||||
launch_game,
|
||||
open_folder,
|
||||
folder_size
|
||||
folder_size,
|
||||
verify_signature
|
||||
])
|
||||
.setup(|app| {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let main_window = app.get_webview_window("main").unwrap();
|
||||
main_window.create_overlay_titlebar().unwrap();
|
||||
if let Some(main_window) = app.get_webview_window("main") {
|
||||
if let Err(e) = main_window.create_overlay_titlebar() {
|
||||
eprintln!("Failed to create overlay titlebar: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Lncvrt Games Launcher",
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.4",
|
||||
"identifier": "xyz.lncvrt.games-launcher",
|
||||
"build": {
|
||||
"beforeDevCommand": "next dev",
|
||||
"beforeDevCommand": "next dev --webpack",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeBuildCommand": "next build",
|
||||
"frontendDist": "../out"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Lncvrt Games Launcher",
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.4",
|
||||
"identifier": "xyz.lncvrt.games-launcher",
|
||||
"build": {
|
||||
"beforeDevCommand": "next dev",
|
||||
"beforeDevCommand": "next dev --webpack",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeBuildCommand": "next build",
|
||||
"frontendDist": "../out"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Lncvrt Games Launcher",
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.4",
|
||||
"identifier": "xyz.lncvrt.games-launcher",
|
||||
"build": {
|
||||
"beforeDevCommand": "next dev",
|
||||
"beforeDevCommand": "next dev --webpack",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeBuildCommand": "next build",
|
||||
"frontendDist": "../out"
|
||||
@@ -30,7 +30,7 @@
|
||||
"active": true,
|
||||
"targets": ["app"],
|
||||
"macOS": {
|
||||
"minimumSystemVersion": "15.0"
|
||||
"minimumSystemVersion": "13.0"
|
||||
},
|
||||
"icon": ["icons/icon.icns"]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Lncvrt Games Launcher",
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.4",
|
||||
"identifier": "xyz.lncvrt.games-launcher",
|
||||
"build": {
|
||||
"beforeDevCommand": "next dev",
|
||||
"beforeDevCommand": "next dev --webpack",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeBuildCommand": "next build",
|
||||
"frontendDist": "../out"
|
||||
|
||||
@@ -1,41 +1,56 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, ReactNode } from 'react'
|
||||
import { DownloadProgress } from './types/DownloadProgress'
|
||||
import { VersionsConfig } from './types/VersionsConfig'
|
||||
import { NormalConfig } from './types/NormalConfig'
|
||||
import { ServerVersionsResponse } from './types/ServerVersionsResponse'
|
||||
import { GameVersion } from './types/GameVersion'
|
||||
import { Game } from './types/Game'
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
ReactNode,
|
||||
Dispatch,
|
||||
SetStateAction
|
||||
} from 'react'
|
||||
import { DownloadProgress } from '@/types/DownloadProgress'
|
||||
import { VersionsConfig } from '@/types/VersionsConfig'
|
||||
import { NormalConfig } from '@/types/NormalConfig'
|
||||
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
|
||||
import { GameVersion } from '@/types/GameVersion'
|
||||
import { Game } from '@/types/Game'
|
||||
|
||||
type GlobalCtxType = {
|
||||
serverVersionList: ServerVersionsResponse | null
|
||||
selectedVersionList: string[]
|
||||
setSelectedVersionList: (v: string[]) => void
|
||||
setSelectedVersionList: (value: SetStateAction<string[]>) => void
|
||||
downloadProgress: DownloadProgress[]
|
||||
setDownloadProgress: (v: DownloadProgress[]) => void
|
||||
setDownloadProgress: Dispatch<SetStateAction<DownloadProgress[]>>
|
||||
showPopup: boolean
|
||||
setShowPopup: (v: boolean) => void
|
||||
setShowPopup: Dispatch<SetStateAction<boolean>>
|
||||
popupMode: number | null
|
||||
setPopupMode: (v: number | null) => void
|
||||
setPopupMode: Dispatch<SetStateAction<number | null>>
|
||||
fadeOut: boolean
|
||||
setFadeOut: (v: boolean) => void
|
||||
setFadeOut: Dispatch<SetStateAction<boolean>>
|
||||
downloadedVersionsConfig: VersionsConfig | null
|
||||
setDownloadedVersionsConfig: (v: VersionsConfig | null) => void
|
||||
setDownloadedVersionsConfig: Dispatch<SetStateAction<VersionsConfig | null>>
|
||||
normalConfig: NormalConfig | null
|
||||
setNormalConfig: (v: NormalConfig | null) => void
|
||||
setNormalConfig: Dispatch<SetStateAction<NormalConfig | null>>
|
||||
managingVersion: string | null
|
||||
setManagingVersion: (v: string | null) => void
|
||||
setSelectedGame: (v: number | null) => void
|
||||
setManagingVersion: Dispatch<SetStateAction<string | null>>
|
||||
setSelectedGame: Dispatch<SetStateAction<number | null>>
|
||||
getVersionInfo: (id: string | undefined) => GameVersion | undefined
|
||||
getGameInfo: (id: number | undefined) => Game | undefined
|
||||
getListOfGames: () => Game[]
|
||||
getGameInfo: (game: number | undefined) => Game | undefined
|
||||
getListOfGames(): Game[]
|
||||
getVersionsAmountData: (gameId: number) => {
|
||||
installed: number
|
||||
total: number
|
||||
} | null
|
||||
viewingInfoFromDownloads: boolean
|
||||
version: string | null
|
||||
downloadVersions: (list: string[]) => Promise<void>
|
||||
category: number
|
||||
setCategory: Dispatch<SetStateAction<number>>
|
||||
downloadQueue: string[]
|
||||
setDownloadQueue: Dispatch<SetStateAction<string[]>>
|
||||
closePopup: () => void
|
||||
getSpecialVersionsList(game?: number | undefined): GameVersion[]
|
||||
selectedGame: number | null
|
||||
setViewingInfoFromDownloads: Dispatch<SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
const GlobalCtx = createContext<GlobalCtxType | null>(null)
|
||||
|
||||
@@ -28,18 +28,6 @@ body {
|
||||
--col8: rgb(152, 128, 128);
|
||||
}
|
||||
|
||||
.green-theme {
|
||||
--col0: rgb(8, 32, 8);
|
||||
--col1: rgb(16, 40, 16);
|
||||
--col2: rgb(32, 56, 32);
|
||||
--col3: rgb(48, 72, 48);
|
||||
--col4: rgb(64, 88, 64);
|
||||
--col5: rgb(80, 104, 80);
|
||||
--col6: rgb(96, 120, 96);
|
||||
--col7: rgb(112, 136, 112);
|
||||
--col8: rgb(128, 152, 128);
|
||||
}
|
||||
|
||||
.blue-theme {
|
||||
--col0: rgb(8, 8, 32);
|
||||
--col1: rgb(16, 16, 40);
|
||||
@@ -64,18 +52,6 @@ body {
|
||||
--col8: rgb(140, 128, 152);
|
||||
}
|
||||
|
||||
.mint-theme {
|
||||
--col0: rgb(8, 32, 16);
|
||||
--col1: rgb(16, 48, 32);
|
||||
--col2: rgb(32, 56, 40);
|
||||
--col3: rgb(48, 72, 48);
|
||||
--col4: rgb(64, 88, 66);
|
||||
--col5: rgb(80, 104, 65);
|
||||
--col6: rgb(96, 120, 80);
|
||||
--col7: rgb(112, 136, 85);
|
||||
--col8: rgb(128, 152, 95);
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply rounded-md cursor-pointer text-[16px] py-1 px-2 transition-colors border;
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
|
||||
.downloads-entry {
|
||||
@apply flex justify-between items-center m-2 p-2 rounded-lg text-gray-200 text-lg transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5);
|
||||
@apply flex justify-between items-center m-2 p-2 rounded-lg text-gray-200 text-lg transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5) cursor-pointer;
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import './Sidebar.css'
|
||||
import Icon from '@/assets/Icon.png'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faCog,
|
||||
faDownload,
|
||||
faGamepad,
|
||||
faHexagonNodes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faDiscord } from '@fortawesome/free-brands-svg-icons'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import { useGlobal } from '../GlobalProvider'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useSearchParams } from 'next/navigation'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { Lexend } from 'next/font/google'
|
||||
|
||||
const lexend = Lexend({
|
||||
subsets: ['latin']
|
||||
})
|
||||
|
||||
export default function Sidebar () {
|
||||
const {
|
||||
normalConfig,
|
||||
getListOfGames,
|
||||
setShowPopup,
|
||||
setPopupMode,
|
||||
setFadeOut,
|
||||
downloadProgress
|
||||
} = useGlobal()
|
||||
|
||||
const pathname = usePathname()
|
||||
const params = useSearchParams()
|
||||
|
||||
return (
|
||||
<aside className='sidebar'>
|
||||
<div
|
||||
className='macos-drag'
|
||||
hidden={platform() != 'macos'}
|
||||
onMouseDown={e => {
|
||||
if (e.buttons === 1) {
|
||||
if (e.detail === 2) {
|
||||
getCurrentWindow().toggleMaximize()
|
||||
} else {
|
||||
getCurrentWindow().startDragging()
|
||||
}
|
||||
}
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className='logo'
|
||||
style={{
|
||||
marginTop:
|
||||
platform() == 'windows'
|
||||
? '32px'
|
||||
: platform() == 'macos'
|
||||
? '28px'
|
||||
: ''
|
||||
}}
|
||||
onMouseDown={e => {
|
||||
if (platform() != 'macos') return
|
||||
if (e.buttons === 1) {
|
||||
if (e.detail === 2) {
|
||||
getCurrentWindow().toggleMaximize()
|
||||
} else {
|
||||
getCurrentWindow().startDragging()
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Image draggable={false} src={Icon} width={36} height={36} alt='' />
|
||||
<p className={`ml-1 text-[16px] whitespace-nowrap ${lexend.className}`}>
|
||||
Lncvrt Games Launcher
|
||||
</p>
|
||||
</div>
|
||||
<nav className='nav-links overflow-auto pt-2'>
|
||||
<Link
|
||||
draggable={false}
|
||||
href='/'
|
||||
className={`link relative flex items-center ${
|
||||
pathname === '/' || pathname === '/game' ? 'active' : ''
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games
|
||||
</Link>
|
||||
{getListOfGames()
|
||||
.sort((a, b) => {
|
||||
return a.id - b.id
|
||||
})
|
||||
.map(i => (
|
||||
<Link
|
||||
key={i.id}
|
||||
draggable={false}
|
||||
href={'/game?id=' + i.id}
|
||||
className={`link ${
|
||||
pathname === '/game' && Number(params.get('id') || 0) == i.id
|
||||
? 'active'
|
||||
: ''
|
||||
} ml-auto w-50 ${
|
||||
normalConfig?.settings.alwaysShowGamesInSidebar ||
|
||||
pathname === '/' ||
|
||||
pathname === '/game'
|
||||
? ''
|
||||
: 'hidden'
|
||||
}`}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<FontAwesomeIcon icon={faGamepad} className='mr-1' />
|
||||
<span className='truncate max-w-full'>{i.name}</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<Link
|
||||
draggable={false}
|
||||
href='/settings'
|
||||
className={`link ${pathname === '/settings' ? 'active' : ''}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => openUrl('https://games.lncvrt.xyz/discord')}
|
||||
className='link mr-auto'
|
||||
>
|
||||
<FontAwesomeIcon icon={faDiscord} className='mr-1' /> Community
|
||||
</button>
|
||||
</nav>
|
||||
<div
|
||||
className='sidebar-downloads'
|
||||
hidden={downloadProgress.length == 0}
|
||||
onClick={() => {
|
||||
setPopupMode(1)
|
||||
setShowPopup(true)
|
||||
setFadeOut(false)
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
<FontAwesomeIcon icon={faDownload} /> Downloads
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useGlobal } from '../GlobalProvider'
|
||||
|
||||
export default function VersionChangelog () {
|
||||
const { getVersionInfo, managingVersion, downloadedVersionsConfig } =
|
||||
useGlobal()
|
||||
if (!managingVersion || !downloadedVersionsConfig) return <></>
|
||||
|
||||
const versionInfo = getVersionInfo(managingVersion)
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>
|
||||
Viewing changelog for {versionInfo?.displayName}
|
||||
</p>
|
||||
<div className='popup-content text-center p-2'>
|
||||
<span
|
||||
className='whitespace-pre-wrap select-text'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: versionInfo?.changelog ? atob(versionInfo.changelog) : ''
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faCheck,
|
||||
faCode,
|
||||
faHardDrive,
|
||||
faShieldHalved,
|
||||
faWarning
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { useGlobal } from '../GlobalProvider'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { useEffect, useState } from 'react'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
|
||||
export default function VersionInfo () {
|
||||
const {
|
||||
getGameInfo,
|
||||
getVersionInfo,
|
||||
managingVersion,
|
||||
downloadedVersionsConfig,
|
||||
viewingInfoFromDownloads,
|
||||
setPopupMode
|
||||
} = useGlobal()
|
||||
const [versionSize, setVersionSize] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
invoke<string>('folder_size', {
|
||||
version: managingVersion
|
||||
}).then(size => {
|
||||
setVersionSize(parseInt(size, 10))
|
||||
})
|
||||
}, [managingVersion, setVersionSize])
|
||||
|
||||
if (!managingVersion || !downloadedVersionsConfig) return <></>
|
||||
|
||||
const versionInfo = getVersionInfo(managingVersion)
|
||||
const gameInfo = getGameInfo(versionInfo?.game)
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>
|
||||
Viewing info for {versionInfo?.displayName}
|
||||
</p>
|
||||
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
<p>
|
||||
Installed{' '}
|
||||
{new Intl.DateTimeFormat(undefined).format(
|
||||
downloadedVersionsConfig.list[managingVersion] ?? 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={!versionInfo || versionInfo.releaseDate == 0}
|
||||
>
|
||||
<p>
|
||||
Released{' '}
|
||||
{new Intl.DateTimeFormat(undefined).format(
|
||||
versionInfo?.releaseDate ? versionInfo.releaseDate * 1000 : 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className='entry-info-item btntheme2' hidden={!gameInfo?.official}>
|
||||
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
|
||||
<p>Official</p>
|
||||
</div>
|
||||
<div className='entry-info-item btntheme2' hidden={gameInfo?.official}>
|
||||
<FontAwesomeIcon
|
||||
icon={gameInfo?.verified ? faShieldHalved : faWarning}
|
||||
color={gameInfo?.verified ? '#19c84b' : '#ffc800'}
|
||||
/>
|
||||
<p>{gameInfo?.verified ? 'Verified' : 'Unverified'}</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={gameInfo?.developer == null}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCode} color='lightgray' />
|
||||
<p>Developer: {gameInfo?.developer}</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={viewingInfoFromDownloads || versionSize === null}
|
||||
>
|
||||
<FontAwesomeIcon icon={faHardDrive} color='lightgray' />
|
||||
<p>
|
||||
Size on disk:{' '}
|
||||
{versionSize !== null
|
||||
? prettyBytes(versionSize, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
: 'Loading...'}
|
||||
</p>
|
||||
</div>
|
||||
<div className='entry-info-item btntheme2' hidden={!versionInfo}>
|
||||
<FontAwesomeIcon icon={faHardDrive} color='lightgray' />
|
||||
<p>
|
||||
Size when downloaded (zipped):{' '}
|
||||
{versionInfo
|
||||
? prettyBytes(versionInfo.size, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
: 'Loading...'}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
onClick={() => setPopupMode(4)}
|
||||
hidden={!versionInfo?.changelog}
|
||||
>
|
||||
<p>View Changelog</p>
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} color='lightgray' />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { BirdColor } from '@/types/BerryDash/BirdColor'
|
||||
import { GetIconForUser } from '@/lib/BerryDash'
|
||||
import Image from 'next/image'
|
||||
import './styles.css'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import { fetch } from '@tauri-apps/plugin-http'
|
||||
import { verifySignature } from '@/lib/Util'
|
||||
|
||||
interface BaseEntry {
|
||||
id: number
|
||||
username: string
|
||||
value: number
|
||||
icon: number
|
||||
overlay: number
|
||||
birdColor: BirdColor
|
||||
overlayColor: BirdColor
|
||||
customIcon: string | null
|
||||
}
|
||||
|
||||
interface Stats {
|
||||
highScore: string
|
||||
totalNormalBerries: string
|
||||
totalPoisonBerries: string
|
||||
totalSlowBerries: string
|
||||
totalUltraBerries: string
|
||||
totalSpeedyBerries: string
|
||||
totalCoinBerries: string
|
||||
totalRandomBerries: string
|
||||
totalAntiBerries: string
|
||||
totalGoldenBerries: string
|
||||
coins: string
|
||||
}
|
||||
|
||||
interface LeaderboardEntry extends BaseEntry {
|
||||
type: 'leaderboard'
|
||||
value: number
|
||||
}
|
||||
|
||||
interface Account extends BaseEntry {
|
||||
type: 'account'
|
||||
stats: Stats
|
||||
xp: bigint
|
||||
}
|
||||
|
||||
export function calculateXP (
|
||||
normalBerries: bigint,
|
||||
poisonBerries: bigint,
|
||||
slowBerries: bigint,
|
||||
ultraBerries: bigint,
|
||||
speedyBerries: bigint,
|
||||
coinBerries: bigint,
|
||||
randomBerries: bigint,
|
||||
antiBerries: bigint,
|
||||
goldenBerries: bigint
|
||||
): bigint {
|
||||
let totalXp = 0n
|
||||
totalXp += normalBerries
|
||||
totalXp -= poisonBerries
|
||||
totalXp -= slowBerries
|
||||
totalXp += ultraBerries * 5n
|
||||
totalXp += speedyBerries * 10n
|
||||
totalXp += coinBerries * 10n
|
||||
totalXp += randomBerries
|
||||
totalXp -= antiBerries
|
||||
totalXp += goldenBerries * 4n
|
||||
|
||||
if (totalXp < 0n) totalXp = 0n
|
||||
return totalXp
|
||||
}
|
||||
|
||||
export function calculateLevel (xp: bigint): number {
|
||||
const levelDivisor = 50.0
|
||||
|
||||
const xpNumber = Number(xp)
|
||||
const discriminant = 95 * 95 + levelDivisor * 2 * xpNumber
|
||||
const level = (-95 + Math.sqrt(discriminant)) / levelDivisor
|
||||
return Math.floor(level) + 1
|
||||
}
|
||||
|
||||
export default function BerryDashLeaderboards () {
|
||||
const [selected, setSelected] = useState<number>(-1)
|
||||
const [selectedBerryOption, setSelectedBerryOption] = useState<number>(0)
|
||||
const [entries, setEntries] = useState<(LeaderboardEntry | Account)[]>([])
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const Refresh = useCallback(async () => {
|
||||
try {
|
||||
if (selected == 3 || selected == 4) {
|
||||
const response = await fetch(
|
||||
'https://games.lncvrt.xyz/api/berrydash/account?username='
|
||||
)
|
||||
const signature = response.headers.get('x-signature') ?? ''
|
||||
const data = await response.json()
|
||||
if (
|
||||
(await verifySignature(JSON.stringify(data), signature)) &&
|
||||
data.success
|
||||
) {
|
||||
let accounts = data.data as Account[]
|
||||
|
||||
accounts = accounts.map(acc => {
|
||||
const xp = calculateXP(
|
||||
BigInt(acc.stats.totalNormalBerries),
|
||||
BigInt(acc.stats.totalPoisonBerries),
|
||||
BigInt(acc.stats.totalSlowBerries),
|
||||
BigInt(acc.stats.totalUltraBerries),
|
||||
BigInt(acc.stats.totalSpeedyBerries),
|
||||
BigInt(acc.stats.totalCoinBerries),
|
||||
BigInt(acc.stats.totalRandomBerries),
|
||||
BigInt(acc.stats.totalAntiBerries),
|
||||
BigInt(acc.stats.totalGoldenBerries)
|
||||
)
|
||||
return { ...acc, xp }
|
||||
})
|
||||
|
||||
accounts.sort((a, b) => (b.xp > a.xp ? 1 : b.xp < a.xp ? -1 : 0))
|
||||
|
||||
setEntries(accounts)
|
||||
} else console.log('Failed')
|
||||
} else {
|
||||
const response = await fetch(
|
||||
'https://games.lncvrt.xyz/api/berrydash/leaderboard/' +
|
||||
(selected == 0
|
||||
? 'score'
|
||||
: selected == 1
|
||||
? 'berry?berry=' + selectedBerryOption
|
||||
: selected == 2
|
||||
? 'coin'
|
||||
: selected == 5
|
||||
? 'legacy'
|
||||
: 'total')
|
||||
)
|
||||
const signature = response.headers.get('x-signature') ?? ''
|
||||
const data = await response.json()
|
||||
if (
|
||||
(await verifySignature(JSON.stringify(data), signature)) &&
|
||||
data.success
|
||||
)
|
||||
setEntries(data.data as LeaderboardEntry[])
|
||||
}
|
||||
} catch {
|
||||
setEntries([])
|
||||
}
|
||||
}, [selected, selectedBerryOption])
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'Lncvrt Games - Berry Dash Leaderboards'
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (selected != -1) setTimeout(() => Refresh(), 0)
|
||||
}, [selected, Refresh])
|
||||
|
||||
return (
|
||||
<div className='mx-4 mt-4'>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<p className='text-3xl'>Berry Dash Leaderboards</p>
|
||||
<div className='flex gap-2'>
|
||||
<button
|
||||
className='button btntheme1'
|
||||
onClick={() => {
|
||||
setEntries([])
|
||||
Refresh()
|
||||
}}
|
||||
title='Click to refresh the leaderboards.'
|
||||
hidden={selected == -1}
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme1'
|
||||
onClick={() => {
|
||||
if (selected == -1) router.push('/game?id=1')
|
||||
else {
|
||||
setSelected(-1)
|
||||
setEntries([])
|
||||
}
|
||||
}}
|
||||
title='Click to go up a level.'
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`box ${
|
||||
platform() == 'windows'
|
||||
? 'h-[calc(100vh-116px)]'
|
||||
: 'h-[calc(100vh-84px)]'
|
||||
}`}
|
||||
>
|
||||
{selected == -1 ? (
|
||||
<>
|
||||
<p className='text-center mt-2 text-xl'>Select a Leaderboard</p>
|
||||
<div className='flex flex-col gap-2 mt-2 items-center justify-center'>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(0)}
|
||||
>
|
||||
Score Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(1)}
|
||||
>
|
||||
Berry Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(2)}
|
||||
>
|
||||
Coins Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(3)}
|
||||
>
|
||||
Level Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(4)}
|
||||
>
|
||||
Total XP Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(5)}
|
||||
>
|
||||
Legacy Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(6)}
|
||||
>
|
||||
Total Berries Leaderboard
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-col gap-2 overflow-y-auto ${
|
||||
selected == 1
|
||||
? platform() == 'windows'
|
||||
? 'h-[calc(100vh-168px)]'
|
||||
: 'h-[calc(100vh-136px)]'
|
||||
: platform() == 'windows'
|
||||
? 'h-[calc(100vh-128px)]'
|
||||
: 'h-[calc(100vh-96px)]'
|
||||
} px-1`}
|
||||
>
|
||||
{entries.map((item, index) => {
|
||||
const isAccount = 'stats' in item
|
||||
const isLeaderboard = 'value' in item && !isAccount
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className='leaderboard-entry flex justify-between items-center'
|
||||
>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Image
|
||||
src={
|
||||
!item.customIcon
|
||||
? `https://games.lncvrt.xyz/api/berrydash/render-icon?icon=${
|
||||
item.icon === 1
|
||||
? GetIconForUser(item.id)
|
||||
: item.icon
|
||||
}&overlay=${item.overlay}&birdR=${
|
||||
item.birdColor[0]
|
||||
}&birdG=${item.birdColor[1]}&birdB=${
|
||||
item.birdColor[2]
|
||||
}&overlayR=${item.overlayColor[0]}&overlayG=${
|
||||
item.overlayColor[1]
|
||||
}&overlayB=${item.overlayColor[2]}`
|
||||
: `https://games.lncvrt.xyz/api/berrydash/icon-marketplace/icon?id=${item.customIcon}&raw=true`
|
||||
}
|
||||
width={48}
|
||||
height={48}
|
||||
alt=''
|
||||
className='pointer-events-none scale-x-[-1]'
|
||||
/>
|
||||
<p>
|
||||
{item.username} (#{index + 1})
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isLeaderboard ? (
|
||||
<p>
|
||||
{selected === 1 || selected === 6
|
||||
? 'Berries'
|
||||
: selected === 2
|
||||
? 'Coins'
|
||||
: 'Score'}
|
||||
: {item.value.toLocaleString('en-US')}
|
||||
</p>
|
||||
) : isAccount ? (
|
||||
<p>
|
||||
{selected === 3 ? 'Level' : 'XP'}:{' '}
|
||||
{selected === 3
|
||||
? calculateLevel(item.xp).toLocaleString('en-US')
|
||||
: item.xp.toLocaleString('en-US')}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{selected == 1 && (
|
||||
<div className='flex justify-center'>
|
||||
<select
|
||||
value={selectedBerryOption}
|
||||
onChange={e => setSelectedBerryOption(Number(e.target.value))}
|
||||
className='leaderboard-select mt-0.75'
|
||||
>
|
||||
<option value='0'>Normal Berry</option>
|
||||
<option value='1'>Poison Berry</option>
|
||||
<option value='2'>Slow Berry</option>
|
||||
<option value='3'>Ultra Berry</option>
|
||||
<option value='4'>Speedy Berry</option>
|
||||
<option value='5'>Coin Berry</option>
|
||||
<option value='6'>Random Berry</option>
|
||||
<option value='7'>Anti Berry</option>
|
||||
<option value='8'>Golden Berry</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
.box {
|
||||
@apply bg-(--col1) border border-(--col3) rounded-lg w-auto p-1;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.leaderboard-entry {
|
||||
@apply rounded-lg text-gray-200 text-lg px-1 py-0.5 transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5) cursor-pointer;
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import '../Installs.css'
|
||||
import { useEffect } from 'react'
|
||||
import '@/app/Installs.css'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { useGlobal } from '../GlobalProvider'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import { faWarning } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { ask } from '@tauri-apps/plugin-dialog'
|
||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||
import { writeVersionsConfig } from '@/lib/BazookaManager'
|
||||
import { openFolder } from '@/lib/Util'
|
||||
|
||||
export default function Installs () {
|
||||
const {
|
||||
@@ -22,17 +26,19 @@ export default function Installs () {
|
||||
getVersionInfo,
|
||||
getGameInfo,
|
||||
setSelectedGame,
|
||||
serverVersionList
|
||||
serverVersionList,
|
||||
category,
|
||||
setCategory,
|
||||
setDownloadedVersionsConfig,
|
||||
downloadVersions
|
||||
} = useGlobal()
|
||||
|
||||
const params = useSearchParams()
|
||||
const router = useRouter()
|
||||
|
||||
const id = Number(params.get('id') || 0)
|
||||
const game = serverVersionList?.games.find(g => g.id === id)
|
||||
|
||||
const [category, setCategory] = useState<number>(-1)
|
||||
const [lastId, setLastId] = useState(id)
|
||||
|
||||
useEffect(() => {
|
||||
if (!showPopup) return
|
||||
setSelectedVersionList([])
|
||||
@@ -40,16 +46,46 @@ export default function Installs () {
|
||||
|
||||
if (!id || !game) return <p>Invalid game</p>
|
||||
|
||||
if (lastId !== id) {
|
||||
setLastId(id)
|
||||
setCategory(-1)
|
||||
const needsRevisionUpdate = (
|
||||
lastRevision: number | undefined,
|
||||
version: string
|
||||
) => {
|
||||
if (!lastRevision) return false
|
||||
return (
|
||||
lastRevision > 0 &&
|
||||
(downloadedVersionsConfig == undefined
|
||||
? 0
|
||||
: downloadedVersionsConfig?.list[version]) /
|
||||
1000 <=
|
||||
lastRevision
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mx-4 mt-4'>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<p className='text-3xl'>Installs</p>
|
||||
<p
|
||||
className={`text-3xl truncate ${
|
||||
category != -1
|
||||
? 'w-[calc(100vw-495px)]'
|
||||
: game.id == 1
|
||||
? 'w-[calc(100vw-560px)]'
|
||||
: 'w-[calc(100vw-440px)]'
|
||||
}`}
|
||||
>
|
||||
{game.name} Installs
|
||||
</p>
|
||||
<div className='flex gap-2'>
|
||||
<button
|
||||
className='button btntheme1'
|
||||
onClick={() => {
|
||||
router.push('/game/berrydash/leaderboards')
|
||||
}}
|
||||
title='View the leaderboards for this game.'
|
||||
hidden={game.id != 1}
|
||||
>
|
||||
Leaderboards
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme1'
|
||||
onClick={() => {
|
||||
@@ -77,7 +113,7 @@ export default function Installs () {
|
||||
<div className='downloads-container'>
|
||||
<div
|
||||
className={`downloads-scroll ${
|
||||
platform() === 'windows'
|
||||
platform() == 'windows'
|
||||
? 'h-[calc(100vh-116px)]'
|
||||
: 'h-[calc(100vh-84px)]'
|
||||
}`}
|
||||
@@ -93,7 +129,7 @@ export default function Installs () {
|
||||
if (!info) return false
|
||||
|
||||
if (
|
||||
platform() === 'linux' &&
|
||||
platform() == 'linux' &&
|
||||
info.wine &&
|
||||
!normalConfig?.settings.useWineOnUnixWhenNeeded
|
||||
)
|
||||
@@ -107,22 +143,10 @@ export default function Installs () {
|
||||
.map(([key, value]) => {
|
||||
return (
|
||||
<div
|
||||
key={value}
|
||||
className={`downloads-entry ${
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'cursor-pointer'
|
||||
}`}
|
||||
title={
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'Click to view category'
|
||||
}
|
||||
onClick={() => {
|
||||
if (normalConfig?.settings.useLegacyInteractButtons)
|
||||
return
|
||||
setCategory(Number(key))
|
||||
}}
|
||||
key={key}
|
||||
className={'downloads-entry'}
|
||||
title={'Click to view category'}
|
||||
onClick={() => setCategory(Number(key))}
|
||||
>
|
||||
<div className='h-18 w-screen relative'>
|
||||
<p className='text-2xl'>{value}</p>
|
||||
@@ -141,7 +165,7 @@ export default function Installs () {
|
||||
const info = getVersionInfo(v)
|
||||
if (!info) return false
|
||||
if (
|
||||
platform() === 'linux' &&
|
||||
platform() == 'linux' &&
|
||||
info.wine &&
|
||||
!normalConfig?.settings
|
||||
.useWineOnUnixWhenNeeded
|
||||
@@ -156,17 +180,6 @@ export default function Installs () {
|
||||
})()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className='button absolute right-0 bottom-0'
|
||||
hidden={
|
||||
!normalConfig?.settings.useLegacyInteractButtons
|
||||
}
|
||||
title='Click to view category'
|
||||
onClick={() => setCategory(Number(key))}
|
||||
>
|
||||
Installs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -187,7 +200,7 @@ export default function Installs () {
|
||||
const info = getVersionInfo(v)
|
||||
if (!info) return false
|
||||
if (
|
||||
platform() === 'linux' &&
|
||||
platform() == 'linux' &&
|
||||
info.wine &&
|
||||
!normalConfig?.settings.useWineOnUnixWhenNeeded
|
||||
)
|
||||
@@ -202,18 +215,73 @@ export default function Installs () {
|
||||
.map(entry => (
|
||||
<div
|
||||
key={entry}
|
||||
className={`downloads-entry ${
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'cursor-pointer'
|
||||
}`}
|
||||
className={'downloads-entry'}
|
||||
title={
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'Click to launch game. Right-click to manage this version install'
|
||||
'Click to launch game. Right-click to manage this version install'
|
||||
}
|
||||
onClick={async () => {
|
||||
if (normalConfig?.settings.useLegacyInteractButtons) return
|
||||
if (
|
||||
needsRevisionUpdate(
|
||||
getVersionInfo(entry)?.lastRevision,
|
||||
entry
|
||||
)
|
||||
) {
|
||||
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.',
|
||||
{
|
||||
title: 'Revision Update',
|
||||
kind: 'warning'
|
||||
}
|
||||
)
|
||||
if (answer) {
|
||||
const answer2 = await ask(
|
||||
'Are you sure you want to update? If you did not read the last popup, please go back and read it.',
|
||||
{
|
||||
title: 'Revision Update',
|
||||
kind: 'warning'
|
||||
}
|
||||
)
|
||||
if (!answer2) return
|
||||
|
||||
//open downloads popup
|
||||
setPopupMode(1)
|
||||
setShowPopup(true)
|
||||
setFadeOut(false)
|
||||
|
||||
//uninstall
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
const updatedList = Object.fromEntries(
|
||||
Object.entries(prev.list).filter(
|
||||
([k]) => k !== entry
|
||||
)
|
||||
)
|
||||
const updatedConfig = {
|
||||
...prev,
|
||||
list: updatedList
|
||||
}
|
||||
writeVersionsConfig(updatedConfig)
|
||||
return updatedConfig
|
||||
})
|
||||
|
||||
if (
|
||||
await exists('game/' + entry, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
)
|
||||
await remove('game/' + entry, {
|
||||
baseDir: BaseDirectory.AppLocalData,
|
||||
recursive: true
|
||||
})
|
||||
|
||||
//reinstall
|
||||
setSelectedVersionList([entry])
|
||||
downloadVersions([entry])
|
||||
} else {
|
||||
openFolder(entry)
|
||||
}
|
||||
return
|
||||
}
|
||||
const verInfo = getVersionInfo(entry)
|
||||
if (verInfo == undefined) return
|
||||
const gameInfo = getGameInfo(verInfo.game)
|
||||
@@ -223,7 +291,7 @@ export default function Installs () {
|
||||
executable: verInfo.executable,
|
||||
displayName: verInfo.displayName,
|
||||
useWine: !!(
|
||||
platform() === 'linux' &&
|
||||
platform() == 'linux' &&
|
||||
verInfo.wine &&
|
||||
normalConfig?.settings.useWineOnUnixWhenNeeded
|
||||
),
|
||||
@@ -232,8 +300,6 @@ export default function Installs () {
|
||||
}}
|
||||
onContextMenu={e => {
|
||||
e.preventDefault()
|
||||
if (normalConfig?.settings.useLegacyInteractButtons) return
|
||||
|
||||
setManagingVersion(entry)
|
||||
setPopupMode(2)
|
||||
setShowPopup(true)
|
||||
@@ -242,7 +308,7 @@ export default function Installs () {
|
||||
>
|
||||
<div className='h-18 w-screen relative'>
|
||||
<p className='text-2xl'>
|
||||
{getVersionInfo(entry)?.displayName}
|
||||
{getVersionInfo(entry)?.displayName}{' '}
|
||||
</p>
|
||||
|
||||
<div className='flex gap-2 absolute left-0 bottom-0'>
|
||||
@@ -263,8 +329,11 @@ export default function Installs () {
|
||||
title='This version is using wine. It cannot be guarenteed to work fully and might not work at all.'
|
||||
hidden={
|
||||
!(
|
||||
platform() === 'linux' &&
|
||||
getVersionInfo(entry)?.wine
|
||||
platform() == 'linux' && getVersionInfo(entry)?.wine
|
||||
) ||
|
||||
needsRevisionUpdate(
|
||||
getVersionInfo(entry)?.lastRevision,
|
||||
entry
|
||||
)
|
||||
}
|
||||
onClick={e => e.stopPropagation()}
|
||||
@@ -272,66 +341,19 @@ export default function Installs () {
|
||||
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
|
||||
<p>Uses wine</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2 absolute right-0 bottom-0'>
|
||||
<button
|
||||
className='button'
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
setManagingVersion(entry)
|
||||
setPopupMode(3)
|
||||
setShowPopup(true)
|
||||
setFadeOut(false)
|
||||
}}
|
||||
title='Click to view version info'
|
||||
>
|
||||
View Info
|
||||
</button>
|
||||
<button
|
||||
className='button'
|
||||
<div
|
||||
className='entry-info-item'
|
||||
onClick={e => e.stopPropagation()}
|
||||
hidden={
|
||||
!normalConfig?.settings.useLegacyInteractButtons
|
||||
!needsRevisionUpdate(
|
||||
getVersionInfo(entry)?.lastRevision,
|
||||
entry
|
||||
)
|
||||
}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
setManagingVersion(entry)
|
||||
setPopupMode(2)
|
||||
setShowPopup(true)
|
||||
setFadeOut(false)
|
||||
}}
|
||||
title='Click to manage this version install'
|
||||
>
|
||||
Manage
|
||||
</button>
|
||||
<button
|
||||
className='button'
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
const verInfo = getVersionInfo(entry)
|
||||
if (verInfo == undefined) return
|
||||
const gameInfo = getGameInfo(verInfo.game)
|
||||
if (gameInfo == undefined) return
|
||||
invoke('launch_game', {
|
||||
name: verInfo.id,
|
||||
executable: verInfo.executable,
|
||||
displayName: verInfo.displayName,
|
||||
useWine: !!(
|
||||
platform() === 'linux' &&
|
||||
verInfo.wine &&
|
||||
normalConfig?.settings.useWineOnUnixWhenNeeded
|
||||
),
|
||||
wineCommand:
|
||||
normalConfig?.settings.wineOnUnixCommand
|
||||
})
|
||||
}}
|
||||
hidden={
|
||||
!normalConfig?.settings.useLegacyInteractButtons
|
||||
}
|
||||
title='Click to launch game'
|
||||
>
|
||||
Launch
|
||||
</button>
|
||||
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
|
||||
<p>Needs revision update!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,52 +1,43 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Sidebar from './componets/Sidebar'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import Sidebar from '@/componets/Sidebar'
|
||||
import './Globals.css'
|
||||
import { DownloadProgress } from './types/DownloadProgress'
|
||||
import { DownloadProgress } from '@/types/DownloadProgress'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faAdd,
|
||||
faCheck,
|
||||
faChevronLeft,
|
||||
faCode,
|
||||
faDownload,
|
||||
faInfo,
|
||||
faRemove,
|
||||
faShieldHalved,
|
||||
faWarning,
|
||||
faXmark
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faChevronLeft, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
readNormalConfig,
|
||||
readVersionsConfig,
|
||||
writeVersionsConfig
|
||||
} from './util/BazookaManager'
|
||||
import { VersionsConfig } from './types/VersionsConfig'
|
||||
import { NormalConfig } from './types/NormalConfig'
|
||||
} from '@/lib/BazookaManager'
|
||||
import { VersionsConfig } from '@/types/VersionsConfig'
|
||||
import { NormalConfig } from '@/types/NormalConfig'
|
||||
import { app } from '@tauri-apps/api'
|
||||
import axios from 'axios'
|
||||
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 { 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 VersionInfo from './componets/VersionInfo'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import ProgressBar from './componets/ProgressBar'
|
||||
import { notifyUser } from './util/Notifications'
|
||||
import { notifyUser } from '@/lib/Notifications'
|
||||
import {
|
||||
isPermissionGranted,
|
||||
requestPermission
|
||||
} from '@tauri-apps/plugin-notification'
|
||||
import VersionChangelog from './componets/VersionChangelog'
|
||||
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'
|
||||
|
||||
const roboto = Roboto({
|
||||
subsets: ['latin']
|
||||
})
|
||||
@@ -60,6 +51,7 @@ export default function RootLayout ({
|
||||
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)
|
||||
@@ -76,13 +68,18 @@ export default function RootLayout ({
|
||||
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 []
|
||||
@@ -93,13 +90,14 @@ export default function RootLayout ({
|
||||
)
|
||||
.filter(v => {
|
||||
if (
|
||||
platform() === 'linux' &&
|
||||
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)
|
||||
}
|
||||
@@ -111,15 +109,21 @@ export default function RootLayout ({
|
||||
})
|
||||
}
|
||||
|
||||
function getVersionInfo (id: string | undefined): GameVersion | undefined {
|
||||
if (!id) return undefined
|
||||
return serverVersionList?.versions.find(v => v.id === id)
|
||||
}
|
||||
const getVersionInfo = useCallback(
|
||||
(id: string | undefined): GameVersion | undefined => {
|
||||
if (!id) return undefined
|
||||
return serverVersionList?.versions.find(v => v.id === id)
|
||||
},
|
||||
[serverVersionList]
|
||||
)
|
||||
|
||||
function getGameInfo (game: number | undefined): Game | undefined {
|
||||
if (!game) return undefined
|
||||
return serverVersionList?.games.find(g => g.id === game)
|
||||
}
|
||||
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 []
|
||||
@@ -144,7 +148,7 @@ export default function RootLayout ({
|
||||
if (!downloadedVersionsConfig || !serverVersionList) return null
|
||||
|
||||
const allowWine =
|
||||
platform() !== 'linux' || normalConfig?.settings.useWineOnUnixWhenNeeded
|
||||
platformName !== 'linux' || normalConfig?.settings.useWineOnUnixWhenNeeded
|
||||
|
||||
const installed = Object.keys(downloadedVersionsConfig.list).filter(v => {
|
||||
const info = getVersionInfo(v)
|
||||
@@ -162,38 +166,37 @@ export default function RootLayout ({
|
||||
return { installed, total }
|
||||
}
|
||||
|
||||
function formatEtaSmart (seconds: number) {
|
||||
if (seconds < 60) return `${Math.floor(seconds)}s`
|
||||
if (seconds < 3600)
|
||||
return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`
|
||||
if (seconds < 86400) {
|
||||
const h = Math.floor(seconds / 3600)
|
||||
const m = Math.floor((seconds % 3600) / 60)
|
||||
return `${h}h ${m}m`
|
||||
}
|
||||
const d = Math.floor(seconds / 86400)
|
||||
const h = Math.floor((seconds % 86400) / 3600)
|
||||
return `${d}d ${h}h`
|
||||
}
|
||||
|
||||
function closePopup () {
|
||||
const closePopup = useCallback(() => {
|
||||
if (popupMode == 0 && selectedGame && pathname === '/') {
|
||||
setSelectedGame(null)
|
||||
setSelectedVersionList([])
|
||||
} else if (viewingInfoFromDownloads) {
|
||||
setViewingInfoFromDownloads(false)
|
||||
setPopupMode(0)
|
||||
} else if (popupMode == 4) {
|
||||
setPopupMode(3)
|
||||
} 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', event => {
|
||||
const [displayName, progStr, totalSizeStr, speedStr, etaSecsStr] =
|
||||
event.payload.split(':')
|
||||
@@ -245,16 +248,23 @@ export default function RootLayout ({
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
setPlatformName(platform())
|
||||
const client = await app.getVersion()
|
||||
setVersion(client)
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
setLoadingText('Checking latest version...')
|
||||
try {
|
||||
const response = await axios.get(
|
||||
const response = await fetch(
|
||||
'https://games.lncvrt.xyz/api/launcher/latest'
|
||||
)
|
||||
if (response.data !== client) {
|
||||
setOutdated(true)
|
||||
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 {
|
||||
@@ -262,17 +272,22 @@ export default function RootLayout ({
|
||||
return
|
||||
}
|
||||
}
|
||||
setLoadingText('Downloading version list...')
|
||||
try {
|
||||
const res = await axios.get(
|
||||
const response = await fetch(
|
||||
`https://games.lncvrt.xyz/api/launcher/versions?platform=${platform()}&arch=${arch()}`
|
||||
)
|
||||
setServerVersionList(res.data)
|
||||
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
|
||||
}
|
||||
setLoadingText('Loading configs...')
|
||||
const normalConfig = await readNormalConfig()
|
||||
const versionsConfig = await readVersionsConfig()
|
||||
setDownloadedVersionsConfig(versionsConfig)
|
||||
@@ -285,78 +300,123 @@ export default function RootLayout ({
|
||||
})()
|
||||
}, [])
|
||||
|
||||
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 (process.env.NODE_ENV !== 'production') return
|
||||
const handler = (e: MouseEvent) => e.preventDefault()
|
||||
document.addEventListener('contextmenu', handler)
|
||||
return () => document.removeEventListener('contextmenu', handler)
|
||||
}, [])
|
||||
if (isProcessingQueue || downloadQueue.length === 0) return
|
||||
|
||||
async function downloadVersions (
|
||||
list: string[],
|
||||
currentConfig: VersionsConfig
|
||||
): Promise<void> {
|
||||
if (list.length === 0) return
|
||||
setSelectedVersionList([])
|
||||
const processNextDownload = async () => {
|
||||
setIsProcessingQueue(true)
|
||||
|
||||
const newDownloads = list.map(
|
||||
version =>
|
||||
new DownloadProgress(version, 0, 0, false, true, false, false, 0, 0)
|
||||
)
|
||||
const versionId = downloadQueue[0]
|
||||
const info = getVersionInfo(versionId)
|
||||
|
||||
setDownloadProgress(newDownloads)
|
||||
|
||||
for (const download of newDownloads) {
|
||||
const info = getVersionInfo(download.version)
|
||||
if (!info) {
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(d => d.version !== download.version)
|
||||
)
|
||||
continue
|
||||
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 !== download.version)
|
||||
)
|
||||
continue
|
||||
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
||||
setDownloadQueue(prev => prev.slice(1))
|
||||
setIsProcessingQueue(false)
|
||||
return
|
||||
}
|
||||
|
||||
setDownloadProgress(prev =>
|
||||
prev.map(d =>
|
||||
d.version === download.version ? { ...d, queued: false } : d
|
||||
)
|
||||
prev.map(d => (d.version === versionId ? { ...d, queued: false } : d))
|
||||
)
|
||||
|
||||
try {
|
||||
await axios.get(
|
||||
'https://games.lncvrt.xyz/api/launcher/download?id=' + info.id
|
||||
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
|
||||
)
|
||||
)
|
||||
} catch {}
|
||||
if (normalConfig?.settings.allowNotifications)
|
||||
await notifyUser(
|
||||
'Download Failed',
|
||||
`The download for version ${info.displayName} has failed.`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const res = await invoke<string>('download', {
|
||||
url: info.downloadUrl,
|
||||
url: data.data.url,
|
||||
name: info.id,
|
||||
executable: info.executable,
|
||||
hash: info.sha512sum
|
||||
hash: data.data.hash
|
||||
})
|
||||
|
||||
if (res === '1') {
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(d => d.version !== download.version)
|
||||
)
|
||||
const date = Date.now()
|
||||
const newConfig = {
|
||||
...currentConfig,
|
||||
list: { ...currentConfig.list, [download.version]: date }
|
||||
}
|
||||
setDownloadedVersionsConfig(newConfig)
|
||||
writeVersionsConfig(newConfig)
|
||||
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 === download.version
|
||||
d.version === versionId
|
||||
? { ...d, queued: false, failed: true, progress: 0 }
|
||||
: d
|
||||
)
|
||||
@@ -367,44 +427,67 @@ export default function RootLayout ({
|
||||
`The download for version ${info.displayName} has failed.`
|
||||
)
|
||||
}
|
||||
|
||||
setDownloadQueue(prev => prev.slice(1))
|
||||
setIsProcessingQueue(false)
|
||||
}
|
||||
|
||||
if (normalConfig?.settings.allowNotifications)
|
||||
await notifyUser('Downloads Finished', 'All downloads have finished.')
|
||||
processNextDownload()
|
||||
}, [
|
||||
downloadQueue,
|
||||
isProcessingQueue,
|
||||
getVersionInfo,
|
||||
getGameInfo,
|
||||
normalConfig
|
||||
])
|
||||
|
||||
setFadeOut(true)
|
||||
setTimeout(() => setShowPopup(false), 200)
|
||||
}
|
||||
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)
|
||||
}
|
||||
previousQueueLength.current = downloadQueue.length + downloadProgress.length
|
||||
}, [
|
||||
downloadQueue,
|
||||
downloadProgress,
|
||||
isProcessingQueue,
|
||||
normalConfig,
|
||||
closePopup
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (revisionCheck.current) return
|
||||
if (!serverVersionList || !downloadedVersionsConfig) return
|
||||
revisionCheck.current = true
|
||||
;(async () => {
|
||||
const newConfig = {
|
||||
...downloadedVersionsConfig,
|
||||
list: { ...downloadedVersionsConfig.list }
|
||||
}
|
||||
const versionsToSelect: string[] = []
|
||||
|
||||
for (const [key, value] of Object.entries(
|
||||
downloadedVersionsConfig.list
|
||||
)) {
|
||||
const verInfo = serverVersionList.versions.find(item => item.id === key)
|
||||
|
||||
if (!verInfo || value / 1000 <= verInfo.lastRevision) {
|
||||
delete newConfig.list[key]
|
||||
versionsToSelect.push(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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setDownloadedVersionsConfig(newConfig)
|
||||
writeVersionsConfig(newConfig)
|
||||
setSelectedVersionList(prev => [...prev, ...versionsToSelect])
|
||||
|
||||
await downloadVersions(versionsToSelect, newConfig)
|
||||
})()
|
||||
}, [serverVersionList, downloadedVersionsConfig])
|
||||
}, [serverVersionList, downloadedVersionsConfig, downloadVersions])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -414,37 +497,45 @@ export default function RootLayout ({
|
||||
normalConfig?.settings.theme === 1
|
||||
? 'red-theme'
|
||||
: normalConfig?.settings.theme === 2
|
||||
? 'green-theme'
|
||||
: normalConfig?.settings.theme === 3
|
||||
? 'blue-theme'
|
||||
: normalConfig?.settings.theme === 4
|
||||
: normalConfig?.settings.theme === 3
|
||||
? 'purple-theme'
|
||||
: normalConfig?.settings.theme === 5
|
||||
? 'mint-theme'
|
||||
: 'dark-theme'
|
||||
}
|
||||
>
|
||||
{loading ? (
|
||||
<div className='w-screen 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>
|
||||
<>
|
||||
<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={{
|
||||
@@ -471,7 +562,16 @@ export default function RootLayout ({
|
||||
setSelectedGame,
|
||||
getVersionsAmountData,
|
||||
viewingInfoFromDownloads,
|
||||
version
|
||||
version,
|
||||
downloadVersions,
|
||||
category,
|
||||
setCategory,
|
||||
downloadQueue,
|
||||
setDownloadQueue,
|
||||
closePopup,
|
||||
getSpecialVersionsList,
|
||||
selectedGame,
|
||||
setViewingInfoFromDownloads
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@@ -482,10 +582,8 @@ export default function RootLayout ({
|
||||
>
|
||||
<Sidebar />
|
||||
<div
|
||||
className='relative z-2 ml-59.75 w-[calc(100vw-239px)] border-b border-(--col3) h-8.25 bg-(--col1)'
|
||||
style={{
|
||||
display: platform() === 'windows' ? 'block' : 'none'
|
||||
}}
|
||||
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>
|
||||
@@ -524,369 +622,14 @@ export default function RootLayout ({
|
||||
/>
|
||||
</button>
|
||||
{popupMode === 0 && selectedGame ? (
|
||||
<>
|
||||
<p className='text-xl text-center'>
|
||||
Select versions to download
|
||||
</p>
|
||||
<div className='popup-content'>
|
||||
{getSpecialVersionsList(selectedGame).map(
|
||||
(v, i) => (
|
||||
<div key={i} className='popup-entry'>
|
||||
<div className='flex items-center'>
|
||||
<p
|
||||
className={`text-2xl truncate ${
|
||||
selectedVersionList.includes(v.id)
|
||||
? 'max-w-84.5'
|
||||
: 'max-w-91.5'
|
||||
}`}
|
||||
>
|
||||
{v.displayName}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className='button btntheme3 right-20.75 bottom-1.75'
|
||||
onClick={() => {
|
||||
setSelectedVersionList(prev =>
|
||||
prev.includes(v.id)
|
||||
? prev.filter(i => i !== v.id)
|
||||
: [...prev, v.id]
|
||||
)
|
||||
}}
|
||||
title={
|
||||
selectedVersionList.includes(v.id)
|
||||
? 'This version will be downloaded. Click to remove from the list of versions that will be downloaded.'
|
||||
: 'This version will NOT be downloaded. Click to add from the list of versions that will be downloaded.'
|
||||
}
|
||||
>
|
||||
{selectedVersionList.includes(v.id) ? (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faRemove} />{' '}
|
||||
Remove
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faAdd} /> Add
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme3 right-1.5 bottom-1.75'
|
||||
onClick={() => {
|
||||
setManagingVersion(v.id)
|
||||
setViewingInfoFromDownloads(true)
|
||||
setPopupMode(3)
|
||||
}}
|
||||
title='Click to view version info'
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfo} /> Info
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
<VersionsDownloadPopup />
|
||||
) : popupMode === 0 && !selectedGame ? (
|
||||
<>
|
||||
<p className='text-xl text-center'>
|
||||
Select a game to download
|
||||
</p>
|
||||
<div className='popup-content'>
|
||||
{serverVersionList?.games.map((v, i) => (
|
||||
<div key={i} className='popup-entry'>
|
||||
<p className='text-2xl'>{v.name}</p>
|
||||
<div className='flex gap-2'>
|
||||
<div
|
||||
className='entry-info-item btntheme3'
|
||||
title='The amount of versions installed of this game in installed/installable format.'
|
||||
>
|
||||
<p>
|
||||
{(() => {
|
||||
const data = getVersionsAmountData(v.id)
|
||||
if (!data) return 'N/A'
|
||||
return `${data.installed}/${data.total}`
|
||||
})()}{' '}
|
||||
versions installed
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme3'
|
||||
hidden={!v.official}
|
||||
title='This game is official.'
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
color='#19c84b'
|
||||
/>
|
||||
<p>Official</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme3'
|
||||
hidden={v.official}
|
||||
title={
|
||||
v.verified
|
||||
? 'This game is verified to be safe'
|
||||
: 'This game is NOT verified to be save. Proceed with caution.'
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
v.verified ? faShieldHalved : faWarning
|
||||
}
|
||||
color={v.verified ? '#19c84b' : '#ffc800'}
|
||||
/>
|
||||
<p>
|
||||
{v.verified ? 'Verified' : 'Unverified'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme3 mt-2'
|
||||
hidden={v.developer == null}
|
||||
title={`The developer of ${v.name} is ${v.developer}.`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faCode}
|
||||
color='lightgray'
|
||||
/>
|
||||
<p>Developer: {v.developer}</p>
|
||||
</div>
|
||||
<button
|
||||
className='button btntheme3 right-2 bottom-2'
|
||||
onClick={() => setSelectedGame(v.id)}
|
||||
title={`Click to download specific versions of the game. You have ${(() => {
|
||||
const data = getVersionsAmountData(v.id)
|
||||
if (!data) return 'N/A'
|
||||
return `${data.installed} of ${data.total}`
|
||||
})()} versions downloaded.`}
|
||||
>
|
||||
<>
|
||||
<FontAwesomeIcon icon={faDownload} />{' '}
|
||||
Download
|
||||
</>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
<GamesDownloadPopup />
|
||||
) : popupMode === 1 ? (
|
||||
<>
|
||||
<p className='text-xl text-center'>Downloads</p>
|
||||
<div className='popup-content'>
|
||||
{downloadProgress.map((v, i) => (
|
||||
<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 ? (
|
||||
<>
|
||||
<div className='flex items-center'>
|
||||
<span className='text-red-500 inline-block w-full text-center'>
|
||||
Download failed
|
||||
</span>
|
||||
<button
|
||||
className='button btntheme3 ml-30 mb-2'
|
||||
onClick={() => {
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(
|
||||
d => d.version !== v.version
|
||||
)
|
||||
)
|
||||
}}
|
||||
title='Click to remove this version from this menu.'
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : v.queued ? (
|
||||
<span className='text-yellow-500 inline-block w-full text-center'>
|
||||
Queued…
|
||||
</span>
|
||||
) : v.queued ? (
|
||||
<span className='text-yellow-500 inline-block w-full text-center'>
|
||||
Queued…
|
||||
</span>
|
||||
) : v.hash_checking ? (
|
||||
<span className='text-blue-500 inline-block w-full text-center'>
|
||||
Checking hash...
|
||||
</span>
|
||||
) : v.finishing ? (
|
||||
<span className='text-green-500 inline-block w-full text-center'>
|
||||
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(
|
||||
getVersionInfo(v.version)?.size ?? 0,
|
||||
{
|
||||
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>
|
||||
</>
|
||||
<DownloadsPopup />
|
||||
) : popupMode === 2 ? (
|
||||
managingVersion ? (
|
||||
<>
|
||||
<p className='text-xl text-center'>
|
||||
Manage{' '}
|
||||
{getVersionInfo(managingVersion)?.displayName}
|
||||
</p>
|
||||
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
|
||||
<button
|
||||
className='button btntheme2'
|
||||
disabled={downloadProgress.length != 0}
|
||||
onClick={async () => {
|
||||
closePopup()
|
||||
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
const updatedList = Object.fromEntries(
|
||||
Object.entries(prev.list).filter(
|
||||
([k]) => k !== managingVersion
|
||||
)
|
||||
)
|
||||
const updatedConfig = {
|
||||
...prev,
|
||||
list: updatedList
|
||||
}
|
||||
writeVersionsConfig(updatedConfig)
|
||||
setManagingVersion(null)
|
||||
setFadeOut(true)
|
||||
setTimeout(() => setShowPopup(false), 200)
|
||||
return updatedConfig
|
||||
})
|
||||
|
||||
if (
|
||||
await exists('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
)
|
||||
await remove('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData,
|
||||
recursive: true
|
||||
})
|
||||
}}
|
||||
title='Click to uninstall this game. This will NOT remove any progress or any save files.'
|
||||
>
|
||||
Uninstall
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme2'
|
||||
onClick={async () =>
|
||||
invoke('open_folder', {
|
||||
name: managingVersion
|
||||
})
|
||||
}
|
||||
title="Click to browse the game's files."
|
||||
>
|
||||
Open Folder
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className='text-xl text-center'>
|
||||
No version selected
|
||||
</p>
|
||||
)
|
||||
) : popupMode === 3 ? (
|
||||
managingVersion && downloadedVersionsConfig ? (
|
||||
<VersionInfo />
|
||||
) : (
|
||||
<p className='text-xl text-center'>
|
||||
No version selected
|
||||
</p>
|
||||
)
|
||||
) : popupMode === 4 ? (
|
||||
managingVersion && downloadedVersionsConfig ? (
|
||||
<VersionChangelog />
|
||||
) : (
|
||||
<p className='text-xl text-center'>
|
||||
No version selected
|
||||
</p>
|
||||
)
|
||||
<VersionVersionPopup />
|
||||
) : null}
|
||||
{popupMode == 0 &&
|
||||
selectedGame &&
|
||||
serverVersionList != null && (
|
||||
<div className='flex justify-center'>
|
||||
<button
|
||||
className='button btntheme1 w-fit mt-2 -mb-4'
|
||||
onClick={() => {
|
||||
setFadeOut(true)
|
||||
setTimeout(() => setShowPopup(false), 200)
|
||||
if (downloadedVersionsConfig)
|
||||
downloadVersions(
|
||||
selectedVersionList,
|
||||
downloadedVersionsConfig
|
||||
)
|
||||
}}
|
||||
disabled={downloadProgress.length != 0}
|
||||
title={
|
||||
downloadProgress.length != 0
|
||||
? "You cannot download the versions as you have another download that hasn't completed."
|
||||
: `Click to download ${
|
||||
selectedVersionList.length
|
||||
} version${
|
||||
selectedVersionList.length == 1 ? '' : 's'
|
||||
} of ${getGameInfo(selectedGame)?.name}`
|
||||
}
|
||||
>
|
||||
Download {selectedVersionList.length} version
|
||||
{selectedVersionList.length == 1 ? '' : 's'}
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme1 w-fit mt-2 ml-2 -mb-4'
|
||||
onClick={() => {
|
||||
const allIds = getSpecialVersionsList(
|
||||
selectedGame
|
||||
).map(v => v.id)
|
||||
setSelectedVersionList(prev =>
|
||||
prev.length === allIds.length ? [] : allIds
|
||||
)
|
||||
}}
|
||||
title={
|
||||
selectedVersionList.length ===
|
||||
getSpecialVersionsList(selectedGame).length
|
||||
? 'Click to remove all selected versions for download.'
|
||||
: 'Click to add all selected versions for download.'
|
||||
}
|
||||
>
|
||||
{selectedVersionList.length ===
|
||||
getSpecialVersionsList(selectedGame).length
|
||||
? 'Deselect All'
|
||||
: 'Select All'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useEffect } from 'react'
|
||||
import './Installs.css'
|
||||
import { useGlobal } from './GlobalProvider'
|
||||
import Link from 'next/link'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faCheck,
|
||||
@@ -24,7 +23,8 @@ export default function Installs () {
|
||||
normalConfig,
|
||||
setSelectedGame,
|
||||
getListOfGames,
|
||||
getVersionsAmountData
|
||||
getVersionsAmountData,
|
||||
setCategory
|
||||
} = useGlobal()
|
||||
|
||||
const router = useRouter()
|
||||
@@ -54,7 +54,7 @@ export default function Installs () {
|
||||
<div className='downloads-container'>
|
||||
<div
|
||||
className={`downloads-scroll ${
|
||||
platform() === 'windows'
|
||||
platform() == 'windows'
|
||||
? 'h-[calc(100vh-116px)]'
|
||||
: 'h-[calc(100vh-84px)]'
|
||||
}`}
|
||||
@@ -68,18 +68,10 @@ export default function Installs () {
|
||||
.map(i => (
|
||||
<div
|
||||
key={i.id}
|
||||
className={`downloads-entry ${
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'cursor-pointer'
|
||||
}`}
|
||||
title={
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'Click to view game installs'
|
||||
}
|
||||
className={'downloads-entry'}
|
||||
title={'Click to view game installs'}
|
||||
onClick={() => {
|
||||
if (normalConfig?.settings.useLegacyInteractButtons) return
|
||||
setCategory(-1)
|
||||
router.push('/game?id=' + i.id)
|
||||
}}
|
||||
>
|
||||
@@ -127,15 +119,6 @@ export default function Installs () {
|
||||
<p>{i.verified ? 'Verified' : 'Unverified'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
className='button absolute right-0 bottom-0'
|
||||
href={'/game?id=' + i.id}
|
||||
hidden={!normalConfig?.settings.useLegacyInteractButtons}
|
||||
title='Click to view game installs'
|
||||
>
|
||||
Installs
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Setting } from '../componets/Setting'
|
||||
import { writeNormalConfig } from '../util/BazookaManager'
|
||||
import { useGlobal } from '../GlobalProvider'
|
||||
import { copyToClipboard } from '../util/Clipboard'
|
||||
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 [useLegacyInteractButtons, setUseLegacyInteractButtons] =
|
||||
useState(false)
|
||||
const [useWineOnUnixWhenNeeded, setUseWineOnUnixWhenNeeded] = useState(false)
|
||||
const [wineOnUnixCommand, setWineOnUnixCommand] = useState('wine %path%')
|
||||
const [theme, setTheme] = useState(0)
|
||||
@@ -27,9 +25,6 @@ export default function Settings () {
|
||||
setAlwaysShowGamesInSidebar(
|
||||
normalConfig.settings.alwaysShowGamesInSidebar
|
||||
)
|
||||
setUseLegacyInteractButtons(
|
||||
normalConfig.settings.useLegacyInteractButtons
|
||||
)
|
||||
setUseWineOnUnixWhenNeeded(
|
||||
normalConfig.settings.useWineOnUnixWhenNeeded
|
||||
)
|
||||
@@ -50,24 +45,22 @@ export default function Settings () {
|
||||
label='Allow sending notifications'
|
||||
value={allowNotifications}
|
||||
onChange={async () => {
|
||||
while (normalConfig != null) {
|
||||
setAllowNotifications(!allowNotifications)
|
||||
setNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
allowNotifications: !allowNotifications
|
||||
}
|
||||
})
|
||||
writeNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
allowNotifications: !allowNotifications
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
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.'
|
||||
/>
|
||||
@@ -75,63 +68,45 @@ export default function Settings () {
|
||||
label='Always show games in sidebar'
|
||||
value={alwaysShowGamesInSidebar}
|
||||
onChange={async () => {
|
||||
while (normalConfig != null) {
|
||||
setAlwaysShowGamesInSidebar(!alwaysShowGamesInSidebar)
|
||||
setNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
alwaysShowGamesInSidebar: !alwaysShowGamesInSidebar
|
||||
}
|
||||
})
|
||||
writeNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
alwaysShowGamesInSidebar: !alwaysShowGamesInSidebar
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
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='Show Installs/Launch/Manage Buttons'
|
||||
value={useLegacyInteractButtons}
|
||||
onChange={async () => {
|
||||
while (normalConfig != null) {
|
||||
setUseLegacyInteractButtons(!useLegacyInteractButtons)
|
||||
setNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
useLegacyInteractButtons: !useLegacyInteractButtons
|
||||
}
|
||||
})
|
||||
writeNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
useLegacyInteractButtons: !useLegacyInteractButtons
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}}
|
||||
title='Enable the legacy method of using the installs/launch/manage buttons. In the future this setting may be removed so try and get used to the new method.'
|
||||
/>
|
||||
<Setting
|
||||
label='Use wine when needed to launch games'
|
||||
value={useWineOnUnixWhenNeeded}
|
||||
onChange={async () => {
|
||||
while (normalConfig != null) {
|
||||
setUseWineOnUnixWhenNeeded(!useWineOnUnixWhenNeeded)
|
||||
normalConfig.settings.useWineOnUnixWhenNeeded =
|
||||
!useWineOnUnixWhenNeeded
|
||||
await writeNormalConfig(normalConfig)
|
||||
break
|
||||
}
|
||||
if (!normalConfig) return
|
||||
setUseWineOnUnixWhenNeeded(!useWineOnUnixWhenNeeded)
|
||||
setNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
useWineOnUnixWhenNeeded: !useWineOnUnixWhenNeeded
|
||||
}
|
||||
})
|
||||
await writeNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
useWineOnUnixWhenNeeded: !useWineOnUnixWhenNeeded
|
||||
}
|
||||
})
|
||||
}}
|
||||
className={platform() == 'linux' ? '' : 'hidden'}
|
||||
/>
|
||||
@@ -142,12 +117,22 @@ export default function Settings () {
|
||||
type='text'
|
||||
value={wineOnUnixCommand}
|
||||
onChange={async e => {
|
||||
while (normalConfig != null) {
|
||||
setWineOnUnixCommand(e.target.value)
|
||||
normalConfig.settings.wineOnUnixCommand = e.target.value
|
||||
await writeNormalConfig(normalConfig)
|
||||
break
|
||||
}
|
||||
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'
|
||||
@@ -159,33 +144,29 @@ export default function Settings () {
|
||||
className='ml-2 bg-(--col2) border border-(--col4) rounded-md'
|
||||
value={theme}
|
||||
onChange={async e => {
|
||||
if (!normalConfig) return
|
||||
const newTheme = parseInt(e.target.value)
|
||||
while (normalConfig != null) {
|
||||
setTheme(newTheme)
|
||||
setNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
theme: newTheme
|
||||
}
|
||||
})
|
||||
writeNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
theme: newTheme
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
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}>Green</option>
|
||||
<option value={3}>Blue</option>
|
||||
<option value={4}>Purple</option>
|
||||
<option value={5}>Mint</option>
|
||||
<option value={2}>Blue</option>
|
||||
<option value={3}>Purple</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface LeaderboardEntry {
|
||||
username: string
|
||||
userid: bigint
|
||||
value: bigint
|
||||
icon: number
|
||||
overlay: number
|
||||
birdColor: number[]
|
||||
overlayColor: number[]
|
||||
customIcon: string | null
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { LeaderboardEntry } from './LeaderboardEntry'
|
||||
|
||||
export interface LeaderboardResponse {
|
||||
entries: LeaderboardEntry[]
|
||||
customIcons: Record<string, string>
|
||||
}
|
||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 1.2 MiB |
@@ -1,7 +1,7 @@
|
||||
import './Setting.css'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
import { SettingProps } from '../types/SettingProps'
|
||||
import { SettingProps } from '@/types/SettingProps'
|
||||
|
||||
export function Setting ({
|
||||
label,
|
||||
@@ -13,10 +13,6 @@
|
||||
@apply bg-(--col3) border-(--col4);
|
||||
}
|
||||
|
||||
.logo {
|
||||
@apply flex items-center pl-2 h-10 w-60 pt-2;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
@apply flex flex-col px-4 space-y-1;
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
'use client'
|
||||
|
||||
import './Sidebar.css'
|
||||
import Icon from '@/assets/Icon.png'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faCog,
|
||||
faDownload,
|
||||
faGamepad,
|
||||
faHexagonNodes,
|
||||
faLayerGroup
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faDiscord } from '@fortawesome/free-brands-svg-icons'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { Lexend } from 'next/font/google'
|
||||
import React from 'react'
|
||||
|
||||
const lexend = Lexend({
|
||||
subsets: ['latin']
|
||||
})
|
||||
|
||||
export default function Sidebar () {
|
||||
const {
|
||||
normalConfig,
|
||||
getListOfGames,
|
||||
setShowPopup,
|
||||
setPopupMode,
|
||||
setFadeOut,
|
||||
downloadProgress,
|
||||
downloadedVersionsConfig,
|
||||
getVersionInfo,
|
||||
category,
|
||||
setCategory
|
||||
} = useGlobal()
|
||||
|
||||
const pathname = usePathname()
|
||||
const params = useSearchParams()
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<aside className='sidebar'>
|
||||
<div
|
||||
className='macos-drag'
|
||||
hidden={platform() != 'macos'}
|
||||
onMouseDown={e => {
|
||||
if (e.buttons === 1) {
|
||||
if (e.detail === 2) {
|
||||
getCurrentWindow().toggleMaximize()
|
||||
} else {
|
||||
getCurrentWindow().startDragging()
|
||||
}
|
||||
}
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className={`flex items-center h-10 w-60 ${
|
||||
(platform() == 'windows' ? 'pl-1 pt-1' : 'pl-2 pt-2') +
|
||||
(platform() == 'macos' ? ' mt-7' : '')
|
||||
}`}
|
||||
onMouseDown={e => {
|
||||
if (platform() != 'macos') return
|
||||
if (e.buttons === 1) {
|
||||
if (e.detail === 2) {
|
||||
getCurrentWindow().toggleMaximize()
|
||||
} else {
|
||||
getCurrentWindow().startDragging()
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Image draggable={false} src={Icon} width={36} height={36} alt='' />
|
||||
<p className={`ml-1 text-[16px] whitespace-nowrap ${lexend.className}`}>
|
||||
Lncvrt Games Launcher
|
||||
</p>
|
||||
</div>
|
||||
<nav className='nav-links overflow-auto pt-2'>
|
||||
<Link
|
||||
draggable={false}
|
||||
href='/'
|
||||
className={`link relative flex items-center ${
|
||||
pathname === '/' || pathname.startsWith('/game') ? 'active' : ''
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games
|
||||
</Link>
|
||||
{getListOfGames()
|
||||
.sort((a, b) => {
|
||||
return a.id - b.id
|
||||
})
|
||||
.map(i => (
|
||||
<React.Fragment key={i.id}>
|
||||
<div
|
||||
draggable={false}
|
||||
className={`link ${
|
||||
(pathname === '/game' &&
|
||||
Number(params.get('id') || 0) == i.id) ||
|
||||
(i.id == 1 && pathname === '/game/berrydash/leaderboards')
|
||||
? 'active'
|
||||
: ''
|
||||
} ml-auto w-50 ${
|
||||
normalConfig?.settings.alwaysShowGamesInSidebar ||
|
||||
pathname === '/' ||
|
||||
pathname.startsWith('/game')
|
||||
? ''
|
||||
: 'hidden'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setCategory(-1)
|
||||
router.push('/game?id=' + i.id)
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
Object.entries(i.categoryNames).length > 0
|
||||
? faLayerGroup
|
||||
: faGamepad
|
||||
}
|
||||
className='mr-1'
|
||||
/>
|
||||
<span className='truncate max-w-full'>{i.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
{Object.entries(i.categoryNames)
|
||||
.sort(([a], [b]) => Number(b) - Number(a))
|
||||
.filter(([key]) => {
|
||||
const count = Object.keys(
|
||||
downloadedVersionsConfig?.list ?? {}
|
||||
).filter(v => {
|
||||
const info = getVersionInfo(v)
|
||||
if (!info) return false
|
||||
|
||||
if (
|
||||
platform() == 'linux' &&
|
||||
info.wine &&
|
||||
!normalConfig?.settings.useWineOnUnixWhenNeeded
|
||||
)
|
||||
return false
|
||||
|
||||
return info.game === i.id && info.category === Number(key)
|
||||
}).length
|
||||
|
||||
return count >= 1
|
||||
})
|
||||
.map(([key, value]) => (
|
||||
<div
|
||||
key={`${i.id}-${key}`}
|
||||
draggable={false}
|
||||
className={`link ${
|
||||
(pathname === '/game' &&
|
||||
Number(params.get('id') || 0) == i.id) ||
|
||||
(i.id == 1 &&
|
||||
pathname === '/game/berrydash/leaderboards' &&
|
||||
category == Number(key))
|
||||
? 'active'
|
||||
: ''
|
||||
} ml-auto w-47.5 ${
|
||||
normalConfig?.settings.alwaysShowGamesInSidebar ||
|
||||
pathname === '/' ||
|
||||
pathname.startsWith('/game')
|
||||
? ''
|
||||
: 'hidden'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setCategory(Number(key))
|
||||
router.push('/game?id=' + i.id)
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<FontAwesomeIcon icon={faGamepad} className='mr-1' />
|
||||
<span className='truncate max-w-full'>{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
<Link
|
||||
draggable={false}
|
||||
href='/settings'
|
||||
className={`link ${pathname === '/settings' ? 'active' : ''}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => openUrl('https://games.lncvrt.xyz/discord')}
|
||||
className='link mr-auto'
|
||||
>
|
||||
<FontAwesomeIcon icon={faDiscord} className='mr-1' /> Community
|
||||
</button>
|
||||
</nav>
|
||||
<div
|
||||
className='sidebar-downloads'
|
||||
hidden={downloadProgress.length == 0}
|
||||
onClick={() => {
|
||||
setPopupMode(1)
|
||||
setShowPopup(true)
|
||||
setFadeOut(false)
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
<FontAwesomeIcon icon={faDownload} /> Downloads
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import {
|
||||
faCheck,
|
||||
faCode,
|
||||
faDownload,
|
||||
faShieldHalved,
|
||||
faWarning
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
export default function GamesDownloadPopup () {
|
||||
const { serverVersionList, getVersionsAmountData, setSelectedGame } =
|
||||
useGlobal()
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>Select a game to download</p>
|
||||
<div className='popup-content'>
|
||||
{serverVersionList?.games
|
||||
.filter(v => {
|
||||
const data = getVersionsAmountData(v.id)
|
||||
if (!data) return false
|
||||
if (data.total > 0) return true
|
||||
})
|
||||
.map((v, i) => (
|
||||
<div key={i} className='popup-entry'>
|
||||
<p className='text-2xl'>{v.name}</p>
|
||||
<div className='flex gap-2'>
|
||||
<div
|
||||
className='entry-info-item btntheme3'
|
||||
title='The amount of versions installed of this game in installed/installable format.'
|
||||
>
|
||||
<p>
|
||||
{(() => {
|
||||
const data = getVersionsAmountData(v.id)
|
||||
if (!data) return 'N/A'
|
||||
return `${data.installed}/${data.total}`
|
||||
})()}{' '}
|
||||
versions installed
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme3'
|
||||
hidden={!v.official}
|
||||
title='This game is official.'
|
||||
>
|
||||
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
|
||||
<p>Official</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme3'
|
||||
hidden={v.official}
|
||||
title={
|
||||
v.verified
|
||||
? 'This game is verified to be safe'
|
||||
: 'This game is NOT verified to be save. Proceed with caution.'
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={v.verified ? faShieldHalved : faWarning}
|
||||
color={v.verified ? '#19c84b' : '#ffc800'}
|
||||
/>
|
||||
<p>{v.verified ? 'Verified' : 'Unverified'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme3 mt-2'
|
||||
hidden={v.developer == null}
|
||||
title={`The developer of ${v.name} is ${v.developer}.`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCode} color='lightgray' />
|
||||
<p>Developer: {v.developer}</p>
|
||||
</div>
|
||||
<button
|
||||
className='button btntheme3 right-2 bottom-2'
|
||||
onClick={() => setSelectedGame(v.id)}
|
||||
title={`Click to download specific versions of the game. You have ${(() => {
|
||||
const data = getVersionsAmountData(v.id)
|
||||
if (!data) return 'N/A'
|
||||
return `${data.installed} of ${data.total}`
|
||||
})()} versions downloaded.`}
|
||||
>
|
||||
<>
|
||||
<FontAwesomeIcon icon={faDownload} /> Download
|
||||
</>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faCheck,
|
||||
faCode,
|
||||
faHardDrive,
|
||||
faShieldHalved,
|
||||
faWarning
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { useEffect, useState } from 'react'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import { message } from '@tauri-apps/plugin-dialog'
|
||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||
import { writeVersionsConfig } from '@/lib/BazookaManager'
|
||||
import { openFolder } from '@/lib/Util'
|
||||
|
||||
export default function VersionVersionPopup () {
|
||||
const {
|
||||
getGameInfo,
|
||||
getVersionInfo,
|
||||
managingVersion,
|
||||
downloadedVersionsConfig,
|
||||
viewingInfoFromDownloads,
|
||||
setManagingVersion,
|
||||
closePopup,
|
||||
setDownloadedVersionsConfig,
|
||||
setPopupMode,
|
||||
setSelectedVersionList,
|
||||
downloadVersions
|
||||
} = useGlobal()
|
||||
const [versionSize, setVersionSize] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (viewingInfoFromDownloads) return
|
||||
invoke<string>('folder_size', {
|
||||
version: managingVersion
|
||||
}).then(size => {
|
||||
setVersionSize(Number(size))
|
||||
})
|
||||
}, [managingVersion, setVersionSize, viewingInfoFromDownloads])
|
||||
|
||||
if (!managingVersion || !downloadedVersionsConfig) return <></>
|
||||
|
||||
const versionInfo = getVersionInfo(managingVersion)
|
||||
const gameInfo = getGameInfo(versionInfo?.game)
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>Viewing {versionInfo?.displayName}</p>
|
||||
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
<p>
|
||||
Installed{' '}
|
||||
{new Intl.DateTimeFormat(undefined).format(
|
||||
downloadedVersionsConfig.list[managingVersion] ?? 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={!versionInfo || versionInfo.releaseDate == 0}
|
||||
>
|
||||
<p>
|
||||
Released{' '}
|
||||
{new Intl.DateTimeFormat(undefined).format(
|
||||
versionInfo?.releaseDate ? versionInfo.releaseDate * 1000 : 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className='entry-info-item btntheme2' hidden={!gameInfo?.official}>
|
||||
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
|
||||
<p>Official</p>
|
||||
</div>
|
||||
<div className='entry-info-item btntheme2' hidden={gameInfo?.official}>
|
||||
<FontAwesomeIcon
|
||||
icon={gameInfo?.verified ? faShieldHalved : faWarning}
|
||||
color={gameInfo?.verified ? '#19c84b' : '#ffc800'}
|
||||
/>
|
||||
<p>{gameInfo?.verified ? 'Verified' : 'Unverified'}</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={gameInfo?.developer == null}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCode} color='lightgray' />
|
||||
<p>Developer: {gameInfo?.developer}</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
<FontAwesomeIcon icon={faHardDrive} color='lightgray' />
|
||||
<p>
|
||||
Size on disk:{' '}
|
||||
{versionSize && versionSize > 0
|
||||
? prettyBytes(versionSize, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
: 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
onClick={async () => {
|
||||
if (!versionInfo) return
|
||||
await message(atob(versionInfo.changelog), {
|
||||
title: 'Changelog for ' + versionInfo.displayName,
|
||||
kind: 'info'
|
||||
})
|
||||
}}
|
||||
hidden={!versionInfo?.changelog}
|
||||
>
|
||||
<p>View Changelog</p>
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} color='lightgray' />
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
onClick={async () => openFolder(managingVersion)}
|
||||
title="Click to browse the game's files."
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
Open Folder
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} color='lightgray' />
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
onClick={async () => {
|
||||
closePopup()
|
||||
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
const updatedList = Object.fromEntries(
|
||||
Object.entries(prev.list).filter(([k]) => k !== managingVersion)
|
||||
)
|
||||
const updatedConfig = {
|
||||
...prev,
|
||||
list: updatedList
|
||||
}
|
||||
writeVersionsConfig(updatedConfig)
|
||||
return updatedConfig
|
||||
})
|
||||
|
||||
if (
|
||||
await exists('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
)
|
||||
await remove('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData,
|
||||
recursive: true
|
||||
})
|
||||
}}
|
||||
title='Click to uninstall this game. This will NOT remove any progress or any save files.'
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
Uninstall
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
onClick={async () => {
|
||||
//change popup to downloads
|
||||
setManagingVersion(null)
|
||||
setPopupMode(1)
|
||||
|
||||
//uninstall
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
const updatedList = Object.fromEntries(
|
||||
Object.entries(prev.list).filter(([k]) => k !== managingVersion)
|
||||
)
|
||||
const updatedConfig = {
|
||||
...prev,
|
||||
list: updatedList
|
||||
}
|
||||
writeVersionsConfig(updatedConfig)
|
||||
return updatedConfig
|
||||
})
|
||||
|
||||
if (
|
||||
await exists('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
)
|
||||
await remove('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData,
|
||||
recursive: true
|
||||
})
|
||||
|
||||
//reinstall
|
||||
setSelectedVersionList([managingVersion])
|
||||
downloadVersions([managingVersion])
|
||||
}}
|
||||
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}
|
||||
>
|
||||
Reinstall
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import { faAdd, faInfo, faRemove } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
export default function VersionsDownloadPopup () {
|
||||
const {
|
||||
selectedVersionList,
|
||||
setSelectedVersionList,
|
||||
setManagingVersion,
|
||||
setPopupMode,
|
||||
getSpecialVersionsList,
|
||||
selectedGame,
|
||||
setViewingInfoFromDownloads,
|
||||
downloadedVersionsConfig,
|
||||
downloadVersions,
|
||||
getGameInfo
|
||||
} = useGlobal()
|
||||
if (!selectedGame) return <></>
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>Select versions to download</p>
|
||||
<div className='popup-content'>
|
||||
{getSpecialVersionsList(selectedGame).map((v, i) => (
|
||||
<div key={i} className='popup-entry'>
|
||||
<div className='flex items-center'>
|
||||
<p
|
||||
className={`text-2xl truncate ${
|
||||
selectedVersionList.includes(v.id)
|
||||
? 'max-w-84.5'
|
||||
: 'max-w-91.5'
|
||||
}`}
|
||||
>
|
||||
{v.displayName}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className='button btntheme3 right-20.75 bottom-1.75'
|
||||
onClick={() => {
|
||||
setSelectedVersionList(prev =>
|
||||
prev.includes(v.id)
|
||||
? prev.filter(i => i !== v.id)
|
||||
: [...prev, v.id]
|
||||
)
|
||||
}}
|
||||
title={
|
||||
selectedVersionList.includes(v.id)
|
||||
? 'This version will be downloaded. Click to remove from the list of versions that will be downloaded.'
|
||||
: 'This version will NOT be downloaded. Click to add from the list of versions that will be downloaded.'
|
||||
}
|
||||
>
|
||||
{selectedVersionList.includes(v.id) ? (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faRemove} /> Remove
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faAdd} /> Add
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme3 right-1.5 bottom-1.75'
|
||||
onClick={() => {
|
||||
setManagingVersion(v.id)
|
||||
setViewingInfoFromDownloads(true)
|
||||
setPopupMode(2)
|
||||
}}
|
||||
title='Click to view version info'
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfo} /> Info
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className='flex justify-center'>
|
||||
<button
|
||||
className='button btntheme1 w-fit mt-2 -mb-4'
|
||||
onClick={() => {
|
||||
if (downloadedVersionsConfig) {
|
||||
downloadVersions(selectedVersionList)
|
||||
setPopupMode(1)
|
||||
}
|
||||
}}
|
||||
disabled={selectedVersionList.length == 0}
|
||||
title={
|
||||
selectedVersionList.length == 0
|
||||
? 'Select at least one version to download'
|
||||
: `Download ${selectedVersionList.length} version${
|
||||
selectedVersionList.length == 1 ? '' : 's'
|
||||
} of ${getGameInfo(selectedGame)?.name}`
|
||||
}
|
||||
>
|
||||
Download {selectedVersionList.length} version
|
||||
{selectedVersionList.length == 1 ? '' : 's'}
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme1 w-fit mt-2 ml-2 -mb-4'
|
||||
onClick={() => {
|
||||
const allIds = getSpecialVersionsList(selectedGame).map(v => v.id)
|
||||
setSelectedVersionList(prev =>
|
||||
prev.length === allIds.length ? [] : allIds
|
||||
)
|
||||
}}
|
||||
title={
|
||||
selectedVersionList.length ===
|
||||
getSpecialVersionsList(selectedGame).length
|
||||
? 'Click to remove all selected versions for download.'
|
||||
: 'Click to add all selected versions for download.'
|
||||
}
|
||||
>
|
||||
{selectedVersionList.length ===
|
||||
getSpecialVersionsList(selectedGame).length
|
||||
? 'Deselect All'
|
||||
: 'Select All'}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { app } from '@tauri-apps/api'
|
||||
import { NormalConfig } from '../types/NormalConfig'
|
||||
import { NormalConfig } from '@/types/NormalConfig'
|
||||
import {
|
||||
BaseDirectory,
|
||||
create,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
readTextFile,
|
||||
writeFile
|
||||
} from '@tauri-apps/plugin-fs'
|
||||
import { VersionsConfig } from '../types/VersionsConfig'
|
||||
import { VersionsConfig } from '@/types/VersionsConfig'
|
||||
|
||||
export async function readNormalConfig (): Promise<NormalConfig> {
|
||||
const version = await app.getVersion()
|
||||
@@ -32,7 +32,31 @@ export async function readNormalConfig (): Promise<NormalConfig> {
|
||||
return new NormalConfig(version)
|
||||
}
|
||||
const config = await readTextFile('config.json', options)
|
||||
return NormalConfig.import(JSON.parse(config))
|
||||
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)
|
||||
}
|
||||
@@ -83,8 +107,7 @@ export async function readVersionsConfig (): Promise<VersionsConfig> {
|
||||
}
|
||||
const config = await readTextFile('versions.json', options)
|
||||
const raw = JSON.parse(config)
|
||||
if (raw.version && raw.list && raw.timestamps) {
|
||||
raw.version = version
|
||||
if (raw.list && raw.timestamps) {
|
||||
raw.list = raw.timestamps
|
||||
delete raw.timestamps
|
||||
|
||||
@@ -94,6 +117,8 @@ export async function readVersionsConfig (): Promise<VersionsConfig> {
|
||||
options
|
||||
)
|
||||
}
|
||||
raw.version = version
|
||||
writeVersionsConfig(raw)
|
||||
return VersionsConfig.import(raw)
|
||||
} catch {
|
||||
return new VersionsConfig(version)
|
||||
@@ -0,0 +1,7 @@
|
||||
export const GetIconForUser = (user: number) => {
|
||||
if (user == 1) return -1
|
||||
else if (user == 2) return -2
|
||||
else if (user == 4) return -3
|
||||
else if (user == 3) return -4
|
||||
else return 1
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NormalConfig } from '../types/NormalConfig'
|
||||
import { NormalConfig } from '@/types/NormalConfig'
|
||||
import { notifyUser } from './Notifications'
|
||||
|
||||
export async function copyToClipboard (
|
||||
@@ -0,0 +1,64 @@
|
||||
import { message } from '@tauri-apps/plugin-dialog'
|
||||
import { BaseDirectory, exists, stat } from '@tauri-apps/plugin-fs'
|
||||
import { openPath } from '@tauri-apps/plugin-opener'
|
||||
import { join, appLocalDataDir } from '@tauri-apps/api/path'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export const openFolder = async (name: string) => {
|
||||
const relativePath = await join('game', name)
|
||||
const basePath = await appLocalDataDir()
|
||||
const absolutePath = await join(basePath, relativePath)
|
||||
|
||||
const folderExists = await exists(relativePath, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
|
||||
if (!folderExists) {
|
||||
await message(`Game folder "${absolutePath}" not found.`, {
|
||||
title: 'Folder not found',
|
||||
kind: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const folderStat = await stat(relativePath, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
|
||||
if (folderStat.isFile) {
|
||||
await message(`Game folder "${absolutePath}" not found.`, {
|
||||
title: 'Folder not found',
|
||||
kind: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await openPath(absolutePath)
|
||||
}
|
||||
|
||||
export const formatEtaSmart = (seconds: number) => {
|
||||
if (seconds < 60) return `${Math.floor(seconds)}s`
|
||||
if (seconds < 3600)
|
||||
return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`
|
||||
if (seconds < 86400) {
|
||||
const h = Math.floor(seconds / 3600)
|
||||
const m = Math.floor((seconds % 3600) / 60)
|
||||
return `${h}h ${m}m`
|
||||
}
|
||||
const d = Math.floor(seconds / 86400)
|
||||
const h = Math.floor((seconds % 86400) / 3600)
|
||||
return `${d}d ${h}h`
|
||||
}
|
||||
|
||||
export const verifySignature = async (body: string, signature: string) => {
|
||||
try {
|
||||
const result = await invoke<boolean>('verify_signature', {
|
||||
body: body ?? '',
|
||||
signature: signature ?? '',
|
||||
publicKey: process.env.NEXT_PUBLIC_PUBLIC_SIGNING_KEY ?? ''
|
||||
})
|
||||
return Boolean(result)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export type BirdColor = [number, number, number]
|
||||
@@ -8,6 +8,7 @@ export class DownloadProgress {
|
||||
public hash_checking: boolean,
|
||||
public finishing: boolean,
|
||||
public speed: number,
|
||||
public size: number,
|
||||
public etaSecs: number
|
||||
) {}
|
||||
}
|
||||
@@ -3,13 +3,11 @@ export interface GameVersion {
|
||||
displayName: string
|
||||
releaseDate: number
|
||||
game: number
|
||||
downloadUrl: string
|
||||
executable: string
|
||||
sha512sum: string
|
||||
size: number
|
||||
place: number
|
||||
changelog: string
|
||||
wine: number | undefined
|
||||
category: number
|
||||
lastRevision: number
|
||||
download: number
|
||||
executable: string
|
||||
}
|
||||
@@ -2,7 +2,6 @@ export class SettingsType {
|
||||
constructor (
|
||||
public allowNotifications: boolean = true,
|
||||
public alwaysShowGamesInSidebar: boolean = true,
|
||||
public useLegacyInteractButtons: boolean = false,
|
||||
public useWineOnUnixWhenNeeded: boolean = false,
|
||||
public wineOnUnixCommand: string = 'wine %path%',
|
||||
public theme: number = 0
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"target": "es2024",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
@@ -28,7 +28,9 @@
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
"**/*.mts",
|
||||
"next.config.ts",
|
||||
"eslint.config.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||