mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
feat(notice): persist toast position preference (#5621)
* feat(notice): persist toast position preference * docs: Changelog.md
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
- 允许代理页面允许高级过滤搜索
|
||||
- 备份设置页面新增导入备份按钮
|
||||
- 允许修改通知弹窗位置(「界面设置」->「通知位置」)
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ pub struct IVerge {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub menu_order: Option<Vec<String>>,
|
||||
|
||||
/// toast / notice position on screen
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub notice_position: Option<String>,
|
||||
|
||||
/// sysproxy tray icon
|
||||
pub sysproxy_tray_icon: Option<bool>,
|
||||
|
||||
@@ -391,6 +395,7 @@ impl IVerge {
|
||||
#[cfg(target_os = "macos")]
|
||||
tray_icon: Some("monochrome".into()),
|
||||
menu_icon: Some("monochrome".into()),
|
||||
notice_position: Some("top-right".into()),
|
||||
common_tray_icon: Some(false),
|
||||
sysproxy_tray_icon: Some(false),
|
||||
tun_tray_icon: Some(false),
|
||||
@@ -475,6 +480,7 @@ impl IVerge {
|
||||
patch!(tray_icon);
|
||||
patch!(menu_icon);
|
||||
patch!(menu_order);
|
||||
patch!(notice_position);
|
||||
patch!(common_tray_icon);
|
||||
patch!(sysproxy_tray_icon);
|
||||
patch!(tun_tray_icon);
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { CloseRounded } from "@mui/icons-material";
|
||||
import { Snackbar, Alert, IconButton, Box } from "@mui/material";
|
||||
import React, { useSyncExternalStore } from "react";
|
||||
import {
|
||||
Snackbar,
|
||||
Alert,
|
||||
IconButton,
|
||||
Box,
|
||||
type SnackbarOrigin,
|
||||
} from "@mui/material";
|
||||
import React, { useMemo, useSyncExternalStore } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
@@ -10,8 +16,41 @@ import {
|
||||
} from "@/services/notice-service";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
|
||||
export const NoticeManager: React.FC = () => {
|
||||
type NoticePosition = NonNullable<IVergeConfig["notice_position"]>;
|
||||
|
||||
const VALID_POSITIONS: NoticePosition[] = [
|
||||
"top-left",
|
||||
"top-right",
|
||||
"bottom-left",
|
||||
"bottom-right",
|
||||
];
|
||||
|
||||
const resolvePosition = (position?: NoticePosition | null): NoticePosition => {
|
||||
if (position && VALID_POSITIONS.includes(position)) {
|
||||
return position;
|
||||
}
|
||||
return "top-right";
|
||||
};
|
||||
|
||||
const getAnchorOrigin = (position: NoticePosition): SnackbarOrigin => {
|
||||
const [vertical, horizontal] = position.split("-") as [
|
||||
SnackbarOrigin["vertical"],
|
||||
SnackbarOrigin["horizontal"],
|
||||
];
|
||||
return { vertical, horizontal };
|
||||
};
|
||||
|
||||
interface NoticeManagerProps {
|
||||
position?: NoticePosition | null;
|
||||
}
|
||||
|
||||
export const NoticeManager: React.FC<NoticeManagerProps> = ({ position }) => {
|
||||
const { t } = useTranslation();
|
||||
const resolvedPosition = useMemo(() => resolvePosition(position), [position]);
|
||||
const anchorOrigin = useMemo(
|
||||
() => getAnchorOrigin(resolvedPosition),
|
||||
[resolvedPosition],
|
||||
);
|
||||
const currentNotices = useSyncExternalStore(
|
||||
subscribeNotices,
|
||||
getSnapshotNotices,
|
||||
@@ -25,8 +64,10 @@ export const NoticeManager: React.FC = () => {
|
||||
<Box
|
||||
sx={{
|
||||
position: "fixed",
|
||||
top: "20px",
|
||||
right: "20px",
|
||||
top: anchorOrigin.vertical === "top" ? "20px" : "auto",
|
||||
bottom: anchorOrigin.vertical === "bottom" ? "20px" : "auto",
|
||||
left: anchorOrigin.horizontal === "left" ? "20px" : "auto",
|
||||
right: anchorOrigin.horizontal === "right" ? "20px" : "auto",
|
||||
zIndex: 1500,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
@@ -38,7 +79,7 @@ export const NoticeManager: React.FC = () => {
|
||||
<Snackbar
|
||||
key={notice.id}
|
||||
open={true}
|
||||
anchorOrigin={{ vertical: "top", horizontal: "right" }}
|
||||
anchorOrigin={anchorOrigin}
|
||||
sx={{
|
||||
position: "relative",
|
||||
transform: "none",
|
||||
|
||||
@@ -190,6 +190,42 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
||||
</GuardState>
|
||||
</Item>
|
||||
|
||||
<Item>
|
||||
<ListItemText
|
||||
primary={t("settings.components.verge.layout.fields.toastPosition")}
|
||||
/>
|
||||
<GuardState
|
||||
value={verge?.notice_position ?? "top-right"}
|
||||
onCatch={onError}
|
||||
onFormat={(e: any) => e.target.value}
|
||||
onChange={(value) => onChangeData({ notice_position: value })}
|
||||
onGuard={(value) => patchVerge({ notice_position: value })}
|
||||
>
|
||||
<Select size="small" sx={{ width: 180, "> div": { py: "7.5px" } }}>
|
||||
<MenuItem value="top-right">
|
||||
{t(
|
||||
"settings.components.verge.layout.options.toastPosition.topRight",
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value="top-left">
|
||||
{t(
|
||||
"settings.components.verge.layout.options.toastPosition.topLeft",
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value="bottom-right">
|
||||
{t(
|
||||
"settings.components.verge.layout.options.toastPosition.bottomRight",
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value="bottom-left">
|
||||
{t(
|
||||
"settings.components.verge.layout.options.toastPosition.bottomLeft",
|
||||
)}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</GuardState>
|
||||
</Item>
|
||||
|
||||
<Item>
|
||||
<ListItemText
|
||||
primary={
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "مخطط حركة المرور",
|
||||
"memoryUsage": "استهلاك الذاكرة",
|
||||
"proxyGroupIcon": "أيقونة مجموعة الوكلاء",
|
||||
"toastPosition": "Toast Position",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "أيقونة التنقل",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "أحادي اللون",
|
||||
"colorful": "ملون",
|
||||
"disable": "تعطيل"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "Top Left",
|
||||
"topRight": "Top Right",
|
||||
"bottomLeft": "Bottom Left",
|
||||
"bottomRight": "Bottom Right"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "Verkehrsdiagramm",
|
||||
"memoryUsage": "Kern-Speichernutzung",
|
||||
"proxyGroupIcon": "Proxy-Gruppen-Symbol",
|
||||
"toastPosition": "Toast-Position",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "Navigationsleiste-Symbol",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "Monochromes Symbol",
|
||||
"colorful": "Farbiges Symbol",
|
||||
"disable": "Deaktivieren"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "Oben links",
|
||||
"topRight": "Oben rechts",
|
||||
"bottomLeft": "Unten links",
|
||||
"bottomRight": "Unten rechts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "Traffic Graph",
|
||||
"memoryUsage": "Core Usage",
|
||||
"proxyGroupIcon": "Proxy Group Icon",
|
||||
"toastPosition": "Toast Position",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "Nav Icon",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "Monochrome",
|
||||
"colorful": "Colorful",
|
||||
"disable": "Disable"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "Top Left",
|
||||
"topRight": "Top Right",
|
||||
"bottomLeft": "Bottom Left",
|
||||
"bottomRight": "Bottom Right"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "Gráfico de tráfico",
|
||||
"memoryUsage": "Uso de memoria del núcleo",
|
||||
"proxyGroupIcon": "Icono del grupo de proxy",
|
||||
"toastPosition": "Posición del aviso",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "Icono de la barra de navegación",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "Icono monocromo",
|
||||
"colorful": "Icono colorido",
|
||||
"disable": "Deshabilitar"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "Arriba a la izquierda",
|
||||
"topRight": "Arriba a la derecha",
|
||||
"bottomLeft": "Abajo a la izquierda",
|
||||
"bottomRight": "Abajo a la derecha"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "نمودار ترافیک",
|
||||
"memoryUsage": "استفاده از حافظه",
|
||||
"proxyGroupIcon": "آیکون گروه پراکسی",
|
||||
"toastPosition": "Toast Position",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "آیکون ناوبری",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "تک رنگ",
|
||||
"colorful": "رنگارنگ",
|
||||
"disable": "غیرفعال کردن"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "Top Left",
|
||||
"topRight": "Top Right",
|
||||
"bottomLeft": "Bottom Left",
|
||||
"bottomRight": "Bottom Right"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "Grafik Lalu Lintas",
|
||||
"memoryUsage": "Penggunaan Memori",
|
||||
"proxyGroupIcon": "Ikon Grup Proksi",
|
||||
"toastPosition": "Posisi toast",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "Ikon Navigasi",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "Monokrom",
|
||||
"colorful": "Berwarna",
|
||||
"disable": "Nonaktifkan"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "Kiri atas",
|
||||
"topRight": "Kanan atas",
|
||||
"bottomLeft": "Kiri bawah",
|
||||
"bottomRight": "Kanan bawah"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "トラフィックグラフ",
|
||||
"memoryUsage": "コアメモリ使用量",
|
||||
"proxyGroupIcon": "プロキシグループアイコン",
|
||||
"toastPosition": "トーストの表示位置",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "ナビゲーションバーアイコン",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "モノクロアイコン",
|
||||
"colorful": "カラーアイコン",
|
||||
"disable": "無効にする"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "左上",
|
||||
"topRight": "右上",
|
||||
"bottomLeft": "左下",
|
||||
"bottomRight": "右下"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "트래픽 그래프",
|
||||
"memoryUsage": "메모리 사용량",
|
||||
"proxyGroupIcon": "프록시 그룹 아이콘",
|
||||
"toastPosition": "토스트 위치",
|
||||
"hoverNavigator": "호버 점프 내비게이터",
|
||||
"hoverNavigatorDelay": "호버 점프 내비게이터 지연",
|
||||
"navIcon": "내비게이션 아이콘",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "단색",
|
||||
"colorful": "컬러",
|
||||
"disable": "비활성화"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "왼쪽 상단",
|
||||
"topRight": "오른쪽 상단",
|
||||
"bottomLeft": "왼쪽 하단",
|
||||
"bottomRight": "오른쪽 하단"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "График трафика",
|
||||
"memoryUsage": "Использование памяти",
|
||||
"proxyGroupIcon": "Иконка Группы прокси",
|
||||
"toastPosition": "Расположение уведомлений",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "Иконки навигации",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "Монохромные",
|
||||
"colorful": "Цветные",
|
||||
"disable": "Отключить"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "Сверху слева",
|
||||
"topRight": "Сверху справа",
|
||||
"bottomLeft": "Снизу слева",
|
||||
"bottomRight": "Снизу справа"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "Trafik Grafiği",
|
||||
"memoryUsage": "Çekirdek Kullanımı",
|
||||
"proxyGroupIcon": "Vekil Grup Simgesi",
|
||||
"toastPosition": "Toast konumu",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "Gezinme Simgesi",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "Tek Renkli",
|
||||
"colorful": "Renkli",
|
||||
"disable": "Devre Dışı Bırak"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "Sol üst",
|
||||
"topRight": "Sağ üst",
|
||||
"bottomLeft": "Sol alt",
|
||||
"bottomRight": "Sağ alt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "Трафик графигы",
|
||||
"memoryUsage": "Хәтер куллану",
|
||||
"proxyGroupIcon": "Прокси төркеме иконкасы",
|
||||
"toastPosition": "Toast Position",
|
||||
"hoverNavigator": "Hover Jump Navigator",
|
||||
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
|
||||
"navIcon": "Навигация иконкасы",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "Монохром",
|
||||
"colorful": "Төсле",
|
||||
"disable": "Сүндерү"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "Top Left",
|
||||
"topRight": "Top Right",
|
||||
"bottomLeft": "Bottom Left",
|
||||
"bottomRight": "Bottom Right"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "流量图显",
|
||||
"memoryUsage": "内核占用",
|
||||
"proxyGroupIcon": "代理组图标",
|
||||
"toastPosition": "通知位置",
|
||||
"hoverNavigator": "悬浮跳转导航",
|
||||
"hoverNavigatorDelay": "悬浮跳转导航延迟",
|
||||
"navIcon": "导航栏图标",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "单色图标",
|
||||
"colorful": "彩色图标",
|
||||
"disable": "禁用"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "左上角",
|
||||
"topRight": "右上角",
|
||||
"bottomLeft": "左下角",
|
||||
"bottomRight": "右下角"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"trafficGraph": "流量圖表",
|
||||
"memoryUsage": "內核佔用",
|
||||
"proxyGroupIcon": "代理組圖示",
|
||||
"toastPosition": "通知位置",
|
||||
"hoverNavigator": "懸浮跳轉導航",
|
||||
"hoverNavigatorDelay": "懸浮跳轉導航延遲",
|
||||
"navIcon": "導覽列圖示",
|
||||
@@ -225,6 +226,12 @@
|
||||
"monochrome": "單色圖示",
|
||||
"colorful": "彩色圖示",
|
||||
"disable": "停用"
|
||||
},
|
||||
"toastPosition": {
|
||||
"topLeft": "左上角",
|
||||
"topRight": "右上角",
|
||||
"bottomLeft": "左下角",
|
||||
"bottomRight": "右下角"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ const Layout = () => {
|
||||
>
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* 左侧底部窗口控制按钮 */}
|
||||
<NoticeManager />
|
||||
<NoticeManager position={verge?.notice_position} />
|
||||
<div
|
||||
style={{
|
||||
animation: "fadeIn 0.5s",
|
||||
|
||||
@@ -425,6 +425,7 @@ export const translationKeys = [
|
||||
"settings.components.verge.layout.fields.trafficGraph",
|
||||
"settings.components.verge.layout.fields.memoryUsage",
|
||||
"settings.components.verge.layout.fields.proxyGroupIcon",
|
||||
"settings.components.verge.layout.fields.toastPosition",
|
||||
"settings.components.verge.layout.fields.hoverNavigator",
|
||||
"settings.components.verge.layout.fields.hoverNavigatorDelay",
|
||||
"settings.components.verge.layout.fields.navIcon",
|
||||
@@ -440,6 +441,10 @@ export const translationKeys = [
|
||||
"settings.components.verge.layout.options.icon.monochrome",
|
||||
"settings.components.verge.layout.options.icon.colorful",
|
||||
"settings.components.verge.layout.options.icon.disable",
|
||||
"settings.components.verge.layout.options.toastPosition.topLeft",
|
||||
"settings.components.verge.layout.options.toastPosition.topRight",
|
||||
"settings.components.verge.layout.options.toastPosition.bottomLeft",
|
||||
"settings.components.verge.layout.options.toastPosition.bottomRight",
|
||||
"settings.modals.clashPort.title",
|
||||
"settings.modals.clashPort.fields.mixed",
|
||||
"settings.modals.clashPort.fields.socks",
|
||||
|
||||
@@ -627,6 +627,7 @@ export interface TranslationResources {
|
||||
proxyGroupIcon: string;
|
||||
showProxyGroupsInline: string;
|
||||
systemProxyTrayIcon: string;
|
||||
toastPosition: string;
|
||||
trafficGraph: string;
|
||||
trayIcon: string;
|
||||
tunTrayIcon: string;
|
||||
@@ -637,6 +638,12 @@ export interface TranslationResources {
|
||||
disable: string;
|
||||
monochrome: string;
|
||||
};
|
||||
toastPosition: {
|
||||
bottomLeft: string;
|
||||
bottomRight: string;
|
||||
topLeft: string;
|
||||
topRight: string;
|
||||
};
|
||||
};
|
||||
title: string;
|
||||
tooltips: {
|
||||
|
||||
1
src/types/global.d.ts
vendored
1
src/types/global.d.ts
vendored
@@ -814,6 +814,7 @@ interface IVergeConfig {
|
||||
enable_group_icon?: boolean;
|
||||
menu_icon?: "monochrome" | "colorful" | "disable";
|
||||
menu_order?: string[];
|
||||
notice_position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
||||
tray_icon?: "monochrome" | "colorful";
|
||||
common_tray_icon?: boolean;
|
||||
sysproxy_tray_icon?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user