mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-26 04:19:27 -07:00
feat: add primary subtitle bar toggle
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
id: TASK-295
|
||||||
|
title: Add primary subtitle visibility keybinding
|
||||||
|
status: Done
|
||||||
|
assignee:
|
||||||
|
- Codex
|
||||||
|
created_date: '2026-04-25 23:09'
|
||||||
|
updated_date: '2026-04-25 23:45'
|
||||||
|
labels:
|
||||||
|
- renderer
|
||||||
|
- keybindings
|
||||||
|
- subtitles
|
||||||
|
dependencies: []
|
||||||
|
priority: medium
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
Add a `v` keybinding that overrides mpv's default `v` subtitle visibility toggle and instead toggles SubMiner's primary subtitle bar visibility on and off. Secondary subtitle hover behavior is out of scope.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [x] #1 Pressing `v` toggles the primary subtitle bar from visible to hidden.
|
||||||
|
- [x] #2 Pressing `v` again restores the primary subtitle bar visibility.
|
||||||
|
- [x] #3 The keybinding does not add or change secondary subtitle hover behavior.
|
||||||
|
- [x] #4 Relevant automated coverage verifies the toggle behavior.
|
||||||
|
- [x] #5 Pressing `v` in the mpv/plugin keybinding path also toggles the primary subtitle bar visibility instead of mpv native subtitle visibility.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
<!-- SECTION:PLAN:BEGIN -->
|
||||||
|
1. Inspect existing renderer keybinding and subtitle bar visibility code, including current local edits in touched files.
|
||||||
|
2. Add a focused failing test for `v` toggling primary subtitle bar visibility without changing secondary hover behavior.
|
||||||
|
3. Implement the minimal renderer/keybinding change.
|
||||||
|
4. Run targeted tests and update acceptance criteria/final notes.
|
||||||
|
<!-- SECTION:PLAN:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
Implemented renderer-local `KeyV` handling before session/mpv binding dispatch so mpv `sub-visibility` is not touched. Visibility state is stored in renderer state and applied via `primary-sub-hidden` class on the primary subtitle container.
|
||||||
|
|
||||||
|
Scope updated after user clarified the toggle must work when focus is in mpv as well as in the overlay renderer. Added a forced mpv plugin binding for `v` that runs `--toggle-primary-subtitle-bar`, then broadcasts a renderer IPC toggle event and reuses the same primary subtitle bar toggle path.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
||||||
|
## Final Summary
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||||
|
Summary:
|
||||||
|
- Added a renderer-local `v` key handler that toggles primary subtitle bar visibility by adding/removing `primary-sub-hidden` on the primary subtitle container.
|
||||||
|
- Added renderer state for the toggle so repeated presses restore the bar without issuing mpv `sub-visibility` commands.
|
||||||
|
- Added a forced mpv plugin `v` binding that invokes `--toggle-primary-subtitle-bar` and broadcasts the same renderer toggle event.
|
||||||
|
- Added CSS for the hidden primary subtitle bar state and regression coverage for both overlay and mpv/plugin entry points.
|
||||||
|
|
||||||
|
Tests:
|
||||||
|
- `bun test src/renderer/handlers/keyboard.test.ts --test-name-pattern "primary subtitle visibility key"`
|
||||||
|
- `bun test src/cli/args.test.ts --test-name-pattern "session action"`
|
||||||
|
- `bun test src/core/services/cli-command.test.ts --test-name-pattern "visibility and utility"`
|
||||||
|
- `bun test src/cli/args.test.ts src/cli/help.test.ts src/core/services/cli-command.test.ts src/main/runtime/cli-command-context.test.ts src/main/runtime/cli-command-context-deps.test.ts src/main/runtime/cli-command-context-main-deps.test.ts src/main/runtime/cli-command-context-factory.test.ts src/main/runtime/composers/cli-startup-composer.test.ts src/main/runtime/first-run-setup-service.test.ts`
|
||||||
|
- `lua scripts/test-plugin-start-gate.lua && lua scripts/test-plugin-lua-compat.lua`
|
||||||
|
- `bun run typecheck`
|
||||||
|
- `bun run test:fast`
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||||
4
changes/295-primary-subtitle-bar-toggle.md
Normal file
4
changes/295-primary-subtitle-bar-toggle.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
type: added
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- Added a `V` shortcut and mpv plugin binding to toggle the SubMiner primary subtitle bar without changing mpv native subtitle visibility.
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
**Changed**
|
**Changed**
|
||||||
- Overlay: Added configurable overlay shortcuts for session help, controller select, and controller debug actions.
|
- Overlay: Added configurable overlay shortcuts for session help, controller select, and controller debug actions.
|
||||||
- Overlay: Added mpv/plugin and CLI routing for session help, controller utilities, and subtitle sidebar toggling through the shared session-action path.
|
- Overlay: Added mpv/plugin and CLI routing for session help, controller utilities, and subtitle sidebar toggling through the shared session-action path.
|
||||||
|
- Overlay: Added a `V` shortcut and mpv plugin binding to toggle the SubMiner primary subtitle bar instead of mpv's native primary subtitle visibility.
|
||||||
- Overlay: Improved dedicated overlay modal retry and focus handling for runtime options, Jimaku, session help, controller tools, and the playlist browser.
|
- Overlay: Improved dedicated overlay modal retry and focus handling for runtime options, Jimaku, session help, controller tools, and the playlist browser.
|
||||||
- Overlay: Fixed controller configuration and controller debug shortcut opens so configured bindings bring up their modals again instead of tripping renderer recovery.
|
- Overlay: Fixed controller configuration and controller debug shortcut opens so configured bindings bring up their modals again instead of tripping renderer recovery.
|
||||||
- Stats: Sessions are rolled up per episode within each day, with a bulk delete that wipes every session in the group.
|
- Stats: Sessions are rolled up per episode within each day, with a bulk delete that wipes every session in the group.
|
||||||
|
|||||||
@@ -41,11 +41,14 @@ All keybindings use a `y` chord prefix — press `y`, then the second key:
|
|||||||
| `y-s` | Start overlay |
|
| `y-s` | Start overlay |
|
||||||
| `y-S` | Stop overlay |
|
| `y-S` | Stop overlay |
|
||||||
| `y-t` | Toggle visible overlay |
|
| `y-t` | Toggle visible overlay |
|
||||||
|
| `v` | Toggle primary subtitle bar visibility |
|
||||||
| `y-o` | Open settings window |
|
| `y-o` | Open settings window |
|
||||||
| `y-r` | Restart overlay |
|
| `y-r` | Restart overlay |
|
||||||
| `y-c` | Check status |
|
| `y-c` | Check status |
|
||||||
| `y-k` | Skip intro (AniSkip) |
|
| `y-k` | Skip intro (AniSkip) |
|
||||||
|
|
||||||
|
The bare `v` binding is a forced mpv binding. It overrides mpv's default primary subtitle visibility toggle and routes the action to SubMiner's primary subtitle bar instead.
|
||||||
|
|
||||||
## Menu
|
## Menu
|
||||||
|
|
||||||
Press `y-y` to open an interactive menu in mpv's OSD:
|
Press `y-y` to open an interactive menu in mpv's OSD:
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ These control playback and subtitle display. They require overlay window focus.
|
|||||||
| Shortcut | Action |
|
| Shortcut | Action |
|
||||||
| -------------------- | --------------------------------------------------- |
|
| -------------------- | --------------------------------------------------- |
|
||||||
| `Space` | Toggle mpv pause |
|
| `Space` | Toggle mpv pause |
|
||||||
|
| `V` | Toggle primary subtitle bar visibility |
|
||||||
| `J` | Cycle primary subtitle track |
|
| `J` | Cycle primary subtitle track |
|
||||||
| `Shift+J` | Cycle secondary subtitle track |
|
| `Shift+J` | Cycle secondary subtitle track |
|
||||||
| `Ctrl+Alt+P` | Open playlist browser for current directory + queue |
|
| `Ctrl+Alt+P` | Open playlist browser for current directory + queue |
|
||||||
@@ -100,11 +101,14 @@ When the mpv plugin is installed, all commands use a `y` chord prefix — press
|
|||||||
| `y-s` | Start overlay |
|
| `y-s` | Start overlay |
|
||||||
| `y-S` | Stop overlay |
|
| `y-S` | Stop overlay |
|
||||||
| `y-t` | Toggle visible overlay |
|
| `y-t` | Toggle visible overlay |
|
||||||
|
| `v` | Toggle primary subtitle bar visibility |
|
||||||
| `y-o` | Open Yomitan settings |
|
| `y-o` | Open Yomitan settings |
|
||||||
| `y-r` | Restart overlay |
|
| `y-r` | Restart overlay |
|
||||||
| `y-c` | Check overlay status |
|
| `y-c` | Check overlay status |
|
||||||
| `y-h` | Open session help |
|
| `y-h` | Open session help |
|
||||||
|
|
||||||
|
The bare `v` plugin binding intentionally overrides mpv's native primary subtitle visibility toggle so the SubMiner primary subtitle bar is hidden or restored instead.
|
||||||
|
|
||||||
When the overlay has focus, press `y` then `d` to toggle DevTools (debugging helper).
|
When the overlay has focus, press `y` then `d` to toggle DevTools (debugging helper).
|
||||||
|
|
||||||
## Drag-and-Drop
|
## Drag-and-Drop
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ SubMiner.AppImage --stop # Stop overlay
|
|||||||
SubMiner.AppImage --start --toggle # Start MPV IPC + toggle visibility
|
SubMiner.AppImage --start --toggle # Start MPV IPC + toggle visibility
|
||||||
SubMiner.AppImage --show-visible-overlay # Force show visible overlay
|
SubMiner.AppImage --show-visible-overlay # Force show visible overlay
|
||||||
SubMiner.AppImage --hide-visible-overlay # Force hide visible overlay
|
SubMiner.AppImage --hide-visible-overlay # Force hide visible overlay
|
||||||
|
SubMiner.AppImage --toggle-primary-subtitle-bar # Toggle primary subtitle bar visibility
|
||||||
SubMiner.AppImage --start --dev # Enable app/dev mode only
|
SubMiner.AppImage --start --dev # Enable app/dev mode only
|
||||||
SubMiner.AppImage --start --debug # Alias for --dev
|
SubMiner.AppImage --start --debug # Alias for --dev
|
||||||
SubMiner.AppImage --start --log-level debug # Force verbose logging without app/dev mode
|
SubMiner.AppImage --start --log-level debug # Force verbose logging without app/dev mode
|
||||||
@@ -327,6 +328,8 @@ See [Keyboard Shortcuts](/shortcuts) for the full reference, including mining sh
|
|||||||
|
|
||||||
Useful overlay-local default keybinding: `Ctrl+Alt+P` opens the playlist browser for the current video's parent directory and the live mpv queue so you can append, reorder, remove, or jump between episodes without leaving playback.
|
Useful overlay-local default keybinding: `Ctrl+Alt+P` opens the playlist browser for the current video's parent directory and the live mpv queue so you can append, reorder, remove, or jump between episodes without leaving playback.
|
||||||
|
|
||||||
|
Press `V` to hide or restore the primary SubMiner subtitle bar. The mpv plugin also binds bare `v` to the same action, overriding mpv's native primary subtitle visibility toggle.
|
||||||
|
|
||||||
`Ctrl/Cmd+Shift+H` opens the session help modal with the current overlay and mpv keybindings. If you use the mpv plugin, the same help view is also available through the `y-h` chord.
|
`Ctrl/Cmd+Shift+H` opens the session help modal with the current overlay and mpv keybindings. If you use the mpv plugin, the same help view is also available through the `y-h` chord.
|
||||||
|
|
||||||
Hovering over subtitle text pauses mpv by default; leaving resumes it. Yomitan popups also pause playback by default. Set `subtitleStyle.autoPauseVideoOnHover: false` or `subtitleStyle.autoPauseVideoOnYomitanPopup: false` to disable either behavior.
|
Hovering over subtitle text pauses mpv by default; leaving resumes it. Yomitan popups also pause playback by default. Set `subtitleStyle.autoPauseVideoOnHover: false` or `subtitleStyle.autoPauseVideoOnYomitanPopup: false` to disable either behavior.
|
||||||
|
|||||||
@@ -462,6 +462,21 @@ function M.create(ctx)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function toggle_primary_subtitle_bar()
|
||||||
|
if not binary.ensure_binary_available() then
|
||||||
|
subminer_log("error", "binary", "SubMiner binary not found")
|
||||||
|
show_osd("Error: binary not found")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
run_control_command_async("toggle-primary-subtitle-bar", nil, function(ok)
|
||||||
|
if not ok then
|
||||||
|
subminer_log("warn", "process", "Primary subtitle bar toggle command failed")
|
||||||
|
show_osd("Primary subtitle toggle failed")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
local function open_options()
|
local function open_options()
|
||||||
if not binary.ensure_binary_available() then
|
if not binary.ensure_binary_available() then
|
||||||
subminer_log("error", "binary", "SubMiner binary not found")
|
subminer_log("error", "binary", "SubMiner binary not found")
|
||||||
@@ -552,6 +567,7 @@ function M.create(ctx)
|
|||||||
stop_overlay = stop_overlay,
|
stop_overlay = stop_overlay,
|
||||||
hide_visible_overlay = hide_visible_overlay,
|
hide_visible_overlay = hide_visible_overlay,
|
||||||
toggle_overlay = toggle_overlay,
|
toggle_overlay = toggle_overlay,
|
||||||
|
toggle_primary_subtitle_bar = toggle_primary_subtitle_bar,
|
||||||
open_options = open_options,
|
open_options = open_options,
|
||||||
restart_overlay = restart_overlay,
|
restart_overlay = restart_overlay,
|
||||||
check_status = check_status,
|
check_status = check_status,
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ function M.create(ctx)
|
|||||||
mp.add_key_binding("y-t", "subminer-toggle", function()
|
mp.add_key_binding("y-t", "subminer-toggle", function()
|
||||||
process.toggle_overlay()
|
process.toggle_overlay()
|
||||||
end)
|
end)
|
||||||
|
mp.add_forced_key_binding("v", "subminer-toggle-primary-subtitle-bar", function()
|
||||||
|
process.toggle_primary_subtitle_bar()
|
||||||
|
end)
|
||||||
mp.add_key_binding("y-y", "subminer-menu", show_menu)
|
mp.add_key_binding("y-y", "subminer-menu", show_menu)
|
||||||
mp.add_key_binding("y-o", "subminer-options", function()
|
mp.add_key_binding("y-o", "subminer-options", function()
|
||||||
process.open_options()
|
process.open_options()
|
||||||
|
|||||||
@@ -151,6 +151,14 @@ local function run_plugin_scenario(config)
|
|||||||
fn = fn,
|
fn = fn,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
function mp.add_forced_key_binding(keys, name, fn)
|
||||||
|
recorded.key_bindings[#recorded.key_bindings + 1] = {
|
||||||
|
keys = keys,
|
||||||
|
name = name,
|
||||||
|
fn = fn,
|
||||||
|
forced = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
function mp.register_event(name, fn)
|
function mp.register_event(name, fn)
|
||||||
if recorded.events[name] == nil then
|
if recorded.events[name] == nil then
|
||||||
recorded.events[name] = {}
|
recorded.events[name] = {}
|
||||||
@@ -537,6 +545,39 @@ do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local recorded, err = run_plugin_scenario({
|
||||||
|
process_list = "",
|
||||||
|
option_overrides = {
|
||||||
|
binary_path = binary_path,
|
||||||
|
auto_start = "no",
|
||||||
|
},
|
||||||
|
files = {
|
||||||
|
[binary_path] = true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert_true(recorded ~= nil, "plugin failed to load for primary subtitle bar binding scenario: " .. tostring(err))
|
||||||
|
local binding = nil
|
||||||
|
for _, candidate in ipairs(recorded.key_bindings) do
|
||||||
|
if candidate.name == "subminer-toggle-primary-subtitle-bar" then
|
||||||
|
binding = candidate
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert_true(binding ~= nil, "primary subtitle bar v binding should be registered")
|
||||||
|
assert_true(binding.keys == "v", "primary subtitle bar binding should use bare v")
|
||||||
|
assert_true(binding.forced == true, "primary subtitle bar binding should override mpv's built-in v binding")
|
||||||
|
binding.fn()
|
||||||
|
assert_true(
|
||||||
|
count_control_calls(recorded.async_calls, "--toggle-primary-subtitle-bar") == 1,
|
||||||
|
"primary subtitle bar binding should issue primary subtitle toggle command"
|
||||||
|
)
|
||||||
|
assert_true(
|
||||||
|
count_control_calls(recorded.async_calls, "--toggle-visible-overlay") == 0,
|
||||||
|
"primary subtitle bar binding should not toggle the whole visible overlay"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
local recorded, err = run_plugin_scenario({
|
local recorded, err = run_plugin_scenario({
|
||||||
process_list = "",
|
process_list = "",
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ test('parseArgs captures session action forwarding flags', () => {
|
|||||||
'--open-jimaku',
|
'--open-jimaku',
|
||||||
'--open-youtube-picker',
|
'--open-youtube-picker',
|
||||||
'--open-playlist-browser',
|
'--open-playlist-browser',
|
||||||
|
'--toggle-primary-subtitle-bar',
|
||||||
'--replay-current-subtitle',
|
'--replay-current-subtitle',
|
||||||
'--play-next-subtitle',
|
'--play-next-subtitle',
|
||||||
'--shift-sub-delay-prev-line',
|
'--shift-sub-delay-prev-line',
|
||||||
@@ -94,6 +95,7 @@ test('parseArgs captures session action forwarding flags', () => {
|
|||||||
assert.equal(args.openJimaku, true);
|
assert.equal(args.openJimaku, true);
|
||||||
assert.equal(args.openYoutubePicker, true);
|
assert.equal(args.openYoutubePicker, true);
|
||||||
assert.equal(args.openPlaylistBrowser, true);
|
assert.equal(args.openPlaylistBrowser, true);
|
||||||
|
assert.equal(args.togglePrimarySubtitleBar, true);
|
||||||
assert.equal(args.replayCurrentSubtitle, true);
|
assert.equal(args.replayCurrentSubtitle, true);
|
||||||
assert.equal(args.playNextSubtitle, true);
|
assert.equal(args.playNextSubtitle, true);
|
||||||
assert.equal(args.shiftSubDelayPrevLine, true);
|
assert.equal(args.shiftSubDelayPrevLine, true);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface CliArgs {
|
|||||||
stop: boolean;
|
stop: boolean;
|
||||||
toggle: boolean;
|
toggle: boolean;
|
||||||
toggleVisibleOverlay: boolean;
|
toggleVisibleOverlay: boolean;
|
||||||
|
togglePrimarySubtitleBar: boolean;
|
||||||
settings: boolean;
|
settings: boolean;
|
||||||
setup: boolean;
|
setup: boolean;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
@@ -106,6 +107,7 @@ export function parseArgs(argv: string[]): CliArgs {
|
|||||||
stop: false,
|
stop: false,
|
||||||
toggle: false,
|
toggle: false,
|
||||||
toggleVisibleOverlay: false,
|
toggleVisibleOverlay: false,
|
||||||
|
togglePrimarySubtitleBar: false,
|
||||||
settings: false,
|
settings: false,
|
||||||
setup: false,
|
setup: false,
|
||||||
show: false,
|
show: false,
|
||||||
@@ -219,6 +221,7 @@ export function parseArgs(argv: string[]): CliArgs {
|
|||||||
} else if (arg === '--stop') args.stop = true;
|
} else if (arg === '--stop') args.stop = true;
|
||||||
else if (arg === '--toggle') args.toggle = true;
|
else if (arg === '--toggle') args.toggle = true;
|
||||||
else if (arg === '--toggle-visible-overlay') args.toggleVisibleOverlay = true;
|
else if (arg === '--toggle-visible-overlay') args.toggleVisibleOverlay = true;
|
||||||
|
else if (arg === '--toggle-primary-subtitle-bar') args.togglePrimarySubtitleBar = true;
|
||||||
else if (arg === '--settings' || arg === '--yomitan') args.settings = true;
|
else if (arg === '--settings' || arg === '--yomitan') args.settings = true;
|
||||||
else if (arg === '--setup') args.setup = true;
|
else if (arg === '--setup') args.setup = true;
|
||||||
else if (arg === '--show') args.show = true;
|
else if (arg === '--show') args.show = true;
|
||||||
@@ -456,6 +459,7 @@ export function hasExplicitCommand(args: CliArgs): boolean {
|
|||||||
args.stop ||
|
args.stop ||
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
args.toggleVisibleOverlay ||
|
args.toggleVisibleOverlay ||
|
||||||
|
args.togglePrimarySubtitleBar ||
|
||||||
args.settings ||
|
args.settings ||
|
||||||
args.setup ||
|
args.setup ||
|
||||||
args.show ||
|
args.show ||
|
||||||
@@ -526,6 +530,7 @@ export function isStandaloneTexthookerCommand(args: CliArgs): boolean {
|
|||||||
!args.stop &&
|
!args.stop &&
|
||||||
!args.toggle &&
|
!args.toggle &&
|
||||||
!args.toggleVisibleOverlay &&
|
!args.toggleVisibleOverlay &&
|
||||||
|
!args.togglePrimarySubtitleBar &&
|
||||||
!args.settings &&
|
!args.settings &&
|
||||||
!args.setup &&
|
!args.setup &&
|
||||||
!args.show &&
|
!args.show &&
|
||||||
@@ -591,6 +596,7 @@ export function shouldStartApp(args: CliArgs): boolean {
|
|||||||
args.launchMpv ||
|
args.launchMpv ||
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
args.toggleVisibleOverlay ||
|
args.toggleVisibleOverlay ||
|
||||||
|
args.togglePrimarySubtitleBar ||
|
||||||
args.settings ||
|
args.settings ||
|
||||||
args.setup ||
|
args.setup ||
|
||||||
args.copySubtitle ||
|
args.copySubtitle ||
|
||||||
@@ -644,6 +650,7 @@ export function shouldRunSettingsOnlyStartup(args: CliArgs): boolean {
|
|||||||
!args.stop &&
|
!args.stop &&
|
||||||
!args.toggle &&
|
!args.toggle &&
|
||||||
!args.toggleVisibleOverlay &&
|
!args.toggleVisibleOverlay &&
|
||||||
|
!args.togglePrimarySubtitleBar &&
|
||||||
!args.show &&
|
!args.show &&
|
||||||
!args.hide &&
|
!args.hide &&
|
||||||
!args.setup &&
|
!args.setup &&
|
||||||
@@ -707,6 +714,7 @@ export function commandNeedsOverlayRuntime(args: CliArgs): boolean {
|
|||||||
return (
|
return (
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
args.toggleVisibleOverlay ||
|
args.toggleVisibleOverlay ||
|
||||||
|
args.togglePrimarySubtitleBar ||
|
||||||
args.show ||
|
args.show ||
|
||||||
args.hide ||
|
args.hide ||
|
||||||
args.showVisibleOverlay ||
|
args.showVisibleOverlay ||
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ ${B}Session${R}
|
|||||||
|
|
||||||
${B}Overlay${R}
|
${B}Overlay${R}
|
||||||
--toggle-visible-overlay Toggle subtitle overlay
|
--toggle-visible-overlay Toggle subtitle overlay
|
||||||
|
--toggle-primary-subtitle-bar Toggle primary subtitle bar
|
||||||
--show-visible-overlay Show subtitle overlay
|
--show-visible-overlay Show subtitle overlay
|
||||||
--hide-visible-overlay Hide subtitle overlay
|
--hide-visible-overlay Hide subtitle overlay
|
||||||
--settings Open Yomitan settings window
|
--settings Open Yomitan settings window
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
|||||||
stop: false,
|
stop: false,
|
||||||
toggle: false,
|
toggle: false,
|
||||||
toggleVisibleOverlay: false,
|
toggleVisibleOverlay: false,
|
||||||
|
togglePrimarySubtitleBar: false,
|
||||||
settings: false,
|
settings: false,
|
||||||
setup: false,
|
setup: false,
|
||||||
show: false,
|
show: false,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
|||||||
openJimaku: false,
|
openJimaku: false,
|
||||||
openYoutubePicker: false,
|
openYoutubePicker: false,
|
||||||
openPlaylistBrowser: false,
|
openPlaylistBrowser: false,
|
||||||
|
togglePrimarySubtitleBar: false,
|
||||||
replayCurrentSubtitle: false,
|
replayCurrentSubtitle: false,
|
||||||
playNextSubtitle: false,
|
playNextSubtitle: false,
|
||||||
shiftSubDelayPrevLine: false,
|
shiftSubDelayPrevLine: false,
|
||||||
@@ -119,6 +120,9 @@ function createDeps(overrides: Partial<CliCommandServiceDeps> = {}) {
|
|||||||
toggleVisibleOverlay: () => {
|
toggleVisibleOverlay: () => {
|
||||||
calls.push('toggleVisibleOverlay');
|
calls.push('toggleVisibleOverlay');
|
||||||
},
|
},
|
||||||
|
togglePrimarySubtitleBar: () => {
|
||||||
|
calls.push('togglePrimarySubtitleBar');
|
||||||
|
},
|
||||||
openYomitanSettingsDelayed: (delayMs) => {
|
openYomitanSettingsDelayed: (delayMs) => {
|
||||||
calls.push(`openYomitanSettingsDelayed:${delayMs}`);
|
calls.push(`openYomitanSettingsDelayed:${delayMs}`);
|
||||||
},
|
},
|
||||||
@@ -533,6 +537,7 @@ test('handleCliCommand handles visibility and utility command dispatches', () =>
|
|||||||
expected: 'startPendingMineSentenceMultiple:2500',
|
expected: 'startPendingMineSentenceMultiple:2500',
|
||||||
},
|
},
|
||||||
{ args: { toggleSecondarySub: true }, expected: 'cycleSecondarySubMode' },
|
{ args: { toggleSecondarySub: true }, expected: 'cycleSecondarySubMode' },
|
||||||
|
{ args: { togglePrimarySubtitleBar: true }, expected: 'togglePrimarySubtitleBar' },
|
||||||
{ args: { toggleStatsOverlay: true }, expected: 'dispatchSessionAction' },
|
{ args: { toggleStatsOverlay: true }, expected: 'dispatchSessionAction' },
|
||||||
{
|
{
|
||||||
args: { openRuntimeOptions: true },
|
args: { openRuntimeOptions: true },
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export interface CliCommandServiceDeps {
|
|||||||
isOverlayRuntimeInitialized: () => boolean;
|
isOverlayRuntimeInitialized: () => boolean;
|
||||||
initializeOverlayRuntime: () => void;
|
initializeOverlayRuntime: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
|
togglePrimarySubtitleBar: () => void;
|
||||||
openFirstRunSetup: () => void;
|
openFirstRunSetup: () => void;
|
||||||
openYomitanSettingsDelayed: (delayMs: number) => void;
|
openYomitanSettingsDelayed: (delayMs: number) => void;
|
||||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||||
@@ -138,6 +139,7 @@ interface OverlayCliRuntime {
|
|||||||
isInitialized: () => boolean;
|
isInitialized: () => boolean;
|
||||||
initialize: () => void;
|
initialize: () => void;
|
||||||
toggleVisible: () => void;
|
toggleVisible: () => void;
|
||||||
|
togglePrimarySubtitleBar: () => void;
|
||||||
setVisible: (visible: boolean) => void;
|
setVisible: (visible: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +246,7 @@ export function createCliCommandDepsRuntime(
|
|||||||
isOverlayRuntimeInitialized: options.overlay.isInitialized,
|
isOverlayRuntimeInitialized: options.overlay.isInitialized,
|
||||||
initializeOverlayRuntime: options.overlay.initialize,
|
initializeOverlayRuntime: options.overlay.initialize,
|
||||||
toggleVisibleOverlay: options.overlay.toggleVisible,
|
toggleVisibleOverlay: options.overlay.toggleVisible,
|
||||||
|
togglePrimarySubtitleBar: options.overlay.togglePrimarySubtitleBar,
|
||||||
openFirstRunSetup: options.ui.openFirstRunSetup,
|
openFirstRunSetup: options.ui.openFirstRunSetup,
|
||||||
openYomitanSettingsDelayed: (delayMs) => {
|
openYomitanSettingsDelayed: (delayMs) => {
|
||||||
options.schedule(() => {
|
options.schedule(() => {
|
||||||
@@ -369,6 +372,8 @@ export function handleCliCommand(
|
|||||||
|
|
||||||
if (args.toggle || args.toggleVisibleOverlay) {
|
if (args.toggle || args.toggleVisibleOverlay) {
|
||||||
deps.toggleVisibleOverlay();
|
deps.toggleVisibleOverlay();
|
||||||
|
} else if (args.togglePrimarySubtitleBar) {
|
||||||
|
deps.togglePrimarySubtitleBar();
|
||||||
} else if (args.setup) {
|
} else if (args.setup) {
|
||||||
deps.openFirstRunSetup();
|
deps.openFirstRunSetup();
|
||||||
deps.log('Opened first-run setup flow.');
|
deps.log('Opened first-run setup flow.');
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
|||||||
stop: false,
|
stop: false,
|
||||||
toggle: false,
|
toggle: false,
|
||||||
toggleVisibleOverlay: false,
|
toggleVisibleOverlay: false,
|
||||||
|
togglePrimarySubtitleBar: false,
|
||||||
settings: false,
|
settings: false,
|
||||||
setup: false,
|
setup: false,
|
||||||
show: false,
|
show: false,
|
||||||
|
|||||||
@@ -4345,6 +4345,10 @@ function toggleSubtitleSidebar(): void {
|
|||||||
broadcastToOverlayWindows(IPC_CHANNELS.event.subtitleSidebarToggle);
|
broadcastToOverlayWindows(IPC_CHANNELS.event.subtitleSidebarToggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function togglePrimarySubtitleBar(): void {
|
||||||
|
broadcastToOverlayWindows(IPC_CHANNELS.event.primarySubtitleBarToggle);
|
||||||
|
}
|
||||||
|
|
||||||
async function triggerSubsyncFromConfig(): Promise<void> {
|
async function triggerSubsyncFromConfig(): Promise<void> {
|
||||||
await subsyncRuntime.triggerFromConfig();
|
await subsyncRuntime.triggerFromConfig();
|
||||||
}
|
}
|
||||||
@@ -4919,6 +4923,7 @@ const { handleCliCommand, handleInitialArgs } = composeCliStartupHandlers({
|
|||||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||||
initializeOverlayRuntime: () => initializeOverlayRuntime(),
|
initializeOverlayRuntime: () => initializeOverlayRuntime(),
|
||||||
toggleVisibleOverlay: () => toggleVisibleOverlay(),
|
toggleVisibleOverlay: () => toggleVisibleOverlay(),
|
||||||
|
togglePrimarySubtitleBar: () => togglePrimarySubtitleBar(),
|
||||||
openFirstRunSetupWindow: () => openFirstRunSetupWindow(),
|
openFirstRunSetupWindow: () => openFirstRunSetupWindow(),
|
||||||
setVisibleOverlayVisible: (visible: boolean) => setVisibleOverlayVisible(visible),
|
setVisibleOverlayVisible: (visible: boolean) => setVisibleOverlayVisible(visible),
|
||||||
copyCurrentSubtitle: () => copyCurrentSubtitle(),
|
copyCurrentSubtitle: () => copyCurrentSubtitle(),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface CliCommandRuntimeServiceContext {
|
|||||||
isOverlayInitialized: () => boolean;
|
isOverlayInitialized: () => boolean;
|
||||||
initializeOverlay: () => void;
|
initializeOverlay: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
|
togglePrimarySubtitleBar: () => void;
|
||||||
openFirstRunSetup: () => void;
|
openFirstRunSetup: () => void;
|
||||||
setVisibleOverlay: (visible: boolean) => void;
|
setVisibleOverlay: (visible: boolean) => void;
|
||||||
copyCurrentSubtitle: () => void;
|
copyCurrentSubtitle: () => void;
|
||||||
@@ -83,6 +84,7 @@ function createCliCommandDepsFromContext(
|
|||||||
isInitialized: context.isOverlayInitialized,
|
isInitialized: context.isOverlayInitialized,
|
||||||
initialize: context.initializeOverlay,
|
initialize: context.initializeOverlay,
|
||||||
toggleVisible: context.toggleVisibleOverlay,
|
toggleVisible: context.toggleVisibleOverlay,
|
||||||
|
togglePrimarySubtitleBar: context.togglePrimarySubtitleBar,
|
||||||
setVisible: context.setVisibleOverlay,
|
setVisible: context.setVisibleOverlay,
|
||||||
},
|
},
|
||||||
mining: {
|
mining: {
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export interface CliCommandRuntimeServiceDepsParams {
|
|||||||
isInitialized: CliCommandDepsRuntimeOptions['overlay']['isInitialized'];
|
isInitialized: CliCommandDepsRuntimeOptions['overlay']['isInitialized'];
|
||||||
initialize: CliCommandDepsRuntimeOptions['overlay']['initialize'];
|
initialize: CliCommandDepsRuntimeOptions['overlay']['initialize'];
|
||||||
toggleVisible: CliCommandDepsRuntimeOptions['overlay']['toggleVisible'];
|
toggleVisible: CliCommandDepsRuntimeOptions['overlay']['toggleVisible'];
|
||||||
|
togglePrimarySubtitleBar: CliCommandDepsRuntimeOptions['overlay']['togglePrimarySubtitleBar'];
|
||||||
setVisible: CliCommandDepsRuntimeOptions['overlay']['setVisible'];
|
setVisible: CliCommandDepsRuntimeOptions['overlay']['setVisible'];
|
||||||
};
|
};
|
||||||
mining: {
|
mining: {
|
||||||
@@ -325,6 +326,7 @@ export function createCliCommandRuntimeServiceDeps(
|
|||||||
isInitialized: params.overlay.isInitialized,
|
isInitialized: params.overlay.isInitialized,
|
||||||
initialize: params.overlay.initialize,
|
initialize: params.overlay.initialize,
|
||||||
toggleVisible: params.overlay.toggleVisible,
|
toggleVisible: params.overlay.toggleVisible,
|
||||||
|
togglePrimarySubtitleBar: params.overlay.togglePrimarySubtitleBar,
|
||||||
setVisible: params.overlay.setVisible,
|
setVisible: params.overlay.setVisible,
|
||||||
},
|
},
|
||||||
mining: {
|
mining: {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ test('build cli command context deps maps handlers and values', () => {
|
|||||||
isOverlayInitialized: () => true,
|
isOverlayInitialized: () => true,
|
||||||
initializeOverlay: () => calls.push('init'),
|
initializeOverlay: () => calls.push('init'),
|
||||||
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
||||||
|
togglePrimarySubtitleBar: () => calls.push('toggle-primary-subtitle'),
|
||||||
openFirstRunSetup: () => calls.push('setup'),
|
openFirstRunSetup: () => calls.push('setup'),
|
||||||
setVisibleOverlay: (visible) => calls.push(`set-visible:${visible}`),
|
setVisibleOverlay: (visible) => calls.push(`set-visible:${visible}`),
|
||||||
copyCurrentSubtitle: () => calls.push('copy'),
|
copyCurrentSubtitle: () => calls.push('copy'),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export function createBuildCliCommandContextDepsHandler(deps: {
|
|||||||
isOverlayInitialized: () => boolean;
|
isOverlayInitialized: () => boolean;
|
||||||
initializeOverlay: () => void;
|
initializeOverlay: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
|
togglePrimarySubtitleBar: () => void;
|
||||||
openFirstRunSetup: () => void;
|
openFirstRunSetup: () => void;
|
||||||
setVisibleOverlay: (visible: boolean) => void;
|
setVisibleOverlay: (visible: boolean) => void;
|
||||||
copyCurrentSubtitle: () => void;
|
copyCurrentSubtitle: () => void;
|
||||||
@@ -69,6 +70,7 @@ export function createBuildCliCommandContextDepsHandler(deps: {
|
|||||||
isOverlayInitialized: deps.isOverlayInitialized,
|
isOverlayInitialized: deps.isOverlayInitialized,
|
||||||
initializeOverlay: deps.initializeOverlay,
|
initializeOverlay: deps.initializeOverlay,
|
||||||
toggleVisibleOverlay: deps.toggleVisibleOverlay,
|
toggleVisibleOverlay: deps.toggleVisibleOverlay,
|
||||||
|
togglePrimarySubtitleBar: deps.togglePrimarySubtitleBar,
|
||||||
openFirstRunSetup: deps.openFirstRunSetup,
|
openFirstRunSetup: deps.openFirstRunSetup,
|
||||||
setVisibleOverlay: deps.setVisibleOverlay,
|
setVisibleOverlay: deps.setVisibleOverlay,
|
||||||
copyCurrentSubtitle: deps.copyCurrentSubtitle,
|
copyCurrentSubtitle: deps.copyCurrentSubtitle,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ test('cli command context factory composes main deps and context handlers', () =
|
|||||||
showMpvOsd: (text) => calls.push(`osd:${text}`),
|
showMpvOsd: (text) => calls.push(`osd:${text}`),
|
||||||
initializeOverlayRuntime: () => calls.push('init-overlay'),
|
initializeOverlayRuntime: () => calls.push('init-overlay'),
|
||||||
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
||||||
|
togglePrimarySubtitleBar: () => calls.push('toggle-primary-subtitle'),
|
||||||
openFirstRunSetupWindow: () => calls.push('setup'),
|
openFirstRunSetupWindow: () => calls.push('setup'),
|
||||||
setVisibleOverlayVisible: (visible) => calls.push(`set-visible:${visible}`),
|
setVisibleOverlayVisible: (visible) => calls.push(`set-visible:${visible}`),
|
||||||
copyCurrentSubtitle: () => calls.push('copy-sub'),
|
copyCurrentSubtitle: () => calls.push('copy-sub'),
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ test('cli command context main deps builder maps state and callbacks', async ()
|
|||||||
|
|
||||||
initializeOverlayRuntime: () => calls.push('init-overlay'),
|
initializeOverlayRuntime: () => calls.push('init-overlay'),
|
||||||
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
||||||
|
togglePrimarySubtitleBar: () => calls.push('toggle-primary-subtitle'),
|
||||||
openFirstRunSetupWindow: () => calls.push('open-setup'),
|
openFirstRunSetupWindow: () => calls.push('open-setup'),
|
||||||
setVisibleOverlayVisible: (visible) => calls.push(`set-visible:${visible}`),
|
setVisibleOverlayVisible: (visible) => calls.push(`set-visible:${visible}`),
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export function createBuildCliCommandContextMainDepsHandler(deps: {
|
|||||||
|
|
||||||
initializeOverlayRuntime: () => void;
|
initializeOverlayRuntime: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
|
togglePrimarySubtitleBar: () => void;
|
||||||
openFirstRunSetupWindow: () => void;
|
openFirstRunSetupWindow: () => void;
|
||||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||||
|
|
||||||
@@ -94,6 +95,7 @@ export function createBuildCliCommandContextMainDepsHandler(deps: {
|
|||||||
isOverlayInitialized: () => deps.appState.overlayRuntimeInitialized,
|
isOverlayInitialized: () => deps.appState.overlayRuntimeInitialized,
|
||||||
initializeOverlay: () => deps.initializeOverlayRuntime(),
|
initializeOverlay: () => deps.initializeOverlayRuntime(),
|
||||||
toggleVisibleOverlay: () => deps.toggleVisibleOverlay(),
|
toggleVisibleOverlay: () => deps.toggleVisibleOverlay(),
|
||||||
|
togglePrimarySubtitleBar: () => deps.togglePrimarySubtitleBar(),
|
||||||
openFirstRunSetup: () => deps.openFirstRunSetupWindow(),
|
openFirstRunSetup: () => deps.openFirstRunSetupWindow(),
|
||||||
setVisibleOverlay: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
setVisibleOverlay: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
||||||
copyCurrentSubtitle: () => deps.copyCurrentSubtitle(),
|
copyCurrentSubtitle: () => deps.copyCurrentSubtitle(),
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ function createDeps() {
|
|||||||
isOverlayInitialized: () => true,
|
isOverlayInitialized: () => true,
|
||||||
initializeOverlay: () => {},
|
initializeOverlay: () => {},
|
||||||
toggleVisibleOverlay: () => {},
|
toggleVisibleOverlay: () => {},
|
||||||
|
togglePrimarySubtitleBar: () => {},
|
||||||
openFirstRunSetup: () => {},
|
openFirstRunSetup: () => {},
|
||||||
setVisibleOverlay: () => {},
|
setVisibleOverlay: () => {},
|
||||||
copyCurrentSubtitle: () => {},
|
copyCurrentSubtitle: () => {},
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export type CliCommandContextFactoryDeps = {
|
|||||||
isOverlayInitialized: () => boolean;
|
isOverlayInitialized: () => boolean;
|
||||||
initializeOverlay: () => void;
|
initializeOverlay: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
|
togglePrimarySubtitleBar: () => void;
|
||||||
openFirstRunSetup: () => void;
|
openFirstRunSetup: () => void;
|
||||||
setVisibleOverlay: (visible: boolean) => void;
|
setVisibleOverlay: (visible: boolean) => void;
|
||||||
copyCurrentSubtitle: () => void;
|
copyCurrentSubtitle: () => void;
|
||||||
@@ -81,6 +82,7 @@ export function createCliCommandContext(
|
|||||||
isOverlayInitialized: deps.isOverlayInitialized,
|
isOverlayInitialized: deps.isOverlayInitialized,
|
||||||
initializeOverlay: deps.initializeOverlay,
|
initializeOverlay: deps.initializeOverlay,
|
||||||
toggleVisibleOverlay: deps.toggleVisibleOverlay,
|
toggleVisibleOverlay: deps.toggleVisibleOverlay,
|
||||||
|
togglePrimarySubtitleBar: deps.togglePrimarySubtitleBar,
|
||||||
openFirstRunSetup: deps.openFirstRunSetup,
|
openFirstRunSetup: deps.openFirstRunSetup,
|
||||||
setVisibleOverlay: deps.setVisibleOverlay,
|
setVisibleOverlay: deps.setVisibleOverlay,
|
||||||
copyCurrentSubtitle: deps.copyCurrentSubtitle,
|
copyCurrentSubtitle: deps.copyCurrentSubtitle,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ test('composeCliStartupHandlers returns callable CLI startup handlers', () => {
|
|||||||
showMpvOsd: () => {},
|
showMpvOsd: () => {},
|
||||||
initializeOverlayRuntime: () => {},
|
initializeOverlayRuntime: () => {},
|
||||||
toggleVisibleOverlay: () => {},
|
toggleVisibleOverlay: () => {},
|
||||||
|
togglePrimarySubtitleBar: () => {},
|
||||||
openFirstRunSetupWindow: () => {},
|
openFirstRunSetupWindow: () => {},
|
||||||
setVisibleOverlayVisible: () => {},
|
setVisibleOverlayVisible: () => {},
|
||||||
copyCurrentSubtitle: () => {},
|
copyCurrentSubtitle: () => {},
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
|||||||
stop: false,
|
stop: false,
|
||||||
toggle: false,
|
toggle: false,
|
||||||
toggleVisibleOverlay: false,
|
toggleVisibleOverlay: false,
|
||||||
|
togglePrimarySubtitleBar: false,
|
||||||
settings: false,
|
settings: false,
|
||||||
setup: false,
|
setup: false,
|
||||||
show: false,
|
show: false,
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ function hasAnyStartupCommandBeyondSetup(args: CliArgs): boolean {
|
|||||||
return Boolean(
|
return Boolean(
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
args.toggleVisibleOverlay ||
|
args.toggleVisibleOverlay ||
|
||||||
|
args.togglePrimarySubtitleBar ||
|
||||||
args.launchMpv ||
|
args.launchMpv ||
|
||||||
args.settings ||
|
args.settings ||
|
||||||
args.show ||
|
args.show ||
|
||||||
|
|||||||
@@ -153,6 +153,9 @@ const onSubsyncManualOpenEvent = createQueuedIpcListenerWithPayload<SubsyncManua
|
|||||||
const onSubtitleSidebarToggleEvent = createQueuedIpcListener(
|
const onSubtitleSidebarToggleEvent = createQueuedIpcListener(
|
||||||
IPC_CHANNELS.event.subtitleSidebarToggle,
|
IPC_CHANNELS.event.subtitleSidebarToggle,
|
||||||
);
|
);
|
||||||
|
const onPrimarySubtitleBarToggleEvent = createQueuedIpcListener(
|
||||||
|
IPC_CHANNELS.event.primarySubtitleBarToggle,
|
||||||
|
);
|
||||||
const onKikuFieldGroupingRequestEvent =
|
const onKikuFieldGroupingRequestEvent =
|
||||||
createQueuedIpcListenerWithPayload<KikuFieldGroupingRequestData>(
|
createQueuedIpcListenerWithPayload<KikuFieldGroupingRequestData>(
|
||||||
IPC_CHANNELS.event.kikuFieldGroupingRequest,
|
IPC_CHANNELS.event.kikuFieldGroupingRequest,
|
||||||
@@ -345,6 +348,7 @@ const electronAPI: ElectronAPI = {
|
|||||||
onOpenPlaylistBrowser: onOpenPlaylistBrowserEvent,
|
onOpenPlaylistBrowser: onOpenPlaylistBrowserEvent,
|
||||||
onOpenCharacterDictionary: onOpenCharacterDictionaryEvent,
|
onOpenCharacterDictionary: onOpenCharacterDictionaryEvent,
|
||||||
onSubtitleSidebarToggle: onSubtitleSidebarToggleEvent,
|
onSubtitleSidebarToggle: onSubtitleSidebarToggleEvent,
|
||||||
|
onPrimarySubtitleBarToggle: onPrimarySubtitleBarToggleEvent,
|
||||||
onCancelYoutubeTrackPicker: onCancelYoutubeTrackPickerEvent,
|
onCancelYoutubeTrackPicker: onCancelYoutubeTrackPickerEvent,
|
||||||
onKeyboardModeToggleRequested: onKeyboardModeToggleRequestedEvent,
|
onKeyboardModeToggleRequested: onKeyboardModeToggleRequestedEvent,
|
||||||
onLookupWindowToggleRequested: onLookupWindowToggleRequestedEvent,
|
onLookupWindowToggleRequested: onLookupWindowToggleRequestedEvent,
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ function installKeyboardTestGlobals() {
|
|||||||
function createKeyboardHandlerHarness() {
|
function createKeyboardHandlerHarness() {
|
||||||
const testGlobals = installKeyboardTestGlobals();
|
const testGlobals = installKeyboardTestGlobals();
|
||||||
const subtitleRootClassList = createClassList();
|
const subtitleRootClassList = createClassList();
|
||||||
|
const subtitleContainerClassList = createClassList();
|
||||||
let controllerSelectKeydownCount = 0;
|
let controllerSelectKeydownCount = 0;
|
||||||
let openControllerSelectCount = 0;
|
let openControllerSelectCount = 0;
|
||||||
let openControllerDebugCount = 0;
|
let openControllerDebugCount = 0;
|
||||||
@@ -349,6 +350,7 @@ function createKeyboardHandlerHarness() {
|
|||||||
querySelectorAll: () => wordNodes,
|
querySelectorAll: () => wordNodes,
|
||||||
},
|
},
|
||||||
subtitleContainer: {
|
subtitleContainer: {
|
||||||
|
classList: subtitleContainerClassList,
|
||||||
contains: () => false,
|
contains: () => false,
|
||||||
},
|
},
|
||||||
overlay: testGlobals.overlay,
|
overlay: testGlobals.overlay,
|
||||||
@@ -405,6 +407,26 @@ function createKeyboardHandlerHarness() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('primary subtitle visibility key hides and restores the subtitle bar without mpv sub-visibility', async () => {
|
||||||
|
const { ctx, handlers, testGlobals } = createKeyboardHandlerHarness();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await handlers.setupMpvInputForwarding();
|
||||||
|
|
||||||
|
testGlobals.dispatchKeydown({ key: 'v', code: 'KeyV' });
|
||||||
|
assert.equal(ctx.dom.subtitleContainer.classList.contains('primary-sub-hidden'), true);
|
||||||
|
|
||||||
|
testGlobals.dispatchKeydown({ key: 'v', code: 'KeyV' });
|
||||||
|
assert.equal(ctx.dom.subtitleContainer.classList.contains('primary-sub-hidden'), false);
|
||||||
|
assert.equal(
|
||||||
|
testGlobals.mpvCommands.some((command) => command.includes('sub-visibility')),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
testGlobals.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('session help chord resolver follows remapped session bindings', async () => {
|
test('session help chord resolver follows remapped session bindings', async () => {
|
||||||
const { handlers, testGlobals } = createKeyboardHandlerHarness();
|
const { handlers, testGlobals } = createKeyboardHandlerHarness();
|
||||||
|
|
||||||
|
|||||||
@@ -361,6 +361,27 @@ export function createKeyboardHandlers(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPrimarySubtitleVisibilityToggle(e: KeyboardEvent): boolean {
|
||||||
|
return (
|
||||||
|
e.code === 'KeyV' &&
|
||||||
|
!e.ctrlKey &&
|
||||||
|
!e.altKey &&
|
||||||
|
!e.metaKey &&
|
||||||
|
!e.shiftKey &&
|
||||||
|
!e.repeat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePrimarySubtitleBarVisibility(): void {
|
||||||
|
const visible = !ctx.state.primarySubtitleBarVisible;
|
||||||
|
ctx.state.primarySubtitleBarVisible = visible;
|
||||||
|
if (visible) {
|
||||||
|
ctx.dom.subtitleContainer.classList.remove('primary-sub-hidden');
|
||||||
|
} else {
|
||||||
|
ctx.dom.subtitleContainer.classList.add('primary-sub-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleMarkWatched(): Promise<void> {
|
async function handleMarkWatched(): Promise<void> {
|
||||||
const marked = await window.electronAPI.markActiveVideoWatched();
|
const marked = await window.electronAPI.markActiveVideoWatched();
|
||||||
if (marked) {
|
if (marked) {
|
||||||
@@ -1065,6 +1086,12 @@ export function createKeyboardHandlers(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPrimarySubtitleVisibilityToggle(e)) {
|
||||||
|
e.preventDefault();
|
||||||
|
togglePrimarySubtitleBarVisibility();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx.state.yomitanPopupVisible || isYomitanPopupVisible(document)) {
|
if (ctx.state.yomitanPopupVisible || isYomitanPopupVisible(document)) {
|
||||||
if (handleYomitanPopupKeybind(e)) {
|
if (handleYomitanPopupKeybind(e)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -1152,6 +1179,7 @@ export function createKeyboardHandlers(
|
|||||||
updateSessionBindings,
|
updateSessionBindings,
|
||||||
syncKeyboardTokenSelection,
|
syncKeyboardTokenSelection,
|
||||||
handleSubtitleContentUpdated,
|
handleSubtitleContentUpdated,
|
||||||
|
togglePrimarySubtitleBarVisibility,
|
||||||
handleKeyboardModeToggleRequested,
|
handleKeyboardModeToggleRequested,
|
||||||
handleLookupWindowToggleRequested,
|
handleLookupWindowToggleRequested,
|
||||||
closeLookupWindow,
|
closeLookupWindow,
|
||||||
|
|||||||
@@ -532,6 +532,12 @@ function registerKeyboardCommandHandlers(): void {
|
|||||||
await subtitleSidebarModal.toggleSubtitleSidebarModal();
|
await subtitleSidebarModal.toggleSubtitleSidebarModal();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.electronAPI.onPrimarySubtitleBarToggle(() => {
|
||||||
|
runGuarded('primary-subtitle-bar:toggle', () => {
|
||||||
|
keyboardHandlers.togglePrimarySubtitleBarVisibility();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function runGuarded(action: string, fn: () => void): void {
|
function runGuarded(action: string, fn: () => void): void {
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ export type RendererState = {
|
|||||||
keyboardSelectionVisible: boolean;
|
keyboardSelectionVisible: boolean;
|
||||||
keyboardSelectedWordIndex: number | null;
|
keyboardSelectedWordIndex: number | null;
|
||||||
yomitanPopupVisible: boolean;
|
yomitanPopupVisible: boolean;
|
||||||
|
primarySubtitleBarVisible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createRendererState(): RendererState {
|
export function createRendererState(): RendererState {
|
||||||
@@ -244,5 +245,6 @@ export function createRendererState(): RendererState {
|
|||||||
keyboardSelectionVisible: false,
|
keyboardSelectionVisible: false,
|
||||||
keyboardSelectedWordIndex: null,
|
keyboardSelectedWordIndex: null,
|
||||||
yomitanPopupVisible: false,
|
yomitanPopupVisible: false,
|
||||||
|
primarySubtitleBarVisible: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -678,6 +678,11 @@ body.subtitle-sidebar-embedded-open #subtitleContainer {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#subtitleContainer.primary-sub-hidden {
|
||||||
|
display: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
body.settings-modal-open #subtitleContainer {
|
body.settings-modal-open #subtitleContainer {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export const IPC_CHANNELS = {
|
|||||||
controllerSelectOpen: 'controller-select:open',
|
controllerSelectOpen: 'controller-select:open',
|
||||||
controllerDebugOpen: 'controller-debug:open',
|
controllerDebugOpen: 'controller-debug:open',
|
||||||
subtitleSidebarToggle: 'subtitle-sidebar:toggle',
|
subtitleSidebarToggle: 'subtitle-sidebar:toggle',
|
||||||
|
primarySubtitleBarToggle: 'primary-subtitle-bar:toggle',
|
||||||
configHotReload: 'config:hot-reload',
|
configHotReload: 'config:hot-reload',
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -433,6 +433,7 @@ export interface ElectronAPI {
|
|||||||
onOpenPlaylistBrowser: (callback: () => void) => void;
|
onOpenPlaylistBrowser: (callback: () => void) => void;
|
||||||
onOpenCharacterDictionary: (callback: () => void) => void;
|
onOpenCharacterDictionary: (callback: () => void) => void;
|
||||||
onSubtitleSidebarToggle: (callback: () => void) => void;
|
onSubtitleSidebarToggle: (callback: () => void) => void;
|
||||||
|
onPrimarySubtitleBarToggle: (callback: () => void) => void;
|
||||||
onCancelYoutubeTrackPicker: (callback: () => void) => void;
|
onCancelYoutubeTrackPicker: (callback: () => void) => void;
|
||||||
onKeyboardModeToggleRequested: (callback: () => void) => void;
|
onKeyboardModeToggleRequested: (callback: () => void) => void;
|
||||||
onLookupWindowToggleRequested: (callback: () => void) => void;
|
onLookupWindowToggleRequested: (callback: () => void) => void;
|
||||||
|
|||||||
Reference in New Issue
Block a user