mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-10 16:19:24 -07:00
feat: streamline Kiku duplicate grouping and popup flow (#38)
This commit is contained in:
@@ -524,6 +524,56 @@ test('popup-visible mpv keybindings still fire for bound keys', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('paused configured subtitle-jump keybinding re-applies pause after backward seek', async () => {
|
||||
const { handlers, testGlobals } = createKeyboardHandlerHarness();
|
||||
|
||||
try {
|
||||
await handlers.setupMpvInputForwarding();
|
||||
handlers.updateKeybindings([
|
||||
{
|
||||
key: 'Shift+KeyH',
|
||||
command: ['sub-seek', -1],
|
||||
},
|
||||
] as never);
|
||||
testGlobals.setPlaybackPausedResponse(true);
|
||||
|
||||
testGlobals.dispatchKeydown({ key: 'H', code: 'KeyH', shiftKey: true });
|
||||
await wait(0);
|
||||
|
||||
assert.deepEqual(testGlobals.mpvCommands.slice(-2), [
|
||||
['sub-seek', -1],
|
||||
['set_property', 'pause', 'yes'],
|
||||
]);
|
||||
} finally {
|
||||
testGlobals.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('configured subtitle-jump keybinding preserves pause when pause state is unknown', async () => {
|
||||
const { handlers, testGlobals } = createKeyboardHandlerHarness();
|
||||
|
||||
try {
|
||||
await handlers.setupMpvInputForwarding();
|
||||
handlers.updateKeybindings([
|
||||
{
|
||||
key: 'Shift+KeyH',
|
||||
command: ['sub-seek', -1],
|
||||
},
|
||||
] as never);
|
||||
testGlobals.setPlaybackPausedResponse(null);
|
||||
|
||||
testGlobals.dispatchKeydown({ key: 'H', code: 'KeyH', shiftKey: true });
|
||||
await wait(0);
|
||||
|
||||
assert.deepEqual(testGlobals.mpvCommands.slice(-2), [
|
||||
['sub-seek', -1],
|
||||
['set_property', 'pause', 'yes'],
|
||||
]);
|
||||
} finally {
|
||||
testGlobals.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('visible-layer y-t dispatches mpv plugin toggle while overlay owns focus', async () => {
|
||||
const { handlers, testGlobals } = createKeyboardHandlerHarness();
|
||||
|
||||
@@ -1159,6 +1209,56 @@ test('keyboard mode: edge jump while paused re-applies paused state after subtit
|
||||
}
|
||||
});
|
||||
|
||||
test('keyboard mode: left edge jump while paused re-applies paused state after subtitle seek', async () => {
|
||||
const { ctx, handlers, testGlobals, setWordCount } = createKeyboardHandlerHarness();
|
||||
|
||||
try {
|
||||
await handlers.setupMpvInputForwarding();
|
||||
handlers.handleKeyboardModeToggleRequested();
|
||||
|
||||
setWordCount(2);
|
||||
ctx.state.keyboardSelectedWordIndex = 0;
|
||||
handlers.syncKeyboardTokenSelection();
|
||||
testGlobals.setPlaybackPausedResponse(true);
|
||||
|
||||
testGlobals.dispatchKeydown({ key: 'ArrowLeft', code: 'ArrowLeft' });
|
||||
await wait(0);
|
||||
|
||||
assert.deepEqual(testGlobals.mpvCommands.slice(-2), [
|
||||
['sub-seek', -1],
|
||||
['set_property', 'pause', 'yes'],
|
||||
]);
|
||||
} finally {
|
||||
ctx.state.keyboardDrivenModeEnabled = false;
|
||||
testGlobals.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('keyboard mode: h edge jump while paused re-applies paused state after subtitle seek', async () => {
|
||||
const { ctx, handlers, testGlobals, setWordCount } = createKeyboardHandlerHarness();
|
||||
|
||||
try {
|
||||
await handlers.setupMpvInputForwarding();
|
||||
handlers.handleKeyboardModeToggleRequested();
|
||||
|
||||
setWordCount(2);
|
||||
ctx.state.keyboardSelectedWordIndex = 0;
|
||||
handlers.syncKeyboardTokenSelection();
|
||||
testGlobals.setPlaybackPausedResponse(true);
|
||||
|
||||
testGlobals.dispatchKeydown({ key: 'h', code: 'KeyH' });
|
||||
await wait(0);
|
||||
|
||||
assert.deepEqual(testGlobals.mpvCommands.slice(-2), [
|
||||
['sub-seek', -1],
|
||||
['set_property', 'pause', 'yes'],
|
||||
]);
|
||||
} finally {
|
||||
ctx.state.keyboardDrivenModeEnabled = false;
|
||||
testGlobals.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('keyboard mode: edge jump with unknown pause state re-applies pause conservatively', async () => {
|
||||
const { ctx, handlers, testGlobals, setWordCount } = createKeyboardHandlerHarness();
|
||||
|
||||
|
||||
@@ -358,6 +358,33 @@ export function createKeyboardHandlers(
|
||||
});
|
||||
}
|
||||
|
||||
function isSubtitleSeekCommand(command: (string | number)[] | undefined): command is [string, number] {
|
||||
return (
|
||||
Array.isArray(command) &&
|
||||
command[0] === 'sub-seek' &&
|
||||
typeof command[1] === 'number'
|
||||
);
|
||||
}
|
||||
|
||||
function dispatchConfiguredMpvCommand(command: (string | number)[]): void {
|
||||
if (!isSubtitleSeekCommand(command)) {
|
||||
window.electronAPI.sendMpvCommand(command);
|
||||
return;
|
||||
}
|
||||
|
||||
void options
|
||||
.getPlaybackPaused()
|
||||
.then((paused) => {
|
||||
window.electronAPI.sendMpvCommand(command);
|
||||
if (paused !== false) {
|
||||
window.electronAPI.sendMpvCommand(['set_property', 'pause', 'yes']);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
window.electronAPI.sendMpvCommand(command);
|
||||
});
|
||||
}
|
||||
|
||||
type ScanModifierState = {
|
||||
shiftKey?: boolean;
|
||||
ctrlKey?: boolean;
|
||||
@@ -954,7 +981,7 @@ export function createKeyboardHandlers(
|
||||
|
||||
if (command) {
|
||||
e.preventDefault();
|
||||
window.electronAPI.sendMpvCommand(command);
|
||||
dispatchConfiguredMpvCommand(command);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
35
src/renderer/kiku-open.test.ts
Normal file
35
src/renderer/kiku-open.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { prepareForKikuFieldGroupingOpen } from './kiku-open';
|
||||
|
||||
test('prepareForKikuFieldGroupingOpen closes lookup popup before pausing playback', () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
prepareForKikuFieldGroupingOpen({
|
||||
closeLookupWindow: () => {
|
||||
calls.push('close');
|
||||
return true;
|
||||
},
|
||||
pausePlayback: () => {
|
||||
calls.push('pause');
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, ['close', 'pause']);
|
||||
});
|
||||
|
||||
test('prepareForKikuFieldGroupingOpen still pauses playback when no popup is open', () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
prepareForKikuFieldGroupingOpen({
|
||||
closeLookupWindow: () => {
|
||||
calls.push('close');
|
||||
return false;
|
||||
},
|
||||
pausePlayback: () => {
|
||||
calls.push('pause');
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, ['close', 'pause']);
|
||||
});
|
||||
7
src/renderer/kiku-open.ts
Normal file
7
src/renderer/kiku-open.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function prepareForKikuFieldGroupingOpen(options: {
|
||||
closeLookupWindow: () => boolean;
|
||||
pausePlayback: () => void;
|
||||
}): void {
|
||||
options.closeLookupWindow();
|
||||
options.pausePlayback();
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import { createControllerDebugModal } from './modals/controller-debug.js';
|
||||
import { createControllerSelectModal } from './modals/controller-select.js';
|
||||
import { createJimakuModal } from './modals/jimaku.js';
|
||||
import { createKikuModal } from './modals/kiku.js';
|
||||
import { prepareForKikuFieldGroupingOpen } from './kiku-open.js';
|
||||
import { createPlaylistBrowserModal } from './modals/playlist-browser.js';
|
||||
import { createSessionHelpModal } from './modals/session-help.js';
|
||||
import { createSubtitleSidebarModal } from './modals/subtitle-sidebar.js';
|
||||
@@ -470,6 +471,12 @@ function registerModalOpenHandlers(): void {
|
||||
window.electronAPI.onKikuFieldGroupingRequest(
|
||||
(data: { original: KikuDuplicateCardInfo; duplicate: KikuDuplicateCardInfo }) => {
|
||||
runGuarded('kiku:field-grouping-open', () => {
|
||||
prepareForKikuFieldGroupingOpen({
|
||||
closeLookupWindow: () => keyboardHandlers.closeLookupWindow(),
|
||||
pausePlayback: () => {
|
||||
window.electronAPI.sendMpvCommand(['set_property', 'pause', 'yes']);
|
||||
},
|
||||
});
|
||||
kikuModal.openKikuFieldGroupingModal(data);
|
||||
window.electronAPI.notifyOverlayModalOpened('kiku');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user