mirror of
https://github.com/dream-pep/koring-launcher.git
synced 2026-09-12 05:45:18 +08:00
UI完善
This commit is contained in:
@@ -0,0 +1 @@
|
||||
dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5b29vWWcwSnZkbzlYQWNNQzJuVEZ2dlJJd3A0MUhTcEpCYmZubFdlVmVvY0FBQkFBQUFBQUFBQUFBQUlBQUFBQXEveUNRTnFXUEE5Q3M5Und2YTFlQWdRUVh1L3hudGJaTnZlRlRHM3FwZkVHQXBhNlpQWmF3bkcwTGJ5N1MwbEFIRFVXM3lCc3QxdUVOeDJqeVdVK29lZkllRmxWZ2FxcW1CK2cvYTFwMUptQ0hMZEpjZXhpdU1Ndm9CVWcrTk91aXBiSHB4MVBKdU09Cg==
|
||||
@@ -0,0 +1 @@
|
||||
dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEYxRDZBQjc3QUQxNDBGNDUKUldSRkR4U3RkNnZXOGNtdHE4TS80d3p4UHlhaEVVMDFUTFRrQmhySnFVNWdZWHNXOWUraFFhRTcK
|
||||
@@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
call "C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsall.bat" arm64
|
||||
set PATH=C:\Users\OseasyVM\scoop\apps\llvm\current\bin;%PATH%
|
||||
set CC=clang
|
||||
cd /d "%~dp0"
|
||||
pnpm dev:t
|
||||
+7
-1
@@ -12,15 +12,20 @@
|
||||
"build:beta": "node scripts/switch-icon.js beta && tsc && vite build --mode beta",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"dev:t": "node scripts/switch-icon.js dev && tauri dev"
|
||||
"dev:t": "node scripts/switch-icon.js dev && tauri dev",
|
||||
"dev:vs": "./dev-vs.cmd"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.6.0",
|
||||
"@fontsource-variable/geist": "^5.2.9",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@react-three/fiber": "^9.6.1",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-shell": "^2.3.5",
|
||||
"@tauri-apps/plugin-updater": "^2.10.1",
|
||||
"@types/three": "^0.184.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.21.0",
|
||||
@@ -28,6 +33,7 @@
|
||||
"react-dom": "^19.1.0",
|
||||
"shadcn": "^4.11.0",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"three": "^0.184.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"zustand": "^5.0.14"
|
||||
},
|
||||
|
||||
Generated
+184
@@ -17,15 +17,27 @@ importers:
|
||||
'@fontsource-variable/inter':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
'@react-three/fiber':
|
||||
specifier: ^9.6.1
|
||||
version: 9.6.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.184.0)
|
||||
'@tauri-apps/api':
|
||||
specifier: ^2
|
||||
version: 2.11.1
|
||||
'@tauri-apps/plugin-opener':
|
||||
specifier: ^2
|
||||
version: 2.5.4
|
||||
'@tauri-apps/plugin-process':
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1
|
||||
'@tauri-apps/plugin-shell':
|
||||
specifier: ^2.3.5
|
||||
version: 2.3.5
|
||||
'@tauri-apps/plugin-updater':
|
||||
specifier: ^2.10.1
|
||||
version: 2.10.1
|
||||
'@types/three':
|
||||
specifier: ^0.184.1
|
||||
version: 0.184.1
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
@@ -47,6 +59,9 @@ importers:
|
||||
tailwind-merge:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
three:
|
||||
specifier: ^0.184.0
|
||||
version: 0.184.0
|
||||
tw-animate-css:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
@@ -253,6 +268,9 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@dimforge/rapier3d-compat@0.12.0':
|
||||
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
||||
|
||||
'@dotenvx/dotenvx@1.74.2':
|
||||
resolution: {integrity: sha512-UucMF9L95sVr/feRojWB/NIc5R+CzoiiXq7rd7sWppYA7j6CIH5bu5bQCuoJ0HJxQARR0Ecg4bYot3ilo100lw==}
|
||||
hasBin: true
|
||||
@@ -496,6 +514,31 @@ packages:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@react-three/fiber@9.6.1':
|
||||
resolution: {integrity: sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==}
|
||||
peerDependencies:
|
||||
expo: '>=43.0'
|
||||
expo-asset: '>=8.4'
|
||||
expo-file-system: '>=11.0'
|
||||
expo-gl: '>=11.0'
|
||||
react: '>=19 <19.3'
|
||||
react-dom: '>=19 <19.3'
|
||||
react-native: '>=0.78'
|
||||
three: '>=0.156'
|
||||
peerDependenciesMeta:
|
||||
expo:
|
||||
optional: true
|
||||
expo-asset:
|
||||
optional: true
|
||||
expo-file-system:
|
||||
optional: true
|
||||
expo-gl:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27':
|
||||
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
||||
|
||||
@@ -820,12 +863,21 @@ packages:
|
||||
'@tauri-apps/plugin-opener@2.5.4':
|
||||
resolution: {integrity: sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==}
|
||||
|
||||
'@tauri-apps/plugin-process@2.3.1':
|
||||
resolution: {integrity: sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==}
|
||||
|
||||
'@tauri-apps/plugin-shell@2.3.5':
|
||||
resolution: {integrity: sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==}
|
||||
|
||||
'@tauri-apps/plugin-updater@2.10.1':
|
||||
resolution: {integrity: sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==}
|
||||
|
||||
'@ts-morph/common@0.27.0':
|
||||
resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==}
|
||||
|
||||
'@tweenjs/tween.js@23.1.3':
|
||||
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@@ -846,12 +898,26 @@ packages:
|
||||
peerDependencies:
|
||||
'@types/react': ^19.2.0
|
||||
|
||||
'@types/react-reconciler@0.28.9':
|
||||
resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
|
||||
'@types/react@19.2.17':
|
||||
resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==}
|
||||
|
||||
'@types/stats.js@0.17.4':
|
||||
resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==}
|
||||
|
||||
'@types/three@0.184.1':
|
||||
resolution: {integrity: sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA==}
|
||||
|
||||
'@types/validate-npm-package-name@4.0.2':
|
||||
resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==}
|
||||
|
||||
'@types/webxr@0.5.24':
|
||||
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
|
||||
|
||||
'@vitejs/plugin-react@4.7.0':
|
||||
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
@@ -912,6 +978,9 @@ packages:
|
||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
baseline-browser-mapping@2.10.38:
|
||||
resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -934,6 +1003,9 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||
|
||||
bundle-name@4.1.0:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1225,6 +1297,9 @@ packages:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
|
||||
fflate@0.8.3:
|
||||
resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==}
|
||||
|
||||
figures@6.1.0:
|
||||
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1339,6 +1414,9 @@ packages:
|
||||
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -1446,6 +1524,11 @@ packages:
|
||||
resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
its-fine@2.0.0:
|
||||
resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==}
|
||||
peerDependencies:
|
||||
react: ^19.0.0
|
||||
|
||||
jiti@2.7.0:
|
||||
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
|
||||
hasBin: true
|
||||
@@ -1608,6 +1691,9 @@ packages:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
meshoptimizer@1.1.1:
|
||||
resolution: {integrity: sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==}
|
||||
|
||||
micromatch@4.0.8:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
@@ -1824,6 +1910,15 @@ packages:
|
||||
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react-use-measure@2.1.7:
|
||||
resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==}
|
||||
peerDependencies:
|
||||
react: '>=16.13'
|
||||
react-dom: '>=16.13'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
react@19.2.7:
|
||||
resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1975,6 +2070,11 @@ packages:
|
||||
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
suspend-react@0.1.3:
|
||||
resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==}
|
||||
peerDependencies:
|
||||
react: '>=17.0'
|
||||
|
||||
systeminformation@5.31.7:
|
||||
resolution: {integrity: sha512-/8NC53e5nP9nmhn42/ncdOkyJnOoue/Vy+tJOyUGd1Yv66G069wK4rrziwhrqDETgk78CudTQupw5z19S5uoZw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
@@ -1991,6 +2091,9 @@ packages:
|
||||
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
three@0.184.0:
|
||||
resolution: {integrity: sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==}
|
||||
|
||||
tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
|
||||
@@ -2387,6 +2490,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@dimforge/rapier3d-compat@0.12.0': {}
|
||||
|
||||
'@dotenvx/dotenvx@1.74.2':
|
||||
dependencies:
|
||||
commander: 11.1.0
|
||||
@@ -2574,6 +2679,26 @@ snapshots:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.20.1
|
||||
|
||||
'@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.184.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
'@types/webxr': 0.5.24
|
||||
base64-js: 1.5.1
|
||||
buffer: 6.0.3
|
||||
its-fine: 2.0.0(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
react-use-measure: 2.1.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
scheduler: 0.27.0
|
||||
suspend-react: 0.1.3(react@19.2.7)
|
||||
three: 0.184.0
|
||||
use-sync-external-store: 1.6.0(react@19.2.7)
|
||||
zustand: 5.0.14(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7))
|
||||
optionalDependencies:
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- immer
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.62.0':
|
||||
@@ -2776,16 +2901,26 @@ snapshots:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.11.1
|
||||
|
||||
'@tauri-apps/plugin-process@2.3.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.11.1
|
||||
|
||||
'@tauri-apps/plugin-shell@2.3.5':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.11.1
|
||||
|
||||
'@tauri-apps/plugin-updater@2.10.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.11.1
|
||||
|
||||
'@ts-morph/common@0.27.0':
|
||||
dependencies:
|
||||
fast-glob: 3.3.3
|
||||
minimatch: 10.2.5
|
||||
path-browserify: 1.0.1
|
||||
|
||||
'@tweenjs/tween.js@23.1.3': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.7
|
||||
@@ -2813,12 +2948,29 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@types/react-reconciler@0.28.9(@types/react@19.2.17)':
|
||||
dependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@types/react@19.2.17':
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/stats.js@0.17.4': {}
|
||||
|
||||
'@types/three@0.184.1':
|
||||
dependencies:
|
||||
'@dimforge/rapier3d-compat': 0.12.0
|
||||
'@tweenjs/tween.js': 23.1.3
|
||||
'@types/stats.js': 0.17.4
|
||||
'@types/webxr': 0.5.24
|
||||
fflate: 0.8.3
|
||||
meshoptimizer: 1.1.1
|
||||
|
||||
'@types/validate-npm-package-name@4.0.2': {}
|
||||
|
||||
'@types/webxr@0.5.24': {}
|
||||
|
||||
'@vitejs/plugin-react@4.7.0(vite@7.3.5(jiti@2.7.0)(lightningcss@1.32.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
@@ -2869,6 +3021,8 @@ snapshots:
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.10.38: {}
|
||||
|
||||
body-parser@2.3.0:
|
||||
@@ -2901,6 +3055,11 @@ snapshots:
|
||||
node-releases: 2.0.48
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
||||
|
||||
buffer@6.0.3:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
bundle-name@4.1.0:
|
||||
dependencies:
|
||||
run-applescript: 7.1.0
|
||||
@@ -3205,6 +3364,8 @@ snapshots:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
|
||||
fflate@0.8.3: {}
|
||||
|
||||
figures@6.1.0:
|
||||
dependencies:
|
||||
is-unicode-supported: 2.1.0
|
||||
@@ -3319,6 +3480,8 @@ snapshots:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
@@ -3384,6 +3547,13 @@ snapshots:
|
||||
|
||||
isexe@3.1.5: {}
|
||||
|
||||
its-fine@2.0.0(@types/react@19.2.17)(react@19.2.7):
|
||||
dependencies:
|
||||
'@types/react-reconciler': 0.28.9(@types/react@19.2.17)
|
||||
react: 19.2.7
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
jiti@2.7.0: {}
|
||||
|
||||
jose@6.2.3: {}
|
||||
@@ -3499,6 +3669,8 @@ snapshots:
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
meshoptimizer@1.1.1: {}
|
||||
|
||||
micromatch@4.0.8:
|
||||
dependencies:
|
||||
braces: 3.0.3
|
||||
@@ -3692,6 +3864,12 @@ snapshots:
|
||||
|
||||
react-refresh@0.17.0: {}
|
||||
|
||||
react-use-measure@2.1.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
react@19.2.7: {}
|
||||
|
||||
recast@0.23.11:
|
||||
@@ -3912,6 +4090,10 @@ snapshots:
|
||||
|
||||
strip-final-newline@4.0.0: {}
|
||||
|
||||
suspend-react@0.1.3(react@19.2.7):
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
|
||||
systeminformation@5.31.7: {}
|
||||
|
||||
tailwind-merge@3.6.0: {}
|
||||
@@ -3920,6 +4102,8 @@ snapshots:
|
||||
|
||||
tapable@2.3.3: {}
|
||||
|
||||
three@0.184.0: {}
|
||||
|
||||
tiny-invariant@1.3.3: {}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@
|
||||
</div>
|
||||
<div class="bottom-bar">
|
||||
<span>Provided by Lingke Koring Studio</span>
|
||||
<span>v0.1.0</span>
|
||||
<span>UI预览版本</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Generated
+385
-7
@@ -47,6 +47,15 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
@@ -412,9 +421,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.64"
|
||||
version = "1.2.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
|
||||
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -670,6 +679,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.1.1"
|
||||
@@ -963,6 +983,16 @@ dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
@@ -1507,6 +1537,21 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.20"
|
||||
@@ -1775,6 +1820,36 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-macros",
|
||||
"jni-sys 0.4.1",
|
||||
"log",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.18",
|
||||
"walkdir",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-macros"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"simd_cesu8",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.1"
|
||||
@@ -1856,7 +1931,9 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-process",
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-updater",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
@@ -1978,6 +2055,12 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minisign-verify"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -2210,6 +2293,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
@@ -2225,6 +2309,18 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-osa-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.2"
|
||||
@@ -2300,6 +2396,12 @@ dependencies = [
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@@ -2326,6 +2428,20 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "osakit"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-osa-kit",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -2723,15 +2839,20 @@ dependencies = [
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
@@ -2743,6 +2864,20 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
@@ -2771,6 +2906,79 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
|
||||
dependencies = [
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"jni 0.22.4",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier-android"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
@@ -2786,6 +2994,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
@@ -2843,6 +3060,29 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.36.1"
|
||||
@@ -3095,6 +3335,22 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
|
||||
[[package]]
|
||||
name = "simd_cesu8"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.3"
|
||||
@@ -3207,6 +3463,12 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
@@ -3290,7 +3552,7 @@ dependencies = [
|
||||
"gdkwayland-sys",
|
||||
"gdkx11-sys",
|
||||
"gtk",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
@@ -3323,6 +3585,17 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.16"
|
||||
@@ -3346,7 +3619,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"libc",
|
||||
"log",
|
||||
"mime",
|
||||
@@ -3480,6 +3753,16 @@ dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-process"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a"
|
||||
dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "2.3.5"
|
||||
@@ -3501,6 +3784,39 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"http",
|
||||
"infer",
|
||||
"log",
|
||||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
"reqwest",
|
||||
"rustls",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tar",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"windows-sys 0.60.2",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.11.3"
|
||||
@@ -3511,7 +3827,7 @@ dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
"http",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"objc2",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
@@ -3534,7 +3850,7 @@ checksum = "fe41e015bf8fc4d6477ff4926a0ef769dc64ff34c7b0038b6f7cacae892acb5c"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
@@ -3733,6 +4049,16 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
@@ -4046,6 +4372,12 @@ version = "1.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -4295,6 +4627,15 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.38.2"
|
||||
@@ -4525,6 +4866,15 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -4820,7 +5170,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"javascriptcore-rs",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"libc",
|
||||
"ndk",
|
||||
"objc2",
|
||||
@@ -4867,6 +5217,16 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.3"
|
||||
@@ -4972,6 +5332,12 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
@@ -5005,6 +5371,18 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"indexmap 2.14.0",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
|
||||
@@ -20,7 +20,9 @@ tauri-build = { version = "2", features = [] }
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-process = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
"core:window:allow-set-focus",
|
||||
"core:webview:allow-create-webview-window",
|
||||
"opener:default",
|
||||
"process:default",
|
||||
"shell:allow-execute",
|
||||
"updater:default",
|
||||
{
|
||||
"identifier": "shell:allow-execute",
|
||||
"allow": [
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 200 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 393 KiB After Width: | Height: | Size: 365 KiB |
@@ -9,7 +9,9 @@ use tauri::Manager;
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.manage(Mutex::new(SidecarManager::new()))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::sidecar_request,
|
||||
|
||||
@@ -52,6 +52,15 @@
|
||||
],
|
||||
"externalBin": [
|
||||
"binaries/koring-sidecar"
|
||||
]
|
||||
],
|
||||
"createUpdaterArtifacts": true
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"endpoints": [
|
||||
"https://cdn.crabnebula.app/update/lingke-network/koring-launcher/{{target}}-{{arch}}/{{current_version}}"
|
||||
],
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEYxRDZBQjc3QUQxNDBGNDUKUldSRkR4U3RkNnZXOGNtdHE4TS80d3p4UHlhaEVVMDFUTFRrQmhySnFVNWdZWHNXOWUraFFhRTcK"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -6,7 +6,10 @@ import { Store } from "./pages/store";
|
||||
import { Today } from "./pages/today";
|
||||
import { PlayLink } from "./pages/play-link";
|
||||
import { Setting } from "./pages/setting";
|
||||
import { Debug } from "./pages/setting/debug";
|
||||
import { Debug } from "./pages/debug";
|
||||
import { SplashDebug } from "./pages/debug/splash-debug";
|
||||
import { DisplayDebug } from "./pages/debug/display-debug";
|
||||
import { VersionCardDebug } from "./pages/debug/version-card-debug";
|
||||
|
||||
const pageMap = {
|
||||
home: Home,
|
||||
@@ -15,6 +18,9 @@ const pageMap = {
|
||||
"play-link": PlayLink,
|
||||
setting: Setting,
|
||||
debug: Debug,
|
||||
"debug-splash": SplashDebug,
|
||||
"debug-display": DisplayDebug,
|
||||
"debug-version-card": VersionCardDebug,
|
||||
} as const;
|
||||
|
||||
function App() {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { check, type Update } from "@tauri-apps/plugin-updater";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
|
||||
export interface DownloadProgress {
|
||||
downloaded: number;
|
||||
contentLength: number;
|
||||
percent: number;
|
||||
}
|
||||
|
||||
export async function checkForUpdates(): Promise<Update | null> {
|
||||
const update = await check();
|
||||
return update;
|
||||
}
|
||||
|
||||
export async function downloadAndInstall(
|
||||
update: Update,
|
||||
onProgress?: (progress: DownloadProgress) => void,
|
||||
): Promise<void> {
|
||||
let downloaded = 0;
|
||||
let contentLength = 0;
|
||||
|
||||
await update.downloadAndInstall((event) => {
|
||||
switch (event.event) {
|
||||
case "Started":
|
||||
contentLength = event.data.contentLength ?? 0;
|
||||
break;
|
||||
case "Progress":
|
||||
downloaded += event.data.chunkLength;
|
||||
onProgress?.({
|
||||
downloaded,
|
||||
contentLength,
|
||||
percent: contentLength > 0 ? Math.round((downloaded / contentLength) * 100) : 0,
|
||||
});
|
||||
break;
|
||||
case "Finished":
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function relaunchApp(): Promise<void> {
|
||||
await relaunch();
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { BUILD_MODE } from "@/lib/mode";
|
||||
import { useUpdateStore } from "@/stores/updateStore";
|
||||
import { relaunchApp } from "@/api/update";
|
||||
import Silk from "@/components/silk/Silk";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import clsx from "clsx";
|
||||
|
||||
const modeColors: Record<string, string> = {
|
||||
dev: "#F59E0B",
|
||||
beta: "#10B981",
|
||||
run: "#3B82F6",
|
||||
};
|
||||
|
||||
const modeLabels: Record<string, string> = {
|
||||
dev: "开发版",
|
||||
beta: "测试版",
|
||||
run: "正式版",
|
||||
};
|
||||
|
||||
const VERSION = "0.1.0";
|
||||
|
||||
type UpdateState = "latest" | "hasUpdate" | "installed";
|
||||
|
||||
interface VersionCardProps {
|
||||
className?: string;
|
||||
/** 开发者模式:覆盖颜色 */
|
||||
overrideMode?: string | null;
|
||||
/** 开发者模式:覆盖更新状态 */
|
||||
overrideState?: UpdateState | null;
|
||||
/** 开发者模式:遮罩透明度 (0-100) */
|
||||
overlayOpacity?: number;
|
||||
/** 开发者模式:模糊强度 (px) */
|
||||
blurAmount?: number;
|
||||
}
|
||||
|
||||
export function VersionCard({
|
||||
className,
|
||||
overrideMode,
|
||||
overrideState,
|
||||
overlayOpacity = 30,
|
||||
blurAmount = 12,
|
||||
}: VersionCardProps) {
|
||||
const color = modeColors[overrideMode ?? BUILD_MODE] ?? modeColors.run;
|
||||
const label = modeLabels[overrideMode ?? BUILD_MODE] ?? modeLabels.run;
|
||||
|
||||
const { checking, downloading, installed, update, check, install } = useUpdateStore();
|
||||
|
||||
const effectiveState: UpdateState = overrideState ?? (installed ? "installed" : update ? "hasUpdate" : "latest");
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"relative overflow-hidden rounded-xl border border-white/10",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{/* Silk 背景 */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Silk speed={3} scale={1.2} color={color} noiseIntensity={1.2} rotation={0.3} />
|
||||
</div>
|
||||
|
||||
{/* 磨砂层 */}
|
||||
<div
|
||||
className="absolute inset-0 z-[1]"
|
||||
style={{
|
||||
background: `rgba(0,0,0,${overlayOpacity / 100})`,
|
||||
backdropFilter: `blur(${blurAmount}px)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 内容层 */}
|
||||
<div className="relative z-10 flex flex-col items-center justify-center px-6 py-10 gap-4">
|
||||
{/* 版本类型 */}
|
||||
<span className="text-[11px] font-bold tracking-wider text-white/70">
|
||||
{label}
|
||||
</span>
|
||||
|
||||
{/* Logo */}
|
||||
<img src="/koring-licon.svg" alt="Koring" className="w-50 h-20 drop-shadow-lg" style={{ filter: "brightness(0) invert(1)" }} />
|
||||
|
||||
{/* 版本号 */}
|
||||
<p className="text-sm text-white/70 font-medium">v{VERSION}</p>
|
||||
|
||||
{/* 按钮组 */}
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
{effectiveState === "latest" && (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="bg-white/10 border-white/20 text-white hover:bg-white/20 hover:text-white"
|
||||
onClick={check}
|
||||
disabled={checking}
|
||||
>
|
||||
{checking ? "检查中..." : "检查更新"}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="bg-white/10 border-white/20 text-white hover:bg-white/20 hover:text-white"
|
||||
>
|
||||
查看亮点
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{effectiveState === "hasUpdate" && (
|
||||
<>
|
||||
<span className="text-[12px] text-white/80 mr-1">
|
||||
有新的版本可用
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="bg-white/10 border-white/20 text-white hover:bg-white/20 hover:text-white"
|
||||
onClick={install}
|
||||
disabled={downloading}
|
||||
>
|
||||
{downloading ? "下载中..." : "下载更新"}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="bg-white/10 border-white/20 text-white hover:bg-white/20 hover:text-white"
|
||||
>
|
||||
查看亮点
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{effectiveState === "installed" && (
|
||||
<>
|
||||
<span className="text-[12px] text-white/80 mr-1">
|
||||
更新已下载
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="bg-white/10 border-white/20 text-white hover:bg-white/20 hover:text-white"
|
||||
onClick={relaunchApp}
|
||||
>
|
||||
立即更新
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export function BackgroundLayer() {
|
||||
const { type, image, color, blur, opacity, animationSpeed, fetchConfig } = useBackgroundStore();
|
||||
const route = useRouteStore((s) => s.current);
|
||||
const forceDisableContentBlur = useDevStore((s) => s.forceDisableContentBlur);
|
||||
const inSetting = route === "setting" || route === "debug";
|
||||
const showContentBlur = route !== "home";
|
||||
|
||||
useEffect(() => {
|
||||
fetchConfig();
|
||||
@@ -82,7 +82,10 @@ export function BackgroundLayer() {
|
||||
}
|
||||
`}</style>
|
||||
<div style={getBackgroundStyle()} />
|
||||
{inSetting && !forceDisableContentBlur && <div className="content-blur-overlay" />}
|
||||
<div
|
||||
className="content-blur-overlay"
|
||||
style={{ opacity: showContentBlur && !forceDisableContentBlur ? 1 : 0 }}
|
||||
/>
|
||||
<div className="dark:block hidden fixed inset-0 z-0 pointer-events-none bg-black/35" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
||||
import { forwardRef, useRef, useMemo, useLayoutEffect } from "react";
|
||||
import { Color, type Mesh, type ShaderMaterial } from "three";
|
||||
|
||||
const hexToNormalizedRGB = (hex: string) => {
|
||||
hex = hex.replace("#", "");
|
||||
return [
|
||||
parseInt(hex.slice(0, 2), 16) / 255,
|
||||
parseInt(hex.slice(2, 4), 16) / 255,
|
||||
parseInt(hex.slice(4, 6), 16) / 255,
|
||||
];
|
||||
};
|
||||
|
||||
const vertexShader = `
|
||||
varying vec2 vUv;
|
||||
varying vec3 vPosition;
|
||||
|
||||
void main() {
|
||||
vPosition = position;
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const fragmentShader = `
|
||||
varying vec2 vUv;
|
||||
varying vec3 vPosition;
|
||||
|
||||
uniform float uTime;
|
||||
uniform vec3 uColor;
|
||||
uniform float uSpeed;
|
||||
uniform float uScale;
|
||||
uniform float uRotation;
|
||||
uniform float uNoiseIntensity;
|
||||
|
||||
const float e = 2.71828182845904523536;
|
||||
|
||||
float noise(vec2 texCoord) {
|
||||
float G = e;
|
||||
vec2 r = (G * sin(G * texCoord));
|
||||
return fract(r.x * r.y * (1.0 + texCoord.x));
|
||||
}
|
||||
|
||||
vec2 rotateUvs(vec2 uv, float angle) {
|
||||
float c = cos(angle);
|
||||
float s = sin(angle);
|
||||
mat2 rot = mat2(c, -s, s, c);
|
||||
return rot * uv;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float rnd = noise(gl_FragCoord.xy);
|
||||
vec2 uv = rotateUvs(vUv * uScale, uRotation);
|
||||
vec2 tex = uv * uScale;
|
||||
float tOffset = uSpeed * uTime;
|
||||
|
||||
tex.y += 0.03 * sin(8.0 * tex.x - tOffset);
|
||||
|
||||
float pattern = 0.6 +
|
||||
0.4 * sin(5.0 * (tex.x + tex.y +
|
||||
cos(3.0 * tex.x + 5.0 * tex.y) +
|
||||
0.02 * tOffset) +
|
||||
sin(20.0 * (tex.x + tex.y - 0.1 * tOffset)));
|
||||
|
||||
vec4 col = vec4(uColor, 1.0) * vec4(pattern) - rnd / 15.0 * uNoiseIntensity;
|
||||
col.a = 1.0;
|
||||
gl_FragColor = col;
|
||||
}
|
||||
`;
|
||||
|
||||
interface SilkPlaneProps {
|
||||
uniforms: Record<string, { value: number | Color }>;
|
||||
}
|
||||
|
||||
const SilkPlane = forwardRef(function SilkPlane({ uniforms }: SilkPlaneProps, ref: React.Ref<Mesh>) {
|
||||
const { viewport } = useThree();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref && typeof ref === "object" && ref.current) {
|
||||
ref.current.scale.set(viewport.width, viewport.height, 1);
|
||||
}
|
||||
}, [ref, viewport]);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
if (ref && typeof ref === "object" && ref.current) {
|
||||
(ref.current.material as ShaderMaterial).uniforms.uTime.value += 0.1 * delta;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<mesh ref={ref}>
|
||||
<planeGeometry args={[1, 1, 1, 1]} />
|
||||
<shaderMaterial uniforms={uniforms} vertexShader={vertexShader} fragmentShader={fragmentShader} />
|
||||
</mesh>
|
||||
);
|
||||
});
|
||||
SilkPlane.displayName = "SilkPlane";
|
||||
|
||||
interface SilkProps {
|
||||
speed?: number;
|
||||
scale?: number;
|
||||
color?: string;
|
||||
noiseIntensity?: number;
|
||||
rotation?: number;
|
||||
}
|
||||
|
||||
const Silk = ({ speed = 5, scale = 1, color = "#7B7481", noiseIntensity = 1.5, rotation = 0 }: SilkProps) => {
|
||||
const meshRef = useRef<Mesh>(null);
|
||||
|
||||
const uniforms = useMemo(
|
||||
() => ({
|
||||
uSpeed: { value: speed },
|
||||
uScale: { value: scale },
|
||||
uNoiseIntensity: { value: noiseIntensity },
|
||||
uColor: { value: new Color(...hexToNormalizedRGB(color)) },
|
||||
uRotation: { value: rotation },
|
||||
uTime: { value: 0 },
|
||||
}),
|
||||
[speed, scale, noiseIntensity, color, rotation],
|
||||
);
|
||||
|
||||
return (
|
||||
<Canvas dpr={[1, 2]} frameloop="always">
|
||||
<SilkPlane ref={meshRef} uniforms={uniforms} />
|
||||
</Canvas>
|
||||
);
|
||||
};
|
||||
|
||||
export default Silk;
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { BUILD_MODE } from "@/lib/mode";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface WindowControlsProps {
|
||||
showMinimize?: boolean;
|
||||
@@ -30,8 +32,23 @@ export function WindowControls({
|
||||
const btnClass = "flex items-center justify-center w-[25px] h-[25px] rounded transition-colors cursor-default hover:bg-black/10 dark:hover:bg-white/15 text-black/70 dark:text-white/70 hover:text-black dark:hover:text-white";
|
||||
const closeBtnClass = "flex items-center justify-center w-[25px] h-[25px] rounded transition-colors cursor-default hover:bg-red-500 text-black/70 dark:text-white/70 hover:text-white";
|
||||
|
||||
const showBadge = BUILD_MODE !== "run";
|
||||
const badgeLabel = BUILD_MODE === "dev" ? "DEV" : "BETA";
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-0.5 pr-2" data-no-drag>
|
||||
{showBadge && (
|
||||
<span
|
||||
className={clsx(
|
||||
"inline-flex items-center justify-center h-[18px] px-1.5 rounded text-[10px] font-bold tracking-wider mr-1",
|
||||
BUILD_MODE === "dev"
|
||||
? "bg-amber-500/65 text-amber-700 dark:text-amber-400 border border-amber-500/20"
|
||||
: "bg-emerald-500/65 text-emerald-800 dark:text-emerald-400 border border-emerald-500/20",
|
||||
)}
|
||||
>
|
||||
{badgeLabel}
|
||||
</span>
|
||||
)}
|
||||
{showMinimize && (
|
||||
<div onClick={handleMinimize} className={btnClass}>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
|
||||
+17
-3
@@ -92,7 +92,7 @@
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
|
||||
/* 标题栏 & 导航 - 亮色 */
|
||||
--titlebar-bg: rgba(255, 255, 255, 0.1);
|
||||
--titlebar-bg: rgba(255, 255, 255, 0.3);
|
||||
--titlebar-border: rgba(0, 0, 0, 0.08);
|
||||
--indicator-bg: rgba(0, 0, 0, 0.1);
|
||||
--indicator-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
@@ -200,6 +200,8 @@
|
||||
animation: 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0.05s both page-in-forward;
|
||||
}
|
||||
|
||||
/* 设置子页面转场(已弃用 View Transitions,改用 keyframe) */
|
||||
|
||||
/* 内容区转场:后退方向 */
|
||||
:root[data-transition-dir="backward"] ::view-transition-old(content) {
|
||||
animation-name: page-out-backward;
|
||||
@@ -226,6 +228,17 @@
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
设置子页面切换 — keyframe 动画(仅内容区)
|
||||
============================================ */
|
||||
.setting-page-enter {
|
||||
animation: setting-page-in 0.25s cubic-bezier(0.4, 0, 0.2, 1) both;
|
||||
}
|
||||
@keyframes setting-page-in {
|
||||
from { opacity: 0; transform: translateY(6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
磨砂卡片 — 复用 --titlebar-bg / --titlebar-border
|
||||
============================================ */
|
||||
@@ -251,10 +264,11 @@
|
||||
-webkit-backdrop-filter: blur(80px) saturate(120%);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
background: rgba(255, 255, 255, var(--content-blur-opacity, 0.5));
|
||||
transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
:root.dark .content-blur-overlay {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: rgba(0, 0, 0, var(--content-blur-opacity, 0.5));
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
|
||||
@@ -17,7 +17,9 @@ export function RootLayout({
|
||||
showMaximize = true,
|
||||
showClose = true,
|
||||
}: RootLayoutProps) {
|
||||
const { reduceMotion, reduceTransparency, highContrast } = useA11yStore();
|
||||
const { reduceMotion, reduceTransparency, highContrast, contentBlurOpacity } = useA11yStore();
|
||||
|
||||
const blurOpacity = reduceTransparency ? 0 : contentBlurOpacity;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -27,6 +29,7 @@ export function RootLayout({
|
||||
reduceTransparency && "reduce-transparency",
|
||||
highContrast && "high-contrast",
|
||||
)}
|
||||
style={{ "--content-blur-opacity": `${blurOpacity / 100}` } as React.CSSProperties}
|
||||
>
|
||||
{/* Layer 0: Background */}
|
||||
<BackgroundLayer />
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { UnderConstruction } from "@/components/UnderConstruction";
|
||||
|
||||
export function PlayLink() {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4 text-foreground">联机</h1>
|
||||
<p className="text-muted-foreground">多人联机与服务器</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <UnderConstruction pageName="联机" description="多人联机与服务器,敬请期待" />;
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
|
||||
export function Debug() {
|
||||
const openSplash = async () => {
|
||||
try {
|
||||
const existing = await WebviewWindow.getByLabel("splashscreen");
|
||||
if (existing) {
|
||||
await existing.show();
|
||||
await existing.setFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
const splash = new WebviewWindow("splashscreen", {
|
||||
url: "/splash.html",
|
||||
width: 480,
|
||||
height: 320,
|
||||
decorations: false,
|
||||
transparent: true,
|
||||
center: true,
|
||||
visible: true,
|
||||
resizable: false,
|
||||
minWidth: 480,
|
||||
maxWidth: 480,
|
||||
minHeight: 320,
|
||||
maxHeight: 320,
|
||||
} as any);
|
||||
|
||||
splash.once("tauri://error", (e) => {
|
||||
console.error("Splash window error:", e);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to open splash:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const closeSplash = async () => {
|
||||
try {
|
||||
const splash = await WebviewWindow.getByLabel("splashscreen");
|
||||
if (splash) {
|
||||
await splash.close();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to close splash:", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-2xl font-bold mb-6 text-foreground">Debug</h1>
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={openSplash}>Open Splash</Button>
|
||||
<Button variant="destructive" onClick={closeSplash}>Close Splash</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,167 @@
|
||||
export function AdvancedSetting() {
|
||||
import { useState } from "react";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
||||
function GlassCard({ children }: { children: React.ReactNode }) {
|
||||
return <div className="glass-card px-5 py-4">{children}</div>;
|
||||
}
|
||||
|
||||
function SettingRow({ label, desc, children }: { label: string; desc?: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
{desc && <p className="text-[13px] text-muted-foreground mt-0.5">{desc}</p>}
|
||||
</div>
|
||||
<div className="shrink-0">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const launcherBehavior = [
|
||||
{ value: "close", label: "关闭启动器" },
|
||||
{ value: "minimize", label: "最小化到任务栏" },
|
||||
{ value: "keep", label: "保持不变" },
|
||||
];
|
||||
|
||||
const windowSize = [
|
||||
{ value: "default", label: "默认窗口大小" },
|
||||
{ value: "fullscreen", label: "全屏启动" },
|
||||
{ value: "custom", label: "自定义尺寸" },
|
||||
];
|
||||
|
||||
export function AdvancedSetting() {
|
||||
const [afterLaunch, setAfterLaunch] = useState("close");
|
||||
const [winMode, setWinMode] = useState("default");
|
||||
const [customWidth, setCustomWidth] = useState("854");
|
||||
const [customHeight, setCustomHeight] = useState("480");
|
||||
const [gameArgs, setGameArgs] = useState("");
|
||||
const [preLaunchCmd, setPreLaunchCmd] = useState("");
|
||||
const [debugMode, setDebugMode] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">高级设置</h2>
|
||||
<p className="text-sm text-muted-foreground">游戏高级选项与调试参数</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">高级设置</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">游戏高级启动参数、调试选项与实验性功能</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== 启动行为 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">启动行为</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">启动后启动器行为</p>
|
||||
<p className="text-[13px] text-muted-foreground">游戏启动后启动器的处理方式</p>
|
||||
<div className="space-y-2 mt-2">
|
||||
{launcherBehavior.map((opt) => (
|
||||
<label key={opt.value} className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="afterLaunch" checked={afterLaunch === opt.value} onChange={() => setAfterLaunch(opt.value)} className="accent-primary" />
|
||||
<span className="text-sm text-foreground">{opt.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 窗口设置 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">窗口设置</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">窗口大小</p>
|
||||
<div className="space-y-2">
|
||||
{windowSize.map((opt) => (
|
||||
<label key={opt.value} className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="winMode" checked={winMode === opt.value} onChange={() => setWinMode(opt.value)} className="accent-primary" />
|
||||
<span className="text-sm text-foreground">{opt.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{winMode === "custom" && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[13px] text-muted-foreground">宽</span>
|
||||
<input
|
||||
type="number"
|
||||
value={customWidth}
|
||||
onChange={(e) => setCustomWidth(e.target.value)}
|
||||
className="w-20 h-8 px-2 rounded-md border border-input bg-background text-sm text-center font-mono focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-muted-foreground">×</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[13px] text-muted-foreground">高</span>
|
||||
<input
|
||||
type="number"
|
||||
value={customHeight}
|
||||
onChange={(e) => setCustomHeight(e.target.value)}
|
||||
className="w-20 h-8 px-2 rounded-md border border-input bg-background text-sm text-center font-mono focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 游戏参数 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">游戏参数</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">额外游戏启动参数</p>
|
||||
<p className="text-[13px] text-muted-foreground">附加到游戏启动命令末尾的参数</p>
|
||||
<input
|
||||
type="text"
|
||||
value={gameArgs}
|
||||
onChange={(e) => setGameArgs(e.target.value)}
|
||||
placeholder="可选,例如 --demo"
|
||||
className="w-full h-8 px-3 rounded-md border border-input bg-background text-sm font-mono placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 启动命令 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">启动命令</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">启动前执行命令</p>
|
||||
<p className="text-[13px] text-muted-foreground">游戏启动前自动执行的命令或程序路径</p>
|
||||
<input
|
||||
type="text"
|
||||
value={preLaunchCmd}
|
||||
onChange={(e) => setPreLaunchCmd(e.target.value)}
|
||||
placeholder="可选,例如 D:\scripts\pre-launch.bat"
|
||||
className="w-full h-8 px-3 rounded-md border border-input bg-background text-sm font-mono placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 调试 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">调试</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<SettingRow label="调试模式" desc="启用后将在控制台输出详细日志,可能影响性能">
|
||||
<Switch checked={debugMode} onCheckedChange={setDebugMode} />
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export function GameAccountSetting() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">游戏账户&档案</h2>
|
||||
<p className="text-sm text-muted-foreground">管理 Minecraft 账户与游戏档案</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">游戏账户&档案</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">管理 Minecraft 游戏内账户、正版验证与游戏档案配置</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,95 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
|
||||
function GlassCard({ children }: { children: React.ReactNode }) {
|
||||
return <div className="glass-card px-5 py-4">{children}</div>;
|
||||
}
|
||||
|
||||
function PathInput({ label, desc, value, onChange }: { label: string; desc: string; value: string; onChange: (v: string) => void }) {
|
||||
return (
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
<p className="text-[13px] text-muted-foreground mt-0.5">{desc}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="flex-1 h-8 px-3 rounded-md border border-input bg-background text-sm font-mono placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
<Button size="sm" variant="outline">浏览</Button>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function GameDirSetting() {
|
||||
const [gameDir, setGameDir] = useState(".minecraft");
|
||||
const [resourceDir, setResourceDir] = useState("");
|
||||
const [savesDir, setSavesDir] = useState("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">游戏目录</h2>
|
||||
<p className="text-sm text-muted-foreground">设置游戏安装与存档目录</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">游戏目录</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">设置 Minecraft 游戏安装路径、存档位置与资源包目录</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== 游戏路径 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">游戏路径</h3>
|
||||
<div className="space-y-3">
|
||||
<PathInput
|
||||
label="游戏安装根目录"
|
||||
desc="Minecraft 游戏文件的根目录,包含 versions、saves、resourcepacks 等文件夹"
|
||||
value={gameDir}
|
||||
onChange={setGameDir}
|
||||
/>
|
||||
<GlassCard>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">已安装版本</p>
|
||||
<p className="text-[13px] text-muted-foreground mt-0.5">扫描当前目录下的游戏版本</p>
|
||||
</div>
|
||||
<Button size="sm" variant="outline">
|
||||
<RefreshCw className="w-3.5 h-3.5 mr-1.5" />
|
||||
扫描
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 资源包 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">资源包目录</h3>
|
||||
<div className="space-y-3">
|
||||
<PathInput
|
||||
label="资源包路径"
|
||||
desc="自定义资源包(材质包)的存放目录,留空使用默认路径"
|
||||
value={resourceDir}
|
||||
onChange={setResourceDir}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 存档 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">存档目录</h3>
|
||||
<div className="space-y-3">
|
||||
<PathInput
|
||||
label="存档路径"
|
||||
desc="自定义世界存档的存放目录,留空使用默认路径"
|
||||
value={savesDir}
|
||||
onChange={setSavesDir}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,185 @@
|
||||
export function JavaMemSetting() {
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Cpu, FolderSearch } from "lucide-react";
|
||||
|
||||
function GlassCard({ children }: { children: React.ReactNode }) {
|
||||
return <div className="glass-card px-5 py-4">{children}</div>;
|
||||
}
|
||||
|
||||
function SettingRow({ label, desc, children }: { label: string; desc?: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
{desc && <p className="text-[13px] text-muted-foreground mt-0.5">{desc}</p>}
|
||||
</div>
|
||||
<div className="shrink-0">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface JavaInfo {
|
||||
path: string;
|
||||
version: string;
|
||||
vendor: string;
|
||||
}
|
||||
|
||||
const mockJavaList: JavaInfo[] = [
|
||||
{ path: "C:\\Program Files\\Java\\jdk-21\\bin\\javaw.exe", version: "21.0.3", vendor: "Oracle OpenJDK" },
|
||||
{ path: "C:\\Program Files\\Eclipse Adoptium\\jdk-17.0.10.7-hotspot\\bin\\javaw.exe", version: "17.0.10", vendor: "Eclipse Temurin" },
|
||||
];
|
||||
|
||||
const gcOptions = [
|
||||
{ value: "auto", label: "不指定(由 Java 自动选择)" },
|
||||
{ value: "zgc", label: "ZGC(低延迟,推荐 Java 21+)" },
|
||||
{ value: "g1", label: "G1GC(标准,兼容性好)" },
|
||||
];
|
||||
|
||||
export function JavaMemSetting() {
|
||||
const [javaPath, setJavaPath] = useState("");
|
||||
const [memMode, setMemMode] = useState<"auto" | "custom">("auto");
|
||||
const [memGB, setMemGB] = useState(4);
|
||||
const [gc, setGc] = useState("auto");
|
||||
const [jvmArgs, setJvmArgs] = useState("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">Java虚拟机与内存</h2>
|
||||
<p className="text-sm text-muted-foreground">配置 Java 路径与内存分配</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">Java 虚拟机与内存</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">配置 Java 运行环境路径、JVM 参数与游戏内存分配</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== Java 环境 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">Java 环境</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<SettingRow label="自动检测" desc="扫描系统中已安装的 Java 版本">
|
||||
<Button size="sm" variant="outline">
|
||||
<FolderSearch className="w-3.5 h-3.5 mr-1.5" />
|
||||
检测
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
|
||||
{/* 检测结果列表 */}
|
||||
{mockJavaList.map((java) => (
|
||||
<GlassCard key={java.path}>
|
||||
<div className="flex items-center gap-3">
|
||||
<Cpu className="w-4 h-4 text-muted-foreground shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
{java.vendor} {java.version}
|
||||
</p>
|
||||
<p className="text-[11px] text-muted-foreground/60 mt-0.5 font-mono truncate">
|
||||
{java.path}
|
||||
</p>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" className="shrink-0">
|
||||
使用
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
))}
|
||||
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">手动指定路径</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={javaPath}
|
||||
onChange={(e) => setJavaPath(e.target.value)}
|
||||
placeholder="输入 javaw.exe 完整路径"
|
||||
className="flex-1 h-8 px-3 rounded-md border border-input bg-background text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
<Button size="sm" variant="outline">浏览</Button>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 内存分配 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">内存分配</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="mem" checked={memMode === "auto"} onChange={() => setMemMode("auto")} className="accent-primary" />
|
||||
<span className="text-sm text-foreground">自动配置</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="mem" checked={memMode === "custom"} onChange={() => setMemMode("custom")} className="accent-primary" />
|
||||
<span className="text-sm text-foreground">自定义</span>
|
||||
</label>
|
||||
</div>
|
||||
{memMode === "custom" && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<span className="text-[13px] text-muted-foreground">分配内存</span>
|
||||
<span className="text-[13px] text-muted-foreground tabular-nums">{memGB} GB</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[memGB]}
|
||||
onValueChange={(v) => setMemGB(Array.isArray(v) ? v[0] : v)}
|
||||
min={1}
|
||||
max={16}
|
||||
step={1}
|
||||
/>
|
||||
<div className="flex justify-between mt-1">
|
||||
<span className="text-[11px] text-muted-foreground/50">1 GB</span>
|
||||
<span className="text-[11px] text-muted-foreground/50">16 GB</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== JVM 参数 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">JVM 参数</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">额外 JVM 启动参数</p>
|
||||
<p className="text-[13px] text-muted-foreground">每行一个参数,例如 -XX:+UseZGC</p>
|
||||
<textarea
|
||||
value={jvmArgs}
|
||||
onChange={(e) => setJvmArgs(e.target.value)}
|
||||
placeholder="可选,留空使用默认参数"
|
||||
rows={3}
|
||||
className="w-full px-3 py-2 rounded-md border border-input bg-background text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring resize-none font-mono"
|
||||
/>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 垃圾回收 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">垃圾回收</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">GC 算法</p>
|
||||
<div className="space-y-2">
|
||||
{gcOptions.map((opt) => (
|
||||
<label key={opt.value} className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="gc" checked={gc === opt.value} onChange={() => setGc(opt.value)} className="accent-primary" />
|
||||
<span className="text-sm text-foreground">{opt.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,101 @@
|
||||
export function AboutSetting() {
|
||||
import { VersionCard } from "@/components/VersionCard";
|
||||
import { BUILD_MODE } from "@/lib/mode";
|
||||
import { ExternalLink, GitFork } from "lucide-react";
|
||||
import { open } from "@tauri-apps/plugin-shell";
|
||||
|
||||
function GlassCard({ children }: { children: React.ReactNode }) {
|
||||
return <div className="glass-card px-5 py-4">{children}</div>;
|
||||
}
|
||||
|
||||
function SettingRow({ label, desc, children }: { label: string; desc?: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
{desc && <p className="text-[13px] text-muted-foreground mt-0.5">{desc}</p>}
|
||||
</div>
|
||||
<div className="shrink-0">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const modeLabels: Record<string, string> = {
|
||||
dev: "开发版",
|
||||
beta: "测试版",
|
||||
run: "正式版",
|
||||
};
|
||||
|
||||
const GITHUB_URL = "https://github.com/koring-launcher/koring-launcher";
|
||||
const OFFICIAL_URL = "https://koring.app";
|
||||
|
||||
export function AboutSetting() {
|
||||
const openLink = (url: string) => {
|
||||
open(url).catch(() => {
|
||||
window.open(url, "_blank");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">关于</h2>
|
||||
<p className="text-sm text-muted-foreground">Koring Launcher 版本信息</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">关于</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">查看版本信息、更新状态与项目相关链接</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 版本卡片 */}
|
||||
<VersionCard />
|
||||
|
||||
{/* 项目信息 */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">项目信息</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<SettingRow label="应用名称" desc="Koring Launcher">
|
||||
<span className="text-[13px] text-muted-foreground">Lingke Network 提供技术支持</span>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
<GlassCard>
|
||||
<SettingRow label="构建模式">
|
||||
<span className="text-[13px] text-muted-foreground">{modeLabels[BUILD_MODE] ?? BUILD_MODE}</span>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
<GlassCard>
|
||||
<SettingRow label="技术栈" desc="Tauri 2 + React 19 + TypeScript + @xmcl">
|
||||
<span className="text-[13px] text-muted-foreground">Rust · Node.js</span>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 链接 */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">相关链接</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<SettingRow label="GitHub 仓库" desc="查看源代码、提交 Issue">
|
||||
<button
|
||||
onClick={() => openLink(GITHUB_URL)}
|
||||
className="inline-flex items-center gap-1.5 text-[13px] text-primary hover:underline"
|
||||
>
|
||||
<GitFork className="w-4 h-4" />
|
||||
打开
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
<GlassCard>
|
||||
<SettingRow label="官方网站" desc="了解更多功能与文档">
|
||||
<button
|
||||
onClick={() => openLink(OFFICIAL_URL)}
|
||||
className="inline-flex items-center gap-1.5 text-[13px] text-primary hover:underline"
|
||||
>
|
||||
访问
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,111 @@
|
||||
export function AccountSetting() {
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { UserCircle, LogOut, RefreshCw } from "lucide-react";
|
||||
|
||||
function GlassCard({ children }: { children: React.ReactNode }) {
|
||||
return <div className="glass-card px-5 py-4">{children}</div>;
|
||||
}
|
||||
|
||||
function SettingRow({ label, desc, children }: { label: string; desc?: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
{desc && <p className="text-[13px] text-muted-foreground mt-0.5">{desc}</p>}
|
||||
</div>
|
||||
<div className="shrink-0">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountSetting() {
|
||||
const { user, loading, logout, startMicrosoftLogin } = useAuthStore();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">Koring Account</h2>
|
||||
<p className="text-sm text-muted-foreground">管理你的 Koring 账户</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">Koring 账户</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">管理 Koring 工作室账户,用于同步数据、皮肤与个人配置</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== 账户信息 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">账户信息</h3>
|
||||
<GlassCard>
|
||||
{user ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-14 h-14 rounded-full bg-foreground/[0.06] flex items-center justify-center shrink-0">
|
||||
<UserCircle className="w-8 h-8 text-foreground/40" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-base font-medium text-foreground truncate">{user.username}</p>
|
||||
<p className="text-[13px] text-muted-foreground mt-0.5">
|
||||
{user.xboxProfile ? "Microsoft 账户" : "本地账户"}
|
||||
</p>
|
||||
{user.uuid && (
|
||||
<p className="text-[11px] text-muted-foreground/60 mt-0.5 font-mono">
|
||||
UUID: {user.uuid}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-14 h-14 rounded-full bg-foreground/[0.06] flex items-center justify-center shrink-0">
|
||||
<UserCircle className="w-8 h-8 text-foreground/30" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm text-muted-foreground">尚未登录</p>
|
||||
<p className="text-[12px] text-muted-foreground/60 mt-0.5">登录后可同步数据、皮肤与个人配置</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* ===== 账户操作 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">账户操作</h3>
|
||||
<div className="space-y-3">
|
||||
{user ? (
|
||||
<>
|
||||
<GlassCard>
|
||||
<SettingRow
|
||||
label="同步数据"
|
||||
desc="立即拉取最新的云端配置与数据"
|
||||
>
|
||||
<Button variant="outline" size="sm" disabled={loading}>
|
||||
<RefreshCw className="w-3.5 h-3.5 mr-1.5" />
|
||||
同步
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
<GlassCard>
|
||||
<SettingRow
|
||||
label="退出登录"
|
||||
desc="退出当前账户,数据将保留在本地"
|
||||
>
|
||||
<Button variant="destructive" size="sm" onClick={logout} disabled={loading}>
|
||||
<LogOut className="w-3.5 h-3.5 mr-1.5" />
|
||||
退出
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
</>
|
||||
) : (
|
||||
<GlassCard>
|
||||
<SettingRow
|
||||
label="登录 Koring 账户"
|
||||
desc="通过 Microsoft 账户登录以同步数据、皮肤与个人配置"
|
||||
>
|
||||
<Button size="sm" onClick={() => startMicrosoftLogin("00000000-0000-0000-0000-000000000000")} disabled={loading}>
|
||||
登录
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,103 @@
|
||||
export function CopyrightSetting() {
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { open } from "@tauri-apps/plugin-shell";
|
||||
|
||||
function GlassCard({ children }: { children: React.ReactNode }) {
|
||||
return <div className="glass-card px-5 py-4">{children}</div>;
|
||||
}
|
||||
|
||||
function SettingRow({ label, desc, children }: { label: string; desc?: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
{desc && <p className="text-[13px] text-muted-foreground mt-0.5">{desc}</p>}
|
||||
</div>
|
||||
<div className="shrink-0">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const openLink = (url: string) => {
|
||||
open(url).catch(() => {
|
||||
window.open(url, "_blank");
|
||||
});
|
||||
};
|
||||
|
||||
const licenses = [
|
||||
{ name: "Tauri", license: "MIT / Apache-2.0", url: "https://github.com/tauri-apps/tauri" },
|
||||
{ name: "React", license: "MIT", url: "https://github.com/facebook/react" },
|
||||
{ name: "@xmcl/core", license: "MIT", url: "https://github.com/VoxelCogs/xmcl" },
|
||||
{ name: "@xmcl/installer", license: "MIT", url: "https://github.com/VoxelCogs/xmcl" },
|
||||
{ name: "@xmcl/user", license: "MIT", url: "https://github.com/VoxelCogs/xmcl" },
|
||||
{ name: "Zustand", license: "MIT", url: "https://github.com/pmndrs/zustand" },
|
||||
{ name: "Tailwind CSS", license: "MIT", url: "https://github.com/tailwindlabs/tailwindcss" },
|
||||
{ name: "shadcn/ui", license: "MIT", url: "https://github.com/shadcn-ui/ui" },
|
||||
{ name: "Vite", license: "MIT", url: "https://github.com/vitejs/vite" },
|
||||
{ name: "Lucide React", license: "ISC", url: "https://github.com/lucide-icons/lucide" },
|
||||
];
|
||||
|
||||
const fonts = [
|
||||
{ name: "Alimama ShuHeiTi", license: "商用免费授权", note: "主标题字体" },
|
||||
{ name: "Geist", license: "OFL-1.1", note: "界面字体" },
|
||||
{ name: "Inter", license: "OFL-1.1", note: "界面字体" },
|
||||
];
|
||||
|
||||
export function CopyrightSetting() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">版权</h2>
|
||||
<p className="text-sm text-muted-foreground">开源协议与版权信息</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">版权</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">开源协议、依赖项目授权与字体版权信息</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 项目协议 */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">项目协议</h3>
|
||||
<GlassCard>
|
||||
<SettingRow label="MIT License" desc="Copyright © 2024 Koring Launcher Contributors">
|
||||
<button
|
||||
onClick={() => openLink("https://opensource.org/licenses/MIT")}
|
||||
className="inline-flex items-center gap-1.5 text-[13px] text-primary hover:underline"
|
||||
>
|
||||
查看
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* 开源依赖 */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">开源依赖</h3>
|
||||
<div className="space-y-2">
|
||||
{licenses.map((dep) => (
|
||||
<GlassCard key={dep.name}>
|
||||
<SettingRow label={dep.name} desc={dep.license}>
|
||||
<button
|
||||
onClick={() => openLink(dep.url)}
|
||||
className="inline-flex items-center gap-1 text-[13px] text-primary hover:underline"
|
||||
>
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 字体 */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">字体授权</h3>
|
||||
<div className="space-y-2">
|
||||
{fonts.map((font) => (
|
||||
<GlassCard key={font.name}>
|
||||
<SettingRow label={font.name} desc={font.license}>
|
||||
<span className="text-[12px] text-muted-foreground">{font.note}</span>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,121 @@
|
||||
export function HomeSetting() {
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
import { BUILD_MODE } from "@/lib/mode";
|
||||
import { useUpdateStore } from "@/stores/updateStore";
|
||||
import {
|
||||
UserCircle,
|
||||
Gamepad2,
|
||||
Palette,
|
||||
Download,
|
||||
Globe,
|
||||
Cpu,
|
||||
Info,
|
||||
ChevronRight,
|
||||
} from "lucide-react";
|
||||
|
||||
function GlassCard({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return <div className={`glass-card px-5 py-4 ${className ?? ""}`}>{children}</div>;
|
||||
}
|
||||
|
||||
interface ShortcutItem {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
desc: string;
|
||||
navKey: string;
|
||||
}
|
||||
|
||||
function ShortcutTile({ icon, label, desc, navKey, onClick }: ShortcutItem & { onClick: (key: string) => void }) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => onClick(navKey)}
|
||||
className="glass-card flex items-center gap-3.5 px-4 py-3.5 text-left transition-all duration-150 hover:bg-foreground/[0.04] group"
|
||||
>
|
||||
<span className="flex items-center justify-center w-9 h-9 rounded-lg bg-foreground/[0.06] text-foreground/60 group-hover:text-foreground/80 shrink-0">
|
||||
{icon}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground truncate">{label}</p>
|
||||
<p className="text-[12px] text-muted-foreground truncate">{desc}</p>
|
||||
</div>
|
||||
<ChevronRight className="w-4 h-4 text-foreground/25 shrink-0" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const modeLabels: Record<string, string> = {
|
||||
dev: "开发版",
|
||||
beta: "测试版",
|
||||
run: "正式版",
|
||||
};
|
||||
|
||||
const shortcuts: ShortcutItem[] = [
|
||||
{ icon: <UserCircle className="w-[18px] h-[18px]" />, label: "Koring 账户", desc: "同步数据、皮肤与个人配置", navKey: "account" },
|
||||
{ icon: <Gamepad2 className="w-[18px] h-[18px]" />, label: "游戏账户与档案", desc: "管理游戏内账户和档案配置", navKey: "game-account" },
|
||||
{ icon: <Palette className="w-[18px] h-[18px]" />, label: "主题与背景", desc: "深色模式、背景图片与视差", navKey: "theme-bg" },
|
||||
{ icon: <Download className="w-[18px] h-[18px]" />, label: "下载设置", desc: "下载线程数与存储路径", navKey: "download" },
|
||||
{ icon: <Globe className="w-[18px] h-[18px]" />, label: "联机功能", desc: "以太联机与陶瓦联机", navKey: "ether-online" },
|
||||
{ icon: <Cpu className="w-[18px] h-[18px]" />, label: "Java 虚拟机与内存", desc: "Java 路径与内存分配", navKey: "java-mem" },
|
||||
{ icon: <Info className="w-[18px] h-[18px]" />, label: "关于 Koring Launcher", desc: "版本信息与更新", navKey: "about" },
|
||||
];
|
||||
|
||||
interface HomeSettingProps {
|
||||
onNavigate: (key: string) => void;
|
||||
}
|
||||
|
||||
export function HomeSetting({ onNavigate }: HomeSettingProps) {
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const { update } = useUpdateStore();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">主页</h2>
|
||||
<p className="text-sm text-muted-foreground">设置启动器主页显示内容</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">主页</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">自定义启动器主页的显示内容与常用设置的快捷入口</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== 搜索栏占位 ===== */}
|
||||
<div className="glass-card flex items-center gap-3 px-4 py-3">
|
||||
<svg className="w-4 h-4 text-muted-foreground shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.3-4.3" />
|
||||
</svg>
|
||||
<span className="text-sm text-muted-foreground">查找设置</span>
|
||||
</div>
|
||||
|
||||
{/* ===== 快捷状态 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">快速概览</h3>
|
||||
<GlassCard>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-foreground/[0.06] flex items-center justify-center shrink-0">
|
||||
<UserCircle className="w-7 h-7 text-foreground/40" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground truncate">
|
||||
{user?.username ?? "未登录"}
|
||||
</p>
|
||||
<p className="text-[12px] text-muted-foreground mt-0.5">
|
||||
{user
|
||||
? `${user.xboxProfile ? "Microsoft" : "离线"} 账户`
|
||||
: "登录以同步数据和皮肤"}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-[11px] text-muted-foreground shrink-0">
|
||||
{modeLabels[BUILD_MODE] ?? BUILD_MODE}
|
||||
{update ? " · 有更新" : ""}
|
||||
</span>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* ===== 快捷入口 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">常用设置</h3>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{shortcuts.map((s) => (
|
||||
<ShortcutTile key={s.navKey} {...s} onClick={onNavigate} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+80
-56
@@ -1,4 +1,4 @@
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { useState, useCallback, type ReactNode } from "react";
|
||||
import {
|
||||
Home,
|
||||
UserCircle,
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
Heart,
|
||||
Code,
|
||||
} from "lucide-react";
|
||||
import { useRouteStore } from "@/stores/routeStore";
|
||||
|
||||
import { HomeSetting } from "./general/home";
|
||||
import { AccountSetting } from "./general/account";
|
||||
@@ -39,13 +40,13 @@ import { TawaOnlineSetting } from "./network/tawa-online";
|
||||
import { SecurityIdSetting } from "./network/security-id";
|
||||
import { FeedbackSetting } from "./other/feedback";
|
||||
import { SponsorSetting } from "./other/sponsor";
|
||||
import { DeveloperSetting } from "./other/developer";
|
||||
|
||||
interface MenuItem {
|
||||
key: string;
|
||||
label: string;
|
||||
icon: ReactNode;
|
||||
component: ReactNode;
|
||||
component?: ReactNode;
|
||||
onSelect?: () => void;
|
||||
}
|
||||
|
||||
interface MenuGroup {
|
||||
@@ -55,63 +56,84 @@ interface MenuGroup {
|
||||
|
||||
const iconCls = "w-[18px] h-[18px] shrink-0";
|
||||
|
||||
const menuData: MenuGroup[] = [
|
||||
{
|
||||
title: "通用",
|
||||
items: [
|
||||
{ key: "home", label: "主页", icon: <Home className={iconCls} />, component: <HomeSetting /> },
|
||||
{ key: "account", label: "Koring Account", icon: <UserCircle className={iconCls} />, component: <AccountSetting /> },
|
||||
{ key: "about", label: "关于", icon: <Info className={iconCls} />, component: <AboutSetting /> },
|
||||
{ key: "copyright", label: "版权", icon: <Copyright className={iconCls} />, component: <CopyrightSetting /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "游戏",
|
||||
items: [
|
||||
{ key: "game-account", label: "游戏账户&档案", icon: <Gamepad2 className={iconCls} />, component: <GameAccountSetting /> },
|
||||
{ key: "java-mem", label: "Java虚拟机与内存", icon: <Cpu className={iconCls} />, component: <JavaMemSetting /> },
|
||||
{ key: "game-dir", label: "游戏目录", icon: <FolderOpen className={iconCls} />, component: <GameDirSetting /> },
|
||||
{ key: "advanced", label: "高级设置", icon: <Settings className={iconCls} />, component: <AdvancedSetting /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "个性化",
|
||||
items: [
|
||||
{ key: "theme-bg", label: "主题与背景", icon: <Palette className={iconCls} />, component: <ThemeBgSetting /> },
|
||||
{ key: "ui", label: "主界面", icon: <Monitor className={iconCls} />, component: <UiSetting /> },
|
||||
{ key: "lang", label: "语言", icon: <Languages className={iconCls} />, component: <LangSetting /> },
|
||||
{ key: "a11y", label: "辅助功能", icon: <Accessibility className={iconCls} />, component: <A11ySetting /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "网络",
|
||||
items: [
|
||||
{ key: "download", label: "下载", icon: <Download className={iconCls} />, component: <DownloadSetting /> },
|
||||
{ key: "ether-online", label: "以太联机", icon: <Globe className={iconCls} />, component: <EtherOnlineSetting /> },
|
||||
{ key: "tawa-online", label: "陶瓦联机", icon: <Network className={iconCls} />, component: <TawaOnlineSetting /> },
|
||||
{ key: "security-id", label: "安全识别服务", icon: <Shield className={iconCls} />, component: <SecurityIdSetting /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "其他",
|
||||
items: [
|
||||
{ key: "feedback", label: "服务与反馈", icon: <MessageSquareHeart className={iconCls} />, component: <FeedbackSetting /> },
|
||||
{ key: "sponsor", label: "赞助我们", icon: <Heart className={iconCls} />, component: <SponsorSetting /> },
|
||||
{ key: "developer", label: "开发者选项", icon: <Code className={iconCls} />, component: <DeveloperSetting /> },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const allItems = menuData.flatMap((g) => g.items);
|
||||
function buildMenuData(
|
||||
switchPage: (key: string) => void,
|
||||
routeNavigate: (key: any) => void,
|
||||
): MenuGroup[] {
|
||||
return [
|
||||
{
|
||||
title: "通用",
|
||||
items: [
|
||||
{ key: "home", label: "主页", icon: <Home className={iconCls} />, component: <HomeSetting onNavigate={switchPage} /> },
|
||||
{ key: "account", label: "Koring 账户", icon: <UserCircle className={iconCls} />, component: <AccountSetting /> },
|
||||
{ key: "about", label: "关于", icon: <Info className={iconCls} />, component: <AboutSetting /> },
|
||||
{ key: "copyright", label: "版权", icon: <Copyright className={iconCls} />, component: <CopyrightSetting /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "游戏",
|
||||
items: [
|
||||
{ key: "game-account", label: "游戏账户&档案", icon: <Gamepad2 className={iconCls} />, component: <GameAccountSetting /> },
|
||||
{ key: "java-mem", label: "Java虚拟机与内存", icon: <Cpu className={iconCls} />, component: <JavaMemSetting /> },
|
||||
{ key: "game-dir", label: "游戏目录", icon: <FolderOpen className={iconCls} />, component: <GameDirSetting /> },
|
||||
{ key: "advanced", label: "高级设置", icon: <Settings className={iconCls} />, component: <AdvancedSetting /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "个性化",
|
||||
items: [
|
||||
{ key: "theme-bg", label: "主题与背景", icon: <Palette className={iconCls} />, component: <ThemeBgSetting /> },
|
||||
{ key: "ui", label: "主界面", icon: <Monitor className={iconCls} />, component: <UiSetting /> },
|
||||
{ key: "lang", label: "语言", icon: <Languages className={iconCls} />, component: <LangSetting /> },
|
||||
{ key: "a11y", label: "辅助功能", icon: <Accessibility className={iconCls} />, component: <A11ySetting /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "网络",
|
||||
items: [
|
||||
{ key: "download", label: "下载", icon: <Download className={iconCls} />, component: <DownloadSetting /> },
|
||||
{ key: "ether-online", label: "以太联机", icon: <Globe className={iconCls} />, component: <EtherOnlineSetting /> },
|
||||
{ key: "tawa-online", label: "陶瓦联机", icon: <Network className={iconCls} />, component: <TawaOnlineSetting /> },
|
||||
{ key: "security-id", label: "安全识别服务", icon: <Shield className={iconCls} />, component: <SecurityIdSetting /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "其他",
|
||||
items: [
|
||||
{ key: "feedback", label: "服务与反馈", icon: <MessageSquareHeart className={iconCls} />, component: <FeedbackSetting /> },
|
||||
{ key: "sponsor", label: "赞助我们", icon: <Heart className={iconCls} />, component: <SponsorSetting /> },
|
||||
{ key: "developer", label: "开发者选项", icon: <Code className={iconCls} />, onSelect: () => routeNavigate("debug") },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function Setting() {
|
||||
const [selected, setSelected] = useState("home");
|
||||
const [animKey, setAnimKey] = useState(0);
|
||||
const routeNavigate = useRouteStore((s) => s.navigate);
|
||||
|
||||
const switchPage = useCallback((key: string) => {
|
||||
if (key === selected) return;
|
||||
setSelected(key);
|
||||
setAnimKey((k) => k + 1);
|
||||
}, [selected]);
|
||||
|
||||
const handleItemClick = useCallback((item: MenuItem) => {
|
||||
if (item.onSelect) {
|
||||
item.onSelect();
|
||||
} else {
|
||||
switchPage(item.key);
|
||||
}
|
||||
}, [switchPage]);
|
||||
|
||||
const menuData = buildMenuData(switchPage, routeNavigate);
|
||||
const allItems = menuData.flatMap((g) => g.items);
|
||||
const current = allItems.find((i) => i.key === selected);
|
||||
|
||||
return (
|
||||
<div className="flex h-full">
|
||||
{/* 侧边栏 */}
|
||||
{/* 侧边栏 — 无动画 */}
|
||||
<aside className="scroll-area w-[240px] shrink-0 h-full overflow-y-auto py-5 pl-5 pr-2">
|
||||
<nav className="space-y-5">
|
||||
{menuData.map((group) => (
|
||||
@@ -121,11 +143,11 @@ export function Setting() {
|
||||
</h3>
|
||||
<div className="space-y-0.5">
|
||||
{group.items.map((item) => {
|
||||
const active = selected === item.key;
|
||||
const active = selected === item.key && !item.onSelect;
|
||||
return (
|
||||
<button
|
||||
key={item.key}
|
||||
onClick={() => setSelected(item.key)}
|
||||
onClick={() => handleItemClick(item)}
|
||||
className={[
|
||||
"w-full flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-[14px] transition-all duration-150",
|
||||
active
|
||||
@@ -146,9 +168,11 @@ export function Setting() {
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{/* 内容区 */}
|
||||
{/* 内容区 — 仅内容有 fade 动画 */}
|
||||
<main className="scroll-area flex-1 h-full overflow-y-auto p-8">
|
||||
{current?.component}
|
||||
<div key={animKey} className="setting-page-enter">
|
||||
{current?.component}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,116 @@
|
||||
import { useState } from "react";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
|
||||
function GlassCard({ children }: { children: React.ReactNode }) {
|
||||
return <div className="glass-card px-5 py-4">{children}</div>;
|
||||
}
|
||||
|
||||
const downloadSources = [
|
||||
{ value: "mirror", label: "尽量使用镜像源(推荐国内用户)" },
|
||||
{ value: "official", label: "优先使用官方源" },
|
||||
{ value: "official-only", label: "仅使用官方源" },
|
||||
];
|
||||
|
||||
export function DownloadSetting() {
|
||||
const [fileSource, setFileSource] = useState("mirror");
|
||||
const [versionSource, setVersionSource] = useState("mirror");
|
||||
const [threads, setThreads] = useState(16);
|
||||
const [speedLimit, setSpeedLimit] = useState("0");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">下载</h2>
|
||||
<p className="text-sm text-muted-foreground">下载源与并发数设置</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">下载</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">配置游戏资源下载源、并发数与存储路径</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== 下载源 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">下载源</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">文件下载源</p>
|
||||
<p className="text-[13px] text-muted-foreground">游戏文件(jar、lib)的下载来源</p>
|
||||
<div className="space-y-2 mt-2">
|
||||
{downloadSources.map((opt) => (
|
||||
<label key={opt.value} className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="fileSource" checked={fileSource === opt.value} onChange={() => setFileSource(opt.value)} className="accent-primary" />
|
||||
<span className="text-sm text-foreground">{opt.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">版本列表源</p>
|
||||
<p className="text-[13px] text-muted-foreground">获取可用游戏版本列表的来源</p>
|
||||
<div className="space-y-2 mt-2">
|
||||
{downloadSources.map((opt) => (
|
||||
<label key={opt.value} className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="versionSource" checked={versionSource === opt.value} onChange={() => setVersionSource(opt.value)} className="accent-primary" />
|
||||
<span className="text-sm text-foreground">{opt.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 并发控制 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">并发控制</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">最大下载线程数</p>
|
||||
<p className="text-[13px] text-muted-foreground mt-0.5">同时下载的文件数量,过高可能导致不稳定</p>
|
||||
</div>
|
||||
<span className="text-[13px] text-muted-foreground tabular-nums shrink-0 ml-4">{threads}</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[threads]}
|
||||
onValueChange={(v) => setThreads(Array.isArray(v) ? v[0] : v)}
|
||||
min={1}
|
||||
max={64}
|
||||
step={1}
|
||||
/>
|
||||
<div className="flex justify-between mt-1">
|
||||
<span className="text-[11px] text-muted-foreground/50">1</span>
|
||||
<span className="text-[11px] text-muted-foreground/50">64</span>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 速度限制 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">速度限制</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">下载速度上限</p>
|
||||
<p className="text-[13px] text-muted-foreground">单位 KB/s,0 表示不限速</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="number"
|
||||
value={speedLimit}
|
||||
onChange={(e) => setSpeedLimit(e.target.value)}
|
||||
min={0}
|
||||
className="w-28 h-8 px-3 rounded-md border border-input bg-background text-sm font-mono placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
<span className="text-[13px] text-muted-foreground">KB/s</span>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export function EtherOnlineSetting() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">以太联机</h2>
|
||||
<p className="text-sm text-muted-foreground">以太联机服务配置</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">以太联机</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">配置以太联机服务,实现多人局域网或远程联机</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,67 @@
|
||||
export function SecurityIdSetting() {
|
||||
import { useState } from "react";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { ShieldCheck } from "lucide-react";
|
||||
|
||||
function GlassCard({ children }: { children: React.ReactNode }) {
|
||||
return <div className="glass-card px-5 py-4">{children}</div>;
|
||||
}
|
||||
|
||||
function SettingRow({ label, desc, children }: { label: string; desc?: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
{desc && <p className="text-[13px] text-muted-foreground mt-0.5">{desc}</p>}
|
||||
</div>
|
||||
<div className="shrink-0">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SecurityIdSetting() {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [authUrl, setAuthUrl] = useState("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">安全识别服务</h2>
|
||||
<p className="text-sm text-muted-foreground">安全验证与身份识别设置</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">安全识别服务</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">管理账户安全验证、设备识别与登录保护</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== 认证服务 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">认证服务</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<SettingRow
|
||||
label="启用第三方认证"
|
||||
desc="使用自定义认证服务器替代 Microsoft 认证(适用于离线服务器)"
|
||||
>
|
||||
<Switch checked={enabled} onCheckedChange={setEnabled} />
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
|
||||
{enabled && (
|
||||
<GlassCard>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">认证服务器 URL</p>
|
||||
<p className="text-[13px] text-muted-foreground">输入第三方认证服务的地址</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<ShieldCheck className="w-4 h-4 text-muted-foreground shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
value={authUrl}
|
||||
onChange={(e) => setAuthUrl(e.target.value)}
|
||||
placeholder="https://auth.example.com"
|
||||
className="flex-1 h-8 px-3 rounded-md border border-input bg-background text-sm font-mono placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export function TawaOnlineSetting() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">陶瓦联机</h2>
|
||||
<p className="text-sm text-muted-foreground">陶瓦联机服务配置</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">陶瓦联机</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">配置陶瓦联机服务,通过中继服务器进行多人游戏</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import { useDevStore } from "@/stores/devStore";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
|
||||
function GlassCard({ children }: { children: React.ReactNode }) {
|
||||
return <div className="glass-card px-5 py-4">{children}</div>;
|
||||
}
|
||||
|
||||
function SettingRow({ label, desc, children }: { label: string; desc: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
<p className="text-[13px] text-muted-foreground mt-0.5">{desc}</p>
|
||||
</div>
|
||||
<div className="shrink-0">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const openSplash = async () => {
|
||||
try {
|
||||
const existing = await WebviewWindow.getByLabel("splashscreen");
|
||||
if (existing) {
|
||||
await existing.show();
|
||||
await existing.setFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
const splash = new WebviewWindow("splashscreen", {
|
||||
url: "/splash.html",
|
||||
width: 480,
|
||||
height: 320,
|
||||
decorations: false,
|
||||
transparent: true,
|
||||
center: true,
|
||||
visible: true,
|
||||
resizable: false,
|
||||
minWidth: 480,
|
||||
maxWidth: 480,
|
||||
minHeight: 320,
|
||||
maxHeight: 320,
|
||||
} as any);
|
||||
|
||||
splash.once("tauri://error", (e) => {
|
||||
console.error("Splash window error:", e);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to open splash:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const closeSplash = async () => {
|
||||
try {
|
||||
const splash = await WebviewWindow.getByLabel("splashscreen");
|
||||
if (splash) {
|
||||
await splash.close();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to close splash:", err);
|
||||
}
|
||||
};
|
||||
|
||||
export function DeveloperSetting() {
|
||||
const { forceDisableContentBlur, setForceDisableContentBlur } = useDevStore();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-6">开发者选项</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== 启动画面 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">启动画面</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<SettingRow
|
||||
label="打开启动画面"
|
||||
desc="立即创建并显示 Splash Screen 窗口(480×320)"
|
||||
>
|
||||
<Button size="sm" onClick={openSplash}>
|
||||
打开
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
<GlassCard>
|
||||
<SettingRow
|
||||
label="关闭启动画面"
|
||||
desc="立即关闭当前显示的 Splash Screen 窗口"
|
||||
>
|
||||
<Button variant="destructive" size="sm" onClick={closeSplash}>
|
||||
关闭
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 显示效果测试 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">显示效果测试</h3>
|
||||
<div className="space-y-3">
|
||||
<GlassCard>
|
||||
<SettingRow
|
||||
label="强制关闭背景「强内容模式」"
|
||||
desc="覆盖系统设置,在设置页面也禁用背景模糊遮罩,用于对比测试"
|
||||
>
|
||||
<Button
|
||||
variant={forceDisableContentBlur ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setForceDisableContentBlur(!forceDisableContentBlur)}
|
||||
>
|
||||
{forceDisableContentBlur ? "已开启" : "已关闭"}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
export function FeedbackSetting() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">服务与反馈</h2>
|
||||
<p className="text-sm text-muted-foreground">提交反馈与问题报告</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">服务与反馈</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">提交问题反馈、功能建议与联系开发团队</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export function SponsorSetting() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">赞助我们</h2>
|
||||
<p className="text-sm text-muted-foreground">支持 Koring Launcher 的开发</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">赞助我们</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">如果 Koring Launcher 对你有帮助,欢迎赞助支持开发</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ export function A11ySetting() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-6">辅助功能</h2>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">辅助功能</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">调整动画、透明度与对比度以改善使用体验</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== 显示 ===== */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">显示</h3>
|
||||
<div className="space-y-3">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export function LangSetting() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">语言</h2>
|
||||
<p className="text-sm text-muted-foreground">选择界面显示语言</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">语言</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">选择启动器界面的显示语言与地区偏好</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useThemeStore, type DarkMode } from "@/stores/themeStore";
|
||||
import { useBackgroundStore } from "@/stores/backgroundStore";
|
||||
import { useA11yStore } from "@/stores/a11yStore";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -107,6 +108,7 @@ function ThemePreviewCard({ mode, selected, onClick }: { mode: DarkMode; selecte
|
||||
export function ThemeBgSetting() {
|
||||
const { darkMode, setDarkMode, parallax, setParallax } = useThemeStore();
|
||||
const { opacity, setOpacity, blur, setBlur, reset } = useBackgroundStore();
|
||||
const { contentBlurOpacity, setContentBlurOpacity } = useA11yStore();
|
||||
|
||||
const handlePickImage = async () => {
|
||||
// TODO: 打开文件选择器
|
||||
@@ -118,7 +120,8 @@ export function ThemeBgSetting() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-6">主题与背景</h2>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">主题与背景</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">切换深色模式、更换背景图片与调整视觉效果</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* ===== 深色模式 ===== */}
|
||||
@@ -180,6 +183,23 @@ export function ThemeBgSetting() {
|
||||
</SettingRow>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">强内容遮罩不透明度</p>
|
||||
<p className="text-[13px] text-muted-foreground mt-0.5">设置页面背景模糊遮罩的不透明度,当前 {contentBlurOpacity}%</p>
|
||||
</div>
|
||||
<Slider
|
||||
className="w-[180px] shrink-0"
|
||||
value={[contentBlurOpacity]}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
onValueChange={(v) => setContentBlurOpacity(Array.isArray(v) ? v[0] : v)}
|
||||
/>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard>
|
||||
<SettingRow label="恢复默认" desc="重置所有背景设置为初始状态">
|
||||
<Button variant="destructive" size="sm" onClick={handleReset}>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export function UiSetting() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-foreground mb-4">主界面</h2>
|
||||
<p className="text-sm text-muted-foreground">自定义主界面布局与显示</p>
|
||||
<h2 className="text-xl font-bold text-foreground mb-1">主界面</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">自定义启动器主界面的布局、模块显示与交互方式</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { UnderConstruction } from "@/components/UnderConstruction";
|
||||
|
||||
export function Store() {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4 text-foreground">资源</h1>
|
||||
<p className="text-muted-foreground">Mod / 整合包 / 材质包</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <UnderConstruction pageName="资源" description="Mod / 整合包 / 材质包,敬请期待" />;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { UnderConstruction } from "@/components/UnderConstruction";
|
||||
|
||||
export function Today() {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4 text-foreground">资讯</h1>
|
||||
<p className="text-muted-foreground">最新资讯与动态</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <UnderConstruction pageName="资讯" description="Minecraft 新闻与社区动态" />;
|
||||
}
|
||||
|
||||
@@ -4,17 +4,21 @@ interface A11yState {
|
||||
reduceMotion: boolean;
|
||||
reduceTransparency: boolean;
|
||||
highContrast: boolean;
|
||||
contentBlurOpacity: number;
|
||||
setReduceMotion: (v: boolean) => void;
|
||||
setReduceTransparency: (v: boolean) => void;
|
||||
setHighContrast: (v: boolean) => void;
|
||||
setContentBlurOpacity: (v: number) => void;
|
||||
}
|
||||
|
||||
export const useA11yStore = create<A11yState>((set) => ({
|
||||
reduceMotion: false,
|
||||
reduceTransparency: false,
|
||||
highContrast: false,
|
||||
contentBlurOpacity: 50,
|
||||
|
||||
setReduceMotion: (v) => set({ reduceMotion: v }),
|
||||
setReduceTransparency: (v) => set({ reduceTransparency: v }),
|
||||
setHighContrast: (v) => set({ highContrast: v }),
|
||||
setContentBlurOpacity: (v) => set({ contentBlurOpacity: v }),
|
||||
}));
|
||||
|
||||
+26
-1
@@ -1,12 +1,37 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
export type PreviewUpdateState = "latest" | "hasUpdate" | "installed";
|
||||
|
||||
interface DevState {
|
||||
forceDisableContentBlur: boolean;
|
||||
setForceDisableContentBlur: (v: boolean) => void;
|
||||
|
||||
previewMode: string | null;
|
||||
setPreviewMode: (v: string | null) => void;
|
||||
|
||||
previewUpdateState: PreviewUpdateState | null;
|
||||
setPreviewUpdateState: (v: PreviewUpdateState | null) => void;
|
||||
|
||||
overlayOpacity: number;
|
||||
setOverlayOpacity: (v: number) => void;
|
||||
|
||||
blurAmount: number;
|
||||
setBlurAmount: (v: number) => void;
|
||||
}
|
||||
|
||||
export const useDevStore = create<DevState>((set) => ({
|
||||
forceDisableContentBlur: false,
|
||||
|
||||
setForceDisableContentBlur: (v) => set({ forceDisableContentBlur: v }),
|
||||
|
||||
previewMode: null,
|
||||
setPreviewMode: (v) => set({ previewMode: v }),
|
||||
|
||||
previewUpdateState: null,
|
||||
setPreviewUpdateState: (v) => set({ previewUpdateState: v }),
|
||||
|
||||
overlayOpacity: 30,
|
||||
setOverlayOpacity: (v) => set({ overlayOpacity: v }),
|
||||
|
||||
blurAmount: 12,
|
||||
setBlurAmount: (v) => set({ blurAmount: v }),
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
export type RouteKey = "home" | "store" | "today" | "play-link" | "setting" | "debug";
|
||||
export type RouteKey =
|
||||
| "home"
|
||||
| "store"
|
||||
| "today"
|
||||
| "play-link"
|
||||
| "setting"
|
||||
| "debug"
|
||||
| "debug-splash"
|
||||
| "debug-display"
|
||||
| "debug-version-card";
|
||||
|
||||
export type TitleBarMode = "default" | "sub" | "window";
|
||||
|
||||
@@ -24,12 +33,20 @@ export const routes: RouteItem[] = [
|
||||
export const allRoutes: RouteItem[] = [
|
||||
...routes,
|
||||
{ key: "debug", label: "调试", path: "/debug", hidden: true },
|
||||
{ key: "debug-splash", label: "启动动画调试", path: "/debug/splash", hidden: true },
|
||||
{ key: "debug-display", label: "显示效果调试", path: "/debug/display", hidden: true },
|
||||
{ key: "debug-version-card", label: "版本卡片调试", path: "/debug/version-card", hidden: true },
|
||||
];
|
||||
|
||||
const parentMap: Partial<Record<RouteKey, RouteKey>> = {
|
||||
debug: "setting",
|
||||
"debug-splash": "debug",
|
||||
"debug-display": "debug",
|
||||
"debug-version-card": "debug",
|
||||
};
|
||||
|
||||
const topLevelKeys = new Set(routes.map((r) => r.key));
|
||||
|
||||
function startTransition(dir: TransitionDirection, callback: () => void) {
|
||||
document.documentElement.dataset.transitionDir = dir;
|
||||
if (document.startViewTransition) {
|
||||
@@ -54,11 +71,10 @@ export const useRouteStore = create<RouteState>((set, get) => ({
|
||||
direction: "forward",
|
||||
navigate: (key) => {
|
||||
const parent = parentMap[key];
|
||||
const isForward = parent !== undefined;
|
||||
startTransition("forward", () => {
|
||||
set({
|
||||
current: key,
|
||||
titleBarMode: isForward ? "sub" : "default",
|
||||
titleBarMode: parent && !topLevelKeys.has(key) ? "sub" : "default",
|
||||
direction: "forward",
|
||||
});
|
||||
});
|
||||
@@ -71,7 +87,7 @@ export const useRouteStore = create<RouteState>((set, get) => ({
|
||||
startTransition("backward", () => {
|
||||
set({
|
||||
current: parent,
|
||||
titleBarMode: "default",
|
||||
titleBarMode: topLevelKeys.has(parent) ? "default" : "sub",
|
||||
direction: "backward",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { create } from "zustand";
|
||||
import {
|
||||
checkForUpdates,
|
||||
downloadAndInstall,
|
||||
type DownloadProgress,
|
||||
} from "../api/update";
|
||||
import type { Update } from "@tauri-apps/plugin-updater";
|
||||
|
||||
interface UpdateState {
|
||||
checking: boolean;
|
||||
downloading: boolean;
|
||||
installed: boolean;
|
||||
progress: DownloadProgress | null;
|
||||
update: Update | null;
|
||||
error: string | null;
|
||||
check: () => Promise<void>;
|
||||
install: () => Promise<void>;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export const useUpdateStore = create<UpdateState>((set, get) => ({
|
||||
checking: false,
|
||||
downloading: false,
|
||||
installed: false,
|
||||
progress: null,
|
||||
update: null,
|
||||
error: null,
|
||||
|
||||
check: async () => {
|
||||
set({ checking: true, error: null });
|
||||
try {
|
||||
const update = await checkForUpdates();
|
||||
set({ update, checking: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message ?? String(e), checking: false });
|
||||
}
|
||||
},
|
||||
|
||||
install: async () => {
|
||||
const { update } = get();
|
||||
if (!update) return;
|
||||
|
||||
set({ downloading: true, error: null, progress: null });
|
||||
try {
|
||||
await downloadAndInstall(update, (progress) => {
|
||||
set({ progress });
|
||||
});
|
||||
set({ downloading: false, installed: true });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message ?? String(e), downloading: false });
|
||||
}
|
||||
},
|
||||
|
||||
reset: () => set({ update: null, error: null, progress: null, installed: false }),
|
||||
}));
|
||||
Reference in New Issue
Block a user