Files
awoooi/apps/web/src/lib/constants/shortcuts.ts
OG T e5ded3b3f2 feat(phase19): OmniTerminal + GenUI + Hybrid SSE 架構實作 (Wave 0-2)
Phase 19 OmniTerminal MVP 完成:
- Wave 0: Backend (Hybrid SSE POST→GET 架構)
- Wave 1: Frontend (OmniTerminal 狀態機 + GenUI Registry)
- Wave 2: UI 組件 (8 個 GenUI 動態卡片)

ADR 文檔:
- ADR-031: OmniTerminal SSE 架構
- ADR-032: GenUI 動態渲染框架
- ADR-033: K3s HA 架構設計

GenUI 組件:
- GenUIRenderer, K8sPodStatusCard, SentryErrorCard
- MetricsSummaryCard, IncidentTimelineCard
- TraceWaterfallCard, ApprovalCard, NuclearKeyButton

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-28 00:17:26 +08:00

197 lines
5.1 KiB
TypeScript

/**
* AWOOOI 快捷鍵系統
* ==========================
* Phase 19.K - Keyboard Shortcuts Architecture
*
* 設計原則:
* 1. 集中管理,禁止散落各處的 keydown listener
* 2. CMD+J 取代 CMD+K (避免瀏覽器 URL 搜索衝突)
* 3. 支援 focus context 判斷 (Terminal 內 vs 外)
*
* @see ADR-031 Omni-Terminal SSE Architecture
* @see docs/meetings/2026-03-27-phase19-omni-terminal-brainstorm.md
*
* @author Claude Code (首席架構師)
* @version 1.0.0
* @date 2026-03-27 (台北時間)
*/
/** 修飾鍵類型 */
export type ModifierKey = 'meta' | 'ctrl' | 'alt' | 'shift'
/** 快捷鍵分類 */
export type ShortcutCategory = 'terminal' | 'navigation' | 'approval' | 'global'
/** 快捷鍵定義 */
export interface ShortcutDefinition {
/** 主鍵 (不含修飾鍵) */
key: string
/** 修飾鍵組合 */
modifiers: ModifierKey[]
/** 顯示文字 */
description: string
/** 對應動作標識 */
action: string
/** 所屬分類 */
category: ShortcutCategory
/** 需要特定 context 才生效 (例如 Terminal 開啟時) */
contextRequired?: string
/** 是否在輸入框內也生效 */
activeInInput?: boolean
}
/**
* 全局快捷鍵定義
*/
export const SHORTCUTS: Record<string, ShortcutDefinition> = {
// =========================================================================
// Terminal 相關
// =========================================================================
TOGGLE_TERMINAL: {
key: 'j',
modifiers: ['meta'],
description: 'Toggle Omni-Terminal',
action: 'terminal.toggle',
category: 'terminal',
},
FOCUS_TERMINAL_INPUT: {
key: 'i',
modifiers: ['meta', 'shift'],
description: 'Focus Terminal Input',
action: 'terminal.focus',
category: 'terminal',
},
ABORT_STREAM: {
key: 'Escape',
modifiers: [],
description: 'Abort Current Stream',
action: 'terminal.abort',
category: 'terminal',
contextRequired: 'terminal',
},
CLOSE_TERMINAL: {
key: 'Escape',
modifiers: [],
description: 'Close Terminal',
action: 'terminal.close',
category: 'terminal',
contextRequired: 'terminal',
},
// =========================================================================
// 導航相關
// =========================================================================
GO_HOME: {
key: 'h',
modifiers: ['meta', 'shift'],
description: 'Go to Dashboard',
action: 'nav.home',
category: 'navigation',
},
GO_INCIDENTS: {
key: '1',
modifiers: ['meta'],
description: 'Go to Incidents',
action: 'nav.incidents',
category: 'navigation',
},
GO_APPROVALS: {
key: '2',
modifiers: ['meta'],
description: 'Go to Approvals',
action: 'nav.approvals',
category: 'navigation',
},
// =========================================================================
// Approval 相關
// =========================================================================
APPROVE_QUICK: {
key: 'y',
modifiers: [],
description: 'Quick Approve (Long Press 2s)',
action: 'approval.approve',
category: 'approval',
contextRequired: 'approval-focus',
},
REJECT_QUICK: {
key: 'n',
modifiers: [],
description: 'Quick Reject',
action: 'approval.reject',
category: 'approval',
contextRequired: 'approval-focus',
},
} as const
/** 快捷鍵名稱 */
export type ShortcutKey = keyof typeof SHORTCUTS
/**
* 檢查事件是否匹配快捷鍵
*/
export function matchShortcut(
event: KeyboardEvent,
shortcut: ShortcutDefinition
): boolean {
// 檢查主鍵
if (event.key.toLowerCase() !== shortcut.key.toLowerCase()) {
return false
}
// 檢查修飾鍵
const hasMeta = shortcut.modifiers.includes('meta')
const hasCtrl = shortcut.modifiers.includes('ctrl')
const hasAlt = shortcut.modifiers.includes('alt')
const hasShift = shortcut.modifiers.includes('shift')
// 支援 Mac (metaKey) 和 Windows (ctrlKey)
const metaOrCtrl = event.metaKey || event.ctrlKey
if (hasMeta && !metaOrCtrl) return false
if (hasCtrl && !event.ctrlKey) return false
if (hasAlt && !event.altKey) return false
if (hasShift && !event.shiftKey) return false
// 確保沒有多餘的修飾鍵
if (!hasMeta && !hasCtrl && metaOrCtrl) return false
if (!hasAlt && event.altKey) return false
if (!hasShift && event.shiftKey) return false
return true
}
/**
* 格式化快捷鍵顯示文字
* @example formatShortcutDisplay(SHORTCUTS.TOGGLE_TERMINAL) // '⌘J'
*/
export function formatShortcutDisplay(shortcut: ShortcutDefinition): string {
const parts: string[] = []
if (shortcut.modifiers.includes('meta')) {
parts.push('⌘')
}
if (shortcut.modifiers.includes('ctrl')) {
parts.push('⌃')
}
if (shortcut.modifiers.includes('alt')) {
parts.push('⌥')
}
if (shortcut.modifiers.includes('shift')) {
parts.push('⇧')
}
parts.push(shortcut.key.toUpperCase())
return parts.join('')
}
/**
* 取得分類下的所有快捷鍵
*/
export function getShortcutsByCategory(
category: ShortcutCategory
): ShortcutDefinition[] {
return Object.values(SHORTCUTS).filter((s) => s.category === category)
}