Compare commits

..

5 Commits

Author SHA1 Message Date
Julian Freeman
d33b2ae2a9 reduce essentials keys 2026-03-30 20:33:07 -04:00
Julian Freeman
4f46d745f0 upgrade essentials versions 2026-03-30 20:21:44 -04:00
Julian Freeman
7afdc845fa optimize fetch logic 2026-03-30 19:34:14 -04:00
Julian Freeman
6b64f36cfb update file struct 2026-03-30 19:22:11 -04:00
Julian Freeman
626a9c21b0 fetch essentials from url 2026-03-30 17:50:34 -04:00
10 changed files with 1099 additions and 82 deletions

525
src-tauri/Cargo.lock generated
View File

@@ -444,6 +444,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.44" version = "0.4.44"
@@ -493,6 +499,16 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.10.1" version = "0.10.1"
@@ -516,9 +532,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"core-foundation", "core-foundation 0.10.1",
"core-graphics-types", "core-graphics-types",
"foreign-types", "foreign-types 0.5.0",
"libc", "libc",
] ]
@@ -529,7 +545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"core-foundation", "core-foundation 0.10.1",
"libc", "libc",
] ]
@@ -852,6 +868,15 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "endi" name = "endi"
version = "1.1.1" version = "1.1.1"
@@ -986,6 +1011,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared 0.1.1",
]
[[package]] [[package]]
name = "foreign-types" name = "foreign-types"
version = "0.5.0" version = "0.5.0"
@@ -993,7 +1027,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [ dependencies = [
"foreign-types-macros", "foreign-types-macros",
"foreign-types-shared", "foreign-types-shared 0.3.1",
] ]
[[package]] [[package]]
@@ -1007,6 +1041,12 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "foreign-types-shared" name = "foreign-types-shared"
version = "0.3.1" version = "0.3.1"
@@ -1252,8 +1292,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi 0.11.1+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
"wasm-bindgen",
] ]
[[package]] [[package]]
@@ -1263,9 +1305,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"r-efi 5.3.0", "r-efi 5.3.0",
"wasip2", "wasip2",
"wasm-bindgen",
] ]
[[package]] [[package]]
@@ -1429,6 +1473,25 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "h2"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap 2.13.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@@ -1545,6 +1608,7 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"h2",
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
@@ -1556,6 +1620,39 @@ dependencies = [
"want", "want",
] ]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
"webpki-roots",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.20" version = "0.1.20"
@@ -1574,9 +1671,11 @@ dependencies = [
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"system-configuration",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
"windows-registry",
] ]
[[package]] [[package]]
@@ -1982,6 +2081,12 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]] [[package]]
name = "mac" name = "mac"
version = "0.1.1" version = "0.1.1"
@@ -2093,6 +2198,23 @@ dependencies = [
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
[[package]]
name = "native-tls"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]] [[package]]
name = "ndk" name = "ndk"
version = "0.9.0" version = "0.9.0"
@@ -2313,6 +2435,50 @@ dependencies = [
"pathdiff", "pathdiff",
] ]
[[package]]
name = "openssl"
version = "0.10.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
dependencies = [
"bitflags 2.11.0",
"cfg-if",
"foreign-types 0.3.2",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-sys"
version = "0.9.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
@@ -2768,6 +2934,61 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"socket2",
"thiserror 2.0.18",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand 0.9.2",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.18",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.45" version = "1.0.45"
@@ -2814,6 +3035,16 @@ dependencies = [
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.5",
]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.2.2" version = "0.2.2"
@@ -2834,6 +3065,16 @@ dependencies = [
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.5.1" version = "0.5.1"
@@ -2852,6 +3093,15 @@ dependencies = [
"getrandom 0.2.17", "getrandom 0.2.17",
] ]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]] [[package]]
name = "rand_hc" name = "rand_hc"
version = "0.2.0" version = "0.2.0"
@@ -2945,6 +3195,50 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "reqwest"
version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
]
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.13.2" version = "0.13.2"
@@ -2979,6 +3273,20 @@ dependencies = [
"web-sys", "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]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "2.1.1" version = "2.1.1"
@@ -3007,12 +3315,53 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "rustls"
version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"web-time",
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
@@ -3022,6 +3371,15 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.22" version = "0.8.22"
@@ -3079,6 +3437,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 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.11.0",
"core-foundation 0.10.1",
"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]] [[package]]
name = "selectors" name = "selectors"
version = "0.24.0" version = "0.24.0"
@@ -3221,6 +3602,18 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "serde_with" name = "serde_with"
version = "3.18.0" version = "3.18.0"
@@ -3469,6 +3862,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "swift-rs" name = "swift-rs"
version = "1.0.7" version = "1.0.7"
@@ -3522,6 +3921,27 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "system-configuration"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
"bitflags 2.11.0",
"core-foundation 0.9.4",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "system-deps" name = "system-deps"
version = "6.2.2" version = "6.2.2"
@@ -3543,7 +3963,7 @@ checksum = "6e06d52c379e63da659a483a958110bbde891695a0ecb53e48cc7786d5eda7bb"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"block2", "block2",
"core-foundation", "core-foundation 0.10.1",
"core-graphics", "core-graphics",
"crossbeam-channel", "crossbeam-channel",
"dispatch2", "dispatch2",
@@ -3620,7 +4040,7 @@ dependencies = [
"percent-encoding", "percent-encoding",
"plist", "plist",
"raw-window-handle", "raw-window-handle",
"reqwest", "reqwest 0.13.2",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
@@ -3948,6 +4368,21 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.50.0" version = "1.50.0"
@@ -3976,6 +4411,26 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.18" version = "0.7.18"
@@ -4280,6 +4735,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.8" version = "2.5.8"
@@ -4329,6 +4790,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version-compare" name = "version-compare"
version = "0.2.1" version = "0.2.1"
@@ -4526,6 +4993,16 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "web_atoms" name = "web_atoms"
version = "0.2.3" version = "0.2.3"
@@ -4582,6 +5059,15 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "webpki-roots"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "webview2-com" name = "webview2-com"
version = "0.38.2" version = "0.38.2"
@@ -4624,6 +5110,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"regex", "regex",
"reqwest 0.12.28",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",
@@ -4781,6 +5268,17 @@ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.3.4" version = "0.3.4"
@@ -4826,6 +5324,15 @@ dependencies = [
"windows-targets 0.42.2", "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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.59.0"
@@ -5369,6 +5876,12 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"
version = "0.2.3" version = "0.2.3"

View File

@@ -25,4 +25,5 @@ serde_json = "1"
tokio = { version = "1.50.0", features = ["full"] } tokio = { version = "1.50.0", features = ["full"] }
chrono = "0.4.44" chrono = "0.4.44"
regex = "1.12.3" regex = "1.12.3"
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }

View File

@@ -4,14 +4,40 @@ use std::fs;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::os::windows::process::CommandExt; use std::os::windows::process::CommandExt;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tauri::{AppHandle, Manager, State, Emitter}; use tauri::{AppHandle, Manager, State, Emitter};
use serde::Serialize; use serde::{Serialize, Deserialize};
use winget::{Software, list_all_software, list_updates, ensure_winget_dependencies}; use winget::{Software, list_all_software, list_updates, ensure_winget_dependencies};
use regex::Regex; use regex::Regex;
#[derive(Clone, Serialize, Deserialize)]
pub struct AppSettings {
pub repo_url: String,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct EssentialsRepo {
pub version: String,
pub essentials: Vec<Software>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct InstallTask {
pub id: String,
pub version: Option<String>,
}
impl Default for AppSettings {
fn default() -> Self {
Self {
repo_url: "https://karlblue.github.io/winget-repo".to_string(),
}
}
}
struct AppState { struct AppState {
install_tx: mpsc::Sender<String>, install_tx: mpsc::Sender<InstallTask>,
} }
#[derive(Clone, Serialize)] #[derive(Clone, Serialize)]
@@ -34,50 +60,98 @@ pub fn emit_log(handle: &AppHandle, id: &str, command: &str, output: &str, statu
}); });
} }
#[tauri::command] fn get_settings_path(app: &AppHandle) -> PathBuf {
async fn initialize_app(app: AppHandle) -> Result<bool, String> {
tokio::task::spawn_blocking(move || {
ensure_winget_dependencies(&app).map(|_| true)
}).await.unwrap_or(Err("Initialization Task Panicked".to_string()))
}
#[tauri::command]
fn get_essentials(app: AppHandle) -> Vec<Software> {
let app_data_dir = app.path().app_data_dir().unwrap_or_default(); let app_data_dir = app.path().app_data_dir().unwrap_or_default();
if !app_data_dir.exists() { if !app_data_dir.exists() {
let _ = fs::create_dir_all(&app_data_dir); let _ = fs::create_dir_all(&app_data_dir);
} }
app_data_dir.join("settings.json")
}
let file_path = app_data_dir.join("setup-essentials.json"); fn get_essentials_path(app: &AppHandle) -> PathBuf {
if !file_path.exists() { let app_data_dir = app.path().app_data_dir().unwrap_or_default();
let default_essentials = vec![ if !app_data_dir.exists() {
Software { let _ = fs::create_dir_all(&app_data_dir);
id: "Microsoft.PowerToys".to_string(), }
name: "PowerToys".to_string(), app_data_dir.join("setup-essentials.json")
description: Some("Microsoft PowerToys 是一组实用程序,供高级用户调整和简化其 Windows 10 和 11 体验。".to_string()), }
version: None,
available_version: None, #[tauri::command]
icon_url: Some("https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/logo.png".to_string()), fn get_settings(app: AppHandle) -> AppSettings {
status: "idle".to_string(), let path = get_settings_path(&app);
progress: 0.0, if !path.exists() {
}, let default_settings = AppSettings::default();
Software { let _ = fs::write(&path, serde_json::to_string_pretty(&default_settings).unwrap());
id: "Google.Chrome".to_string(), return default_settings;
name: "Google Chrome".to_string(), }
description: Some("Google Chrome 是一款快速、安全且免费的浏览器。".to_string()), let content = fs::read_to_string(path).unwrap_or_default();
version: None, serde_json::from_str(&content).unwrap_or_default()
available_version: None, }
icon_url: Some("https://www.google.com/chrome/static/images/chrome-logo.svg".to_string()),
status: "idle".to_string(), #[tauri::command]
progress: 0.0, fn save_settings(app: AppHandle, settings: AppSettings) -> Result<(), String> {
let path = get_settings_path(&app);
let content = serde_json::to_string_pretty(&settings).map_err(|e| e.to_string())?;
fs::write(path, content).map_err(|e| e.to_string())
}
#[tauri::command]
async fn sync_essentials(app: AppHandle) -> Result<bool, String> {
let settings = get_settings(app.clone());
let url = format!("{}/setup-essentials.json", settings.repo_url.trim_end_matches('/'));
emit_log(&app, "sync-essentials", "Syncing Essentials", &format!("Downloading from {}...", url), "info");
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| e.to_string())?;
match client.get(&url).send().await {
Ok(response) => {
if response.status().is_success() {
let content = response.text().await.map_err(|e| e.to_string())?;
// 验证 JSON 格式(新格式:{ version: string, essentials: Vec<Software> }
let validation: Result<EssentialsRepo, _> = serde_json::from_str(&content);
if validation.is_ok() {
let path = get_essentials_path(&app);
fs::write(path, content).map_err(|e| e.to_string())?;
emit_log(&app, "sync-essentials", "Result", "Essentials list updated successfully.", "success");
Ok(true)
} else {
emit_log(&app, "sync-essentials", "Error", "Invalid JSON format from repository. Expected { version, essentials }.", "error");
Err("Invalid JSON format".to_string())
}
} else {
let err_msg = format!("HTTP Error: {}", response.status());
emit_log(&app, "sync-essentials", "Error", &err_msg, "error");
Err(err_msg)
} }
]; }
let _ = fs::write(&file_path, serde_json::to_string_pretty(&default_essentials).unwrap()); Err(e) => {
return default_essentials; emit_log(&app, "sync-essentials", "Skipped", &format!("Network issue: {}. Using local cache.", e), "info");
Ok(false)
}
}
}
#[tauri::command]
async fn initialize_app(app: AppHandle) -> Result<bool, String> {
let app_clone = app.clone();
tokio::task::spawn_blocking(move || {
ensure_winget_dependencies(&app_clone).map(|_| true)
}).await.unwrap_or(Err("Initialization Task Panicked".to_string()))
}
#[tauri::command]
fn get_essentials(app: AppHandle) -> Option<EssentialsRepo> {
let file_path = get_essentials_path(&app);
if !file_path.exists() {
return None;
} }
let content = fs::read_to_string(file_path).unwrap_or_else(|_| "[]".to_string()); let content = fs::read_to_string(file_path).unwrap_or_default();
serde_json::from_str(&content).unwrap_or_default() serde_json::from_str(&content).ok()
} }
#[tauri::command] #[tauri::command]
@@ -91,8 +165,8 @@ async fn get_updates(app: AppHandle) -> Vec<Software> {
} }
#[tauri::command] #[tauri::command]
async fn install_software(id: String, state: State<'_, AppState>) -> Result<(), String> { async fn install_software(id: String, version: Option<String>, state: State<'_, AppState>) -> Result<(), String> {
state.install_tx.send(id).await.map_err(|e| e.to_string()) state.install_tx.send(InstallTask { id, version }).await.map_err(|e| e.to_string())
} }
#[tauri::command] #[tauri::command]
@@ -112,14 +186,17 @@ pub fn run() {
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.setup(move |app| { .setup(move |app| {
let handle = app.handle().clone(); let handle = app.handle().clone();
let (tx, mut rx) = mpsc::channel::<String>(100); let (tx, mut rx) = mpsc::channel::<InstallTask>(100);
app.manage(AppState { install_tx: tx }); app.manage(AppState { install_tx: tx });
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let perc_re = Regex::new(r"(\d+)\s*%").unwrap(); let perc_re = Regex::new(r"(\d+)\s*%").unwrap();
let size_re = Regex::new(r"([\d\.]+)\s*[a-zA-Z]+\s*/\s*([\d\.]+)\s*[a-zA-Z]+").unwrap(); let size_re = Regex::new(r"([\d\.]+)\s*[a-zA-Z]+\s*/\s*([\d\.]+)\s*[a-zA-Z]+").unwrap();
while let Some(id) = rx.recv().await { while let Some(task) = rx.recv().await {
let id = task.id;
let version = task.version;
let log_id = format!("install-{}", id); let log_id = format!("install-{}", id);
let _ = handle.emit("install-status", InstallProgress { let _ = handle.emit("install-status", InstallProgress {
id: id.clone(), id: id.clone(),
@@ -127,17 +204,34 @@ pub fn run() {
progress: 0.0, progress: 0.0,
}); });
emit_log(&handle, &log_id, &format!("Winget Install: {}", id), "Starting...", "info"); let display_cmd = match &version {
Some(v) => format!("Winget Install: {} (v{})", id, v),
None => format!("Winget Install: {}", id),
};
emit_log(&handle, &log_id, &display_cmd, "Starting...", "info");
let id_for_cmd = id.clone(); let id_for_cmd = id.clone();
let h = handle.clone(); let h = handle.clone();
let mut args = vec![
"install".to_string(),
"--id".to_string(), id_for_cmd.clone(),
"-e".to_string(),
"--silent".to_string(),
"--accept-package-agreements".to_string(),
"--accept-source-agreements".to_string(),
"--disable-interactivity".to_string(),
];
if let Some(v) = version {
if !v.is_empty() {
args.push("--version".to_string());
args.push(v);
}
}
let child = Command::new("winget") let child = Command::new("winget")
.args([ .args(&args)
"install", "--id", &id_for_cmd, "-e", "--silent",
"--accept-package-agreements", "--accept-source-agreements",
"--disable-interactivity"
])
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.creation_flags(0x08000000) .creation_flags(0x08000000)
@@ -210,6 +304,9 @@ pub fn run() {
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
initialize_app, initialize_app,
get_settings,
save_settings,
sync_essentials,
get_essentials, get_essentials,
get_all_software, get_all_software,
get_updates, get_updates,

View File

@@ -12,10 +12,15 @@ pub struct Software {
pub version: Option<String>, pub version: Option<String>,
pub available_version: Option<String>, pub available_version: Option<String>,
pub icon_url: Option<String>, pub icon_url: Option<String>,
#[serde(default = "default_status")]
pub status: String, // "idle", "pending", "installing", "success", "error" pub status: String, // "idle", "pending", "installing", "success", "error"
#[serde(default = "default_progress")]
pub progress: f32, pub progress: f32,
} }
fn default_status() -> String { "idle".to_string() }
fn default_progress() -> f32 { 0.0 }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
struct WingetPackage { struct WingetPackage {

View File

@@ -34,19 +34,30 @@
全部软件 全部软件
</router-link> </router-link>
<!-- 底部日志选项 --> <!-- 底部选项 -->
<router-link to="/logs" class="nav-item nav-logs"> <div class="sidebar-footer">
<span class="nav-icon"> <router-link to="/logs" class="nav-item nav-logs">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <span class="nav-icon">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="14 2 14 8 20 8"></polyline> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<line x1="16" y1="13" x2="8" y2="13"></line> <polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="17" x2="8" y2="17"></line> <line x1="16" y1="13" x2="8" y2="13"></line>
<polyline points="10 9 9 9 8 9"></polyline> <line x1="16" y1="17" x2="8" y2="17"></line>
</svg> <polyline points="10 9 9 9 8 9"></polyline>
</span> </svg>
运行日志 </span>
</router-link> 运行日志
</router-link>
<router-link to="/settings" class="nav-item nav-settings">
<span class="nav-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1Z"></path>
</svg>
</span>
软件设置
</router-link>
</div>
</nav> </nav>
</div> </div>
</template> </template>
@@ -106,8 +117,15 @@
justify-content: center; justify-content: center;
} }
.nav-logs { .sidebar-footer {
margin-top: auto; /* 将日志推到底部 */ margin-top: auto;
margin-bottom: 20px; padding-bottom: 20px;
display: flex;
flex-direction: column;
gap: 8px;
}
.nav-logs, .nav-settings {
margin-top: 0;
} }
</style> </style>

View File

@@ -35,8 +35,16 @@
</div> </div>
<p class="description" v-if="software.description">{{ software.description }}</p> <p class="description" v-if="software.description">{{ software.description }}</p>
<div class="version-info"> <div class="version-info">
<span class="version-tag">当前: {{ software.version || '--' }}</span> <template v-if="software.status === 'installed'">
<span class="version-tag available" v-if="software.available_version"> <span class="version-tag">当前: {{ software.version || '--' }}</span>
</template>
<template v-else>
<span class="version-tag recommended">
推荐: {{ software.version || '最新版' }}
</span>
</template>
<span class="version-tag available" v-if="software.status !== 'installed' && software.available_version">
最新: {{ software.available_version }} 最新: {{ software.available_version }}
</span> </span>
</div> </div>
@@ -276,7 +284,7 @@ const handleCardClick = () => {
font-weight: 500; font-weight: 500;
} }
.version-tag.available { .version-tag.available, .version-tag.recommended {
color: var(--primary-color); color: var(--primary-color);
background: rgba(0, 122, 255, 0.08); background: rgba(0, 122, 255, 0.08);
padding: 0 6px; padding: 0 6px;

View File

@@ -22,6 +22,10 @@ const router = createRouter({
{ {
path: '/logs', path: '/logs',
component: () => import('../views/Logs.vue') component: () => import('../views/Logs.vue')
},
{
path: '/settings',
component: () => import('../views/Settings.vue')
} }
] ]
}) })

View File

@@ -15,26 +15,34 @@ export interface LogEntry {
export const useSoftwareStore = defineStore('software', { export const useSoftwareStore = defineStore('software', {
state: () => ({ state: () => ({
essentials: [] as any[], essentials: [] as any[],
essentialsVersion: '',
updates: [] as any[], updates: [] as any[],
allSoftware: [] as any[], allSoftware: [] as any[],
selectedEssentialIds: [] as string[], selectedEssentialIds: [] as string[],
selectedUpdateIds: [] as string[], selectedUpdateIds: [] as string[],
logs: [] as LogEntry[], logs: [] as LogEntry[],
settings: {
repo_url: 'https://karlblue.github.io/winget-repo'
},
loading: false, loading: false,
isInitialized: false, isInitialized: false,
initStatus: '正在检查系统环境...', initStatus: '正在检查系统环境...',
lastFetched: 0 lastFetched: 0
}), }),
getters: { getters: {
// ... (mergedEssentials, sortedUpdates, sortedAllSoftware, isBusy getters stay the same)
mergedEssentials: (state) => { mergedEssentials: (state) => {
return state.essentials.map(item => { return state.essentials.map(item => {
const isInstalled = state.allSoftware.some(s => s.id.toLowerCase() === item.id.toLowerCase()); const installedInfo = state.allSoftware.find(s => s.id.toLowerCase() === item.id.toLowerCase());
const isInstalled = !!installedInfo;
const hasUpdate = state.updates.some(s => s.id.toLowerCase() === item.id.toLowerCase()); const hasUpdate = state.updates.some(s => s.id.toLowerCase() === item.id.toLowerCase());
let displayStatus = item.status; let displayStatus = item.status;
let actionLabel = '安装'; let actionLabel = '安装';
let currentVersion = item.version; // 默认使用清单中的推荐版本
if (isInstalled) { if (isInstalled) {
currentVersion = installedInfo.version; // 如果已安装,显示本地真实版本
if (hasUpdate) { if (hasUpdate) {
actionLabel = '更新'; actionLabel = '更新';
} else if (displayStatus === 'idle') { } else if (displayStatus === 'idle') {
@@ -42,7 +50,14 @@ export const useSoftwareStore = defineStore('software', {
actionLabel = '已安装'; actionLabel = '已安装';
} }
} }
return { ...item, status: displayStatus, actionLabel };
return {
...item,
version: currentVersion,
recommended_version: item.version, // 额外保存一个原始推荐版本字段供前端判断
status: displayStatus,
actionLabel
};
}); });
}, },
sortedUpdates: (state) => [...state.updates].sort(sortByName), sortedUpdates: (state) => [...state.updates].sort(sortByName),
@@ -55,8 +70,10 @@ export const useSoftwareStore = defineStore('software', {
actions: { actions: {
async initializeApp() { async initializeApp() {
if (this.isInitialized) return; if (this.isInitialized) return;
this.initStatus = '正在同步 Winget 模块...'; this.initStatus = '正在加载应用配置...';
try { try {
this.settings = await invoke('get_settings');
this.initStatus = '正在同步 Winget 模块...';
await invoke('initialize_app'); await invoke('initialize_app');
this.isInitialized = true; this.isInitialized = true;
} catch (err) { } catch (err) {
@@ -64,6 +81,23 @@ export const useSoftwareStore = defineStore('software', {
setTimeout(() => { this.isInitialized = true; }, 2000); setTimeout(() => { this.isInitialized = true; }, 2000);
} }
}, },
async saveSettings(newSettings: any) {
await invoke('save_settings', { settings: newSettings });
this.settings = newSettings;
},
async syncEssentials() {
this.loading = true;
try {
await invoke('sync_essentials');
await this.fetchEssentials();
} finally {
this.loading = false;
}
},
// ... (Selection methods stay the same)
toggleSelection(id: string, type: 'essential' | 'update') { toggleSelection(id: string, type: 'essential' | 'update') {
if (this.isBusy) return; if (this.isBusy) return;
const list = type === 'essential' ? this.selectedEssentialIds : this.selectedUpdateIds; const list = type === 'essential' ? this.selectedEssentialIds : this.selectedUpdateIds;
@@ -94,7 +128,25 @@ export const useSoftwareStore = defineStore('software', {
}, },
async fetchEssentials() { async fetchEssentials() {
this.essentials = await invoke('get_essentials') let repo = await invoke('get_essentials') as any;
// 如果本地没有文件,则尝试联网获取一次
if (!repo) {
try {
await invoke('sync_essentials');
repo = await invoke('get_essentials') as any;
} catch (err) {
console.error('Initial sync failed:', err);
}
}
if (repo) {
this.essentials = repo.essentials;
this.essentialsVersion = repo.version;
} else {
this.essentials = [];
this.essentialsVersion = '';
}
}, },
async fetchUpdates() { async fetchUpdates() {
if (this.isBusy) return; if (this.isBusy) return;
@@ -130,12 +182,15 @@ export const useSoftwareStore = defineStore('software', {
async fetchAllData() { async fetchAllData() {
this.loading = true; this.loading = true;
try { try {
const [essentials, all, updates] = await Promise.all([ // 先确保加载了必备清单(内部处理本地缺失逻辑)
invoke('get_essentials'), await this.fetchEssentials();
// 然后同步本地软件安装/更新状态,不再强制联网下载 JSON
const [all, updates] = await Promise.all([
invoke('get_all_software'), invoke('get_all_software'),
invoke('get_updates') invoke('get_updates')
]); ]);
this.essentials = essentials as any[];
this.allSoftware = all as any[]; this.allSoftware = all as any[];
this.updates = updates as any[]; this.updates = updates as any[];
this.lastFetched = Date.now(); this.lastFetched = Date.now();
@@ -146,8 +201,10 @@ export const useSoftwareStore = defineStore('software', {
}, },
async install(id: string) { async install(id: string) {
const software = this.findSoftware(id) const software = this.findSoftware(id)
if (software) software.status = 'pending'; if (software) {
await invoke('install_software', { id }) software.status = 'pending';
await invoke('install_software', { id, version: software.version })
}
}, },
findSoftware(id: string) { findSoftware(id: string) {
return this.essentials.find(s => s.id === id) || return this.essentials.find(s => s.id === id) ||

View File

@@ -3,6 +3,7 @@
<header class="content-header"> <header class="content-header">
<div class="header-left"> <div class="header-left">
<h1>装机必备</h1> <h1>装机必备</h1>
<span v-if="store.essentialsVersion" class="version-badge">版本: {{ store.essentialsVersion }}</span>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<button <button
@@ -101,6 +102,21 @@ onMounted(() => {
margin-bottom: 24px; margin-bottom: 24px;
} }
.header-left {
display: flex;
align-items: baseline;
gap: 12px;
}
.version-badge {
font-size: 13px;
font-weight: 500;
color: var(--text-sec);
background-color: rgba(0, 0, 0, 0.05);
padding: 4px 10px;
border-radius: 20px;
}
.content-header h1 { .content-header h1 {
font-size: 32px; font-size: 32px;
font-weight: 700; font-weight: 700;

298
src/views/Settings.vue Normal file
View File

@@ -0,0 +1,298 @@
<template>
<div class="settings-view">
<header class="view-header">
<h1>软件设置</h1>
<p class="subtitle">管理仓库地址与应用配置</p>
</header>
<div class="settings-content">
<section class="settings-section">
<h3 class="section-title">仓库配置</h3>
<div class="settings-card">
<div class="setting-item">
<div class="setting-info">
<label>仓库地址</label>
<p>应用将从该地址同步装机必备软件清单</p>
</div>
<div class="setting-action">
<input
type="text"
v-model="tempRepoUrl"
placeholder="https://..."
class="settings-input"
/>
</div>
</div>
<div class="setting-footer">
<button
@click="handleSave"
class="btn-primary"
:disabled="isSaving || !isValidUrl"
>
{{ isSaving ? '正在保存...' : '保存配置' }}
</button>
<button
@click="handleSync"
class="btn-secondary"
:disabled="store.loading"
>
<span v-if="store.loading" class="spinner"></span>
{{ store.loading ? '正在同步...' : '立即同步清单' }}
</button>
</div>
</div>
</section>
<section class="settings-section">
<h3 class="section-title">关于</h3>
<div class="settings-card about-card">
<p>Windows 软件管理器 v0.1.0</p>
<p class="hint">基于 Tauri WinGet 构建的现代化软件管理工具</p>
</div>
</section>
</div>
<!-- 通知提示 -->
<transition name="toast">
<div v-if="toast" class="toast" :class="toast.type">
{{ toast.message }}
</div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useSoftwareStore } from '../store/software'
const store = useSoftwareStore()
const tempRepoUrl = ref('')
const isSaving = ref(false)
const toast = ref<{ message: string, type: 'success' | 'error' } | null>(null)
const isValidUrl = computed(() => {
try {
new URL(tempRepoUrl.value)
return true
} catch {
return false
}
})
onMounted(() => {
tempRepoUrl.value = store.settings.repo_url
})
const showToast = (message: string, type: 'success' | 'error' = 'success') => {
toast.value = { message, type }
setTimeout(() => {
toast.value = null
}, 3000)
}
const handleSave = async () => {
if (!isValidUrl.value) return
isSaving.value = true
try {
await store.saveSettings({ repo_url: tempRepoUrl.value })
showToast('配置已保存')
} catch (err) {
showToast('保存失败: ' + err, 'error')
} finally {
isSaving.value = false
}
}
const handleSync = async () => {
try {
await store.syncEssentials()
showToast('清单同步成功')
} catch (err) {
showToast('同步失败,请检查网络或地址', 'error')
}
}
</script>
<style scoped>
.settings-view {
padding: 60px;
height: 100%;
overflow-y: auto;
background-color: var(--bg-light);
}
.view-header {
margin-bottom: 40px;
}
.view-header h1 {
font-size: 34px;
font-weight: 700;
letter-spacing: -0.5px;
margin-bottom: 8px;
}
.subtitle {
color: var(--text-sec);
font-size: 17px;
}
.settings-content {
max-width: 800px;
}
.settings-section {
margin-bottom: 40px;
}
.section-title {
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
color: var(--text-sec);
margin-bottom: 12px;
padding-left: 4px;
}
.settings-card {
background: white;
border-radius: var(--radius-card);
padding: 24px;
box-shadow: var(--card-shadow);
border: 1px solid var(--border-color);
}
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.setting-info label {
display: block;
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
}
.setting-info p {
font-size: 14px;
color: var(--text-sec);
}
.settings-input {
width: 320px;
padding: 12px 16px;
border-radius: 10px;
border: 1px solid var(--border-color);
font-size: 14px;
transition: all 0.2s ease;
background-color: #f5f5f7;
}
.settings-input:focus {
outline: none;
border-color: var(--primary-color);
background-color: white;
box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.1);
}
.setting-footer {
display: flex;
gap: 12px;
border-top: 1px solid var(--border-color);
padding-top: 24px;
}
.btn-primary, .btn-secondary {
padding: 10px 20px;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
border: none;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: var(--primary-hover);
transform: translateY(-1px);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
background-color: #f5f5f7;
color: var(--text-main);
}
.btn-secondary:hover:not(:disabled) {
background-color: #e5e5e7;
}
.about-card p {
margin-bottom: 4px;
font-weight: 600;
}
.hint {
font-size: 14px;
color: var(--text-sec);
font-weight: 400 !important;
}
/* Toast */
.toast {
position: fixed;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
padding: 12px 24px;
border-radius: 12px;
background: #323232;
color: white;
font-size: 14px;
font-weight: 500;
box-shadow: 0 8px 24px rgba(0,0,0,0.2);
z-index: 1000;
}
.toast.error {
background: #ff3b30;
}
.toast-enter-active, .toast-leave-active {
transition: all 0.3s ease;
}
.toast-enter-from, .toast-leave-to {
opacity: 0;
transform: translate(-50%, 20px);
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(0,0,0,0.1);
border-top-color: var(--text-main);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>