mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-12 04:19:25 -07:00
Honor configured controller shortcuts and clean up modal opens
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user