Files
SubMiner/src/settings/settings-keybinding-controls.ts
T
sudacode a4314ab5de feat(config): reorganize settings window and move annotation colors to subtitleStyle
- Reorganize Configuration window into Appearance, Behavior, Anki, Input, and Integration sections
- Add AnkiConnect-backed deck, note-type, and field pickers in the Anki section
- Add click-to-learn keybinding controls
- Move known-word and N+1 highlight colors to subtitleStyle.knownWordColor / subtitleStyle.nPlusOneColor; legacy ankiConnect.knownWords.color and ankiConnect.nPlusOne.nPlusOne keys still accepted with deprecation warnings
- Add deckNames, modelNames, modelFieldNames, and fieldNamesForDeck methods to AnkiConnectClient
- Mark discordPresence.presenceStyle as an enum in the config registry
2026-05-17 02:39:38 -07:00

139 lines
4.2 KiB
TypeScript

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;
}