feat(v1.2): 体验升级 — 快捷键系统 + 标签拖拽 + SFTP拖拽上传 + 组件拆分 + 响应式
任务1: 全局快捷键管理器 — useKeyboardShortcuts hook Ctrl+Tab/Shift+Tab标签切换, Ctrl+1~9跳转, F11全屏, Ctrl+Shift+N新建标签 任务2: 标签拖拽排序 — tabStore.moveTab + TabBar HTML5 DnD 不可变更新, 拖拽视觉反馈(opacity+指示线), 活跃标签底部accent-blue指示线 任务3: SFTP拖拽上传 + useSFTP调试清理 FileListPanel拖拽覆盖层(Upload图标+虚线边框), 5个调试函数→createLogger, disconnect()正确调用IPC, 删除mock data 任务4: SFTP右键菜单完善 — 重命名/复制路径/属性/权限 FilePropertiesDialog+ChmodDialog, sftpStat/sftpChmod IPC全栈实现 任务5-6: 侧边栏分组折叠动画 + 宽度可拖拽调整 Grid 0fr→1fr折叠动画, ChevronRight旋转, resize handle 180~400px, settings记忆 任务7: 响应式布局 + 视觉优化V1~V5 useMediaQuery hook, <800px侧边栏overlay模式, SFTP垂直堆叠, WelcomeView响应式grid 任务8: Loading/Skeleton骨架屏 Skeleton/SkeletonLine/SkeletonCircle/SkeletonBlock + FileListSkeleton/SessionTreeSkeleton/TabContentSkeleton 任务9: 空状态设计 EmptyState通用组件, SessionTree/FileListPanel/WelcomeView/SFTPView空状态引导 任务10: TerminalView组件拆分重构 711行→285行 useTerminalConnection(300行) + useTerminalSearch(123行) + TerminalOverlay(98行) + TerminalSearchBar(74行) 33 files changed, 2682 insertions(+), 872 deletions(-)
This commit is contained in:
1 parent
d344900118
commit
caba5f4c0d
33 files changed
+2683
-873
No files matched your search
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* useKeyboardShortcuts - 全局快捷键管理 Hook
|
||||
*
|
||||
* 在 App.tsx 挂载,处理全局键盘快捷键:
|
||||
* - Ctrl+Tab / Ctrl+Shift+Tab: 切换标签
|
||||
* - Ctrl+1~9: 跳转到指定索引标签
|
||||
* - Ctrl+W: 关闭当前标签
|
||||
* - F11: 全屏切换
|
||||
* - Ctrl+Shift+N: 新建标签
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useTabStore } from '../stores/tabStore'
|
||||
import { createLogger } from '../utils/logger'
|
||||
|
||||
const logger = createLogger('KeyboardShortcuts')
|
||||
|
||||
/** 检查事件目标是否为输入元素 */
|
||||
function isInputElement(e: KeyboardEvent): boolean {
|
||||
const target = e.target as HTMLElement
|
||||
if (!target) return false
|
||||
const tagName = target.tagName.toLowerCase()
|
||||
return (
|
||||
tagName === 'input' ||
|
||||
tagName === 'textarea' ||
|
||||
tagName === 'select' ||
|
||||
target.isContentEditable
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断快捷键是否应该在输入框中触发
|
||||
* Ctrl+Tab/Ctrl+Shift+Tab 等导航类快捷键始终生效
|
||||
*/
|
||||
function shouldSkipInInput(e: KeyboardEvent): boolean {
|
||||
// Ctrl+Tab 系列:始终生效(浏览器级别标签切换)
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Tab') return false
|
||||
// F11:始终生效
|
||||
if ( e.key === 'F11') return false
|
||||
// 其他快捷键在输入框中不触发
|
||||
return true
|
||||
}
|
||||
|
||||
export function useKeyboardShortcuts(): void {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const ctrlOrMeta = e.ctrlKey || e.metaKey
|
||||
|
||||
// ---- Ctrl+Tab: 切换到下一个标签 ----
|
||||
if (ctrlOrMeta && e.key === 'Tab' && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
const { switchToNextTab } = useTabStore.getState()
|
||||
switchToNextTab()
|
||||
return
|
||||
}
|
||||
|
||||
// ---- Ctrl+Shift+Tab: 切换到上一个标签 ----
|
||||
if (ctrlOrMeta && e.key === 'Tab' && e.shiftKey) {
|
||||
e.preventDefault()
|
||||
const { switchToPrevTab } = useTabStore.getState()
|
||||
switchToPrevTab()
|
||||
return
|
||||
}
|
||||
|
||||
// ---- Ctrl+1~9: 跳转到指定索引标签 ----
|
||||
if (ctrlOrMeta && !e.shiftKey && !e.altKey) {
|
||||
const num = parseInt(e.key, 10)
|
||||
if (num >= 1 && num <= 9) {
|
||||
e.preventDefault()
|
||||
const { activateTabByIndex } = useTabStore.getState()
|
||||
activateTabByIndex(num - 1) // 0-indexed
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ---- F11: 全屏切换 ----
|
||||
if (e.key === 'F11') {
|
||||
e.preventDefault()
|
||||
// Electron 渲染进程通过 IPC 请求全屏切换
|
||||
if (window.electronAPI?.settingsSet) {
|
||||
window.electronAPI.settingsGet('fullscreen').then((res: any) => {
|
||||
const isFullscreen = res?.value === true
|
||||
window.electronAPI?.settingsSet('fullscreen', !isFullscreen)
|
||||
}).catch(() => {
|
||||
logger.warn('Failed to toggle fullscreen')
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ---- 以下快捷键在输入框中不触发 ----
|
||||
if (isInputElement(e) && shouldSkipInInput(e)) {
|
||||
return
|
||||
}
|
||||
|
||||
// ---- Ctrl+W: 关闭当前标签 ----
|
||||
if (ctrlOrMeta && e.key === 'w') {
|
||||
e.preventDefault()
|
||||
const { tabs, activeTabId, closeTab } = useTabStore.getState()
|
||||
if (activeTabId && tabs.length > 1) {
|
||||
logger.debug('Ctrl+W: closing tab', activeTabId)
|
||||
closeTab(activeTabId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ---- Ctrl+Shift+N: 新建标签 ----
|
||||
if (ctrlOrMeta && e.shiftKey && (e.key === 'n' || e.key === 'N')) {
|
||||
e.preventDefault()
|
||||
const { addTab } = useTabStore.getState()
|
||||
addTab({ title: '新标签', type: 'welcome' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [])
|
||||
}
|
||||
|
||||
export default useKeyboardShortcuts
|
||||
Reference in new issue
Block a user