Compare commits

...
4 Commits
Author SHA1 Message Date
Tunglies a00c67e8ae chore: update changelog 2026-05-20 14:23:46 +08:00
Christine. 6ebdaaa7ff fix: switch node slowly on Windows (#7052) 2026-05-20 14:21:24 +08:00
wonfen 15de400f2f fix: align backup dialogs with in-app modal behavior 2026-05-20 10:24:11 +08:00
Tunglies 6f36613103 chore: bump version to 2.5.1 2026-05-19 14:01:41 +08:00
18 changed files with 221 additions and 196 deletions
+1
View File
@@ -17,3 +17,4 @@ CLAUDE.md
.vfox.toml
.vfox/
.claude
AGENTS.md
Generated
+44 -5
View File
@@ -1150,7 +1150,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "clash-verge"
version = "2.5.0"
version = "2.5.1"
dependencies = [
"aes-gcm",
"anyhow",
@@ -1665,6 +1665,19 @@ dependencies = [
"once_cell",
]
[[package]]
name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
@@ -1693,6 +1706,15 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
@@ -3560,7 +3582,7 @@ dependencies = [
"tokio",
"tower-service",
"tracing",
"windows-registry",
"windows-registry 0.6.1",
]
[[package]]
@@ -7785,7 +7807,7 @@ dependencies = [
"thiserror 2.0.18",
"tracing",
"url",
"windows-registry",
"windows-registry 0.5.3",
"windows-result 0.3.4",
]
@@ -7899,12 +7921,18 @@ dependencies = [
[[package]]
name = "tauri-plugin-mihomo"
version = "0.1.9"
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#a185fe1a8802367d0b3d50d402cb823759d2c57e"
version = "0.1.5"
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo?branch=revert#1cc80bc0fbe1245315617f4cecd93710a152325b"
dependencies = [
"base64 0.22.1",
"bytes",
"crossbeam",
"futures-util",
"http 1.4.0",
"http-body-util",
"httparse",
"hyper 1.9.0",
"libc",
"log",
"pin-project",
"rand 0.9.4",
@@ -9803,6 +9831,17 @@ dependencies = [
"windows-strings 0.4.2",
]
[[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]]
name = "windows-result"
version = "0.3.4"
+4 -31
View File
@@ -1,35 +1,8 @@
## v2.5.0
> [!IMPORTANT]
> 关于版本的说明:Clash Verge 版本号遵循 x.y.z:x 为重大架构变更,y 为功能新增,z 为 Bug 修复。
- **Mihomo(Meta) 内核升级至 v1.19.25**
## v2.5.1
### 🐞 修复问题
- 修复系统代理关闭后在 PAC 模式下未完全关闭
- 修复 macOS 开关代理时可能的卡死
- 修复修改定时自动更新后记时未及时刷新
- 修复 Linux 关闭 TUN 不立即生效
- 修复系统代理关闭序列逻辑(防止快速退出时系统代理关闭状态没有保存)
- 修复 Linux 快捷键映射错误
- 修复 Linux Wayland 兼容性导致渲染错误
- 修复 Linux 边缘情况无法加载页面
- 备份设置功能异常
- 修复 Windows 节点交互异常
### ✨ 新增功能
- 订阅 QR code 分享
- 新增 macOS 托盘速率显示
- 快捷键操作通知操作结果
- 软件自动更新(后台下载,下次启动自动安装)
### 🚀 优化改进
- 优化 macOS 读取系统代理性能
- 优化前端 CPU 性能
- 更健壮的服务模式与边缘状况内核恢复
- 优化白名单网络下的订阅 TLS 更新兼容性
### 👙 界面样式
- 代理组实现 Sticky Scroll 效果
</details>
+37
View File
@@ -1,3 +1,40 @@
## v2.5.0
> [!IMPORTANT]
> 关于版本的说明:Clash Verge 版本号遵循 x.y.z:x 为重大架构变更,y 为功能新增,z 为 Bug 修复。
- **Mihomo(Meta) 内核升级至 v1.19.25**
### 🐞 修复问题
- 修复系统代理关闭后在 PAC 模式下未完全关闭
- 修复 macOS 开关代理时可能的卡死
- 修复修改定时自动更新后记时未及时刷新
- 修复 Linux 关闭 TUN 不立即生效
- 修复系统代理关闭序列逻辑(防止快速退出时系统代理关闭状态没有保存)
- 修复 Linux 快捷键映射错误
- 修复 Linux Wayland 兼容性导致渲染错误
- 修复 Linux 边缘情况无法加载页面
### ✨ 新增功能
- 订阅 QR code 分享
- 新增 macOS 托盘速率显示
- 快捷键操作通知操作结果
- 软件自动更新(后台下载,下次启动自动安装)
### 🚀 优化改进
- 优化 macOS 读取系统代理性能
- 优化前端 CPU 性能
- 更健壮的服务模式与边缘状况内核恢复
- 优化白名单网络下的订阅 TLS 更新兼容性
### 👙 界面样式
- 代理组实现 Sticky Scroll 效果
## v2.4.7
### 🐞 修复问题
+1 -2
View File
@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "2.5.0",
"version": "2.5.1",
"license": "GPL-3.0-only",
"scripts": {
"prepare": "husky || true",
@@ -42,7 +42,6 @@
"@juggle/resize-observer": "^3.4.0",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^9.0.0",
"@mui/lab": "9.0.0-beta.2",
"@mui/material": "^9.0.0",
"@tanstack/react-query": "^5.96.1",
"@tanstack/react-table": "^8.21.3",
-40
View File
@@ -32,9 +32,6 @@ importers:
'@mui/icons-material':
specifier: ^9.0.0
version: 9.0.0(@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
'@mui/lab':
specifier: 9.0.0-beta.2
version: 9.0.0-beta.2(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@mui/material':
specifier: ^9.0.0
version: 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -1068,27 +1065,6 @@ packages:
'@types/react':
optional: true
'@mui/lab@9.0.0-beta.2':
resolution: {integrity: sha512-UqykXQCn7HNSExyYBvrFpaaEEmUwHbEgjFDBApEkawVPcBILyYNFhpXoqkwkafiZy+WsvcxIeRF0z4tCgisTRg==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
'@emotion/styled': ^11.3.0
'@mui/material': ^9.0.0
'@mui/material-pigment-css': ^9.0.0
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/react':
optional: true
'@emotion/styled':
optional: true
'@mui/material-pigment-css':
optional: true
'@types/react':
optional: true
'@mui/material@9.0.0':
resolution: {integrity: sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw==}
engines: {node: '>=14.0.0'}
@@ -4651,22 +4627,6 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.14
'@mui/lab@9.0.0-beta.2(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
dependencies:
'@babel/runtime': 7.29.2
'@mui/material': 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@mui/system': 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
'@mui/types': 9.0.0(@types/react@19.2.14)
'@mui/utils': 9.0.0(@types/react@19.2.14)(react@19.2.5)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.5)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
'@types/react': 19.2.14
'@mui/material@9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
dependencies:
'@babel/runtime': 7.29.2
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "2.5.0"
version = "2.5.1"
description = "clash verge"
authors = ["zzzgydi", "Tunglies", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
@@ -95,7 +95,7 @@ backon = { version = "1.6.0", features = ["tokio-sleep"] }
tauri-plugin-http = "2.5.7"
console-subscriber = { version = "0.5.0", optional = true }
tauri-plugin-devtools = { version = "2.0.1" }
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo" }
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", branch = "revert" }
clash_verge_logger = { git = "https://github.com/clash-verge-rev/clash-verge-logger" }
async-trait = "0.1.89"
clash_verge_service_ipc = { version = "2.3.0", features = [
+6 -8
View File
@@ -6,7 +6,7 @@ use parking_lot::Mutex;
use std::sync::Arc;
use std::time::Duration;
use tauri::async_runtime::JoinHandle;
use tauri_plugin_mihomo::models::WebSocketConnectionId;
use tauri_plugin_mihomo::models::ConnectionId;
/// 托盘速率流异常后的重连间隔。
const TRAY_SPEED_RETRY_DELAY: Duration = Duration::from_secs(1);
@@ -19,7 +19,7 @@ const TRAY_SPEED_STALE_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Clone)]
pub struct TraySpeedController {
speed_task: Arc<Mutex<Option<JoinHandle<()>>>>,
speed_connection_id: Arc<Mutex<Option<WebSocketConnectionId>>>,
speed_connection_id: Arc<Mutex<Option<ConnectionId>>>,
}
impl Default for TraySpeedController {
@@ -162,19 +162,17 @@ impl TraySpeedController {
}
fn set_speed_connection_id(
speed_connection_id: &Arc<Mutex<Option<WebSocketConnectionId>>>,
connection_id: Option<WebSocketConnectionId>,
speed_connection_id: &Arc<Mutex<Option<ConnectionId>>>,
connection_id: Option<ConnectionId>,
) {
*speed_connection_id.lock() = connection_id;
}
fn take_speed_connection_id(
speed_connection_id: &Arc<Mutex<Option<WebSocketConnectionId>>>,
) -> Option<WebSocketConnectionId> {
fn take_speed_connection_id(speed_connection_id: &Arc<Mutex<Option<ConnectionId>>>) -> Option<ConnectionId> {
speed_connection_id.lock().take()
}
async fn disconnect_speed_connection(speed_connection_id: &Arc<Mutex<Option<WebSocketConnectionId>>>) {
async fn disconnect_speed_connection(speed_connection_id: &Arc<Mutex<Option<ConnectionId>>>) {
if let Some(connection_id) = Self::take_speed_connection_id(speed_connection_id) {
connections_stream::disconnect_connection(connection_id).await;
}
+10
View File
@@ -24,6 +24,7 @@ use tauri::{AppHandle, Manager as _};
#[cfg(target_os = "macos")]
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt as _;
use tauri_plugin_mihomo::RejectPolicy;
pub static APP_HANDLE: OnceCell<AppHandle> = OnceCell::new();
/// Application initialization helper functions
@@ -58,6 +59,15 @@ mod app_init {
tauri_plugin_mihomo::Builder::new()
.protocol(tauri_plugin_mihomo::models::Protocol::LocalSocket)
.socket_path(crate::config::IClashTemp::guard_external_controller_ipc())
.pool_config(
tauri_plugin_mihomo::IpcPoolConfigBuilder::new()
.min_connections(3)
.max_connections(32)
.idle_timeout(std::time::Duration::from_secs(60))
.health_check_interval(std::time::Duration::from_secs(60))
.reject_policy(RejectPolicy::Wait)
.build(),
)
.build(),
);
+3 -3
View File
@@ -3,7 +3,7 @@ use anyhow::Result;
use serde::Deserialize;
use serde_json::Value;
use std::time::Duration;
use tauri_plugin_mihomo::models::{WebSocketConnectionId, WebSocketMessage};
use tauri_plugin_mihomo::models::{ConnectionId, WebSocketMessage};
use tokio::sync::mpsc;
use tokio::time::Instant;
@@ -39,7 +39,7 @@ enum InternalWsEvent<T> {
/// Mihomo WebSocket 订阅句柄(通用事件流)。
pub struct MihomoWsEventStream<T> {
/// 当前订阅连接 ID,用于主动断开。
pub connection_id: WebSocketConnectionId,
pub connection_id: ConnectionId,
/// 当前订阅消息接收器。
receiver: mpsc::Receiver<InternalWsEvent<T>>,
/// 最近一次收到有效事件的时间戳。
@@ -162,7 +162,7 @@ impl<T> MihomoWsEventStream<T> {
///
/// # Arguments
/// * `connection_id` - 目标连接 ID
pub async fn disconnect_connection(connection_id: WebSocketConnectionId) {
pub async fn disconnect_connection(connection_id: ConnectionId) {
if let Err(err) = handle::Handle::mihomo()
.await
.disconnect(connection_id, Some(MIHOMO_WS_STREAM_CLOSE_CODE))
+1 -1
View File
@@ -1,5 +1,5 @@
{
"version": "2.5.0",
"version": "2.5.1",
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"bundle": {
"active": true,
+2 -3
View File
@@ -1,4 +1,3 @@
import { LoadingButton } from '@mui/lab'
import {
Button,
Dialog,
@@ -66,9 +65,9 @@ export const BaseDialog: React.FC<Props> = ({
</Button>
)}
{!disableOk && (
<LoadingButton loading={loading} variant="contained" onClick={onOk}>
<Button loading={loading} variant="contained" onClick={onOk}>
{okBtn}
</LoadingButton>
</Button>
)}
</DialogActions>
)}
-46
View File
@@ -1,46 +0,0 @@
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
} from '@mui/material'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
interface Props {
open: boolean
title: string
message: string
onClose: () => void
onConfirm: () => void
}
export const ConfirmViewer = (props: Props) => {
const { open, title, message, onClose, onConfirm } = props
const { t } = useTranslation()
useEffect(() => {
if (!open) return
}, [open])
return (
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
<DialogTitle>{title}</DialogTitle>
<DialogContent sx={{ pb: 1, userSelect: 'text' }}>
{message}
</DialogContent>
<DialogActions>
<Button onClick={onClose} variant="outlined">
{t('shared.actions.cancel')}
</Button>
<Button onClick={onConfirm} variant="contained">
{t('shared.actions.confirm')}
</Button>
</DialogActions>
</Dialog>
)
}
+12 -5
View File
@@ -22,7 +22,7 @@ import dayjs from 'dayjs'
import { useCallback, useEffect, useReducer, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ConfirmViewer } from '@/components/profile/confirm-viewer'
import { BaseDialog } from '@/components/base'
import { EditorViewer } from '@/components/profile/editor-viewer'
import { GroupsEditorViewer } from '@/components/profile/groups-editor-viewer'
import { RulesEditorViewer } from '@/components/profile/rules-editor-viewer'
@@ -990,16 +990,23 @@ export const ProfileItem = (props: Props) => {
/>
)}
<ConfirmViewer
<BaseDialog
title={t('profiles.modals.confirmDelete.title')}
message={t('profiles.modals.confirmDelete.message')}
open={confirmOpen}
okBtn={t('shared.actions.confirm')}
cancelBtn={t('shared.actions.cancel')}
contentSx={{ width: { xs: 320, sm: 420 }, userSelect: 'text' }}
onCancel={() => setConfirmOpen(false)}
onClose={() => setConfirmOpen(false)}
onConfirm={() => {
onOk={() => {
onDelete()
setConfirmOpen(false)
}}
/>
>
<Typography variant="body2" sx={{ wordBreak: 'break-word' }}>
{t('profiles.modals.confirmDelete.message')}
</Typography>
</BaseDialog>
{qrOpen && itemData.url && (
<QrViewer
open={true}
@@ -49,6 +49,11 @@ const DATE_FORMAT = 'YYYY-MM-DD_HH-mm-ss'
const FILENAME_PATTERN = /\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/
type BackupSource = 'local' | 'webdav'
type PendingConfirmation = {
action: 'delete' | 'restore'
filename: string
source: BackupSource
} | null
interface BackupHistoryViewerProps {
open: boolean
@@ -67,11 +72,6 @@ interface BackupRow {
sort_value: number
}
const confirmAsync = async (message: string) => {
const fn = window.confirm as (msg?: string) => boolean
return fn(message)
}
export const BackupHistoryViewer = ({
open,
source,
@@ -86,6 +86,9 @@ export const BackupHistoryViewer = ({
const [loading, setLoading] = useState(false)
const [isRestoring, setIsRestoring] = useState(false)
const [isRestarting, setIsRestarting] = useState(false)
const [isConfirming, setIsConfirming] = useState(false)
const [pendingConfirmation, setPendingConfirmation] =
useState<PendingConfirmation>(null)
const isLocal = source === 'local'
const isWebDavConfigured = Boolean(
verge?.webdav_url && verge?.webdav_username && verge?.webdav_password,
@@ -94,7 +97,7 @@ export const BackupHistoryViewer = ({
const webdavStatus = getWebdavStatus(webdavSignature)
const shouldSkipWebDav = !isLocal && !isWebDavConfigured
const pageSize = 8
const isBusy = loading || isRestoring || isRestarting
const isBusy = loading || isRestoring || isRestarting || isConfirming
const buildRow = useCallback(
(item: ILocalBackupFile | IWebDavFile): BackupRow | null => {
@@ -204,45 +207,54 @@ export const BackupHistoryViewer = ({
})
}, [isLocal, rows, shouldSkipWebDav, t, total, webdavStatus])
const handleDelete = useLockFn(async (filename: string) => {
const handleDelete = (filename: string) => {
if (isRestarting) return
if (
!(await confirmAsync(t('settings.modals.backup.messages.confirmDelete')))
)
return
if (isLocal) {
await deleteLocalBackup(filename)
} else {
await deleteWebdavBackup(filename)
}
await fetchRows()
})
setPendingConfirmation({ action: 'delete', filename, source })
}
const handleRestore = useLockFn(async (filename: string) => {
const handleRestore = (filename: string) => {
if (isRestoring || isRestarting) return
if (
!(await confirmAsync(t('settings.modals.backup.messages.confirmRestore')))
)
return
setIsRestoring(true)
setPendingConfirmation({ action: 'restore', filename, source })
}
const handleConfirmAction = useLockFn(async () => {
if (!pendingConfirmation) return
const { action, filename, source: actionSource } = pendingConfirmation
const actionIsLocal = actionSource === 'local'
setIsConfirming(true)
if (action === 'restore') {
setIsRestoring(true)
}
try {
if (isLocal) {
await restoreLocalBackup(filename)
if (action === 'delete') {
if (actionIsLocal) {
await deleteLocalBackup(filename)
} else {
await deleteWebdavBackup(filename)
}
setPendingConfirmation(null)
await fetchRows()
} else {
await restoreWebDavBackup(filename)
if (actionIsLocal) {
await restoreLocalBackup(filename)
} else {
await restoreWebDavBackup(filename)
}
setPendingConfirmation(null)
showNotice.success('settings.modals.backup.messages.restoreSuccess')
setIsRestarting(true)
window.setTimeout(() => {
void restartApp().catch((err: unknown) => {
setIsRestarting(false)
showNotice.error(err)
})
}, 1000)
}
showNotice.success('settings.modals.backup.messages.restoreSuccess')
setIsRestarting(true)
window.setTimeout(() => {
void restartApp().catch((err: unknown) => {
setIsRestarting(false)
showNotice.error(err)
})
}, 1000)
} catch (error) {
console.error(error)
showNotice.error(error)
} finally {
setIsConfirming(false)
setIsRestoring(false)
}
})
@@ -267,6 +279,20 @@ export const BackupHistoryViewer = ({
void fetchRows({ force: true })
}
const closeConfirmDialog = () => {
if (isConfirming) return
setPendingConfirmation(null)
}
const confirmTitle =
pendingConfirmation?.action === 'delete'
? t('settings.modals.backup.actions.deleteBackup')
: t('settings.modals.backup.actions.restoreBackup')
const confirmMessage =
pendingConfirmation?.action === 'delete'
? t('settings.modals.backup.messages.confirmDelete')
: t('settings.modals.backup.messages.confirmRestore')
return (
<BaseDialog
open={open}
@@ -423,6 +449,30 @@ export const BackupHistoryViewer = ({
)}
</Stack>
</Box>
<BaseDialog
open={pendingConfirmation !== null}
title={confirmTitle}
okBtn={t('shared.actions.confirm')}
cancelBtn={t('shared.actions.cancel')}
contentSx={{ width: { xs: 320, sm: 420 } }}
loading={isConfirming}
onCancel={closeConfirmDialog}
onClose={closeConfirmDialog}
onOk={handleConfirmAction}
>
<Typography variant="body2" sx={{ wordBreak: 'break-word' }}>
{confirmMessage}
</Typography>
{pendingConfirmation?.filename && (
<Typography
variant="caption"
color="text.secondary"
sx={{ display: 'block', mt: 1, wordBreak: 'break-all' }}
>
{pendingConfirmation.filename}
</Typography>
)}
</BaseDialog>
</BaseDialog>
)
}
@@ -1,4 +1,3 @@
import { LoadingButton } from '@mui/lab'
import {
Button,
List,
@@ -156,7 +155,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
title: t('settings.modals.backup.tabs.local'),
description: t('settings.modals.backup.manual.local'),
actions: [
<LoadingButton
<Button
key="backup"
variant="contained"
size="small"
@@ -165,7 +164,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
onClick={() => handleBackup('local')}
>
{t('settings.modals.backup.actions.backup')}
</LoadingButton>,
</Button>,
<Button
key="history"
variant="outlined"
@@ -175,7 +174,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
>
{t('settings.modals.backup.actions.viewHistory')}
</Button>,
<LoadingButton
<Button
key="import"
variant="text"
size="small"
@@ -184,7 +183,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
onClick={() => handleImport()}
>
{t('settings.modals.backup.actions.importBackup')}
</LoadingButton>,
</Button>,
],
},
{
@@ -192,7 +191,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
title: t('settings.modals.backup.tabs.webdav'),
description: t('settings.modals.backup.manual.webdav'),
actions: [
<LoadingButton
<Button
key="backup"
variant="contained"
size="small"
@@ -200,7 +199,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
onClick={() => handleBackup('webdav')}
>
{t('settings.modals.backup.actions.backup')}
</LoadingButton>,
</Button>,
<Button
key="history"
variant="outlined"
@@ -2,9 +2,9 @@ import {
RestartAltRounded,
SwitchAccessShortcutRounded,
} from '@mui/icons-material'
import { LoadingButton } from '@mui/lab'
import {
Box,
Button,
Chip,
CircularProgress,
List,
@@ -120,7 +120,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
{t('settings.sections.clash.form.fields.clashCore')}
<Box>
<LoadingButton
<Button
variant="contained"
size="small"
startIcon={<SwitchAccessShortcutRounded />}
@@ -131,8 +131,8 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
onClick={onUpgrade}
>
{t('shared.actions.upgrade')}
</LoadingButton>
<LoadingButton
</Button>
<Button
variant="contained"
size="small"
startIcon={<RestartAltRounded />}
@@ -142,7 +142,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
onClick={onRestart}
>
{t('shared.actions.restart')}
</LoadingButton>
</Button>
</Box>
</Box>
}
+2 -3
View File
@@ -20,7 +20,6 @@ import {
RefreshRounded,
TextSnippetOutlined,
} from '@mui/icons-material'
import { LoadingButton } from '@mui/lab'
import { Box, Button, Divider, Grid, IconButton, Stack } from '@mui/material'
import { useQuery } from '@tanstack/react-query'
import { listen, TauriEvent } from '@tauri-apps/api/event'
@@ -970,7 +969,7 @@ const ProfilePage = () => {
},
}}
/>
<LoadingButton
<Button
disabled={!url || disabled}
loading={loading}
variant="contained"
@@ -979,7 +978,7 @@ const ProfilePage = () => {
onClick={onImport}
>
{t('profiles.page.actions.import')}
</LoadingButton>
</Button>
<Button
variant="contained"
size="small"