import { DEFAULT_KEYBINDINGS } from '../config/definitions/shared'; import type { ConfigSettingsField } from '../types/settings'; import { buildMpvKeybindingConfigValue, createMpvKeybindingRows, keyboardEventToConfigKey, parseMpvCommandText, type KeyInputMode, type MpvKeybindingRow, } from './key-input'; import type { SettingsControlContext } from './settings-control-context'; import { createElement } from './settings-control-dom'; let activeKeyLearningStop: (() => void) | null = null; function startKeyLearning( button: HTMLButtonElement, mode: KeyInputMode, onValue: (value: string) => void, ): void { activeKeyLearningStop?.(); const previousText = button.textContent ?? ''; button.textContent = 'Press Keys...'; button.classList.add('learning'); let onKeyDown: (event: KeyboardEvent) => void; let onBlur: () => void; let onMouseDown: (event: MouseEvent) => void; const stop = (): void => { window.removeEventListener('keydown', onKeyDown, true); window.removeEventListener('blur', onBlur, true); window.removeEventListener('mousedown', onMouseDown, true); button.classList.remove('learning'); if (button.textContent === 'Press Keys...') { button.textContent = previousText; } if (activeKeyLearningStop === stop) { activeKeyLearningStop = null; } }; onKeyDown = (event: KeyboardEvent): void => { if (event.key === 'Escape') { stop(); return; } event.preventDefault(); event.stopPropagation(); const next = keyboardEventToConfigKey(event, mode); if (!next) return; stop(); onValue(next); }; onBlur = (): void => stop(); onMouseDown = (event: MouseEvent): void => { if (event.target !== button) { stop(); } }; window.addEventListener('keydown', onKeyDown, true); window.addEventListener('blur', onBlur, true); window.addEventListener('mousedown', onMouseDown, true); activeKeyLearningStop = stop; } function renderKeyLearnButton( value: string, mode: KeyInputMode, onValue: (value: string) => void, ): HTMLButtonElement { const button = createElement('button', 'key-learn-button') as HTMLButtonElement; button.type = 'button'; button.textContent = value || 'Unset'; button.addEventListener('click', () => startKeyLearning(button, mode, (next) => { button.textContent = next; onValue(next); }), ); return button; } export function renderKeyboardInput( context: SettingsControlContext, field: ConfigSettingsField, mode: KeyInputMode, ): HTMLElement { const value = context.valueForField(field); return renderKeyLearnButton(typeof value === 'string' ? value : '', mode, (next) => { context.updateDraft(field.configPath, next); }); } function applyMpvRows( context: SettingsControlContext, field: ConfigSettingsField, rows: MpvKeybindingRow[], ): void { context.updateDraft(field.configPath, buildMpvKeybindingConfigValue(DEFAULT_KEYBINDINGS, rows)); } export function renderMpvKeybindingsInput( context: SettingsControlContext, field: ConfigSettingsField, ): HTMLElement { const rows = createMpvKeybindingRows(DEFAULT_KEYBINDINGS, context.valueForField(field)); const container = createElement('div', 'keybinding-editor'); for (const row of rows) { const item = createElement('div', 'keybinding-row'); const keyButton = renderKeyLearnButton(row.key, 'dom-code', (next) => { row.key = next; applyMpvRows(context, field, rows); }); const command = createElement('input', 'config-input mono-input') as HTMLInputElement; command.type = 'text'; command.value = row.commandText; command.placeholder = '["cycle","pause"]'; command.addEventListener('input', () => { const parsed = parseMpvCommandText(command.value); if (parsed === undefined) { command.classList.add('invalid'); context.setFieldError(field.configPath, 'Invalid MPV command JSON'); return; } command.classList.remove('invalid'); context.setFieldError(field.configPath, null); row.command = parsed; row.commandText = command.value; applyMpvRows(context, field, rows); }); item.append(keyButton, command); container.append(item); } return container; }