fix: 修复Escape键事件冒泡问题并改进全屏处理

修复多个组件中Escape键事件未阻止冒泡的问题
添加全屏控制IPC通道
将全屏退出逻辑移至渲染进程处理
移除主进程中冗余的全屏退出处理代码
This commit is contained in:
icarus 2025-08-15 14:02:09 +08:00
parent fc174ba4ac
commit f92edadb61
13 changed files with 54 additions and 22 deletions

View File

@ -35,6 +35,7 @@ export enum IpcChannel {
App_InstallBunBinary = 'app:install-bun-binary', App_InstallBunBinary = 'app:install-bun-binary',
App_LogToMain = 'app:log-to-main', App_LogToMain = 'app:log-to-main',
App_SaveData = 'app:save-data', App_SaveData = 'app:save-data',
App_SetFullScreen = 'app:set-full-screen',
App_MacIsProcessTrusted = 'app:mac-is-process-trusted', App_MacIsProcessTrusted = 'app:mac-is-process-trusted',
App_MacRequestProcessTrust = 'app:mac-request-process-trust', App_MacRequestProcessTrust = 'app:mac-request-process-trust',

View File

@ -191,6 +191,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
}) })
} }
ipcMain.handle(IpcChannel.App_SetFullScreen, (_, value: boolean): void => {
mainWindow.setFullScreen(value)
})
ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any, isNotify: boolean = false) => { ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any, isNotify: boolean = false) => {
configManager.set(key, value, isNotify) configManager.set(key, value, isNotify)
}) })

View File

@ -223,26 +223,26 @@ export class WindowService {
}) })
// 添加Escape键退出全屏的支持 // 添加Escape键退出全屏的支持
mainWindow.webContents.on('before-input-event', (event, input) => { // mainWindow.webContents.on('before-input-event', (event, input) => {
// 当按下Escape键且窗口处于全屏状态时退出全屏 // // 当按下Escape键且窗口处于全屏状态时退出全屏
if (input.key === 'Escape' && !input.alt && !input.control && !input.meta && !input.shift) { // if (input.key === 'Escape' && !input.alt && !input.control && !input.meta && !input.shift) {
if (mainWindow.isFullScreen()) { // if (mainWindow.isFullScreen()) {
// 获取 shortcuts 配置 // // 获取 shortcuts 配置
const shortcuts = configManager.getShortcuts() // const shortcuts = configManager.getShortcuts()
const exitFullscreenShortcut = shortcuts.find((s) => s.key === 'exit_fullscreen') // const exitFullscreenShortcut = shortcuts.find((s) => s.key === 'exit_fullscreen')
if (exitFullscreenShortcut == undefined) { // if (exitFullscreenShortcut == undefined) {
mainWindow.setFullScreen(false) // mainWindow.setFullScreen(false)
return // return
} // }
if (exitFullscreenShortcut?.enabled) { // if (exitFullscreenShortcut?.enabled) {
event.preventDefault() // event.preventDefault()
mainWindow.setFullScreen(false) // mainWindow.setFullScreen(false)
return // return
} // }
} // }
} // }
return // return
}) // })
} }
private setupWebContentsHandlers(mainWindow: BrowserWindow) { private setupWebContentsHandlers(mainWindow: BrowserWindow) {

View File

@ -76,6 +76,7 @@ const api = {
clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache), clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache),
logToMain: (source: LogSourceWithContext, level: LogLevel, message: string, data: any[]) => logToMain: (source: LogSourceWithContext, level: LogLevel, message: string, data: any[]) =>
ipcRenderer.invoke(IpcChannel.App_LogToMain, source, level, message, data), ipcRenderer.invoke(IpcChannel.App_LogToMain, source, level, message, data),
setFullScreen: (value: boolean): Promise<void> => ipcRenderer.invoke(IpcChannel.App_SetFullScreen, value),
mac: { mac: {
isProcessTrusted: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacIsProcessTrusted), isProcessTrusted: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacIsProcessTrusted),
requestProcessTrust: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacRequestProcessTrust) requestProcessTrust: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacRequestProcessTrust)

View File

@ -62,6 +62,7 @@ const CollapsibleSearchBar: React.FC<CollapsibleSearchBarProps> = ({ onSearch, i
onChange={(e) => handleTextChange(e.target.value)} onChange={(e) => handleTextChange(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
e.stopPropagation()
handleTextChange('') handleTextChange('')
if (!searchText) setSearchVisible(false) if (!searchText) setSearchVisible(false)
} }

View File

@ -282,6 +282,7 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
implementation.searchNext() implementation.searchNext()
} }
} else if (event.key === 'Escape') { } else if (event.key === 'Escape') {
event.stopPropagation()
implementation.disable() implementation.disable()
} }
}, },

View File

@ -63,6 +63,7 @@ const EditableNumber: FC<EditableNumberProps> = ({
if (e.key === 'Enter') { if (e.key === 'Enter') {
handleBlur() handleBlur()
} else if (e.key === 'Escape') { } else if (e.key === 'Escape') {
e.stopPropagation()
setInputValue(value) setInputValue(value)
setIsEditing(false) setIsEditing(false)
} }

View File

@ -256,6 +256,7 @@ const PopupContainer: React.FC<Props> = ({ model, resolve, modelFilter }) => {
break break
case 'Escape': case 'Escape':
e.preventDefault() e.preventDefault()
e.stopPropagation()
setOpen(false) setOpen(false)
resolve(undefined) resolve(undefined)
break break

View File

@ -456,6 +456,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
} }
break break
case 'Escape': case 'Escape':
e.stopPropagation()
handleClose('esc') handleClose('esc')
break break
} }

View File

@ -1,5 +1,7 @@
import { loggerService } from '@logger'
import TopViewMinappContainer from '@renderer/components/MinApp/TopViewMinappContainer' import TopViewMinappContainer from '@renderer/components/MinApp/TopViewMinappContainer'
import { useAppInit } from '@renderer/hooks/useAppInit' import { useAppInit } from '@renderer/hooks/useAppInit'
import { useShortcuts } from '@renderer/hooks/useShortcuts'
import { message, Modal } from 'antd' import { message, Modal } from 'antd'
import React, { PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react' import React, { PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react'
@ -24,6 +26,8 @@ type ElementItem = {
element: React.FC | React.ReactNode element: React.FC | React.ReactNode
} }
const logger = loggerService.withContext('TopView')
const TopViewContainer: React.FC<Props> = ({ children }) => { const TopViewContainer: React.FC<Props> = ({ children }) => {
const [elements, setElements] = useState<ElementItem[]>([]) const [elements, setElements] = useState<ElementItem[]>([])
const elementsRef = useRef<ElementItem[]>([]) const elementsRef = useRef<ElementItem[]>([])
@ -31,6 +35,8 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
const [messageApi, messageContextHolder] = message.useMessage() const [messageApi, messageContextHolder] = message.useMessage()
const [modal, modalContextHolder] = Modal.useModal() const [modal, modalContextHolder] = Modal.useModal()
const { shortcuts } = useShortcuts()
const enableQuitFullScreen = shortcuts.find((item) => item.key === 'exit_fullscreen')?.enabled
useAppInit() useAppInit()
@ -72,8 +78,20 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
) )
}, []) }, [])
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
logger.debug('keydown', e)
if (!enableQuitFullScreen) return
if (e.key === 'Escape' && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
window.api.setFullScreen(false)
}
},
[enableQuitFullScreen]
)
return ( return (
<> <div onKeyDown={handleKeyDown}>
{children} {children}
{messageContextHolder} {messageContextHolder}
{modalContextHolder} {modalContextHolder}
@ -83,7 +101,7 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
{typeof Element === 'function' ? <Element /> : Element} {typeof Element === 'function' ? <Element /> : Element}
</FullScreenContainer> </FullScreenContainer>
))} ))}
</> </div>
) )
} }

View File

@ -66,6 +66,7 @@ export function useInPlaceEdit(options: UseInPlaceEditOptions): UseInPlaceEditRe
saveEdit() saveEdit()
} else if (e.key === 'Escape') { } else if (e.key === 'Escape') {
e.preventDefault() e.preventDefault()
e.stopPropagation()
cancelEdit() cancelEdit()
} }
}, },

View File

@ -400,6 +400,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
if (expanded) { if (expanded) {
if (event.key === 'Escape') { if (event.key === 'Escape') {
event.stopPropagation()
return onToggleExpanded() return onToggleExpanded()
} }
} }

View File

@ -466,6 +466,7 @@ const ProvidersList: FC = () => {
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
e.stopPropagation()
setSearchText('') setSearchText('')
} }
}} }}