14 phase updated

This commit is contained in:
yunchan8804 2026-04-08 00:14:12 +09:00
parent 023714442d
commit dd68fc9ccd
3 changed files with 1003 additions and 25 deletions

View file

@ -85,6 +85,10 @@ export function HotkeyRecordModal({
const [captured, setCaptured] = useState<Array<{ keyCode: number; name: string }> | null>(null)
const [error, setError] = useState<string | null>(null)
const pressedRef = useRef<Map<number, string>>(new Map())
// modifier-only 확정을 위한 타이머 (Alt만 눌렀을 때 바로 확정하지 않고 잠시 대기)
const modifierTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
// 가장 최근 pressedKeys 스냅샷 (타이머 콜백에서 stale closure 방지)
const lastPressedRef = useRef<Array<{ keyCode: number; name: string }>>([])
// Modal 열릴 때 리셋
useEffect(() => {
@ -93,6 +97,11 @@ export function HotkeyRecordModal({
setCaptured(null)
setError(null)
pressedRef.current.clear()
lastPressedRef.current = []
if (modifierTimerRef.current) {
clearTimeout(modifierTimerRef.current)
modifierTimerRef.current = null
}
}
}, [open])
@ -101,6 +110,12 @@ export function HotkeyRecordModal({
e.preventDefault()
e.stopPropagation()
// 새 키가 눌렸으므로 modifier-only 타이머 취소
if (modifierTimerRef.current) {
clearTimeout(modifierTimerRef.current)
modifierTimerRef.current = null
}
const keyCode = e.keyCode || e.which
if (pressedRef.current.has(keyCode)) return
@ -109,8 +124,9 @@ export function HotkeyRecordModal({
const keys = Array.from(pressedRef.current.entries()).map(([kc, n]) => ({ keyCode: kc, name: n }))
setPressedKeys(keys)
lastPressedRef.current = keys
// modifier가 아닌 키가 눌리면 → 조합 확정
// modifier가 아닌 키가 눌리면 → 조합 즉시 확정 (Alt+1, Ctrl+F5 등)
if (!isModifier(keyCode)) {
setCaptured(keys)
}
@ -122,14 +138,20 @@ export function HotkeyRecordModal({
const keyCode = e.keyCode || e.which
pressedRef.current.delete(keyCode)
// 모든 키를 놓았고 modifier만 눌렀었다면 → 단일 modifier 확정
if (pressedRef.current.size === 0 && pressedKeys.length > 0 && pressedKeys.every(k => isModifier(k.keyCode))) {
setCaptured(pressedKeys)
// 모든 키를 놓았고 modifier만 눌렀었다면 → 500ms 대기 후 확정
// 이 대기 시간 동안 추가 키를 누르면 타이머가 취소되어 조합키로 확장 가능
if (pressedRef.current.size === 0 && lastPressedRef.current.length > 0 && lastPressedRef.current.every(k => isModifier(k.keyCode))) {
if (modifierTimerRef.current) clearTimeout(modifierTimerRef.current)
modifierTimerRef.current = setTimeout(() => {
// 타이머 만료: 여전히 confirmed 안됐고 추가 키 입력 없으면 단일 modifier로 확정
setCaptured(lastPressedRef.current)
modifierTimerRef.current = null
}, 500)
}
const keys = Array.from(pressedRef.current.entries()).map(([kc, n]) => ({ keyCode: kc, name: n }))
setPressedKeys(keys)
}, [captured, pressedKeys])
}, [captured])
useEffect(() => {
if (!open) return
@ -159,15 +181,27 @@ export function HotkeyRecordModal({
}
// HotkeyBinding 생성
// 주 키(main key) = modifier가 아닌 키가 있으면 그것, 없으면 첫 번째 modifier
const mainKey = captured.find(k => !isModifier(k.keyCode))
const primaryKeyCode = mainKey?.keyCode ?? captured[0].keyCode
// modifier 플래그: 주 키 자체가 해당 modifier인 경우 false 유지
// 예: Right Alt 단독 → keyCode=165, alt=false (주 키가 Alt 자체이므로)
// 예: Ctrl+A → keyCode=A, ctrl=true (Ctrl은 modifier로 사용)
const isCtrlKey = (kc: number) => kc === 17 || kc === 162 || kc === 163
const isAltKey = (kc: number) => kc === 18 || kc === 164 || kc === 165
const isShiftKey = (kc: number) => kc === 16 || kc === 160 || kc === 161
const isMetaKey = (kc: number) => kc === 91 || kc === 92
// 주 키를 제외한 나머지 키들만 modifier로 취급
const modifierKeys = captured.filter(k => k.keyCode !== primaryKeyCode)
const binding: HotkeyBinding = {
keyCode: primaryKeyCode,
ctrl: captured.some(k => k.keyCode === 17 || k.keyCode === 162 || k.keyCode === 163),
alt: captured.some(k => k.keyCode === 18 || k.keyCode === 164 || k.keyCode === 165),
shift: captured.some(k => k.keyCode === 16 || k.keyCode === 160 || k.keyCode === 161),
meta: captured.some(k => k.keyCode === 91 || k.keyCode === 92),
ctrl: modifierKeys.some(k => isCtrlKey(k.keyCode)),
alt: modifierKeys.some(k => isAltKey(k.keyCode)),
shift: modifierKeys.some(k => isShiftKey(k.keyCode)),
meta: modifierKeys.some(k => isMetaKey(k.keyCode)),
displayLabel: label,
}