mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-19 16:10:46 +08:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da050d3e7e | ||
|
|
9a7b15eb15 | ||
|
|
98072a17a3 | ||
|
|
32a093e44b | ||
|
|
7ecec42f4d |
@@ -17,6 +17,7 @@
|
|||||||
<details>
|
<details>
|
||||||
<summary><strong> 🚀 优化改进 </strong></summary>
|
<summary><strong> 🚀 优化改进 </strong></summary>
|
||||||
|
|
||||||
|
- 记住「启用系统标题栏」设置,Windows/macOS 即时生效,Linux 重启后生效
|
||||||
|
|
||||||
**🖥️ Windows**
|
**🖥️ Windows**
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ pub struct IVerge {
|
|||||||
|
|
||||||
pub collapse_navbar: Option<bool>,
|
pub collapse_navbar: Option<bool>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub enable_system_titlebar: Option<bool>,
|
||||||
|
|
||||||
pub sysproxy_tray_icon: Option<bool>,
|
pub sysproxy_tray_icon: Option<bool>,
|
||||||
|
|
||||||
pub tun_tray_icon: Option<bool>,
|
pub tun_tray_icon: Option<bool>,
|
||||||
@@ -374,6 +377,7 @@ impl IVerge {
|
|||||||
menu_icon: Some("monochrome".into()),
|
menu_icon: Some("monochrome".into()),
|
||||||
notice_position: Some("top-right".into()),
|
notice_position: Some("top-right".into()),
|
||||||
collapse_navbar: Some(false),
|
collapse_navbar: Some(false),
|
||||||
|
enable_system_titlebar: Some(true),
|
||||||
common_tray_icon: Some(false),
|
common_tray_icon: Some(false),
|
||||||
sysproxy_tray_icon: Some(false),
|
sysproxy_tray_icon: Some(false),
|
||||||
tun_tray_icon: Some(false),
|
tun_tray_icon: Some(false),
|
||||||
@@ -461,6 +465,7 @@ impl IVerge {
|
|||||||
patch!(proxy_group_tools_position);
|
patch!(proxy_group_tools_position);
|
||||||
patch!(notice_position);
|
patch!(notice_position);
|
||||||
patch!(collapse_navbar);
|
patch!(collapse_navbar);
|
||||||
|
patch!(enable_system_titlebar);
|
||||||
patch!(common_tray_icon);
|
patch!(common_tray_icon);
|
||||||
patch!(sysproxy_tray_icon);
|
patch!(sysproxy_tray_icon);
|
||||||
patch!(tun_tray_icon);
|
patch!(tun_tray_icon);
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ verge_registry! {
|
|||||||
proxy_group_tools_position => NoEffect;
|
proxy_group_tools_position => NoEffect;
|
||||||
notice_position => NoEffect;
|
notice_position => NoEffect;
|
||||||
collapse_navbar => NoEffect;
|
collapse_navbar => NoEffect;
|
||||||
|
enable_system_titlebar => NoEffect;
|
||||||
sysproxy_tray_icon => [TrayIcon];
|
sysproxy_tray_icon => [TrayIcon];
|
||||||
tun_tray_icon => [TrayIcon];
|
tun_tray_icon => [TrayIcon];
|
||||||
enable_tun_mode => Tun;
|
enable_tun_mode => Tun;
|
||||||
|
|||||||
@@ -116,9 +116,13 @@ mod app_init {
|
|||||||
/// Setup window state management
|
/// Setup window state management
|
||||||
pub fn setup_window_state(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn setup_window_state(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
logging!(debug, Type::Setup, "初始化窗口状态管理...");
|
logging!(debug, Type::Setup, "初始化窗口状态管理...");
|
||||||
|
// Decorations are config-driven (`enable_system_titlebar`); restoring a stale
|
||||||
|
// value here would override the builder's `.decorations(...)` on window recreate.
|
||||||
|
let state_flags =
|
||||||
|
tauri_plugin_window_state::StateFlags::all() & !tauri_plugin_window_state::StateFlags::DECORATIONS;
|
||||||
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
let window_state_plugin = tauri_plugin_window_state::Builder::new()
|
||||||
.with_filename(files::WINDOW_STATE)
|
.with_filename(files::WINDOW_STATE)
|
||||||
.with_state_flags(tauri_plugin_window_state::StateFlags::default())
|
.with_state_flags(state_flags)
|
||||||
.build();
|
.build();
|
||||||
app.handle().plugin(window_state_plugin)?;
|
app.handle().plugin(window_state_plugin)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ const DEFAULT_HEIGHT: f64 = 700.0;
|
|||||||
const MINIMAL_WIDTH: f64 = 520.0;
|
const MINIMAL_WIDTH: f64 = 520.0;
|
||||||
const MINIMAL_HEIGHT: f64 = 520.0;
|
const MINIMAL_HEIGHT: f64 = 520.0;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
const DEFAULT_DECORATIONS: bool = false;
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
|
||||||
const DEFAULT_DECORATIONS: bool = true;
|
|
||||||
|
|
||||||
const fn restored_window_size_is_too_small(width: u32, height: u32) -> bool {
|
const fn restored_window_size_is_too_small(width: u32, height: u32) -> bool {
|
||||||
width < MINIMAL_WIDTH as u32 || height < MINIMAL_HEIGHT as u32
|
width < MINIMAL_WIDTH as u32 || height < MINIMAL_HEIGHT as u32
|
||||||
}
|
}
|
||||||
@@ -49,6 +44,7 @@ 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 decorations = latest.enable_system_titlebar.unwrap_or(true);
|
||||||
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() {
|
let initial_theme_mode = match latest.theme_mode.as_deref() {
|
||||||
Some("dark") => "dark",
|
Some("dark") => "dark",
|
||||||
@@ -83,7 +79,7 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
|||||||
)
|
)
|
||||||
.title("Clash Verge")
|
.title("Clash Verge")
|
||||||
.center()
|
.center()
|
||||||
.decorations(DEFAULT_DECORATIONS)
|
.decorations(decorations)
|
||||||
.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)
|
||||||
|
|||||||
@@ -202,15 +202,9 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
|
||||||
&-drag-region {
|
|
||||||
align-self: stretch;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-content__left .the-logo {
|
.layout-content__left .the-logo {
|
||||||
@@ -235,16 +229,9 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
|
||||||
&-drag-region {
|
|
||||||
align-self: stretch;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-content__left {
|
.layout-content__left {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Close, CropSquare, FilterNone, Minimize } from '@mui/icons-material'
|
import { Close, CropSquare, FilterNone, Minimize } from '@mui/icons-material'
|
||||||
import { Box, IconButton } from '@mui/material'
|
import { Box, IconButton } from '@mui/material'
|
||||||
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
type MouseEvent,
|
||||||
type PointerEvent,
|
type PointerEvent,
|
||||||
useCallback,
|
useCallback,
|
||||||
useImperativeHandle,
|
useEffect,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
|
||||||
import { useWindowControls } from '@/hooks/use-window'
|
|
||||||
import getSystem from '@/utils/get-system'
|
import getSystem from '@/utils/get-system'
|
||||||
|
|
||||||
const RESIZE_HANDLES = [
|
const RESIZE_HANDLES = [
|
||||||
@@ -21,9 +21,7 @@ const RESIZE_HANDLES = [
|
|||||||
{ direction: 'NorthWest', position: 'north-west' },
|
{ direction: 'NorthWest', position: 'north-west' },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export const WindowResizeHandles = () => {
|
export const WindowResizeHandles = ({ maximized }: { maximized: boolean }) => {
|
||||||
const { currentWindow, maximized } = useWindowControls()
|
|
||||||
|
|
||||||
const startResizeDragging = useCallback(
|
const startResizeDragging = useCallback(
|
||||||
(event: PointerEvent<HTMLDivElement>) => {
|
(event: PointerEvent<HTMLDivElement>) => {
|
||||||
if (event.button !== 0) return
|
if (event.button !== 0) return
|
||||||
@@ -33,14 +31,14 @@ export const WindowResizeHandles = () => {
|
|||||||
const handle = RESIZE_HANDLES.find((item) => item.direction === direction)
|
const handle = RESIZE_HANDLES.find((item) => item.direction === direction)
|
||||||
|
|
||||||
if (handle) {
|
if (handle) {
|
||||||
void currentWindow
|
void getCurrentWindow()
|
||||||
.startResizeDragging(handle.direction)
|
.startResizeDragging(handle.direction)
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
console.warn('[WindowResizeHandles] 调整窗口大小失败:', error),
|
console.warn('[WindowResizeHandles] 调整窗口大小失败:', error),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[currentWindow],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
if (getSystem() !== 'linux' || maximized) return null
|
if (getSystem() !== 'linux' || maximized) return null
|
||||||
@@ -63,44 +61,77 @@ export const WindowResizeHandles = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WindowControls = forwardRef(function WindowControls(props, ref) {
|
export const WindowControls = ({
|
||||||
|
maximized,
|
||||||
|
setMaximized,
|
||||||
|
}: {
|
||||||
|
maximized: boolean
|
||||||
|
setMaximized: (value: boolean) => void
|
||||||
|
}) => {
|
||||||
const OS = getSystem()
|
const OS = getSystem()
|
||||||
const {
|
|
||||||
currentWindow,
|
|
||||||
maximized,
|
|
||||||
minimize,
|
|
||||||
close,
|
|
||||||
toggleFullscreen,
|
|
||||||
toggleMaximize,
|
|
||||||
} = useWindowControls()
|
|
||||||
|
|
||||||
useImperativeHandle(
|
const close = useCallback(async () => {
|
||||||
ref,
|
// Delay one frame so the UI can clear :hover before the window hides.
|
||||||
() => ({
|
await new Promise((resolve) => setTimeout(resolve, 20))
|
||||||
currentWindow,
|
await getCurrentWindow().close()
|
||||||
maximized,
|
}, [])
|
||||||
minimize,
|
|
||||||
close,
|
const minimize = useCallback(async () => {
|
||||||
toggleFullscreen,
|
// Delay one frame so the UI can clear :hover before the window hides.
|
||||||
toggleMaximize,
|
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||||
}),
|
await getCurrentWindow().minimize()
|
||||||
[
|
}, [])
|
||||||
currentWindow,
|
|
||||||
maximized,
|
const toggleMaximize = useCallback(async () => {
|
||||||
minimize,
|
const currentWindow = getCurrentWindow()
|
||||||
close,
|
if (await currentWindow.isMaximized()) {
|
||||||
toggleFullscreen,
|
await currentWindow.unmaximize()
|
||||||
toggleMaximize,
|
setMaximized(false)
|
||||||
],
|
} else {
|
||||||
|
await currentWindow.maximize()
|
||||||
|
setMaximized(true)
|
||||||
|
}
|
||||||
|
}, [setMaximized])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getCurrentWindow().setMinimizable(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleTitlebarMouseDown = useCallback(
|
||||||
|
(event: MouseEvent<HTMLDivElement>) => {
|
||||||
|
// Left button only, and only on the first press: detail 2 is the second
|
||||||
|
// press of a double-click, which `handleTitlebarDoubleClick` turns into maximize.
|
||||||
|
if (event.button !== 0 || event.detail !== 1) return
|
||||||
|
// Only the empty bar area drags; the control buttons keep their own clicks.
|
||||||
|
if (event.target !== event.currentTarget) return
|
||||||
|
event.preventDefault()
|
||||||
|
void getCurrentWindow()
|
||||||
|
.startDragging()
|
||||||
|
.catch((error) => console.warn('[WindowControls] 开始拖动失败:', error))
|
||||||
|
},
|
||||||
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
// 通过前端对 tauri 窗口进行翻转全屏时会短暂地与系统图标重叠渲染。
|
const handleTitlebarDoubleClick = useCallback(
|
||||||
// 这可能是上游缺陷,保险起见跨平台以窗口的最大化翻转为准。
|
(event: MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (event.target !== event.currentTarget) return
|
||||||
|
void toggleMaximize().catch((error) =>
|
||||||
|
console.warn('[WindowControls] 切换最大化失败:', error),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[toggleMaximize],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
onMouseDown={handleTitlebarMouseDown}
|
||||||
|
onDoubleClick={handleTitlebarDoubleClick}
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
justifyContent: OS === 'macos' ? 'flex-start' : 'flex-end',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
px: 1,
|
||||||
gap: 1,
|
gap: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
'> button': {
|
'> button': {
|
||||||
@@ -186,4 +217,4 @@ export const WindowControls = forwardRef(function WindowControls(props, ref) {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|||||||
@@ -12,16 +12,21 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { join } from '@tauri-apps/api/path'
|
import { join } from '@tauri-apps/api/path'
|
||||||
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
import { open as openDialog } from '@tauri-apps/plugin-dialog'
|
||||||
import { exists } from '@tauri-apps/plugin-fs'
|
import { exists } from '@tauri-apps/plugin-fs'
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
import {
|
||||||
|
BaseDialog,
|
||||||
|
type DialogRef,
|
||||||
|
Switch,
|
||||||
|
TooltipIcon,
|
||||||
|
} from '@/components/base'
|
||||||
import { DEFAULT_HOVER_DELAY } from '@/components/proxy/proxy-group-navigator'
|
import { DEFAULT_HOVER_DELAY } from '@/components/proxy/proxy-group-navigator'
|
||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import { useWindowDecorations } from '@/hooks/use-window'
|
import { copyIconFile, getAppDir, restartApp } from '@/services/cmds'
|
||||||
import { copyIconFile, getAppDir } from '@/services/cmds'
|
|
||||||
import { mutate } from '@/services/mutate'
|
import { mutate } from '@/services/mutate'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
import getSystem from '@/utils/get-system'
|
import getSystem from '@/utils/get-system'
|
||||||
@@ -58,8 +63,6 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
const [sysproxyIcon, setSysproxyIcon] = useState('')
|
const [sysproxyIcon, setSysproxyIcon] = useState('')
|
||||||
const [tunIcon, setTunIcon] = useState('')
|
const [tunIcon, setTunIcon] = useState('')
|
||||||
|
|
||||||
const { decorated, toggleDecorations } = useWindowDecorations()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initIconPath()
|
initIconPath()
|
||||||
}, [])
|
}, [])
|
||||||
@@ -123,17 +126,50 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
|||||||
<List>
|
<List>
|
||||||
<Item>
|
<Item>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t(
|
primary={
|
||||||
'settings.components.verge.layout.fields.preferSystemTitlebar',
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
)}
|
<span>
|
||||||
|
{t(
|
||||||
|
'settings.components.verge.layout.fields.enableSystemTitlebar',
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<TooltipIcon
|
||||||
|
title={t(
|
||||||
|
'settings.components.verge.layout.tooltips.enableSystemTitlebar',
|
||||||
|
)}
|
||||||
|
sx={{ opacity: '0.7' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={decorated}
|
value={verge?.enable_system_titlebar ?? true}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
onChange={async () => {
|
onChange={(e) => onChangeData({ enable_system_titlebar: e })}
|
||||||
await toggleDecorations()
|
onGuard={async (e) => {
|
||||||
|
await patchVerge({ enable_system_titlebar: e })
|
||||||
|
if (OS === 'linux') {
|
||||||
|
void mutate(() => restartApp(), {
|
||||||
|
id: 'restart-app',
|
||||||
|
errorNotice: false,
|
||||||
|
}).catch((error) => {
|
||||||
|
console.warn('[LayoutViewer] failed to restart app:', error)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const currentWindow = getCurrentWindow()
|
||||||
|
try {
|
||||||
|
await currentWindow.setDecorations(e)
|
||||||
|
// macOS clears the Miniaturizable style bit when decorations are disabled; re-enable minimize.
|
||||||
|
await currentWindow.setMinimizable(true)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
'[LayoutViewer] failed to apply system titlebar setting:',
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Switch edge="end" />
|
<Switch edge="end" />
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import debounce from '@/utils/debounce'
|
||||||
|
|
||||||
|
export const useWindowMaximized = (enabled: boolean) => {
|
||||||
|
const [maximized, setMaximized] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enabled) return
|
||||||
|
|
||||||
|
const currentWindow = getCurrentWindow()
|
||||||
|
let isUnmounted = false
|
||||||
|
|
||||||
|
const syncMaximized = async () => {
|
||||||
|
if (isUnmounted) return
|
||||||
|
try {
|
||||||
|
setMaximized(await currentWindow.isMaximized())
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[useWindowMaximized] 读取最大化状态失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void syncMaximized()
|
||||||
|
|
||||||
|
const unlistenPromise = currentWindow.onResized(
|
||||||
|
debounce(syncMaximized, 300),
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isUnmounted = true
|
||||||
|
unlistenPromise
|
||||||
|
.then((unlisten) => unlisten())
|
||||||
|
.catch((err) =>
|
||||||
|
console.warn('[useWindowMaximized] 清理监听器失败:', err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [enabled])
|
||||||
|
|
||||||
|
return { maximized, setMaximized }
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { use } from 'react'
|
|
||||||
|
|
||||||
import { WindowContext, type WindowContextType } from '@/providers/window'
|
|
||||||
|
|
||||||
const useWindow = () => {
|
|
||||||
const context = use(WindowContext)
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('useWindow must be used within WindowProvider')
|
|
||||||
}
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useWindowControls = () => {
|
|
||||||
const {
|
|
||||||
maximized,
|
|
||||||
minimize,
|
|
||||||
toggleMaximize,
|
|
||||||
close,
|
|
||||||
toggleFullscreen,
|
|
||||||
currentWindow,
|
|
||||||
} = useWindow()
|
|
||||||
return {
|
|
||||||
maximized,
|
|
||||||
minimize,
|
|
||||||
toggleMaximize,
|
|
||||||
close,
|
|
||||||
toggleFullscreen,
|
|
||||||
currentWindow,
|
|
||||||
} satisfies Pick<
|
|
||||||
WindowContextType,
|
|
||||||
| 'maximized'
|
|
||||||
| 'minimize'
|
|
||||||
| 'toggleMaximize'
|
|
||||||
| 'close'
|
|
||||||
| 'toggleFullscreen'
|
|
||||||
| 'currentWindow'
|
|
||||||
>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useWindowDecorations = () => {
|
|
||||||
const { decorated, toggleDecorations, refreshDecorated } = useWindow()
|
|
||||||
return { decorated, toggleDecorations, refreshDecorated } satisfies Pick<
|
|
||||||
WindowContextType,
|
|
||||||
'decorated' | 'toggleDecorations' | 'refreshDecorated'
|
|
||||||
>
|
|
||||||
}
|
|
||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "إعدادات التخطيط",
|
"title": "إعدادات التخطيط",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Prefer System Titlebar",
|
"enableSystemTitlebar": "تفعيل شريط عنوان النظام",
|
||||||
"trafficGraph": "مخطط حركة المرور",
|
"trafficGraph": "مخطط حركة المرور",
|
||||||
"memoryUsage": "استهلاك الذاكرة",
|
"memoryUsage": "استهلاك الذاكرة",
|
||||||
"proxyGroupIcon": "أيقونة مجموعة الوكلاء",
|
"proxyGroupIcon": "أيقونة مجموعة الوكلاء",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "إيقاف عرض إحصاءات حركة المرور مؤقتًا عند فقدان التركيز"
|
"pauseRenderTrafficStatsOnBlur": "إيقاف عرض إحصاءات حركة المرور مؤقتًا عند فقدان التركيز"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "استخدام شريط عنوان النظام الأصلي بدلاً من شريط العنوان المخصص المدمج (يلزم إعادة التشغيل على Linux).",
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Layout-Einstellungen",
|
"title": "Layout-Einstellungen",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "System-Titelleiste bevorzugen",
|
"enableSystemTitlebar": "System-Titelleiste aktivieren",
|
||||||
"trafficGraph": "Verkehrsdiagramm",
|
"trafficGraph": "Verkehrsdiagramm",
|
||||||
"memoryUsage": "Kern-Speichernutzung",
|
"memoryUsage": "Kern-Speichernutzung",
|
||||||
"proxyGroupIcon": "Proxy-Gruppen-Symbol",
|
"proxyGroupIcon": "Proxy-Gruppen-Symbol",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Rendering der Verkehrsstatistiken bei Fokusverlust pausieren"
|
"pauseRenderTrafficStatsOnBlur": "Rendering der Verkehrsstatistiken bei Fokusverlust pausieren"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "Die native Titelleiste des Systems anstelle der integrierten benutzerdefinierten Titelleiste verwenden (unter Linux ist ein Neustart erforderlich).",
|
||||||
"hoverNavigator": "Beim Darüberfahren mit der Maus über die Buchstaben automatisch zur passenden Proxy-Gruppe scrollen",
|
"hoverNavigator": "Beim Darüberfahren mit der Maus über die Buchstaben automatisch zur passenden Proxy-Gruppe scrollen",
|
||||||
"hoverNavigatorDelay": "Wartezeit vor dem automatischen Scrollen beim Darüberfahren, in Millisekunden"
|
"hoverNavigatorDelay": "Wartezeit vor dem automatischen Scrollen beim Darüberfahren, in Millisekunden"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Layout Setting",
|
"title": "Layout Setting",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Prefer System Titlebar",
|
"enableSystemTitlebar": "Enable System Titlebar",
|
||||||
"trafficGraph": "Traffic Graph",
|
"trafficGraph": "Traffic Graph",
|
||||||
"memoryUsage": "Core Usage",
|
"memoryUsage": "Core Usage",
|
||||||
"proxyGroupIcon": "Proxy Group Icon",
|
"proxyGroupIcon": "Proxy Group Icon",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Pause rendering traffic statistics when blurred"
|
"pauseRenderTrafficStatsOnBlur": "Pause rendering traffic statistics when blurred"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "Use the system's native title bar instead of the built-in custom title bar (restart required on Linux).",
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Configuración de la interfaz",
|
"title": "Configuración de la interfaz",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Prefer System Titlebar",
|
"enableSystemTitlebar": "Activar barra de título del sistema",
|
||||||
"trafficGraph": "Gráfico de tráfico",
|
"trafficGraph": "Gráfico de tráfico",
|
||||||
"memoryUsage": "Uso de memoria del núcleo",
|
"memoryUsage": "Uso de memoria del núcleo",
|
||||||
"proxyGroupIcon": "Icono del grupo de proxy",
|
"proxyGroupIcon": "Icono del grupo de proxy",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Pausar el renderizado de estadísticas de tráfico al perder el foco"
|
"pauseRenderTrafficStatsOnBlur": "Pausar el renderizado de estadísticas de tráfico al perder el foco"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "Usar la barra de título nativa del sistema en lugar de la barra de título personalizada integrada (se requiere reiniciar en Linux).",
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "تنظیمات چیدمان",
|
"title": "تنظیمات چیدمان",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Prefer System Titlebar",
|
"enableSystemTitlebar": "فعالسازی نوار عنوان سیستم",
|
||||||
"trafficGraph": "نمودار ترافیک",
|
"trafficGraph": "نمودار ترافیک",
|
||||||
"memoryUsage": "استفاده از حافظه",
|
"memoryUsage": "استفاده از حافظه",
|
||||||
"proxyGroupIcon": "آیکون گروه پراکسی",
|
"proxyGroupIcon": "آیکون گروه پراکسی",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "توقف رندر آمار ترافیک هنگام از دست رفتن فوکوس"
|
"pauseRenderTrafficStatsOnBlur": "توقف رندر آمار ترافیک هنگام از دست رفتن فوکوس"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "استفاده از نوار عنوان بومی سیستم بهجای نوار عنوان سفارشی داخلی (در لینوکس نیاز به راهاندازی مجدد است).",
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Pengaturan Tata Letak",
|
"title": "Pengaturan Tata Letak",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Prefer System Titlebar",
|
"enableSystemTitlebar": "Aktifkan bilah judul sistem",
|
||||||
"trafficGraph": "Grafik Lalu Lintas",
|
"trafficGraph": "Grafik Lalu Lintas",
|
||||||
"memoryUsage": "Penggunaan Memori",
|
"memoryUsage": "Penggunaan Memori",
|
||||||
"proxyGroupIcon": "Ikon Grup Proksi",
|
"proxyGroupIcon": "Ikon Grup Proksi",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Jeda render statistik lalu lintas saat jendela tidak fokus"
|
"pauseRenderTrafficStatsOnBlur": "Jeda render statistik lalu lintas saat jendela tidak fokus"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "Gunakan bilah judul asli sistem sebagai ganti bilah judul kustom bawaan (perlu mulai ulang di Linux).",
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "レイアウト設定",
|
"title": "レイアウト設定",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Prefer System Titlebar",
|
"enableSystemTitlebar": "システムタイトルバーを有効にする",
|
||||||
"trafficGraph": "トラフィックグラフ",
|
"trafficGraph": "トラフィックグラフ",
|
||||||
"memoryUsage": "コアメモリ使用量",
|
"memoryUsage": "コアメモリ使用量",
|
||||||
"proxyGroupIcon": "プロキシグループアイコン",
|
"proxyGroupIcon": "プロキシグループアイコン",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "フォーカスを失ったときにトラフィック統計の描画を一時停止"
|
"pauseRenderTrafficStatsOnBlur": "フォーカスを失ったときにトラフィック統計の描画を一時停止"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "内蔵のカスタムタイトルバーの代わりにシステム標準のタイトルバーを使用します(Linux では再起動が必要です)。",
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "레이아웃 설정",
|
"title": "레이아웃 설정",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "시스템 제목 표시줄 사용",
|
"enableSystemTitlebar": "시스템 제목 표시줄 활성화",
|
||||||
"trafficGraph": "트래픽 그래프",
|
"trafficGraph": "트래픽 그래프",
|
||||||
"memoryUsage": "메모리 사용량",
|
"memoryUsage": "메모리 사용량",
|
||||||
"proxyGroupIcon": "프록시 그룹 아이콘",
|
"proxyGroupIcon": "프록시 그룹 아이콘",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "포커스를 잃으면 트래픽 통계 렌더링 일시 중지"
|
"pauseRenderTrafficStatsOnBlur": "포커스를 잃으면 트래픽 통계 렌더링 일시 중지"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "내장 사용자 지정 제목 표시줄 대신 시스템 기본 제목 표시줄을 사용합니다(Linux에서는 다시 시작해야 함).",
|
||||||
"hoverNavigator": "알파벳에 마우스를 올리면 해당 프록시 그룹으로 자동 스크롤합니다",
|
"hoverNavigator": "알파벳에 마우스를 올리면 해당 프록시 그룹으로 자동 스크롤합니다",
|
||||||
"hoverNavigatorDelay": "호버 시 자동 스크롤까지의 지연(밀리초)"
|
"hoverNavigatorDelay": "호버 시 자동 스크롤까지의 지연(밀리초)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Configurações de Layout",
|
"title": "Configurações de Layout",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Preferir Barra de Título do Sistema",
|
"enableSystemTitlebar": "Ativar barra de título do sistema",
|
||||||
"trafficGraph": "Gráfico de Tráfego",
|
"trafficGraph": "Gráfico de Tráfego",
|
||||||
"memoryUsage": "Uso do Núcleo",
|
"memoryUsage": "Uso do Núcleo",
|
||||||
"proxyGroupIcon": "Ícone do Grupo de Proxy",
|
"proxyGroupIcon": "Ícone do Grupo de Proxy",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Pausar a renderização das estatísticas de tráfego quando sem foco"
|
"pauseRenderTrafficStatsOnBlur": "Pausar a renderização das estatísticas de tráfego quando sem foco"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "Usar a barra de título nativa do sistema em vez da barra de título personalizada integrada (é necessário reiniciar no Linux).",
|
||||||
"hoverNavigator": "Rola automaticamente até o grupo de proxy correspondente ao passar o mouse sobre as letras do alfabeto",
|
"hoverNavigator": "Rola automaticamente até o grupo de proxy correspondente ao passar o mouse sobre as letras do alfabeto",
|
||||||
"hoverNavigatorDelay": "Atraso antes da rolagem automática ao passar o mouse, em milissegundos"
|
"hoverNavigatorDelay": "Atraso antes da rolagem automática ao passar o mouse, em milissegundos"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Настройки раскладки",
|
"title": "Настройки раскладки",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Использовать системную панель заголовка",
|
"enableSystemTitlebar": "Включить системную панель заголовка",
|
||||||
"trafficGraph": "График трафика",
|
"trafficGraph": "График трафика",
|
||||||
"memoryUsage": "Использование памяти",
|
"memoryUsage": "Использование памяти",
|
||||||
"proxyGroupIcon": "Значок группы прокси",
|
"proxyGroupIcon": "Значок группы прокси",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "При потере фокуса приостанавливать отрисовку статистики трафика"
|
"pauseRenderTrafficStatsOnBlur": "При потере фокуса приостанавливать отрисовку статистики трафика"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "Использовать системную панель заголовка вместо встроенной настраиваемой (в Linux требуется перезапуск).",
|
||||||
"hoverNavigator": "Автоматически прокручивает к группе прокси при наведении на буквы",
|
"hoverNavigator": "Автоматически прокручивает к группе прокси при наведении на буквы",
|
||||||
"hoverNavigatorDelay": "Задержка перед прокруткой при наведении (в миллисекундах)"
|
"hoverNavigatorDelay": "Задержка перед прокруткой при наведении (в миллисекундах)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Düzen Ayarı",
|
"title": "Düzen Ayarı",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Sistem Başlık Çubuğunu Tercih Et",
|
"enableSystemTitlebar": "Sistem başlık çubuğunu etkinleştir",
|
||||||
"trafficGraph": "Trafik Grafiği",
|
"trafficGraph": "Trafik Grafiği",
|
||||||
"memoryUsage": "Çekirdek Kullanımı",
|
"memoryUsage": "Çekirdek Kullanımı",
|
||||||
"proxyGroupIcon": "Vekil Grup Simgesi",
|
"proxyGroupIcon": "Vekil Grup Simgesi",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Odak kaybolduğunda trafik istatistiklerinin işlenmesini duraklat"
|
"pauseRenderTrafficStatsOnBlur": "Odak kaybolduğunda trafik istatistiklerinin işlenmesini duraklat"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "Yerleşik özel başlık çubuğu yerine sistemin yerel başlık çubuğunu kullan (Linux'ta yeniden başlatma gerekir).",
|
||||||
"hoverNavigator": "Alfabe harflerinin üzerine gelindiğinde ilgili vekil grubuna otomatik olarak kaydırır",
|
"hoverNavigator": "Alfabe harflerinin üzerine gelindiğinde ilgili vekil grubuna otomatik olarak kaydırır",
|
||||||
"hoverNavigatorDelay": "Üzerine gelindiğinde otomatik kaydırmadan önceki gecikme, milisaniye cinsinden"
|
"hoverNavigatorDelay": "Üzerine gelindiğinde otomatik kaydırmadan önceki gecikme, milisaniye cinsinden"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "Расположение көйләүләре",
|
"title": "Расположение көйләүләре",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "Prefer System Titlebar",
|
"enableSystemTitlebar": "Enable System Titlebar",
|
||||||
"trafficGraph": "Трафик графигы",
|
"trafficGraph": "Трафик графигы",
|
||||||
"memoryUsage": "Хәтер куллану",
|
"memoryUsage": "Хәтер куллану",
|
||||||
"proxyGroupIcon": "Прокси төркеме иконкасы",
|
"proxyGroupIcon": "Прокси төркеме иконкасы",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "Фокус югалганда трафик статистикасын сызуны туктатып тору"
|
"pauseRenderTrafficStatsOnBlur": "Фокус югалганда трафик статистикасын сызуны туктатып тору"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "Use the system's native title bar instead of the built-in custom title bar (restart required on Linux).",
|
||||||
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
|
||||||
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "界面设置",
|
"title": "界面设置",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "优先使用系统标题栏",
|
"enableSystemTitlebar": "启用系统标题栏",
|
||||||
"trafficGraph": "流量图显",
|
"trafficGraph": "流量图显",
|
||||||
"memoryUsage": "内核占用",
|
"memoryUsage": "内核占用",
|
||||||
"proxyGroupIcon": "代理组图标",
|
"proxyGroupIcon": "代理组图标",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "失去焦点时暂停渲染流量统计"
|
"pauseRenderTrafficStatsOnBlur": "失去焦点时暂停渲染流量统计"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "使用系统原生标题栏替代内置的自定义标题栏(Linux 下需重启生效)",
|
||||||
"hoverNavigator": "鼠标悬停在字母上时自动滚动到对应代理组",
|
"hoverNavigator": "鼠标悬停在字母上时自动滚动到对应代理组",
|
||||||
"hoverNavigatorDelay": "鼠标悬停后触发自动跳转前等待的毫秒数"
|
"hoverNavigatorDelay": "鼠标悬停后触发自动跳转前等待的毫秒数"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"title": "介面設定",
|
"title": "介面設定",
|
||||||
"fields": {
|
"fields": {
|
||||||
"preferSystemTitlebar": "優先使用系統標題欄",
|
"enableSystemTitlebar": "啟用系統標題欄",
|
||||||
"trafficGraph": "流量圖表",
|
"trafficGraph": "流量圖表",
|
||||||
"memoryUsage": "內核佔用",
|
"memoryUsage": "內核佔用",
|
||||||
"proxyGroupIcon": "代理組圖示",
|
"proxyGroupIcon": "代理組圖示",
|
||||||
@@ -251,6 +251,7 @@
|
|||||||
"pauseRenderTrafficStatsOnBlur": "失去焦點時暫停渲染流量統計"
|
"pauseRenderTrafficStatsOnBlur": "失去焦點時暫停渲染流量統計"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
|
"enableSystemTitlebar": "使用系統原生標題欄取代內建的自訂標題欄(Linux 需重新啟動生效)",
|
||||||
"hoverNavigator": "滑鼠懸停在字母上時自動捲動到對應代理組",
|
"hoverNavigator": "滑鼠懸停在字母上時自動捲動到對應代理組",
|
||||||
"hoverNavigatorDelay": "滑鼠懸停後觸發自動跳轉前等待的毫秒數"
|
"hoverNavigatorDelay": "滑鼠懸停後觸發自動跳轉前等待的毫秒數"
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-6
@@ -12,7 +12,6 @@ import { BaseErrorBoundary } from './components/base'
|
|||||||
import { router } from './pages/_routers'
|
import { router } from './pages/_routers'
|
||||||
import { preloadHomePageCards } from './pages/home'
|
import { preloadHomePageCards } from './pages/home'
|
||||||
import { AppDataProvider } from './providers/app-data-provider'
|
import { AppDataProvider } from './providers/app-data-provider'
|
||||||
import { WindowProvider } from './providers/window'
|
|
||||||
import { FALLBACK_LANGUAGE, initializeLanguage } from './services/i18n'
|
import { FALLBACK_LANGUAGE, initializeLanguage } from './services/i18n'
|
||||||
import {
|
import {
|
||||||
preloadAppData,
|
preloadAppData,
|
||||||
@@ -50,11 +49,9 @@ const initializeApp = (initialThemeMode: 'light' | 'dark') => {
|
|||||||
<ComposeContextProvider contexts={contexts}>
|
<ComposeContextProvider contexts={contexts}>
|
||||||
<BaseErrorBoundary>
|
<BaseErrorBoundary>
|
||||||
<SWRConfig value={swrConfig}>
|
<SWRConfig value={swrConfig}>
|
||||||
<WindowProvider>
|
<AppDataProvider>
|
||||||
<AppDataProvider>
|
<RouterProvider router={router} />
|
||||||
<RouterProvider router={router} />
|
</AppDataProvider>
|
||||||
</AppDataProvider>
|
|
||||||
</WindowProvider>
|
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
</BaseErrorBoundary>
|
</BaseErrorBoundary>
|
||||||
</ComposeContextProvider>
|
</ComposeContextProvider>
|
||||||
|
|||||||
+9
-13
@@ -1,7 +1,7 @@
|
|||||||
import { Paper, ThemeProvider } from '@mui/material'
|
import { Paper, ThemeProvider } from '@mui/material'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
import { useCallback, useEffect, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Outlet, useNavigate } from 'react-router'
|
import { Outlet, useNavigate } from 'react-router'
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from '@/components/layout/window-controller'
|
} from '@/components/layout/window-controller'
|
||||||
import { useI18n } from '@/hooks/use-i18n'
|
import { useI18n } from '@/hooks/use-i18n'
|
||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import { useWindowDecorations } from '@/hooks/use-window'
|
import { useWindowMaximized } from '@/hooks/use-window-maximized'
|
||||||
import { useEventBus } from '@/services/bus'
|
import { useEventBus } from '@/services/bus'
|
||||||
import { handleNoticeMessage } from '@/services/notice-handlers'
|
import { handleNoticeMessage } from '@/services/notice-handlers'
|
||||||
import { useThemeMode } from '@/services/states'
|
import { useThemeMode } from '@/services/states'
|
||||||
@@ -46,21 +46,17 @@ const Layout = () => {
|
|||||||
const { switchLanguage } = useI18n()
|
const { switchLanguage } = useI18n()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const themeReady = useMemo(() => Boolean(theme), [theme])
|
const themeReady = useMemo(() => Boolean(theme), [theme])
|
||||||
const windowControlsRef = useRef<any>(null)
|
const showCustomTitlebar = verge?.enable_system_titlebar === false
|
||||||
const { decorated } = useWindowDecorations()
|
const { maximized, setMaximized } = useWindowMaximized(showCustomTitlebar)
|
||||||
|
|
||||||
const customTitlebar = useMemo(
|
const customTitlebar = useMemo(
|
||||||
() =>
|
() =>
|
||||||
decorated === false ? (
|
showCustomTitlebar ? (
|
||||||
<div className="the_titlebar">
|
<div className="the_titlebar">
|
||||||
<div
|
<WindowControls maximized={maximized} setMaximized={setMaximized} />
|
||||||
className="the_titlebar-drag-region"
|
|
||||||
data-tauri-drag-region="true"
|
|
||||||
/>
|
|
||||||
<WindowControls ref={windowControlsRef} />
|
|
||||||
</div>
|
</div>
|
||||||
) : null,
|
) : null,
|
||||||
[decorated],
|
[showCustomTitlebar, maximized, setMaximized],
|
||||||
)
|
)
|
||||||
|
|
||||||
useLoadingOverlay(themeReady)
|
useLoadingOverlay(themeReady)
|
||||||
@@ -154,9 +150,9 @@ const Layout = () => {
|
|||||||
: {},
|
: {},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{decorated === false && <WindowResizeHandles />}
|
{showCustomTitlebar && <WindowResizeHandles maximized={maximized} />}
|
||||||
|
|
||||||
{/* Custom titlebar - rendered only when decorated is false, memoized for performance */}
|
{/* Custom titlebar - rendered only when the system titlebar is not preferred, memoized for performance */}
|
||||||
{customTitlebar}
|
{customTitlebar}
|
||||||
|
|
||||||
<div className="layout-content">
|
<div className="layout-content">
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export { WindowContext } from './window-context'
|
|
||||||
export type { WindowContextType } from './window-context'
|
|
||||||
export { WindowProvider } from './window-provider'
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
|
||||||
import { createContext } from 'react'
|
|
||||||
|
|
||||||
export interface WindowContextType {
|
|
||||||
decorated: boolean | null
|
|
||||||
maximized: boolean | null
|
|
||||||
toggleDecorations: () => Promise<void>
|
|
||||||
refreshDecorated: () => Promise<boolean>
|
|
||||||
minimize: () => Promise<void>
|
|
||||||
close: () => Promise<void>
|
|
||||||
toggleMaximize: () => Promise<void>
|
|
||||||
toggleFullscreen: () => Promise<void>
|
|
||||||
currentWindow: ReturnType<typeof getCurrentWindow>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WindowContext = createContext<WindowContextType | undefined>(
|
|
||||||
undefined,
|
|
||||||
)
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
||||||
|
|
||||||
import debounce from '@/utils/debounce'
|
|
||||||
|
|
||||||
import { WindowContext } from './window-context'
|
|
||||||
|
|
||||||
export const WindowProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const currentWindow = useMemo(() => getCurrentWindow(), [])
|
|
||||||
const [decorated, setDecorated] = useState<boolean | null>(null)
|
|
||||||
const [maximized, setMaximized] = useState<boolean | null>(null)
|
|
||||||
|
|
||||||
const close = useCallback(async () => {
|
|
||||||
// Delay one frame so the UI can clear :hover before the window hides.
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
||||||
await currentWindow.close()
|
|
||||||
}, [currentWindow])
|
|
||||||
const minimize = useCallback(async () => {
|
|
||||||
// Delay one frame so the UI can clear :hover before the window hides.
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
||||||
await currentWindow.minimize()
|
|
||||||
}, [currentWindow])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let isUnmounted = false
|
|
||||||
let lastWidth = -1
|
|
||||||
let lastHeight = -1
|
|
||||||
|
|
||||||
const checkMaximized = debounce(
|
|
||||||
async (event: { payload: { width: number; height: number } }) => {
|
|
||||||
if (isUnmounted) return
|
|
||||||
const { width, height } = event.payload
|
|
||||||
if (width === lastWidth && height === lastHeight) return
|
|
||||||
lastWidth = width
|
|
||||||
lastHeight = height
|
|
||||||
const value = await currentWindow.isMaximized()
|
|
||||||
setMaximized(value)
|
|
||||||
},
|
|
||||||
300,
|
|
||||||
)
|
|
||||||
|
|
||||||
const unlistenPromise = currentWindow.onResized(checkMaximized)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
isUnmounted = true
|
|
||||||
unlistenPromise
|
|
||||||
.then((unlisten) => unlisten())
|
|
||||||
.catch((err) => console.warn('[WindowProvider] 清理监听器失败:', err))
|
|
||||||
}
|
|
||||||
}, [currentWindow])
|
|
||||||
|
|
||||||
const toggleMaximize = useCallback(async () => {
|
|
||||||
if (await currentWindow.isMaximized()) {
|
|
||||||
await currentWindow.unmaximize()
|
|
||||||
setMaximized(false)
|
|
||||||
} else {
|
|
||||||
await currentWindow.maximize()
|
|
||||||
setMaximized(true)
|
|
||||||
}
|
|
||||||
}, [currentWindow])
|
|
||||||
|
|
||||||
const toggleFullscreen = useCallback(async () => {
|
|
||||||
await currentWindow.setFullscreen(!(await currentWindow.isFullscreen()))
|
|
||||||
}, [currentWindow])
|
|
||||||
|
|
||||||
const refreshDecorated = useCallback(async () => {
|
|
||||||
const val = await currentWindow.isDecorated()
|
|
||||||
setDecorated(val)
|
|
||||||
return val
|
|
||||||
}, [currentWindow])
|
|
||||||
|
|
||||||
const toggleDecorations = useCallback(async () => {
|
|
||||||
const currentVal = await currentWindow.isDecorated()
|
|
||||||
await currentWindow.setDecorations(!currentVal)
|
|
||||||
setDecorated(!currentVal)
|
|
||||||
}, [currentWindow])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refreshDecorated()
|
|
||||||
currentWindow.setMinimizable?.(true)
|
|
||||||
}, [currentWindow, refreshDecorated])
|
|
||||||
|
|
||||||
const contextValue = useMemo(
|
|
||||||
() => ({
|
|
||||||
decorated,
|
|
||||||
maximized,
|
|
||||||
toggleDecorations,
|
|
||||||
refreshDecorated,
|
|
||||||
minimize,
|
|
||||||
close,
|
|
||||||
toggleMaximize,
|
|
||||||
toggleFullscreen,
|
|
||||||
currentWindow,
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
decorated,
|
|
||||||
maximized,
|
|
||||||
toggleDecorations,
|
|
||||||
refreshDecorated,
|
|
||||||
minimize,
|
|
||||||
close,
|
|
||||||
toggleMaximize,
|
|
||||||
toggleFullscreen,
|
|
||||||
currentWindow,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
return <WindowContext value={contextValue}>{children}</WindowContext>
|
|
||||||
}
|
|
||||||
@@ -375,6 +375,7 @@ export type IVerge_Deserialize = {
|
|||||||
proxy_group_tools_position?: string | null
|
proxy_group_tools_position?: string | null
|
||||||
notice_position?: string | null
|
notice_position?: string | null
|
||||||
collapse_navbar?: boolean | null
|
collapse_navbar?: boolean | null
|
||||||
|
enable_system_titlebar?: boolean | null
|
||||||
sysproxy_tray_icon?: boolean | null
|
sysproxy_tray_icon?: boolean | null
|
||||||
tun_tray_icon?: boolean | null
|
tun_tray_icon?: boolean | null
|
||||||
enable_tun_mode?: boolean | null
|
enable_tun_mode?: boolean | null
|
||||||
@@ -465,6 +466,7 @@ export type IVerge_Serialize = {
|
|||||||
proxy_group_tools_position?: string | null
|
proxy_group_tools_position?: string | null
|
||||||
notice_position?: string | null
|
notice_position?: string | null
|
||||||
collapse_navbar: boolean | null
|
collapse_navbar: boolean | null
|
||||||
|
enable_system_titlebar?: boolean | null
|
||||||
sysproxy_tray_icon: boolean | null
|
sysproxy_tray_icon: boolean | null
|
||||||
tun_tray_icon: boolean | null
|
tun_tray_icon: boolean | null
|
||||||
enable_tun_mode: boolean | null
|
enable_tun_mode: boolean | null
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ export const translationKeys = [
|
|||||||
'settings.components.verge.theme.actions.editCss',
|
'settings.components.verge.theme.actions.editCss',
|
||||||
'settings.components.verge.theme.dialogs.editCssTitle',
|
'settings.components.verge.theme.dialogs.editCssTitle',
|
||||||
'settings.components.verge.layout.title',
|
'settings.components.verge.layout.title',
|
||||||
'settings.components.verge.layout.fields.preferSystemTitlebar',
|
'settings.components.verge.layout.fields.enableSystemTitlebar',
|
||||||
'settings.components.verge.layout.fields.trafficGraph',
|
'settings.components.verge.layout.fields.trafficGraph',
|
||||||
'settings.components.verge.layout.fields.memoryUsage',
|
'settings.components.verge.layout.fields.memoryUsage',
|
||||||
'settings.components.verge.layout.fields.proxyGroupIcon',
|
'settings.components.verge.layout.fields.proxyGroupIcon',
|
||||||
@@ -551,6 +551,7 @@ export const translationKeys = [
|
|||||||
'settings.components.verge.layout.fields.enableTrayIcon',
|
'settings.components.verge.layout.fields.enableTrayIcon',
|
||||||
'settings.components.verge.layout.fields.enableTraySpeed',
|
'settings.components.verge.layout.fields.enableTraySpeed',
|
||||||
'settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur',
|
'settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur',
|
||||||
|
'settings.components.verge.layout.tooltips.enableSystemTitlebar',
|
||||||
'settings.components.verge.layout.tooltips.hoverNavigator',
|
'settings.components.verge.layout.tooltips.hoverNavigator',
|
||||||
'settings.components.verge.layout.tooltips.hoverNavigatorDelay',
|
'settings.components.verge.layout.tooltips.hoverNavigatorDelay',
|
||||||
'settings.components.verge.layout.options.icon.monochrome',
|
'settings.components.verge.layout.options.icon.monochrome',
|
||||||
|
|||||||
@@ -746,6 +746,7 @@ export interface TranslationResources {
|
|||||||
fields: {
|
fields: {
|
||||||
collapseNavBar: string
|
collapseNavBar: string
|
||||||
commonTrayIcon: string
|
commonTrayIcon: string
|
||||||
|
enableSystemTitlebar: string
|
||||||
enableTrayIcon: string
|
enableTrayIcon: string
|
||||||
enableTraySpeed: string
|
enableTraySpeed: string
|
||||||
hoverNavigator: string
|
hoverNavigator: string
|
||||||
@@ -753,7 +754,6 @@ export interface TranslationResources {
|
|||||||
memoryUsage: string
|
memoryUsage: string
|
||||||
navIcon: string
|
navIcon: string
|
||||||
pauseRenderTrafficStatsOnBlur: string
|
pauseRenderTrafficStatsOnBlur: string
|
||||||
preferSystemTitlebar: string
|
|
||||||
proxyGroupIcon: string
|
proxyGroupIcon: string
|
||||||
proxyGroupsDisplayMode: string
|
proxyGroupsDisplayMode: string
|
||||||
showOutboundModesInline: string
|
showOutboundModesInline: string
|
||||||
@@ -783,6 +783,7 @@ export interface TranslationResources {
|
|||||||
}
|
}
|
||||||
title: string
|
title: string
|
||||||
tooltips: {
|
tooltips: {
|
||||||
|
enableSystemTitlebar: string
|
||||||
hoverNavigator: string
|
hoverNavigator: string
|
||||||
hoverNavigatorDelay: string
|
hoverNavigatorDelay: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default function getSystem() {
|
|||||||
|
|
||||||
if (/win64|win32/i.test(ua) || platform === 'win32') return 'windows'
|
if (/win64|win32/i.test(ua) || platform === 'win32') return 'windows'
|
||||||
|
|
||||||
if (/linux/i.test(ua)) return 'linux'
|
if (/linux/i.test(ua) || platform === 'linux') return 'linux'
|
||||||
|
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user