mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 17:12:09 +08:00
fix(ui): prevent light flash on dark startup (#5637)
* fix(ui): prevent light flash on dark startup * fix(window): apply initial dark/light theme to prevent white flash * refactor(boot): optimize config fetch and theme fallback * fix: system theme detection * fix(window): remove black flash before loader by aligning initial paint with theme
This commit is contained in:
142
Cargo.lock
generated
142
Cargo.lock
generated
@@ -170,6 +170,24 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ashpd"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de3d60bee1a1d38c2077030f4788e1b4e31058d2e79a8cfc8f2b440bd44db290"
|
||||||
|
dependencies = [
|
||||||
|
"async-fs 2.2.0",
|
||||||
|
"async-net 2.0.0",
|
||||||
|
"enumflags2",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
|
"url",
|
||||||
|
"zbus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ashpd"
|
name = "ashpd"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -252,6 +270,32 @@ dependencies = [
|
|||||||
"futures-lite 1.13.0",
|
"futures-lite 1.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-fs"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5"
|
||||||
|
dependencies = [
|
||||||
|
"async-lock 3.4.1",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite 2.6.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-global-executor"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel 2.5.0",
|
||||||
|
"async-executor",
|
||||||
|
"async-io 2.6.0",
|
||||||
|
"async-lock 3.4.1",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite 2.6.1",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-io"
|
name = "async-io"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
@@ -321,6 +365,17 @@ dependencies = [
|
|||||||
"futures-lite 1.13.0",
|
"futures-lite 1.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-net"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
|
||||||
|
dependencies = [
|
||||||
|
"async-io 2.6.0",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite 2.6.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-process"
|
name = "async-process"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
@@ -385,6 +440,32 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-std"
|
||||||
|
version = "1.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel 1.9.0",
|
||||||
|
"async-global-executor",
|
||||||
|
"async-io 2.6.0",
|
||||||
|
"async-lock 3.4.1",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite 2.6.1",
|
||||||
|
"gloo-timers",
|
||||||
|
"kv-log-macro",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@@ -1124,6 +1205,7 @@ dependencies = [
|
|||||||
"compact_str",
|
"compact_str",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
"dark-light",
|
||||||
"deelevate",
|
"deelevate",
|
||||||
"delay_timer",
|
"delay_timer",
|
||||||
"dunce",
|
"dunce",
|
||||||
@@ -1705,6 +1787,20 @@ dependencies = [
|
|||||||
"cipher",
|
"cipher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dark-light"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18e1a09f280e29a8b00bc7e81eca5ac87dca0575639c9422a5fa25a07bb884b8"
|
||||||
|
dependencies = [
|
||||||
|
"ashpd 0.10.3",
|
||||||
|
"async-std",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-foundation 0.2.2",
|
||||||
|
"web-sys",
|
||||||
|
"winreg 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.21.3"
|
version = "0.21.3"
|
||||||
@@ -2977,6 +3073,18 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-timers"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gobject-sys"
|
name = "gobject-sys"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@@ -3969,6 +4077,15 @@ dependencies = [
|
|||||||
"selectors",
|
"selectors",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kv-log-macro"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -4107,6 +4224,9 @@ name = "log"
|
|||||||
version = "0.4.28"
|
version = "0.4.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||||
|
dependencies = [
|
||||||
|
"value-bag",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "loom"
|
name = "loom"
|
||||||
@@ -6227,7 +6347,7 @@ version = "0.15.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
|
checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ashpd",
|
"ashpd 0.11.0",
|
||||||
"block2 0.6.2",
|
"block2 0.6.2",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
@@ -7049,10 +7169,10 @@ checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel 1.9.0",
|
"async-channel 1.9.0",
|
||||||
"async-executor",
|
"async-executor",
|
||||||
"async-fs",
|
"async-fs 1.6.0",
|
||||||
"async-io 1.13.0",
|
"async-io 1.13.0",
|
||||||
"async-lock 2.8.0",
|
"async-lock 2.8.0",
|
||||||
"async-net",
|
"async-net 1.8.0",
|
||||||
"async-process 1.8.1",
|
"async-process 1.8.1",
|
||||||
"blocking",
|
"blocking",
|
||||||
"futures-lite 1.13.0",
|
"futures-lite 1.13.0",
|
||||||
@@ -8940,6 +9060,12 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "value-bag"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@@ -9939,6 +10065,16 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.55.0"
|
version = "0.55.0"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
- 修复解锁测试部分地区图标编码不正确
|
- 修复解锁测试部分地区图标编码不正确
|
||||||
- 修复 IP 检测切页后强制刷新,改为仅在必要时更新
|
- 修复 IP 检测切页后强制刷新,改为仅在必要时更新
|
||||||
- 修复在搜索框输入不完整正则直接崩溃
|
- 修复在搜索框输入不完整正则直接崩溃
|
||||||
- 修复创建窗口时在非简体中文环境下的短暂闪烁
|
- 修复创建窗口时在非简体中文环境或深色主题下的短暂闪烁
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ clash_verge_service_ipc = { version = "2.0.21", features = [
|
|||||||
arc-swap = "1.7.1"
|
arc-swap = "1.7.1"
|
||||||
rust-i18n = "3.1.5"
|
rust-i18n = "3.1.5"
|
||||||
rust_iso3166 = "0.1.14"
|
rust_iso3166 = "0.1.14"
|
||||||
|
dark-light = "2.0.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
deelevate = { workspace = true }
|
deelevate = { workspace = true }
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
use tauri::WebviewWindow;
|
use dark_light::{Mode as SystemTheme, detect as detect_system_theme};
|
||||||
|
use tauri::utils::config::Color;
|
||||||
|
use tauri::{Theme, WebviewWindow};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
core::handle,
|
core::handle,
|
||||||
utils::resolve::window_script::{INITIAL_LOADING_OVERLAY, WINDOW_INITIAL_SCRIPT},
|
utils::resolve::window_script::{INITIAL_LOADING_OVERLAY, build_window_initial_script},
|
||||||
};
|
};
|
||||||
use clash_verge_logging::{Type, logging_error};
|
use clash_verge_logging::{Type, logging_error};
|
||||||
|
|
||||||
|
const DARK_BACKGROUND_COLOR: Color = Color(46, 48, 61, 255); // #2E303D
|
||||||
|
const LIGHT_BACKGROUND_COLOR: Color = Color(245, 245, 245, 255); // #F5F5F5
|
||||||
|
const DARK_BACKGROUND_HEX: &str = "#2E303D";
|
||||||
|
const LIGHT_BACKGROUND_HEX: &str = "#F5F5F5";
|
||||||
|
|
||||||
// 定义默认窗口尺寸常量
|
// 定义默认窗口尺寸常量
|
||||||
const DEFAULT_WIDTH: f64 = 940.0;
|
const DEFAULT_WIDTH: f64 = 940.0;
|
||||||
const DEFAULT_HEIGHT: f64 = 700.0;
|
const DEFAULT_HEIGHT: f64 = 700.0;
|
||||||
@@ -21,8 +28,37 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
|||||||
let config = Config::verge().await;
|
let config = Config::verge().await;
|
||||||
let latest = config.latest_arc();
|
let latest = config.latest_arc();
|
||||||
let start_page = latest.start_page.as_deref().unwrap_or("/");
|
let start_page = latest.start_page.as_deref().unwrap_or("/");
|
||||||
|
let initial_theme_mode = match latest.theme_mode.as_deref() {
|
||||||
|
Some("dark") => "dark",
|
||||||
|
Some("light") => "light",
|
||||||
|
_ => "system",
|
||||||
|
};
|
||||||
|
|
||||||
match tauri::WebviewWindowBuilder::new(
|
let resolved_theme = match initial_theme_mode {
|
||||||
|
"dark" => Some(Theme::Dark),
|
||||||
|
"light" => Some(Theme::Light),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let prefers_dark_background = match resolved_theme {
|
||||||
|
Some(Theme::Dark) => true,
|
||||||
|
Some(Theme::Light) => false,
|
||||||
|
_ => !matches!(detect_system_theme().ok(), Some(SystemTheme::Light)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let background_color = if prefers_dark_background {
|
||||||
|
DARK_BACKGROUND_COLOR
|
||||||
|
} else {
|
||||||
|
LIGHT_BACKGROUND_COLOR
|
||||||
|
};
|
||||||
|
|
||||||
|
let initial_script = build_window_initial_script(
|
||||||
|
initial_theme_mode,
|
||||||
|
DARK_BACKGROUND_HEX,
|
||||||
|
LIGHT_BACKGROUND_HEX,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut builder = tauri::WebviewWindowBuilder::new(
|
||||||
app_handle,
|
app_handle,
|
||||||
"main", /* the unique window label */
|
"main", /* the unique window label */
|
||||||
tauri::WebviewUrl::App(start_page.into()),
|
tauri::WebviewUrl::App(start_page.into()),
|
||||||
@@ -34,11 +70,21 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
|||||||
.fullscreen(false)
|
.fullscreen(false)
|
||||||
.inner_size(DEFAULT_WIDTH, DEFAULT_HEIGHT)
|
.inner_size(DEFAULT_WIDTH, DEFAULT_HEIGHT)
|
||||||
.min_inner_size(MINIMAL_WIDTH, MINIMAL_HEIGHT)
|
.min_inner_size(MINIMAL_WIDTH, MINIMAL_HEIGHT)
|
||||||
.visible(true) // 立即显示窗口,避免用户等待
|
.visible(false) // 等待主题色准备好后再展示,避免启动色差
|
||||||
.initialization_script(WINDOW_INITIAL_SCRIPT)
|
.initialization_script(&initial_script);
|
||||||
.build()
|
|
||||||
{
|
if let Some(theme) = resolved_theme {
|
||||||
|
builder = builder.theme(Some(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder = builder.background_color(background_color);
|
||||||
|
|
||||||
|
match builder.build() {
|
||||||
Ok(window) => {
|
Ok(window) => {
|
||||||
|
logging_error!(
|
||||||
|
Type::Window,
|
||||||
|
window.set_background_color(Some(background_color))
|
||||||
|
);
|
||||||
logging_error!(Type::Window, window.eval(INITIAL_LOADING_OVERLAY));
|
logging_error!(Type::Window, window.eval(INITIAL_LOADING_OVERLAY));
|
||||||
Ok(window)
|
Ok(window)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,121 @@
|
|||||||
pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
pub fn build_window_initial_script(
|
||||||
|
initial_theme_mode: &str,
|
||||||
|
dark_background: &str,
|
||||||
|
light_background: &str,
|
||||||
|
) -> String {
|
||||||
|
let theme_mode = match initial_theme_mode {
|
||||||
|
"dark" => "dark",
|
||||||
|
"light" => "light",
|
||||||
|
_ => "system",
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
window.__VERGE_INITIAL_THEME_MODE = "{theme_mode}";
|
||||||
|
window.__VERGE_INITIAL_THEME_COLORS = {{
|
||||||
|
darkBg: "{dark_background}",
|
||||||
|
lightBg: "{light_background}",
|
||||||
|
}};
|
||||||
|
{script}
|
||||||
|
"#,
|
||||||
|
theme_mode = theme_mode,
|
||||||
|
dark_background = dark_background,
|
||||||
|
light_background = light_background,
|
||||||
|
script = WINDOW_INITIAL_SCRIPT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const WINDOW_INITIAL_SCRIPT: &str = r##"
|
||||||
console.log('[Tauri] 窗口初始化脚本开始执行');
|
console.log('[Tauri] 窗口初始化脚本开始执行');
|
||||||
|
|
||||||
function createLoadingOverlay() {
|
const initialColors = (() => {
|
||||||
|
try {
|
||||||
|
const colors = window.__VERGE_INITIAL_THEME_COLORS;
|
||||||
|
if (colors && typeof colors === "object") {
|
||||||
|
const { darkBg, lightBg } = colors;
|
||||||
|
if (typeof darkBg === "string" && typeof lightBg === "string") {
|
||||||
|
return { darkBg, lightBg };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[Tauri] 读取初始主题颜色失败:", error);
|
||||||
|
}
|
||||||
|
return { darkBg: "#2E303D", lightBg: "#F5F5F5" };
|
||||||
|
})();
|
||||||
|
|
||||||
if (document.getElementById('initial-loading-overlay')) {
|
const prefersDark = (() => {
|
||||||
console.log('[Tauri] 加载指示器已存在');
|
try {
|
||||||
|
return !!window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)")?.matches;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[Tauri] 读取系统主题失败:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const initialThemeMode = typeof window.__VERGE_INITIAL_THEME_MODE === "string"
|
||||||
|
? window.__VERGE_INITIAL_THEME_MODE
|
||||||
|
: "system";
|
||||||
|
|
||||||
|
let initialTheme = prefersDark ? "dark" : "light";
|
||||||
|
if (initialThemeMode === "dark") {
|
||||||
|
initialTheme = "dark";
|
||||||
|
} else if (initialThemeMode === "light") {
|
||||||
|
initialTheme = "light";
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyInitialTheme = (theme) => {
|
||||||
|
const isDark = theme === "dark";
|
||||||
|
const root = document.documentElement;
|
||||||
|
const bgColor = isDark ? initialColors.darkBg : initialColors.lightBg;
|
||||||
|
const textColor = isDark ? "#ffffff" : "#333";
|
||||||
|
if (root) {
|
||||||
|
root.dataset.theme = theme;
|
||||||
|
root.style.setProperty("--bg-color", bgColor);
|
||||||
|
root.style.setProperty("--text-color", textColor);
|
||||||
|
root.style.colorScheme = isDark ? "dark" : "light";
|
||||||
|
root.style.backgroundColor = bgColor;
|
||||||
|
root.style.color = textColor;
|
||||||
|
}
|
||||||
|
const paintBody = () => {
|
||||||
|
if (!document.body) return;
|
||||||
|
document.body.style.backgroundColor = bgColor;
|
||||||
|
document.body.style.color = textColor;
|
||||||
|
};
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", paintBody, { once: true });
|
||||||
|
} else {
|
||||||
|
paintBody();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
localStorage.setItem("verge-theme-mode-cache", theme);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[Tauri] 缓存主题模式失败:", error);
|
||||||
|
}
|
||||||
|
return isDark;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDarkTheme = applyInitialTheme(initialTheme);
|
||||||
|
|
||||||
|
const getInitialOverlayColors = () => ({
|
||||||
|
bg: isDarkTheme ? initialColors.darkBg : initialColors.lightBg,
|
||||||
|
text: isDarkTheme ? "#ffffff" : "#333",
|
||||||
|
spinnerTrack: isDarkTheme ? "#3a3a3a" : "#e3e3e3",
|
||||||
|
spinnerTop: isDarkTheme ? "#0a84ff" : "#3498db",
|
||||||
|
});
|
||||||
|
|
||||||
|
function createOrUpdateLoadingOverlay() {
|
||||||
|
const colors = getInitialOverlayColors();
|
||||||
|
const existed = document.getElementById('initial-loading-overlay');
|
||||||
|
|
||||||
|
const applyOverlayColors = (element) => {
|
||||||
|
element.style.setProperty("--bg-color", colors.bg);
|
||||||
|
element.style.setProperty("--text-color", colors.text);
|
||||||
|
element.style.setProperty("--spinner-track", colors.spinnerTrack);
|
||||||
|
element.style.setProperty("--spinner-top", colors.spinnerTop);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existed) {
|
||||||
|
console.log('[Tauri] 复用已有加载指示器');
|
||||||
|
applyOverlayColors(existed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +125,7 @@ pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
|||||||
loadingDiv.innerHTML = `
|
loadingDiv.innerHTML = `
|
||||||
<div style="
|
<div style="
|
||||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||||
background: var(--bg-color, #f5f5f5); color: var(--text-color, #333);
|
background: var(--bg-color, ${colors.bg}); color: var(--text-color, ${colors.text});
|
||||||
display: flex; flex-direction: column; align-items: center;
|
display: flex; flex-direction: column; align-items: center;
|
||||||
justify-content: center; z-index: 9999;
|
justify-content: center; z-index: 9999;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
@@ -22,8 +133,8 @@ pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
|||||||
">
|
">
|
||||||
<div style="margin-bottom: 20px;">
|
<div style="margin-bottom: 20px;">
|
||||||
<div style="
|
<div style="
|
||||||
width: 40px; height: 40px; border: 3px solid #e3e3e3;
|
width: 40px; height: 40px; border: 3px solid var(--spinner-track, ${colors.spinnerTrack});
|
||||||
border-top: 3px solid #3498db; border-radius: 50%;
|
border-top: 3px solid var(--spinner-top, ${colors.spinnerTop}); border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
"></div>
|
"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,12 +145,11 @@ pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
|||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root { --bg-color: #1a1a1a; --text-color: #ffffff; }
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
applyOverlayColors(loadingDiv);
|
||||||
|
|
||||||
if (document.body) {
|
if (document.body) {
|
||||||
document.body.appendChild(loadingDiv);
|
document.body.appendChild(loadingDiv);
|
||||||
} else {
|
} else {
|
||||||
@@ -51,16 +161,16 @@ pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createLoadingOverlay();
|
createOrUpdateLoadingOverlay();
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', createLoadingOverlay);
|
document.addEventListener('DOMContentLoaded', createOrUpdateLoadingOverlay);
|
||||||
} else {
|
} else {
|
||||||
createLoadingOverlay();
|
createOrUpdateLoadingOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Tauri] 窗口初始化脚本执行完成');
|
console.log('[Tauri] 窗口初始化脚本执行完成');
|
||||||
"#;
|
"##;
|
||||||
|
|
||||||
pub const INITIAL_LOADING_OVERLAY: &str = r"
|
pub const INITIAL_LOADING_OVERLAY: &str = r"
|
||||||
const overlay = document.getElementById('initial-loading-overlay');
|
const overlay = document.getElementById('initial-loading-overlay');
|
||||||
|
|||||||
@@ -9,8 +9,93 @@
|
|||||||
/>
|
/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Clash Verge</title>
|
<title>Clash Verge</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--initial-bg: #f5f5f5;
|
||||||
|
--initial-text: #333;
|
||||||
|
--initial-spinner-track: #e3e3e3;
|
||||||
|
--initial-spinner-top: #3498db;
|
||||||
|
--bg-color: var(--initial-bg);
|
||||||
|
--text-color: var(--initial-text);
|
||||||
|
--spinner-track: var(--initial-spinner-track);
|
||||||
|
--spinner-top: var(--initial-spinner-top);
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--initial-bg: #2e303d;
|
||||||
|
--initial-text: #ffffff;
|
||||||
|
--initial-spinner-track: #3a3a3a;
|
||||||
|
--initial-spinner-top: #0a84ff;
|
||||||
|
--bg-color: var(--initial-bg);
|
||||||
|
--text-color: var(--initial-text);
|
||||||
|
--spinner-track: var(--initial-spinner-track);
|
||||||
|
--spinner-top: var(--initial-spinner-top);
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#initial-loading-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#initial-loading-overlay[data-hidden="true"] {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.initial-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid var(--spinner-track);
|
||||||
|
border-top: 3px solid var(--spinner-top);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: initial-spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes initial-spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="initial-loading-overlay">
|
||||||
|
<div class="initial-spinner"></div>
|
||||||
|
<div style="font-size: 14px; opacity: 0.7; margin-top: 20px">
|
||||||
|
Loading Clash Verge...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="./main.tsx"></script>
|
<script type="module" src="./main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
104
src/main.tsx
104
src/main.tsx
@@ -55,9 +55,45 @@ document.addEventListener("keydown", (event) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const initializeApp = () => {
|
let cachedVergeConfig: IVergeConfig | null = null;
|
||||||
|
|
||||||
|
const detectSystemTheme = (): "light" | "dark" => {
|
||||||
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function")
|
||||||
|
return "light";
|
||||||
|
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||||
|
? "dark"
|
||||||
|
: "light";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitialThemeModeFromWindow = ():
|
||||||
|
| IVergeConfig["theme_mode"]
|
||||||
|
| undefined => {
|
||||||
|
if (typeof window === "undefined") return undefined;
|
||||||
|
const mode = (
|
||||||
|
window as typeof window & {
|
||||||
|
__VERGE_INITIAL_THEME_MODE?: unknown;
|
||||||
|
}
|
||||||
|
).__VERGE_INITIAL_THEME_MODE;
|
||||||
|
if (mode === "light" || mode === "dark" || mode === "system") {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveInitialThemeMode = (
|
||||||
|
vergeConfig?: IVergeConfig | null,
|
||||||
|
): "light" | "dark" => {
|
||||||
|
const initialMode =
|
||||||
|
vergeConfig?.theme_mode ?? getInitialThemeModeFromWindow();
|
||||||
|
if (initialMode === "dark" || initialMode === "light") {
|
||||||
|
return initialMode;
|
||||||
|
}
|
||||||
|
return detectSystemTheme();
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeApp = (initialThemeMode: "light" | "dark") => {
|
||||||
const contexts = [
|
const contexts = [
|
||||||
<ThemeModeProvider key="theme" />,
|
<ThemeModeProvider key="theme" initialState={initialThemeMode} />,
|
||||||
<LoadingCacheProvider key="loading" />,
|
<LoadingCacheProvider key="loading" />,
|
||||||
<UpdateStateProvider key="update" />,
|
<UpdateStateProvider key="update" />,
|
||||||
];
|
];
|
||||||
@@ -78,24 +114,47 @@ const initializeApp = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const determineInitialLanguage = async () => {
|
const determineInitialLanguage = async (
|
||||||
|
vergeConfig?: IVergeConfig | null,
|
||||||
|
loadVergeConfig?: () => Promise<IVergeConfig | null>,
|
||||||
|
) => {
|
||||||
const cachedLanguage = getCachedLanguage();
|
const cachedLanguage = getCachedLanguage();
|
||||||
if (cachedLanguage) {
|
if (cachedLanguage) {
|
||||||
return cachedLanguage;
|
return cachedLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resolvedConfig = vergeConfig;
|
||||||
|
|
||||||
|
if (resolvedConfig === undefined) {
|
||||||
|
if (loadVergeConfig) {
|
||||||
try {
|
try {
|
||||||
const vergeConfig = await getVergeConfig();
|
resolvedConfig = await loadVergeConfig();
|
||||||
if (vergeConfig?.language) {
|
|
||||||
const resolved = resolveLanguage(vergeConfig.language);
|
|
||||||
cacheLanguage(resolved);
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[main.tsx] Failed to read language from Verge config:",
|
"[main.tsx] Failed to read language from Verge config:",
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
|
resolvedConfig = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
resolvedConfig = await getVergeConfig();
|
||||||
|
cachedVergeConfig = resolvedConfig;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
"[main.tsx] Failed to read language from Verge config:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
resolvedConfig = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const languageFromConfig = resolvedConfig?.language;
|
||||||
|
if (languageFromConfig) {
|
||||||
|
const resolved = resolveLanguage(languageFromConfig);
|
||||||
|
cacheLanguage(resolved);
|
||||||
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
const browserLanguage = resolveLanguage(
|
const browserLanguage = resolveLanguage(
|
||||||
@@ -105,10 +164,29 @@ const determineInitialLanguage = async () => {
|
|||||||
return browserLanguage;
|
return browserLanguage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchVergeConfig = async () => {
|
||||||
|
try {
|
||||||
|
const config = await getVergeConfig();
|
||||||
|
cachedVergeConfig = config;
|
||||||
|
return config;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[main.tsx] Failed to read Verge config:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const bootstrap = async () => {
|
const bootstrap = async () => {
|
||||||
const initialLanguage = await determineInitialLanguage();
|
const vergeConfigPromise = fetchVergeConfig();
|
||||||
await initializeLanguage(initialLanguage);
|
const initialLanguage = await determineInitialLanguage(
|
||||||
initializeApp();
|
undefined,
|
||||||
|
() => vergeConfigPromise,
|
||||||
|
);
|
||||||
|
const [vergeConfig] = await Promise.all([
|
||||||
|
vergeConfigPromise,
|
||||||
|
initializeLanguage(initialLanguage),
|
||||||
|
]);
|
||||||
|
const initialThemeMode = resolveInitialThemeMode(vergeConfig);
|
||||||
|
initializeApp(initialThemeMode);
|
||||||
};
|
};
|
||||||
|
|
||||||
bootstrap().catch((error) => {
|
bootstrap().catch((error) => {
|
||||||
@@ -124,7 +202,7 @@ bootstrap().catch((error) => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
initializeApp();
|
initializeApp(resolveInitialThemeMode(cachedVergeConfig));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { LogLevel } from "tauri-plugin-mihomo-api";
|
|||||||
|
|
||||||
const [ThemeModeProvider, useThemeMode, useSetThemeMode] = createContextState<
|
const [ThemeModeProvider, useThemeMode, useSetThemeMode] = createContextState<
|
||||||
"light" | "dark"
|
"light" | "dark"
|
||||||
>("light");
|
>();
|
||||||
|
|
||||||
export type LogFilter = "all" | "debug" | "info" | "warn" | "err";
|
export type LogFilter = "all" | "debug" | "info" | "warn" | "err";
|
||||||
export type LogOrder = "asc" | "desc";
|
export type LogOrder = "asc" | "desc";
|
||||||
|
|||||||
Reference in New Issue
Block a user