mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-23 18:10:26 +08:00
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
This commit is contained in:
parent
499fb306f6
commit
d4fe5dfa32
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<BrowserWindow, { onFocusHandler: () => 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 => {
|
||||
|
||||
26
src/main/utils/zoom.ts
Normal file
26
src/main/utils/zoom.ts
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "リセット"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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": "Сбросить"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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 = () => {
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.zoom.title')}</SettingRowTitle>
|
||||
<Select style={{ width: 120 }} value={zoomFactor} onChange={setZoomFactor} options={ZOOM_OPTIONS} />
|
||||
<ZoomButtonGroup>
|
||||
<Button onClick={() => handleZoomFactor(-0.1)} icon={<Minus size="14" />} />
|
||||
<ZoomValue>{Math.round(currentZoom * 100)}%</ZoomValue>
|
||||
<Button onClick={() => handleZoomFactor(0.1)} icon={<Plus size="14" />} />
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleZoomFactor(0, true)
|
||||
}}
|
||||
style={{ marginLeft: 8 }}>
|
||||
{t('settings.zoom.reset')}
|
||||
</Button>
|
||||
</ZoomButtonGroup>
|
||||
</SettingRow>
|
||||
{isMac && (
|
||||
<>
|
||||
@ -221,5 +256,16 @@ const ResetButtonWrapper = styled.div`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
const ZoomButtonGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 210px;
|
||||
`
|
||||
const ZoomValue = styled.span`
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
margin: 0 5px;
|
||||
`
|
||||
|
||||
export default DisplaySettings
|
||||
|
||||
@ -130,7 +130,6 @@ export interface SettingsState {
|
||||
siyuan: boolean
|
||||
docx: boolean
|
||||
}
|
||||
zoomFactor: number
|
||||
}
|
||||
|
||||
export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid'
|
||||
@ -236,8 +235,7 @@ export const initialState: SettingsState = {
|
||||
obsidian: true,
|
||||
siyuan: true,
|
||||
docx: true
|
||||
},
|
||||
zoomFactor: 1
|
||||
}
|
||||
}
|
||||
|
||||
const settingsSlice = createSlice({
|
||||
@ -515,9 +513,6 @@ const settingsSlice = createSlice({
|
||||
},
|
||||
setEnableBackspaceDeleteModel: (state, action: PayloadAction<boolean>) => {
|
||||
state.enableBackspaceDeleteModel = action.payload
|
||||
},
|
||||
setZoomFactor: (state, action: PayloadAction<number>) => {
|
||||
state.zoomFactor = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -611,8 +606,7 @@ export const {
|
||||
setEnableDataCollection,
|
||||
setEnableQuickPanelTriggers,
|
||||
setExportMenuOptions,
|
||||
setEnableBackspaceDeleteModel,
|
||||
setZoomFactor
|
||||
setEnableBackspaceDeleteModel
|
||||
} = settingsSlice.actions
|
||||
|
||||
export default settingsSlice.reducer
|
||||
|
||||
Loading…
Reference in New Issue
Block a user