Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52134c2200 | |||
| 892ea44c17 | |||
| ca612797ee | |||
| 787962c6d0 | |||
| 09434fcc5b | |||
| edbe04b9fc |
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Firefly Shelter
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
5
Makefile
@@ -22,6 +22,11 @@ build:
|
||||
wails3 build
|
||||
@echo Done!
|
||||
|
||||
generate:
|
||||
@echo Generating bindings...
|
||||
wails3 generate bindings -ts
|
||||
@echo Done!
|
||||
|
||||
release:
|
||||
@echo Building release application...
|
||||
wails3 package
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
// @ts-ignore: Unused imports
|
||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||
|
||||
/**
|
||||
* @returns {$CancellablePromise<[boolean, string]>}
|
||||
*/
|
||||
export function CloseApp() {
|
||||
return $Call.ByID(3422460836);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} timeout
|
||||
* @returns {$CancellablePromise<[boolean, string]>}
|
||||
@@ -20,3 +27,31 @@ export function CloseAppAfterTimeout(timeout) {
|
||||
export function GetCurrentLauncherVersion() {
|
||||
return $Call.ByID(3575133982);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {$CancellablePromise<[boolean, string]>}
|
||||
*/
|
||||
export function HideApp() {
|
||||
return $Call.ByID(88003266);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {$CancellablePromise<[boolean, string]>}
|
||||
*/
|
||||
export function MaximizeApp() {
|
||||
return $Call.ByID(1257306588);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {$CancellablePromise<[boolean, string]>}
|
||||
*/
|
||||
export function MinimizeApp() {
|
||||
return $Call.ByID(3434614194);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {$CancellablePromise<[boolean, string]>}
|
||||
*/
|
||||
export function RestoreApp() {
|
||||
return $Call.ByID(3115625834);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,22 @@ export function FileExistsInZip(archivePath, fileInside) {
|
||||
return $Call.ByID(2509699047, archivePath, fileInside);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {$CancellablePromise<string>}
|
||||
*/
|
||||
export function GetDir(path) {
|
||||
return $Call.ByID(1744445742, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} paths
|
||||
* @returns {$CancellablePromise<string>}
|
||||
*/
|
||||
export function Join(...paths) {
|
||||
return $Call.ByID(2460588289, paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {$CancellablePromise<[boolean, string]>}
|
||||
@@ -54,6 +70,14 @@ export function PickFolder() {
|
||||
return $Call.ByID(3654471460);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {$CancellablePromise<void>}
|
||||
*/
|
||||
export function RemoveFile(path) {
|
||||
return $Call.ByID(3206735043, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {$CancellablePromise<boolean>}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="dracula">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
352
frontend/package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "react-ts-latest",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@tanstack/react-router": "^1.131.27",
|
||||
"@tanstack/react-router-devtools": "^1.131.27",
|
||||
"lucide-react": "^0.541.0",
|
||||
@@ -16,8 +16,9 @@
|
||||
"path-browserify": "^1.0.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-easy-crop": "^5.5.3",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -30,7 +31,7 @@
|
||||
"@typescript-eslint/parser": "^8.40.0",
|
||||
"@vitejs/plugin-react": "^5.0.1",
|
||||
"@wailsio/runtime": "^3.0.0-alpha.66",
|
||||
"daisyui": "^5.1.27",
|
||||
"daisyui": "^5.4.4",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
@@ -1160,18 +1161,6 @@
|
||||
"url": "https://github.com/sponsors/nzakas"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/fs-minipass": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minipass": "^7.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
@@ -1285,52 +1274,47 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.14.tgz",
|
||||
"integrity": "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.16.tgz",
|
||||
"integrity": "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.4",
|
||||
"enhanced-resolve": "^5.18.3",
|
||||
"jiti": "^2.6.0",
|
||||
"lightningcss": "1.30.1",
|
||||
"jiti": "^2.6.1",
|
||||
"lightningcss": "1.30.2",
|
||||
"magic-string": "^0.30.19",
|
||||
"source-map-js": "^1.2.1",
|
||||
"tailwindcss": "4.1.14"
|
||||
"tailwindcss": "4.1.16"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.14.tgz",
|
||||
"integrity": "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==",
|
||||
"hasInstallScript": true,
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz",
|
||||
"integrity": "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.4",
|
||||
"tar": "^7.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "4.1.14",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.1.14",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.1.14",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.14",
|
||||
"@tailwindcss/oxide-wasm32-wasi": "4.1.14",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.14",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.14"
|
||||
"@tailwindcss/oxide-android-arm64": "4.1.16",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.1.16",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.1.16",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.1.16",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.16",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.16",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.16",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.16",
|
||||
"@tailwindcss/oxide-wasm32-wasi": "4.1.16",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.16",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.16"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.14.tgz",
|
||||
"integrity": "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.16.tgz",
|
||||
"integrity": "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1344,9 +1328,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.14.tgz",
|
||||
"integrity": "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.16.tgz",
|
||||
"integrity": "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1360,9 +1344,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.14.tgz",
|
||||
"integrity": "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.16.tgz",
|
||||
"integrity": "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1376,9 +1360,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.14.tgz",
|
||||
"integrity": "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.16.tgz",
|
||||
"integrity": "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1392,9 +1376,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.14.tgz",
|
||||
"integrity": "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.16.tgz",
|
||||
"integrity": "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -1408,9 +1392,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.14.tgz",
|
||||
"integrity": "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.16.tgz",
|
||||
"integrity": "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1424,9 +1408,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.14.tgz",
|
||||
"integrity": "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.16.tgz",
|
||||
"integrity": "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1440,9 +1424,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.14.tgz",
|
||||
"integrity": "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.16.tgz",
|
||||
"integrity": "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1456,9 +1440,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.14.tgz",
|
||||
"integrity": "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.16.tgz",
|
||||
"integrity": "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1472,9 +1456,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.14.tgz",
|
||||
"integrity": "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.16.tgz",
|
||||
"integrity": "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==",
|
||||
"bundleDependencies": [
|
||||
"@napi-rs/wasm-runtime",
|
||||
"@emnapi/core",
|
||||
@@ -1492,7 +1476,7 @@
|
||||
"@emnapi/core": "^1.5.0",
|
||||
"@emnapi/runtime": "^1.5.0",
|
||||
"@emnapi/wasi-threads": "^1.1.0",
|
||||
"@napi-rs/wasm-runtime": "^1.0.5",
|
||||
"@napi-rs/wasm-runtime": "^1.0.7",
|
||||
"@tybys/wasm-util": "^0.10.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
@@ -1501,9 +1485,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.14.tgz",
|
||||
"integrity": "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz",
|
||||
"integrity": "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1517,9 +1501,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.14.tgz",
|
||||
"integrity": "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.16.tgz",
|
||||
"integrity": "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1533,14 +1517,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.14.tgz",
|
||||
"integrity": "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.16.tgz",
|
||||
"integrity": "sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tailwindcss/node": "4.1.14",
|
||||
"@tailwindcss/oxide": "4.1.14",
|
||||
"tailwindcss": "4.1.14"
|
||||
"@tailwindcss/node": "4.1.16",
|
||||
"@tailwindcss/oxide": "4.1.16",
|
||||
"tailwindcss": "4.1.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.2.0 || ^6 || ^7"
|
||||
@@ -2440,15 +2424,6 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
@@ -2520,9 +2495,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/daisyui": {
|
||||
"version": "5.1.27",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.1.27.tgz",
|
||||
"integrity": "sha512-oCC2RwjrGRPTeEpaXoNmYydzVfSRdyJCthFsi/Q5PErAvaoD8kDarxlW+wiPAx2WmM8W8lXvyAHJUqjObCMWOQ==",
|
||||
"version": "5.4.4",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.4.4.tgz",
|
||||
"integrity": "sha512-FEfdwmGdb3ZbtYN3OqaZn/RMarlZTPOv6mWpGUo7KX42RkcBanEZk5tgYp2ZgirKl7QvH8pCiwpuy55wi2dHyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -3356,9 +3331,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
@@ -3371,22 +3346,43 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-darwin-arm64": "1.30.1",
|
||||
"lightningcss-darwin-x64": "1.30.1",
|
||||
"lightningcss-freebsd-x64": "1.30.1",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.30.1",
|
||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
||||
"lightningcss-linux-arm64-musl": "1.30.1",
|
||||
"lightningcss-linux-x64-gnu": "1.30.1",
|
||||
"lightningcss-linux-x64-musl": "1.30.1",
|
||||
"lightningcss-win32-arm64-msvc": "1.30.1",
|
||||
"lightningcss-win32-x64-msvc": "1.30.1"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-android-arm64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
|
||||
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
|
||||
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
|
||||
"integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3404,9 +3400,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
|
||||
"integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
|
||||
"integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3424,9 +3420,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
|
||||
"integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
|
||||
"integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3444,9 +3440,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
|
||||
"integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
|
||||
"integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3464,9 +3460,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
|
||||
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
|
||||
"integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3484,9 +3480,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
|
||||
"integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
|
||||
"integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3504,9 +3500,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
|
||||
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
||||
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3524,9 +3520,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
|
||||
"integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
|
||||
"integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3544,9 +3540,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
|
||||
"integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
|
||||
"integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3564,9 +3560,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
|
||||
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
|
||||
"integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3626,9 +3622,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.19",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
|
||||
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
@@ -3674,27 +3670,6 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
|
||||
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/motion": {
|
||||
"version": "12.23.22",
|
||||
"resolved": "https://registry.npmjs.org/motion/-/motion-12.23.22.tgz",
|
||||
@@ -3783,6 +3758,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-wheel": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
|
||||
"integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -4000,6 +3981,20 @@
|
||||
"react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-easy-crop": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.5.3.tgz",
|
||||
"integrity": "sha512-iKwFTnAsq+IVuyF6N0Q3zjRx9DG1NMySkwWxVfM/xAOeHYH1vhvM+V2kFiq5HOIQGWouITjfltCx54mbDpMpmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.4.0",
|
||||
"react-dom": ">=16.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
@@ -4261,9 +4256,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
||||
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
|
||||
"version": "4.1.16",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
|
||||
"integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
@@ -4279,31 +4274,6 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz",
|
||||
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"minizlib": "^3.1.0",
|
||||
"yallist": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/yallist": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@tanstack/react-router": "^1.131.27",
|
||||
"@tanstack/react-router-devtools": "^1.131.27",
|
||||
"lucide-react": "^0.541.0",
|
||||
@@ -19,8 +19,9 @@
|
||||
"path-browserify": "^1.0.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-easy-crop": "^5.5.3",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -33,7 +34,7 @@
|
||||
"@typescript-eslint/parser": "^8.40.0",
|
||||
"@vitejs/plugin-react": "^5.0.1",
|
||||
"@wailsio/runtime": "^3.0.0-alpha.66",
|
||||
"daisyui": "^5.1.27",
|
||||
"daisyui": "^5.4.4",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
|
||||
BIN
frontend/public/bg-1.jpeg
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
frontend/public/bg-10.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
frontend/public/bg-11.jpeg
Normal file
|
After Width: | Height: | Size: 386 KiB |
BIN
frontend/public/bg-12.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
frontend/public/bg-13.jpg
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
frontend/public/bg-16.jpg
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
frontend/public/bg-2.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
frontend/public/bg-3.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
frontend/public/bg-5.jpeg
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
frontend/public/bg-6.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
frontend/public/bg-7.jpeg
Normal file
|
After Width: | Height: | Size: 349 KiB |
BIN
frontend/public/bg-8.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
frontend/public/bg-9.jpeg
Normal file
|
After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
BIN
frontend/public/heart-hsr.gif
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
180
frontend/src/components/backgroudModal/index.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useRef } from 'react'
|
||||
import { X, Image as ImageIcon, Plus, Upload, Check } from 'lucide-react'
|
||||
import useSettingStore from '@/stores/settingStore'
|
||||
import Cropper from 'react-easy-crop'
|
||||
import getCroppedImg from '@/utils/cropImage'
|
||||
|
||||
const initialImages = {
|
||||
"bg-1": "bg-1.jpeg",
|
||||
"bg-2": "bg-2.png",
|
||||
"bg-3": "bg-3.png",
|
||||
"bg-6": "bg-6.png",
|
||||
"bg-7": "bg-7.jpeg",
|
||||
"bg-8": "bg-8.png",
|
||||
"bg-9": "bg-9.jpeg",
|
||||
"bg-10": "bg-10.jpg",
|
||||
"bg-11": "bg-11.jpeg",
|
||||
"bg-12": "bg-12.jpg",
|
||||
"bg-13": "bg-13.jpg",
|
||||
"bg-16": "bg-16.jpg",
|
||||
}
|
||||
|
||||
export const BackgroundSelector = () => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [newUrl, setNewUrl] = useState('')
|
||||
const [croppingImage, setCroppingImage] = useState<string | null>(null)
|
||||
const [crop, setCrop] = useState({ x: 0, y: 0 })
|
||||
const [zoom, setZoom] = useState(1)
|
||||
const [croppedAreaPixels, setCroppedAreaPixels] = useState<any>(null)
|
||||
const { background, setBackground, extraBackgrounds, setExtraBackgrounds } = useSettingStore()
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const handleSelect = (img: string) => {
|
||||
setIsOpen(false)
|
||||
setBackground(img)
|
||||
}
|
||||
|
||||
const handleAddUrl = () => {
|
||||
if (!newUrl.trim()) return setCroppingImage(newUrl)
|
||||
setNewUrl('')
|
||||
}
|
||||
|
||||
const handleRemoveExtra = (url: string) => {
|
||||
setExtraBackgrounds(extraBackgrounds.filter(bg => bg !== url))
|
||||
}
|
||||
|
||||
const handleUploadFile = (file: File) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => setCroppingImage(reader.result as string)
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
const handleCropComplete = async () => {
|
||||
if (!croppingImage || !croppedAreaPixels) return
|
||||
const croppedBase64 = await getCroppedImg(croppingImage, croppedAreaPixels)
|
||||
setExtraBackgrounds([croppedBase64, ...extraBackgrounds])
|
||||
setCroppingImage(null)
|
||||
}
|
||||
|
||||
const allBackgrounds = [...extraBackgrounds, ...Object.values(initialImages)]
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<div className="tooltip tooltip-right" data-tip="Select Background">
|
||||
<button
|
||||
className="group btn btn-primary btn-circle flex items-center justify-center shadow-md transition-all duration-300 hover:scale-110 hover:shadow-lg hover:bg-primary/80"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<ImageIcon size={22} className="text-white transition-all duration-300 group-hover:rotate-6 group-hover:scale-110 group-hover:text-yellow-300" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div className="fixed inset-0 z-40 flex items-center justify-center bg-base-200/60 pt-10">
|
||||
<div className="bg-base-200 text-white rounded-xl shadow-xl p-6 w-[90%] max-w-2xl relative">
|
||||
<button className="btn btn-ghost btn-circle absolute top-3 right-3" onClick={() => setIsOpen(false)}>
|
||||
<X size={20} />
|
||||
</button>
|
||||
|
||||
<h2 className="text-lg font-semibold mb-4">Choose Background</h2>
|
||||
|
||||
{/* Add via URL */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Paste image URL (https://...)"
|
||||
className="input input-bordered w-full text-info"
|
||||
value={newUrl}
|
||||
onChange={(e) => setNewUrl(e.target.value)}
|
||||
/>
|
||||
<button className="btn btn-success flex items-center gap-1" onClick={handleAddUrl}>
|
||||
<Plus size={16} /> Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Upload from computer */}
|
||||
<div className="flex mb-4">
|
||||
<button
|
||||
className="btn btn-warning flex items-center gap-1"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
>
|
||||
<Upload size={16} /> Upload from computer
|
||||
</button>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
ref={fileInputRef}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (file) handleUploadFile(file)
|
||||
e.target.value = ''
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Crop Modal */}
|
||||
{croppingImage && (
|
||||
<div className="fixed inset-0 z-60 flex flex-col items-center justify-center bg-black/70 p-4">
|
||||
<div className="relative w-full max-w-3xl h-[400px] bg-gray-800 rounded-lg">
|
||||
<Cropper
|
||||
image={croppingImage}
|
||||
crop={crop}
|
||||
zoom={zoom}
|
||||
aspect={16 / 9}
|
||||
onCropChange={setCrop}
|
||||
onZoomChange={setZoom}
|
||||
onCropComplete={(_, croppedAreaPixels) => setCroppedAreaPixels(croppedAreaPixels)}
|
||||
/>
|
||||
<button
|
||||
className="absolute bottom-4 left-1/2 -translate-x-1/2 btn btn-success"
|
||||
onClick={handleCropComplete}
|
||||
>
|
||||
<Check size={20} /> Done
|
||||
</button>
|
||||
<button
|
||||
className="absolute top-2 right-2 btn btn-ghost btn-circle"
|
||||
onClick={() => setCroppingImage(null)}
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-3 gap-4 max-h-[60vh] overflow-y-auto">
|
||||
{allBackgrounds.map((value, i) => {
|
||||
const isExtra = i < extraBackgrounds.length
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={`relative rounded-lg overflow-hidden cursor-pointer border-2 transition-all duration-200 ${
|
||||
value === background ? 'border-blue-500' : 'border-transparent hover:border-gray-500'
|
||||
}`}
|
||||
onClick={() => handleSelect(value)}
|
||||
>
|
||||
<img src={value} alt={`bg-${i}`} loading="lazy" className="w-full h-28 object-cover" />
|
||||
{isExtra && (
|
||||
<button
|
||||
className="absolute top-1 right-1 bg-black/50 hover:bg-black/70 text-white rounded-full p-1"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleRemoveExtra(value)
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
83
frontend/src/components/closeModal/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { motion } from "motion/react"
|
||||
import { AppService } from "@bindings/firefly-launcher/internal/app-service"
|
||||
import { toast } from "react-toastify"
|
||||
import useSettingStore from "@/stores/settingStore"
|
||||
|
||||
export default function CloseModal({
|
||||
isOpen,
|
||||
onClose
|
||||
}: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}) {
|
||||
if (!isOpen) return null
|
||||
const { closingOption, setClosingOption } = useSettingStore()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 h-full flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-2xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4 flex justify-between items-center">
|
||||
<h3 className="font-bold text-xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-600">
|
||||
Confirm Action
|
||||
</h3>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||
onClick={onClose}
|
||||
>
|
||||
✕
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pt-2 pb-6">
|
||||
<p className="mb-4 text-lg">
|
||||
Do you want to minimize the application to the system tray or close the application?
|
||||
</p>
|
||||
|
||||
<div className="flex items-center mb-4">
|
||||
<input
|
||||
id="dontAskAgain"
|
||||
type="checkbox"
|
||||
className="checkbox checkbox-sm mr-2"
|
||||
checked={!closingOption.isAsk}
|
||||
onChange={(e) => setClosingOption({ isMinimize: closingOption.isMinimize, isAsk: !e.target.checked })}
|
||||
/>
|
||||
<label htmlFor="dontAskAgain" className="text-sm font-semibold text-accent">
|
||||
Do not ask me again
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 justify-end gap-3">
|
||||
<button
|
||||
className="btn btn-warning"
|
||||
onClick={async () => {
|
||||
onClose()
|
||||
const [success, message] = await AppService.HideApp()
|
||||
if (!success) toast.error(message)
|
||||
if (!closingOption.isAsk) {
|
||||
setClosingOption({ isMinimize: true, isAsk: false })
|
||||
}
|
||||
}}
|
||||
>
|
||||
Minimize
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-error btn-outline"
|
||||
onClick={async () => {
|
||||
onClose()
|
||||
const [success, message] = await AppService.CloseApp()
|
||||
if (!success) toast.error(message)
|
||||
if (!closingOption.isAsk) {
|
||||
setClosingOption({ isMinimize: false, isAsk: false })
|
||||
}
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
163
frontend/src/components/header/index.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import useModalStore from "@/stores/modalStore";
|
||||
import { Blend, BookOpen, Diff, Home, Info, Languages, Minus, Puzzle, Settings, TrendingUpDown, Wrench, X } from "lucide-react";
|
||||
import { AppService } from "@bindings/firefly-launcher/internal/app-service";
|
||||
|
||||
export default function Header() {
|
||||
const { setIsOpenSettingModal } = useModalStore()
|
||||
|
||||
const controlButtons = [
|
||||
{
|
||||
icon: <Settings className="w-5 h-5" />,
|
||||
action: () => setIsOpenSettingModal(true),
|
||||
tip: "Settings",
|
||||
},
|
||||
{
|
||||
icon: <Minus className="w-5 h-5" />,
|
||||
action: () => AppService.MinimizeApp(),
|
||||
tip: "Minimize",
|
||||
},
|
||||
{
|
||||
icon: <X className="w-5 h-5" />,
|
||||
action: () => AppService.CloseApp(),
|
||||
tip: "Close",
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="navbar sticky top-0 z-50 px-3" style={{ '--wails-draggable': 'drag' } as any}>
|
||||
<div className="navbar-start">
|
||||
<div className="dropdown">
|
||||
<div tabIndex={0} role="button" className="btn btn-ghost md:hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" /> </svg>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="menu menu-sm dropdown-content bg-black/50 backdrop-blur-md rounded-box z-1 mt-3 w-52 p-2 shadow">
|
||||
<li><Link to="/">Home</Link></li>
|
||||
|
||||
<li>
|
||||
<a>Tools</a>
|
||||
<ul className="p-2">
|
||||
<li><Link to="/language">Language</Link></li>
|
||||
<li><Link to="/diff">Diff</Link></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a>Plugins</a>
|
||||
<ul className="p-2">
|
||||
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
|
||||
<li><Link to="/srtools">SrTools</Link></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><Link to="/howto">How to?</Link></li>
|
||||
<li><Link to="/about">About</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<Link to="/" className="grid grid-cols-1 items-start text-left gap-0">
|
||||
<div className="flex items-center justify-center">
|
||||
<img src="/appicon.png" alt="Logo" className='w-13 h-13 rounded-lg mx-2' />
|
||||
<div className="flex flex-col justify-center items-start">
|
||||
<h1 className="text-xl font-bold">
|
||||
<span className="text-emerald-500"
|
||||
style={{
|
||||
textShadow: '0 1px 2px rgba(255, 255, 255, 0.2)',
|
||||
}}
|
||||
>Firefly </span>
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500"
|
||||
style={{
|
||||
textShadow: '0 1px 2px rgba(255, 255, 255, 0.2)',
|
||||
}}
|
||||
>
|
||||
Launcher
|
||||
</span>
|
||||
</h1>
|
||||
<p
|
||||
className="text-white text-sm"
|
||||
style={{
|
||||
textShadow: '0 1px 2px rgba(0, 0, 0, 0.8)',
|
||||
}}
|
||||
>
|
||||
By Kain
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="navbar-center hidden md:flex bg-black/40 backdrop-blur-sm rounded-lg shadow-lg">
|
||||
<ul className="menu menu-horizontal px-1 gap-4 text-white">
|
||||
<li>
|
||||
<Link to="/" className="flex items-center gap-2 hover:text-cyan-300 transition-colors">
|
||||
<Home size={18} /> Home
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<details>
|
||||
<summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300 transition-colors">
|
||||
<Wrench size={18} /> Tools
|
||||
</summary>
|
||||
<ul className="p-2 bg-black/75 text-white rounded-lg">
|
||||
<li>
|
||||
<Link to="/language" className="flex items-center gap-2 hover:text-cyan-300">
|
||||
<Languages size={18} /> Language
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/diff" className="flex items-center gap-2 hover:text-cyan-300">
|
||||
<Diff size={18} /> Client update
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
<li>
|
||||
<details>
|
||||
<summary className="flex items-center gap-2 cursor-pointer hover:text-cyan-300 transition-colors">
|
||||
<Puzzle size={18} /> Plugins
|
||||
</summary>
|
||||
<ul className="p-2 bg-black/75 text-white rounded-lg">
|
||||
<li>
|
||||
<Link to="/analysis" className="flex items-center gap-2 hover:text-cyan-300">
|
||||
<TrendingUpDown size={18} /> Analysis (Veritas)
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/srtools" className="flex items-center gap-2 hover:text-cyan-300">
|
||||
<Blend size={18} /> Firefly Tools
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/howto" className="flex items-center gap-2 hover:text-cyan-300 transition-colors">
|
||||
<BookOpen size={18} /> How to?
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/about" className="flex items-center gap-2 hover:text-cyan-300 transition-colors">
|
||||
<Info size={18} /> About
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="navbar-end flex gap-2 z-52">
|
||||
<div className="flex items-center gap-2 bg-black/40 backdrop-blur-sm rounded-lg">
|
||||
{controlButtons.map((btn, i) => (
|
||||
<div key={i} className="tooltip tooltip-bottom" data-tip={btn.tip}>
|
||||
<button
|
||||
onClick={btn.action}
|
||||
className="btn btn-ghost btn-circle bg-transparent border-none flex items-center justify-center"
|
||||
>
|
||||
<div className="hover:text-cyan-300 transition-colors">
|
||||
{btn.icon}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
117
frontend/src/components/settingModal/index.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { CheckUpdateLauncher } from "@/helper"
|
||||
import useModalStore from "@/stores/modalStore"
|
||||
import useSettingStore from "@/stores/settingStore"
|
||||
import useLauncherStore from "@/stores/launcherStore"
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
export default function SettingModal({
|
||||
isOpen,
|
||||
onClose
|
||||
}: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}) {
|
||||
if (!isOpen) return null
|
||||
|
||||
const { setIsOpenSelfUpdateModal } = useModalStore()
|
||||
const { closingOption, setClosingOption, serverVersion,
|
||||
proxyVersion, } = useSettingStore()
|
||||
const { setUpdateData, updateData, launcherVersion } = useLauncherStore()
|
||||
const CheckUpdate = async () => {
|
||||
const launcherData = await CheckUpdateLauncher()
|
||||
if (!launcherData.isUpdate) {
|
||||
toast.success("Launcher is already up to date")
|
||||
return
|
||||
}
|
||||
setUpdateData({
|
||||
server: updateData.server,
|
||||
proxy: updateData.proxy,
|
||||
launcher: launcherData
|
||||
})
|
||||
|
||||
setIsOpenSelfUpdateModal(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-10 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-md bg-base-100 text-base-content rounded-2xl border border-purple-500/30 shadow-2xl shadow-purple-500/30 p-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="font-extrabold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-500">
|
||||
Settings
|
||||
</h3>
|
||||
<button
|
||||
className="btn btn-circle btn-sm bg-red-600 hover:bg-red-700 text-white border-none shadow-lg"
|
||||
onClick={onClose}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Section 1: Launcher Update */}
|
||||
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
|
||||
<h4 className="font-bold text-lg mb-2">Launcher Update</h4>
|
||||
<p className="text-sm text-info mb-3">
|
||||
Check if your launcher is up to date.
|
||||
</p>
|
||||
<button
|
||||
className="btn btn-primary bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-400 hover:to-red-500 text-white shadow-md hover:shadow-lg transition-all duration-200"
|
||||
onClick={CheckUpdate}
|
||||
>
|
||||
Check for Launcher Updates
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Section 2: Closing Option */}
|
||||
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
|
||||
<h4 className="font-bold text-lg mb-2">Closing Options</h4>
|
||||
<label className="flex items-start gap-3 cursor-pointer select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox checkbox-primary w-5 h-5 mt-1"
|
||||
checked={!closingOption.isAsk}
|
||||
onChange={(e) => {
|
||||
setClosingOption({
|
||||
isMinimize: closingOption.isMinimize,
|
||||
isAsk: !e.target.checked
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-base font-medium text-info">
|
||||
Set do not ask again
|
||||
</span>
|
||||
<span className="text-sm text-accent">
|
||||
Next time you close the app, it will automatically{" "}
|
||||
{closingOption.isMinimize ? "minimize to system tray" : "quit the app"}{" "}
|
||||
without asking.
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Section 3: Launcher Version */}
|
||||
<div className="p-4 bg-base-200 rounded-xl border border-purple-300 shadow-sm">
|
||||
<h4 className="font-bold text-lg mb-2">Version</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<p className="text-base text-info">
|
||||
Server: {serverVersion}
|
||||
</p>
|
||||
<p className="text-base text-info">
|
||||
Proxy: {proxyVersion}
|
||||
</p>
|
||||
|
||||
<p className="text-base text-info">
|
||||
Launcher: {launcherVersion}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function ThemeController() {
|
||||
const [theme, setTheme] = useState(localStorage.getItem("theme") ?? "light");
|
||||
const handleToggle = (e: any) => {
|
||||
if (e.target.checked) {
|
||||
setTheme("cupcake");
|
||||
} else {
|
||||
setTheme("night");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('theme', theme!)
|
||||
const localTheme = localStorage.getItem('theme')
|
||||
document.querySelector('html')?.setAttribute('data-theme', localTheme!)
|
||||
}, [theme]);
|
||||
|
||||
|
||||
return (
|
||||
<label className="toggle text-base-content">
|
||||
<input type="checkbox" onChange={handleToggle} className="theme-controller" />
|
||||
|
||||
<svg aria-label="moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g strokeLinejoin="round" strokeLinecap="round" strokeWidth="2" fill="none" stroke="currentColor"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path></g></svg>
|
||||
<svg aria-label="sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g strokeLinejoin="round" strokeLinecap="round" strokeWidth="2" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path><path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path></g></svg>
|
||||
|
||||
</label>
|
||||
)
|
||||
}
|
||||
64
frontend/src/components/updateModal/index.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
interface UpdateModalProps {
|
||||
isOpen: boolean
|
||||
title: string
|
||||
message: string
|
||||
buttons: {
|
||||
text: string
|
||||
onClick: () => Promise<void> | void
|
||||
variant?: "primary" | "error" | "outline"
|
||||
}[]
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export default function UpdateModal({ isOpen, title, message, buttons, onClose }: UpdateModalProps) {
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||
onClick={onClose}
|
||||
>
|
||||
✕
|
||||
</motion.button>
|
||||
|
||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6">
|
||||
<div className="mb-6">
|
||||
<p className="text-accent text-lg">{message}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
{buttons.map((btn, idx) => (
|
||||
<motion.button
|
||||
key={idx}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className={`btn ${
|
||||
btn.variant === "primary"
|
||||
? "btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||
: btn.variant === "error"
|
||||
? "btn-error"
|
||||
: "btn-outline btn-error"
|
||||
}`}
|
||||
onClick={btn.onClick}
|
||||
>
|
||||
{btn.text}
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,29 +2,17 @@
|
||||
import { useEffect } from "react";
|
||||
import { Events } from "@wailsio/runtime";
|
||||
import { toast } from "react-toastify";
|
||||
import useSettingStore from "@/stores/settingStore";
|
||||
import { AppService } from "@bindings/firefly-launcher/internal/app-service";
|
||||
import useModalStore from "@/stores/modalStore";
|
||||
import useDiffStore from "@/stores/diffStore";
|
||||
import useLauncherStore from "@/stores/launcherStore";
|
||||
|
||||
export function useGlobalEvents({
|
||||
setGameRunning,
|
||||
setServerRunning,
|
||||
setProxyRunning,
|
||||
setProgressUpdate,
|
||||
setMaxProgressUpdate,
|
||||
setProgressDownload,
|
||||
setDownloadSpeed,
|
||||
setMessageUpdate,
|
||||
setStageType,
|
||||
export function useGlobalEvents() {
|
||||
const { setIsOpenCloseModal } = useModalStore()
|
||||
const { setGameRunning, setServerRunning, setProxyRunning, setProgressDownload, setDownloadSpeed } = useLauncherStore()
|
||||
const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate, setStageType } = useDiffStore()
|
||||
|
||||
}: {
|
||||
setGameRunning: (v: boolean) => void;
|
||||
setServerRunning: (v: boolean) => void;
|
||||
setProxyRunning: (v: boolean) => void;
|
||||
setProgressUpdate: (v: number) => void;
|
||||
setMaxProgressUpdate: (v: number) => void;
|
||||
setProgressDownload: (v: number) => void;
|
||||
setDownloadSpeed: (v: string) => void;
|
||||
setMessageUpdate: (v: string) => void;
|
||||
setStageType: (v: string) => void,
|
||||
}) {
|
||||
useEffect(() => {
|
||||
const onGameExit = () => setGameRunning(false);
|
||||
const onServerExit = () => setServerRunning(false);
|
||||
@@ -64,6 +52,20 @@ export function useGlobalEvents({
|
||||
const { message } = event.data[0];
|
||||
toast.error(message);
|
||||
});
|
||||
Events.On("window:close", async () => {
|
||||
const option = useSettingStore.getState().closingOption
|
||||
if (option.isAsk) {
|
||||
setIsOpenCloseModal(true);
|
||||
return
|
||||
}
|
||||
if (option.isMinimize) {
|
||||
const [success, message] = await AppService.MinimizeApp()
|
||||
if (!success) toast.error(message)
|
||||
} else {
|
||||
const [success, message] = await AppService.CloseApp()
|
||||
if (!success) toast.error(message)
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
Events.Off("download:server");
|
||||
@@ -75,6 +77,7 @@ export function useGlobalEvents({
|
||||
Events.Off("diff:message");
|
||||
Events.Off("diff:stage");
|
||||
Events.Off("version:check");
|
||||
Events.Off("window:close");
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function AboutPage() {
|
||||
I created a lightweight and modern <span className="font-semibold text-success">Game Launcher</span> to help users easily launch and manage their games with better performance and simplicity.
|
||||
</p>
|
||||
<p className="text-lg leading-relaxed">
|
||||
The launcher is built using <span className="font-mono text-info">Go + Wails3</span>, with a clean and responsive interface styled with <span className="text-warning">Tailwind CSS</span> and <span className="text-warning">DaisyUI</span>.
|
||||
The launcher is built using <span className="font-mono text-info">Go + Wails3</span>, with a clean and responsive interface styled with <span className="text-accent">Tailwind CSS</span> and <span className="text-accent">DaisyUI</span>.
|
||||
</p>
|
||||
<p className="text-lg leading-relaxed">
|
||||
My goal is to make tools that are fast, efficient, and enjoyable to use — and this launcher is just the beginning.
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function AnalysisPage() {
|
||||
</div>
|
||||
<a
|
||||
href="https://sranalysis.kain.id.vn/"
|
||||
className="link link-warning font-mono text-sm break-all"
|
||||
className="link link-accent font-mono text-sm break-all"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -79,7 +79,7 @@ export default function AnalysisPage() {
|
||||
</div>
|
||||
<a
|
||||
href="https://firefly-sranalysis.vercel.app/"
|
||||
className="link link-warning font-mono text-sm break-all"
|
||||
className="link link-accent font-mono text-sm break-all"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -309,7 +309,7 @@ export default function DiffPage() {
|
||||
<div className="w-full p-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-center items-center text-sm text-white/80">
|
||||
<span className="font-bold text-lg text-warning">{stageType}:</span>
|
||||
<span className="font-bold text-lg text-accent">{stageType}:</span>
|
||||
<div className="flex items-center gap-4 ml-2">
|
||||
{stageType !== 'Cut Data' && <span className="text-white font-bold">{progressUpdate.toFixed(0)} / {maxProgressUpdate.toFixed(0)}</span>}
|
||||
{stageType === 'Cut Data' && <span className="text-white font-bold truncate max-w-full overflow-hidden whitespace-nowrap">{messageUpdate}</span>}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function FireflyToolsPage() {
|
||||
This site is a another version of {" "}
|
||||
<span className="font-semibold text-success">Firefly Tools {" "}</span>
|
||||
developed by {" "}
|
||||
<span className="font-semibold text-warning">Me {"(Kain)"}</span>
|
||||
<span className="font-semibold text-accent">Me {"(Kain)"}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
@@ -30,7 +30,7 @@ export default function FireflyToolsPage() {
|
||||
</div>
|
||||
<a
|
||||
href="https://srtools.kain.id.vn/"
|
||||
className="link link-warning font-mono text-sm break-all"
|
||||
className="link link-accent font-mono text-sm break-all"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -45,7 +45,7 @@ export default function FireflyToolsPage() {
|
||||
</div>
|
||||
<a
|
||||
href="https://firefly-srtools.vercel.app/"
|
||||
className="link link-warning font-mono text-sm break-all"
|
||||
className="link link-accent font-mono text-sm break-all"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -55,14 +55,14 @@ export default function FireflyToolsPage() {
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">👨💻</div>
|
||||
<p>The original tool was created by a third-party developer named <span className="font-semibold text-warning">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
|
||||
<p>The original tool was created by a third-party developer named <span className="font-semibold text-accent">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-blue-600 text-lg">🔗</div>
|
||||
<p>There is also a more modern version by the same author available at{" "}
|
||||
<a
|
||||
href="https://srtools.neonteam.dev/"
|
||||
className="link link-warning"
|
||||
className="link link-accent"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -71,7 +71,7 @@ export default function FireflyToolsPage() {
|
||||
{" "}and the original version at{" "}
|
||||
<a
|
||||
href="https://srtools.pages.dev/"
|
||||
className="link link-warning"
|
||||
className="link link-accent"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -95,14 +95,14 @@ export default function FireflyToolsPage() {
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-lg">🔌</div>
|
||||
<p>Instantly apply setups to <span className="font-semibold text-warning">Firefly GO Server</span> using <span className="font-semibold">Connect PS</span> — no manual file uploads required.</p>
|
||||
<p>Instantly apply setups to <span className="font-semibold text-accent">Firefly GO Server</span> using <span className="font-semibold">Connect PS</span> — no manual file uploads required.</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-2xl">✨</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-green-800 text-lg">Extra Settings</h4>
|
||||
<p className="text-green-700 mt-1">
|
||||
Enhance your <span className="font-semibold text-warning">Firefly GO Server</span> experience with extra features:
|
||||
Enhance your <span className="font-semibold text-accent">Firefly GO Server</span> experience with extra features:
|
||||
</p>
|
||||
<ul className="list-disc list-inside mt-2 space-y-1 text-green-700">
|
||||
<li>🎭 <span className="font-medium">Hidden Game UI</span> — remove the entire game interface.</li>
|
||||
@@ -110,7 +110,7 @@ export default function FireflyToolsPage() {
|
||||
<li>🧪 <span className="font-medium">Theorycraft Mode</span> — configure HP, cycles, and more via the web.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-green-600 text-lg">📂</div>
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function HowToPage() {
|
||||
|
||||
<p className="text-blue-700 mb-4">
|
||||
Below are in-game chat commands you can use. Some commands require you to enable{" "}
|
||||
<span className="font-semibold text-warning">Theorycraft Mode</span>.
|
||||
<span className="font-semibold text-accent">Theorycraft Mode</span>.
|
||||
</p>
|
||||
|
||||
{/* Theorycraft Mode Warning */}
|
||||
|
||||
@@ -208,10 +208,10 @@ export default function LanguagePage() {
|
||||
|
||||
<div className="bg-warning/5 rounded-lg p-2 border border-warning/30">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Mic size={20} className="text-warning" />
|
||||
<span className="font-bold text-warning">Voice Language</span>
|
||||
<Mic size={20} className="text-accent" />
|
||||
<span className="font-bold text-accent">Voice Language</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-warning">
|
||||
<p className="text-2xl font-bold text-accent">
|
||||
{getLanguageLabel(voiceLang)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -250,7 +250,7 @@ export default function LanguagePage() {
|
||||
|
||||
{/* Voice Language */}
|
||||
<div className="space-y-3">
|
||||
<label className="flex text-sm font-medium text-warning items-center gap-2">
|
||||
<label className="flex text-sm font-medium text-accent items-center gap-2">
|
||||
<Mic size={16} />
|
||||
Voice Language
|
||||
</label>
|
||||
|
||||
@@ -10,9 +10,13 @@ import useLauncherStore from '@/stores/launcherStore';
|
||||
import { motion } from 'motion/react';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { CheckUpdateLauncher, CheckUpdateProxy, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper';
|
||||
import UpdateModal from '@/components/updateModal';
|
||||
import { BackgroundSelector } from '@/components/backgroudModal';
|
||||
|
||||
|
||||
export default function LauncherPage() {
|
||||
const { gamePath,
|
||||
const {
|
||||
gamePath,
|
||||
setGamePath,
|
||||
setGameDir,
|
||||
serverPath,
|
||||
@@ -20,7 +24,9 @@ export default function LauncherPage() {
|
||||
gameDir,
|
||||
serverVersion,
|
||||
proxyVersion,
|
||||
background
|
||||
} = useSettingStore()
|
||||
|
||||
const {
|
||||
isOpenDownloadDataModal,
|
||||
isOpenUpdateDataModal,
|
||||
@@ -29,6 +35,7 @@ export default function LauncherPage() {
|
||||
setIsOpenUpdateDataModal,
|
||||
setIsOpenSelfUpdateModal
|
||||
} = useModalStore()
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
downloadType,
|
||||
@@ -41,7 +48,7 @@ export default function LauncherPage() {
|
||||
progressDownload,
|
||||
downloadSpeed,
|
||||
updateData,
|
||||
launcherVersion,
|
||||
// launcherVersion,
|
||||
setLauncherVersion,
|
||||
setIsLoading,
|
||||
setDownloadType,
|
||||
@@ -54,6 +61,33 @@ export default function LauncherPage() {
|
||||
setUpdateData,
|
||||
} = useLauncherStore()
|
||||
|
||||
const widgetLinks = [
|
||||
{
|
||||
tooltip: "Firefly SRAnalysis",
|
||||
href: "https://sranalysis.kain.id.vn/",
|
||||
img: "https://sranalysis.kain.id.vn/ff-sranalysis.png",
|
||||
btnClass: "btn-primary"
|
||||
},
|
||||
{
|
||||
tooltip: "Firefly SRTools",
|
||||
href: "https://srtools.kain.id.vn/",
|
||||
img: "https://srtools.kain.id.vn/ff-srtool.png",
|
||||
btnClass: "btn-secondary"
|
||||
},
|
||||
{
|
||||
tooltip: "Amazing's SRTools (Original UI)",
|
||||
href: "https://srtools.pages.dev/",
|
||||
img: "https://icons.duckduckgo.com/ip3/srtools.pages.dev.ico",
|
||||
btnClass: "btn-primary"
|
||||
},
|
||||
{
|
||||
tooltip: "Amazing's SRTools (Modern UI)",
|
||||
href: "https://srtools.neonteam.dev/",
|
||||
img: "https://icons.duckduckgo.com/ip3/srtools.neonteam.dev.ico",
|
||||
btnClass: "btn-secondary"
|
||||
}
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
const check = async () => {
|
||||
if (!serverVersion || !proxyVersion) {
|
||||
@@ -172,7 +206,10 @@ export default function LauncherPage() {
|
||||
}
|
||||
setServerRunning(true)
|
||||
}
|
||||
await sleep(2000)
|
||||
await sleep(1000)
|
||||
const gameFolder = await FSService.GetDir(gamePath)
|
||||
const fileNeedRemove = await FSService.Join(gameFolder, "StarRail_Data", "Plugins", "x86_64", "AccountPlatNative.dll")
|
||||
await FSService.RemoveFile(fileNeedRemove)
|
||||
if (gamePath.endsWith("launcher.exe")) {
|
||||
const resultGame = await FSService.StartWithConsole(gamePath)
|
||||
if (!resultGame) {
|
||||
@@ -200,18 +237,18 @@ export default function LauncherPage() {
|
||||
setIsDownloading(true)
|
||||
if (updateData.launcher.isUpdate) {
|
||||
await UpdateLauncher(updateData.launcher.version)
|
||||
setUpdateData({...updateData, launcher: { isUpdate: false, isExists: true, version: updateData.launcher.version }})
|
||||
setUpdateData({ ...updateData, launcher: { isUpdate: false, isExists: true, version: updateData.launcher.version } })
|
||||
setIsOpenSelfUpdateModal(true)
|
||||
}
|
||||
if (updateData.server.isUpdate || !updateData.server.isExists) {
|
||||
await UpdateServer(updateData.server.version)
|
||||
setServerReady(true)
|
||||
setUpdateData({...updateData, server: { isUpdate: false, isExists: true, version: updateData.server.version }})
|
||||
setUpdateData({ ...updateData, server: { isUpdate: false, isExists: true, version: updateData.server.version } })
|
||||
}
|
||||
if (updateData.proxy.isUpdate || !updateData.proxy.isExists) {
|
||||
await UpdateProxy(updateData.proxy.version)
|
||||
setProxyReady(true)
|
||||
setUpdateData({...updateData, proxy: { isUpdate: false, isExists: true, version: updateData.proxy.version }})
|
||||
setUpdateData({ ...updateData, proxy: { isUpdate: false, isExists: true, version: updateData.proxy.version } })
|
||||
}
|
||||
|
||||
setDownloadType("")
|
||||
@@ -235,78 +272,39 @@ export default function LauncherPage() {
|
||||
|
||||
return (
|
||||
<div className="relative min-h-fit overflow-hidden">
|
||||
<div
|
||||
className="fixed inset-0 z-0 w-full h-full"
|
||||
style={{
|
||||
backgroundImage: "url('/bg.jpg')",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat"
|
||||
<img
|
||||
src={background}
|
||||
alt="background"
|
||||
className="fixed inset-0 w-full h-full object-cover z-0"
|
||||
onError={(e) => {
|
||||
const target = e.currentTarget as HTMLImageElement
|
||||
target.src = "bg-12.jpg"
|
||||
}}
|
||||
></div>
|
||||
/>
|
||||
|
||||
{/* Header */}
|
||||
<header className="hidden sm:flex fixed z-10 items-center justify-between p-6">
|
||||
<div className="text-2xl font-bold text-white">Firefly GO</div>
|
||||
<header className="hidden sm:flex fixed z-10 items-center justify-between py-6 px-4 ">
|
||||
<div className="text-2xl font-bold text-white bg-gray-500/5 rounded-full p-1">Firefly GO</div>
|
||||
</header>
|
||||
|
||||
<div className="hidden sm:flex fixed top-1/4 left-4 z-10 flex-col space-y-3 bg-black/30 backdrop-blur-md rounded-xl p-3 shadow-lg">
|
||||
<div className="hidden sm:flex fixed top-1/4 right-4 z-10 flex-col space-y-3 bg-white/5 rounded-xl p-3 shadow-lg">
|
||||
{widgetLinks.map((link, idx) => (
|
||||
<div key={idx} className="tooltip tooltip-left" data-tip={link.tooltip}>
|
||||
<a
|
||||
className={`btn btn-circle ${link.btnClass}`}
|
||||
target="_blank"
|
||||
href={link.href}
|
||||
>
|
||||
<img
|
||||
src={link.img}
|
||||
alt={link.tooltip}
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="tooltip tooltip-right" data-tip="Firefly SRAnalysis">
|
||||
<a
|
||||
className="btn btn-circle btn-primary"
|
||||
target="_blank"
|
||||
href="https://sranalysis.kain.id.vn/"
|
||||
>
|
||||
<img
|
||||
src="https://sranalysis.kain.id.vn/ff-sranalysis.png"
|
||||
alt="SRAnalysis Logo"
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="tooltip tooltip-right" data-tip="Firefly SRTools">
|
||||
<a
|
||||
className="btn btn-circle btn-secondary"
|
||||
target="_blank"
|
||||
href="https://srtools.kain.id.vn/"
|
||||
>
|
||||
<img
|
||||
src="https://srtools.kain.id.vn/ff-srtool.png"
|
||||
alt="SRTools Logo"
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="tooltip tooltip-right" data-tip="Amazing's SRTools (Original UI)">
|
||||
<a
|
||||
className="btn btn-circle btn-primary"
|
||||
target="_blank"
|
||||
href="https://srtools.pages.dev/"
|
||||
>
|
||||
<img
|
||||
src="https://icons.duckduckgo.com/ip3/srtools.pages.dev.ico"
|
||||
alt="SRTools Logo"
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="tooltip tooltip-right" data-tip="Amazing's SRTools (Modern UI)">
|
||||
<a
|
||||
className="btn btn-circle btn-secondary"
|
||||
target="_blank"
|
||||
href="https://srtools.neonteam.dev/"
|
||||
>
|
||||
<img
|
||||
src="https://icons.duckduckgo.com/ip3/srtools.neonteam.dev.ico"
|
||||
alt="SRTools Logo"
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="tooltip tooltip-right" data-tip="How to use all tools & commands">
|
||||
<div className="tooltip tooltip-left" data-tip="How to use all tools & commands">
|
||||
<Link
|
||||
to="/howto"
|
||||
className="btn btn-warning btn-circle"
|
||||
@@ -316,6 +314,9 @@ export default function LauncherPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:flex fixed top-1/2 left-5 z-10 ">
|
||||
<BackgroundSelector />
|
||||
</div>
|
||||
|
||||
{/* Bottom Panel */}
|
||||
{serverReady && proxyReady && !isDownloading && (
|
||||
@@ -345,20 +346,17 @@ export default function LauncherPage() {
|
||||
</div>
|
||||
<ul tabIndex={0} className="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm">
|
||||
<li><button onClick={handlePickFile}>Change Game Path</button></li>
|
||||
<li><button
|
||||
<li>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const serverData = await CheckUpdateServer(serverPath, serverVersion)
|
||||
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
|
||||
const launcherData = await CheckUpdateLauncher()
|
||||
setUpdateData({
|
||||
server: serverData,
|
||||
proxy: proxyData,
|
||||
launcher: launcherData
|
||||
launcher: updateData.launcher
|
||||
})
|
||||
if (launcherData.isUpdate) {
|
||||
setIsOpenSelfUpdateModal(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (!serverData.isExists || !proxyData.isExists) {
|
||||
setIsOpenDownloadDataModal(true)
|
||||
return
|
||||
@@ -369,8 +367,12 @@ export default function LauncherPage() {
|
||||
}
|
||||
toast.success("No updates available")
|
||||
}}>
|
||||
Check for Updates
|
||||
</button></li>
|
||||
Check for Updates Server & Proxy
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
</li>
|
||||
<li><button disabled={!serverPath} onClick={() => {
|
||||
if (serverPath) {
|
||||
FSService.OpenFolder("./server")
|
||||
@@ -423,6 +425,7 @@ export default function LauncherPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDownloading && updateData.launcher.isUpdate && (
|
||||
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/20 backdrop-blur-sm rounded-lg p-4 shadow-lg">
|
||||
<div className="space-y-3 text-sm text-white/80 text-center">
|
||||
@@ -466,151 +469,44 @@ export default function LauncherPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Version Info */}
|
||||
{serverReady && proxyReady && !isDownloading && (
|
||||
<div className="hidden md:block fixed bottom-4 left-4 z-10 text-sm font-bold bg-black/20 backdrop-blur-sm rounded-lg p-2 shadow">
|
||||
<p className="text-primary">Version server: {serverVersion}</p>
|
||||
<p className="mt-2 text-secondary">Version proxy: {proxyVersion}</p>
|
||||
<p className="mt-2 text-success">Version launcher: {launcherVersion}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="hidden md:block fixed bottom-4 left-5 z-10">
|
||||
|
||||
<img src="/heart-hsr.gif" alt="firefly animation" className="rounded-lg w-24 h-24" />
|
||||
|
||||
</div>
|
||||
{/* Modal */}
|
||||
{isOpenUpdateDataModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||
onClick={() => setIsOpenUpdateDataModal(false)}
|
||||
>
|
||||
✕
|
||||
</motion.button>
|
||||
<UpdateModal
|
||||
isOpen={isOpenUpdateDataModal}
|
||||
onClose={() => setIsOpenUpdateDataModal(false)}
|
||||
title="Update Data"
|
||||
message="Do you want to update data server and proxy?"
|
||||
buttons={[
|
||||
{ text: "No", onClick: () => setIsOpenUpdateDataModal(false), variant: "outline" },
|
||||
{ text: "Yes", onClick: async () => { setIsOpenUpdateDataModal(false); await handlerUpdateData() }, variant: "primary" }
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
|
||||
Update Data
|
||||
</h3>
|
||||
</div>
|
||||
<UpdateModal
|
||||
isOpen={isOpenDownloadDataModal}
|
||||
onClose={() => setIsOpenDownloadDataModal(false)}
|
||||
title="Download Data"
|
||||
message="Data server and proxy download required"
|
||||
buttons={[
|
||||
{ text: "Download", onClick: async () => { setIsOpenDownloadDataModal(false); await handlerUpdateData() }, variant: "primary" }
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="px-6 pb-6">
|
||||
<div className="mb-6">
|
||||
<p className="text-warning text-lg">
|
||||
Do you want to update data server and proxy?
|
||||
</p>
|
||||
</div>
|
||||
<UpdateModal
|
||||
isOpen={isOpenSelfUpdateModal}
|
||||
onClose={() => setIsOpenSelfUpdateModal(false)}
|
||||
title="Update Launcher"
|
||||
message="Do you want to update launcher?"
|
||||
buttons={[
|
||||
{ text: "No", onClick: () => setIsOpenSelfUpdateModal(false), variant: "outline" },
|
||||
{ text: "Yes", onClick: async () => { setIsOpenSelfUpdateModal(false); await handlerUpdateData() }, variant: "primary" }
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-outline btn-error"
|
||||
onClick={() => setIsOpenUpdateDataModal(false)}
|
||||
>
|
||||
No
|
||||
</motion.button>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||
onClick={async () => {
|
||||
setIsOpenUpdateDataModal(false)
|
||||
await handlerUpdateData()
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isOpenDownloadDataModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
|
||||
Download Data
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6">
|
||||
<div className="mb-6">
|
||||
<p className="text-warning text-lg">
|
||||
Data server and proxy download required
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||
onClick={async () => {
|
||||
setIsOpenDownloadDataModal(false)
|
||||
await handlerUpdateData()
|
||||
}}
|
||||
>
|
||||
Download
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isOpenSelfUpdateModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||
onClick={() => setIsOpenSelfUpdateModal(false)}
|
||||
>
|
||||
✕
|
||||
</motion.button>
|
||||
|
||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
|
||||
Update Launcher
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6">
|
||||
<div className="mb-6">
|
||||
<p className="text-warning text-lg">
|
||||
Do you want to update launcher?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-outline btn-error"
|
||||
onClick={() => setIsOpenSelfUpdateModal(false)}
|
||||
>
|
||||
No
|
||||
</motion.button>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||
onClick={async () => {
|
||||
setIsOpenSelfUpdateModal(false)
|
||||
await handlerUpdateData()
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,107 +1,31 @@
|
||||
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
|
||||
import ThemeController from '../components/themeController'
|
||||
import { createRootRoute, Outlet } from '@tanstack/react-router'
|
||||
import { ToastContainer } from 'react-toastify'
|
||||
import { useGlobalEvents } from '@/hooks';
|
||||
import useLauncherStore from '@/stores/launcherStore';
|
||||
import useDiffStore from '@/stores/diffStore';
|
||||
import useModalStore from '@/stores/modalStore';;
|
||||
import SettingModal from '@/components/settingModal';
|
||||
import CloseModal from '@/components/closeModal';
|
||||
import Header from '@/components/header';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: RootLayout
|
||||
})
|
||||
|
||||
function RootLayout() {
|
||||
const { setGameRunning, setServerRunning, setProxyRunning, setProgressDownload, setDownloadSpeed } = useLauncherStore()
|
||||
const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate, setStageType } = useDiffStore()
|
||||
useGlobalEvents({
|
||||
setGameRunning,
|
||||
setServerRunning,
|
||||
setProxyRunning,
|
||||
setProgressUpdate,
|
||||
setMaxProgressUpdate,
|
||||
setProgressDownload,
|
||||
setDownloadSpeed,
|
||||
setMessageUpdate,
|
||||
setStageType
|
||||
});
|
||||
const { setIsOpenCloseModal, isOpenCloseModal, isOpenSettingModal, setIsOpenSettingModal } = useModalStore()
|
||||
|
||||
useGlobalEvents();
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="navbar bg-base-100 shadow-sm sticky top-0 z-50 px-3">
|
||||
<div className="navbar-start">
|
||||
<div className="dropdown">
|
||||
<div tabIndex={0} role="button" className="btn btn-ghost md:hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h8m-8 6h16" /> </svg>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow">
|
||||
<li><Link to="/">Home</Link></li>
|
||||
<Header />
|
||||
|
||||
<li>
|
||||
<a>Tools</a>
|
||||
<ul className="p-2">
|
||||
<li><Link to="/language">Language</Link></li>
|
||||
<li><Link to="/diff">Diff</Link></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a>Plugins</a>
|
||||
<ul className="p-2">
|
||||
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
|
||||
<li><Link to="/srtools">SrTools</Link></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><Link to="/howto">How to?</Link></li>
|
||||
<li><Link to="/about">About</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<Link to="/" className="grid grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
|
||||
<div className="flex items-center justify-center">
|
||||
<img src="/appicon.png" alt="Logo" className='w-13 h-13 rounded-lg mx-2' />
|
||||
<div className="flex flex-col justify-center items-start">
|
||||
<h1 className="text-xl font-bold">
|
||||
<span className="text-emerald-500">Firefly </span>
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
|
||||
Launcher
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500">By Kain</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="navbar-center hidden md:flex">
|
||||
<ul className="menu menu-horizontal px-1 gap-4">
|
||||
<li><Link to="/">Home</Link></li>
|
||||
<li>
|
||||
<details>
|
||||
<summary>Tools</summary>
|
||||
<ul className="p-2">
|
||||
<li><Link to="/language">Language</Link></li>
|
||||
<li><Link to="/diff">Diff</Link></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
<li>
|
||||
<details>
|
||||
<summary>Plugins</summary>
|
||||
<ul className="p-2">
|
||||
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
|
||||
<li><Link to="/srtools">Firefly Tools</Link></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
<li><Link to="/howto">How to?</Link></li>
|
||||
<li><Link to="/about">About</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="navbar-end">
|
||||
<ThemeController />
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-[78vh]">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
<CloseModal isOpen={isOpenCloseModal} onClose={() => setIsOpenCloseModal(false)} />
|
||||
<SettingModal isOpen={isOpenSettingModal} onClose={() => setIsOpenSettingModal(false)} />
|
||||
<ToastContainer />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -5,18 +5,26 @@ interface ModalState {
|
||||
isOpenDownloadDataModal: boolean;
|
||||
isOpenUpdateDataModal: boolean;
|
||||
isOpenSelfUpdateModal: boolean;
|
||||
isOpenCloseModal: boolean;
|
||||
isOpenSettingModal: boolean;
|
||||
setIsOpenDownloadDataModal: (modal: boolean) => void;
|
||||
setIsOpenUpdateDataModal: (modal: boolean) => void;
|
||||
setIsOpenSelfUpdateModal: (modal: boolean) => void;
|
||||
setIsOpenCloseModal: (modal: boolean) => void;
|
||||
setIsOpenSettingModal: (modal: boolean) => void;
|
||||
}
|
||||
|
||||
const useModalStore = create<ModalState>((set, get) => ({
|
||||
isOpenDownloadDataModal: false,
|
||||
isOpenUpdateDataModal: false,
|
||||
isOpenSelfUpdateModal: false,
|
||||
isOpenCloseModal: false,
|
||||
isOpenSettingModal: false,
|
||||
setIsOpenDownloadDataModal: (modal: boolean) => set({ isOpenDownloadDataModal: modal }),
|
||||
setIsOpenUpdateDataModal: (modal: boolean) => set({ isOpenUpdateDataModal: modal }),
|
||||
setIsOpenSelfUpdateModal: (modal: boolean) => set({ isOpenSelfUpdateModal: modal }),
|
||||
setIsOpenCloseModal: (modal: boolean) => set({ isOpenCloseModal: modal }),
|
||||
setIsOpenSettingModal: (modal: boolean) => set({ isOpenSettingModal: modal }),
|
||||
}));
|
||||
|
||||
export default useModalStore;
|
||||
@@ -10,6 +10,15 @@ interface SettingState {
|
||||
proxyPath: string;
|
||||
serverVersion: string;
|
||||
proxyVersion: string;
|
||||
closingOption: {
|
||||
isMinimize: boolean;
|
||||
isAsk: boolean;
|
||||
}
|
||||
background: string;
|
||||
extraBackgrounds: string[];
|
||||
setExtraBackgrounds: (newExtraBackgrounds: string[]) => void;
|
||||
setBackground: (newBackground: string) => void;
|
||||
setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => void;
|
||||
setLocale: (newLocale: string) => void;
|
||||
setGamePath: (newGamePath: string) => void;
|
||||
setGameDir: (newGameDir: string) => void;
|
||||
@@ -29,6 +38,15 @@ const useSettingStore = create<SettingState>()(
|
||||
proxyPath: "",
|
||||
serverVersion: "",
|
||||
proxyVersion: "",
|
||||
closingOption: {
|
||||
isMinimize: false,
|
||||
isAsk: true,
|
||||
},
|
||||
background: "bg-12.jpg",
|
||||
extraBackgrounds: [],
|
||||
setExtraBackgrounds: (newExtraBackgrounds: string[]) => set({ extraBackgrounds: newExtraBackgrounds }),
|
||||
setBackground: (newBackground: string) => set({ background: newBackground }),
|
||||
setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => set({ closingOption: newClosingOption }),
|
||||
setLocale: (newLocale: string) => set({ locale: newLocale }),
|
||||
setGamePath: (newGamePath: string) => set({ gamePath: newGamePath }),
|
||||
setGameDir: (newGameDir: string) => set({ gameDir: newGameDir }),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui"{
|
||||
themes: night --default, night --prefersdark, cupcake;
|
||||
themes: dracula --default;
|
||||
}
|
||||
|
||||
25
frontend/src/utils/cropImage.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
export default function getCroppedImg(imageSrc: string, crop: any): Promise<string> {
|
||||
const image = new Image()
|
||||
image.src = imageSrc
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')!
|
||||
canvas.width = crop.width
|
||||
canvas.height = crop.height
|
||||
return new Promise((resolve) => {
|
||||
image.onload = () => {
|
||||
ctx.drawImage(
|
||||
image,
|
||||
crop.x,
|
||||
crop.y,
|
||||
crop.width,
|
||||
crop.height,
|
||||
0,
|
||||
0,
|
||||
crop.width,
|
||||
crop.height
|
||||
)
|
||||
resolve(canvas.toDataURL('image/jpeg'))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -20,3 +20,44 @@ func (a *AppService) CloseAppAfterTimeout(timeout int) (bool, string) {
|
||||
}()
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (a *AppService) CloseApp() (bool, string) {
|
||||
application.Get().Quit()
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (a *AppService) HideApp() (bool, string) {
|
||||
window := application.Get().Window.Current()
|
||||
if window == nil {
|
||||
return false, "not found window"
|
||||
}
|
||||
window.Hide()
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (a *AppService) MinimizeApp() (bool, string) {
|
||||
window := application.Get().Window.Current()
|
||||
if window == nil {
|
||||
return false, "not found window"
|
||||
}
|
||||
window.Minimise()
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (a *AppService) MaximizeApp() (bool, string) {
|
||||
window := application.Get().Window.Current()
|
||||
if window == nil {
|
||||
return false, "not found window"
|
||||
}
|
||||
window.Maximise()
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (a *AppService) RestoreApp() (bool, string) {
|
||||
window := application.Get().Window.Current()
|
||||
if window == nil {
|
||||
return false, "not found window"
|
||||
}
|
||||
window.Restore()
|
||||
return true, ""
|
||||
}
|
||||
|
||||
@@ -63,6 +63,19 @@ func (f *FSService) FileExists(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FSService) GetDir(path string) string {
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
func (f *FSService) Join(paths ...string) string {
|
||||
return filepath.Join(paths...)
|
||||
}
|
||||
|
||||
func (f *FSService) RemoveFile(path string) error {
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
|
||||
func (f *FSService) StartApp(path string) (bool, error) {
|
||||
cmd := exec.Command(path)
|
||||
err := cmd.Start()
|
||||
|
||||
18
main.go
@@ -17,6 +17,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
//go:embed all:frontend/dist
|
||||
@@ -84,7 +85,7 @@ func main() {
|
||||
})
|
||||
|
||||
// Create window
|
||||
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Firefly Launcher - Kain",
|
||||
Mac: application.MacWindow{
|
||||
InvisibleTitleBarHeight: 50,
|
||||
@@ -92,10 +93,12 @@ func main() {
|
||||
TitleBar: application.MacTitleBarHiddenInset,
|
||||
},
|
||||
BackgroundColour: application.NewRGB(27, 38, 54),
|
||||
Width: 1200,
|
||||
Height: 600,
|
||||
Width: 1280,
|
||||
Height: 720,
|
||||
URL: "/",
|
||||
DevToolsEnabled: true,
|
||||
Frameless: true,
|
||||
DisableResize: true,
|
||||
})
|
||||
|
||||
iconBytes, _ := tools.ReadFile("assets/appicon.png")
|
||||
@@ -106,7 +109,7 @@ func main() {
|
||||
// Attach the window to the system tray
|
||||
menu := application.NewMenu()
|
||||
menu.Add("Open").OnClick(func(ctx *application.Context) {
|
||||
// Handle click
|
||||
window.Show()
|
||||
})
|
||||
menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
@@ -114,6 +117,13 @@ func main() {
|
||||
|
||||
systemTray.SetMenu(menu)
|
||||
|
||||
|
||||
|
||||
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
app.Event.Emit("window:close")
|
||||
e.Cancel()
|
||||
})
|
||||
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
@@ -10,7 +10,7 @@ const ProxyZipFile = "64bit.zip"
|
||||
const LauncherFile = "firefly-launcher.exe"
|
||||
const TempUrl = "./temp"
|
||||
|
||||
const CurrentLauncherVersion = "2.0.1"
|
||||
const CurrentLauncherVersion = "2.3.0"
|
||||
|
||||
type ToolFile string
|
||||
|
||||
|
||||