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>
197 lines
5.1 KiB
TypeScript
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)
|
|
}
|