Honor configured controller shortcuts and clean up modal opens

This commit is contained in:
2026-04-11 00:52:18 -07:00
committed by sudacode
parent 1d4b3b4ed0
commit 3c711ed00b
49 changed files with 944 additions and 211 deletions

View File

@@ -69,6 +69,10 @@ function installKeyboardTestGlobals() {
markAudioCard: '',
openRuntimeOptions: 'CommandOrControl+Shift+O',
openJimaku: 'Ctrl+Shift+J',
openSessionHelp: 'CommandOrControl+Shift+H',
openControllerSelect: 'Alt+C',
openControllerDebug: 'Alt+Shift+C',
toggleSubtitleSidebar: '',
toggleVisibleOverlayGlobal: '',
};
let markActiveVideoWatchedResult = true;
@@ -321,8 +325,6 @@ function installKeyboardTestGlobals() {
function createKeyboardHandlerHarness() {
const testGlobals = installKeyboardTestGlobals();
const subtitleRootClassList = createClassList();
let controllerSelectOpenCount = 0;
let controllerDebugOpenCount = 0;
let controllerSelectKeydownCount = 0;
let playlistBrowserKeydownCount = 0;
@@ -373,20 +375,12 @@ function createKeyboardHandlerHarness() {
openSessionHelpModal: () => {},
appendClipboardVideoToQueue: () => {},
getPlaybackPaused: () => testGlobals.getPlaybackPaused(),
openControllerSelectModal: () => {
controllerSelectOpenCount += 1;
},
openControllerDebugModal: () => {
controllerDebugOpenCount += 1;
},
});
return {
ctx,
handlers,
testGlobals,
controllerSelectOpenCount: () => controllerSelectOpenCount,
controllerDebugOpenCount: () => controllerDebugOpenCount,
controllerSelectKeydownCount: () => controllerSelectKeydownCount,
playlistBrowserKeydownCount: () => playlistBrowserKeydownCount,
setWordCount: (count: number) => {
@@ -659,31 +653,78 @@ test('keyboard mode: controller helpers dispatch popup audio play/cycle and scro
}
});
test('keyboard mode: Alt+Shift+C opens controller debug modal', async () => {
const { testGlobals, handlers, controllerDebugOpenCount } = createKeyboardHandlerHarness();
test('keyboard mode: configured controller debug binding dispatches session action', async () => {
const { testGlobals, handlers } = createKeyboardHandlerHarness();
try {
await handlers.setupMpvInputForwarding();
handlers.updateSessionBindings([
{
sourcePath: 'shortcuts.openControllerDebug',
originalKey: 'Alt+Shift+D',
key: { code: 'KeyD', modifiers: ['alt', 'shift'] },
actionType: 'session-action',
actionId: 'openControllerDebug',
},
] as never);
testGlobals.dispatchKeydown({
key: 'C',
code: 'KeyC',
key: 'D',
code: 'KeyD',
altKey: true,
shiftKey: true,
});
assert.equal(controllerDebugOpenCount(), 1);
assert.deepEqual(testGlobals.sessionActions, [{ actionId: 'openControllerDebug', payload: undefined }]);
} finally {
testGlobals.restore();
}
});
test('keyboard mode: Alt+Shift+C opens controller debug modal even while popup is visible', async () => {
const { ctx, testGlobals, handlers, controllerDebugOpenCount } = createKeyboardHandlerHarness();
test('keyboard mode: configured controller debug binding is not swallowed while popup is visible', async () => {
const { ctx, testGlobals, handlers } = createKeyboardHandlerHarness();
try {
await handlers.setupMpvInputForwarding();
ctx.state.yomitanPopupVisible = true;
testGlobals.setPopupVisible(true);
handlers.updateSessionBindings([
{
sourcePath: 'shortcuts.openControllerDebug',
originalKey: 'Alt+Shift+D',
key: { code: 'KeyD', modifiers: ['alt', 'shift'] },
actionType: 'session-action',
actionId: 'openControllerDebug',
},
] as never);
testGlobals.dispatchKeydown({
key: 'D',
code: 'KeyD',
altKey: true,
shiftKey: true,
});
assert.deepEqual(testGlobals.sessionActions, [{ actionId: 'openControllerDebug', payload: undefined }]);
} finally {
testGlobals.restore();
}
});
test('keyboard mode: former fixed Alt+Shift+C does nothing when controller debug is remapped', async () => {
const { testGlobals, handlers } = createKeyboardHandlerHarness();
try {
await handlers.setupMpvInputForwarding();
handlers.updateSessionBindings([
{
sourcePath: 'shortcuts.openControllerDebug',
originalKey: 'Alt+Shift+D',
key: { code: 'KeyD', modifiers: ['alt', 'shift'] },
actionType: 'session-action',
actionId: 'openControllerDebug',
},
] as never);
testGlobals.dispatchKeydown({
key: 'C',
@@ -692,7 +733,7 @@ test('keyboard mode: Alt+Shift+C opens controller debug modal even while popup i
shiftKey: true,
});
assert.equal(controllerDebugOpenCount(), 1);
assert.deepEqual(testGlobals.sessionActions, []);
} finally {
testGlobals.restore();
}

View File

@@ -27,8 +27,6 @@ export function createKeyboardHandlers(
}) => void;
appendClipboardVideoToQueue: () => void;
getPlaybackPaused: () => Promise<boolean | null>;
openControllerSelectModal: () => void;
openControllerDebugModal: () => void;
toggleSubtitleSidebarModal?: () => void;
},
) {
@@ -298,10 +296,6 @@ export function createKeyboardHandlers(
return isPrimaryModifierPressed(e) && !e.altKey && !e.shiftKey && isYKey && !e.repeat;
}
function isControllerModalShortcut(e: KeyboardEvent): boolean {
return !e.ctrlKey && !e.metaKey && e.altKey && !e.repeat && e.code === 'KeyC';
}
function isSubtitleSidebarToggle(e: KeyboardEvent): boolean {
const toggleKey = ctx.state.subtitleSidebarToggleKey;
if (!toggleKey) return false;
@@ -1040,10 +1034,7 @@ export function createKeyboardHandlers(
return;
}
if (
(ctx.state.yomitanPopupVisible || isYomitanPopupVisible(document)) &&
!isControllerModalShortcut(e)
) {
if (ctx.state.yomitanPopupVisible || isYomitanPopupVisible(document)) {
if (handleYomitanPopupKeybind(e)) {
e.preventDefault();
return;
@@ -1100,16 +1091,6 @@ export function createKeyboardHandlers(
return;
}
if (isControllerModalShortcut(e)) {
e.preventDefault();
if (e.shiftKey) {
options.openControllerDebugModal();
} else {
options.openControllerSelectModal();
}
return;
}
const keyString = keyEventToString(e);
const binding = ctx.state.sessionBindingMap.get(keyString);
if (binding) {

View File

@@ -586,31 +586,10 @@ export function createSessionHelpModal(
}
}
async function openSessionHelpModal(opening: SessionHelpBindingInfo): Promise<void> {
function openSessionHelpModal(opening: SessionHelpBindingInfo): void {
openBinding = opening;
priorFocus = document.activeElement;
const dataLoaded = await render();
ctx.dom.sessionHelpShortcut.textContent = `Session help opened with ${formatBindingHint(openBinding)}`;
if (openBinding.fallbackUnavailable) {
ctx.dom.sessionHelpWarning.textContent =
'Both Y-H and Y-K are bound; Y-K remains the fallback for this session.';
} else if (openBinding.fallbackUsed) {
ctx.dom.sessionHelpWarning.textContent = 'Y-H is already bound; using Y-K as fallback.';
} else {
ctx.dom.sessionHelpWarning.textContent = '';
}
if (dataLoaded) {
ctx.dom.sessionHelpStatus.textContent =
'Use Arrow keys, J/K/H/L, mouse, click, or / then type to filter. Esc closes.';
} else {
ctx.dom.sessionHelpStatus.textContent =
'Session help data is unavailable right now. Press Esc to close.';
ctx.dom.sessionHelpWarning.textContent =
'Unable to load latest shortcut settings from the runtime.';
}
ctx.state.sessionHelpModalOpen = true;
options.syncSettingsModalSubtitleSuppression();
ctx.dom.overlay.classList.add('interactive');
@@ -623,6 +602,17 @@ export function createSessionHelpModal(
window.electronAPI.setIgnoreMouseEvents(false);
}
ctx.dom.sessionHelpShortcut.textContent = `Session help opened with ${formatBindingHint(openBinding)}`;
if (openBinding.fallbackUnavailable) {
ctx.dom.sessionHelpWarning.textContent =
'Both Y-H and Y-K are bound; Y-K remains the fallback for this session.';
} else if (openBinding.fallbackUsed) {
ctx.dom.sessionHelpWarning.textContent = 'Y-H is already bound; using Y-K as fallback.';
} else {
ctx.dom.sessionHelpWarning.textContent = '';
}
ctx.dom.sessionHelpStatus.textContent = 'Loading session help data...';
if (focusGuard === null) {
focusGuard = (event: FocusEvent) => {
if (!ctx.state.sessionHelpModalOpen) return;
@@ -639,6 +629,19 @@ export function createSessionHelpModal(
requestOverlayFocus();
window.focus();
enforceModalFocus();
void render().then((dataLoaded) => {
if (!ctx.state.sessionHelpModalOpen) return;
if (dataLoaded) {
ctx.dom.sessionHelpStatus.textContent =
'Use Arrow keys, J/K/H/L, mouse, click, or / then type to filter. Esc closes.';
} else {
ctx.dom.sessionHelpStatus.textContent =
'Session help data is unavailable right now. Press Esc to close.';
ctx.dom.sessionHelpWarning.textContent =
'Unable to load latest shortcut settings from the runtime.';
}
});
}
function closeSessionHelpModal(): void {
@@ -648,6 +651,7 @@ export function createSessionHelpModal(
options.syncSettingsModalSubtitleSuppression();
ctx.dom.sessionHelpModal.classList.add('hidden');
ctx.dom.sessionHelpModal.setAttribute('aria-hidden', 'true');
window.electronAPI.notifyOverlayModalClosed('session-help');
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
ctx.dom.overlay.classList.remove('interactive');
}

View File

@@ -178,14 +178,6 @@ const keyboardHandlers = createKeyboardHandlers(ctx, {
void window.electronAPI.appendClipboardVideoToQueue();
},
getPlaybackPaused: () => window.electronAPI.getPlaybackPaused(),
openControllerSelectModal: () => {
controllerSelectModal.openControllerSelectModal();
window.electronAPI.notifyOverlayModalOpened('controller-select');
},
openControllerDebugModal: () => {
controllerDebugModal.openControllerDebugModal();
window.electronAPI.notifyOverlayModalOpened('controller-debug');
},
toggleSubtitleSidebarModal: () => {
void subtitleSidebarModal.toggleSubtitleSidebarModal();
},
@@ -437,6 +429,28 @@ function registerModalOpenHandlers(): void {
window.electronAPI.notifyOverlayModalOpened('runtime-options');
});
});
window.electronAPI.onOpenSessionHelp(() => {
runGuarded('session-help:open', () => {
sessionHelpModal.openSessionHelpModal({
bindingKey: 'KeyH',
fallbackUsed: false,
fallbackUnavailable: false,
});
window.electronAPI.notifyOverlayModalOpened('session-help');
});
});
window.electronAPI.onOpenControllerSelect(() => {
runGuarded('controller-select:open', () => {
controllerSelectModal.openControllerSelectModal();
window.electronAPI.notifyOverlayModalOpened('controller-select');
});
});
window.electronAPI.onOpenControllerDebug(() => {
runGuarded('controller-debug:open', () => {
controllerDebugModal.openControllerDebugModal();
window.electronAPI.notifyOverlayModalOpened('controller-debug');
});
});
window.electronAPI.onOpenJimaku(() => {
runGuarded('jimaku:open', () => {
jimakuModal.openJimakuModal();
@@ -492,6 +506,12 @@ function registerKeyboardCommandHandlers(): void {
keyboardHandlers.handleLookupWindowToggleRequested();
});
});
window.electronAPI.onSubtitleSidebarToggle(() => {
runGuarded('subtitle-sidebar:toggle', () => {
void subtitleSidebarModal.toggleSubtitleSidebarModal();
});
});
}
function runGuarded(action: string, fn: () => void): void {