From d4fe5dfa326d01f6fa184ef9cd3c84790432d510 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sun, 11 May 2025 18:38:31 +0800 Subject: [PATCH] refactor: update zoom handling in IPC and settings (#5868) * refactor: update zoom handling in IPC and settings * renamed zoom-related IPC channels for clarity * refactored zoom handling logic in ipc.ts and ShortcutService * removed unused zoom factor state management from settings * updated DisplaySettings component to manage zoom via buttons * localized zoom settings in multiple languages * refactor: remove unused zoom factor state from settings * eliminated the zoomFactor state from initialSettings as part of the zoom handling refactor * refactor: improve zoom handling in DisplaySettings component * initialized current zoom value on component mount * refactored resize event listener to prevent memory leaks * added cleanup for resize event listener to enhance performance * refactor: enhance resize event handling in DisplaySettings component * added logic to track previous window width to prevent unnecessary updates during resize events * improved comments for clarity on zoom handling and resize event listener functionality * refactor: streamline zoom handling across IPC and ShortcutService * updated handleZoomFactor function to accept an array of BrowserWindows for batch processing * simplified zoom factor adjustments in IPC and shortcut handlers * removed unnecessary window destruction checks for improved performance --- packages/shared/IpcChannel.ts | 3 +- src/main/ipc.ts | 12 ++-- src/main/services/ShortcutService.ts | 52 ++-------------- src/main/utils/zoom.ts | 26 ++++++++ src/preload/index.ts | 14 +---- src/renderer/src/hooks/useAppInit.ts | 25 +------- src/renderer/src/hooks/useSettings.ts | 7 +-- src/renderer/src/i18n/locales/en-us.json | 1 - src/renderer/src/i18n/locales/ja-jp.json | 5 +- src/renderer/src/i18n/locales/ru-ru.json | 5 +- src/renderer/src/i18n/locales/zh-cn.json | 5 +- src/renderer/src/i18n/locales/zh-tw.json | 5 +- .../DisplaySettings/DisplaySettings.tsx | 60 ++++++++++++++++--- src/renderer/src/store/settings.ts | 10 +--- 14 files changed, 110 insertions(+), 120 deletions(-) create mode 100644 src/main/utils/zoom.ts diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 38aadf9233..1dd61c6364 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -13,8 +13,7 @@ export enum IpcChannel { App_RestartTray = 'app:restart-tray', App_SetTheme = 'app:set-theme', App_SetAutoUpdate = 'app:set-auto-update', - App_SetZoomFactor = 'app:set-zoom-factor', - ZoomFactorUpdated = 'app:zoom-factor-updated', + App_HandleZoomFactor = 'app:handle-zoom-factor', App_IsBinaryExist = 'app:is-binary-exist', App_GetBinaryPath = 'app:get-binary-path', diff --git a/src/main/ipc.ts b/src/main/ipc.ts index e26b623198..d628dd45a5 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -3,6 +3,7 @@ import { arch } from 'node:os' import { isMac, isWin } from '@main/constant' import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process' +import { handleZoomFactor } from '@main/utils/zoom' import { IpcChannel } from '@shared/IpcChannel' import { Shortcut, ThemeMode } from '@types' import { BrowserWindow, ipcMain, nativeTheme, session, shell } from 'electron' @@ -141,15 +142,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { notifyThemeChange() }) - // zoom factor - ipcMain.handle(IpcChannel.App_SetZoomFactor, (_, factor: number) => { - configManager.setZoomFactor(factor) + ipcMain.handle(IpcChannel.App_HandleZoomFactor, (_, delta: number, reset: boolean = false) => { const windows = BrowserWindow.getAllWindows() - windows.forEach((win) => { - if (!win.isDestroyed()) { - win.webContents.setZoomFactor(factor) - } - }) + handleZoomFactor(windows, delta, reset) + return configManager.getZoomFactor() }) // clear cache diff --git a/src/main/services/ShortcutService.ts b/src/main/services/ShortcutService.ts index c384f57fa9..d69c80b325 100644 --- a/src/main/services/ShortcutService.ts +++ b/src/main/services/ShortcutService.ts @@ -1,5 +1,4 @@ -import { ZOOM_LEVELS } from '@shared/config/constant' -import { IpcChannel } from '@shared/IpcChannel' +import { handleZoomFactor } from '@main/utils/zoom' import { Shortcut } from '@types' import { BrowserWindow, globalShortcut } from 'electron' import Logger from 'electron-log' @@ -16,14 +15,11 @@ const windowOnHandlers = new Map void; on function getShortcutHandler(shortcut: Shortcut) { switch (shortcut.key) { case 'zoom_in': - return (window: BrowserWindow) => handleZoom(1)(window) + return (window: BrowserWindow) => handleZoomFactor([window], 0.1) case 'zoom_out': - return (window: BrowserWindow) => handleZoom(-1)(window) + return (window: BrowserWindow) => handleZoomFactor([window], -0.1) case 'zoom_reset': - return (window: BrowserWindow) => { - window.webContents.setZoomFactor(1) - configManager.setZoomFactor(1) - } + return (window: BrowserWindow) => handleZoomFactor([window], 0, true) case 'show_app': return () => { windowService.toggleMainWindow() @@ -41,46 +37,6 @@ function formatShortcutKey(shortcut: string[]): string { return shortcut.join('+') } -function handleZoom(delta: number) { - return (window: BrowserWindow) => { - const currentZoom = configManager.getZoomFactor() - let currentIndex = ZOOM_LEVELS.indexOf(currentZoom) - - // 如果当前缩放比例不在预设列表中,找到最接近的 - if (currentIndex === -1) { - let closestIndex = 0 - let minDiff = Math.abs(ZOOM_LEVELS[0] - currentZoom) - for (let i = 1; i < ZOOM_LEVELS.length; i++) { - const diff = Math.abs(ZOOM_LEVELS[i] - currentZoom) - if (diff < minDiff) { - minDiff = diff - closestIndex = i - } - } - currentIndex = closestIndex - } - - let nextIndex = currentIndex + delta - - // 边界检查 - if (nextIndex < 0) { - nextIndex = 0 // 已经是最小值 - } else if (nextIndex >= ZOOM_LEVELS.length) { - nextIndex = ZOOM_LEVELS.length - 1 // 已经是最大值 - } - - const newZoom = ZOOM_LEVELS[nextIndex] - - if (newZoom !== currentZoom) { - // 只有在实际改变时才更新 - configManager.setZoomFactor(newZoom) - // 通知所有渲染进程更新 zoomFactor - window.webContents.setZoomFactor(newZoom) - window.webContents.send(IpcChannel.ZoomFactorUpdated, newZoom) - } - } -} - const convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat = ( shortcut: string | string[] ): string => { diff --git a/src/main/utils/zoom.ts b/src/main/utils/zoom.ts new file mode 100644 index 0000000000..8e7145313c --- /dev/null +++ b/src/main/utils/zoom.ts @@ -0,0 +1,26 @@ +import { BrowserWindow } from 'electron' + +import { configManager } from '../services/ConfigManager' + +export function handleZoomFactor(wins: BrowserWindow[], delta: number, reset: boolean = false) { + if (reset) { + wins.forEach((win) => { + win.webContents.setZoomFactor(1) + }) + configManager.setZoomFactor(1) + return + } + + if (delta === 0) { + return + } + + const currentZoom = configManager.getZoomFactor() + const newZoom = Number((currentZoom + delta).toFixed(1)) + if (newZoom >= 0.5 && newZoom <= 2.0) { + wins.forEach((win) => { + win.webContents.setZoomFactor(newZoom) + }) + configManager.setZoomFactor(newZoom) + } +} diff --git a/src/preload/index.ts b/src/preload/index.ts index 425a052c42..eeea6ec3de 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -19,7 +19,8 @@ const api = { setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive), restartTray: () => ipcRenderer.invoke(IpcChannel.App_RestartTray), setTheme: (theme: 'light' | 'dark' | 'auto') => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme), - setZoomFactor: (factor: number) => ipcRenderer.invoke(IpcChannel.App_SetZoomFactor, factor), + handleZoomFactor: (delta: number, reset: boolean = false) => + ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset), setAutoUpdate: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetAutoUpdate, isActive), openWebsite: (url: string) => ipcRenderer.invoke(IpcChannel.Open_Website, url), clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache), @@ -192,17 +193,6 @@ const api = { subscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Subscribe), unsubscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Unsubscribe), onUpdate: (action: any) => ipcRenderer.invoke(IpcChannel.StoreSync_OnUpdate, action) - }, - // 新增:监听主进程的 zoom factor 更新 - onZoomFactorUpdate: (callback: (factor: number) => void) => { - const listener = (_event: Electron.IpcRendererEvent, factor: number) => { - callback(factor) - } - ipcRenderer.on(IpcChannel.ZoomFactorUpdated, listener) - // 返回一个移除监听器的函数 - return () => { - ipcRenderer.removeListener(IpcChannel.ZoomFactorUpdated, listener) - } } } diff --git a/src/renderer/src/hooks/useAppInit.ts b/src/renderer/src/hooks/useAppInit.ts index 29108a42c2..b31c325400 100644 --- a/src/renderer/src/hooks/useAppInit.ts +++ b/src/renderer/src/hooks/useAppInit.ts @@ -18,17 +18,7 @@ import useUpdateHandler from './useUpdateHandler' export function useAppInit() { const dispatch = useAppDispatch() - const { - proxyUrl, - language, - windowStyle, - autoCheckUpdate, - proxyMode, - customCss, - enableDataCollection, - setZoomFactor, - zoomFactor - } = useSettings() + const { proxyUrl, language, windowStyle, autoCheckUpdate, proxyMode, customCss, enableDataCollection } = useSettings() const { minappShow } = useRuntime() const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel() const avatar = useLiveQuery(() => db.settings.get('image://avatar')) @@ -41,19 +31,6 @@ export function useAppInit() { avatar?.value && dispatch(setAvatar(avatar.value)) }, [avatar, dispatch]) - useEffect(() => { - const removeZoomListener = window.api.onZoomFactorUpdate((factor) => { - setZoomFactor(factor) - }) - return () => { - removeZoomListener() - } - }, [setZoomFactor]) - - useEffect(() => { - setZoomFactor(zoomFactor) - }, [setZoomFactor, zoomFactor]) - useEffect(() => { document.getElementById('spinner')?.remove() runAsyncFunction(async () => { diff --git a/src/renderer/src/hooks/useSettings.ts b/src/renderer/src/hooks/useSettings.ts index 4e1e9e9599..ba6185aba9 100644 --- a/src/renderer/src/hooks/useSettings.ts +++ b/src/renderer/src/hooks/useSettings.ts @@ -14,8 +14,7 @@ import { setTopicPosition, setTray as _setTray, setTrayOnClose, - setWindowStyle, - setZoomFactor + setWindowStyle } from '@renderer/store/settings' import { SidebarIcon, ThemeMode, TranslateLanguageVarious } from '@renderer/types' @@ -80,10 +79,6 @@ export function useSettings() { }, setAssistantIconType(assistantIconType: AssistantIconType) { dispatch(setAssistantIconType(assistantIconType)) - }, - setZoomFactor(factor: number) { - dispatch(setZoomFactor(factor)) - window.api.setZoomFactor(factor) } } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index d5500fac41..a7a6e0f0c0 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1546,7 +1546,6 @@ "theme.window.style.opaque": "Opaque Window", "theme.window.style.title": "Window Style", "theme.window.style.transparent": "Transparent Window", - "zoom.title": "Page Zoom", "title": "Settings", "topic.position": "Topic position", "topic.position.left": "Left", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index a87d01d6ce..d168e8b580 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1544,7 +1544,6 @@ "theme.window.style.opaque": "不透明ウィンドウ", "theme.window.style.title": "ウィンドウスタイル", "theme.window.style.transparent": "透明ウィンドウ", - "zoom.title": "ページズーム", "title": "設定", "topic.position": "トピックの位置", "topic.position.left": "左", @@ -1662,6 +1661,10 @@ "quit": "終了", "show_window": "ウィンドウを表示", "visualization": "可視化" + }, + "zoom": { + "title": "ズーム", + "reset": "リセット" } } } \ No newline at end of file diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index aa1d22cf46..c3b3483609 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1544,7 +1544,6 @@ "theme.window.style.opaque": "Непрозрачное окно", "theme.window.style.title": "Стиль окна", "theme.window.style.transparent": "Прозрачное окно", - "zoom.title": "Масштаб страницы", "title": "Настройки", "topic.position": "Позиция топиков", "topic.position.left": "Слева", @@ -1662,6 +1661,10 @@ "quit": "Выйти", "show_window": "Показать окно", "visualization": "Визуализация" + }, + "zoom": { + "title": "Масштаб", + "reset": "Сбросить" } } } diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 6f68ce6f68..17b88e1e65 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1546,7 +1546,6 @@ "theme.window.style.opaque": "不透明窗口", "theme.window.style.title": "窗口样式", "theme.window.style.transparent": "透明窗口", - "zoom.title": "页面缩放", "title": "设置", "topic.position": "话题位置", "topic.position.left": "左侧", @@ -1618,6 +1617,10 @@ "privacy": { "title": "隐私设置", "enable_privacy_mode": "匿名发送错误报告和数据统计" + }, + "zoom": { + "title": "缩放", + "reset": "重置" } }, "translate": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 3d013c5f14..58c52aa7d8 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1545,7 +1545,6 @@ "theme.window.style.opaque": "不透明視窗", "theme.window.style.title": "視窗樣式", "theme.window.style.transparent": "透明視窗", - "zoom.title": "頁面縮放", "title": "設定", "topic.position": "話題位置", "topic.position.left": "左側", @@ -1618,6 +1617,10 @@ "privacy": { "title": "隱私設定", "enable_privacy_mode": "匿名發送錯誤報告和資料統計" + }, + "zoom": { + "title": "縮放", + "reset": "重置" } }, "translate": { diff --git a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx index 7db2ec0889..335f8ecbf4 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx @@ -13,9 +13,9 @@ import { setSidebarIcons } from '@renderer/store/settings' import { ThemeMode } from '@renderer/types' -import { ZOOM_OPTIONS } from '@shared/config/constant' -import { Button, Input, Segmented, Select, Switch } from 'antd' -import { FC, useCallback, useMemo, useState } from 'react' +import { Button, Input, Segmented, Switch } from 'antd' +import { Minus, Plus } from 'lucide-react' +import { FC, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -34,13 +34,12 @@ const DisplaySettings: FC = () => { showTopicTime, customCss, sidebarIcons, - assistantIconType, - zoomFactor, - setZoomFactor + assistantIconType } = useSettings() const { theme: themeMode } = useTheme() const { t } = useTranslation() const dispatch = useAppDispatch() + const [currentZoom, setCurrentZoom] = useState(1.0) const [visibleIcons, setVisibleIcons] = useState(sidebarIcons?.visible || DEFAULT_SIDEBAR_ICONS) const [disabledIcons, setDisabledIcons] = useState(sidebarIcons?.disabled || []) @@ -91,6 +90,31 @@ const DisplaySettings: FC = () => { [t] ) + useEffect(() => { + // 初始化获取当前缩放值 + window.api.handleZoomFactor(0).then((factor) => { + setCurrentZoom(factor) + }) + + const handleResize = () => { + window.api.handleZoomFactor(0).then((factor) => { + setCurrentZoom(factor) + }) + } + // 添加resize事件监听 + window.addEventListener('resize', handleResize) + + // 清理事件监听,防止内存泄漏 + return () => { + window.removeEventListener('resize', handleResize) + } + }, []) + + const handleZoomFactor = async (delta: number, reset: boolean = false) => { + const zoomFactor = await window.api.handleZoomFactor(delta, reset) + setCurrentZoom(zoomFactor) + } + const assistantIconTypeOptions = useMemo( () => [ { value: 'model', label: t('settings.assistant.icon.type.model') }, @@ -112,7 +136,18 @@ const DisplaySettings: FC = () => { {t('settings.zoom.title')} -