Compare commits

...
19 Commits
18 changed files with 475 additions and 202 deletions

No files matched your search

+25 -25
View File
@@ -8,7 +8,7 @@
"@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/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",
@@ -17,7 +17,7 @@
"@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",
"axios": "1.13.5",
"next": "16.1.6",
"pretty-bytes": "7.1.0",
"react": "19.2.4",
@@ -28,8 +28,8 @@
"@tailwindcss/postcss": "4.1.18",
"@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.2.2",
"@types/react": "19.2.13",
"@types/react-dom": "19.2.3",
"crypto-js": "4.2.0",
"eslint": "9.39.2",
@@ -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=="],
@@ -106,7 +106,7 @@
"@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/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=="],
@@ -290,31 +290,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.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
"@types/react": ["@types/react@19.2.10", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw=="],
"@types/react": ["@types/react@19.2.13", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ=="],
"@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.55.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/type-utils": "8.55.0", "@typescript-eslint/utils": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ=="],
"@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.55.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw=="],
"@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.55.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.55.0", "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ=="],
"@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.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0" } }, "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q=="],
"@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.55.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q=="],
"@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.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0", "@typescript-eslint/utils": "8.55.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-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="],
"@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.55.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.55.0", "@typescript-eslint/tsconfig-utils": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.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-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw=="],
"@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.55.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow=="],
"@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.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA=="],
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
@@ -392,7 +392,7 @@
"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=="],
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
@@ -414,7 +414,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.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -562,7 +562,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=="],
@@ -900,7 +900,7 @@
"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.55.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.55.0", "@typescript-eslint/parser": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0", "@typescript-eslint/utils": "8.55.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw=="],
"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=="],
@@ -952,7 +952,7 @@
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@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=="],
"eslint-config-next/globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
@@ -966,13 +966,13 @@
"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=="],
}
+2 -1
View File
@@ -11,7 +11,8 @@ const eslintConfig = defineConfig([
'.next/**',
'out/**',
'build/**',
'next-env.d.ts'
'next-env.d.ts',
'src-tauri/**'
])
])
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "lncvrt-games-launcher",
"private": true,
"version": "1.5.4",
"version": "1.5.7",
"type": "module",
"scripts": {
"dev": "next dev",
@@ -14,7 +14,7 @@
"@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/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",
@@ -23,7 +23,7 @@
"@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",
"axios": "1.13.5",
"next": "16.1.6",
"pretty-bytes": "7.1.0",
"react": "19.2.4",
@@ -34,8 +34,8 @@
"@tailwindcss/postcss": "4.1.18",
"@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.2.2",
"@types/react": "19.2.13",
"@types/react-dom": "19.2.3",
"crypto-js": "4.2.0",
"eslint": "9.39.2",
+8 -8
View File
@@ -1,6 +1,6 @@
[package]
name = "lncvrt-games-launcher"
version = "1.5.4"
version = "1.5.7"
authors = ["Lncvrt"]
edition = "2024"
@@ -9,24 +9,24 @@ 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"] }
tauri-plugin-decorum = "1.1.1"
tauri-plugin-fs = "2.4.5"
zip = "7.2.0"
libc = "0.2.180"
zip = "7.4.0"
libc = "0.2.181"
tauri-plugin-dialog = "2.6.0"
tauri-plugin-notification = "2.3.3"
sysinfo = "0.38.0"
sysinfo = "0.38.1"
sha2 = "0.10.9"
tauri-plugin-clipboard-manager = "2.3.2"
@@ -34,7 +34,7 @@ tauri-plugin-clipboard-manager = "2.3.2"
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"
+31 -29
View File
@@ -22,6 +22,9 @@ 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();
@@ -37,23 +40,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))?;
}
}
}
@@ -225,31 +252,6 @@ async fn download(
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();
}
return "1".to_string();
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher",
"version": "1.5.4",
"version": "1.5.7",
"identifier": "xyz.lncvrt.games-launcher",
"build": {
"beforeDevCommand": "next dev",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher",
"version": "1.5.4",
"version": "1.5.7",
"identifier": "xyz.lncvrt.games-launcher",
"build": {
"beforeDevCommand": "next dev",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher",
"version": "1.5.4",
"version": "1.5.7",
"identifier": "xyz.lncvrt.games-launcher",
"build": {
"beforeDevCommand": "next dev",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher",
"version": "1.5.4",
"version": "1.5.7",
"identifier": "xyz.lncvrt.games-launcher",
"build": {
"beforeDevCommand": "next dev",
+19 -12
View File
@@ -1,6 +1,12 @@
'use client'
import { createContext, useContext, ReactNode } from 'react'
import {
createContext,
useContext,
ReactNode,
Dispatch,
SetStateAction
} from 'react'
import { DownloadProgress } from './types/DownloadProgress'
import { VersionsConfig } from './types/VersionsConfig'
import { NormalConfig } from './types/NormalConfig'
@@ -11,31 +17,32 @@ 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>
}
const GlobalCtx = createContext<GlobalCtxType | null>(null)
-24
View File
@@ -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;
}
+4 -4
View File
@@ -23,13 +23,13 @@ export default function VersionInfo () {
viewingInfoFromDownloads,
setPopupMode
} = useGlobal()
const [versionSize, setVersionSize] = useState<number | null>(null)
const [versionSize, setVersionSize] = useState<number>(0)
useEffect(() => {
invoke<string>('folder_size', {
version: managingVersion
}).then(size => {
setVersionSize(parseInt(size, 10))
setVersionSize(Number(size))
})
}, [managingVersion, setVersionSize])
@@ -91,12 +91,12 @@ export default function VersionInfo () {
<FontAwesomeIcon icon={faHardDrive} color='lightgray' />
<p>
Size on disk:{' '}
{versionSize !== null
{versionSize > 0
? prettyBytes(versionSize, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
: 'Loading...'}
: 'N/A'}
</p>
</div>
<div className='entry-info-item btntheme2' hidden={!versionInfo}>
+105
View File
@@ -0,0 +1,105 @@
'use client'
import { invoke } from '@tauri-apps/api/core'
import { useGlobal } from '../GlobalProvider'
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
import { writeVersionsConfig } from '../util/BazookaManager'
import { useState } from 'react'
export default function VersionUpdateWarning () {
const [confirmed, setConfirmed] = useState<-1 | 0>(-1)
const {
managingVersion,
setDownloadedVersionsConfig,
setManagingVersion,
setPopupMode,
setSelectedVersionList,
downloadVersions
} = useGlobal()
if (!managingVersion) return <p>Error</p>
return (
<>
<p className='text-xl text-center'>Warning!</p>
<div className='popup-content text-center p-2 relative'>
<p className='mb-2'>
Before proceeding, please note that any modifications to the
installation directory <b>(NOT THE SAVE DATA)</b> will be completely
wiped/reset.
</p>
<p>
If you do not want your installation directory wiped just yet, please
backup the files to another directory. When you click update, it will
be wiped.
</p>
<p className='my-2'>
Updating will have the same effect as clicking the uninstall button
then installing again.
</p>
<p>Revisions are not a frequent thing and rarely ever happen.</p>
<div className='flex flex-row gap-2 absolute bottom-2 left-1/2 -translate-x-1/2 w-max'>
<button
className='button btntheme2'
onClick={async () =>
invoke('open_folder', {
name: managingVersion
})
}
title="Click to browse the game's files."
>
Open Folder / Installation directory
</button>
<button
className='button btntheme2'
onClick={async () => {
if (confirmed == -1) {
setConfirmed(0)
return
}
//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 update the game'
>
<span className='text-red-500 font-bold underline'>
{confirmed == -1 ? 'Update' : 'Are you sure?'}
</span>
</button>
</div>
</div>
</>
)
}
+89 -8
View File
@@ -45,6 +45,21 @@ export default function Installs () {
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'>
@@ -107,7 +122,7 @@ export default function Installs () {
.map(([key, value]) => {
return (
<div
key={value}
key={key}
className={`downloads-entry ${
normalConfig?.settings.useLegacyInteractButtons
? ''
@@ -203,17 +218,32 @@ export default function Installs () {
<div
key={entry}
className={`downloads-entry ${
normalConfig?.settings.useLegacyInteractButtons
normalConfig?.settings.useLegacyInteractButtons ||
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
? ''
: 'cursor-pointer'
}`}
title={
normalConfig?.settings.useLegacyInteractButtons
normalConfig?.settings.useLegacyInteractButtons ||
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
? ''
: 'Click to launch game. Right-click to manage this version install'
}
onClick={async () => {
if (normalConfig?.settings.useLegacyInteractButtons) return
if (
normalConfig?.settings.useLegacyInteractButtons ||
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
)
return
const verInfo = getVersionInfo(entry)
if (verInfo == undefined) return
const gameInfo = getGameInfo(verInfo.game)
@@ -232,7 +262,14 @@ export default function Installs () {
}}
onContextMenu={e => {
e.preventDefault()
if (normalConfig?.settings.useLegacyInteractButtons) return
if (
normalConfig?.settings.useLegacyInteractButtons ||
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
)
return
setManagingVersion(entry)
setPopupMode(2)
@@ -242,7 +279,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'>
@@ -272,6 +309,19 @@ export default function Installs () {
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
<p>Uses wine</p>
</div>
<div
className='entry-info-item'
onClick={e => e.stopPropagation()}
hidden={
!needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
}
>
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
<p>Needs revision update!</p>
</div>
</div>
<div className='flex gap-2 absolute right-0 bottom-0'>
@@ -284,6 +334,10 @@ export default function Installs () {
setShowPopup(true)
setFadeOut(false)
}}
hidden={needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)}
title='Click to view version info'
>
View Info
@@ -291,7 +345,11 @@ export default function Installs () {
<button
className='button'
hidden={
!normalConfig?.settings.useLegacyInteractButtons
!normalConfig?.settings.useLegacyInteractButtons ||
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
}
onClick={e => {
e.stopPropagation()
@@ -326,12 +384,35 @@ export default function Installs () {
})
}}
hidden={
!normalConfig?.settings.useLegacyInteractButtons
!normalConfig?.settings.useLegacyInteractButtons ||
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
}
title='Click to launch game'
>
Launch
</button>
<button
className='button'
onClick={e => {
e.stopPropagation()
setManagingVersion(entry)
setPopupMode(5)
setShowPopup(true)
setFadeOut(false)
}}
hidden={
!needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
}
title='Click to update the game'
>
Update
</button>
</div>
</div>
</div>
+114 -49
View File
@@ -1,6 +1,6 @@
'use client'
import { useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import Sidebar from './componets/Sidebar'
import './Globals.css'
import { DownloadProgress } from './types/DownloadProgress'
@@ -46,6 +46,7 @@ import {
} from '@tauri-apps/plugin-notification'
import VersionChangelog from './componets/VersionChangelog'
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
import VersionUpdateWarning from './componets/VersionUpdateWarning'
const roboto = Roboto({
subsets: ['latin']
@@ -111,15 +112,21 @@ export default function RootLayout ({
})
}
function getVersionInfo (id: string | undefined): GameVersion | undefined {
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 {
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 []
@@ -292,10 +299,8 @@ export default function RootLayout ({
return () => document.removeEventListener('contextmenu', handler)
}, [])
async function downloadVersions (
list: string[],
currentConfig: VersionsConfig
): Promise<void> {
const downloadVersions = useCallback(
async (list: string[]): Promise<void> => {
if (list.length === 0) return
setSelectedVersionList([])
@@ -346,13 +351,20 @@ export default function RootLayout ({
setDownloadProgress(prev =>
prev.filter(d => d.version !== download.version)
)
const date = Date.now()
const newConfig = {
...currentConfig,
list: { ...currentConfig.list, [download.version]: date }
setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updated = {
...prev,
list: {
...prev.list,
[download.version]: Date.now()
}
setDownloadedVersionsConfig(newConfig)
writeVersionsConfig(newConfig)
}
writeVersionsConfig(updated)
return updated
})
} else {
setDownloadProgress(prev =>
prev.map(d =>
@@ -374,37 +386,37 @@ export default function RootLayout ({
setFadeOut(true)
setTimeout(() => setShowPopup(false), 200)
}
},
[getGameInfo, getVersionInfo, normalConfig]
)
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,13 +426,9 @@ 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'
}
>
@@ -471,7 +479,8 @@ export default function RootLayout ({
setSelectedGame,
getVersionsAmountData,
viewingInfoFromDownloads,
version
version,
downloadVersions
}}
>
<div
@@ -591,7 +600,13 @@ export default function RootLayout ({
Select a game to download
</p>
<div className='popup-content'>
{serverVersionList?.games.map((v, i) => (
{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'>
@@ -601,7 +616,9 @@ export default function RootLayout ({
>
<p>
{(() => {
const data = getVersionsAmountData(v.id)
const data = getVersionsAmountData(
v.id
)
if (!data) return 'N/A'
return `${data.installed}/${data.total}`
})()}{' '}
@@ -630,9 +647,13 @@ export default function RootLayout ({
>
<FontAwesomeIcon
icon={
v.verified ? faShieldHalved : faWarning
v.verified
? faShieldHalved
: faWarning
}
color={
v.verified ? '#19c84b' : '#ffc800'
}
color={v.verified ? '#19c84b' : '#ffc800'}
/>
<p>
{v.verified ? 'Verified' : 'Unverified'}
@@ -779,9 +800,6 @@ export default function RootLayout ({
list: updatedList
}
writeVersionsConfig(updatedConfig)
setManagingVersion(null)
setFadeOut(true)
setTimeout(() => setShowPopup(false), 200)
return updatedConfig
})
@@ -799,6 +817,48 @@ export default function RootLayout ({
>
Uninstall
</button>
<button
className='button btntheme2'
disabled={downloadProgress.length != 0}
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."
>
Reinstall
</button>
<button
className='button btntheme2'
onClick={async () =>
@@ -833,6 +893,14 @@ export default function RootLayout ({
No version selected
</p>
)
) : popupMode === 5 ? (
managingVersion && downloadedVersionsConfig ? (
<VersionUpdateWarning />
) : (
<p className='text-xl text-center'>
No version selected
</p>
)
) : null}
{popupMode == 0 &&
selectedGame &&
@@ -844,10 +912,7 @@ export default function RootLayout ({
setFadeOut(true)
setTimeout(() => setShowPopup(false), 200)
if (downloadedVersionsConfig)
downloadVersions(
selectedVersionList,
downloadedVersionsConfig
)
downloadVersions(selectedVersionList)
}}
disabled={downloadProgress.length != 0}
title={
+38 -29
View File
@@ -50,7 +50,7 @@ export default function Settings () {
label='Allow sending notifications'
value={allowNotifications}
onChange={async () => {
while (normalConfig != null) {
if (!normalConfig) return
setAllowNotifications(!allowNotifications)
setNormalConfig({
...normalConfig,
@@ -59,15 +59,13 @@ export default function Settings () {
allowNotifications: !allowNotifications
}
})
writeNormalConfig({
await writeNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
allowNotifications: !allowNotifications
}
})
break
}
}}
title='This setting does as you expect, allow the launcher to send notifications for when stuff like downloading is done.'
/>
@@ -75,7 +73,7 @@ export default function Settings () {
label='Always show games in sidebar'
value={alwaysShowGamesInSidebar}
onChange={async () => {
while (normalConfig != null) {
if (!normalConfig) return
setAlwaysShowGamesInSidebar(!alwaysShowGamesInSidebar)
setNormalConfig({
...normalConfig,
@@ -84,15 +82,13 @@ export default function Settings () {
alwaysShowGamesInSidebar: !alwaysShowGamesInSidebar
}
})
writeNormalConfig({
await writeNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
alwaysShowGamesInSidebar: !alwaysShowGamesInSidebar
}
})
break
}
}}
title="This setting will make it so when you are on a page like this, the games won't disappear."
/>
@@ -100,7 +96,7 @@ export default function Settings () {
label='Show Installs/Launch/Manage Buttons'
value={useLegacyInteractButtons}
onChange={async () => {
while (normalConfig != null) {
if (!normalConfig) return
setUseLegacyInteractButtons(!useLegacyInteractButtons)
setNormalConfig({
...normalConfig,
@@ -109,15 +105,13 @@ export default function Settings () {
useLegacyInteractButtons: !useLegacyInteractButtons
}
})
writeNormalConfig({
await 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.'
/>
@@ -125,13 +119,22 @@ export default function Settings () {
label='Use wine when needed to launch games'
value={useWineOnUnixWhenNeeded}
onChange={async () => {
while (normalConfig != null) {
if (!normalConfig) return
setUseWineOnUnixWhenNeeded(!useWineOnUnixWhenNeeded)
normalConfig.settings.useWineOnUnixWhenNeeded =
!useWineOnUnixWhenNeeded
await writeNormalConfig(normalConfig)
break
setNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
useWineOnUnixWhenNeeded: !useWineOnUnixWhenNeeded
}
})
await writeNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
useWineOnUnixWhenNeeded: !useWineOnUnixWhenNeeded
}
})
}}
className={platform() == 'linux' ? '' : 'hidden'}
/>
@@ -142,12 +145,22 @@ export default function Settings () {
type='text'
value={wineOnUnixCommand}
onChange={async e => {
while (normalConfig != null) {
if (!normalConfig) return
setWineOnUnixCommand(e.target.value)
normalConfig.settings.wineOnUnixCommand = e.target.value
await writeNormalConfig(normalConfig)
break
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,8 +172,8 @@ 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,
@@ -169,23 +182,19 @@ export default function Settings () {
theme: newTheme
}
})
writeNormalConfig({
await writeNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
theme: newTheme
}
})
break
}
}}
>
<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>
+28 -3
View File
@@ -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)
+3 -1
View File
@@ -28,7 +28,9 @@
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
"**/*.mts",
"next.config.ts",
"eslint.config.ts"
],
"exclude": ["node_modules"]
}