mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
refactor: remove invisible subtitle overlay code
This commit is contained in:
@@ -73,7 +73,7 @@ subminer app --start --yomitan
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
subminer app --start --background
|
subminer app --start --background
|
||||||
subminer video.mkv # toggle invisible overlay with y-i and visible overlay with y-t
|
subminer video.mkv # y-t toggles overlay visibility
|
||||||
```
|
```
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ make docs-preview # Preview built site at http://localhost:4173
|
|||||||
|
|
||||||
- [Installation](/installation) — Requirements, Linux/macOS/Windows install, mpv plugin setup
|
- [Installation](/installation) — Requirements, Linux/macOS/Windows install, mpv plugin setup
|
||||||
- [Usage](/usage) — `subminer` wrapper + subcommands (`jellyfin`, `yt`, `doctor`, `config`, `mpv`, `texthooker`, `app`), mpv plugin, keybindings
|
- [Usage](/usage) — `subminer` wrapper + subcommands (`jellyfin`, `yt`, `doctor`, `config`, `mpv`, `texthooker`, `app`), mpv plugin, keybindings
|
||||||
- [Mining Workflow](/mining-workflow) — End-to-end sentence mining guide, overlay layers, card creation
|
- [Mining Workflow](/mining-workflow) — End-to-end sentence mining guide, single overlay + modals, card creation
|
||||||
|
|
||||||
### Reference
|
### Reference
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ The configuration file includes several main sections:
|
|||||||
- [**Auto-Start Overlay**](#auto-start-overlay) - Automatically show overlay on MPV connection
|
- [**Auto-Start Overlay**](#auto-start-overlay) - Automatically show overlay on MPV connection
|
||||||
- [**Visible Overlay Subtitle Binding**](#visible-overlay-subtitle-binding) - Link visible overlay toggles to MPV subtitle visibility
|
- [**Visible Overlay Subtitle Binding**](#visible-overlay-subtitle-binding) - Link visible overlay toggles to MPV subtitle visibility
|
||||||
- [**Auto Subtitle Sync**](#auto-subtitle-sync) - Sync current subtitle with `alass`/`ffsubsync`
|
- [**Auto Subtitle Sync**](#auto-subtitle-sync) - Sync current subtitle with `alass`/`ffsubsync`
|
||||||
- [**Invisible Overlay**](#invisible-overlay) - Startup visibility behavior for the invisible mining layer
|
- [**Subtitle Position Edit**](#subtitle-position-edit) - Fine-tune subtitle alignment in overlay
|
||||||
- [**Jimaku**](#jimaku) - Jimaku API configuration and defaults
|
- [**Jimaku**](#jimaku) - Jimaku API configuration and defaults
|
||||||
- [**AniList**](#anilist) - Optional post-watch progress updates
|
- [**AniList**](#anilist) - Optional post-watch progress updates
|
||||||
- [**Jellyfin**](#jellyfin) - Optional Jellyfin auth, library listing, and playback launch
|
- [**Jellyfin**](#jellyfin) - Optional Jellyfin auth, library listing, and playback launch
|
||||||
@@ -338,7 +338,7 @@ Control whether the overlay automatically becomes visible when it connects to mp
|
|||||||
| -------------------- | --------------- | ------------------------------------------------------ |
|
| -------------------- | --------------- | ------------------------------------------------------ |
|
||||||
| `auto_start_overlay` | `true`, `false` | Auto-show overlay on mpv connection (default: `false`) |
|
| `auto_start_overlay` | `true`, `false` | Auto-show overlay on mpv connection (default: `false`) |
|
||||||
|
|
||||||
The mpv plugin controls startup per layer via `auto_start_visible_overlay` and `auto_start_invisible_overlay` in `subminer.conf` (`platform-default` for invisible means hidden on Linux, visible on macOS/Windows).
|
The mpv plugin controls startup overlay visibility via `auto_start_visible_overlay` in `subminer.conf`.
|
||||||
|
|
||||||
### Visible Overlay Subtitle Binding
|
### Visible Overlay Subtitle Binding
|
||||||
|
|
||||||
@@ -379,20 +379,12 @@ Sync the active subtitle track using `alass` (preferred) or `ffsubsync`:
|
|||||||
Default trigger is `Ctrl+Alt+S` via `shortcuts.triggerSubsync`.
|
Default trigger is `Ctrl+Alt+S` via `shortcuts.triggerSubsync`.
|
||||||
Customize it there, or set it to `null` to disable.
|
Customize it there, or set it to `null` to disable.
|
||||||
|
|
||||||
### Invisible Overlay
|
### Subtitle Position Edit
|
||||||
|
|
||||||
SubMiner includes a second subtitle mining layer that can be visually invisible while still interactive for Yomitan lookups.
|
Subtitle positioning can be adjusted directly in the overlay:
|
||||||
|
|
||||||
- `invisibleOverlay.startupVisibility` values:
|
|
||||||
|
|
||||||
1. `"platform-default"`: hidden on Wayland, visible on Windows/macOS/other sessions.
|
|
||||||
2. `"visible"`: always shown on startup.
|
|
||||||
3. `"hidden"`: always hidden on startup.
|
|
||||||
|
|
||||||
Invisible subtitle positioning can be adjusted directly in the invisible layer:
|
|
||||||
|
|
||||||
- `Ctrl/Cmd+Shift+P` toggles position edit mode.
|
- `Ctrl/Cmd+Shift+P` toggles position edit mode.
|
||||||
- Use arrow keys to move the invisible subtitle text.
|
- Use arrow keys to move subtitle text.
|
||||||
- Press `Enter` or `Ctrl/Cmd+S` to save, or `Esc` to cancel.
|
- Press `Enter` or `Ctrl/Cmd+S` to save, or `Esc` to cancel.
|
||||||
- This edit-mode shortcut is fixed (not currently configurable in `shortcuts`/`keybindings`).
|
- This edit-mode shortcut is fixed (not currently configurable in `shortcuts`/`keybindings`).
|
||||||
|
|
||||||
@@ -670,7 +662,6 @@ See `config.example.jsonc` for detailed configuration options.
|
|||||||
{
|
{
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O",
|
"toggleVisibleOverlayGlobal": "Alt+Shift+O",
|
||||||
"toggleInvisibleOverlayGlobal": "Alt+Shift+I",
|
|
||||||
"copySubtitle": "CommandOrControl+C",
|
"copySubtitle": "CommandOrControl+C",
|
||||||
"copySubtitleMultiple": "CommandOrControl+Shift+C",
|
"copySubtitleMultiple": "CommandOrControl+Shift+C",
|
||||||
"updateLastCardFromClipboard": "CommandOrControl+V",
|
"updateLastCardFromClipboard": "CommandOrControl+V",
|
||||||
@@ -689,7 +680,6 @@ See `config.example.jsonc` for detailed configuration options.
|
|||||||
| Option | Values | Description |
|
| Option | Values | Description |
|
||||||
| ------------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `toggleVisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling visible subtitle overlay (default: `"Alt+Shift+O"`) |
|
| `toggleVisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling visible subtitle overlay (default: `"Alt+Shift+O"`) |
|
||||||
| `toggleInvisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling invisible interactive overlay (default: `"Alt+Shift+I"`) |
|
|
||||||
| `copySubtitle` | string \| `null` | Accelerator for copying current subtitle (default: `"CommandOrControl+C"`) |
|
| `copySubtitle` | string \| `null` | Accelerator for copying current subtitle (default: `"CommandOrControl+C"`) |
|
||||||
| `copySubtitleMultiple` | string \| `null` | Accelerator for multi-copy mode (default: `"CommandOrControl+Shift+C"`) |
|
| `copySubtitleMultiple` | string \| `null` | Accelerator for multi-copy mode (default: `"CommandOrControl+Shift+C"`) |
|
||||||
| `updateLastCardFromClipboard` | string \| `null` | Accelerator for updating card from clipboard (default: `"CommandOrControl+V"`) |
|
| `updateLastCardFromClipboard` | string \| `null` | Accelerator for updating card from clipboard (default: `"CommandOrControl+V"`) |
|
||||||
|
|||||||
@@ -181,9 +181,6 @@ 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 |
|
||||||
| `y-i` | Toggle invisible overlay |
|
|
||||||
| `y-I` | Show invisible overlay |
|
|
||||||
| `y-u` | Hide invisible overlay |
|
|
||||||
| `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 |
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ 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 |
|
||||||
| `y-i` | Toggle invisible overlay |
|
|
||||||
| `y-I` | Show invisible overlay |
|
|
||||||
| `y-u` | Hide invisible overlay |
|
|
||||||
| `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 |
|
||||||
@@ -50,10 +47,9 @@ SubMiner:
|
|||||||
1. Start overlay
|
1. Start overlay
|
||||||
2. Stop overlay
|
2. Stop overlay
|
||||||
3. Toggle overlay
|
3. Toggle overlay
|
||||||
4. Toggle invisible overlay
|
4. Open options
|
||||||
5. Open options
|
5. Restart overlay
|
||||||
6. Restart overlay
|
6. Check status
|
||||||
7. Check status
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Select an item by pressing its number.
|
Select an item by pressing its number.
|
||||||
@@ -84,10 +80,6 @@ auto_start=no
|
|||||||
# Show the visible overlay on auto-start.
|
# Show the visible overlay on auto-start.
|
||||||
auto_start_visible_overlay=no
|
auto_start_visible_overlay=no
|
||||||
|
|
||||||
# Invisible overlay startup: platform-default, visible, hidden.
|
|
||||||
# platform-default = hidden on Linux, visible on macOS/Windows.
|
|
||||||
auto_start_invisible_overlay=platform-default
|
|
||||||
|
|
||||||
# Show OSD messages for overlay status changes.
|
# Show OSD messages for overlay status changes.
|
||||||
osd_messages=yes
|
osd_messages=yes
|
||||||
|
|
||||||
@@ -129,7 +121,6 @@ aniskip_button_duration=3
|
|||||||
| `backend` | `auto` | `auto`, `hyprland`, `sway`, `x11`, `macos` | Window manager backend |
|
| `backend` | `auto` | `auto`, `hyprland`, `sway`, `x11`, `macos` | Window manager backend |
|
||||||
| `auto_start` | `no` | `yes` / `no` | Auto-start overlay on file load |
|
| `auto_start` | `no` | `yes` / `no` | Auto-start overlay on file load |
|
||||||
| `auto_start_visible_overlay` | `no` | `yes` / `no` | Show visible layer on auto-start |
|
| `auto_start_visible_overlay` | `no` | `yes` / `no` | Show visible layer on auto-start |
|
||||||
| `auto_start_invisible_overlay` | `platform-default` | `platform-default`, `visible`, `hidden` | Invisible layer on auto-start |
|
|
||||||
| `osd_messages` | `yes` | `yes` / `no` | Show OSD status messages |
|
| `osd_messages` | `yes` | `yes` / `no` | Show OSD status messages |
|
||||||
| `log_level` | `info` | `debug`, `info`, `warn`, `error` | Log verbosity |
|
| `log_level` | `info` | `debug`, `info`, `warn`, `error` | Log verbosity |
|
||||||
| `aniskip_enabled` | `yes` | `yes` / `no` | Enable AniSkip intro detection |
|
| `aniskip_enabled` | `yes` | `yes` / `no` | Enable AniSkip intro detection |
|
||||||
@@ -182,9 +173,6 @@ The plugin can be controlled from other mpv scripts or the mpv command line usin
|
|||||||
script-message subminer-start
|
script-message subminer-start
|
||||||
script-message subminer-stop
|
script-message subminer-stop
|
||||||
script-message subminer-toggle
|
script-message subminer-toggle
|
||||||
script-message subminer-toggle-invisible
|
|
||||||
script-message subminer-show-invisible
|
|
||||||
script-message subminer-hide-invisible
|
|
||||||
script-message subminer-menu
|
script-message subminer-menu
|
||||||
script-message subminer-options
|
script-message subminer-options
|
||||||
script-message subminer-restart
|
script-message subminer-restart
|
||||||
|
|||||||
@@ -53,7 +53,6 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Toggle visible overlay global setting.
|
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Toggle visible overlay global setting.
|
||||||
"toggleInvisibleOverlayGlobal": "Alt+Shift+I", // Toggle invisible overlay global setting.
|
|
||||||
"copySubtitle": "CommandOrControl+C", // Copy subtitle setting.
|
"copySubtitle": "CommandOrControl+C", // Copy subtitle setting.
|
||||||
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Copy subtitle multiple setting.
|
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Copy subtitle multiple setting.
|
||||||
"updateLastCardFromClipboard": "CommandOrControl+V", // Update last card from clipboard setting.
|
"updateLastCardFromClipboard": "CommandOrControl+V", // Update last card from clipboard setting.
|
||||||
@@ -68,16 +67,6 @@
|
|||||||
"openJimaku": "Ctrl+Shift+J" // Open jimaku setting.
|
"openJimaku": "Ctrl+Shift+J" // Open jimaku setting.
|
||||||
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
|
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// Invisible Overlay
|
|
||||||
// Startup behavior for the invisible interactive subtitle mining layer.
|
|
||||||
// Invisible subtitle position edit mode: Ctrl/Cmd+Shift+P to toggle, arrow keys to move, Enter or Ctrl/Cmd+S to save, Esc to cancel.
|
|
||||||
// This edit-mode shortcut is fixed and is not currently configurable.
|
|
||||||
// ==========================================
|
|
||||||
"invisibleOverlay": {
|
|
||||||
"startupVisibility": "platform-default" // Startup visibility setting.
|
|
||||||
}, // Startup behavior for the invisible interactive subtitle mining layer.
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Keybindings (MPV Commands)
|
// Keybindings (MPV Commands)
|
||||||
// Extra keybindings that are merged with built-in defaults.
|
// Extra keybindings that are merged with built-in defaults.
|
||||||
@@ -123,9 +112,11 @@
|
|||||||
// Hot-reload: subtitle style changes apply live without restarting SubMiner.
|
// Hot-reload: subtitle style changes apply live without restarting SubMiner.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"subtitleStyle": {
|
"subtitleStyle": {
|
||||||
|
// Additional CSS declarations are also allowed and applied directly to subtitle roots/containers (for example: lineHeight, textShadow, -webkit-text-stroke, backdropFilter).
|
||||||
"enableJlpt": false, // Enable JLPT vocabulary level underlines. When disabled, JLPT tagging lookup and underlines are skipped. Values: true | false
|
"enableJlpt": false, // Enable JLPT vocabulary level underlines. When disabled, JLPT tagging lookup and underlines are skipped. Values: true | false
|
||||||
"preserveLineBreaks": false, // Preserve line breaks in visible overlay subtitle rendering. When false, line breaks are flattened to spaces for a single-line flow. Values: true | false
|
"preserveLineBreaks": false, // Preserve line breaks in visible overlay subtitle rendering. When false, line breaks are flattened to spaces for a single-line flow. Values: true | false
|
||||||
"hoverTokenColor": "#c6a0f6", // Hex color used for hovered subtitle token highlight in mpv.
|
"hoverTokenColor": "#f4dbd6", // Hex color used for hovered subtitle token highlight in the overlay.
|
||||||
|
"hoverTokenBackgroundColor": "#363a4fd6", // CSS color used for hovered subtitle token background highlight in the overlay.
|
||||||
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif", // Font family setting.
|
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif", // Font family setting.
|
||||||
"fontSize": 35, // Font size setting.
|
"fontSize": 35, // Font size setting.
|
||||||
"fontColor": "#cad3f5", // Font color setting.
|
"fontColor": "#cad3f5", // Font color setting.
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ These work system-wide regardless of which window has focus.
|
|||||||
| Shortcut | Action | Configurable |
|
| Shortcut | Action | Configurable |
|
||||||
| ------------- | ------------------------ | ---------------------------------------- |
|
| ------------- | ------------------------ | ---------------------------------------- |
|
||||||
| `Alt+Shift+O` | Toggle visible overlay | `shortcuts.toggleVisibleOverlayGlobal` |
|
| `Alt+Shift+O` | Toggle visible overlay | `shortcuts.toggleVisibleOverlayGlobal` |
|
||||||
| `Alt+Shift+I` | Toggle invisible overlay | `shortcuts.toggleInvisibleOverlayGlobal` |
|
|
||||||
| `Alt+Shift+Y` | Open Yomitan settings | Fixed (not configurable) |
|
| `Alt+Shift+Y` | Open Yomitan settings | Fixed (not configurable) |
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
@@ -64,9 +63,9 @@ These keybindings can be overridden or disabled via the `keybindings` config arr
|
|||||||
| `Ctrl+Shift+J` | Open Jimaku subtitle search modal | `shortcuts.openJimaku` |
|
| `Ctrl+Shift+J` | Open Jimaku subtitle search modal | `shortcuts.openJimaku` |
|
||||||
| `Ctrl+Alt+S` | Open subtitle sync (subsync) modal | `shortcuts.triggerSubsync` |
|
| `Ctrl+Alt+S` | Open subtitle sync (subsync) modal | `shortcuts.triggerSubsync` |
|
||||||
|
|
||||||
## Invisible Subtitle Position Edit Mode
|
## Subtitle Position Edit Mode
|
||||||
|
|
||||||
Enter edit mode to fine-tune invisible overlay alignment with mpv's native subtitles.
|
Enter edit mode to fine-tune subtitle alignment.
|
||||||
|
|
||||||
| Shortcut | Action |
|
| Shortcut | Action |
|
||||||
| --------------------- | -------------------------------- |
|
| --------------------- | -------------------------------- |
|
||||||
@@ -86,9 +85,6 @@ 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 |
|
||||||
| `y-i` | Toggle invisible overlay |
|
|
||||||
| `y-I` | Show invisible overlay |
|
|
||||||
| `y-u` | Hide invisible overlay |
|
|
||||||
| `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 |
|
||||||
@@ -112,7 +108,6 @@ All `shortcuts.*` keys accept [Electron accelerator strings](https://www.electro
|
|||||||
"mineSentence": "CommandOrControl+S",
|
"mineSentence": "CommandOrControl+S",
|
||||||
"copySubtitle": "CommandOrControl+C",
|
"copySubtitle": "CommandOrControl+C",
|
||||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O",
|
"toggleVisibleOverlayGlobal": "Alt+Shift+O",
|
||||||
"toggleInvisibleOverlayGlobal": "Alt+Shift+I",
|
|
||||||
"openJimaku": null, // disabled
|
"openJimaku": null, // disabled
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ SubMiner positions the overlay by tracking the mpv window. If tracking fails:
|
|||||||
- Sway: Ensure `swaymsg` is available.
|
- Sway: Ensure `swaymsg` is available.
|
||||||
- X11: Ensure `xdotool` and `xwininfo` are installed.
|
- X11: Ensure `xdotool` and `xwininfo` are installed.
|
||||||
|
|
||||||
If the overlay position is slightly off, use invisible subtitle position edit mode (`Ctrl/Cmd+Shift+P`) to fine-tune the offset with arrow keys, then save with `Enter` or `Ctrl+S`.
|
If the overlay position is slightly off, use subtitle position edit mode (`Ctrl/Cmd+Shift+P`) to fine-tune the offset with arrow keys, then save with `Enter` or `Ctrl+S`.
|
||||||
|
|
||||||
## Yomitan
|
## Yomitan
|
||||||
|
|
||||||
@@ -217,10 +217,10 @@ Media generation has a 30-second timeout (60 seconds for animated AVIF). If your
|
|||||||
|
|
||||||
**"Failed to register global shortcut"**
|
**"Failed to register global shortcut"**
|
||||||
|
|
||||||
Global shortcuts (`Alt+Shift+O`, `Alt+Shift+I`, `Alt+Shift+Y`) may conflict with other applications or desktop environment keybindings.
|
Global shortcuts (`Alt+Shift+O`, `Alt+Shift+Y`) may conflict with other applications or desktop environment keybindings.
|
||||||
|
|
||||||
- Check your DE/WM keybinding settings for conflicts.
|
- Check your DE/WM keybinding settings for conflicts.
|
||||||
- Change the shortcuts in your config under `shortcuts.toggleVisibleOverlayGlobal`, `shortcuts.toggleInvisibleOverlayGlobal`.
|
- Change the shortcut in your config under `shortcuts.toggleVisibleOverlayGlobal`.
|
||||||
- On Wayland, global shortcut registration has limitations depending on the compositor.
|
- On Wayland, global shortcut registration has limitations depending on the compositor.
|
||||||
|
|
||||||
**Overlay keybindings not working**
|
**Overlay keybindings not working**
|
||||||
@@ -273,5 +273,5 @@ The Jimaku API has rate limits. If you see 429 errors, wait for the retry durati
|
|||||||
### macOS
|
### macOS
|
||||||
|
|
||||||
- **Accessibility permission**: Required for window tracking. Grant it in System Settings > Privacy & Security > Accessibility.
|
- **Accessibility permission**: Required for window tracking. Grant it in System Settings > Privacy & Security > Accessibility.
|
||||||
- **Font rendering**: macOS uses a 0.87x font compensation factor for subtitle alignment between mpv and the overlay. If text alignment looks off, adjust the invisible subtitle offset.
|
- **Font rendering**: macOS uses a 0.87x font compensation factor for subtitle alignment between mpv and the overlay. If text alignment looks off, adjust subtitle offset in position edit mode.
|
||||||
- **Gatekeeper**: If macOS blocks SubMiner, right-click the app and select "Open" to bypass the warning, or remove the quarantine attribute: `xattr -d com.apple.quarantine /path/to/SubMiner.app`
|
- **Gatekeeper**: If macOS blocks SubMiner, right-click the app and select "Open" to bypass the warning, or remove the quarantine attribute: `xattr -d com.apple.quarantine /path/to/SubMiner.app`
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ There are two ways to use SubMiner — the `subminer` wrapper script or the mpv
|
|||||||
| Approach | Best For |
|
| Approach | Best For |
|
||||||
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **subminer script** | All-in-one solution. Handles video selection, launches MPV with the correct socket, and manages app commands. Overlay start is explicit (`--start`, `-S`, or `y-s`). |
|
| **subminer script** | All-in-one solution. Handles video selection, launches MPV with the correct socket, and manages app commands. Overlay start is explicit (`--start`, `-S`, or `y-s`). |
|
||||||
| **MPV plugin** | When you launch MPV yourself or from other tools. Provides in-MPV chord keybindings (e.g. `y-y` for menu) to control visible and invisible overlay layers. Requires `--input-ipc-server=/tmp/subminer-socket`. |
|
| **MPV plugin** | When you launch MPV yourself or from other tools. Provides in-MPV chord keybindings (e.g. `y-y` for menu) to control overlay visibility. Requires `--input-ipc-server=/tmp/subminer-socket`. |
|
||||||
|
|
||||||
You can use both together—install the plugin for on-demand control, but use `subminer` when you want the streamlined workflow.
|
You can use both together—install the plugin for on-demand control, but use `subminer` when you want the streamlined workflow.
|
||||||
|
|
||||||
@@ -68,11 +68,8 @@ SubMiner.AppImage --start --texthooker # Start overlay with texthooker
|
|||||||
SubMiner.AppImage --texthooker # Launch texthooker only (no overlay window)
|
SubMiner.AppImage --texthooker # Launch texthooker only (no overlay window)
|
||||||
SubMiner.AppImage --stop # Stop overlay
|
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 --start --toggle-invisible-overlay # Start MPV IPC + toggle invisible layer
|
|
||||||
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 --show-invisible-overlay # Force show invisible overlay
|
|
||||||
SubMiner.AppImage --hide-invisible-overlay # Force hide invisible overlay
|
|
||||||
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
|
||||||
@@ -173,7 +170,6 @@ Notes:
|
|||||||
| Keybind | Action |
|
| Keybind | Action |
|
||||||
| ------------- | ------------------------ |
|
| ------------- | ------------------------ |
|
||||||
| `Alt+Shift+O` | Toggle visible overlay |
|
| `Alt+Shift+O` | Toggle visible overlay |
|
||||||
| `Alt+Shift+I` | Toggle invisible overlay |
|
|
||||||
| `Alt+Shift+Y` | Open Yomitan settings |
|
| `Alt+Shift+Y` | Open Yomitan settings |
|
||||||
|
|
||||||
`Alt+Shift+Y` is a fixed global shortcut; it is not part of `shortcuts` config.
|
`Alt+Shift+Y` is a fixed global shortcut; it is not part of `shortcuts` config.
|
||||||
@@ -195,10 +191,10 @@ Notes:
|
|||||||
| `Ctrl+W` | Quit mpv |
|
| `Ctrl+W` | Quit mpv |
|
||||||
| `Right-click` | Toggle MPV pause (outside subtitle area) |
|
| `Right-click` | Toggle MPV pause (outside subtitle area) |
|
||||||
| `Right-click + drag` | Move subtitle position (on subtitle) |
|
| `Right-click + drag` | Move subtitle position (on subtitle) |
|
||||||
| `Ctrl/Cmd+Shift+P` | Toggle invisible subtitle position edit mode |
|
| `Ctrl/Cmd+Shift+P` | Toggle subtitle position edit mode |
|
||||||
| `Arrow keys` | Move invisible subtitles while edit mode is active |
|
| `Arrow keys` | Move subtitles while edit mode is active |
|
||||||
| `Enter` / `Ctrl+S` | Save invisible subtitle position in edit mode |
|
| `Enter` / `Ctrl+S` | Save subtitle position in edit mode |
|
||||||
| `Esc` | Cancel invisible subtitle position edit mode |
|
| `Esc` | Cancel subtitle position edit mode |
|
||||||
| `Ctrl/Cmd+A` | Append clipboard video path to MPV playlist |
|
| `Ctrl/Cmd+A` | Append clipboard video path to MPV playlist |
|
||||||
|
|
||||||
These keybindings only work when the overlay window has focus. See [Configuration](/configuration) for customization.
|
These keybindings only work when the overlay window has focus. See [Configuration](/configuration) for customization.
|
||||||
|
|||||||
@@ -26,11 +26,6 @@ auto_start=no
|
|||||||
# Automatically show visible overlay when overlay starts
|
# Automatically show visible overlay when overlay starts
|
||||||
auto_start_visible_overlay=no
|
auto_start_visible_overlay=no
|
||||||
|
|
||||||
# Automatically show invisible overlay when overlay starts
|
|
||||||
# Values: platform-default, visible, hidden
|
|
||||||
# platform-default => hidden on Linux, visible on macOS/Windows
|
|
||||||
auto_start_invisible_overlay=platform-default
|
|
||||||
|
|
||||||
# Legacy alias (maps to auto_start_visible_overlay)
|
# Legacy alias (maps to auto_start_visible_overlay)
|
||||||
# auto_start_overlay=no
|
# auto_start_overlay=no
|
||||||
|
|
||||||
@@ -70,4 +65,3 @@ aniskip_button_duration=3
|
|||||||
|
|
||||||
# MPV keybindings provided by plugin/subminer.lua:
|
# MPV keybindings provided by plugin/subminer.lua:
|
||||||
# y-s start, y-S stop, y-t toggle visible overlay
|
# y-s start, y-S stop, y-t toggle visible overlay
|
||||||
# y-i toggle invisible overlay, y-I show invisible overlay, y-u hide invisible overlay
|
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ local function default_socket_path()
|
|||||||
return "/tmp/subminer-socket"
|
return "/tmp/subminer-socket"
|
||||||
end
|
end
|
||||||
|
|
||||||
local function is_linux()
|
|
||||||
return not is_windows() and not is_macos()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_subminer_process_running()
|
local function is_subminer_process_running()
|
||||||
local command = is_windows() and { "tasklist", "/FO", "CSV", "/NH" } or { "ps", "-A", "-o", "args=" }
|
local command = is_windows() and { "tasklist", "/FO", "CSV", "/NH" } or { "ps", "-A", "-o", "args=" }
|
||||||
local result = mp.command_native({
|
local result = mp.command_native({
|
||||||
@@ -140,7 +136,6 @@ local opts = {
|
|||||||
auto_start = true,
|
auto_start = true,
|
||||||
auto_start_overlay = false, -- legacy alias, maps to auto_start_visible_overlay
|
auto_start_overlay = false, -- legacy alias, maps to auto_start_visible_overlay
|
||||||
auto_start_visible_overlay = false,
|
auto_start_visible_overlay = false,
|
||||||
auto_start_invisible_overlay = "platform-default", -- platform-default | visible | hidden
|
|
||||||
osd_messages = true,
|
osd_messages = true,
|
||||||
log_level = "info",
|
log_level = "info",
|
||||||
aniskip_enabled = true,
|
aniskip_enabled = true,
|
||||||
@@ -163,7 +158,6 @@ local state = {
|
|||||||
binary_available = false,
|
binary_available = false,
|
||||||
binary_path = nil,
|
binary_path = nil,
|
||||||
detected_backend = nil,
|
detected_backend = nil,
|
||||||
invisible_overlay_visible = false,
|
|
||||||
hover_highlight = {
|
hover_highlight = {
|
||||||
revision = -1,
|
revision = -1,
|
||||||
payload = nil,
|
payload = nil,
|
||||||
@@ -796,6 +790,15 @@ local function fix_ass_color(input, fallback)
|
|||||||
return b .. g .. r
|
return b .. g .. r
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function sanitize_hover_ass_color(input, fallback_rgb)
|
||||||
|
local fallback = fix_ass_color(fallback_rgb or DEFAULT_HOVER_COLOR, DEFAULT_HOVER_COLOR)
|
||||||
|
local converted = fix_ass_color(input, fallback)
|
||||||
|
if converted == "000000" then
|
||||||
|
return fallback
|
||||||
|
end
|
||||||
|
return converted
|
||||||
|
end
|
||||||
|
|
||||||
local function escape_ass_text(text)
|
local function escape_ass_text(text)
|
||||||
return (text or "")
|
return (text or "")
|
||||||
:gsub("\\", "\\\\")
|
:gsub("\\", "\\\\")
|
||||||
@@ -858,7 +861,7 @@ local function resolve_metrics()
|
|||||||
border = sub_border_size * window_scale,
|
border = sub_border_size * window_scale,
|
||||||
shadow = sub_shadow_offset * window_scale,
|
shadow = sub_shadow_offset * window_scale,
|
||||||
base_color = fix_ass_color(mp.get_property("sub-color"), DEFAULT_HOVER_BASE_COLOR),
|
base_color = fix_ass_color(mp.get_property("sub-color"), DEFAULT_HOVER_BASE_COLOR),
|
||||||
hover_color = fix_ass_color(mp.get_property("sub-color"), DEFAULT_HOVER_COLOR),
|
hover_color = sanitize_hover_ass_color(nil, DEFAULT_HOVER_COLOR),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1068,7 +1071,7 @@ local function build_hover_subtitle_content(payload)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local metrics = resolve_metrics()
|
local metrics = resolve_metrics()
|
||||||
local hover_color = fix_ass_color(payload.colors and payload.colors.hover or nil, metrics.hover_color)
|
local hover_color = sanitize_hover_ass_color(payload.colors and payload.colors.hover or nil, DEFAULT_HOVER_COLOR)
|
||||||
local base_color = fix_ass_color(payload.colors and payload.colors.base or nil, metrics.base_color)
|
local base_color = fix_ass_color(payload.colors and payload.colors.base or nil, metrics.base_color)
|
||||||
return inject_hover_color_to_ass(source_ass, plain_map, hover_start, hover_end, hover_color, base_color)
|
return inject_hover_color_to_ass(source_ass, plain_map, hover_start, hover_end, hover_color, base_color)
|
||||||
end
|
end
|
||||||
@@ -1443,39 +1446,13 @@ local function resolve_visible_overlay_startup()
|
|||||||
return visible
|
return visible
|
||||||
end
|
end
|
||||||
|
|
||||||
local function resolve_invisible_overlay_startup()
|
|
||||||
local raw = opts.auto_start_invisible_overlay
|
|
||||||
if type(raw) == "boolean" then
|
|
||||||
return raw
|
|
||||||
end
|
|
||||||
|
|
||||||
local mode = type(raw) == "string" and raw:lower() or "platform-default"
|
|
||||||
if mode == "visible" or mode == "show" or mode == "yes" or mode == "true" or mode == "on" then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
if mode == "hidden" or mode == "hide" or mode == "no" or mode == "false" or mode == "off" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- platform-default
|
|
||||||
return not is_linux()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function apply_startup_overlay_preferences()
|
local function apply_startup_overlay_preferences()
|
||||||
local should_show_visible = resolve_visible_overlay_startup()
|
local should_show_visible = resolve_visible_overlay_startup()
|
||||||
local should_show_invisible = resolve_invisible_overlay_startup()
|
|
||||||
|
|
||||||
local visible_action = should_show_visible and "show-visible-overlay" or "hide-visible-overlay"
|
local visible_action = should_show_visible and "show-visible-overlay" or "hide-visible-overlay"
|
||||||
if not run_control_command(visible_action) then
|
if not run_control_command(visible_action) then
|
||||||
subminer_log("warn", "process", "Failed to apply visible startup action: " .. visible_action)
|
subminer_log("warn", "process", "Failed to apply visible startup action: " .. visible_action)
|
||||||
end
|
end
|
||||||
|
|
||||||
local invisible_action = should_show_invisible and "show-invisible-overlay" or "hide-invisible-overlay"
|
|
||||||
if not run_control_command(invisible_action) then
|
|
||||||
subminer_log("warn", "process", "Failed to apply invisible startup action: " .. invisible_action)
|
|
||||||
end
|
|
||||||
|
|
||||||
state.invisible_overlay_visible = should_show_invisible
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function build_texthooker_args()
|
local function build_texthooker_args()
|
||||||
@@ -1646,90 +1623,6 @@ local function toggle_overlay()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function toggle_invisible_overlay()
|
|
||||||
if not ensure_binary_available() then
|
|
||||||
subminer_log("error", "binary", "SubMiner binary not found")
|
|
||||||
show_osd("Error: binary not found")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local args = build_command_args("toggle-invisible-overlay")
|
|
||||||
subminer_log("info", "process", "Toggling invisible overlay: " .. table.concat(args, " "))
|
|
||||||
|
|
||||||
local result = mp.command_native({
|
|
||||||
name = "subprocess",
|
|
||||||
args = args,
|
|
||||||
playback_only = false,
|
|
||||||
capture_stdout = true,
|
|
||||||
capture_stderr = true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if result and result.status ~= 0 then
|
|
||||||
subminer_log("warn", "process", "Invisible toggle command failed")
|
|
||||||
show_osd("Invisible toggle failed")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
state.invisible_overlay_visible = not state.invisible_overlay_visible
|
|
||||||
show_osd("Invisible overlay: " .. (state.invisible_overlay_visible and "visible" or "hidden"))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function show_invisible_overlay()
|
|
||||||
if not ensure_binary_available() then
|
|
||||||
subminer_log("error", "binary", "SubMiner binary not found")
|
|
||||||
show_osd("Error: binary not found")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local args = build_command_args("show-invisible-overlay")
|
|
||||||
subminer_log("info", "process", "Showing invisible overlay: " .. table.concat(args, " "))
|
|
||||||
|
|
||||||
local result = mp.command_native({
|
|
||||||
name = "subprocess",
|
|
||||||
args = args,
|
|
||||||
playback_only = false,
|
|
||||||
capture_stdout = true,
|
|
||||||
capture_stderr = true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if result and result.status ~= 0 then
|
|
||||||
subminer_log("warn", "process", "Show invisible command failed")
|
|
||||||
show_osd("Show invisible failed")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
state.invisible_overlay_visible = true
|
|
||||||
show_osd("Invisible overlay: visible")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function hide_invisible_overlay()
|
|
||||||
if not ensure_binary_available() then
|
|
||||||
subminer_log("error", "binary", "SubMiner binary not found")
|
|
||||||
show_osd("Error: binary not found")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local args = build_command_args("hide-invisible-overlay")
|
|
||||||
subminer_log("info", "process", "Hiding invisible overlay: " .. table.concat(args, " "))
|
|
||||||
|
|
||||||
local result = mp.command_native({
|
|
||||||
name = "subprocess",
|
|
||||||
args = args,
|
|
||||||
playback_only = false,
|
|
||||||
capture_stdout = true,
|
|
||||||
capture_stderr = true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if result and result.status ~= 0 then
|
|
||||||
subminer_log("warn", "process", "Hide invisible command failed")
|
|
||||||
show_osd("Hide invisible failed")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
state.invisible_overlay_visible = false
|
|
||||||
show_osd("Invisible overlay: hidden")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function open_options()
|
local function open_options()
|
||||||
if not state.binary_available then
|
if not state.binary_available then
|
||||||
subminer_log("error", "binary", "SubMiner binary not found")
|
subminer_log("error", "binary", "SubMiner binary not found")
|
||||||
@@ -1768,7 +1661,6 @@ local function show_menu()
|
|||||||
"Start overlay",
|
"Start overlay",
|
||||||
"Stop overlay",
|
"Stop overlay",
|
||||||
"Toggle overlay",
|
"Toggle overlay",
|
||||||
"Toggle invisible overlay",
|
|
||||||
"Open options",
|
"Open options",
|
||||||
"Restart overlay",
|
"Restart overlay",
|
||||||
"Check status",
|
"Check status",
|
||||||
@@ -1778,7 +1670,6 @@ local function show_menu()
|
|||||||
start_overlay,
|
start_overlay,
|
||||||
stop_overlay,
|
stop_overlay,
|
||||||
toggle_overlay,
|
toggle_overlay,
|
||||||
toggle_invisible_overlay,
|
|
||||||
open_options,
|
open_options,
|
||||||
restart_overlay,
|
restart_overlay,
|
||||||
check_status,
|
check_status,
|
||||||
@@ -1895,9 +1786,6 @@ local function register_keybindings()
|
|||||||
mp.add_key_binding("y-s", "subminer-start", start_overlay)
|
mp.add_key_binding("y-s", "subminer-start", start_overlay)
|
||||||
mp.add_key_binding("y-S", "subminer-stop", stop_overlay)
|
mp.add_key_binding("y-S", "subminer-stop", stop_overlay)
|
||||||
mp.add_key_binding("y-t", "subminer-toggle", toggle_overlay)
|
mp.add_key_binding("y-t", "subminer-toggle", toggle_overlay)
|
||||||
mp.add_key_binding("y-i", "subminer-toggle-invisible", toggle_invisible_overlay)
|
|
||||||
mp.add_key_binding("y-I", "subminer-show-invisible", show_invisible_overlay)
|
|
||||||
mp.add_key_binding("y-u", "subminer-hide-invisible", hide_invisible_overlay)
|
|
||||||
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", open_options)
|
mp.add_key_binding("y-o", "subminer-options", open_options)
|
||||||
mp.add_key_binding("y-r", "subminer-restart", restart_overlay)
|
mp.add_key_binding("y-r", "subminer-restart", restart_overlay)
|
||||||
@@ -1914,9 +1802,6 @@ local function register_script_messages()
|
|||||||
mp.register_script_message("subminer-start", start_overlay_from_script_message)
|
mp.register_script_message("subminer-start", start_overlay_from_script_message)
|
||||||
mp.register_script_message("subminer-stop", stop_overlay)
|
mp.register_script_message("subminer-stop", stop_overlay)
|
||||||
mp.register_script_message("subminer-toggle", toggle_overlay)
|
mp.register_script_message("subminer-toggle", toggle_overlay)
|
||||||
mp.register_script_message("subminer-toggle-invisible", toggle_invisible_overlay)
|
|
||||||
mp.register_script_message("subminer-show-invisible", show_invisible_overlay)
|
|
||||||
mp.register_script_message("subminer-hide-invisible", hide_invisible_overlay)
|
|
||||||
mp.register_script_message("subminer-menu", show_menu)
|
mp.register_script_message("subminer-menu", show_menu)
|
||||||
mp.register_script_message("subminer-options", open_options)
|
mp.register_script_message("subminer-options", open_options)
|
||||||
mp.register_script_message("subminer-restart", restart_overlay)
|
mp.register_script_message("subminer-restart", restart_overlay)
|
||||||
|
|||||||
@@ -4,14 +4,11 @@ export interface CliArgs {
|
|||||||
stop: boolean;
|
stop: boolean;
|
||||||
toggle: boolean;
|
toggle: boolean;
|
||||||
toggleVisibleOverlay: boolean;
|
toggleVisibleOverlay: boolean;
|
||||||
toggleInvisibleOverlay: boolean;
|
|
||||||
settings: boolean;
|
settings: boolean;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
hide: boolean;
|
hide: boolean;
|
||||||
showVisibleOverlay: boolean;
|
showVisibleOverlay: boolean;
|
||||||
hideVisibleOverlay: boolean;
|
hideVisibleOverlay: boolean;
|
||||||
showInvisibleOverlay: boolean;
|
|
||||||
hideInvisibleOverlay: boolean;
|
|
||||||
copySubtitle: boolean;
|
copySubtitle: boolean;
|
||||||
copySubtitleMultiple: boolean;
|
copySubtitleMultiple: boolean;
|
||||||
mineSentence: boolean;
|
mineSentence: boolean;
|
||||||
@@ -67,14 +64,11 @@ export function parseArgs(argv: string[]): CliArgs {
|
|||||||
stop: false,
|
stop: false,
|
||||||
toggle: false,
|
toggle: false,
|
||||||
toggleVisibleOverlay: false,
|
toggleVisibleOverlay: false,
|
||||||
toggleInvisibleOverlay: false,
|
|
||||||
settings: false,
|
settings: false,
|
||||||
show: false,
|
show: false,
|
||||||
hide: false,
|
hide: false,
|
||||||
showVisibleOverlay: false,
|
showVisibleOverlay: false,
|
||||||
hideVisibleOverlay: false,
|
hideVisibleOverlay: false,
|
||||||
showInvisibleOverlay: false,
|
|
||||||
hideInvisibleOverlay: false,
|
|
||||||
copySubtitle: false,
|
copySubtitle: false,
|
||||||
copySubtitleMultiple: false,
|
copySubtitleMultiple: false,
|
||||||
mineSentence: false,
|
mineSentence: false,
|
||||||
@@ -122,14 +116,11 @@ 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-invisible-overlay') args.toggleInvisibleOverlay = true;
|
|
||||||
else if (arg === '--settings' || arg === '--yomitan') args.settings = true;
|
else if (arg === '--settings' || arg === '--yomitan') args.settings = true;
|
||||||
else if (arg === '--show') args.show = true;
|
else if (arg === '--show') args.show = true;
|
||||||
else if (arg === '--hide') args.hide = true;
|
else if (arg === '--hide') args.hide = true;
|
||||||
else if (arg === '--show-visible-overlay') args.showVisibleOverlay = true;
|
else if (arg === '--show-visible-overlay') args.showVisibleOverlay = true;
|
||||||
else if (arg === '--hide-visible-overlay') args.hideVisibleOverlay = true;
|
else if (arg === '--hide-visible-overlay') args.hideVisibleOverlay = true;
|
||||||
else if (arg === '--show-invisible-overlay') args.showInvisibleOverlay = true;
|
|
||||||
else if (arg === '--hide-invisible-overlay') args.hideInvisibleOverlay = true;
|
|
||||||
else if (arg === '--copy-subtitle') args.copySubtitle = true;
|
else if (arg === '--copy-subtitle') args.copySubtitle = true;
|
||||||
else if (arg === '--copy-subtitle-multiple') args.copySubtitleMultiple = true;
|
else if (arg === '--copy-subtitle-multiple') args.copySubtitleMultiple = true;
|
||||||
else if (arg === '--mine-sentence') args.mineSentence = true;
|
else if (arg === '--mine-sentence') args.mineSentence = true;
|
||||||
@@ -263,14 +254,11 @@ export function hasExplicitCommand(args: CliArgs): boolean {
|
|||||||
args.stop ||
|
args.stop ||
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
args.toggleVisibleOverlay ||
|
args.toggleVisibleOverlay ||
|
||||||
args.toggleInvisibleOverlay ||
|
|
||||||
args.settings ||
|
args.settings ||
|
||||||
args.show ||
|
args.show ||
|
||||||
args.hide ||
|
args.hide ||
|
||||||
args.showVisibleOverlay ||
|
args.showVisibleOverlay ||
|
||||||
args.hideVisibleOverlay ||
|
args.hideVisibleOverlay ||
|
||||||
args.showInvisibleOverlay ||
|
|
||||||
args.hideInvisibleOverlay ||
|
|
||||||
args.copySubtitle ||
|
args.copySubtitle ||
|
||||||
args.copySubtitleMultiple ||
|
args.copySubtitleMultiple ||
|
||||||
args.mineSentence ||
|
args.mineSentence ||
|
||||||
@@ -307,7 +295,6 @@ export function shouldStartApp(args: CliArgs): boolean {
|
|||||||
args.start ||
|
args.start ||
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
args.toggleVisibleOverlay ||
|
args.toggleVisibleOverlay ||
|
||||||
args.toggleInvisibleOverlay ||
|
|
||||||
args.copySubtitle ||
|
args.copySubtitle ||
|
||||||
args.copySubtitleMultiple ||
|
args.copySubtitleMultiple ||
|
||||||
args.mineSentence ||
|
args.mineSentence ||
|
||||||
@@ -331,13 +318,10 @@ export function commandNeedsOverlayRuntime(args: CliArgs): boolean {
|
|||||||
return (
|
return (
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
args.toggleVisibleOverlay ||
|
args.toggleVisibleOverlay ||
|
||||||
args.toggleInvisibleOverlay ||
|
|
||||||
args.show ||
|
args.show ||
|
||||||
args.hide ||
|
args.hide ||
|
||||||
args.showVisibleOverlay ||
|
args.showVisibleOverlay ||
|
||||||
args.hideVisibleOverlay ||
|
args.hideVisibleOverlay ||
|
||||||
args.showInvisibleOverlay ||
|
|
||||||
args.hideInvisibleOverlay ||
|
|
||||||
args.copySubtitle ||
|
args.copySubtitle ||
|
||||||
args.copySubtitleMultiple ||
|
args.copySubtitleMultiple ||
|
||||||
args.mineSentence ||
|
args.mineSentence ||
|
||||||
|
|||||||
@@ -17,11 +17,8 @@ ${B}Session${R}
|
|||||||
|
|
||||||
${B}Overlay${R}
|
${B}Overlay${R}
|
||||||
--toggle-visible-overlay Toggle subtitle overlay
|
--toggle-visible-overlay Toggle subtitle overlay
|
||||||
--toggle-invisible-overlay Toggle interactive overlay ${D}(Yomitan lookup)${R}
|
|
||||||
--show-visible-overlay Show subtitle overlay
|
--show-visible-overlay Show subtitle overlay
|
||||||
--hide-visible-overlay Hide subtitle overlay
|
--hide-visible-overlay Hide subtitle overlay
|
||||||
--show-invisible-overlay Show interactive overlay
|
|
||||||
--hide-invisible-overlay Hide interactive overlay
|
|
||||||
--settings Open Yomitan settings window
|
--settings Open Yomitan settings window
|
||||||
--auto-start-overlay Auto-hide mpv subs, show overlay on connect
|
--auto-start-overlay Auto-hide mpv subs, show overlay on connect
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ test('loads defaults when config is missing', () => {
|
|||||||
assert.equal(config.discordPresence.updateIntervalMs, 3_000);
|
assert.equal(config.discordPresence.updateIntervalMs, 3_000);
|
||||||
assert.equal(config.subtitleStyle.backgroundColor, 'rgb(30, 32, 48, 0.88)');
|
assert.equal(config.subtitleStyle.backgroundColor, 'rgb(30, 32, 48, 0.88)');
|
||||||
assert.equal(config.subtitleStyle.preserveLineBreaks, false);
|
assert.equal(config.subtitleStyle.preserveLineBreaks, false);
|
||||||
assert.equal(config.subtitleStyle.hoverTokenColor, '#c6a0f6');
|
assert.equal(config.subtitleStyle.hoverTokenColor, '#f4dbd6');
|
||||||
|
assert.equal(config.subtitleStyle.hoverTokenBackgroundColor, '#363a4fd6');
|
||||||
assert.equal(config.immersionTracking.enabled, true);
|
assert.equal(config.immersionTracking.enabled, true);
|
||||||
assert.equal(config.immersionTracking.dbPath, '');
|
assert.equal(config.immersionTracking.dbPath, '');
|
||||||
assert.equal(config.immersionTracking.batchSize, 25);
|
assert.equal(config.immersionTracking.batchSize, 25);
|
||||||
@@ -136,6 +137,44 @@ test('parses subtitleStyle.hoverTokenColor and warns on invalid values', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('parses subtitleStyle.hoverTokenBackgroundColor and warns on invalid values', () => {
|
||||||
|
const validDir = makeTempDir();
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(validDir, 'config.jsonc'),
|
||||||
|
`{
|
||||||
|
"subtitleStyle": {
|
||||||
|
"hoverTokenBackgroundColor": "#363a4fd6"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
|
||||||
|
const validService = new ConfigService(validDir);
|
||||||
|
assert.equal(validService.getConfig().subtitleStyle.hoverTokenBackgroundColor, '#363a4fd6');
|
||||||
|
|
||||||
|
const invalidDir = makeTempDir();
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(invalidDir, 'config.jsonc'),
|
||||||
|
`{
|
||||||
|
"subtitleStyle": {
|
||||||
|
"hoverTokenBackgroundColor": true
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
|
||||||
|
const invalidService = new ConfigService(invalidDir);
|
||||||
|
assert.equal(
|
||||||
|
invalidService.getConfig().subtitleStyle.hoverTokenBackgroundColor,
|
||||||
|
DEFAULT_CONFIG.subtitleStyle.hoverTokenBackgroundColor,
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
invalidService
|
||||||
|
.getWarnings()
|
||||||
|
.some((warning) => warning.path === 'subtitleStyle.hoverTokenBackgroundColor'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('parses anilist.enabled and warns for invalid value', () => {
|
test('parses anilist.enabled and warns for invalid value', () => {
|
||||||
const dir = makeTempDir();
|
const dir = makeTempDir();
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
@@ -597,19 +636,15 @@ test('warns and ignores unknown top-level config keys', () => {
|
|||||||
assert.ok(warnings.some((warning) => warning.path === 'unknownFeatureFlag'));
|
assert.ok(warnings.some((warning) => warning.path === 'unknownFeatureFlag'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses invisible overlay config and new global shortcuts', () => {
|
test('parses global shortcuts and startup visibility flags', () => {
|
||||||
const dir = makeTempDir();
|
const dir = makeTempDir();
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(dir, 'config.jsonc'),
|
path.join(dir, 'config.jsonc'),
|
||||||
`{
|
`{
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"toggleVisibleOverlayGlobal": "Alt+Shift+U",
|
"toggleVisibleOverlayGlobal": "Alt+Shift+U",
|
||||||
"toggleInvisibleOverlayGlobal": "Alt+Shift+I",
|
|
||||||
"openJimaku": "Ctrl+Alt+J"
|
"openJimaku": "Ctrl+Alt+J"
|
||||||
},
|
},
|
||||||
"invisibleOverlay": {
|
|
||||||
"startupVisibility": "hidden"
|
|
||||||
},
|
|
||||||
"bind_visible_overlay_to_mpv_sub_visibility": false,
|
"bind_visible_overlay_to_mpv_sub_visibility": false,
|
||||||
"youtubeSubgen": {
|
"youtubeSubgen": {
|
||||||
"primarySubLanguages": ["ja", "jpn", "jp"]
|
"primarySubLanguages": ["ja", "jpn", "jp"]
|
||||||
@@ -621,9 +656,7 @@ test('parses invisible overlay config and new global shortcuts', () => {
|
|||||||
const service = new ConfigService(dir);
|
const service = new ConfigService(dir);
|
||||||
const config = service.getConfig();
|
const config = service.getConfig();
|
||||||
assert.equal(config.shortcuts.toggleVisibleOverlayGlobal, 'Alt+Shift+U');
|
assert.equal(config.shortcuts.toggleVisibleOverlayGlobal, 'Alt+Shift+U');
|
||||||
assert.equal(config.shortcuts.toggleInvisibleOverlayGlobal, 'Alt+Shift+I');
|
|
||||||
assert.equal(config.shortcuts.openJimaku, 'Ctrl+Alt+J');
|
assert.equal(config.shortcuts.openJimaku, 'Ctrl+Alt+J');
|
||||||
assert.equal(config.invisibleOverlay.startupVisibility, 'hidden');
|
|
||||||
assert.equal(config.bind_visible_overlay_to_mpv_sub_visibility, false);
|
assert.equal(config.bind_visible_overlay_to_mpv_sub_visibility, false);
|
||||||
assert.deepEqual(config.youtubeSubgen.primarySubLanguages, ['ja', 'jpn', 'jp']);
|
assert.deepEqual(config.youtubeSubgen.primarySubLanguages, ['ja', 'jpn', 'jp']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ const {
|
|||||||
subsync,
|
subsync,
|
||||||
auto_start_overlay,
|
auto_start_overlay,
|
||||||
bind_visible_overlay_to_mpv_sub_visibility,
|
bind_visible_overlay_to_mpv_sub_visibility,
|
||||||
invisibleOverlay,
|
|
||||||
} = CORE_DEFAULT_CONFIG;
|
} = CORE_DEFAULT_CONFIG;
|
||||||
const { ankiConnect, jimaku, anilist, jellyfin, discordPresence, youtubeSubgen } =
|
const { ankiConnect, jimaku, anilist, jellyfin, discordPresence, youtubeSubgen } =
|
||||||
INTEGRATIONS_DEFAULT_CONFIG;
|
INTEGRATIONS_DEFAULT_CONFIG;
|
||||||
@@ -54,7 +53,6 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
|
|||||||
jellyfin,
|
jellyfin,
|
||||||
discordPresence,
|
discordPresence,
|
||||||
youtubeSubgen,
|
youtubeSubgen,
|
||||||
invisibleOverlay,
|
|
||||||
immersionTracking,
|
immersionTracking,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
|||||||
| 'subsync'
|
| 'subsync'
|
||||||
| 'auto_start_overlay'
|
| 'auto_start_overlay'
|
||||||
| 'bind_visible_overlay_to_mpv_sub_visibility'
|
| 'bind_visible_overlay_to_mpv_sub_visibility'
|
||||||
| 'invisibleOverlay'
|
|
||||||
> = {
|
> = {
|
||||||
subtitlePosition: { yPercent: 10 },
|
subtitlePosition: { yPercent: 10 },
|
||||||
keybindings: [],
|
keybindings: [],
|
||||||
@@ -28,7 +27,6 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
|||||||
},
|
},
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
toggleVisibleOverlayGlobal: 'Alt+Shift+O',
|
toggleVisibleOverlayGlobal: 'Alt+Shift+O',
|
||||||
toggleInvisibleOverlayGlobal: 'Alt+Shift+I',
|
|
||||||
copySubtitle: 'CommandOrControl+C',
|
copySubtitle: 'CommandOrControl+C',
|
||||||
copySubtitleMultiple: 'CommandOrControl+Shift+C',
|
copySubtitleMultiple: 'CommandOrControl+Shift+C',
|
||||||
updateLastCardFromClipboard: 'CommandOrControl+V',
|
updateLastCardFromClipboard: 'CommandOrControl+V',
|
||||||
@@ -55,7 +53,4 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
|||||||
},
|
},
|
||||||
auto_start_overlay: false,
|
auto_start_overlay: false,
|
||||||
bind_visible_overlay_to_mpv_sub_visibility: true,
|
bind_visible_overlay_to_mpv_sub_visibility: true,
|
||||||
invisibleOverlay: {
|
|
||||||
startupVisibility: 'platform-default',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ export const SUBTITLE_DEFAULT_CONFIG: Pick<ResolvedConfig, 'subtitleStyle'> = {
|
|||||||
subtitleStyle: {
|
subtitleStyle: {
|
||||||
enableJlpt: false,
|
enableJlpt: false,
|
||||||
preserveLineBreaks: false,
|
preserveLineBreaks: false,
|
||||||
hoverTokenColor: '#c6a0f6',
|
hoverTokenColor: '#f4dbd6',
|
||||||
|
hoverTokenBackgroundColor: '#363a4fd6',
|
||||||
fontFamily:
|
fontFamily:
|
||||||
'M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif',
|
'M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif',
|
||||||
fontSize: 35,
|
fontSize: 35,
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ export function buildSubtitleConfigOptionRegistry(
|
|||||||
defaultValue: defaultConfig.subtitleStyle.hoverTokenColor,
|
defaultValue: defaultConfig.subtitleStyle.hoverTokenColor,
|
||||||
description: 'Hex color used for hovered subtitle token highlight in mpv.',
|
description: 'Hex color used for hovered subtitle token highlight in mpv.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'subtitleStyle.hoverTokenBackgroundColor',
|
||||||
|
kind: 'string',
|
||||||
|
defaultValue: defaultConfig.subtitleStyle.hoverTokenBackgroundColor,
|
||||||
|
description: 'CSS color used for hovered subtitle token background highlight in mpv.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'subtitleStyle.frequencyDictionary.enabled',
|
path: 'subtitleStyle.frequencyDictionary.enabled',
|
||||||
kind: 'boolean',
|
kind: 'boolean',
|
||||||
|
|||||||
@@ -40,15 +40,6 @@ const CORE_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
|
|||||||
notes: ['Hot-reload: shortcut changes apply live and update the session help modal on reopen.'],
|
notes: ['Hot-reload: shortcut changes apply live and update the session help modal on reopen.'],
|
||||||
key: 'shortcuts',
|
key: 'shortcuts',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Invisible Overlay',
|
|
||||||
description: ['Startup behavior for the invisible interactive subtitle mining layer.'],
|
|
||||||
notes: [
|
|
||||||
'Invisible subtitle position edit mode: Ctrl/Cmd+Shift+P to toggle, arrow keys to move, Enter or Ctrl/Cmd+S to save, Esc to cancel.',
|
|
||||||
'This edit-mode shortcut is fixed and is not currently configurable.',
|
|
||||||
],
|
|
||||||
key: 'invisibleOverlay',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Keybindings (MPV Commands)',
|
title: 'Keybindings (MPV Commands)',
|
||||||
description: [
|
description: [
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
|
|||||||
const fallbackSubtitleStyleEnableJlpt = resolved.subtitleStyle.enableJlpt;
|
const fallbackSubtitleStyleEnableJlpt = resolved.subtitleStyle.enableJlpt;
|
||||||
const fallbackSubtitleStylePreserveLineBreaks = resolved.subtitleStyle.preserveLineBreaks;
|
const fallbackSubtitleStylePreserveLineBreaks = resolved.subtitleStyle.preserveLineBreaks;
|
||||||
const fallbackSubtitleStyleHoverTokenColor = resolved.subtitleStyle.hoverTokenColor;
|
const fallbackSubtitleStyleHoverTokenColor = resolved.subtitleStyle.hoverTokenColor;
|
||||||
|
const fallbackSubtitleStyleHoverTokenBackgroundColor =
|
||||||
|
resolved.subtitleStyle.hoverTokenBackgroundColor;
|
||||||
resolved.subtitleStyle = {
|
resolved.subtitleStyle = {
|
||||||
...resolved.subtitleStyle,
|
...resolved.subtitleStyle,
|
||||||
...(src.subtitleStyle as ResolvedConfig['subtitleStyle']),
|
...(src.subtitleStyle as ResolvedConfig['subtitleStyle']),
|
||||||
@@ -154,6 +156,24 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hoverTokenBackgroundColor = asString(
|
||||||
|
(src.subtitleStyle as { hoverTokenBackgroundColor?: unknown }).hoverTokenBackgroundColor,
|
||||||
|
);
|
||||||
|
if (hoverTokenBackgroundColor !== undefined) {
|
||||||
|
resolved.subtitleStyle.hoverTokenBackgroundColor = hoverTokenBackgroundColor;
|
||||||
|
} else if (
|
||||||
|
(src.subtitleStyle as { hoverTokenBackgroundColor?: unknown }).hoverTokenBackgroundColor !==
|
||||||
|
undefined
|
||||||
|
) {
|
||||||
|
resolved.subtitleStyle.hoverTokenBackgroundColor = fallbackSubtitleStyleHoverTokenBackgroundColor;
|
||||||
|
warn(
|
||||||
|
'subtitleStyle.hoverTokenBackgroundColor',
|
||||||
|
(src.subtitleStyle as { hoverTokenBackgroundColor?: unknown }).hoverTokenBackgroundColor,
|
||||||
|
resolved.subtitleStyle.hoverTokenBackgroundColor,
|
||||||
|
'Expected string.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const frequencyDictionary = isObject(
|
const frequencyDictionary = isObject(
|
||||||
(src.subtitleStyle as { frequencyDictionary?: unknown }).frequencyDictionary,
|
(src.subtitleStyle as { frequencyDictionary?: unknown }).frequencyDictionary,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,14 +10,11 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
|||||||
stop: false,
|
stop: false,
|
||||||
toggle: false,
|
toggle: false,
|
||||||
toggleVisibleOverlay: false,
|
toggleVisibleOverlay: false,
|
||||||
toggleInvisibleOverlay: false,
|
|
||||||
settings: false,
|
settings: false,
|
||||||
show: false,
|
show: false,
|
||||||
hide: false,
|
hide: false,
|
||||||
showVisibleOverlay: false,
|
showVisibleOverlay: false,
|
||||||
hideVisibleOverlay: false,
|
hideVisibleOverlay: false,
|
||||||
showInvisibleOverlay: false,
|
|
||||||
hideInvisibleOverlay: false,
|
|
||||||
copySubtitle: false,
|
copySubtitle: false,
|
||||||
copySubtitleMultiple: false,
|
copySubtitleMultiple: false,
|
||||||
mineSentence: false,
|
mineSentence: false,
|
||||||
@@ -94,18 +91,12 @@ function createDeps(overrides: Partial<CliCommandServiceDeps> = {}) {
|
|||||||
toggleVisibleOverlay: () => {
|
toggleVisibleOverlay: () => {
|
||||||
calls.push('toggleVisibleOverlay');
|
calls.push('toggleVisibleOverlay');
|
||||||
},
|
},
|
||||||
toggleInvisibleOverlay: () => {
|
|
||||||
calls.push('toggleInvisibleOverlay');
|
|
||||||
},
|
|
||||||
openYomitanSettingsDelayed: (delayMs) => {
|
openYomitanSettingsDelayed: (delayMs) => {
|
||||||
calls.push(`openYomitanSettingsDelayed:${delayMs}`);
|
calls.push(`openYomitanSettingsDelayed:${delayMs}`);
|
||||||
},
|
},
|
||||||
setVisibleOverlayVisible: (visible) => {
|
setVisibleOverlayVisible: (visible) => {
|
||||||
calls.push(`setVisibleOverlayVisible:${visible}`);
|
calls.push(`setVisibleOverlayVisible:${visible}`);
|
||||||
},
|
},
|
||||||
setInvisibleOverlayVisible: (visible) => {
|
|
||||||
calls.push(`setInvisibleOverlayVisible:${visible}`);
|
|
||||||
},
|
|
||||||
copyCurrentSubtitle: () => {
|
copyCurrentSubtitle: () => {
|
||||||
calls.push('copyCurrentSubtitle');
|
calls.push('copyCurrentSubtitle');
|
||||||
},
|
},
|
||||||
@@ -339,10 +330,6 @@ test('handleCliCommand handles visibility and utility command dispatches', () =>
|
|||||||
args: Partial<CliArgs>;
|
args: Partial<CliArgs>;
|
||||||
expected: string;
|
expected: string;
|
||||||
}> = [
|
}> = [
|
||||||
{
|
|
||||||
args: { toggleInvisibleOverlay: true },
|
|
||||||
expected: 'toggleInvisibleOverlay',
|
|
||||||
},
|
|
||||||
{ args: { settings: true }, expected: 'openYomitanSettingsDelayed:1000' },
|
{ args: { settings: true }, expected: 'openYomitanSettingsDelayed:1000' },
|
||||||
{
|
{
|
||||||
args: { showVisibleOverlay: true },
|
args: { showVisibleOverlay: true },
|
||||||
@@ -352,14 +339,6 @@ test('handleCliCommand handles visibility and utility command dispatches', () =>
|
|||||||
args: { hideVisibleOverlay: true },
|
args: { hideVisibleOverlay: true },
|
||||||
expected: 'setVisibleOverlayVisible:false',
|
expected: 'setVisibleOverlayVisible:false',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
args: { showInvisibleOverlay: true },
|
|
||||||
expected: 'setInvisibleOverlayVisible:true',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: { hideInvisibleOverlay: true },
|
|
||||||
expected: 'setInvisibleOverlayVisible:false',
|
|
||||||
},
|
|
||||||
{ args: { copySubtitle: true }, expected: 'copyCurrentSubtitle' },
|
{ args: { copySubtitle: true }, expected: 'copyCurrentSubtitle' },
|
||||||
{
|
{
|
||||||
args: { copySubtitleMultiple: true },
|
args: { copySubtitleMultiple: true },
|
||||||
|
|||||||
@@ -16,10 +16,8 @@ export interface CliCommandServiceDeps {
|
|||||||
isOverlayRuntimeInitialized: () => boolean;
|
isOverlayRuntimeInitialized: () => boolean;
|
||||||
initializeOverlayRuntime: () => void;
|
initializeOverlayRuntime: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
toggleInvisibleOverlay: () => void;
|
|
||||||
openYomitanSettingsDelayed: (delayMs: number) => void;
|
openYomitanSettingsDelayed: (delayMs: number) => void;
|
||||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => void;
|
|
||||||
copyCurrentSubtitle: () => void;
|
copyCurrentSubtitle: () => void;
|
||||||
startPendingMultiCopy: (timeoutMs: number) => void;
|
startPendingMultiCopy: (timeoutMs: number) => void;
|
||||||
mineSentenceCard: () => Promise<void>;
|
mineSentenceCard: () => Promise<void>;
|
||||||
@@ -93,9 +91,7 @@ interface OverlayCliRuntime {
|
|||||||
isInitialized: () => boolean;
|
isInitialized: () => boolean;
|
||||||
initialize: () => void;
|
initialize: () => void;
|
||||||
toggleVisible: () => void;
|
toggleVisible: () => void;
|
||||||
toggleInvisible: () => void;
|
|
||||||
setVisible: (visible: boolean) => void;
|
setVisible: (visible: boolean) => void;
|
||||||
setInvisible: (visible: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MiningCliRuntime {
|
interface MiningCliRuntime {
|
||||||
@@ -180,14 +176,12 @@ 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,
|
||||||
toggleInvisibleOverlay: options.overlay.toggleInvisible,
|
|
||||||
openYomitanSettingsDelayed: (delayMs) => {
|
openYomitanSettingsDelayed: (delayMs) => {
|
||||||
options.schedule(() => {
|
options.schedule(() => {
|
||||||
options.ui.openYomitanSettings();
|
options.ui.openYomitanSettings();
|
||||||
}, delayMs);
|
}, delayMs);
|
||||||
},
|
},
|
||||||
setVisibleOverlayVisible: options.overlay.setVisible,
|
setVisibleOverlayVisible: options.overlay.setVisible,
|
||||||
setInvisibleOverlayVisible: options.overlay.setInvisible,
|
|
||||||
copyCurrentSubtitle: options.mining.copyCurrentSubtitle,
|
copyCurrentSubtitle: options.mining.copyCurrentSubtitle,
|
||||||
startPendingMultiCopy: options.mining.startPendingMultiCopy,
|
startPendingMultiCopy: options.mining.startPendingMultiCopy,
|
||||||
mineSentenceCard: options.mining.mineSentenceCard,
|
mineSentenceCard: options.mining.mineSentenceCard,
|
||||||
@@ -242,14 +236,11 @@ export function handleCliCommand(
|
|||||||
args.stop ||
|
args.stop ||
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
args.toggleVisibleOverlay ||
|
args.toggleVisibleOverlay ||
|
||||||
args.toggleInvisibleOverlay ||
|
|
||||||
args.settings ||
|
args.settings ||
|
||||||
args.show ||
|
args.show ||
|
||||||
args.hide ||
|
args.hide ||
|
||||||
args.showVisibleOverlay ||
|
args.showVisibleOverlay ||
|
||||||
args.hideVisibleOverlay ||
|
args.hideVisibleOverlay ||
|
||||||
args.showInvisibleOverlay ||
|
|
||||||
args.hideInvisibleOverlay ||
|
|
||||||
args.copySubtitle ||
|
args.copySubtitle ||
|
||||||
args.copySubtitleMultiple ||
|
args.copySubtitleMultiple ||
|
||||||
args.mineSentence ||
|
args.mineSentence ||
|
||||||
@@ -286,10 +277,7 @@ export function handleCliCommand(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shouldStart =
|
const shouldStart =
|
||||||
args.start ||
|
args.start || args.toggle || args.toggleVisibleOverlay;
|
||||||
args.toggle ||
|
|
||||||
args.toggleVisibleOverlay ||
|
|
||||||
args.toggleInvisibleOverlay;
|
|
||||||
const needsOverlayRuntime = commandNeedsOverlayRuntime(args);
|
const needsOverlayRuntime = commandNeedsOverlayRuntime(args);
|
||||||
const shouldInitializeOverlayRuntime = needsOverlayRuntime || args.start;
|
const shouldInitializeOverlayRuntime = needsOverlayRuntime || args.start;
|
||||||
|
|
||||||
@@ -325,18 +313,12 @@ export function handleCliCommand(
|
|||||||
|
|
||||||
if (args.toggle || args.toggleVisibleOverlay) {
|
if (args.toggle || args.toggleVisibleOverlay) {
|
||||||
deps.toggleVisibleOverlay();
|
deps.toggleVisibleOverlay();
|
||||||
} else if (args.toggleInvisibleOverlay) {
|
|
||||||
deps.toggleInvisibleOverlay();
|
|
||||||
} else if (args.settings) {
|
} else if (args.settings) {
|
||||||
deps.openYomitanSettingsDelayed(1000);
|
deps.openYomitanSettingsDelayed(1000);
|
||||||
} else if (args.show || args.showVisibleOverlay) {
|
} else if (args.show || args.showVisibleOverlay) {
|
||||||
deps.setVisibleOverlayVisible(true);
|
deps.setVisibleOverlayVisible(true);
|
||||||
} else if (args.hide || args.hideVisibleOverlay) {
|
} else if (args.hide || args.hideVisibleOverlay) {
|
||||||
deps.setVisibleOverlayVisible(false);
|
deps.setVisibleOverlayVisible(false);
|
||||||
} else if (args.showInvisibleOverlay) {
|
|
||||||
deps.setInvisibleOverlayVisible(true);
|
|
||||||
} else if (args.hideInvisibleOverlay) {
|
|
||||||
deps.setInvisibleOverlayVisible(false);
|
|
||||||
} else if (args.copySubtitle) {
|
} else if (args.copySubtitle) {
|
||||||
deps.copyCurrentSubtitle();
|
deps.copyCurrentSubtitle();
|
||||||
} else if (args.copySubtitleMultiple) {
|
} else if (args.copySubtitleMultiple) {
|
||||||
|
|||||||
@@ -19,11 +19,9 @@ test('createFieldGroupingOverlayRuntime sends overlay messages and sets restore
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
getVisibleOverlayVisible: () => visible,
|
getVisibleOverlayVisible: () => visible,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
setVisibleOverlayVisible: (next) => {
|
setVisibleOverlayVisible: (next) => {
|
||||||
visible = next;
|
visible = next;
|
||||||
},
|
},
|
||||||
setInvisibleOverlayVisible: () => {},
|
|
||||||
getResolver: () => null,
|
getResolver: () => null,
|
||||||
setResolver: () => {},
|
setResolver: () => {},
|
||||||
getRestoreVisibleOverlayOnModalClose: () => restore,
|
getRestoreVisibleOverlayOnModalClose: () => restore,
|
||||||
@@ -44,9 +42,7 @@ test('createFieldGroupingOverlayRuntime callback cancels when send fails', async
|
|||||||
const runtime = createFieldGroupingOverlayRuntime<'runtime-options' | 'subsync'>({
|
const runtime = createFieldGroupingOverlayRuntime<'runtime-options' | 'subsync'>({
|
||||||
getMainWindow: () => null,
|
getMainWindow: () => null,
|
||||||
getVisibleOverlayVisible: () => false,
|
getVisibleOverlayVisible: () => false,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
setVisibleOverlayVisible: () => {},
|
setVisibleOverlayVisible: () => {},
|
||||||
setInvisibleOverlayVisible: () => {},
|
|
||||||
getResolver: () => resolver,
|
getResolver: () => resolver,
|
||||||
setResolver: (next: ((choice: KikuFieldGroupingChoice) => void) | null) => {
|
setResolver: (next: ((choice: KikuFieldGroupingChoice) => void) | null) => {
|
||||||
resolver = next;
|
resolver = next;
|
||||||
@@ -87,12 +83,10 @@ test('createFieldGroupingOverlayRuntime callback restores hidden visible overlay
|
|||||||
const runtime = createFieldGroupingOverlayRuntime<'runtime-options' | 'subsync'>({
|
const runtime = createFieldGroupingOverlayRuntime<'runtime-options' | 'subsync'>({
|
||||||
getMainWindow: () => null,
|
getMainWindow: () => null,
|
||||||
getVisibleOverlayVisible: () => visible,
|
getVisibleOverlayVisible: () => visible,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
setVisibleOverlayVisible: (nextVisible) => {
|
setVisibleOverlayVisible: (nextVisible) => {
|
||||||
visible = nextVisible;
|
visible = nextVisible;
|
||||||
visibilityTransitions.push(nextVisible);
|
visibilityTransitions.push(nextVisible);
|
||||||
},
|
},
|
||||||
setInvisibleOverlayVisible: () => {},
|
|
||||||
getResolver: () => resolver as ((choice: KikuFieldGroupingChoice) => void) | null,
|
getResolver: () => resolver as ((choice: KikuFieldGroupingChoice) => void) | null,
|
||||||
setResolver: (nextResolver: ((choice: KikuFieldGroupingChoice) => void) | null) => {
|
setResolver: (nextResolver: ((choice: KikuFieldGroupingChoice) => void) | null) => {
|
||||||
resolver = nextResolver;
|
resolver = nextResolver;
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ interface WindowLike {
|
|||||||
export interface FieldGroupingOverlayRuntimeOptions<T extends string> {
|
export interface FieldGroupingOverlayRuntimeOptions<T extends string> {
|
||||||
getMainWindow: () => WindowLike | null;
|
getMainWindow: () => WindowLike | null;
|
||||||
getVisibleOverlayVisible: () => boolean;
|
getVisibleOverlayVisible: () => boolean;
|
||||||
getInvisibleOverlayVisible: () => boolean;
|
|
||||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => void;
|
|
||||||
getResolver: () => ((choice: KikuFieldGroupingChoice) => void) | null;
|
getResolver: () => ((choice: KikuFieldGroupingChoice) => void) | null;
|
||||||
setResolver: (resolver: ((choice: KikuFieldGroupingChoice) => void) | null) => void;
|
setResolver: (resolver: ((choice: KikuFieldGroupingChoice) => void) | null) => void;
|
||||||
getRestoreVisibleOverlayOnModalClose: () => Set<T>;
|
getRestoreVisibleOverlayOnModalClose: () => Set<T>;
|
||||||
@@ -65,9 +63,7 @@ export function createFieldGroupingOverlayRuntime<T extends string>(
|
|||||||
) => Promise<KikuFieldGroupingChoice>) => {
|
) => Promise<KikuFieldGroupingChoice>) => {
|
||||||
return createFieldGroupingCallbackRuntime({
|
return createFieldGroupingCallbackRuntime({
|
||||||
getVisibleOverlayVisible: options.getVisibleOverlayVisible,
|
getVisibleOverlayVisible: options.getVisibleOverlayVisible,
|
||||||
getInvisibleOverlayVisible: options.getInvisibleOverlayVisible,
|
|
||||||
setVisibleOverlayVisible: options.setVisibleOverlayVisible,
|
setVisibleOverlayVisible: options.setVisibleOverlayVisible,
|
||||||
setInvisibleOverlayVisible: options.setInvisibleOverlayVisible,
|
|
||||||
getResolver: options.getResolver,
|
getResolver: options.getResolver,
|
||||||
setResolver: options.setResolver,
|
setResolver: options.setResolver,
|
||||||
sendToVisibleOverlay,
|
sendToVisibleOverlay,
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ import { KikuFieldGroupingChoice, KikuFieldGroupingRequestData } from '../../typ
|
|||||||
|
|
||||||
export function createFieldGroupingCallback(options: {
|
export function createFieldGroupingCallback(options: {
|
||||||
getVisibleOverlayVisible: () => boolean;
|
getVisibleOverlayVisible: () => boolean;
|
||||||
getInvisibleOverlayVisible: () => boolean;
|
|
||||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => void;
|
|
||||||
getResolver: () => ((choice: KikuFieldGroupingChoice) => void) | null;
|
getResolver: () => ((choice: KikuFieldGroupingChoice) => void) | null;
|
||||||
setResolver: (resolver: ((choice: KikuFieldGroupingChoice) => void) | null) => void;
|
setResolver: (resolver: ((choice: KikuFieldGroupingChoice) => void) | null) => void;
|
||||||
sendRequestToVisibleOverlay: (data: KikuFieldGroupingRequestData) => boolean;
|
sendRequestToVisibleOverlay: (data: KikuFieldGroupingRequestData) => boolean;
|
||||||
@@ -22,7 +20,6 @@ export function createFieldGroupingCallback(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const previousVisibleOverlay = options.getVisibleOverlayVisible();
|
const previousVisibleOverlay = options.getVisibleOverlayVisible();
|
||||||
const previousInvisibleOverlay = options.getInvisibleOverlayVisible();
|
|
||||||
let settled = false;
|
let settled = false;
|
||||||
|
|
||||||
const finish = (choice: KikuFieldGroupingChoice): void => {
|
const finish = (choice: KikuFieldGroupingChoice): void => {
|
||||||
@@ -36,9 +33,6 @@ export function createFieldGroupingCallback(options: {
|
|||||||
if (!previousVisibleOverlay && options.getVisibleOverlayVisible()) {
|
if (!previousVisibleOverlay && options.getVisibleOverlayVisible()) {
|
||||||
options.setVisibleOverlayVisible(false);
|
options.setVisibleOverlayVisible(false);
|
||||||
}
|
}
|
||||||
if (options.getInvisibleOverlayVisible() !== previousInvisibleOverlay) {
|
|
||||||
options.setInvisibleOverlayVisible(previousInvisibleOverlay);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
options.setResolver(finish);
|
options.setResolver(finish);
|
||||||
|
|||||||
@@ -36,10 +36,8 @@ function createFakeIpcRegistrar(): {
|
|||||||
test('createIpcDepsRuntime wires AniList handlers', async () => {
|
test('createIpcDepsRuntime wires AniList handlers', async () => {
|
||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
const deps = createIpcDepsRuntime({
|
const deps = createIpcDepsRuntime({
|
||||||
getInvisibleWindow: () => null,
|
|
||||||
getMainWindow: () => null,
|
getMainWindow: () => null,
|
||||||
getVisibleOverlayVisibility: () => false,
|
getVisibleOverlayVisibility: () => false,
|
||||||
getInvisibleOverlayVisibility: () => false,
|
|
||||||
onOverlayModalClosed: () => {},
|
onOverlayModalClosed: () => {},
|
||||||
openYomitanSettings: () => {},
|
openYomitanSettings: () => {},
|
||||||
quitApp: () => {},
|
quitApp: () => {},
|
||||||
@@ -47,7 +45,6 @@ test('createIpcDepsRuntime wires AniList handlers', async () => {
|
|||||||
tokenizeCurrentSubtitle: async () => null,
|
tokenizeCurrentSubtitle: async () => null,
|
||||||
getCurrentSubtitleRaw: () => '',
|
getCurrentSubtitleRaw: () => '',
|
||||||
getCurrentSubtitleAss: () => '',
|
getCurrentSubtitleAss: () => '',
|
||||||
getMpvSubtitleRenderMetrics: () => null,
|
|
||||||
getSubtitlePosition: () => null,
|
getSubtitlePosition: () => null,
|
||||||
getSubtitleStyle: () => null,
|
getSubtitleStyle: () => null,
|
||||||
saveSubtitlePosition: () => {},
|
saveSubtitlePosition: () => {},
|
||||||
@@ -64,7 +61,6 @@ test('createIpcDepsRuntime wires AniList handlers', async () => {
|
|||||||
setRuntimeOption: () => ({ ok: true }),
|
setRuntimeOption: () => ({ ok: true }),
|
||||||
cycleRuntimeOption: () => ({ ok: true }),
|
cycleRuntimeOption: () => ({ ok: true }),
|
||||||
reportOverlayContentBounds: () => {},
|
reportOverlayContentBounds: () => {},
|
||||||
reportHoveredSubtitleToken: () => {},
|
|
||||||
getAnilistStatus: () => ({ tokenStatus: 'resolved' }),
|
getAnilistStatus: () => ({ tokenStatus: 'resolved' }),
|
||||||
clearAnilistToken: () => {
|
clearAnilistToken: () => {
|
||||||
calls.push('clearAnilistToken');
|
calls.push('clearAnilistToken');
|
||||||
@@ -101,20 +97,15 @@ test('registerIpcHandlers rejects malformed runtime-option payloads', async () =
|
|||||||
const cycles: Array<{ id: string; direction: 1 | -1 }> = [];
|
const cycles: Array<{ id: string; direction: 1 | -1 }> = [];
|
||||||
registerIpcHandlers(
|
registerIpcHandlers(
|
||||||
{
|
{
|
||||||
getInvisibleWindow: () => null,
|
|
||||||
isVisibleOverlayVisible: () => false,
|
|
||||||
setInvisibleIgnoreMouseEvents: () => {},
|
|
||||||
onOverlayModalClosed: () => {},
|
onOverlayModalClosed: () => {},
|
||||||
openYomitanSettings: () => {},
|
openYomitanSettings: () => {},
|
||||||
quitApp: () => {},
|
quitApp: () => {},
|
||||||
toggleDevTools: () => {},
|
toggleDevTools: () => {},
|
||||||
getVisibleOverlayVisibility: () => false,
|
getVisibleOverlayVisibility: () => false,
|
||||||
toggleVisibleOverlay: () => {},
|
toggleVisibleOverlay: () => {},
|
||||||
getInvisibleOverlayVisibility: () => false,
|
|
||||||
tokenizeCurrentSubtitle: async () => null,
|
tokenizeCurrentSubtitle: async () => null,
|
||||||
getCurrentSubtitleRaw: () => '',
|
getCurrentSubtitleRaw: () => '',
|
||||||
getCurrentSubtitleAss: () => '',
|
getCurrentSubtitleAss: () => '',
|
||||||
getMpvSubtitleRenderMetrics: () => null,
|
|
||||||
getSubtitlePosition: () => null,
|
getSubtitlePosition: () => null,
|
||||||
getSubtitleStyle: () => null,
|
getSubtitleStyle: () => null,
|
||||||
saveSubtitlePosition: () => {},
|
saveSubtitlePosition: () => {},
|
||||||
@@ -138,7 +129,6 @@ test('registerIpcHandlers rejects malformed runtime-option payloads', async () =
|
|||||||
return { ok: true };
|
return { ok: true };
|
||||||
},
|
},
|
||||||
reportOverlayContentBounds: () => {},
|
reportOverlayContentBounds: () => {},
|
||||||
reportHoveredSubtitleToken: () => {},
|
|
||||||
getAnilistStatus: () => ({}),
|
getAnilistStatus: () => ({}),
|
||||||
clearAnilistToken: () => {},
|
clearAnilistToken: () => {},
|
||||||
openAnilistSetup: () => {},
|
openAnilistSetup: () => {},
|
||||||
@@ -176,25 +166,24 @@ test('registerIpcHandlers rejects malformed runtime-option payloads', async () =
|
|||||||
test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
|
test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
|
||||||
const { registrar, handlers } = createFakeIpcRegistrar();
|
const { registrar, handlers } = createFakeIpcRegistrar();
|
||||||
const saves: unknown[] = [];
|
const saves: unknown[] = [];
|
||||||
const modals: unknown[] = [];
|
const closedModals: unknown[] = [];
|
||||||
|
const openedModals: unknown[] = [];
|
||||||
registerIpcHandlers(
|
registerIpcHandlers(
|
||||||
{
|
{
|
||||||
getInvisibleWindow: () => null,
|
|
||||||
isVisibleOverlayVisible: () => false,
|
|
||||||
setInvisibleIgnoreMouseEvents: () => {},
|
|
||||||
onOverlayModalClosed: (modal) => {
|
onOverlayModalClosed: (modal) => {
|
||||||
modals.push(modal);
|
closedModals.push(modal);
|
||||||
|
},
|
||||||
|
onOverlayModalOpened: (modal) => {
|
||||||
|
openedModals.push(modal);
|
||||||
},
|
},
|
||||||
openYomitanSettings: () => {},
|
openYomitanSettings: () => {},
|
||||||
quitApp: () => {},
|
quitApp: () => {},
|
||||||
toggleDevTools: () => {},
|
toggleDevTools: () => {},
|
||||||
getVisibleOverlayVisibility: () => false,
|
getVisibleOverlayVisibility: () => false,
|
||||||
toggleVisibleOverlay: () => {},
|
toggleVisibleOverlay: () => {},
|
||||||
getInvisibleOverlayVisibility: () => false,
|
|
||||||
tokenizeCurrentSubtitle: async () => null,
|
tokenizeCurrentSubtitle: async () => null,
|
||||||
getCurrentSubtitleRaw: () => '',
|
getCurrentSubtitleRaw: () => '',
|
||||||
getCurrentSubtitleAss: () => '',
|
getCurrentSubtitleAss: () => '',
|
||||||
getMpvSubtitleRenderMetrics: () => null,
|
|
||||||
getSubtitlePosition: () => null,
|
getSubtitlePosition: () => null,
|
||||||
getSubtitleStyle: () => null,
|
getSubtitleStyle: () => null,
|
||||||
saveSubtitlePosition: (position) => {
|
saveSubtitlePosition: (position) => {
|
||||||
@@ -214,7 +203,6 @@ test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
|
|||||||
setRuntimeOption: () => ({ ok: true }),
|
setRuntimeOption: () => ({ ok: true }),
|
||||||
cycleRuntimeOption: () => ({ ok: true }),
|
cycleRuntimeOption: () => ({ ok: true }),
|
||||||
reportOverlayContentBounds: () => {},
|
reportOverlayContentBounds: () => {},
|
||||||
reportHoveredSubtitleToken: () => {},
|
|
||||||
getAnilistStatus: () => ({}),
|
getAnilistStatus: () => ({}),
|
||||||
clearAnilistToken: () => {},
|
clearAnilistToken: () => {},
|
||||||
openAnilistSetup: () => {},
|
openAnilistSetup: () => {},
|
||||||
@@ -228,11 +216,16 @@ test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
|
|||||||
handlers.on.get(IPC_CHANNELS.command.saveSubtitlePosition)!({}, { yPercent: 'bad' });
|
handlers.on.get(IPC_CHANNELS.command.saveSubtitlePosition)!({}, { yPercent: 'bad' });
|
||||||
handlers.on.get(IPC_CHANNELS.command.saveSubtitlePosition)!({}, { yPercent: 42 });
|
handlers.on.get(IPC_CHANNELS.command.saveSubtitlePosition)!({}, { yPercent: 42 });
|
||||||
assert.deepEqual(saves, [
|
assert.deepEqual(saves, [
|
||||||
{ yPercent: 42, invisibleOffsetXPx: undefined, invisibleOffsetYPx: undefined },
|
{ yPercent: 42 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'not-a-modal');
|
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'not-a-modal');
|
||||||
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'subsync');
|
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'subsync');
|
||||||
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'kiku');
|
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'kiku');
|
||||||
assert.deepEqual(modals, ['subsync', 'kiku']);
|
assert.deepEqual(closedModals, ['subsync', 'kiku']);
|
||||||
|
|
||||||
|
handlers.on.get(IPC_CHANNELS.command.overlayModalOpened)!({}, 'bad');
|
||||||
|
handlers.on.get(IPC_CHANNELS.command.overlayModalOpened)!({}, 'subsync');
|
||||||
|
handlers.on.get(IPC_CHANNELS.command.overlayModalOpened)!({}, 'runtime-options');
|
||||||
|
assert.deepEqual(openedModals, ['subsync', 'runtime-options']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,20 +19,16 @@ import {
|
|||||||
} from '../../shared/ipc/validators';
|
} from '../../shared/ipc/validators';
|
||||||
|
|
||||||
export interface IpcServiceDeps {
|
export interface IpcServiceDeps {
|
||||||
getInvisibleWindow: () => WindowLike | null;
|
|
||||||
isVisibleOverlayVisible: () => boolean;
|
|
||||||
setInvisibleIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => void;
|
|
||||||
onOverlayModalClosed: (modal: OverlayHostedModal) => void;
|
onOverlayModalClosed: (modal: OverlayHostedModal) => void;
|
||||||
|
onOverlayModalOpened?: (modal: OverlayHostedModal) => void;
|
||||||
openYomitanSettings: () => void;
|
openYomitanSettings: () => void;
|
||||||
quitApp: () => void;
|
quitApp: () => void;
|
||||||
toggleDevTools: () => void;
|
toggleDevTools: () => void;
|
||||||
getVisibleOverlayVisibility: () => boolean;
|
getVisibleOverlayVisibility: () => boolean;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
getInvisibleOverlayVisibility: () => boolean;
|
|
||||||
tokenizeCurrentSubtitle: () => Promise<unknown>;
|
tokenizeCurrentSubtitle: () => Promise<unknown>;
|
||||||
getCurrentSubtitleRaw: () => string;
|
getCurrentSubtitleRaw: () => string;
|
||||||
getCurrentSubtitleAss: () => string;
|
getCurrentSubtitleAss: () => string;
|
||||||
getMpvSubtitleRenderMetrics: () => unknown;
|
|
||||||
getSubtitlePosition: () => unknown;
|
getSubtitlePosition: () => unknown;
|
||||||
getSubtitleStyle: () => unknown;
|
getSubtitleStyle: () => unknown;
|
||||||
saveSubtitlePosition: (position: SubtitlePosition) => void;
|
saveSubtitlePosition: (position: SubtitlePosition) => void;
|
||||||
@@ -54,7 +50,6 @@ export interface IpcServiceDeps {
|
|||||||
setRuntimeOption: (id: RuntimeOptionId, value: RuntimeOptionValue) => unknown;
|
setRuntimeOption: (id: RuntimeOptionId, value: RuntimeOptionValue) => unknown;
|
||||||
cycleRuntimeOption: (id: RuntimeOptionId, direction: 1 | -1) => unknown;
|
cycleRuntimeOption: (id: RuntimeOptionId, direction: 1 | -1) => unknown;
|
||||||
reportOverlayContentBounds: (payload: unknown) => void;
|
reportOverlayContentBounds: (payload: unknown) => void;
|
||||||
reportHoveredSubtitleToken: (tokenIndex: number | null) => void;
|
|
||||||
getAnilistStatus: () => unknown;
|
getAnilistStatus: () => unknown;
|
||||||
clearAnilistToken: () => void;
|
clearAnilistToken: () => void;
|
||||||
openAnilistSetup: () => void;
|
openAnilistSetup: () => void;
|
||||||
@@ -91,18 +86,16 @@ interface IpcMainRegistrar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IpcDepsRuntimeOptions {
|
export interface IpcDepsRuntimeOptions {
|
||||||
getInvisibleWindow: () => WindowLike | null;
|
|
||||||
getMainWindow: () => WindowLike | null;
|
getMainWindow: () => WindowLike | null;
|
||||||
getVisibleOverlayVisibility: () => boolean;
|
getVisibleOverlayVisibility: () => boolean;
|
||||||
getInvisibleOverlayVisibility: () => boolean;
|
|
||||||
onOverlayModalClosed: (modal: OverlayHostedModal) => void;
|
onOverlayModalClosed: (modal: OverlayHostedModal) => void;
|
||||||
|
onOverlayModalOpened?: (modal: OverlayHostedModal) => void;
|
||||||
openYomitanSettings: () => void;
|
openYomitanSettings: () => void;
|
||||||
quitApp: () => void;
|
quitApp: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
tokenizeCurrentSubtitle: () => Promise<unknown>;
|
tokenizeCurrentSubtitle: () => Promise<unknown>;
|
||||||
getCurrentSubtitleRaw: () => string;
|
getCurrentSubtitleRaw: () => string;
|
||||||
getCurrentSubtitleAss: () => string;
|
getCurrentSubtitleAss: () => string;
|
||||||
getMpvSubtitleRenderMetrics: () => unknown;
|
|
||||||
getSubtitlePosition: () => unknown;
|
getSubtitlePosition: () => unknown;
|
||||||
getSubtitleStyle: () => unknown;
|
getSubtitleStyle: () => unknown;
|
||||||
saveSubtitlePosition: (position: SubtitlePosition) => void;
|
saveSubtitlePosition: (position: SubtitlePosition) => void;
|
||||||
@@ -119,7 +112,6 @@ export interface IpcDepsRuntimeOptions {
|
|||||||
setRuntimeOption: (id: RuntimeOptionId, value: RuntimeOptionValue) => unknown;
|
setRuntimeOption: (id: RuntimeOptionId, value: RuntimeOptionValue) => unknown;
|
||||||
cycleRuntimeOption: (id: RuntimeOptionId, direction: 1 | -1) => unknown;
|
cycleRuntimeOption: (id: RuntimeOptionId, direction: 1 | -1) => unknown;
|
||||||
reportOverlayContentBounds: (payload: unknown) => void;
|
reportOverlayContentBounds: (payload: unknown) => void;
|
||||||
reportHoveredSubtitleToken: (tokenIndex: number | null) => void;
|
|
||||||
getAnilistStatus: () => unknown;
|
getAnilistStatus: () => unknown;
|
||||||
clearAnilistToken: () => void;
|
clearAnilistToken: () => void;
|
||||||
openAnilistSetup: () => void;
|
openAnilistSetup: () => void;
|
||||||
@@ -130,14 +122,8 @@ export interface IpcDepsRuntimeOptions {
|
|||||||
|
|
||||||
export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcServiceDeps {
|
export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcServiceDeps {
|
||||||
return {
|
return {
|
||||||
getInvisibleWindow: () => options.getInvisibleWindow(),
|
|
||||||
isVisibleOverlayVisible: options.getVisibleOverlayVisibility,
|
|
||||||
setInvisibleIgnoreMouseEvents: (ignore, eventsOptions) => {
|
|
||||||
const invisibleWindow = options.getInvisibleWindow();
|
|
||||||
if (!invisibleWindow || invisibleWindow.isDestroyed()) return;
|
|
||||||
invisibleWindow.setIgnoreMouseEvents(ignore, eventsOptions);
|
|
||||||
},
|
|
||||||
onOverlayModalClosed: options.onOverlayModalClosed,
|
onOverlayModalClosed: options.onOverlayModalClosed,
|
||||||
|
onOverlayModalOpened: options.onOverlayModalOpened,
|
||||||
openYomitanSettings: options.openYomitanSettings,
|
openYomitanSettings: options.openYomitanSettings,
|
||||||
quitApp: options.quitApp,
|
quitApp: options.quitApp,
|
||||||
toggleDevTools: () => {
|
toggleDevTools: () => {
|
||||||
@@ -147,11 +133,9 @@ export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcService
|
|||||||
},
|
},
|
||||||
getVisibleOverlayVisibility: options.getVisibleOverlayVisibility,
|
getVisibleOverlayVisibility: options.getVisibleOverlayVisibility,
|
||||||
toggleVisibleOverlay: options.toggleVisibleOverlay,
|
toggleVisibleOverlay: options.toggleVisibleOverlay,
|
||||||
getInvisibleOverlayVisibility: options.getInvisibleOverlayVisibility,
|
|
||||||
tokenizeCurrentSubtitle: options.tokenizeCurrentSubtitle,
|
tokenizeCurrentSubtitle: options.tokenizeCurrentSubtitle,
|
||||||
getCurrentSubtitleRaw: options.getCurrentSubtitleRaw,
|
getCurrentSubtitleRaw: options.getCurrentSubtitleRaw,
|
||||||
getCurrentSubtitleAss: options.getCurrentSubtitleAss,
|
getCurrentSubtitleAss: options.getCurrentSubtitleAss,
|
||||||
getMpvSubtitleRenderMetrics: options.getMpvSubtitleRenderMetrics,
|
|
||||||
getSubtitlePosition: options.getSubtitlePosition,
|
getSubtitlePosition: options.getSubtitlePosition,
|
||||||
getSubtitleStyle: options.getSubtitleStyle,
|
getSubtitleStyle: options.getSubtitleStyle,
|
||||||
saveSubtitlePosition: options.saveSubtitlePosition,
|
saveSubtitlePosition: options.saveSubtitlePosition,
|
||||||
@@ -182,7 +166,6 @@ export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcService
|
|||||||
setRuntimeOption: options.setRuntimeOption,
|
setRuntimeOption: options.setRuntimeOption,
|
||||||
cycleRuntimeOption: options.cycleRuntimeOption,
|
cycleRuntimeOption: options.cycleRuntimeOption,
|
||||||
reportOverlayContentBounds: options.reportOverlayContentBounds,
|
reportOverlayContentBounds: options.reportOverlayContentBounds,
|
||||||
reportHoveredSubtitleToken: options.reportHoveredSubtitleToken,
|
|
||||||
getAnilistStatus: options.getAnilistStatus,
|
getAnilistStatus: options.getAnilistStatus,
|
||||||
clearAnilistToken: options.clearAnilistToken,
|
clearAnilistToken: options.clearAnilistToken,
|
||||||
openAnilistSetup: options.openAnilistSetup,
|
openAnilistSetup: options.openAnilistSetup,
|
||||||
@@ -200,18 +183,8 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
|
|||||||
const parsedOptions = parseOptionalForwardingOptions(options);
|
const parsedOptions = parseOptionalForwardingOptions(options);
|
||||||
const senderWindow = BrowserWindow.fromWebContents((event as IpcMainEvent).sender);
|
const senderWindow = BrowserWindow.fromWebContents((event as IpcMainEvent).sender);
|
||||||
if (senderWindow && !senderWindow.isDestroyed()) {
|
if (senderWindow && !senderWindow.isDestroyed()) {
|
||||||
const invisibleWindow = deps.getInvisibleWindow();
|
|
||||||
if (
|
|
||||||
senderWindow === invisibleWindow &&
|
|
||||||
deps.isVisibleOverlayVisible() &&
|
|
||||||
invisibleWindow &&
|
|
||||||
!invisibleWindow.isDestroyed()
|
|
||||||
) {
|
|
||||||
deps.setInvisibleIgnoreMouseEvents(true, { forward: true });
|
|
||||||
} else {
|
|
||||||
senderWindow.setIgnoreMouseEvents(ignore, parsedOptions);
|
senderWindow.setIgnoreMouseEvents(ignore, parsedOptions);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -220,6 +193,12 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
|
|||||||
if (!parsedModal) return;
|
if (!parsedModal) return;
|
||||||
deps.onOverlayModalClosed(parsedModal);
|
deps.onOverlayModalClosed(parsedModal);
|
||||||
});
|
});
|
||||||
|
ipc.on(IPC_CHANNELS.command.overlayModalOpened, (_event: unknown, modal: unknown) => {
|
||||||
|
const parsedModal = parseOverlayHostedModal(modal);
|
||||||
|
if (!parsedModal) return;
|
||||||
|
if (!deps.onOverlayModalOpened) return;
|
||||||
|
deps.onOverlayModalOpened(parsedModal);
|
||||||
|
});
|
||||||
|
|
||||||
ipc.on(IPC_CHANNELS.command.openYomitanSettings, () => {
|
ipc.on(IPC_CHANNELS.command.openYomitanSettings, () => {
|
||||||
deps.openYomitanSettings();
|
deps.openYomitanSettings();
|
||||||
@@ -245,10 +224,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
|
|||||||
return deps.getVisibleOverlayVisibility();
|
return deps.getVisibleOverlayVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.handle(IPC_CHANNELS.request.getInvisibleOverlayVisibility, () => {
|
|
||||||
return deps.getInvisibleOverlayVisibility();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.handle(IPC_CHANNELS.request.getCurrentSubtitle, async () => {
|
ipc.handle(IPC_CHANNELS.request.getCurrentSubtitle, async () => {
|
||||||
return await deps.tokenizeCurrentSubtitle();
|
return await deps.tokenizeCurrentSubtitle();
|
||||||
});
|
});
|
||||||
@@ -261,10 +236,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
|
|||||||
return deps.getCurrentSubtitleAss();
|
return deps.getCurrentSubtitleAss();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.handle(IPC_CHANNELS.request.getMpvSubtitleRenderMetrics, () => {
|
|
||||||
return deps.getMpvSubtitleRenderMetrics();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.handle(IPC_CHANNELS.request.getSubtitlePosition, () => {
|
ipc.handle(IPC_CHANNELS.request.getSubtitlePosition, () => {
|
||||||
return deps.getSubtitlePosition();
|
return deps.getSubtitlePosition();
|
||||||
});
|
});
|
||||||
@@ -358,17 +329,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
|
|||||||
deps.reportOverlayContentBounds(payload);
|
deps.reportOverlayContentBounds(payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('subtitle-token-hover:set', (_event: unknown, tokenIndex: unknown) => {
|
|
||||||
if (tokenIndex === null) {
|
|
||||||
deps.reportHoveredSubtitleToken(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Number.isInteger(tokenIndex) || (tokenIndex as number) < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deps.reportHoveredSubtitleToken(tokenIndex as number);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.handle(IPC_CHANNELS.request.getAnilistStatus, () => {
|
ipc.handle(IPC_CHANNELS.request.getAnilistStatus, () => {
|
||||||
return deps.getAnilistStatus();
|
return deps.getAnilistStatus();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,13 +33,51 @@ test('sendToVisibleOverlayRuntime restores visibility flag when opening hidden o
|
|||||||
assert.deepEqual(sent, [['runtime-options:open']]);
|
assert.deepEqual(sent, [['runtime-options:open']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sendToVisibleOverlayRuntime waits for overlay page before sending open command', () => {
|
||||||
|
const sent: unknown[][] = [];
|
||||||
|
const restoreSet = new Set<'runtime-options' | 'subsync'>();
|
||||||
|
let loading = true;
|
||||||
|
let currentURL = '';
|
||||||
|
const finishCallbacks: Array<() => void> = [];
|
||||||
|
|
||||||
|
const ok = sendToVisibleOverlayRuntime({
|
||||||
|
mainWindow: {
|
||||||
|
isDestroyed: () => false,
|
||||||
|
webContents: {
|
||||||
|
isLoading: () => loading,
|
||||||
|
getURL: () => currentURL,
|
||||||
|
send: (...args: unknown[]) => {
|
||||||
|
sent.push(args);
|
||||||
|
},
|
||||||
|
once: (_event: string, callback: () => void) => {
|
||||||
|
finishCallbacks.push(callback);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as Electron.BrowserWindow,
|
||||||
|
visibleOverlayVisible: false,
|
||||||
|
setVisibleOverlayVisible: () => {},
|
||||||
|
channel: 'runtime-options:open',
|
||||||
|
restoreOnModalClose: 'runtime-options',
|
||||||
|
restoreVisibleOverlayOnModalClose: restoreSet,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(ok, true);
|
||||||
|
assert.deepEqual(sent, []);
|
||||||
|
assert.equal(restoreSet.has('runtime-options'), true);
|
||||||
|
|
||||||
|
loading = false;
|
||||||
|
currentURL = 'file:///overlay/index.html?layer=visible';
|
||||||
|
assert.ok(finishCallbacks[0]);
|
||||||
|
finishCallbacks[0]!();
|
||||||
|
|
||||||
|
assert.deepEqual(sent, [['runtime-options:open']]);
|
||||||
|
});
|
||||||
|
|
||||||
test('createFieldGroupingCallbackRuntime cancels when overlay request cannot be sent', async () => {
|
test('createFieldGroupingCallbackRuntime cancels when overlay request cannot be sent', async () => {
|
||||||
let resolver: ((choice: KikuFieldGroupingChoice) => void) | null = null;
|
let resolver: ((choice: KikuFieldGroupingChoice) => void) | null = null;
|
||||||
const callback = createFieldGroupingCallbackRuntime<'runtime-options' | 'subsync'>({
|
const callback = createFieldGroupingCallbackRuntime<'runtime-options' | 'subsync'>({
|
||||||
getVisibleOverlayVisible: () => false,
|
getVisibleOverlayVisible: () => false,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
setVisibleOverlayVisible: () => {},
|
setVisibleOverlayVisible: () => {},
|
||||||
setInvisibleOverlayVisible: () => {},
|
|
||||||
getResolver: () => resolver,
|
getResolver: () => resolver,
|
||||||
setResolver: (next) => {
|
setResolver: (next) => {
|
||||||
resolver = next;
|
resolver = next;
|
||||||
|
|||||||
@@ -26,27 +26,32 @@ export function sendToVisibleOverlayRuntime<T extends string>(options: {
|
|||||||
options.mainWindow!.webContents.send(options.channel, options.payload);
|
options.mainWindow!.webContents.send(options.channel, options.payload);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (options.mainWindow.webContents.isLoading()) {
|
|
||||||
|
const getURL = options.mainWindow.webContents.getURL;
|
||||||
|
const currentURL =
|
||||||
|
typeof getURL === 'function' ? getURL.call(options.mainWindow.webContents) : 'ready';
|
||||||
|
const isReady =
|
||||||
|
!options.mainWindow.webContents.isLoading() &&
|
||||||
|
currentURL !== '' &&
|
||||||
|
currentURL !== 'about:blank';
|
||||||
|
|
||||||
|
if (!isReady) {
|
||||||
options.mainWindow.webContents.once('did-finish-load', () => {
|
options.mainWindow.webContents.once('did-finish-load', () => {
|
||||||
if (
|
if (!options.mainWindow || options.mainWindow.isDestroyed()) return;
|
||||||
options.mainWindow &&
|
if (!options.mainWindow.webContents.isLoading()) {
|
||||||
!options.mainWindow.isDestroyed() &&
|
|
||||||
!options.mainWindow.webContents.isLoading()
|
|
||||||
) {
|
|
||||||
sendNow();
|
sendNow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNow();
|
sendNow();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFieldGroupingCallbackRuntime<T extends string>(options: {
|
export function createFieldGroupingCallbackRuntime<T extends string>(options: {
|
||||||
getVisibleOverlayVisible: () => boolean;
|
getVisibleOverlayVisible: () => boolean;
|
||||||
getInvisibleOverlayVisible: () => boolean;
|
|
||||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => void;
|
|
||||||
getResolver: () => ((choice: KikuFieldGroupingChoice) => void) | null;
|
getResolver: () => ((choice: KikuFieldGroupingChoice) => void) | null;
|
||||||
setResolver: (resolver: ((choice: KikuFieldGroupingChoice) => void) | null) => void;
|
setResolver: (resolver: ((choice: KikuFieldGroupingChoice) => void) | null) => void;
|
||||||
sendToVisibleOverlay: (
|
sendToVisibleOverlay: (
|
||||||
@@ -57,9 +62,7 @@ export function createFieldGroupingCallbackRuntime<T extends string>(options: {
|
|||||||
}): (data: KikuFieldGroupingRequestData) => Promise<KikuFieldGroupingChoice> {
|
}): (data: KikuFieldGroupingRequestData) => Promise<KikuFieldGroupingChoice> {
|
||||||
return createFieldGroupingCallback({
|
return createFieldGroupingCallback({
|
||||||
getVisibleOverlayVisible: options.getVisibleOverlayVisible,
|
getVisibleOverlayVisible: options.getVisibleOverlayVisible,
|
||||||
getInvisibleOverlayVisible: options.getInvisibleOverlayVisible,
|
|
||||||
setVisibleOverlayVisible: options.setVisibleOverlayVisible,
|
setVisibleOverlayVisible: options.setVisibleOverlayVisible,
|
||||||
setInvisibleOverlayVisible: options.setInvisibleOverlayVisible,
|
|
||||||
getResolver: options.getResolver,
|
getResolver: options.getResolver,
|
||||||
setResolver: options.setResolver,
|
setResolver: options.setResolver,
|
||||||
sendRequestToVisibleOverlay: (data) =>
|
sendRequestToVisibleOverlay: (data) =>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ test('sanitizeOverlayContentMeasurement accepts valid payload with null rect', (
|
|||||||
test('sanitizeOverlayContentMeasurement rejects invalid ranges', () => {
|
test('sanitizeOverlayContentMeasurement rejects invalid ranges', () => {
|
||||||
const measurement = sanitizeOverlayContentMeasurement(
|
const measurement = sanitizeOverlayContentMeasurement(
|
||||||
{
|
{
|
||||||
layer: 'invisible',
|
layer: 'visible',
|
||||||
measuredAtMs: 100,
|
measuredAtMs: 100,
|
||||||
viewport: { width: 0, height: 1080 },
|
viewport: { width: 0, height: 1080 },
|
||||||
contentRect: { x: 0, y: 0, width: 100, height: 20 },
|
contentRect: { x: 0, y: 0, width: 100, height: 20 },
|
||||||
@@ -39,7 +39,7 @@ test('sanitizeOverlayContentMeasurement rejects invalid ranges', () => {
|
|||||||
assert.equal(measurement, null);
|
assert.equal(measurement, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('overlay measurement store keeps latest payload per layer', () => {
|
test('overlay measurement store keeps latest payload for visible layer', () => {
|
||||||
const store = createOverlayContentMeasurementStore({
|
const store = createOverlayContentMeasurementStore({
|
||||||
now: () => 1000,
|
now: () => 1000,
|
||||||
warn: () => {
|
warn: () => {
|
||||||
@@ -53,17 +53,9 @@ test('overlay measurement store keeps latest payload per layer', () => {
|
|||||||
viewport: { width: 1280, height: 720 },
|
viewport: { width: 1280, height: 720 },
|
||||||
contentRect: { x: 50, y: 60, width: 400, height: 80 },
|
contentRect: { x: 50, y: 60, width: 400, height: 80 },
|
||||||
});
|
});
|
||||||
const invisible = store.report({
|
|
||||||
layer: 'invisible',
|
|
||||||
measuredAtMs: 910,
|
|
||||||
viewport: { width: 1280, height: 720 },
|
|
||||||
contentRect: { x: 20, y: 30, width: 300, height: 40 },
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(visible?.layer, 'visible');
|
assert.equal(visible?.layer, 'visible');
|
||||||
assert.equal(invisible?.layer, 'invisible');
|
|
||||||
assert.equal(store.getLatestByLayer('visible')?.contentRect?.width, 400);
|
assert.equal(store.getLatestByLayer('visible')?.contentRect?.width, 400);
|
||||||
assert.equal(store.getLatestByLayer('invisible')?.contentRect?.height, 40);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('overlay measurement store rate-limits invalid payload warnings', () => {
|
test('overlay measurement store rate-limits invalid payload warnings', () => {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function sanitizeOverlayContentMeasurement(
|
|||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (candidate.layer !== 'visible' && candidate.layer !== 'invisible') {
|
if (candidate.layer !== 'visible') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +112,6 @@ export function createOverlayContentMeasurementStore(options?: {
|
|||||||
const warn = options?.warn ?? ((message: string) => logger.warn(message));
|
const warn = options?.warn ?? ((message: string) => logger.warn(message));
|
||||||
const latestByLayer: OverlayMeasurementStore = {
|
const latestByLayer: OverlayMeasurementStore = {
|
||||||
visible: null,
|
visible: null,
|
||||||
invisible: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let droppedInvalid = 0;
|
let droppedInvalid = 0;
|
||||||
|
|||||||
@@ -10,16 +10,11 @@ import {
|
|||||||
|
|
||||||
export function initializeOverlayRuntime(options: {
|
export function initializeOverlayRuntime(options: {
|
||||||
backendOverride: string | null;
|
backendOverride: string | null;
|
||||||
getInitialInvisibleOverlayVisibility: () => boolean;
|
|
||||||
createMainWindow: () => void;
|
createMainWindow: () => void;
|
||||||
createInvisibleWindow: () => void;
|
|
||||||
registerGlobalShortcuts: () => void;
|
registerGlobalShortcuts: () => void;
|
||||||
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
||||||
updateInvisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
|
||||||
isVisibleOverlayVisible: () => boolean;
|
isVisibleOverlayVisible: () => boolean;
|
||||||
isInvisibleOverlayVisible: () => boolean;
|
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
getOverlayWindows: () => BrowserWindow[];
|
getOverlayWindows: () => BrowserWindow[];
|
||||||
syncOverlayShortcuts: () => void;
|
syncOverlayShortcuts: () => void;
|
||||||
setWindowTracker: (tracker: BaseWindowTracker | null) => void;
|
setWindowTracker: (tracker: BaseWindowTracker | null) => void;
|
||||||
@@ -38,12 +33,8 @@ export function initializeOverlayRuntime(options: {
|
|||||||
data: KikuFieldGroupingRequestData,
|
data: KikuFieldGroupingRequestData,
|
||||||
) => Promise<KikuFieldGroupingChoice>;
|
) => Promise<KikuFieldGroupingChoice>;
|
||||||
getKnownWordCacheStatePath: () => string;
|
getKnownWordCacheStatePath: () => string;
|
||||||
}): {
|
}): void {
|
||||||
invisibleOverlayVisible: boolean;
|
|
||||||
} {
|
|
||||||
options.createMainWindow();
|
options.createMainWindow();
|
||||||
options.createInvisibleWindow();
|
|
||||||
const invisibleOverlayVisible = options.getInitialInvisibleOverlayVisibility();
|
|
||||||
options.registerGlobalShortcuts();
|
options.registerGlobalShortcuts();
|
||||||
|
|
||||||
const windowTracker = createWindowTracker(options.backendOverride, options.getMpvSocketPath());
|
const windowTracker = createWindowTracker(options.backendOverride, options.getMpvSocketPath());
|
||||||
@@ -51,17 +42,12 @@ export function initializeOverlayRuntime(options: {
|
|||||||
if (windowTracker) {
|
if (windowTracker) {
|
||||||
windowTracker.onGeometryChange = (geometry: WindowGeometry) => {
|
windowTracker.onGeometryChange = (geometry: WindowGeometry) => {
|
||||||
options.updateVisibleOverlayBounds(geometry);
|
options.updateVisibleOverlayBounds(geometry);
|
||||||
options.updateInvisibleOverlayBounds(geometry);
|
|
||||||
};
|
};
|
||||||
windowTracker.onWindowFound = (geometry: WindowGeometry) => {
|
windowTracker.onWindowFound = (geometry: WindowGeometry) => {
|
||||||
options.updateVisibleOverlayBounds(geometry);
|
options.updateVisibleOverlayBounds(geometry);
|
||||||
options.updateInvisibleOverlayBounds(geometry);
|
|
||||||
if (options.isVisibleOverlayVisible()) {
|
if (options.isVisibleOverlayVisible()) {
|
||||||
options.updateVisibleOverlayVisibility();
|
options.updateVisibleOverlayVisibility();
|
||||||
}
|
}
|
||||||
if (options.isInvisibleOverlayVisible()) {
|
|
||||||
options.updateInvisibleOverlayVisibility();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
windowTracker.onWindowLost = () => {
|
windowTracker.onWindowLost = () => {
|
||||||
for (const window of options.getOverlayWindows()) {
|
for (const window of options.getOverlayWindows()) {
|
||||||
@@ -101,7 +87,4 @@ export function initializeOverlayRuntime(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options.updateVisibleOverlayVisibility();
|
options.updateVisibleOverlayVisibility();
|
||||||
options.updateInvisibleOverlayVisibility();
|
|
||||||
|
|
||||||
return { invisibleOverlayVisible };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
function makeShortcuts(overrides: Partial<ConfiguredShortcuts> = {}): ConfiguredShortcuts {
|
function makeShortcuts(overrides: Partial<ConfiguredShortcuts> = {}): ConfiguredShortcuts {
|
||||||
return {
|
return {
|
||||||
toggleVisibleOverlayGlobal: null,
|
toggleVisibleOverlayGlobal: null,
|
||||||
toggleInvisibleOverlayGlobal: null,
|
|
||||||
copySubtitle: null,
|
copySubtitle: null,
|
||||||
copySubtitleMultiple: null,
|
copySubtitleMultiple: null,
|
||||||
updateLastCardFromClipboard: null,
|
updateLastCardFromClipboard: null,
|
||||||
|
|||||||
265
src/core/services/overlay-visibility.test.ts
Normal file
265
src/core/services/overlay-visibility.test.ts
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import { setVisibleOverlayVisible, updateVisibleOverlayVisibility } from './overlay-visibility';
|
||||||
|
|
||||||
|
type WindowTrackerStub = {
|
||||||
|
isTracking: () => boolean;
|
||||||
|
getGeometry: () => { x: number; y: number; width: number; height: number } | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function createMainWindowRecorder() {
|
||||||
|
const calls: string[] = [];
|
||||||
|
const window = {
|
||||||
|
isDestroyed: () => false,
|
||||||
|
hide: () => {
|
||||||
|
calls.push('hide');
|
||||||
|
},
|
||||||
|
show: () => {
|
||||||
|
calls.push('show');
|
||||||
|
},
|
||||||
|
focus: () => {
|
||||||
|
calls.push('focus');
|
||||||
|
},
|
||||||
|
setIgnoreMouseEvents: () => {
|
||||||
|
calls.push('mouse-ignore');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return { window, calls };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('macOS keeps visible overlay hidden while tracker is not ready and emits one loading OSD', () => {
|
||||||
|
const { window, calls } = createMainWindowRecorder();
|
||||||
|
let trackerWarning = false;
|
||||||
|
const osdMessages: string[] = [];
|
||||||
|
const tracker: WindowTrackerStub = {
|
||||||
|
isTracking: () => false,
|
||||||
|
getGeometry: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const run = () =>
|
||||||
|
updateVisibleOverlayVisibility({
|
||||||
|
visibleOverlayVisible: true,
|
||||||
|
mainWindow: window as never,
|
||||||
|
windowTracker: tracker as never,
|
||||||
|
trackerNotReadyWarningShown: trackerWarning,
|
||||||
|
setTrackerNotReadyWarningShown: (shown: boolean) => {
|
||||||
|
trackerWarning = shown;
|
||||||
|
},
|
||||||
|
updateVisibleOverlayBounds: () => {
|
||||||
|
calls.push('update-bounds');
|
||||||
|
},
|
||||||
|
ensureOverlayWindowLevel: () => {
|
||||||
|
calls.push('ensure-level');
|
||||||
|
},
|
||||||
|
syncPrimaryOverlayWindowLayer: () => {
|
||||||
|
calls.push('sync-layer');
|
||||||
|
},
|
||||||
|
enforceOverlayLayerOrder: () => {
|
||||||
|
calls.push('enforce-order');
|
||||||
|
},
|
||||||
|
syncOverlayShortcuts: () => {
|
||||||
|
calls.push('sync-shortcuts');
|
||||||
|
},
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
showOverlayLoadingOsd: (message: string) => {
|
||||||
|
osdMessages.push(message);
|
||||||
|
},
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
run();
|
||||||
|
run();
|
||||||
|
|
||||||
|
assert.equal(trackerWarning, true);
|
||||||
|
assert.deepEqual(osdMessages, ['Overlay loading...']);
|
||||||
|
assert.ok(calls.includes('hide'));
|
||||||
|
assert.ok(!calls.includes('show'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('non-macOS keeps fallback visible overlay behavior when tracker is not ready', () => {
|
||||||
|
const { window, calls } = createMainWindowRecorder();
|
||||||
|
let trackerWarning = false;
|
||||||
|
const tracker: WindowTrackerStub = {
|
||||||
|
isTracking: () => false,
|
||||||
|
getGeometry: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateVisibleOverlayVisibility({
|
||||||
|
visibleOverlayVisible: true,
|
||||||
|
mainWindow: window as never,
|
||||||
|
windowTracker: tracker as never,
|
||||||
|
trackerNotReadyWarningShown: trackerWarning,
|
||||||
|
setTrackerNotReadyWarningShown: (shown: boolean) => {
|
||||||
|
trackerWarning = shown;
|
||||||
|
},
|
||||||
|
updateVisibleOverlayBounds: () => {
|
||||||
|
calls.push('update-bounds');
|
||||||
|
},
|
||||||
|
ensureOverlayWindowLevel: () => {
|
||||||
|
calls.push('ensure-level');
|
||||||
|
},
|
||||||
|
syncPrimaryOverlayWindowLayer: () => {
|
||||||
|
calls.push('sync-layer');
|
||||||
|
},
|
||||||
|
enforceOverlayLayerOrder: () => {
|
||||||
|
calls.push('enforce-order');
|
||||||
|
},
|
||||||
|
syncOverlayShortcuts: () => {
|
||||||
|
calls.push('sync-shortcuts');
|
||||||
|
},
|
||||||
|
isMacOSPlatform: false,
|
||||||
|
showOverlayLoadingOsd: () => {
|
||||||
|
calls.push('osd');
|
||||||
|
},
|
||||||
|
resolveFallbackBounds: () => ({ x: 12, y: 24, width: 640, height: 360 }),
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
assert.equal(trackerWarning, true);
|
||||||
|
assert.ok(calls.includes('update-bounds'));
|
||||||
|
assert.ok(calls.includes('show'));
|
||||||
|
assert.ok(calls.includes('focus'));
|
||||||
|
assert.ok(!calls.includes('osd'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('macOS keeps visible overlay hidden while tracker is not initialized yet', () => {
|
||||||
|
const { window, calls } = createMainWindowRecorder();
|
||||||
|
let trackerWarning = false;
|
||||||
|
const osdMessages: string[] = [];
|
||||||
|
|
||||||
|
updateVisibleOverlayVisibility({
|
||||||
|
visibleOverlayVisible: true,
|
||||||
|
mainWindow: window as never,
|
||||||
|
windowTracker: null,
|
||||||
|
trackerNotReadyWarningShown: trackerWarning,
|
||||||
|
setTrackerNotReadyWarningShown: (shown: boolean) => {
|
||||||
|
trackerWarning = shown;
|
||||||
|
},
|
||||||
|
updateVisibleOverlayBounds: () => {
|
||||||
|
calls.push('update-bounds');
|
||||||
|
},
|
||||||
|
ensureOverlayWindowLevel: () => {
|
||||||
|
calls.push('ensure-level');
|
||||||
|
},
|
||||||
|
syncPrimaryOverlayWindowLayer: () => {
|
||||||
|
calls.push('sync-layer');
|
||||||
|
},
|
||||||
|
enforceOverlayLayerOrder: () => {
|
||||||
|
calls.push('enforce-order');
|
||||||
|
},
|
||||||
|
syncOverlayShortcuts: () => {
|
||||||
|
calls.push('sync-shortcuts');
|
||||||
|
},
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
showOverlayLoadingOsd: (message: string) => {
|
||||||
|
osdMessages.push(message);
|
||||||
|
},
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
assert.equal(trackerWarning, true);
|
||||||
|
assert.deepEqual(osdMessages, ['Overlay loading...']);
|
||||||
|
assert.ok(calls.includes('hide'));
|
||||||
|
assert.ok(!calls.includes('show'));
|
||||||
|
assert.ok(!calls.includes('update-bounds'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setVisibleOverlayVisible does not mutate mpv subtitle visibility directly', () => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
setVisibleOverlayVisible({
|
||||||
|
visible: true,
|
||||||
|
setVisibleOverlayVisibleState: (visible) => {
|
||||||
|
calls.push(`state:${visible}`);
|
||||||
|
},
|
||||||
|
updateVisibleOverlayVisibility: () => {
|
||||||
|
calls.push('update');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(calls, ['state:true', 'update']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('macOS loading OSD can show again after overlay is hidden and retried', () => {
|
||||||
|
const { window, calls } = createMainWindowRecorder();
|
||||||
|
const osdMessages: string[] = [];
|
||||||
|
let trackerWarning = false;
|
||||||
|
|
||||||
|
updateVisibleOverlayVisibility({
|
||||||
|
visibleOverlayVisible: true,
|
||||||
|
mainWindow: window as never,
|
||||||
|
windowTracker: null,
|
||||||
|
trackerNotReadyWarningShown: trackerWarning,
|
||||||
|
setTrackerNotReadyWarningShown: (shown: boolean) => {
|
||||||
|
trackerWarning = shown;
|
||||||
|
calls.push(`warn:${shown ? 'yes' : 'no'}`);
|
||||||
|
},
|
||||||
|
updateVisibleOverlayBounds: () => {
|
||||||
|
calls.push('update-bounds');
|
||||||
|
},
|
||||||
|
ensureOverlayWindowLevel: () => {
|
||||||
|
calls.push('ensure-level');
|
||||||
|
},
|
||||||
|
syncPrimaryOverlayWindowLayer: () => {
|
||||||
|
calls.push('sync-layer');
|
||||||
|
},
|
||||||
|
enforceOverlayLayerOrder: () => {
|
||||||
|
calls.push('enforce-order');
|
||||||
|
},
|
||||||
|
syncOverlayShortcuts: () => {
|
||||||
|
calls.push('sync-shortcuts');
|
||||||
|
},
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
showOverlayLoadingOsd: (message: string) => {
|
||||||
|
osdMessages.push(message);
|
||||||
|
},
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
updateVisibleOverlayVisibility({
|
||||||
|
visibleOverlayVisible: false,
|
||||||
|
mainWindow: window as never,
|
||||||
|
windowTracker: null,
|
||||||
|
trackerNotReadyWarningShown: trackerWarning,
|
||||||
|
setTrackerNotReadyWarningShown: (shown: boolean) => {
|
||||||
|
trackerWarning = shown;
|
||||||
|
calls.push(`warn:${shown ? 'yes' : 'no'}`);
|
||||||
|
},
|
||||||
|
updateVisibleOverlayBounds: () => {},
|
||||||
|
ensureOverlayWindowLevel: () => {},
|
||||||
|
syncPrimaryOverlayWindowLayer: () => {},
|
||||||
|
enforceOverlayLayerOrder: () => {},
|
||||||
|
syncOverlayShortcuts: () => {},
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
showOverlayLoadingOsd: () => {},
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
updateVisibleOverlayVisibility({
|
||||||
|
visibleOverlayVisible: true,
|
||||||
|
mainWindow: window as never,
|
||||||
|
windowTracker: null,
|
||||||
|
trackerNotReadyWarningShown: trackerWarning,
|
||||||
|
setTrackerNotReadyWarningShown: (shown: boolean) => {
|
||||||
|
trackerWarning = shown;
|
||||||
|
calls.push(`warn:${shown ? 'yes' : 'no'}`);
|
||||||
|
},
|
||||||
|
updateVisibleOverlayBounds: () => {
|
||||||
|
calls.push('update-bounds');
|
||||||
|
},
|
||||||
|
ensureOverlayWindowLevel: () => {
|
||||||
|
calls.push('ensure-level');
|
||||||
|
},
|
||||||
|
syncPrimaryOverlayWindowLayer: () => {
|
||||||
|
calls.push('sync-layer');
|
||||||
|
},
|
||||||
|
enforceOverlayLayerOrder: () => {
|
||||||
|
calls.push('enforce-order');
|
||||||
|
},
|
||||||
|
syncOverlayShortcuts: () => {
|
||||||
|
calls.push('sync-shortcuts');
|
||||||
|
},
|
||||||
|
isMacOSPlatform: true,
|
||||||
|
showOverlayLoadingOsd: (message: string) => {
|
||||||
|
osdMessages.push(message);
|
||||||
|
},
|
||||||
|
} as never);
|
||||||
|
|
||||||
|
assert.deepEqual(osdMessages, ['Overlay loading...', 'Overlay loading...']);
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BrowserWindow, screen } from 'electron';
|
import type { BrowserWindow } from 'electron';
|
||||||
import { BaseWindowTracker } from '../../window-trackers';
|
import { BaseWindowTracker } from '../../window-trackers';
|
||||||
import { WindowGeometry } from '../../types';
|
import { WindowGeometry } from '../../types';
|
||||||
|
|
||||||
@@ -10,14 +10,19 @@ export function updateVisibleOverlayVisibility(args: {
|
|||||||
setTrackerNotReadyWarningShown: (shown: boolean) => void;
|
setTrackerNotReadyWarningShown: (shown: boolean) => void;
|
||||||
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
||||||
ensureOverlayWindowLevel: (window: BrowserWindow) => void;
|
ensureOverlayWindowLevel: (window: BrowserWindow) => void;
|
||||||
|
syncPrimaryOverlayWindowLayer: (layer: 'visible') => void;
|
||||||
enforceOverlayLayerOrder: () => void;
|
enforceOverlayLayerOrder: () => void;
|
||||||
syncOverlayShortcuts: () => void;
|
syncOverlayShortcuts: () => void;
|
||||||
|
isMacOSPlatform?: boolean;
|
||||||
|
showOverlayLoadingOsd?: (message: string) => void;
|
||||||
|
resolveFallbackBounds: () => WindowGeometry;
|
||||||
}): void {
|
}): void {
|
||||||
if (!args.mainWindow || args.mainWindow.isDestroyed()) {
|
if (!args.mainWindow || args.mainWindow.isDestroyed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args.visibleOverlayVisible) {
|
if (!args.visibleOverlayVisible) {
|
||||||
|
args.setTrackerNotReadyWarningShown(false);
|
||||||
args.mainWindow.hide();
|
args.mainWindow.hide();
|
||||||
args.syncOverlayShortcuts();
|
args.syncOverlayShortcuts();
|
||||||
return;
|
return;
|
||||||
@@ -29,6 +34,8 @@ export function updateVisibleOverlayVisibility(args: {
|
|||||||
if (geometry) {
|
if (geometry) {
|
||||||
args.updateVisibleOverlayBounds(geometry);
|
args.updateVisibleOverlayBounds(geometry);
|
||||||
}
|
}
|
||||||
|
args.syncPrimaryOverlayWindowLayer('visible');
|
||||||
|
args.mainWindow.setIgnoreMouseEvents(false);
|
||||||
args.ensureOverlayWindowLevel(args.mainWindow);
|
args.ensureOverlayWindowLevel(args.mainWindow);
|
||||||
args.mainWindow.show();
|
args.mainWindow.show();
|
||||||
args.mainWindow.focus();
|
args.mainWindow.focus();
|
||||||
@@ -38,7 +45,18 @@ export function updateVisibleOverlayVisibility(args: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!args.windowTracker) {
|
if (!args.windowTracker) {
|
||||||
|
if (args.isMacOSPlatform) {
|
||||||
|
if (!args.trackerNotReadyWarningShown) {
|
||||||
|
args.setTrackerNotReadyWarningShown(true);
|
||||||
|
args.showOverlayLoadingOsd?.('Overlay loading...');
|
||||||
|
}
|
||||||
|
args.mainWindow.hide();
|
||||||
|
args.syncOverlayShortcuts();
|
||||||
|
return;
|
||||||
|
}
|
||||||
args.setTrackerNotReadyWarningShown(false);
|
args.setTrackerNotReadyWarningShown(false);
|
||||||
|
args.syncPrimaryOverlayWindowLayer('visible');
|
||||||
|
args.mainWindow.setIgnoreMouseEvents(false);
|
||||||
args.ensureOverlayWindowLevel(args.mainWindow);
|
args.ensureOverlayWindowLevel(args.mainWindow);
|
||||||
args.mainWindow.show();
|
args.mainWindow.show();
|
||||||
args.mainWindow.focus();
|
args.mainWindow.focus();
|
||||||
@@ -49,16 +67,21 @@ export function updateVisibleOverlayVisibility(args: {
|
|||||||
|
|
||||||
if (!args.trackerNotReadyWarningShown) {
|
if (!args.trackerNotReadyWarningShown) {
|
||||||
args.setTrackerNotReadyWarningShown(true);
|
args.setTrackerNotReadyWarningShown(true);
|
||||||
|
if (args.isMacOSPlatform) {
|
||||||
|
args.showOverlayLoadingOsd?.('Overlay loading...');
|
||||||
}
|
}
|
||||||
const cursorPoint = screen.getCursorScreenPoint();
|
}
|
||||||
const display = screen.getDisplayNearestPoint(cursorPoint);
|
|
||||||
const fallbackBounds = display.workArea;
|
if (args.isMacOSPlatform) {
|
||||||
args.updateVisibleOverlayBounds({
|
args.mainWindow.hide();
|
||||||
x: fallbackBounds.x,
|
args.syncOverlayShortcuts();
|
||||||
y: fallbackBounds.y,
|
return;
|
||||||
width: fallbackBounds.width,
|
}
|
||||||
height: fallbackBounds.height,
|
|
||||||
});
|
const fallbackBounds = args.resolveFallbackBounds();
|
||||||
|
args.updateVisibleOverlayBounds(fallbackBounds);
|
||||||
|
args.syncPrimaryOverlayWindowLayer('visible');
|
||||||
|
args.mainWindow.setIgnoreMouseEvents(false);
|
||||||
args.ensureOverlayWindowLevel(args.mainWindow);
|
args.ensureOverlayWindowLevel(args.mainWindow);
|
||||||
args.mainWindow.show();
|
args.mainWindow.show();
|
||||||
args.mainWindow.focus();
|
args.mainWindow.focus();
|
||||||
@@ -66,111 +89,11 @@ export function updateVisibleOverlayVisibility(args: {
|
|||||||
args.syncOverlayShortcuts();
|
args.syncOverlayShortcuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateInvisibleOverlayVisibility(args: {
|
|
||||||
invisibleWindow: BrowserWindow | null;
|
|
||||||
visibleOverlayVisible: boolean;
|
|
||||||
invisibleOverlayVisible: boolean;
|
|
||||||
windowTracker: BaseWindowTracker | null;
|
|
||||||
updateInvisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
|
||||||
ensureOverlayWindowLevel: (window: BrowserWindow) => void;
|
|
||||||
enforceOverlayLayerOrder: () => void;
|
|
||||||
syncOverlayShortcuts: () => void;
|
|
||||||
}): void {
|
|
||||||
if (!args.invisibleWindow || args.invisibleWindow.isDestroyed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.visibleOverlayVisible) {
|
|
||||||
args.invisibleWindow.hide();
|
|
||||||
args.syncOverlayShortcuts();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const showInvisibleWithoutFocus = (): void => {
|
|
||||||
args.ensureOverlayWindowLevel(args.invisibleWindow!);
|
|
||||||
if (typeof args.invisibleWindow!.showInactive === 'function') {
|
|
||||||
args.invisibleWindow!.showInactive();
|
|
||||||
} else {
|
|
||||||
args.invisibleWindow!.show();
|
|
||||||
}
|
|
||||||
args.enforceOverlayLayerOrder();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!args.invisibleOverlayVisible) {
|
|
||||||
args.invisibleWindow.hide();
|
|
||||||
args.syncOverlayShortcuts();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.windowTracker && args.windowTracker.isTracking()) {
|
|
||||||
const geometry = args.windowTracker.getGeometry();
|
|
||||||
if (geometry) {
|
|
||||||
args.updateInvisibleOverlayBounds(geometry);
|
|
||||||
}
|
|
||||||
showInvisibleWithoutFocus();
|
|
||||||
args.syncOverlayShortcuts();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args.windowTracker) {
|
|
||||||
showInvisibleWithoutFocus();
|
|
||||||
args.syncOverlayShortcuts();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cursorPoint = screen.getCursorScreenPoint();
|
|
||||||
const display = screen.getDisplayNearestPoint(cursorPoint);
|
|
||||||
const fallbackBounds = display.workArea;
|
|
||||||
args.updateInvisibleOverlayBounds({
|
|
||||||
x: fallbackBounds.x,
|
|
||||||
y: fallbackBounds.y,
|
|
||||||
width: fallbackBounds.width,
|
|
||||||
height: fallbackBounds.height,
|
|
||||||
});
|
|
||||||
showInvisibleWithoutFocus();
|
|
||||||
args.syncOverlayShortcuts();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function syncInvisibleOverlayMousePassthrough(options: {
|
|
||||||
hasInvisibleWindow: () => boolean;
|
|
||||||
setIgnoreMouseEvents: (ignore: boolean, extra?: { forward: boolean }) => void;
|
|
||||||
visibleOverlayVisible: boolean;
|
|
||||||
invisibleOverlayVisible: boolean;
|
|
||||||
}): void {
|
|
||||||
if (!options.hasInvisibleWindow()) return;
|
|
||||||
if (options.visibleOverlayVisible) {
|
|
||||||
options.setIgnoreMouseEvents(true, { forward: true });
|
|
||||||
} else if (options.invisibleOverlayVisible) {
|
|
||||||
options.setIgnoreMouseEvents(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setVisibleOverlayVisible(options: {
|
export function setVisibleOverlayVisible(options: {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
setVisibleOverlayVisibleState: (visible: boolean) => void;
|
setVisibleOverlayVisibleState: (visible: boolean) => void;
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => void;
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => boolean;
|
|
||||||
isMpvConnected: () => boolean;
|
|
||||||
setMpvSubVisibility: (visible: boolean) => void;
|
|
||||||
}): void {
|
}): void {
|
||||||
options.setVisibleOverlayVisibleState(options.visible);
|
options.setVisibleOverlayVisibleState(options.visible);
|
||||||
options.updateVisibleOverlayVisibility();
|
options.updateVisibleOverlayVisibility();
|
||||||
options.updateInvisibleOverlayVisibility();
|
|
||||||
options.syncInvisibleOverlayMousePassthrough();
|
|
||||||
if (options.shouldBindVisibleOverlayToMpvSubVisibility() && options.isMpvConnected()) {
|
|
||||||
options.setMpvSubVisibility(!options.visible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setInvisibleOverlayVisible(options: {
|
|
||||||
visible: boolean;
|
|
||||||
setInvisibleOverlayVisibleState: (visible: boolean) => void;
|
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => void;
|
|
||||||
}): void {
|
|
||||||
options.setInvisibleOverlayVisibleState(options.visible);
|
|
||||||
options.updateInvisibleOverlayVisibility();
|
|
||||||
options.syncInvisibleOverlayMousePassthrough();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import {
|
import {
|
||||||
getInitialInvisibleOverlayVisibility,
|
|
||||||
isAutoUpdateEnabledRuntime,
|
isAutoUpdateEnabledRuntime,
|
||||||
shouldAutoInitializeOverlayRuntimeFromConfig,
|
shouldAutoInitializeOverlayRuntimeFromConfig,
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility,
|
shouldBindVisibleOverlayToMpvSubVisibility,
|
||||||
@@ -10,9 +9,6 @@ import {
|
|||||||
const BASE_CONFIG = {
|
const BASE_CONFIG = {
|
||||||
auto_start_overlay: false,
|
auto_start_overlay: false,
|
||||||
bind_visible_overlay_to_mpv_sub_visibility: true,
|
bind_visible_overlay_to_mpv_sub_visibility: true,
|
||||||
invisibleOverlay: {
|
|
||||||
startupVisibility: 'platform-default' as const,
|
|
||||||
},
|
|
||||||
ankiConnect: {
|
ankiConnect: {
|
||||||
behavior: {
|
behavior: {
|
||||||
autoUpdateNewCards: true,
|
autoUpdateNewCards: true,
|
||||||
@@ -20,26 +16,7 @@ const BASE_CONFIG = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
test('getInitialInvisibleOverlayVisibility handles visibility + platform', () => {
|
test('shouldAutoInitializeOverlayRuntimeFromConfig respects auto start', () => {
|
||||||
assert.equal(
|
|
||||||
getInitialInvisibleOverlayVisibility(
|
|
||||||
{ ...BASE_CONFIG, invisibleOverlay: { startupVisibility: 'visible' } },
|
|
||||||
'linux',
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
getInitialInvisibleOverlayVisibility(
|
|
||||||
{ ...BASE_CONFIG, invisibleOverlay: { startupVisibility: 'hidden' } },
|
|
||||||
'darwin',
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
assert.equal(getInitialInvisibleOverlayVisibility(BASE_CONFIG, 'linux'), false);
|
|
||||||
assert.equal(getInitialInvisibleOverlayVisibility(BASE_CONFIG, 'darwin'), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('shouldAutoInitializeOverlayRuntimeFromConfig respects auto start and visible startup', () => {
|
|
||||||
assert.equal(shouldAutoInitializeOverlayRuntimeFromConfig(BASE_CONFIG), false);
|
assert.equal(shouldAutoInitializeOverlayRuntimeFromConfig(BASE_CONFIG), false);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
shouldAutoInitializeOverlayRuntimeFromConfig({
|
shouldAutoInitializeOverlayRuntimeFromConfig({
|
||||||
@@ -48,13 +25,6 @@ test('shouldAutoInitializeOverlayRuntimeFromConfig respects auto start and visib
|
|||||||
}),
|
}),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
assert.equal(
|
|
||||||
shouldAutoInitializeOverlayRuntimeFromConfig({
|
|
||||||
...BASE_CONFIG,
|
|
||||||
invisibleOverlay: { startupVisibility: 'visible' },
|
|
||||||
}),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('shouldBindVisibleOverlayToMpvSubVisibility returns config value', () => {
|
test('shouldBindVisibleOverlayToMpvSubVisibility returns config value', () => {
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ const logger = createLogger('main:shortcut');
|
|||||||
|
|
||||||
export interface GlobalShortcutConfig {
|
export interface GlobalShortcutConfig {
|
||||||
toggleVisibleOverlayGlobal: string | null | undefined;
|
toggleVisibleOverlayGlobal: string | null | undefined;
|
||||||
toggleInvisibleOverlayGlobal: string | null | undefined;
|
|
||||||
openJimaku?: string | null | undefined;
|
openJimaku?: string | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterGlobalShortcutsServiceOptions {
|
export interface RegisterGlobalShortcutsServiceOptions {
|
||||||
shortcuts: GlobalShortcutConfig;
|
shortcuts: GlobalShortcutConfig;
|
||||||
onToggleVisibleOverlay: () => void;
|
onToggleVisibleOverlay: () => void;
|
||||||
onToggleInvisibleOverlay: () => void;
|
|
||||||
onOpenYomitanSettings: () => void;
|
onOpenYomitanSettings: () => void;
|
||||||
onOpenJimaku?: () => void;
|
onOpenJimaku?: () => void;
|
||||||
isDev: boolean;
|
isDev: boolean;
|
||||||
@@ -21,9 +19,7 @@ export interface RegisterGlobalShortcutsServiceOptions {
|
|||||||
|
|
||||||
export function registerGlobalShortcuts(options: RegisterGlobalShortcutsServiceOptions): void {
|
export function registerGlobalShortcuts(options: RegisterGlobalShortcutsServiceOptions): void {
|
||||||
const visibleShortcut = options.shortcuts.toggleVisibleOverlayGlobal;
|
const visibleShortcut = options.shortcuts.toggleVisibleOverlayGlobal;
|
||||||
const invisibleShortcut = options.shortcuts.toggleInvisibleOverlayGlobal;
|
|
||||||
const normalizedVisible = visibleShortcut?.replace(/\s+/g, '').toLowerCase();
|
const normalizedVisible = visibleShortcut?.replace(/\s+/g, '').toLowerCase();
|
||||||
const normalizedInvisible = invisibleShortcut?.replace(/\s+/g, '').toLowerCase();
|
|
||||||
const normalizedJimaku = options.shortcuts.openJimaku?.replace(/\s+/g, '').toLowerCase();
|
const normalizedJimaku = options.shortcuts.openJimaku?.replace(/\s+/g, '').toLowerCase();
|
||||||
const normalizedSettings = 'alt+shift+y';
|
const normalizedSettings = 'alt+shift+y';
|
||||||
|
|
||||||
@@ -38,31 +34,10 @@ export function registerGlobalShortcuts(options: RegisterGlobalShortcutsServiceO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invisibleShortcut && normalizedInvisible && normalizedInvisible !== normalizedVisible) {
|
|
||||||
const toggleInvisibleRegistered = globalShortcut.register(invisibleShortcut, () => {
|
|
||||||
options.onToggleInvisibleOverlay();
|
|
||||||
});
|
|
||||||
if (!toggleInvisibleRegistered) {
|
|
||||||
logger.warn(
|
|
||||||
`Failed to register global shortcut toggleInvisibleOverlayGlobal: ${invisibleShortcut}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
invisibleShortcut &&
|
|
||||||
normalizedInvisible &&
|
|
||||||
normalizedInvisible === normalizedVisible
|
|
||||||
) {
|
|
||||||
logger.warn(
|
|
||||||
'Skipped registering toggleInvisibleOverlayGlobal because it collides with toggleVisibleOverlayGlobal',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.shortcuts.openJimaku && options.onOpenJimaku) {
|
if (options.shortcuts.openJimaku && options.onOpenJimaku) {
|
||||||
if (
|
if (
|
||||||
normalizedJimaku &&
|
normalizedJimaku &&
|
||||||
(normalizedJimaku === normalizedVisible ||
|
(normalizedJimaku === normalizedVisible || normalizedJimaku === normalizedSettings)
|
||||||
normalizedJimaku === normalizedInvisible ||
|
|
||||||
normalizedJimaku === normalizedSettings)
|
|
||||||
) {
|
) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'Skipped registering openJimaku because it collides with another global shortcut',
|
'Skipped registering openJimaku because it collides with another global shortcut',
|
||||||
|
|||||||
@@ -10,14 +10,11 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
|||||||
stop: false,
|
stop: false,
|
||||||
toggle: false,
|
toggle: false,
|
||||||
toggleVisibleOverlay: false,
|
toggleVisibleOverlay: false,
|
||||||
toggleInvisibleOverlay: false,
|
|
||||||
settings: false,
|
settings: false,
|
||||||
show: false,
|
show: false,
|
||||||
hide: false,
|
hide: false,
|
||||||
showVisibleOverlay: false,
|
showVisibleOverlay: false,
|
||||||
hideVisibleOverlay: false,
|
hideVisibleOverlay: false,
|
||||||
showInvisibleOverlay: false,
|
|
||||||
hideInvisibleOverlay: false,
|
|
||||||
copySubtitle: false,
|
copySubtitle: false,
|
||||||
copySubtitleMultiple: false,
|
copySubtitleMultiple: false,
|
||||||
mineSentence: false,
|
mineSentence: false,
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ interface RuntimeAutoUpdateOptionManagerLike {
|
|||||||
export interface RuntimeConfigLike {
|
export interface RuntimeConfigLike {
|
||||||
auto_start_overlay?: boolean;
|
auto_start_overlay?: boolean;
|
||||||
bind_visible_overlay_to_mpv_sub_visibility: boolean;
|
bind_visible_overlay_to_mpv_sub_visibility: boolean;
|
||||||
invisibleOverlay: {
|
|
||||||
startupVisibility: 'visible' | 'hidden' | 'platform-default';
|
|
||||||
};
|
|
||||||
ankiConnect?: {
|
ankiConnect?: {
|
||||||
behavior?: {
|
behavior?: {
|
||||||
autoUpdateNewCards?: boolean;
|
autoUpdateNewCards?: boolean;
|
||||||
@@ -155,21 +152,8 @@ function getStartupCriticalConfigErrors(config: AppReadyConfigLike): string[] {
|
|||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInitialInvisibleOverlayVisibility(
|
|
||||||
config: RuntimeConfigLike,
|
|
||||||
platform: NodeJS.Platform,
|
|
||||||
): boolean {
|
|
||||||
const visibility = config.invisibleOverlay.startupVisibility;
|
|
||||||
if (visibility === 'visible') return true;
|
|
||||||
if (visibility === 'hidden') return false;
|
|
||||||
if (platform === 'linux') return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldAutoInitializeOverlayRuntimeFromConfig(config: RuntimeConfigLike): boolean {
|
export function shouldAutoInitializeOverlayRuntimeFromConfig(config: RuntimeConfigLike): boolean {
|
||||||
if (config.auto_start_overlay === true) return true;
|
return config.auto_start_overlay === true;
|
||||||
if (config.invisibleOverlay.startupVisibility === 'visible') return true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldBindVisibleOverlayToMpvSubVisibility(config: RuntimeConfigLike): boolean {
|
export function shouldBindVisibleOverlayToMpvSubVisibility(config: RuntimeConfigLike): boolean {
|
||||||
|
|||||||
@@ -101,20 +101,7 @@ export function loadSubtitlePosition(
|
|||||||
const data = fs.readFileSync(positionPath, 'utf-8');
|
const data = fs.readFileSync(positionPath, 'utf-8');
|
||||||
const parsed = JSON.parse(data) as Partial<SubtitlePosition>;
|
const parsed = JSON.parse(data) as Partial<SubtitlePosition>;
|
||||||
if (parsed && typeof parsed.yPercent === 'number' && Number.isFinite(parsed.yPercent)) {
|
if (parsed && typeof parsed.yPercent === 'number' && Number.isFinite(parsed.yPercent)) {
|
||||||
const position: SubtitlePosition = { yPercent: parsed.yPercent };
|
return { yPercent: parsed.yPercent };
|
||||||
if (
|
|
||||||
typeof parsed.invisibleOffsetXPx === 'number' &&
|
|
||||||
Number.isFinite(parsed.invisibleOffsetXPx)
|
|
||||||
) {
|
|
||||||
position.invisibleOffsetXPx = parsed.invisibleOffsetXPx;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof parsed.invisibleOffsetYPx === 'number' &&
|
|
||||||
Number.isFinite(parsed.invisibleOffsetYPx)
|
|
||||||
) {
|
|
||||||
position.invisibleOffsetYPx = parsed.invisibleOffsetYPx;
|
|
||||||
}
|
|
||||||
return position;
|
|
||||||
}
|
}
|
||||||
return options.fallbackPosition;
|
return options.fallbackPosition;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Config } from '../../types';
|
|||||||
|
|
||||||
export interface ConfiguredShortcuts {
|
export interface ConfiguredShortcuts {
|
||||||
toggleVisibleOverlayGlobal: string | null | undefined;
|
toggleVisibleOverlayGlobal: string | null | undefined;
|
||||||
toggleInvisibleOverlayGlobal: string | null | undefined;
|
|
||||||
copySubtitle: string | null | undefined;
|
copySubtitle: string | null | undefined;
|
||||||
copySubtitleMultiple: string | null | undefined;
|
copySubtitleMultiple: string | null | undefined;
|
||||||
updateLastCardFromClipboard: string | null | undefined;
|
updateLastCardFromClipboard: string | null | undefined;
|
||||||
@@ -33,10 +32,6 @@ export function resolveConfiguredShortcuts(
|
|||||||
config.shortcuts?.toggleVisibleOverlayGlobal ??
|
config.shortcuts?.toggleVisibleOverlayGlobal ??
|
||||||
defaultConfig.shortcuts?.toggleVisibleOverlayGlobal,
|
defaultConfig.shortcuts?.toggleVisibleOverlayGlobal,
|
||||||
),
|
),
|
||||||
toggleInvisibleOverlayGlobal: normalizeShortcut(
|
|
||||||
config.shortcuts?.toggleInvisibleOverlayGlobal ??
|
|
||||||
defaultConfig.shortcuts?.toggleInvisibleOverlayGlobal,
|
|
||||||
),
|
|
||||||
copySubtitle: normalizeShortcut(
|
copySubtitle: normalizeShortcut(
|
||||||
config.shortcuts?.copySubtitle ?? defaultConfig.shortcuts?.copySubtitle,
|
config.shortcuts?.copySubtitle ?? defaultConfig.shortcuts?.copySubtitle,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ export interface CliCommandRuntimeServiceContext {
|
|||||||
isOverlayInitialized: () => boolean;
|
isOverlayInitialized: () => boolean;
|
||||||
initializeOverlay: () => void;
|
initializeOverlay: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
toggleInvisibleOverlay: () => void;
|
|
||||||
setVisibleOverlay: (visible: boolean) => void;
|
setVisibleOverlay: (visible: boolean) => void;
|
||||||
setInvisibleOverlay: (visible: boolean) => void;
|
|
||||||
copyCurrentSubtitle: () => void;
|
copyCurrentSubtitle: () => void;
|
||||||
startPendingMultiCopy: (timeoutMs: number) => void;
|
startPendingMultiCopy: (timeoutMs: number) => void;
|
||||||
mineSentenceCard: () => Promise<void>;
|
mineSentenceCard: () => Promise<void>;
|
||||||
@@ -74,9 +72,7 @@ function createCliCommandDepsFromContext(
|
|||||||
isInitialized: context.isOverlayInitialized,
|
isInitialized: context.isOverlayInitialized,
|
||||||
initialize: context.initializeOverlay,
|
initialize: context.initializeOverlay,
|
||||||
toggleVisible: context.toggleVisibleOverlay,
|
toggleVisible: context.toggleVisibleOverlay,
|
||||||
toggleInvisible: context.toggleInvisibleOverlay,
|
|
||||||
setVisible: context.setVisibleOverlay,
|
setVisible: context.setVisibleOverlay,
|
||||||
setInvisible: context.setInvisibleOverlay,
|
|
||||||
},
|
},
|
||||||
mining: {
|
mining: {
|
||||||
copyCurrentSubtitle: context.copyCurrentSubtitle,
|
copyCurrentSubtitle: context.copyCurrentSubtitle,
|
||||||
|
|||||||
@@ -53,11 +53,10 @@ export function createSubsyncRuntimeDeps(params: SubsyncRuntimeDepsParams): Subs
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MainIpcRuntimeServiceDepsParams {
|
export interface MainIpcRuntimeServiceDepsParams {
|
||||||
getInvisibleWindow: IpcDepsRuntimeOptions['getInvisibleWindow'];
|
|
||||||
getMainWindow: IpcDepsRuntimeOptions['getMainWindow'];
|
getMainWindow: IpcDepsRuntimeOptions['getMainWindow'];
|
||||||
getVisibleOverlayVisibility: IpcDepsRuntimeOptions['getVisibleOverlayVisibility'];
|
getVisibleOverlayVisibility: IpcDepsRuntimeOptions['getVisibleOverlayVisibility'];
|
||||||
getInvisibleOverlayVisibility: IpcDepsRuntimeOptions['getInvisibleOverlayVisibility'];
|
|
||||||
onOverlayModalClosed: IpcDepsRuntimeOptions['onOverlayModalClosed'];
|
onOverlayModalClosed: IpcDepsRuntimeOptions['onOverlayModalClosed'];
|
||||||
|
onOverlayModalOpened?: IpcDepsRuntimeOptions['onOverlayModalOpened'];
|
||||||
openYomitanSettings: IpcDepsRuntimeOptions['openYomitanSettings'];
|
openYomitanSettings: IpcDepsRuntimeOptions['openYomitanSettings'];
|
||||||
quitApp: IpcDepsRuntimeOptions['quitApp'];
|
quitApp: IpcDepsRuntimeOptions['quitApp'];
|
||||||
toggleVisibleOverlay: IpcDepsRuntimeOptions['toggleVisibleOverlay'];
|
toggleVisibleOverlay: IpcDepsRuntimeOptions['toggleVisibleOverlay'];
|
||||||
@@ -65,7 +64,6 @@ export interface MainIpcRuntimeServiceDepsParams {
|
|||||||
getCurrentSubtitleRaw: IpcDepsRuntimeOptions['getCurrentSubtitleRaw'];
|
getCurrentSubtitleRaw: IpcDepsRuntimeOptions['getCurrentSubtitleRaw'];
|
||||||
getCurrentSubtitleAss: IpcDepsRuntimeOptions['getCurrentSubtitleAss'];
|
getCurrentSubtitleAss: IpcDepsRuntimeOptions['getCurrentSubtitleAss'];
|
||||||
focusMainWindow?: IpcDepsRuntimeOptions['focusMainWindow'];
|
focusMainWindow?: IpcDepsRuntimeOptions['focusMainWindow'];
|
||||||
getMpvSubtitleRenderMetrics: IpcDepsRuntimeOptions['getMpvSubtitleRenderMetrics'];
|
|
||||||
getSubtitlePosition: IpcDepsRuntimeOptions['getSubtitlePosition'];
|
getSubtitlePosition: IpcDepsRuntimeOptions['getSubtitlePosition'];
|
||||||
getSubtitleStyle: IpcDepsRuntimeOptions['getSubtitleStyle'];
|
getSubtitleStyle: IpcDepsRuntimeOptions['getSubtitleStyle'];
|
||||||
saveSubtitlePosition: IpcDepsRuntimeOptions['saveSubtitlePosition'];
|
saveSubtitlePosition: IpcDepsRuntimeOptions['saveSubtitlePosition'];
|
||||||
@@ -81,7 +79,6 @@ export interface MainIpcRuntimeServiceDepsParams {
|
|||||||
setRuntimeOption: IpcDepsRuntimeOptions['setRuntimeOption'];
|
setRuntimeOption: IpcDepsRuntimeOptions['setRuntimeOption'];
|
||||||
cycleRuntimeOption: IpcDepsRuntimeOptions['cycleRuntimeOption'];
|
cycleRuntimeOption: IpcDepsRuntimeOptions['cycleRuntimeOption'];
|
||||||
reportOverlayContentBounds: IpcDepsRuntimeOptions['reportOverlayContentBounds'];
|
reportOverlayContentBounds: IpcDepsRuntimeOptions['reportOverlayContentBounds'];
|
||||||
reportHoveredSubtitleToken: IpcDepsRuntimeOptions['reportHoveredSubtitleToken'];
|
|
||||||
getAnilistStatus: IpcDepsRuntimeOptions['getAnilistStatus'];
|
getAnilistStatus: IpcDepsRuntimeOptions['getAnilistStatus'];
|
||||||
clearAnilistToken: IpcDepsRuntimeOptions['clearAnilistToken'];
|
clearAnilistToken: IpcDepsRuntimeOptions['clearAnilistToken'];
|
||||||
openAnilistSetup: IpcDepsRuntimeOptions['openAnilistSetup'];
|
openAnilistSetup: IpcDepsRuntimeOptions['openAnilistSetup'];
|
||||||
@@ -132,9 +129,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'];
|
||||||
toggleInvisible: CliCommandDepsRuntimeOptions['overlay']['toggleInvisible'];
|
|
||||||
setVisible: CliCommandDepsRuntimeOptions['overlay']['setVisible'];
|
setVisible: CliCommandDepsRuntimeOptions['overlay']['setVisible'];
|
||||||
setInvisible: CliCommandDepsRuntimeOptions['overlay']['setInvisible'];
|
|
||||||
};
|
};
|
||||||
mining: {
|
mining: {
|
||||||
copyCurrentSubtitle: CliCommandDepsRuntimeOptions['mining']['copyCurrentSubtitle'];
|
copyCurrentSubtitle: CliCommandDepsRuntimeOptions['mining']['copyCurrentSubtitle'];
|
||||||
@@ -192,18 +187,16 @@ export function createMainIpcRuntimeServiceDeps(
|
|||||||
params: MainIpcRuntimeServiceDepsParams,
|
params: MainIpcRuntimeServiceDepsParams,
|
||||||
): IpcDepsRuntimeOptions {
|
): IpcDepsRuntimeOptions {
|
||||||
return {
|
return {
|
||||||
getInvisibleWindow: params.getInvisibleWindow,
|
|
||||||
getMainWindow: params.getMainWindow,
|
getMainWindow: params.getMainWindow,
|
||||||
getVisibleOverlayVisibility: params.getVisibleOverlayVisibility,
|
getVisibleOverlayVisibility: params.getVisibleOverlayVisibility,
|
||||||
getInvisibleOverlayVisibility: params.getInvisibleOverlayVisibility,
|
|
||||||
onOverlayModalClosed: params.onOverlayModalClosed,
|
onOverlayModalClosed: params.onOverlayModalClosed,
|
||||||
|
onOverlayModalOpened: params.onOverlayModalOpened,
|
||||||
openYomitanSettings: params.openYomitanSettings,
|
openYomitanSettings: params.openYomitanSettings,
|
||||||
quitApp: params.quitApp,
|
quitApp: params.quitApp,
|
||||||
toggleVisibleOverlay: params.toggleVisibleOverlay,
|
toggleVisibleOverlay: params.toggleVisibleOverlay,
|
||||||
tokenizeCurrentSubtitle: params.tokenizeCurrentSubtitle,
|
tokenizeCurrentSubtitle: params.tokenizeCurrentSubtitle,
|
||||||
getCurrentSubtitleRaw: params.getCurrentSubtitleRaw,
|
getCurrentSubtitleRaw: params.getCurrentSubtitleRaw,
|
||||||
getCurrentSubtitleAss: params.getCurrentSubtitleAss,
|
getCurrentSubtitleAss: params.getCurrentSubtitleAss,
|
||||||
getMpvSubtitleRenderMetrics: params.getMpvSubtitleRenderMetrics,
|
|
||||||
getSubtitlePosition: params.getSubtitlePosition,
|
getSubtitlePosition: params.getSubtitlePosition,
|
||||||
getSubtitleStyle: params.getSubtitleStyle,
|
getSubtitleStyle: params.getSubtitleStyle,
|
||||||
saveSubtitlePosition: params.saveSubtitlePosition,
|
saveSubtitlePosition: params.saveSubtitlePosition,
|
||||||
@@ -220,7 +213,6 @@ export function createMainIpcRuntimeServiceDeps(
|
|||||||
setRuntimeOption: params.setRuntimeOption,
|
setRuntimeOption: params.setRuntimeOption,
|
||||||
cycleRuntimeOption: params.cycleRuntimeOption,
|
cycleRuntimeOption: params.cycleRuntimeOption,
|
||||||
reportOverlayContentBounds: params.reportOverlayContentBounds,
|
reportOverlayContentBounds: params.reportOverlayContentBounds,
|
||||||
reportHoveredSubtitleToken: params.reportHoveredSubtitleToken,
|
|
||||||
getAnilistStatus: params.getAnilistStatus,
|
getAnilistStatus: params.getAnilistStatus,
|
||||||
clearAnilistToken: params.clearAnilistToken,
|
clearAnilistToken: params.clearAnilistToken,
|
||||||
openAnilistSetup: params.openAnilistSetup,
|
openAnilistSetup: params.openAnilistSetup,
|
||||||
@@ -279,9 +271,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,
|
||||||
toggleInvisible: params.overlay.toggleInvisible,
|
|
||||||
setVisible: params.overlay.setVisible,
|
setVisible: params.overlay.setVisible,
|
||||||
setInvisible: params.overlay.setInvisible,
|
|
||||||
},
|
},
|
||||||
mining: {
|
mining: {
|
||||||
copyCurrentSubtitle: params.mining.copyCurrentSubtitle,
|
copyCurrentSubtitle: params.mining.copyCurrentSubtitle,
|
||||||
|
|||||||
@@ -2,50 +2,31 @@ import type { BrowserWindow } from 'electron';
|
|||||||
|
|
||||||
import type { BaseWindowTracker } from '../window-trackers';
|
import type { BaseWindowTracker } from '../window-trackers';
|
||||||
import type { WindowGeometry } from '../types';
|
import type { WindowGeometry } from '../types';
|
||||||
import {
|
import { updateVisibleOverlayVisibility } from '../core/services';
|
||||||
syncInvisibleOverlayMousePassthrough,
|
|
||||||
updateInvisibleOverlayVisibility,
|
|
||||||
updateVisibleOverlayVisibility,
|
|
||||||
} from '../core/services';
|
|
||||||
|
|
||||||
export interface OverlayVisibilityRuntimeDeps {
|
export interface OverlayVisibilityRuntimeDeps {
|
||||||
getMainWindow: () => BrowserWindow | null;
|
getMainWindow: () => BrowserWindow | null;
|
||||||
getInvisibleWindow: () => BrowserWindow | null;
|
|
||||||
getVisibleOverlayVisible: () => boolean;
|
getVisibleOverlayVisible: () => boolean;
|
||||||
getInvisibleOverlayVisible: () => boolean;
|
|
||||||
getWindowTracker: () => BaseWindowTracker | null;
|
getWindowTracker: () => BaseWindowTracker | null;
|
||||||
getTrackerNotReadyWarningShown: () => boolean;
|
getTrackerNotReadyWarningShown: () => boolean;
|
||||||
setTrackerNotReadyWarningShown: (shown: boolean) => void;
|
setTrackerNotReadyWarningShown: (shown: boolean) => void;
|
||||||
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
||||||
updateInvisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
|
||||||
ensureOverlayWindowLevel: (window: BrowserWindow) => void;
|
ensureOverlayWindowLevel: (window: BrowserWindow) => void;
|
||||||
|
syncPrimaryOverlayWindowLayer: (layer: 'visible') => void;
|
||||||
enforceOverlayLayerOrder: () => void;
|
enforceOverlayLayerOrder: () => void;
|
||||||
syncOverlayShortcuts: () => void;
|
syncOverlayShortcuts: () => void;
|
||||||
|
isMacOSPlatform: () => boolean;
|
||||||
|
showOverlayLoadingOsd: (message: string) => void;
|
||||||
|
resolveFallbackBounds: () => WindowGeometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OverlayVisibilityRuntimeService {
|
export interface OverlayVisibilityRuntimeService {
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createOverlayVisibilityRuntimeService(
|
export function createOverlayVisibilityRuntimeService(
|
||||||
deps: OverlayVisibilityRuntimeDeps,
|
deps: OverlayVisibilityRuntimeDeps,
|
||||||
): OverlayVisibilityRuntimeService {
|
): OverlayVisibilityRuntimeService {
|
||||||
const hasInvisibleWindow = (): boolean => {
|
|
||||||
const invisibleWindow = deps.getInvisibleWindow();
|
|
||||||
return Boolean(invisibleWindow && !invisibleWindow.isDestroyed());
|
|
||||||
};
|
|
||||||
|
|
||||||
const setIgnoreMouseEvents = (
|
|
||||||
ignore: boolean,
|
|
||||||
options?: Parameters<BrowserWindow['setIgnoreMouseEvents']>[1],
|
|
||||||
): void => {
|
|
||||||
const invisibleWindow = deps.getInvisibleWindow();
|
|
||||||
if (!invisibleWindow || invisibleWindow.isDestroyed()) return;
|
|
||||||
invisibleWindow.setIgnoreMouseEvents(ignore, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateVisibleOverlayVisibility(): void {
|
updateVisibleOverlayVisibility(): void {
|
||||||
updateVisibleOverlayVisibility({
|
updateVisibleOverlayVisibility({
|
||||||
@@ -59,31 +40,13 @@ export function createOverlayVisibilityRuntimeService(
|
|||||||
updateVisibleOverlayBounds: (geometry: WindowGeometry) =>
|
updateVisibleOverlayBounds: (geometry: WindowGeometry) =>
|
||||||
deps.updateVisibleOverlayBounds(geometry),
|
deps.updateVisibleOverlayBounds(geometry),
|
||||||
ensureOverlayWindowLevel: (window: BrowserWindow) => deps.ensureOverlayWindowLevel(window),
|
ensureOverlayWindowLevel: (window: BrowserWindow) => deps.ensureOverlayWindowLevel(window),
|
||||||
|
syncPrimaryOverlayWindowLayer: (layer: 'visible') =>
|
||||||
|
deps.syncPrimaryOverlayWindowLayer(layer),
|
||||||
enforceOverlayLayerOrder: () => deps.enforceOverlayLayerOrder(),
|
enforceOverlayLayerOrder: () => deps.enforceOverlayLayerOrder(),
|
||||||
syncOverlayShortcuts: () => deps.syncOverlayShortcuts(),
|
syncOverlayShortcuts: () => deps.syncOverlayShortcuts(),
|
||||||
});
|
isMacOSPlatform: deps.isMacOSPlatform(),
|
||||||
},
|
showOverlayLoadingOsd: (message: string) => deps.showOverlayLoadingOsd(message),
|
||||||
|
resolveFallbackBounds: () => deps.resolveFallbackBounds(),
|
||||||
updateInvisibleOverlayVisibility(): void {
|
|
||||||
updateInvisibleOverlayVisibility({
|
|
||||||
invisibleWindow: deps.getInvisibleWindow(),
|
|
||||||
visibleOverlayVisible: deps.getVisibleOverlayVisible(),
|
|
||||||
invisibleOverlayVisible: deps.getInvisibleOverlayVisible(),
|
|
||||||
windowTracker: deps.getWindowTracker(),
|
|
||||||
updateInvisibleOverlayBounds: (geometry: WindowGeometry) =>
|
|
||||||
deps.updateInvisibleOverlayBounds(geometry),
|
|
||||||
ensureOverlayWindowLevel: (window: BrowserWindow) => deps.ensureOverlayWindowLevel(window),
|
|
||||||
enforceOverlayLayerOrder: () => deps.enforceOverlayLayerOrder(),
|
|
||||||
syncOverlayShortcuts: () => deps.syncOverlayShortcuts(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
syncInvisibleOverlayMousePassthrough(): void {
|
|
||||||
syncInvisibleOverlayMousePassthrough({
|
|
||||||
hasInvisibleWindow,
|
|
||||||
setIgnoreMouseEvents,
|
|
||||||
visibleOverlayVisible: deps.getVisibleOverlayVisible(),
|
|
||||||
invisibleOverlayVisible: deps.getInvisibleOverlayVisible(),
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,14 +19,12 @@ test('restore windows on activate deps builder maps all restoration callbacks',
|
|||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
const deps = createBuildRestoreWindowsOnActivateMainDepsHandler({
|
const deps = createBuildRestoreWindowsOnActivateMainDepsHandler({
|
||||||
createMainWindow: () => calls.push('main'),
|
createMainWindow: () => calls.push('main'),
|
||||||
createInvisibleWindow: () => calls.push('invisible'),
|
|
||||||
updateVisibleOverlayVisibility: () => calls.push('visible'),
|
updateVisibleOverlayVisibility: () => calls.push('visible'),
|
||||||
updateInvisibleOverlayVisibility: () => calls.push('invisible-visible'),
|
syncOverlayMpvSubtitleSuppression: () => calls.push('mpv-sync'),
|
||||||
})();
|
})();
|
||||||
|
|
||||||
deps.createMainWindow();
|
deps.createMainWindow();
|
||||||
deps.createInvisibleWindow();
|
|
||||||
deps.updateVisibleOverlayVisibility();
|
deps.updateVisibleOverlayVisibility();
|
||||||
deps.updateInvisibleOverlayVisibility();
|
deps.syncOverlayMpvSubtitleSuppression();
|
||||||
assert.deepEqual(calls, ['main', 'invisible', 'visible', 'invisible-visible']);
|
assert.deepEqual(calls, ['main', 'visible', 'mpv-sync']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,14 +10,12 @@ export function createBuildShouldRestoreWindowsOnActivateMainDepsHandler(deps: {
|
|||||||
|
|
||||||
export function createBuildRestoreWindowsOnActivateMainDepsHandler(deps: {
|
export function createBuildRestoreWindowsOnActivateMainDepsHandler(deps: {
|
||||||
createMainWindow: () => void;
|
createMainWindow: () => void;
|
||||||
createInvisibleWindow: () => void;
|
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
syncOverlayMpvSubtitleSuppression: () => void;
|
||||||
}) {
|
}) {
|
||||||
return () => ({
|
return () => ({
|
||||||
createMainWindow: () => deps.createMainWindow(),
|
createMainWindow: () => deps.createMainWindow(),
|
||||||
createInvisibleWindow: () => deps.createInvisibleWindow(),
|
|
||||||
updateVisibleOverlayVisibility: () => deps.updateVisibleOverlayVisibility(),
|
updateVisibleOverlayVisibility: () => deps.updateVisibleOverlayVisibility(),
|
||||||
updateInvisibleOverlayVisibility: () => deps.updateInvisibleOverlayVisibility(),
|
syncOverlayMpvSubtitleSuppression: () => deps.syncOverlayMpvSubtitleSuppression(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,26 +50,18 @@ test('initialize overlay runtime main deps map build options and callbacks', ()
|
|||||||
isOverlayRuntimeInitialized: () => false,
|
isOverlayRuntimeInitialized: () => false,
|
||||||
initializeOverlayRuntimeCore: (value) => {
|
initializeOverlayRuntimeCore: (value) => {
|
||||||
calls.push(`core:${JSON.stringify(value)}`);
|
calls.push(`core:${JSON.stringify(value)}`);
|
||||||
return { invisibleOverlayVisible: true };
|
|
||||||
},
|
},
|
||||||
buildOptions: () => options,
|
buildOptions: () => options,
|
||||||
setInvisibleOverlayVisible: (visible) => calls.push(`set-invisible:${visible}`),
|
|
||||||
setOverlayRuntimeInitialized: (initialized) => calls.push(`set-initialized:${initialized}`),
|
setOverlayRuntimeInitialized: (initialized) => calls.push(`set-initialized:${initialized}`),
|
||||||
startBackgroundWarmups: () => calls.push('warmups'),
|
startBackgroundWarmups: () => calls.push('warmups'),
|
||||||
})();
|
})();
|
||||||
|
|
||||||
assert.equal(deps.isOverlayRuntimeInitialized(), false);
|
assert.equal(deps.isOverlayRuntimeInitialized(), false);
|
||||||
assert.equal(deps.buildOptions(), options);
|
assert.equal(deps.buildOptions(), options);
|
||||||
assert.deepEqual(deps.initializeOverlayRuntimeCore(options), { invisibleOverlayVisible: true });
|
assert.equal(deps.initializeOverlayRuntimeCore(options), undefined);
|
||||||
deps.setInvisibleOverlayVisible(true);
|
|
||||||
deps.setOverlayRuntimeInitialized(true);
|
deps.setOverlayRuntimeInitialized(true);
|
||||||
deps.startBackgroundWarmups();
|
deps.startBackgroundWarmups();
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, ['core:{"id":"opts"}', 'set-initialized:true', 'warmups']);
|
||||||
'core:{"id":"opts"}',
|
|
||||||
'set-invisible:true',
|
|
||||||
'set-initialized:true',
|
|
||||||
'warmups',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('open yomitan settings main deps map async open callbacks', async () => {
|
test('open yomitan settings main deps map async open callbacks', async () => {
|
||||||
|
|||||||
@@ -45,9 +45,8 @@ export function createBuildDestroyTrayMainDepsHandler<TTray>(deps: {
|
|||||||
|
|
||||||
export function createBuildInitializeOverlayRuntimeBootstrapMainDepsHandler<TOptions>(deps: {
|
export function createBuildInitializeOverlayRuntimeBootstrapMainDepsHandler<TOptions>(deps: {
|
||||||
isOverlayRuntimeInitialized: () => boolean;
|
isOverlayRuntimeInitialized: () => boolean;
|
||||||
initializeOverlayRuntimeCore: (options: TOptions) => { invisibleOverlayVisible: boolean };
|
initializeOverlayRuntimeCore: (options: TOptions) => void;
|
||||||
buildOptions: () => TOptions;
|
buildOptions: () => TOptions;
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => void;
|
|
||||||
setOverlayRuntimeInitialized: (initialized: boolean) => void;
|
setOverlayRuntimeInitialized: (initialized: boolean) => void;
|
||||||
startBackgroundWarmups: () => void;
|
startBackgroundWarmups: () => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -55,7 +54,6 @@ export function createBuildInitializeOverlayRuntimeBootstrapMainDepsHandler<TOpt
|
|||||||
isOverlayRuntimeInitialized: () => deps.isOverlayRuntimeInitialized(),
|
isOverlayRuntimeInitialized: () => deps.isOverlayRuntimeInitialized(),
|
||||||
initializeOverlayRuntimeCore: (options: TOptions) => deps.initializeOverlayRuntimeCore(options),
|
initializeOverlayRuntimeCore: (options: TOptions) => deps.initializeOverlayRuntimeCore(options),
|
||||||
buildOptions: () => deps.buildOptions(),
|
buildOptions: () => deps.buildOptions(),
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => deps.setInvisibleOverlayVisible(visible),
|
|
||||||
setOverlayRuntimeInitialized: (initialized: boolean) =>
|
setOverlayRuntimeInitialized: (initialized: boolean) =>
|
||||||
deps.setOverlayRuntimeInitialized(initialized),
|
deps.setOverlayRuntimeInitialized(initialized),
|
||||||
startBackgroundWarmups: () => deps.startBackgroundWarmups(),
|
startBackgroundWarmups: () => deps.startBackgroundWarmups(),
|
||||||
|
|||||||
@@ -18,9 +18,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'),
|
||||||
toggleInvisibleOverlay: () => calls.push('toggle-invisible'),
|
|
||||||
setVisibleOverlay: (visible) => calls.push(`set-visible:${visible}`),
|
setVisibleOverlay: (visible) => calls.push(`set-visible:${visible}`),
|
||||||
setInvisibleOverlay: (visible) => calls.push(`set-invisible:${visible}`),
|
|
||||||
copyCurrentSubtitle: () => calls.push('copy'),
|
copyCurrentSubtitle: () => calls.push('copy'),
|
||||||
startPendingMultiCopy: (ms) => calls.push(`multi:${ms}`),
|
startPendingMultiCopy: (ms) => calls.push(`multi:${ms}`),
|
||||||
mineSentenceCard: async () => {
|
mineSentenceCard: async () => {
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ export function createBuildCliCommandContextDepsHandler(deps: {
|
|||||||
isOverlayInitialized: () => boolean;
|
isOverlayInitialized: () => boolean;
|
||||||
initializeOverlay: () => void;
|
initializeOverlay: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
toggleInvisibleOverlay: () => void;
|
|
||||||
setVisibleOverlay: (visible: boolean) => void;
|
setVisibleOverlay: (visible: boolean) => void;
|
||||||
setInvisibleOverlay: (visible: boolean) => void;
|
|
||||||
copyCurrentSubtitle: () => void;
|
copyCurrentSubtitle: () => void;
|
||||||
startPendingMultiCopy: (timeoutMs: number) => void;
|
startPendingMultiCopy: (timeoutMs: number) => void;
|
||||||
mineSentenceCard: () => Promise<void>;
|
mineSentenceCard: () => Promise<void>;
|
||||||
@@ -60,9 +58,7 @@ export function createBuildCliCommandContextDepsHandler(deps: {
|
|||||||
isOverlayInitialized: deps.isOverlayInitialized,
|
isOverlayInitialized: deps.isOverlayInitialized,
|
||||||
initializeOverlay: deps.initializeOverlay,
|
initializeOverlay: deps.initializeOverlay,
|
||||||
toggleVisibleOverlay: deps.toggleVisibleOverlay,
|
toggleVisibleOverlay: deps.toggleVisibleOverlay,
|
||||||
toggleInvisibleOverlay: deps.toggleInvisibleOverlay,
|
|
||||||
setVisibleOverlay: deps.setVisibleOverlay,
|
setVisibleOverlay: deps.setVisibleOverlay,
|
||||||
setInvisibleOverlay: deps.setInvisibleOverlay,
|
|
||||||
copyCurrentSubtitle: deps.copyCurrentSubtitle,
|
copyCurrentSubtitle: deps.copyCurrentSubtitle,
|
||||||
startPendingMultiCopy: deps.startPendingMultiCopy,
|
startPendingMultiCopy: deps.startPendingMultiCopy,
|
||||||
mineSentenceCard: deps.mineSentenceCard,
|
mineSentenceCard: deps.mineSentenceCard,
|
||||||
|
|||||||
@@ -20,9 +20,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'),
|
||||||
toggleInvisibleOverlay: () => calls.push('toggle-invisible'),
|
|
||||||
setVisibleOverlayVisible: (visible) => calls.push(`set-visible:${visible}`),
|
setVisibleOverlayVisible: (visible) => calls.push(`set-visible:${visible}`),
|
||||||
setInvisibleOverlayVisible: (visible) => calls.push(`set-invisible:${visible}`),
|
|
||||||
copyCurrentSubtitle: () => calls.push('copy-sub'),
|
copyCurrentSubtitle: () => calls.push('copy-sub'),
|
||||||
startPendingMultiCopy: (timeoutMs) => calls.push(`multi:${timeoutMs}`),
|
startPendingMultiCopy: (timeoutMs) => calls.push(`multi:${timeoutMs}`),
|
||||||
mineSentenceCard: async () => {},
|
mineSentenceCard: async () => {},
|
||||||
@@ -73,16 +71,8 @@ test('cli command context factory composes main deps and context handlers', () =
|
|||||||
context.setSocketPath('/tmp/new.sock');
|
context.setSocketPath('/tmp/new.sock');
|
||||||
context.showOsd('hello');
|
context.showOsd('hello');
|
||||||
context.setVisibleOverlay(true);
|
context.setVisibleOverlay(true);
|
||||||
context.setInvisibleOverlay(false);
|
|
||||||
context.toggleVisibleOverlay();
|
context.toggleVisibleOverlay();
|
||||||
context.toggleInvisibleOverlay();
|
|
||||||
|
|
||||||
assert.equal(appState.mpvSocketPath, '/tmp/new.sock');
|
assert.equal(appState.mpvSocketPath, '/tmp/new.sock');
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, ['osd:hello', 'set-visible:true', 'toggle-visible']);
|
||||||
'osd:hello',
|
|
||||||
'set-visible:true',
|
|
||||||
'set-invisible:false',
|
|
||||||
'toggle-visible',
|
|
||||||
'toggle-invisible',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,9 +23,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'),
|
||||||
toggleInvisibleOverlay: () => calls.push('toggle-invisible'),
|
|
||||||
setVisibleOverlayVisible: (visible) => calls.push(`set-visible:${visible}`),
|
setVisibleOverlayVisible: (visible) => calls.push(`set-visible:${visible}`),
|
||||||
setInvisibleOverlayVisible: (visible) => calls.push(`set-invisible:${visible}`),
|
|
||||||
|
|
||||||
copyCurrentSubtitle: () => calls.push('copy-sub'),
|
copyCurrentSubtitle: () => calls.push('copy-sub'),
|
||||||
startPendingMultiCopy: (timeoutMs) => calls.push(`multi:${timeoutMs}`),
|
startPendingMultiCopy: (timeoutMs) => calls.push(`multi:${timeoutMs}`),
|
||||||
@@ -103,16 +101,9 @@ test('cli command context main deps builder maps state and callbacks', async ()
|
|||||||
deps.showOsd('hello');
|
deps.showOsd('hello');
|
||||||
deps.initializeOverlay();
|
deps.initializeOverlay();
|
||||||
deps.setVisibleOverlay(true);
|
deps.setVisibleOverlay(true);
|
||||||
deps.setInvisibleOverlay(false);
|
|
||||||
deps.printHelp();
|
deps.printHelp();
|
||||||
|
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, ['osd:hello', 'init-overlay', 'set-visible:true', 'help']);
|
||||||
'osd:hello',
|
|
||||||
'init-overlay',
|
|
||||||
'set-visible:true',
|
|
||||||
'set-invisible:false',
|
|
||||||
'help',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const retry = await deps.retryAnilistQueueNow();
|
const retry = await deps.retryAnilistQueueNow();
|
||||||
assert.deepEqual(retry, { ok: true, message: 'ok' });
|
assert.deepEqual(retry, { ok: true, message: 'ok' });
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ export function createBuildCliCommandContextMainDepsHandler(deps: {
|
|||||||
|
|
||||||
initializeOverlayRuntime: () => void;
|
initializeOverlayRuntime: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
toggleInvisibleOverlay: () => void;
|
|
||||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => void;
|
|
||||||
|
|
||||||
copyCurrentSubtitle: () => void;
|
copyCurrentSubtitle: () => void;
|
||||||
startPendingMultiCopy: (timeoutMs: number) => void;
|
startPendingMultiCopy: (timeoutMs: number) => void;
|
||||||
@@ -70,9 +68,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(),
|
||||||
toggleInvisibleOverlay: () => deps.toggleInvisibleOverlay(),
|
|
||||||
setVisibleOverlay: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
setVisibleOverlay: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
||||||
setInvisibleOverlay: (visible: boolean) => deps.setInvisibleOverlayVisible(visible),
|
|
||||||
copyCurrentSubtitle: () => deps.copyCurrentSubtitle(),
|
copyCurrentSubtitle: () => deps.copyCurrentSubtitle(),
|
||||||
startPendingMultiCopy: (timeoutMs: number) => deps.startPendingMultiCopy(timeoutMs),
|
startPendingMultiCopy: (timeoutMs: number) => deps.startPendingMultiCopy(timeoutMs),
|
||||||
mineSentenceCard: () => deps.mineSentenceCard(),
|
mineSentenceCard: () => deps.mineSentenceCard(),
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ function createDeps() {
|
|||||||
isOverlayInitialized: () => true,
|
isOverlayInitialized: () => true,
|
||||||
initializeOverlay: () => {},
|
initializeOverlay: () => {},
|
||||||
toggleVisibleOverlay: () => {},
|
toggleVisibleOverlay: () => {},
|
||||||
toggleInvisibleOverlay: () => {},
|
|
||||||
setVisibleOverlay: () => {},
|
setVisibleOverlay: () => {},
|
||||||
setInvisibleOverlay: () => {},
|
|
||||||
copyCurrentSubtitle: () => {},
|
copyCurrentSubtitle: () => {},
|
||||||
startPendingMultiCopy: () => {},
|
startPendingMultiCopy: () => {},
|
||||||
mineSentenceCard: async () => {},
|
mineSentenceCard: async () => {},
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ export type CliCommandContextFactoryDeps = {
|
|||||||
isOverlayInitialized: () => boolean;
|
isOverlayInitialized: () => boolean;
|
||||||
initializeOverlay: () => void;
|
initializeOverlay: () => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
toggleInvisibleOverlay: () => void;
|
|
||||||
setVisibleOverlay: (visible: boolean) => void;
|
setVisibleOverlay: (visible: boolean) => void;
|
||||||
setInvisibleOverlay: (visible: boolean) => void;
|
|
||||||
copyCurrentSubtitle: () => void;
|
copyCurrentSubtitle: () => void;
|
||||||
startPendingMultiCopy: (timeoutMs: number) => void;
|
startPendingMultiCopy: (timeoutMs: number) => void;
|
||||||
mineSentenceCard: () => Promise<void>;
|
mineSentenceCard: () => Promise<void>;
|
||||||
@@ -72,9 +70,7 @@ export function createCliCommandContext(
|
|||||||
isOverlayInitialized: deps.isOverlayInitialized,
|
isOverlayInitialized: deps.isOverlayInitialized,
|
||||||
initializeOverlay: deps.initializeOverlay,
|
initializeOverlay: deps.initializeOverlay,
|
||||||
toggleVisibleOverlay: deps.toggleVisibleOverlay,
|
toggleVisibleOverlay: deps.toggleVisibleOverlay,
|
||||||
toggleInvisibleOverlay: deps.toggleInvisibleOverlay,
|
|
||||||
setVisibleOverlay: deps.setVisibleOverlay,
|
setVisibleOverlay: deps.setVisibleOverlay,
|
||||||
setInvisibleOverlay: deps.setInvisibleOverlay,
|
|
||||||
copyCurrentSubtitle: deps.copyCurrentSubtitle,
|
copyCurrentSubtitle: deps.copyCurrentSubtitle,
|
||||||
startPendingMultiCopy: deps.startPendingMultiCopy,
|
startPendingMultiCopy: deps.startPendingMultiCopy,
|
||||||
mineSentenceCard: deps.mineSentenceCard,
|
mineSentenceCard: deps.mineSentenceCard,
|
||||||
|
|||||||
@@ -32,10 +32,8 @@ test('composeIpcRuntimeHandlers returns callable IPC handlers and registration b
|
|||||||
showMpvOsd: () => {},
|
showMpvOsd: () => {},
|
||||||
},
|
},
|
||||||
mainDeps: {
|
mainDeps: {
|
||||||
getInvisibleWindow: () => null,
|
|
||||||
getMainWindow: () => null,
|
getMainWindow: () => null,
|
||||||
getVisibleOverlayVisibility: () => false,
|
getVisibleOverlayVisibility: () => false,
|
||||||
getInvisibleOverlayVisibility: () => false,
|
|
||||||
focusMainWindow: () => {},
|
focusMainWindow: () => {},
|
||||||
onOverlayModalClosed: () => {},
|
onOverlayModalClosed: () => {},
|
||||||
openYomitanSettings: () => {},
|
openYomitanSettings: () => {},
|
||||||
@@ -44,7 +42,6 @@ test('composeIpcRuntimeHandlers returns callable IPC handlers and registration b
|
|||||||
tokenizeCurrentSubtitle: async () => null,
|
tokenizeCurrentSubtitle: async () => null,
|
||||||
getCurrentSubtitleRaw: () => '',
|
getCurrentSubtitleRaw: () => '',
|
||||||
getCurrentSubtitleAss: () => '',
|
getCurrentSubtitleAss: () => '',
|
||||||
getMpvSubtitleRenderMetrics: () => ({}) as never,
|
|
||||||
getSubtitlePosition: () => ({}) as never,
|
getSubtitlePosition: () => ({}) as never,
|
||||||
getSubtitleStyle: () => ({}) as never,
|
getSubtitleStyle: () => ({}) as never,
|
||||||
saveSubtitlePosition: () => {},
|
saveSubtitlePosition: () => {},
|
||||||
@@ -56,7 +53,6 @@ test('composeIpcRuntimeHandlers returns callable IPC handlers and registration b
|
|||||||
getAnkiConnectStatus: () => false,
|
getAnkiConnectStatus: () => false,
|
||||||
getRuntimeOptions: () => [],
|
getRuntimeOptions: () => [],
|
||||||
reportOverlayContentBounds: () => {},
|
reportOverlayContentBounds: () => {},
|
||||||
reportHoveredSubtitleToken: () => {},
|
|
||||||
getAnilistStatus: () => ({}) as never,
|
getAnilistStatus: () => ({}) as never,
|
||||||
clearAnilistToken: () => {},
|
clearAnilistToken: () => {},
|
||||||
openAnilistSetup: () => {},
|
openAnilistSetup: () => {},
|
||||||
|
|||||||
@@ -68,12 +68,14 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
|||||||
scheduleQuitCheck: () => {},
|
scheduleQuitCheck: () => {},
|
||||||
quitApp: () => {},
|
quitApp: () => {},
|
||||||
reportJellyfinRemoteStopped: () => {},
|
reportJellyfinRemoteStopped: () => {},
|
||||||
|
syncOverlayMpvSubtitleSuppression: () => {},
|
||||||
maybeRunAnilistPostWatchUpdate: async () => {},
|
maybeRunAnilistPostWatchUpdate: async () => {},
|
||||||
logSubtitleTimingError: () => {},
|
logSubtitleTimingError: () => {},
|
||||||
broadcastToOverlayWindows: () => {},
|
broadcastToOverlayWindows: () => {},
|
||||||
onSubtitleChange: () => {},
|
onSubtitleChange: () => {},
|
||||||
refreshDiscordPresence: () => {},
|
refreshDiscordPresence: () => {},
|
||||||
updateCurrentMediaPath: () => {},
|
updateCurrentMediaPath: () => {},
|
||||||
|
restoreMpvSubVisibilityForInvisibleOverlay: () => {},
|
||||||
getCurrentAnilistMediaKey: () => null,
|
getCurrentAnilistMediaKey: () => null,
|
||||||
resetAnilistMediaTracking: () => {},
|
resetAnilistMediaTracking: () => {},
|
||||||
maybeProbeAnilistDuration: () => {},
|
maybeProbeAnilistDuration: () => {},
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ test('composeShortcutRuntimes returns callable shortcut runtime handlers', () =>
|
|||||||
getConfiguredShortcuts: () => ({}) as never,
|
getConfiguredShortcuts: () => ({}) as never,
|
||||||
registerGlobalShortcutsCore: () => {},
|
registerGlobalShortcutsCore: () => {},
|
||||||
toggleVisibleOverlay: () => {},
|
toggleVisibleOverlay: () => {},
|
||||||
toggleInvisibleOverlay: () => {},
|
|
||||||
openYomitanSettings: () => {},
|
openYomitanSettings: () => {},
|
||||||
isDev: false,
|
isDev: false,
|
||||||
getMainWindow: () => null,
|
getMainWindow: () => null,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { RuntimeOptionsManager } from '../../runtime-options';
|
import type { RuntimeOptionsManager } from '../../runtime-options';
|
||||||
import type { JimakuApiResponse, JimakuLanguagePreference, ResolvedConfig } from '../../types';
|
import type { JimakuApiResponse, JimakuLanguagePreference, ResolvedConfig } from '../../types';
|
||||||
import {
|
import {
|
||||||
getInitialInvisibleOverlayVisibility as getInitialInvisibleOverlayVisibilityCore,
|
|
||||||
getJimakuLanguagePreference as getJimakuLanguagePreferenceCore,
|
getJimakuLanguagePreference as getJimakuLanguagePreferenceCore,
|
||||||
getJimakuMaxEntryResults as getJimakuMaxEntryResultsCore,
|
getJimakuMaxEntryResults as getJimakuMaxEntryResultsCore,
|
||||||
isAutoUpdateEnabledRuntime as isAutoUpdateEnabledRuntimeCore,
|
isAutoUpdateEnabledRuntime as isAutoUpdateEnabledRuntimeCore,
|
||||||
@@ -14,14 +13,12 @@ import {
|
|||||||
export type ConfigDerivedRuntimeDeps = {
|
export type ConfigDerivedRuntimeDeps = {
|
||||||
getResolvedConfig: () => ResolvedConfig;
|
getResolvedConfig: () => ResolvedConfig;
|
||||||
getRuntimeOptionsManager: () => RuntimeOptionsManager | null;
|
getRuntimeOptionsManager: () => RuntimeOptionsManager | null;
|
||||||
platform: NodeJS.Platform;
|
|
||||||
defaultJimakuLanguagePreference: JimakuLanguagePreference;
|
defaultJimakuLanguagePreference: JimakuLanguagePreference;
|
||||||
defaultJimakuMaxEntryResults: number;
|
defaultJimakuMaxEntryResults: number;
|
||||||
defaultJimakuApiBaseUrl: string;
|
defaultJimakuApiBaseUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createConfigDerivedRuntime(deps: ConfigDerivedRuntimeDeps): {
|
export function createConfigDerivedRuntime(deps: ConfigDerivedRuntimeDeps): {
|
||||||
getInitialInvisibleOverlayVisibility: () => boolean;
|
|
||||||
shouldAutoInitializeOverlayRuntimeFromConfig: () => boolean;
|
shouldAutoInitializeOverlayRuntimeFromConfig: () => boolean;
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => boolean;
|
shouldBindVisibleOverlayToMpvSubVisibility: () => boolean;
|
||||||
isAutoUpdateEnabledRuntime: () => boolean;
|
isAutoUpdateEnabledRuntime: () => boolean;
|
||||||
@@ -34,8 +31,6 @@ export function createConfigDerivedRuntime(deps: ConfigDerivedRuntimeDeps): {
|
|||||||
) => Promise<JimakuApiResponse<T>>;
|
) => Promise<JimakuApiResponse<T>>;
|
||||||
} {
|
} {
|
||||||
return {
|
return {
|
||||||
getInitialInvisibleOverlayVisibility: () =>
|
|
||||||
getInitialInvisibleOverlayVisibilityCore(deps.getResolvedConfig(), deps.platform),
|
|
||||||
shouldAutoInitializeOverlayRuntimeFromConfig: () =>
|
shouldAutoInitializeOverlayRuntimeFromConfig: () =>
|
||||||
shouldAutoInitializeOverlayRuntimeFromConfigCore(deps.getResolvedConfig()),
|
shouldAutoInitializeOverlayRuntimeFromConfigCore(deps.getResolvedConfig()),
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () =>
|
shouldBindVisibleOverlayToMpvSubVisibility: () =>
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ test('field grouping overlay main deps builder maps window visibility and resolv
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
getVisibleOverlayVisible: () => true,
|
getVisibleOverlayVisible: () => true,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
setVisibleOverlayVisible: (visible) => calls.push(`visible:${visible}`),
|
setVisibleOverlayVisible: (visible) => calls.push(`visible:${visible}`),
|
||||||
setInvisibleOverlayVisible: (visible) => calls.push(`invisible:${visible}`),
|
|
||||||
getResolver: () => resolver,
|
getResolver: () => resolver,
|
||||||
setResolver: (nextResolver) => {
|
setResolver: (nextResolver) => {
|
||||||
calls.push(`set-resolver:${nextResolver ? 'set' : 'null'}`);
|
calls.push(`set-resolver:${nextResolver ? 'set' : 'null'}`);
|
||||||
@@ -31,17 +29,10 @@ test('field grouping overlay main deps builder maps window visibility and resolv
|
|||||||
|
|
||||||
assert.equal(deps.getMainWindow()?.isDestroyed(), false);
|
assert.equal(deps.getMainWindow()?.isDestroyed(), false);
|
||||||
assert.equal(deps.getVisibleOverlayVisible(), true);
|
assert.equal(deps.getVisibleOverlayVisible(), true);
|
||||||
assert.equal(deps.getInvisibleOverlayVisible(), false);
|
|
||||||
assert.equal(deps.getResolver(), resolver);
|
assert.equal(deps.getResolver(), resolver);
|
||||||
assert.equal(deps.getRestoreVisibleOverlayOnModalClose(), modalSet);
|
assert.equal(deps.getRestoreVisibleOverlayOnModalClose(), modalSet);
|
||||||
deps.setVisibleOverlayVisible(true);
|
deps.setVisibleOverlayVisible(true);
|
||||||
deps.setInvisibleOverlayVisible(false);
|
|
||||||
deps.setResolver(null);
|
deps.setResolver(null);
|
||||||
assert.equal(deps.sendToVisibleOverlay('kiku:open', 1), true);
|
assert.equal(deps.sendToVisibleOverlay('kiku:open', 1), true);
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, ['visible:true', 'set-resolver:null', 'send:kiku:open:1']);
|
||||||
'visible:true',
|
|
||||||
'invisible:false',
|
|
||||||
'set-resolver:null',
|
|
||||||
'send:kiku:open:1',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ export function createBuildFieldGroupingOverlayMainDepsHandler<TModal extends st
|
|||||||
return (): BuiltFieldGroupingOverlayMainDeps<TModal> => ({
|
return (): BuiltFieldGroupingOverlayMainDeps<TModal> => ({
|
||||||
getMainWindow: () => deps.getMainWindow(),
|
getMainWindow: () => deps.getMainWindow(),
|
||||||
getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(),
|
getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(),
|
||||||
getInvisibleOverlayVisible: () => deps.getInvisibleOverlayVisible(),
|
|
||||||
setVisibleOverlayVisible: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
setVisibleOverlayVisible: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => deps.setInvisibleOverlayVisible(visible),
|
|
||||||
getResolver: () => deps.getResolver(),
|
getResolver: () => deps.getResolver(),
|
||||||
setResolver: (resolver) => deps.setResolver(resolver),
|
setResolver: (resolver) => deps.setResolver(resolver),
|
||||||
getRestoreVisibleOverlayOnModalClose: () => deps.getRestoreVisibleOverlayOnModalClose(),
|
getRestoreVisibleOverlayOnModalClose: () => deps.getRestoreVisibleOverlayOnModalClose(),
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ test('register global shortcuts main deps map callbacks and flags', () => {
|
|||||||
getConfiguredShortcuts: () => ({ copySubtitle: 's' } as never),
|
getConfiguredShortcuts: () => ({ copySubtitle: 's' } as never),
|
||||||
registerGlobalShortcutsCore: () => calls.push('register'),
|
registerGlobalShortcutsCore: () => calls.push('register'),
|
||||||
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
||||||
toggleInvisibleOverlay: () => calls.push('toggle-invisible'),
|
|
||||||
openYomitanSettings: () => calls.push('open-yomitan'),
|
openYomitanSettings: () => calls.push('open-yomitan'),
|
||||||
isDev: true,
|
isDev: true,
|
||||||
getMainWindow: () => mainWindow as never,
|
getMainWindow: () => mainWindow as never,
|
||||||
@@ -38,17 +37,15 @@ test('register global shortcuts main deps map callbacks and flags', () => {
|
|||||||
deps.registerGlobalShortcutsCore({
|
deps.registerGlobalShortcutsCore({
|
||||||
shortcuts: deps.getConfiguredShortcuts(),
|
shortcuts: deps.getConfiguredShortcuts(),
|
||||||
onToggleVisibleOverlay: () => undefined,
|
onToggleVisibleOverlay: () => undefined,
|
||||||
onToggleInvisibleOverlay: () => undefined,
|
|
||||||
onOpenYomitanSettings: () => undefined,
|
onOpenYomitanSettings: () => undefined,
|
||||||
isDev: deps.isDev,
|
isDev: deps.isDev,
|
||||||
getMainWindow: deps.getMainWindow,
|
getMainWindow: deps.getMainWindow,
|
||||||
});
|
});
|
||||||
deps.onToggleVisibleOverlay();
|
deps.onToggleVisibleOverlay();
|
||||||
deps.onToggleInvisibleOverlay();
|
|
||||||
deps.onOpenYomitanSettings();
|
deps.onOpenYomitanSettings();
|
||||||
assert.equal(deps.isDev, true);
|
assert.equal(deps.isDev, true);
|
||||||
assert.deepEqual(deps.getMainWindow(), mainWindow);
|
assert.deepEqual(deps.getMainWindow(), mainWindow);
|
||||||
assert.deepEqual(calls, ['register', 'toggle-visible', 'toggle-invisible', 'open-yomitan']);
|
assert.deepEqual(calls, ['register', 'toggle-visible', 'open-yomitan']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('refresh global shortcuts main deps map passthrough handlers', () => {
|
test('refresh global shortcuts main deps map passthrough handlers', () => {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export function createBuildRegisterGlobalShortcutsMainDepsHandler(deps: {
|
|||||||
getConfiguredShortcuts: () => RegisterGlobalShortcutsServiceOptions['shortcuts'];
|
getConfiguredShortcuts: () => RegisterGlobalShortcutsServiceOptions['shortcuts'];
|
||||||
registerGlobalShortcutsCore: (options: RegisterGlobalShortcutsServiceOptions) => void;
|
registerGlobalShortcutsCore: (options: RegisterGlobalShortcutsServiceOptions) => void;
|
||||||
toggleVisibleOverlay: () => void;
|
toggleVisibleOverlay: () => void;
|
||||||
toggleInvisibleOverlay: () => void;
|
|
||||||
openYomitanSettings: () => void;
|
openYomitanSettings: () => void;
|
||||||
isDev: boolean;
|
isDev: boolean;
|
||||||
getMainWindow: RegisterGlobalShortcutsServiceOptions['getMainWindow'];
|
getMainWindow: RegisterGlobalShortcutsServiceOptions['getMainWindow'];
|
||||||
@@ -29,7 +28,6 @@ export function createBuildRegisterGlobalShortcutsMainDepsHandler(deps: {
|
|||||||
registerGlobalShortcutsCore: (options: RegisterGlobalShortcutsServiceOptions) =>
|
registerGlobalShortcutsCore: (options: RegisterGlobalShortcutsServiceOptions) =>
|
||||||
deps.registerGlobalShortcutsCore(options),
|
deps.registerGlobalShortcutsCore(options),
|
||||||
onToggleVisibleOverlay: () => deps.toggleVisibleOverlay(),
|
onToggleVisibleOverlay: () => deps.toggleVisibleOverlay(),
|
||||||
onToggleInvisibleOverlay: () => deps.toggleInvisibleOverlay(),
|
|
||||||
onOpenYomitanSettings: () => deps.openYomitanSettings(),
|
onOpenYomitanSettings: () => deps.openYomitanSettings(),
|
||||||
isDev: deps.isDev,
|
isDev: deps.isDev,
|
||||||
getMainWindow: deps.getMainWindow,
|
getMainWindow: deps.getMainWindow,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { createGlobalShortcutsRuntimeHandlers } from './global-shortcuts-runtime
|
|||||||
function createShortcuts(): ConfiguredShortcuts {
|
function createShortcuts(): ConfiguredShortcuts {
|
||||||
return {
|
return {
|
||||||
toggleVisibleOverlayGlobal: 'CommandOrControl+Shift+O',
|
toggleVisibleOverlayGlobal: 'CommandOrControl+Shift+O',
|
||||||
toggleInvisibleOverlayGlobal: 'CommandOrControl+Shift+I',
|
|
||||||
copySubtitle: 's',
|
copySubtitle: 's',
|
||||||
copySubtitleMultiple: 'CommandOrControl+s',
|
copySubtitleMultiple: 'CommandOrControl+s',
|
||||||
updateLastCardFromClipboard: 'c',
|
updateLastCardFromClipboard: 'c',
|
||||||
@@ -38,7 +37,6 @@ test('global shortcuts runtime handlers compose get/register/refresh flow', () =
|
|||||||
assert.equal(options.shortcuts, shortcuts);
|
assert.equal(options.shortcuts, shortcuts);
|
||||||
},
|
},
|
||||||
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
toggleVisibleOverlay: () => calls.push('toggle-visible'),
|
||||||
toggleInvisibleOverlay: () => calls.push('toggle-invisible'),
|
|
||||||
openYomitanSettings: () => calls.push('open-yomitan'),
|
openYomitanSettings: () => calls.push('open-yomitan'),
|
||||||
isDev: false,
|
isDev: false,
|
||||||
getMainWindow: () => null,
|
getMainWindow: () => null,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import type { ConfiguredShortcuts } from '../../core/utils/shortcut-config';
|
|||||||
function createShortcuts(): ConfiguredShortcuts {
|
function createShortcuts(): ConfiguredShortcuts {
|
||||||
return {
|
return {
|
||||||
toggleVisibleOverlayGlobal: 'CommandOrControl+Shift+O',
|
toggleVisibleOverlayGlobal: 'CommandOrControl+Shift+O',
|
||||||
toggleInvisibleOverlayGlobal: 'CommandOrControl+Shift+I',
|
|
||||||
copySubtitle: 's',
|
copySubtitle: 's',
|
||||||
copySubtitleMultiple: 'CommandOrControl+s',
|
copySubtitleMultiple: 'CommandOrControl+s',
|
||||||
updateLastCardFromClipboard: 'c',
|
updateLastCardFromClipboard: 'c',
|
||||||
@@ -58,18 +57,16 @@ test('register global shortcuts handler passes through callbacks and shortcuts',
|
|||||||
assert.equal(options.isDev, true);
|
assert.equal(options.isDev, true);
|
||||||
assert.equal(options.getMainWindow(), mainWindow);
|
assert.equal(options.getMainWindow(), mainWindow);
|
||||||
options.onToggleVisibleOverlay();
|
options.onToggleVisibleOverlay();
|
||||||
options.onToggleInvisibleOverlay();
|
|
||||||
options.onOpenYomitanSettings();
|
options.onOpenYomitanSettings();
|
||||||
},
|
},
|
||||||
onToggleVisibleOverlay: () => calls.push('toggle-visible'),
|
onToggleVisibleOverlay: () => calls.push('toggle-visible'),
|
||||||
onToggleInvisibleOverlay: () => calls.push('toggle-invisible'),
|
|
||||||
onOpenYomitanSettings: () => calls.push('open-yomitan'),
|
onOpenYomitanSettings: () => calls.push('open-yomitan'),
|
||||||
isDev: true,
|
isDev: true,
|
||||||
getMainWindow: () => mainWindow,
|
getMainWindow: () => mainWindow,
|
||||||
});
|
});
|
||||||
|
|
||||||
registerGlobalShortcuts();
|
registerGlobalShortcuts();
|
||||||
assert.deepEqual(calls, ['register', 'toggle-visible', 'toggle-invisible', 'open-yomitan']);
|
assert.deepEqual(calls, ['register', 'toggle-visible', 'open-yomitan']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('refresh global and overlay shortcuts unregisters then re-registers', () => {
|
test('refresh global and overlay shortcuts unregisters then re-registers', () => {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export function createRegisterGlobalShortcutsHandler(deps: {
|
|||||||
getConfiguredShortcuts: () => RegisterGlobalShortcutsServiceOptions['shortcuts'];
|
getConfiguredShortcuts: () => RegisterGlobalShortcutsServiceOptions['shortcuts'];
|
||||||
registerGlobalShortcutsCore: (options: RegisterGlobalShortcutsServiceOptions) => void;
|
registerGlobalShortcutsCore: (options: RegisterGlobalShortcutsServiceOptions) => void;
|
||||||
onToggleVisibleOverlay: () => void;
|
onToggleVisibleOverlay: () => void;
|
||||||
onToggleInvisibleOverlay: () => void;
|
|
||||||
onOpenYomitanSettings: () => void;
|
onOpenYomitanSettings: () => void;
|
||||||
isDev: boolean;
|
isDev: boolean;
|
||||||
getMainWindow: RegisterGlobalShortcutsServiceOptions['getMainWindow'];
|
getMainWindow: RegisterGlobalShortcutsServiceOptions['getMainWindow'];
|
||||||
@@ -27,7 +26,6 @@ export function createRegisterGlobalShortcutsHandler(deps: {
|
|||||||
deps.registerGlobalShortcutsCore({
|
deps.registerGlobalShortcutsCore({
|
||||||
shortcuts: deps.getConfiguredShortcuts(),
|
shortcuts: deps.getConfiguredShortcuts(),
|
||||||
onToggleVisibleOverlay: deps.onToggleVisibleOverlay,
|
onToggleVisibleOverlay: deps.onToggleVisibleOverlay,
|
||||||
onToggleInvisibleOverlay: deps.onToggleInvisibleOverlay,
|
|
||||||
onOpenYomitanSettings: deps.onOpenYomitanSettings,
|
onOpenYomitanSettings: deps.onOpenYomitanSettings,
|
||||||
isDev: deps.isDev,
|
isDev: deps.isDev,
|
||||||
getMainWindow: deps.getMainWindow,
|
getMainWindow: deps.getMainWindow,
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ function createArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
|||||||
toggleOverlay: false,
|
toggleOverlay: false,
|
||||||
hideOverlay: false,
|
hideOverlay: false,
|
||||||
showOverlay: false,
|
showOverlay: false,
|
||||||
toggleInvisibleOverlay: false,
|
|
||||||
hideInvisibleOverlay: false,
|
|
||||||
showInvisibleOverlay: false,
|
|
||||||
copyCurrentSubtitle: false,
|
copyCurrentSubtitle: false,
|
||||||
multiCopy: false,
|
multiCopy: false,
|
||||||
mineSentence: false,
|
mineSentence: false,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ test('mpv connection handler reports stop and quits when disconnect guard passes
|
|||||||
const handler = createHandleMpvConnectionChangeHandler({
|
const handler = createHandleMpvConnectionChangeHandler({
|
||||||
reportJellyfinRemoteStopped: () => calls.push('report-stop'),
|
reportJellyfinRemoteStopped: () => calls.push('report-stop'),
|
||||||
refreshDiscordPresence: () => calls.push('presence-refresh'),
|
refreshDiscordPresence: () => calls.push('presence-refresh'),
|
||||||
|
syncOverlayMpvSubtitleSuppression: () => calls.push('sync-overlay-mpv-sub'),
|
||||||
hasInitialJellyfinPlayArg: () => true,
|
hasInitialJellyfinPlayArg: () => true,
|
||||||
isOverlayRuntimeInitialized: () => false,
|
isOverlayRuntimeInitialized: () => false,
|
||||||
isQuitOnDisconnectArmed: () => true,
|
isQuitOnDisconnectArmed: () => true,
|
||||||
@@ -26,6 +27,27 @@ test('mpv connection handler reports stop and quits when disconnect guard passes
|
|||||||
assert.deepEqual(calls, ['presence-refresh', 'report-stop', 'schedule', 'quit']);
|
assert.deepEqual(calls, ['presence-refresh', 'report-stop', 'schedule', 'quit']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('mpv connection handler syncs overlay subtitle suppression on connect', () => {
|
||||||
|
const calls: string[] = [];
|
||||||
|
const handler = createHandleMpvConnectionChangeHandler({
|
||||||
|
reportJellyfinRemoteStopped: () => calls.push('report-stop'),
|
||||||
|
refreshDiscordPresence: () => calls.push('presence-refresh'),
|
||||||
|
syncOverlayMpvSubtitleSuppression: () => calls.push('sync-overlay-mpv-sub'),
|
||||||
|
hasInitialJellyfinPlayArg: () => true,
|
||||||
|
isOverlayRuntimeInitialized: () => false,
|
||||||
|
isQuitOnDisconnectArmed: () => true,
|
||||||
|
scheduleQuitCheck: () => {
|
||||||
|
calls.push('schedule');
|
||||||
|
},
|
||||||
|
isMpvConnected: () => false,
|
||||||
|
quitApp: () => calls.push('quit'),
|
||||||
|
});
|
||||||
|
|
||||||
|
handler({ connected: true });
|
||||||
|
|
||||||
|
assert.deepEqual(calls, ['presence-refresh', 'sync-overlay-mpv-sub']);
|
||||||
|
});
|
||||||
|
|
||||||
test('mpv subtitle timing handler ignores blank subtitle lines', () => {
|
test('mpv subtitle timing handler ignores blank subtitle lines', () => {
|
||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
const handler = createHandleMpvSubtitleTimingHandler({
|
const handler = createHandleMpvSubtitleTimingHandler({
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type MpvEventClient = {
|
|||||||
export function createHandleMpvConnectionChangeHandler(deps: {
|
export function createHandleMpvConnectionChangeHandler(deps: {
|
||||||
reportJellyfinRemoteStopped: () => void;
|
reportJellyfinRemoteStopped: () => void;
|
||||||
refreshDiscordPresence: () => void;
|
refreshDiscordPresence: () => void;
|
||||||
|
syncOverlayMpvSubtitleSuppression: () => void;
|
||||||
hasInitialJellyfinPlayArg: () => boolean;
|
hasInitialJellyfinPlayArg: () => boolean;
|
||||||
isOverlayRuntimeInitialized: () => boolean;
|
isOverlayRuntimeInitialized: () => boolean;
|
||||||
isQuitOnDisconnectArmed: () => boolean;
|
isQuitOnDisconnectArmed: () => boolean;
|
||||||
@@ -27,7 +28,10 @@ export function createHandleMpvConnectionChangeHandler(deps: {
|
|||||||
}) {
|
}) {
|
||||||
return ({ connected }: { connected: boolean }): void => {
|
return ({ connected }: { connected: boolean }): void => {
|
||||||
deps.refreshDiscordPresence();
|
deps.refreshDiscordPresence();
|
||||||
if (connected) return;
|
if (connected) {
|
||||||
|
deps.syncOverlayMpvSubtitleSuppression();
|
||||||
|
return;
|
||||||
|
}
|
||||||
deps.reportJellyfinRemoteStopped();
|
deps.reportJellyfinRemoteStopped();
|
||||||
if (!deps.hasInitialJellyfinPlayArg()) return;
|
if (!deps.hasInitialJellyfinPlayArg()) return;
|
||||||
if (deps.isOverlayRuntimeInitialized()) return;
|
if (deps.isOverlayRuntimeInitialized()) return;
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
import assert from 'node:assert/strict';
|
|
||||||
import test from 'node:test';
|
|
||||||
|
|
||||||
import { PartOfSpeech, type SubtitleData } from '../../types';
|
|
||||||
import {
|
|
||||||
HOVER_TOKEN_MESSAGE,
|
|
||||||
HOVER_SCRIPT_NAME,
|
|
||||||
buildHoveredTokenMessageCommand,
|
|
||||||
buildHoveredTokenPayload,
|
|
||||||
createApplyHoveredTokenOverlayHandler,
|
|
||||||
} from './mpv-hover-highlight';
|
|
||||||
|
|
||||||
const SUBTITLE: SubtitleData = {
|
|
||||||
text: '昨日は雨だった。',
|
|
||||||
tokens: [
|
|
||||||
{
|
|
||||||
surface: '昨日',
|
|
||||||
reading: 'きのう',
|
|
||||||
headword: '昨日',
|
|
||||||
startPos: 0,
|
|
||||||
endPos: 2,
|
|
||||||
partOfSpeech: PartOfSpeech.noun,
|
|
||||||
isMerged: false,
|
|
||||||
isKnown: false,
|
|
||||||
isNPlusOneTarget: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
surface: 'は',
|
|
||||||
reading: 'は',
|
|
||||||
headword: 'は',
|
|
||||||
startPos: 2,
|
|
||||||
endPos: 3,
|
|
||||||
partOfSpeech: PartOfSpeech.particle,
|
|
||||||
isMerged: false,
|
|
||||||
isKnown: true,
|
|
||||||
isNPlusOneTarget: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
surface: '雨',
|
|
||||||
reading: 'あめ',
|
|
||||||
headword: '雨',
|
|
||||||
startPos: 3,
|
|
||||||
endPos: 4,
|
|
||||||
partOfSpeech: PartOfSpeech.noun,
|
|
||||||
isMerged: false,
|
|
||||||
isKnown: false,
|
|
||||||
isNPlusOneTarget: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
surface: 'だった。',
|
|
||||||
reading: 'だった。',
|
|
||||||
headword: 'だ',
|
|
||||||
startPos: 4,
|
|
||||||
endPos: 8,
|
|
||||||
partOfSpeech: PartOfSpeech.other,
|
|
||||||
isMerged: false,
|
|
||||||
isKnown: false,
|
|
||||||
isNPlusOneTarget: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
test('buildHoveredTokenPayload normalizes metadata and strips empty tokens', () => {
|
|
||||||
const payload = buildHoveredTokenPayload({
|
|
||||||
subtitle: SUBTITLE,
|
|
||||||
hoveredTokenIndex: 2,
|
|
||||||
revision: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(payload.revision, 5);
|
|
||||||
assert.equal(payload.subtitle, '昨日は雨だった。');
|
|
||||||
assert.equal(payload.hoveredTokenIndex, 2);
|
|
||||||
assert.equal(payload.tokens.length, 4);
|
|
||||||
assert.equal(payload.tokens[0]?.text, '昨日');
|
|
||||||
assert.equal(payload.tokens[0]?.index, 0);
|
|
||||||
assert.equal(payload.tokens[1]?.index, 1);
|
|
||||||
assert.equal(payload.colors.hover, 'C6A0F6');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('buildHoveredTokenPayload normalizes hover color override', () => {
|
|
||||||
const payload = buildHoveredTokenPayload({
|
|
||||||
subtitle: SUBTITLE,
|
|
||||||
hoveredTokenIndex: 1,
|
|
||||||
revision: 7,
|
|
||||||
hoverColor: '#c6a0f6',
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(payload.colors.hover, 'C6A0F6');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('buildHoveredTokenMessageCommand sends script-message-to subminer payload', () => {
|
|
||||||
const payload = buildHoveredTokenPayload({
|
|
||||||
subtitle: SUBTITLE,
|
|
||||||
hoveredTokenIndex: 0,
|
|
||||||
revision: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const command = buildHoveredTokenMessageCommand(payload);
|
|
||||||
|
|
||||||
assert.equal(command[0], 'script-message-to');
|
|
||||||
assert.equal(command[1], HOVER_SCRIPT_NAME);
|
|
||||||
assert.equal(command[2], HOVER_TOKEN_MESSAGE);
|
|
||||||
|
|
||||||
const raw = command[3] as string;
|
|
||||||
const parsed = JSON.parse(raw);
|
|
||||||
assert.equal(parsed.revision, 1);
|
|
||||||
assert.equal(parsed.hoveredTokenIndex, 0);
|
|
||||||
assert.equal(parsed.subtitle, '昨日は雨だった。');
|
|
||||||
assert.equal(parsed.tokens.length, 4);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('createApplyHoveredTokenOverlayHandler sends clear payload when hovered token is missing', () => {
|
|
||||||
const commands: Array<(string | number)[]> = [];
|
|
||||||
const apply = createApplyHoveredTokenOverlayHandler({
|
|
||||||
getMpvClient: () => ({
|
|
||||||
connected: true,
|
|
||||||
send: ({ command }: { command: (string | number)[] }) => {
|
|
||||||
commands.push(command);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
getCurrentSubtitleData: () => SUBTITLE,
|
|
||||||
getHoveredTokenIndex: () => null,
|
|
||||||
getHoveredSubtitleRevision: () => 3,
|
|
||||||
getHoverTokenColor: () => null,
|
|
||||||
});
|
|
||||||
|
|
||||||
apply();
|
|
||||||
|
|
||||||
const parsed = JSON.parse(commands[0]?.[3] as string);
|
|
||||||
assert.equal(parsed.hoveredTokenIndex, null);
|
|
||||||
assert.equal(parsed.subtitle, null);
|
|
||||||
assert.equal(parsed.tokens.length, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('createApplyHoveredTokenOverlayHandler sends highlight payload when hover is active', () => {
|
|
||||||
const commands: Array<(string | number)[]> = [];
|
|
||||||
const apply = createApplyHoveredTokenOverlayHandler({
|
|
||||||
getMpvClient: () => ({
|
|
||||||
connected: true,
|
|
||||||
send: ({ command }: { command: (string | number)[] }) => {
|
|
||||||
commands.push(command);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
getCurrentSubtitleData: () => SUBTITLE,
|
|
||||||
getHoveredTokenIndex: () => 0,
|
|
||||||
getHoveredSubtitleRevision: () => 3,
|
|
||||||
getHoverTokenColor: () => '#c6a0f6',
|
|
||||||
});
|
|
||||||
|
|
||||||
apply();
|
|
||||||
|
|
||||||
const parsed = JSON.parse(commands[0]?.[3] as string);
|
|
||||||
assert.equal(parsed.hoveredTokenIndex, 0);
|
|
||||||
assert.equal(parsed.subtitle, '昨日は雨だった。');
|
|
||||||
assert.equal(parsed.tokens.length, 4);
|
|
||||||
assert.equal(parsed.colors.hover, 'C6A0F6');
|
|
||||||
assert.equal(commands[0]?.[0], 'script-message-to');
|
|
||||||
assert.equal(commands[0]?.[1], HOVER_SCRIPT_NAME);
|
|
||||||
});
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import type { SubtitleData } from '../../types';
|
|
||||||
|
|
||||||
export const HOVER_SCRIPT_NAME = 'subminer';
|
|
||||||
export const HOVER_TOKEN_MESSAGE = 'subminer-hover-token';
|
|
||||||
|
|
||||||
const DEFAULT_HOVER_TOKEN_COLOR = 'C6A0F6';
|
|
||||||
const DEFAULT_TOKEN_COLOR = 'FFFFFF';
|
|
||||||
|
|
||||||
export type HoverPayloadToken = {
|
|
||||||
text: string;
|
|
||||||
index: number;
|
|
||||||
startPos: number | null;
|
|
||||||
endPos: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type HoverTokenPayload = {
|
|
||||||
revision: number;
|
|
||||||
subtitle: string | null;
|
|
||||||
hoveredTokenIndex: number | null;
|
|
||||||
tokens: HoverPayloadToken[];
|
|
||||||
colors: {
|
|
||||||
base: string;
|
|
||||||
hover: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type HoverTokenInput = {
|
|
||||||
subtitle: SubtitleData | null;
|
|
||||||
hoveredTokenIndex: number | null;
|
|
||||||
revision: number;
|
|
||||||
hoverColor?: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
function normalizeHexColor(color: string | null | undefined, fallback: string): string {
|
|
||||||
if (typeof color !== 'string') {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
const normalized = color.trim().replace(/^#/, '').toUpperCase();
|
|
||||||
return /^[0-9A-F]{6}$/.test(normalized) ? normalized : fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeSubtitleText(text: string): string {
|
|
||||||
return text
|
|
||||||
.replace(/\\N/g, '\n')
|
|
||||||
.replace(/\\n/g, '\n')
|
|
||||||
.replace(/\{[^}]*\}/g, '')
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeTokenSurface(surface: unknown): string {
|
|
||||||
return typeof surface === 'string' ? surface : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasHoveredToken(subtitle: SubtitleData | null, hoveredTokenIndex: number | null): boolean {
|
|
||||||
if (!subtitle || hoveredTokenIndex === null || hoveredTokenIndex < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return subtitle.tokens?.some((token, index) => index === hoveredTokenIndex) ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildHoveredTokenPayload(input: HoverTokenInput): HoverTokenPayload {
|
|
||||||
const { subtitle, hoveredTokenIndex, revision, hoverColor } = input;
|
|
||||||
|
|
||||||
const tokens: HoverPayloadToken[] = [];
|
|
||||||
|
|
||||||
if (subtitle?.tokens && subtitle.tokens.length > 0) {
|
|
||||||
for (let tokenIndex = 0; tokenIndex < subtitle.tokens.length; tokenIndex += 1) {
|
|
||||||
const token = subtitle.tokens[tokenIndex];
|
|
||||||
if (!token) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const surface = sanitizeTokenSurface(token?.surface);
|
|
||||||
if (!surface || surface.trim().length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens.push({
|
|
||||||
text: surface,
|
|
||||||
index: tokenIndex,
|
|
||||||
startPos: Number.isFinite(token.startPos) ? token.startPos : null,
|
|
||||||
endPos: Number.isFinite(token.endPos) ? token.endPos : null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
revision,
|
|
||||||
subtitle: subtitle ? sanitizeSubtitleText(subtitle.text) : null,
|
|
||||||
hoveredTokenIndex:
|
|
||||||
hoveredTokenIndex !== null && hoveredTokenIndex >= 0 ? hoveredTokenIndex : null,
|
|
||||||
tokens,
|
|
||||||
colors: {
|
|
||||||
base: DEFAULT_TOKEN_COLOR,
|
|
||||||
hover: normalizeHexColor(hoverColor, DEFAULT_HOVER_TOKEN_COLOR),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildHoveredTokenMessageCommand(payload: HoverTokenPayload): (string | number)[] {
|
|
||||||
return [
|
|
||||||
'script-message-to',
|
|
||||||
HOVER_SCRIPT_NAME,
|
|
||||||
HOVER_TOKEN_MESSAGE,
|
|
||||||
JSON.stringify(payload),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createApplyHoveredTokenOverlayHandler(deps: {
|
|
||||||
getMpvClient: () => {
|
|
||||||
connected: boolean;
|
|
||||||
send: (payload: { command: (string | number)[] }) => boolean;
|
|
||||||
} | null;
|
|
||||||
getCurrentSubtitleData: () => SubtitleData | null;
|
|
||||||
getHoveredTokenIndex: () => number | null;
|
|
||||||
getHoveredSubtitleRevision: () => number;
|
|
||||||
getHoverTokenColor: () => string | null;
|
|
||||||
}) {
|
|
||||||
return (): void => {
|
|
||||||
const mpvClient = deps.getMpvClient();
|
|
||||||
if (!mpvClient || !mpvClient.connected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subtitle = deps.getCurrentSubtitleData();
|
|
||||||
const hoveredTokenIndex = deps.getHoveredTokenIndex();
|
|
||||||
const revision = deps.getHoveredSubtitleRevision();
|
|
||||||
const hoverColor = deps.getHoverTokenColor();
|
|
||||||
const payload = buildHoveredTokenPayload({
|
|
||||||
subtitle: subtitle && hasHoveredToken(subtitle, hoveredTokenIndex) ? subtitle : null,
|
|
||||||
hoveredTokenIndex: hoveredTokenIndex,
|
|
||||||
revision,
|
|
||||||
hoverColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
mpvClient.send({ command: buildHoveredTokenMessageCommand(payload) });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -51,6 +51,7 @@ test('media path change handler reports stop for empty path and probes media key
|
|||||||
const handler = createHandleMpvMediaPathChangeHandler({
|
const handler = createHandleMpvMediaPathChangeHandler({
|
||||||
updateCurrentMediaPath: (path) => calls.push(`path:${path}`),
|
updateCurrentMediaPath: (path) => calls.push(`path:${path}`),
|
||||||
reportJellyfinRemoteStopped: () => calls.push('stopped'),
|
reportJellyfinRemoteStopped: () => calls.push('stopped'),
|
||||||
|
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
||||||
getCurrentAnilistMediaKey: () => 'show:1',
|
getCurrentAnilistMediaKey: () => 'show:1',
|
||||||
resetAnilistMediaTracking: (mediaKey) => calls.push(`reset:${String(mediaKey)}`),
|
resetAnilistMediaTracking: (mediaKey) => calls.push(`reset:${String(mediaKey)}`),
|
||||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||||
@@ -63,6 +64,7 @@ test('media path change handler reports stop for empty path and probes media key
|
|||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, [
|
||||||
'path:',
|
'path:',
|
||||||
'stopped',
|
'stopped',
|
||||||
|
'restore-mpv-sub',
|
||||||
'reset:show:1',
|
'reset:show:1',
|
||||||
'probe:show:1',
|
'probe:show:1',
|
||||||
'guess:show:1',
|
'guess:show:1',
|
||||||
|
|||||||
@@ -19,12 +19,10 @@ test('overlay content measurement store main deps builder maps callbacks', () =>
|
|||||||
|
|
||||||
test('overlay modal runtime main deps builder maps window resolvers', () => {
|
test('overlay modal runtime main deps builder maps window resolvers', () => {
|
||||||
const mainWindow = { id: 'main' };
|
const mainWindow = { id: 'main' };
|
||||||
const invisibleWindow = { id: 'invisible' };
|
|
||||||
const modalWindow = { id: 'modal' };
|
const modalWindow = { id: 'modal' };
|
||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
const deps = createBuildOverlayModalRuntimeMainDepsHandler({
|
const deps = createBuildOverlayModalRuntimeMainDepsHandler({
|
||||||
getMainWindow: () => mainWindow as never,
|
getMainWindow: () => mainWindow as never,
|
||||||
getInvisibleWindow: () => invisibleWindow as never,
|
|
||||||
getModalWindow: () => modalWindow as never,
|
getModalWindow: () => modalWindow as never,
|
||||||
createModalWindow: () => modalWindow as never,
|
createModalWindow: () => modalWindow as never,
|
||||||
getModalGeometry: () => ({ x: 1, y: 2, width: 3, height: 4 }),
|
getModalGeometry: () => ({ x: 1, y: 2, width: 3, height: 4 }),
|
||||||
@@ -33,7 +31,6 @@ test('overlay modal runtime main deps builder maps window resolvers', () => {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
assert.equal(deps.getMainWindow(), mainWindow);
|
assert.equal(deps.getMainWindow(), mainWindow);
|
||||||
assert.equal(deps.getInvisibleWindow(), invisibleWindow);
|
|
||||||
assert.equal(deps.getModalWindow(), modalWindow);
|
assert.equal(deps.getModalWindow(), modalWindow);
|
||||||
assert.equal(deps.createModalWindow(), modalWindow);
|
assert.equal(deps.createModalWindow(), modalWindow);
|
||||||
assert.deepEqual(deps.getModalGeometry(), { x: 1, y: 2, width: 3, height: 4 });
|
assert.deepEqual(deps.getModalGeometry(), { x: 1, y: 2, width: 3, height: 4 });
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export function createBuildOverlayModalRuntimeMainDepsHandler(
|
|||||||
) {
|
) {
|
||||||
return (): OverlayWindowResolver => ({
|
return (): OverlayWindowResolver => ({
|
||||||
getMainWindow: () => deps.getMainWindow(),
|
getMainWindow: () => deps.getMainWindow(),
|
||||||
getInvisibleWindow: () => deps.getInvisibleWindow(),
|
|
||||||
getModalWindow: () => deps.getModalWindow(),
|
getModalWindow: () => deps.getModalWindow(),
|
||||||
createModalWindow: () => deps.createModalWindow(),
|
createModalWindow: () => deps.createModalWindow(),
|
||||||
getModalGeometry: () => deps.getModalGeometry(),
|
getModalGeometry: () => deps.getModalGeometry(),
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ test('overlay runtime bootstrap handlers compose options builder and bootstrap h
|
|||||||
ankiIntegration: null as unknown,
|
ankiIntegration: null as unknown,
|
||||||
};
|
};
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
let invisibleOverlayVisible = false;
|
|
||||||
let warmupsStarted = 0;
|
let warmupsStarted = 0;
|
||||||
|
|
||||||
const { initializeOverlayRuntime } = createOverlayRuntimeBootstrapHandlers({
|
const { initializeOverlayRuntime } = createOverlayRuntimeBootstrapHandlers({
|
||||||
@@ -23,21 +22,16 @@ test('overlay runtime bootstrap handlers compose options builder and bootstrap h
|
|||||||
appState,
|
appState,
|
||||||
overlayManager: {
|
overlayManager: {
|
||||||
getVisibleOverlayVisible: () => true,
|
getVisibleOverlayVisible: () => true,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
},
|
},
|
||||||
overlayVisibilityRuntime: {
|
overlayVisibilityRuntime: {
|
||||||
updateVisibleOverlayVisibility: () => {},
|
updateVisibleOverlayVisibility: () => {},
|
||||||
updateInvisibleOverlayVisibility: () => {},
|
|
||||||
},
|
},
|
||||||
overlayShortcutsRuntime: {
|
overlayShortcutsRuntime: {
|
||||||
syncOverlayShortcuts: () => {},
|
syncOverlayShortcuts: () => {},
|
||||||
},
|
},
|
||||||
getInitialInvisibleOverlayVisibility: () => false,
|
|
||||||
createMainWindow: () => {},
|
createMainWindow: () => {},
|
||||||
createInvisibleWindow: () => {},
|
|
||||||
registerGlobalShortcuts: () => {},
|
registerGlobalShortcuts: () => {},
|
||||||
updateVisibleOverlayBounds: () => {},
|
updateVisibleOverlayBounds: () => {},
|
||||||
updateInvisibleOverlayBounds: () => {},
|
|
||||||
getOverlayWindows: () => [],
|
getOverlayWindows: () => [],
|
||||||
getResolvedConfig: () => ({}),
|
getResolvedConfig: () => ({}),
|
||||||
showDesktopNotification: () => {},
|
showDesktopNotification: () => {},
|
||||||
@@ -52,10 +46,7 @@ test('overlay runtime bootstrap handlers compose options builder and bootstrap h
|
|||||||
},
|
},
|
||||||
initializeOverlayRuntimeBootstrapDeps: {
|
initializeOverlayRuntimeBootstrapDeps: {
|
||||||
isOverlayRuntimeInitialized: () => initialized,
|
isOverlayRuntimeInitialized: () => initialized,
|
||||||
initializeOverlayRuntimeCore: () => ({ invisibleOverlayVisible: true }),
|
initializeOverlayRuntimeCore: () => {},
|
||||||
setInvisibleOverlayVisible: (visible) => {
|
|
||||||
invisibleOverlayVisible = visible;
|
|
||||||
},
|
|
||||||
setOverlayRuntimeInitialized: (next) => {
|
setOverlayRuntimeInitialized: (next) => {
|
||||||
initialized = next;
|
initialized = next;
|
||||||
},
|
},
|
||||||
@@ -68,7 +59,6 @@ test('overlay runtime bootstrap handlers compose options builder and bootstrap h
|
|||||||
initializeOverlayRuntime();
|
initializeOverlayRuntime();
|
||||||
initializeOverlayRuntime();
|
initializeOverlayRuntime();
|
||||||
|
|
||||||
assert.equal(invisibleOverlayVisible, true);
|
|
||||||
assert.equal(initialized, true);
|
assert.equal(initialized, true);
|
||||||
assert.equal(warmupsStarted, 1);
|
assert.equal(warmupsStarted, 1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ test('overlay runtime bootstrap no-ops when already initialized', () => {
|
|||||||
isOverlayRuntimeInitialized: () => true,
|
isOverlayRuntimeInitialized: () => true,
|
||||||
initializeOverlayRuntimeCore: () => {
|
initializeOverlayRuntimeCore: () => {
|
||||||
coreCalls += 1;
|
coreCalls += 1;
|
||||||
return { invisibleOverlayVisible: false };
|
|
||||||
},
|
},
|
||||||
buildOptions: () => ({} as never),
|
buildOptions: () => ({} as never),
|
||||||
setInvisibleOverlayVisible: () => {},
|
|
||||||
setOverlayRuntimeInitialized: () => {},
|
setOverlayRuntimeInitialized: () => {},
|
||||||
startBackgroundWarmups: () => {},
|
startBackgroundWarmups: () => {},
|
||||||
});
|
});
|
||||||
@@ -27,15 +25,11 @@ test('overlay runtime bootstrap runs core init and applies post-init state', ()
|
|||||||
isOverlayRuntimeInitialized: () => initialized,
|
isOverlayRuntimeInitialized: () => initialized,
|
||||||
initializeOverlayRuntimeCore: () => {
|
initializeOverlayRuntimeCore: () => {
|
||||||
calls.push('core');
|
calls.push('core');
|
||||||
return { invisibleOverlayVisible: true };
|
|
||||||
},
|
},
|
||||||
buildOptions: () => {
|
buildOptions: () => {
|
||||||
calls.push('options');
|
calls.push('options');
|
||||||
return {} as never;
|
return {} as never;
|
||||||
},
|
},
|
||||||
setInvisibleOverlayVisible: (visible) => {
|
|
||||||
calls.push(`invisible:${visible ? 'yes' : 'no'}`);
|
|
||||||
},
|
|
||||||
setOverlayRuntimeInitialized: (value) => {
|
setOverlayRuntimeInitialized: (value) => {
|
||||||
initialized = value;
|
initialized = value;
|
||||||
calls.push(`initialized:${value ? 'yes' : 'no'}`);
|
calls.push(`initialized:${value ? 'yes' : 'no'}`);
|
||||||
@@ -47,5 +41,5 @@ test('overlay runtime bootstrap runs core init and applies post-init state', ()
|
|||||||
|
|
||||||
initialize();
|
initialize();
|
||||||
assert.equal(initialized, true);
|
assert.equal(initialized, true);
|
||||||
assert.deepEqual(calls, ['options', 'core', 'invisible:yes', 'initialized:yes', 'warmups']);
|
assert.deepEqual(calls, ['options', 'core', 'initialized:yes', 'warmups']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,16 +9,11 @@ import type {
|
|||||||
|
|
||||||
type InitializeOverlayRuntimeCore = (options: {
|
type InitializeOverlayRuntimeCore = (options: {
|
||||||
backendOverride: string | null;
|
backendOverride: string | null;
|
||||||
getInitialInvisibleOverlayVisibility: () => boolean;
|
|
||||||
createMainWindow: () => void;
|
createMainWindow: () => void;
|
||||||
createInvisibleWindow: () => void;
|
|
||||||
registerGlobalShortcuts: () => void;
|
registerGlobalShortcuts: () => void;
|
||||||
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
||||||
updateInvisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
|
||||||
isVisibleOverlayVisible: () => boolean;
|
isVisibleOverlayVisible: () => boolean;
|
||||||
isInvisibleOverlayVisible: () => boolean;
|
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
getOverlayWindows: () => BrowserWindow[];
|
getOverlayWindows: () => BrowserWindow[];
|
||||||
syncOverlayShortcuts: () => void;
|
syncOverlayShortcuts: () => void;
|
||||||
setWindowTracker: (tracker: BaseWindowTracker | null) => void;
|
setWindowTracker: (tracker: BaseWindowTracker | null) => void;
|
||||||
@@ -35,20 +30,18 @@ type InitializeOverlayRuntimeCore = (options: {
|
|||||||
data: KikuFieldGroupingRequestData,
|
data: KikuFieldGroupingRequestData,
|
||||||
) => Promise<KikuFieldGroupingChoice>;
|
) => Promise<KikuFieldGroupingChoice>;
|
||||||
getKnownWordCacheStatePath: () => string;
|
getKnownWordCacheStatePath: () => string;
|
||||||
}) => { invisibleOverlayVisible: boolean };
|
}) => void;
|
||||||
|
|
||||||
export function createInitializeOverlayRuntimeHandler(deps: {
|
export function createInitializeOverlayRuntimeHandler(deps: {
|
||||||
isOverlayRuntimeInitialized: () => boolean;
|
isOverlayRuntimeInitialized: () => boolean;
|
||||||
initializeOverlayRuntimeCore: InitializeOverlayRuntimeCore;
|
initializeOverlayRuntimeCore: InitializeOverlayRuntimeCore;
|
||||||
buildOptions: () => Parameters<InitializeOverlayRuntimeCore>[0];
|
buildOptions: () => Parameters<InitializeOverlayRuntimeCore>[0];
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => void;
|
|
||||||
setOverlayRuntimeInitialized: (initialized: boolean) => void;
|
setOverlayRuntimeInitialized: (initialized: boolean) => void;
|
||||||
startBackgroundWarmups: () => void;
|
startBackgroundWarmups: () => void;
|
||||||
}) {
|
}) {
|
||||||
return (): void => {
|
return (): void => {
|
||||||
if (deps.isOverlayRuntimeInitialized()) return;
|
if (deps.isOverlayRuntimeInitialized()) return;
|
||||||
const result = deps.initializeOverlayRuntimeCore(deps.buildOptions());
|
deps.initializeOverlayRuntimeCore(deps.buildOptions());
|
||||||
deps.setInvisibleOverlayVisible(result.invisibleOverlayVisible);
|
|
||||||
deps.setOverlayRuntimeInitialized(true);
|
deps.setOverlayRuntimeInitialized(true);
|
||||||
deps.startBackgroundWarmups();
|
deps.startBackgroundWarmups();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,14 +57,12 @@ test('set overlay debug visualization main deps builder maps callbacks', () => {
|
|||||||
setOverlayDebugVisualizationEnabledRuntime: () => calls.push('set-runtime'),
|
setOverlayDebugVisualizationEnabledRuntime: () => calls.push('set-runtime'),
|
||||||
getCurrentEnabled: () => false,
|
getCurrentEnabled: () => false,
|
||||||
setCurrentEnabled: () => calls.push('set-current'),
|
setCurrentEnabled: () => calls.push('set-current'),
|
||||||
broadcastToOverlayWindows: () => calls.push('broadcast'),
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
deps.setOverlayDebugVisualizationEnabledRuntime(false, true, () => {}, () => {});
|
deps.setOverlayDebugVisualizationEnabledRuntime(false, true, () => {});
|
||||||
assert.equal(deps.getCurrentEnabled(), false);
|
assert.equal(deps.getCurrentEnabled(), false);
|
||||||
deps.setCurrentEnabled(true);
|
deps.setCurrentEnabled(true);
|
||||||
deps.broadcastToOverlayWindows('overlay:debug');
|
assert.deepEqual(calls, ['set-runtime', 'set-current']);
|
||||||
assert.deepEqual(calls, ['set-runtime', 'set-current', 'broadcast']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('open runtime options palette main deps builder maps callbacks', () => {
|
test('open runtime options palette main deps builder maps callbacks', () => {
|
||||||
|
|||||||
@@ -65,18 +65,14 @@ export function createBuildSetOverlayDebugVisualizationEnabledMainDepsHandler(
|
|||||||
currentEnabled,
|
currentEnabled,
|
||||||
nextEnabled,
|
nextEnabled,
|
||||||
setCurrentEnabled,
|
setCurrentEnabled,
|
||||||
broadcastToOverlayWindows,
|
|
||||||
) =>
|
) =>
|
||||||
deps.setOverlayDebugVisualizationEnabledRuntime(
|
deps.setOverlayDebugVisualizationEnabledRuntime(
|
||||||
currentEnabled,
|
currentEnabled,
|
||||||
nextEnabled,
|
nextEnabled,
|
||||||
setCurrentEnabled,
|
setCurrentEnabled,
|
||||||
broadcastToOverlayWindows,
|
|
||||||
),
|
),
|
||||||
getCurrentEnabled: () => deps.getCurrentEnabled(),
|
getCurrentEnabled: () => deps.getCurrentEnabled(),
|
||||||
setCurrentEnabled: (enabled: boolean) => deps.setCurrentEnabled(enabled),
|
setCurrentEnabled: (enabled: boolean) => deps.setCurrentEnabled(enabled),
|
||||||
broadcastToOverlayWindows: (channel: string, ...args: unknown[]) =>
|
|
||||||
deps.broadcastToOverlayWindows(channel, ...args),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,22 +104,21 @@ test('set overlay debug visualization enabled delegates with current state and b
|
|||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
let current = false;
|
let current = false;
|
||||||
const setEnabled = createSetOverlayDebugVisualizationEnabledHandler({
|
const setEnabled = createSetOverlayDebugVisualizationEnabledHandler({
|
||||||
setOverlayDebugVisualizationEnabledRuntime: (curr, next, setCurrent, broadcast) => {
|
setOverlayDebugVisualizationEnabledRuntime: (curr, next, setCurrent) => {
|
||||||
calls.push(`runtime:${curr}->${next}`);
|
calls.push(`runtime:${curr}->${next}`);
|
||||||
setCurrent(next);
|
setCurrent(next);
|
||||||
broadcast('overlay-debug:set', next);
|
// no renderer-level side effects for this legacy debug path.
|
||||||
},
|
},
|
||||||
getCurrentEnabled: () => current,
|
getCurrentEnabled: () => current,
|
||||||
setCurrentEnabled: (enabled) => {
|
setCurrentEnabled: (enabled) => {
|
||||||
current = enabled;
|
current = enabled;
|
||||||
calls.push(`set:${enabled}`);
|
calls.push(`set:${enabled}`);
|
||||||
},
|
},
|
||||||
broadcastToOverlayWindows: (channel, value) => calls.push(`emit:${channel}:${value}`),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
assert.equal(current, true);
|
assert.equal(current, true);
|
||||||
assert.deepEqual(calls, ['runtime:false->true', 'set:true', 'emit:overlay-debug:set:true']);
|
assert.deepEqual(calls, ['runtime:false->true', 'set:true']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('open runtime options palette handler delegates to runtime', () => {
|
test('open runtime options palette handler delegates to runtime', () => {
|
||||||
|
|||||||
@@ -65,18 +65,15 @@ export function createSetOverlayDebugVisualizationEnabledHandler(deps: {
|
|||||||
currentEnabled: boolean,
|
currentEnabled: boolean,
|
||||||
nextEnabled: boolean,
|
nextEnabled: boolean,
|
||||||
setCurrentEnabled: (enabled: boolean) => void,
|
setCurrentEnabled: (enabled: boolean) => void,
|
||||||
broadcastToOverlayWindows: (channel: string, ...args: unknown[]) => void,
|
|
||||||
) => void;
|
) => void;
|
||||||
getCurrentEnabled: () => boolean;
|
getCurrentEnabled: () => boolean;
|
||||||
setCurrentEnabled: (enabled: boolean) => void;
|
setCurrentEnabled: (enabled: boolean) => void;
|
||||||
broadcastToOverlayWindows: (channel: string, ...args: unknown[]) => void;
|
|
||||||
}) {
|
}) {
|
||||||
return (enabled: boolean): void => {
|
return (enabled: boolean): void => {
|
||||||
deps.setOverlayDebugVisualizationEnabledRuntime(
|
deps.setOverlayDebugVisualizationEnabledRuntime(
|
||||||
deps.getCurrentEnabled(),
|
deps.getCurrentEnabled(),
|
||||||
enabled,
|
enabled,
|
||||||
(next) => deps.setCurrentEnabled(next),
|
(next) => deps.setCurrentEnabled(next),
|
||||||
(channel, ...args) => deps.broadcastToOverlayWindows(channel, ...args),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,21 +19,16 @@ test('overlay runtime main deps builder maps runtime state and callbacks', () =>
|
|||||||
appState,
|
appState,
|
||||||
overlayManager: {
|
overlayManager: {
|
||||||
getVisibleOverlayVisible: () => true,
|
getVisibleOverlayVisible: () => true,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
},
|
},
|
||||||
overlayVisibilityRuntime: {
|
overlayVisibilityRuntime: {
|
||||||
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
|
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
|
||||||
updateInvisibleOverlayVisibility: () => calls.push('update-invisible'),
|
|
||||||
},
|
},
|
||||||
overlayShortcutsRuntime: {
|
overlayShortcutsRuntime: {
|
||||||
syncOverlayShortcuts: () => calls.push('sync-shortcuts'),
|
syncOverlayShortcuts: () => calls.push('sync-shortcuts'),
|
||||||
},
|
},
|
||||||
getInitialInvisibleOverlayVisibility: () => true,
|
|
||||||
createMainWindow: () => calls.push('create-main'),
|
createMainWindow: () => calls.push('create-main'),
|
||||||
createInvisibleWindow: () => calls.push('create-invisible'),
|
|
||||||
registerGlobalShortcuts: () => calls.push('register-shortcuts'),
|
registerGlobalShortcuts: () => calls.push('register-shortcuts'),
|
||||||
updateVisibleOverlayBounds: () => calls.push('visible-bounds'),
|
updateVisibleOverlayBounds: () => calls.push('visible-bounds'),
|
||||||
updateInvisibleOverlayBounds: () => calls.push('invisible-bounds'),
|
|
||||||
getOverlayWindows: () => [],
|
getOverlayWindows: () => [],
|
||||||
getResolvedConfig: () => ({}),
|
getResolvedConfig: () => ({}),
|
||||||
showDesktopNotification: () => calls.push('notify'),
|
showDesktopNotification: () => calls.push('notify'),
|
||||||
@@ -48,19 +43,14 @@ test('overlay runtime main deps builder maps runtime state and callbacks', () =>
|
|||||||
|
|
||||||
const deps = build();
|
const deps = build();
|
||||||
assert.equal(deps.getBackendOverride(), 'x11');
|
assert.equal(deps.getBackendOverride(), 'x11');
|
||||||
assert.equal(deps.getInitialInvisibleOverlayVisibility(), true);
|
|
||||||
assert.equal(deps.isVisibleOverlayVisible(), true);
|
assert.equal(deps.isVisibleOverlayVisible(), true);
|
||||||
assert.equal(deps.isInvisibleOverlayVisible(), false);
|
|
||||||
assert.equal(deps.getMpvSocketPath(), '/tmp/mpv.sock');
|
assert.equal(deps.getMpvSocketPath(), '/tmp/mpv.sock');
|
||||||
assert.equal(deps.getKnownWordCacheStatePath(), '/tmp/known-words-cache.json');
|
assert.equal(deps.getKnownWordCacheStatePath(), '/tmp/known-words-cache.json');
|
||||||
|
|
||||||
deps.createMainWindow();
|
deps.createMainWindow();
|
||||||
deps.createInvisibleWindow();
|
|
||||||
deps.registerGlobalShortcuts();
|
deps.registerGlobalShortcuts();
|
||||||
deps.updateVisibleOverlayBounds({ x: 0, y: 0, width: 10, height: 10 });
|
deps.updateVisibleOverlayBounds({ x: 0, y: 0, width: 10, height: 10 });
|
||||||
deps.updateInvisibleOverlayBounds({ x: 0, y: 0, width: 10, height: 10 });
|
|
||||||
deps.updateVisibleOverlayVisibility();
|
deps.updateVisibleOverlayVisibility();
|
||||||
deps.updateInvisibleOverlayVisibility();
|
|
||||||
deps.syncOverlayShortcuts();
|
deps.syncOverlayShortcuts();
|
||||||
deps.showDesktopNotification('title', {});
|
deps.showDesktopNotification('title', {});
|
||||||
|
|
||||||
@@ -73,12 +63,9 @@ test('overlay runtime main deps builder maps runtime state and callbacks', () =>
|
|||||||
|
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, [
|
||||||
'create-main',
|
'create-main',
|
||||||
'create-invisible',
|
|
||||||
'register-shortcuts',
|
'register-shortcuts',
|
||||||
'visible-bounds',
|
'visible-bounds',
|
||||||
'invisible-bounds',
|
|
||||||
'update-visible',
|
'update-visible',
|
||||||
'update-invisible',
|
|
||||||
'sync-shortcuts',
|
'sync-shortcuts',
|
||||||
'notify',
|
'notify',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -17,18 +17,14 @@ export function createBuildInitializeOverlayRuntimeMainDepsHandler(deps: {
|
|||||||
};
|
};
|
||||||
overlayManager: {
|
overlayManager: {
|
||||||
getVisibleOverlayVisible: () => boolean;
|
getVisibleOverlayVisible: () => boolean;
|
||||||
getInvisibleOverlayVisible: () => boolean;
|
|
||||||
};
|
};
|
||||||
overlayVisibilityRuntime: {
|
overlayVisibilityRuntime: {
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
};
|
};
|
||||||
overlayShortcutsRuntime: {
|
overlayShortcutsRuntime: {
|
||||||
syncOverlayShortcuts: () => void;
|
syncOverlayShortcuts: () => void;
|
||||||
};
|
};
|
||||||
getInitialInvisibleOverlayVisibility: () => boolean;
|
|
||||||
createMainWindow: () => void;
|
createMainWindow: () => void;
|
||||||
createInvisibleWindow: () => void;
|
|
||||||
registerGlobalShortcuts: () => void;
|
registerGlobalShortcuts: () => void;
|
||||||
updateVisibleOverlayBounds: (geometry: {
|
updateVisibleOverlayBounds: (geometry: {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -36,12 +32,6 @@ export function createBuildInitializeOverlayRuntimeMainDepsHandler(deps: {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}) => void;
|
}) => void;
|
||||||
updateInvisibleOverlayBounds: (geometry: {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}) => void;
|
|
||||||
getOverlayWindows: OverlayRuntimeOptionsMainDeps['getOverlayWindows'];
|
getOverlayWindows: OverlayRuntimeOptionsMainDeps['getOverlayWindows'];
|
||||||
getResolvedConfig: () => { ankiConnect?: AnkiConnectConfig };
|
getResolvedConfig: () => { ankiConnect?: AnkiConnectConfig };
|
||||||
showDesktopNotification: (title: string, options: { body?: string; icon?: string }) => void;
|
showDesktopNotification: (title: string, options: { body?: string; icon?: string }) => void;
|
||||||
@@ -50,9 +40,7 @@ export function createBuildInitializeOverlayRuntimeMainDepsHandler(deps: {
|
|||||||
}) {
|
}) {
|
||||||
return (): OverlayRuntimeOptionsMainDeps => ({
|
return (): OverlayRuntimeOptionsMainDeps => ({
|
||||||
getBackendOverride: () => deps.appState.backendOverride,
|
getBackendOverride: () => deps.appState.backendOverride,
|
||||||
getInitialInvisibleOverlayVisibility: () => deps.getInitialInvisibleOverlayVisibility(),
|
|
||||||
createMainWindow: () => deps.createMainWindow(),
|
createMainWindow: () => deps.createMainWindow(),
|
||||||
createInvisibleWindow: () => deps.createInvisibleWindow(),
|
|
||||||
registerGlobalShortcuts: () => deps.registerGlobalShortcuts(),
|
registerGlobalShortcuts: () => deps.registerGlobalShortcuts(),
|
||||||
updateVisibleOverlayBounds: (geometry: {
|
updateVisibleOverlayBounds: (geometry: {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -60,18 +48,9 @@ export function createBuildInitializeOverlayRuntimeMainDepsHandler(deps: {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}) => deps.updateVisibleOverlayBounds(geometry),
|
}) => deps.updateVisibleOverlayBounds(geometry),
|
||||||
updateInvisibleOverlayBounds: (geometry: {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}) => deps.updateInvisibleOverlayBounds(geometry),
|
|
||||||
isVisibleOverlayVisible: () => deps.overlayManager.getVisibleOverlayVisible(),
|
isVisibleOverlayVisible: () => deps.overlayManager.getVisibleOverlayVisible(),
|
||||||
isInvisibleOverlayVisible: () => deps.overlayManager.getInvisibleOverlayVisible(),
|
|
||||||
updateVisibleOverlayVisibility: () =>
|
updateVisibleOverlayVisibility: () =>
|
||||||
deps.overlayVisibilityRuntime.updateVisibleOverlayVisibility(),
|
deps.overlayVisibilityRuntime.updateVisibleOverlayVisibility(),
|
||||||
updateInvisibleOverlayVisibility: () =>
|
|
||||||
deps.overlayVisibilityRuntime.updateInvisibleOverlayVisibility(),
|
|
||||||
getOverlayWindows: () => deps.getOverlayWindows(),
|
getOverlayWindows: () => deps.getOverlayWindows(),
|
||||||
syncOverlayShortcuts: () => deps.overlayShortcutsRuntime.syncOverlayShortcuts(),
|
syncOverlayShortcuts: () => deps.overlayShortcutsRuntime.syncOverlayShortcuts(),
|
||||||
setWindowTracker: (tracker) => {
|
setWindowTracker: (tracker) => {
|
||||||
|
|||||||
@@ -6,16 +6,11 @@ test('build initialize overlay runtime options maps dependencies', () => {
|
|||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
const buildOptions = createBuildInitializeOverlayRuntimeOptionsHandler({
|
const buildOptions = createBuildInitializeOverlayRuntimeOptionsHandler({
|
||||||
getBackendOverride: () => 'x11',
|
getBackendOverride: () => 'x11',
|
||||||
getInitialInvisibleOverlayVisibility: () => true,
|
|
||||||
createMainWindow: () => calls.push('create-main'),
|
createMainWindow: () => calls.push('create-main'),
|
||||||
createInvisibleWindow: () => calls.push('create-invisible'),
|
|
||||||
registerGlobalShortcuts: () => calls.push('register-shortcuts'),
|
registerGlobalShortcuts: () => calls.push('register-shortcuts'),
|
||||||
updateVisibleOverlayBounds: () => calls.push('update-visible-bounds'),
|
updateVisibleOverlayBounds: () => calls.push('update-visible-bounds'),
|
||||||
updateInvisibleOverlayBounds: () => calls.push('update-invisible-bounds'),
|
|
||||||
isVisibleOverlayVisible: () => true,
|
isVisibleOverlayVisible: () => true,
|
||||||
isInvisibleOverlayVisible: () => false,
|
|
||||||
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
|
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
|
||||||
updateInvisibleOverlayVisibility: () => calls.push('update-invisible'),
|
|
||||||
getOverlayWindows: () => [],
|
getOverlayWindows: () => [],
|
||||||
syncOverlayShortcuts: () => calls.push('sync-shortcuts'),
|
syncOverlayShortcuts: () => calls.push('sync-shortcuts'),
|
||||||
setWindowTracker: () => calls.push('set-tracker'),
|
setWindowTracker: () => calls.push('set-tracker'),
|
||||||
@@ -37,18 +32,13 @@ test('build initialize overlay runtime options maps dependencies', () => {
|
|||||||
|
|
||||||
const options = buildOptions();
|
const options = buildOptions();
|
||||||
assert.equal(options.backendOverride, 'x11');
|
assert.equal(options.backendOverride, 'x11');
|
||||||
assert.equal(options.getInitialInvisibleOverlayVisibility(), true);
|
|
||||||
assert.equal(options.isVisibleOverlayVisible(), true);
|
assert.equal(options.isVisibleOverlayVisible(), true);
|
||||||
assert.equal(options.isInvisibleOverlayVisible(), false);
|
|
||||||
assert.equal(options.getMpvSocketPath(), '/tmp/mpv.sock');
|
assert.equal(options.getMpvSocketPath(), '/tmp/mpv.sock');
|
||||||
assert.equal(options.getKnownWordCacheStatePath(), '/tmp/known-words-cache.json');
|
assert.equal(options.getKnownWordCacheStatePath(), '/tmp/known-words-cache.json');
|
||||||
options.createMainWindow();
|
options.createMainWindow();
|
||||||
options.createInvisibleWindow();
|
|
||||||
options.registerGlobalShortcuts();
|
options.registerGlobalShortcuts();
|
||||||
options.updateVisibleOverlayBounds({ x: 0, y: 0, width: 10, height: 10 });
|
options.updateVisibleOverlayBounds({ x: 0, y: 0, width: 10, height: 10 });
|
||||||
options.updateInvisibleOverlayBounds({ x: 0, y: 0, width: 10, height: 10 });
|
|
||||||
options.updateVisibleOverlayVisibility();
|
options.updateVisibleOverlayVisibility();
|
||||||
options.updateInvisibleOverlayVisibility();
|
|
||||||
options.syncOverlayShortcuts();
|
options.syncOverlayShortcuts();
|
||||||
options.setWindowTracker(null);
|
options.setWindowTracker(null);
|
||||||
options.setAnkiIntegration(null);
|
options.setAnkiIntegration(null);
|
||||||
@@ -56,12 +46,9 @@ test('build initialize overlay runtime options maps dependencies', () => {
|
|||||||
|
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, [
|
||||||
'create-main',
|
'create-main',
|
||||||
'create-invisible',
|
|
||||||
'register-shortcuts',
|
'register-shortcuts',
|
||||||
'update-visible-bounds',
|
'update-visible-bounds',
|
||||||
'update-invisible-bounds',
|
|
||||||
'update-visible',
|
'update-visible',
|
||||||
'update-invisible',
|
|
||||||
'sync-shortcuts',
|
'sync-shortcuts',
|
||||||
'set-tracker',
|
'set-tracker',
|
||||||
'set-anki',
|
'set-anki',
|
||||||
|
|||||||
@@ -9,16 +9,11 @@ import type { BaseWindowTracker } from '../../window-trackers';
|
|||||||
|
|
||||||
type OverlayRuntimeOptions = {
|
type OverlayRuntimeOptions = {
|
||||||
backendOverride: string | null;
|
backendOverride: string | null;
|
||||||
getInitialInvisibleOverlayVisibility: () => boolean;
|
|
||||||
createMainWindow: () => void;
|
createMainWindow: () => void;
|
||||||
createInvisibleWindow: () => void;
|
|
||||||
registerGlobalShortcuts: () => void;
|
registerGlobalShortcuts: () => void;
|
||||||
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
||||||
updateInvisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
|
||||||
isVisibleOverlayVisible: () => boolean;
|
isVisibleOverlayVisible: () => boolean;
|
||||||
isInvisibleOverlayVisible: () => boolean;
|
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
getOverlayWindows: () => BrowserWindow[];
|
getOverlayWindows: () => BrowserWindow[];
|
||||||
syncOverlayShortcuts: () => void;
|
syncOverlayShortcuts: () => void;
|
||||||
setWindowTracker: (tracker: BaseWindowTracker | null) => void;
|
setWindowTracker: (tracker: BaseWindowTracker | null) => void;
|
||||||
@@ -39,16 +34,11 @@ type OverlayRuntimeOptions = {
|
|||||||
|
|
||||||
export function createBuildInitializeOverlayRuntimeOptionsHandler(deps: {
|
export function createBuildInitializeOverlayRuntimeOptionsHandler(deps: {
|
||||||
getBackendOverride: () => string | null;
|
getBackendOverride: () => string | null;
|
||||||
getInitialInvisibleOverlayVisibility: () => boolean;
|
|
||||||
createMainWindow: () => void;
|
createMainWindow: () => void;
|
||||||
createInvisibleWindow: () => void;
|
|
||||||
registerGlobalShortcuts: () => void;
|
registerGlobalShortcuts: () => void;
|
||||||
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
||||||
updateInvisibleOverlayBounds: (geometry: WindowGeometry) => void;
|
|
||||||
isVisibleOverlayVisible: () => boolean;
|
isVisibleOverlayVisible: () => boolean;
|
||||||
isInvisibleOverlayVisible: () => boolean;
|
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
getOverlayWindows: () => BrowserWindow[];
|
getOverlayWindows: () => BrowserWindow[];
|
||||||
syncOverlayShortcuts: () => void;
|
syncOverlayShortcuts: () => void;
|
||||||
setWindowTracker: (tracker: BaseWindowTracker | null) => void;
|
setWindowTracker: (tracker: BaseWindowTracker | null) => void;
|
||||||
@@ -68,16 +58,11 @@ export function createBuildInitializeOverlayRuntimeOptionsHandler(deps: {
|
|||||||
}) {
|
}) {
|
||||||
return (): OverlayRuntimeOptions => ({
|
return (): OverlayRuntimeOptions => ({
|
||||||
backendOverride: deps.getBackendOverride(),
|
backendOverride: deps.getBackendOverride(),
|
||||||
getInitialInvisibleOverlayVisibility: deps.getInitialInvisibleOverlayVisibility,
|
|
||||||
createMainWindow: deps.createMainWindow,
|
createMainWindow: deps.createMainWindow,
|
||||||
createInvisibleWindow: deps.createInvisibleWindow,
|
|
||||||
registerGlobalShortcuts: deps.registerGlobalShortcuts,
|
registerGlobalShortcuts: deps.registerGlobalShortcuts,
|
||||||
updateVisibleOverlayBounds: deps.updateVisibleOverlayBounds,
|
updateVisibleOverlayBounds: deps.updateVisibleOverlayBounds,
|
||||||
updateInvisibleOverlayBounds: deps.updateInvisibleOverlayBounds,
|
|
||||||
isVisibleOverlayVisible: deps.isVisibleOverlayVisible,
|
isVisibleOverlayVisible: deps.isVisibleOverlayVisible,
|
||||||
isInvisibleOverlayVisible: deps.isInvisibleOverlayVisible,
|
|
||||||
updateVisibleOverlayVisibility: deps.updateVisibleOverlayVisibility,
|
updateVisibleOverlayVisibility: deps.updateVisibleOverlayVisibility,
|
||||||
updateInvisibleOverlayVisibility: deps.updateInvisibleOverlayVisibility,
|
|
||||||
getOverlayWindows: deps.getOverlayWindows,
|
getOverlayWindows: deps.getOverlayWindows,
|
||||||
syncOverlayShortcuts: deps.syncOverlayShortcuts,
|
syncOverlayShortcuts: deps.syncOverlayShortcuts,
|
||||||
setWindowTracker: deps.setWindowTracker,
|
setWindowTracker: deps.setWindowTracker,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import {
|
import {
|
||||||
createBuildSetInvisibleOverlayVisibleMainDepsHandler,
|
|
||||||
createBuildSetVisibleOverlayVisibleMainDepsHandler,
|
createBuildSetVisibleOverlayVisibleMainDepsHandler,
|
||||||
createBuildToggleInvisibleOverlayMainDepsHandler,
|
|
||||||
createBuildToggleVisibleOverlayMainDepsHandler,
|
createBuildToggleVisibleOverlayMainDepsHandler,
|
||||||
} from './overlay-visibility-actions-main-deps';
|
} from './overlay-visibility-actions-main-deps';
|
||||||
|
|
||||||
@@ -14,45 +12,14 @@ test('overlay visibility action main deps builders map callbacks', () => {
|
|||||||
setVisibleOverlayVisibleCore: () => calls.push('visible-core'),
|
setVisibleOverlayVisibleCore: () => calls.push('visible-core'),
|
||||||
setVisibleOverlayVisibleState: (visible) => calls.push(`visible-state:${visible}`),
|
setVisibleOverlayVisibleState: (visible) => calls.push(`visible-state:${visible}`),
|
||||||
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
|
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
|
||||||
updateInvisibleOverlayVisibility: () => calls.push('update-invisible'),
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => calls.push('sync'),
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
|
|
||||||
isMpvConnected: () => true,
|
|
||||||
setMpvSubVisibility: (visible) => calls.push(`mpv:${visible}`),
|
|
||||||
})();
|
})();
|
||||||
setVisible.setVisibleOverlayVisibleCore({
|
setVisible.setVisibleOverlayVisibleCore({
|
||||||
visible: true,
|
visible: true,
|
||||||
setVisibleOverlayVisibleState: () => {},
|
setVisibleOverlayVisibleState: () => {},
|
||||||
updateVisibleOverlayVisibility: () => {},
|
updateVisibleOverlayVisibility: () => {},
|
||||||
updateInvisibleOverlayVisibility: () => {},
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => {},
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
|
|
||||||
isMpvConnected: () => true,
|
|
||||||
setMpvSubVisibility: () => {},
|
|
||||||
});
|
});
|
||||||
setVisible.setVisibleOverlayVisibleState(true);
|
setVisible.setVisibleOverlayVisibleState(true);
|
||||||
setVisible.updateVisibleOverlayVisibility();
|
setVisible.updateVisibleOverlayVisibility();
|
||||||
setVisible.updateInvisibleOverlayVisibility();
|
|
||||||
setVisible.syncInvisibleOverlayMousePassthrough();
|
|
||||||
assert.equal(setVisible.shouldBindVisibleOverlayToMpvSubVisibility(), true);
|
|
||||||
assert.equal(setVisible.isMpvConnected(), true);
|
|
||||||
setVisible.setMpvSubVisibility(false);
|
|
||||||
|
|
||||||
const setInvisible = createBuildSetInvisibleOverlayVisibleMainDepsHandler({
|
|
||||||
setInvisibleOverlayVisibleCore: () => calls.push('invisible-core'),
|
|
||||||
setInvisibleOverlayVisibleState: (visible) => calls.push(`invisible-state:${visible}`),
|
|
||||||
updateInvisibleOverlayVisibility: () => calls.push('update-only-invisible'),
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => calls.push('sync-only'),
|
|
||||||
})();
|
|
||||||
setInvisible.setInvisibleOverlayVisibleCore({
|
|
||||||
visible: false,
|
|
||||||
setInvisibleOverlayVisibleState: () => {},
|
|
||||||
updateInvisibleOverlayVisibility: () => {},
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => {},
|
|
||||||
});
|
|
||||||
setInvisible.setInvisibleOverlayVisibleState(false);
|
|
||||||
setInvisible.updateInvisibleOverlayVisibility();
|
|
||||||
setInvisible.syncInvisibleOverlayMousePassthrough();
|
|
||||||
|
|
||||||
const toggleVisible = createBuildToggleVisibleOverlayMainDepsHandler({
|
const toggleVisible = createBuildToggleVisibleOverlayMainDepsHandler({
|
||||||
getVisibleOverlayVisible: () => false,
|
getVisibleOverlayVisible: () => false,
|
||||||
@@ -61,25 +28,10 @@ test('overlay visibility action main deps builders map callbacks', () => {
|
|||||||
assert.equal(toggleVisible.getVisibleOverlayVisible(), false);
|
assert.equal(toggleVisible.getVisibleOverlayVisible(), false);
|
||||||
toggleVisible.setVisibleOverlayVisible(true);
|
toggleVisible.setVisibleOverlayVisible(true);
|
||||||
|
|
||||||
const toggleInvisible = createBuildToggleInvisibleOverlayMainDepsHandler({
|
|
||||||
getInvisibleOverlayVisible: () => true,
|
|
||||||
setInvisibleOverlayVisible: (visible) => calls.push(`toggle-invisible:${visible}`),
|
|
||||||
})();
|
|
||||||
assert.equal(toggleInvisible.getInvisibleOverlayVisible(), true);
|
|
||||||
toggleInvisible.setInvisibleOverlayVisible(false);
|
|
||||||
|
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, [
|
||||||
'visible-core',
|
'visible-core',
|
||||||
'visible-state:true',
|
'visible-state:true',
|
||||||
'update-visible',
|
'update-visible',
|
||||||
'update-invisible',
|
|
||||||
'sync',
|
|
||||||
'mpv:false',
|
|
||||||
'invisible-core',
|
|
||||||
'invisible-state:false',
|
|
||||||
'update-only-invisible',
|
|
||||||
'sync-only',
|
|
||||||
'toggle-visible:true',
|
'toggle-visible:true',
|
||||||
'toggle-invisible:false',
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import type {
|
import type {
|
||||||
createSetInvisibleOverlayVisibleHandler,
|
|
||||||
createSetVisibleOverlayVisibleHandler,
|
createSetVisibleOverlayVisibleHandler,
|
||||||
createToggleInvisibleOverlayHandler,
|
|
||||||
createToggleVisibleOverlayHandler,
|
createToggleVisibleOverlayHandler,
|
||||||
} from './overlay-visibility-actions';
|
} from './overlay-visibility-actions';
|
||||||
|
|
||||||
type SetVisibleOverlayVisibleMainDeps = Parameters<typeof createSetVisibleOverlayVisibleHandler>[0];
|
type SetVisibleOverlayVisibleMainDeps = Parameters<typeof createSetVisibleOverlayVisibleHandler>[0];
|
||||||
type SetInvisibleOverlayVisibleMainDeps = Parameters<typeof createSetInvisibleOverlayVisibleHandler>[0];
|
|
||||||
type ToggleVisibleOverlayMainDeps = Parameters<typeof createToggleVisibleOverlayHandler>[0];
|
type ToggleVisibleOverlayMainDeps = Parameters<typeof createToggleVisibleOverlayHandler>[0];
|
||||||
type ToggleInvisibleOverlayMainDeps = Parameters<typeof createToggleInvisibleOverlayHandler>[0];
|
|
||||||
|
|
||||||
export function createBuildSetVisibleOverlayVisibleMainDepsHandler(
|
export function createBuildSetVisibleOverlayVisibleMainDepsHandler(
|
||||||
deps: SetVisibleOverlayVisibleMainDeps,
|
deps: SetVisibleOverlayVisibleMainDeps,
|
||||||
@@ -17,22 +13,6 @@ export function createBuildSetVisibleOverlayVisibleMainDepsHandler(
|
|||||||
setVisibleOverlayVisibleCore: (options) => deps.setVisibleOverlayVisibleCore(options),
|
setVisibleOverlayVisibleCore: (options) => deps.setVisibleOverlayVisibleCore(options),
|
||||||
setVisibleOverlayVisibleState: (visible: boolean) => deps.setVisibleOverlayVisibleState(visible),
|
setVisibleOverlayVisibleState: (visible: boolean) => deps.setVisibleOverlayVisibleState(visible),
|
||||||
updateVisibleOverlayVisibility: () => deps.updateVisibleOverlayVisibility(),
|
updateVisibleOverlayVisibility: () => deps.updateVisibleOverlayVisibility(),
|
||||||
updateInvisibleOverlayVisibility: () => deps.updateInvisibleOverlayVisibility(),
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => deps.syncInvisibleOverlayMousePassthrough(),
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => deps.shouldBindVisibleOverlayToMpvSubVisibility(),
|
|
||||||
isMpvConnected: () => deps.isMpvConnected(),
|
|
||||||
setMpvSubVisibility: (visible: boolean) => deps.setMpvSubVisibility(visible),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createBuildSetInvisibleOverlayVisibleMainDepsHandler(
|
|
||||||
deps: SetInvisibleOverlayVisibleMainDeps,
|
|
||||||
) {
|
|
||||||
return (): SetInvisibleOverlayVisibleMainDeps => ({
|
|
||||||
setInvisibleOverlayVisibleCore: (options) => deps.setInvisibleOverlayVisibleCore(options),
|
|
||||||
setInvisibleOverlayVisibleState: (visible: boolean) => deps.setInvisibleOverlayVisibleState(visible),
|
|
||||||
updateInvisibleOverlayVisibility: () => deps.updateInvisibleOverlayVisibility(),
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => deps.syncInvisibleOverlayMousePassthrough(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +22,3 @@ export function createBuildToggleVisibleOverlayMainDepsHandler(deps: ToggleVisib
|
|||||||
setVisibleOverlayVisible: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
setVisibleOverlayVisible: (visible: boolean) => deps.setVisibleOverlayVisible(visible),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBuildToggleInvisibleOverlayMainDepsHandler(
|
|
||||||
deps: ToggleInvisibleOverlayMainDeps,
|
|
||||||
) {
|
|
||||||
return (): ToggleInvisibleOverlayMainDeps => ({
|
|
||||||
getInvisibleOverlayVisible: () => deps.getInvisibleOverlayVisible(),
|
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => deps.setInvisibleOverlayVisible(visible),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import {
|
import {
|
||||||
createSetInvisibleOverlayVisibleHandler,
|
|
||||||
createSetVisibleOverlayVisibleHandler,
|
createSetVisibleOverlayVisibleHandler,
|
||||||
createToggleInvisibleOverlayHandler,
|
|
||||||
createToggleVisibleOverlayHandler,
|
createToggleVisibleOverlayHandler,
|
||||||
} from './overlay-visibility-actions';
|
} from './overlay-visibility-actions';
|
||||||
|
|
||||||
@@ -14,17 +12,9 @@ test('set visible overlay handler forwards dependencies to core', () => {
|
|||||||
calls.push(`core:${options.visible}`);
|
calls.push(`core:${options.visible}`);
|
||||||
options.setVisibleOverlayVisibleState(options.visible);
|
options.setVisibleOverlayVisibleState(options.visible);
|
||||||
options.updateVisibleOverlayVisibility();
|
options.updateVisibleOverlayVisibility();
|
||||||
options.updateInvisibleOverlayVisibility();
|
|
||||||
options.syncInvisibleOverlayMousePassthrough();
|
|
||||||
options.setMpvSubVisibility(!options.visible);
|
|
||||||
},
|
},
|
||||||
setVisibleOverlayVisibleState: (visible) => calls.push(`state:${visible}`),
|
setVisibleOverlayVisibleState: (visible) => calls.push(`state:${visible}`),
|
||||||
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
|
updateVisibleOverlayVisibility: () => calls.push('update-visible'),
|
||||||
updateInvisibleOverlayVisibility: () => calls.push('update-invisible'),
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => calls.push('sync-mouse'),
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
|
|
||||||
isMpvConnected: () => true,
|
|
||||||
setMpvSubVisibility: (visible) => calls.push(`mpv-sub:${visible}`),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
@@ -32,30 +22,9 @@ test('set visible overlay handler forwards dependencies to core', () => {
|
|||||||
'core:true',
|
'core:true',
|
||||||
'state:true',
|
'state:true',
|
||||||
'update-visible',
|
'update-visible',
|
||||||
'update-invisible',
|
|
||||||
'sync-mouse',
|
|
||||||
'mpv-sub:false',
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('set invisible overlay handler forwards dependencies to core', () => {
|
|
||||||
const calls: string[] = [];
|
|
||||||
const setInvisible = createSetInvisibleOverlayVisibleHandler({
|
|
||||||
setInvisibleOverlayVisibleCore: (options) => {
|
|
||||||
calls.push(`core:${options.visible}`);
|
|
||||||
options.setInvisibleOverlayVisibleState(options.visible);
|
|
||||||
options.updateInvisibleOverlayVisibility();
|
|
||||||
options.syncInvisibleOverlayMousePassthrough();
|
|
||||||
},
|
|
||||||
setInvisibleOverlayVisibleState: (visible) => calls.push(`state:${visible}`),
|
|
||||||
updateInvisibleOverlayVisibility: () => calls.push('update-invisible'),
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => calls.push('sync-mouse'),
|
|
||||||
});
|
|
||||||
|
|
||||||
setInvisible(false);
|
|
||||||
assert.deepEqual(calls, ['core:false', 'state:false', 'update-invisible', 'sync-mouse']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('toggle visible overlay flips current visible state', () => {
|
test('toggle visible overlay flips current visible state', () => {
|
||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
let current = false;
|
let current = false;
|
||||||
@@ -71,19 +40,3 @@ test('toggle visible overlay flips current visible state', () => {
|
|||||||
toggle();
|
toggle();
|
||||||
assert.deepEqual(calls, ['set:true', 'set:false']);
|
assert.deepEqual(calls, ['set:true', 'set:false']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('toggle invisible overlay flips current invisible state', () => {
|
|
||||||
const calls: string[] = [];
|
|
||||||
let current = true;
|
|
||||||
const toggle = createToggleInvisibleOverlayHandler({
|
|
||||||
getInvisibleOverlayVisible: () => current,
|
|
||||||
setInvisibleOverlayVisible: (visible) => {
|
|
||||||
current = visible;
|
|
||||||
calls.push(`set:${visible}`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
toggle();
|
|
||||||
toggle();
|
|
||||||
assert.deepEqual(calls, ['set:false', 'set:true']);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -3,52 +3,15 @@ export function createSetVisibleOverlayVisibleHandler(deps: {
|
|||||||
visible: boolean;
|
visible: boolean;
|
||||||
setVisibleOverlayVisibleState: (visible: boolean) => void;
|
setVisibleOverlayVisibleState: (visible: boolean) => void;
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => void;
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => boolean;
|
|
||||||
isMpvConnected: () => boolean;
|
|
||||||
setMpvSubVisibility: (visible: boolean) => void;
|
|
||||||
}) => void;
|
}) => void;
|
||||||
setVisibleOverlayVisibleState: (visible: boolean) => void;
|
setVisibleOverlayVisibleState: (visible: boolean) => void;
|
||||||
updateVisibleOverlayVisibility: () => void;
|
updateVisibleOverlayVisibility: () => void;
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => void;
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => boolean;
|
|
||||||
isMpvConnected: () => boolean;
|
|
||||||
setMpvSubVisibility: (visible: boolean) => void;
|
|
||||||
}) {
|
}) {
|
||||||
return (visible: boolean): void => {
|
return (visible: boolean): void => {
|
||||||
deps.setVisibleOverlayVisibleCore({
|
deps.setVisibleOverlayVisibleCore({
|
||||||
visible,
|
visible,
|
||||||
setVisibleOverlayVisibleState: deps.setVisibleOverlayVisibleState,
|
setVisibleOverlayVisibleState: deps.setVisibleOverlayVisibleState,
|
||||||
updateVisibleOverlayVisibility: deps.updateVisibleOverlayVisibility,
|
updateVisibleOverlayVisibility: deps.updateVisibleOverlayVisibility,
|
||||||
updateInvisibleOverlayVisibility: deps.updateInvisibleOverlayVisibility,
|
|
||||||
syncInvisibleOverlayMousePassthrough: deps.syncInvisibleOverlayMousePassthrough,
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility:
|
|
||||||
deps.shouldBindVisibleOverlayToMpvSubVisibility,
|
|
||||||
isMpvConnected: deps.isMpvConnected,
|
|
||||||
setMpvSubVisibility: deps.setMpvSubVisibility,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createSetInvisibleOverlayVisibleHandler(deps: {
|
|
||||||
setInvisibleOverlayVisibleCore: (options: {
|
|
||||||
visible: boolean;
|
|
||||||
setInvisibleOverlayVisibleState: (visible: boolean) => void;
|
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => void;
|
|
||||||
}) => void;
|
|
||||||
setInvisibleOverlayVisibleState: (visible: boolean) => void;
|
|
||||||
updateInvisibleOverlayVisibility: () => void;
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => void;
|
|
||||||
}) {
|
|
||||||
return (visible: boolean): void => {
|
|
||||||
deps.setInvisibleOverlayVisibleCore({
|
|
||||||
visible,
|
|
||||||
setInvisibleOverlayVisibleState: deps.setInvisibleOverlayVisibleState,
|
|
||||||
updateInvisibleOverlayVisibility: deps.updateInvisibleOverlayVisibility,
|
|
||||||
syncInvisibleOverlayMousePassthrough: deps.syncInvisibleOverlayMousePassthrough,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -61,12 +24,3 @@ export function createToggleVisibleOverlayHandler(deps: {
|
|||||||
deps.setVisibleOverlayVisible(!deps.getVisibleOverlayVisible());
|
deps.setVisibleOverlayVisible(!deps.getVisibleOverlayVisible());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createToggleInvisibleOverlayHandler(deps: {
|
|
||||||
getInvisibleOverlayVisible: () => boolean;
|
|
||||||
setInvisibleOverlayVisible: (visible: boolean) => void;
|
|
||||||
}) {
|
|
||||||
return (): void => {
|
|
||||||
deps.setInvisibleOverlayVisible(!deps.getInvisibleOverlayVisible());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,14 +8,11 @@ test('overlay visibility runtime main deps builder maps state and geometry callb
|
|||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
let trackerNotReadyWarningShown = false;
|
let trackerNotReadyWarningShown = false;
|
||||||
const mainWindow = { id: 'main' } as never;
|
const mainWindow = { id: 'main' } as never;
|
||||||
const invisibleWindow = { id: 'invisible' } as never;
|
|
||||||
const tracker = { id: 'tracker' } as unknown as BaseWindowTracker;
|
const tracker = { id: 'tracker' } as unknown as BaseWindowTracker;
|
||||||
|
|
||||||
const deps = createBuildOverlayVisibilityRuntimeMainDepsHandler({
|
const deps = createBuildOverlayVisibilityRuntimeMainDepsHandler({
|
||||||
getMainWindow: () => mainWindow,
|
getMainWindow: () => mainWindow,
|
||||||
getInvisibleWindow: () => invisibleWindow,
|
|
||||||
getVisibleOverlayVisible: () => true,
|
getVisibleOverlayVisible: () => true,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
getWindowTracker: () => tracker,
|
getWindowTracker: () => tracker,
|
||||||
getTrackerNotReadyWarningShown: () => trackerNotReadyWarningShown,
|
getTrackerNotReadyWarningShown: () => trackerNotReadyWarningShown,
|
||||||
setTrackerNotReadyWarningShown: (shown) => {
|
setTrackerNotReadyWarningShown: (shown) => {
|
||||||
@@ -23,30 +20,35 @@ test('overlay visibility runtime main deps builder maps state and geometry callb
|
|||||||
calls.push(`tracker-warning:${shown}`);
|
calls.push(`tracker-warning:${shown}`);
|
||||||
},
|
},
|
||||||
updateVisibleOverlayBounds: () => calls.push('visible-bounds'),
|
updateVisibleOverlayBounds: () => calls.push('visible-bounds'),
|
||||||
updateInvisibleOverlayBounds: () => calls.push('invisible-bounds'),
|
|
||||||
ensureOverlayWindowLevel: () => calls.push('ensure-level'),
|
ensureOverlayWindowLevel: () => calls.push('ensure-level'),
|
||||||
|
syncPrimaryOverlayWindowLayer: (layer) => calls.push(`primary-layer:${layer}`),
|
||||||
enforceOverlayLayerOrder: () => calls.push('enforce-order'),
|
enforceOverlayLayerOrder: () => calls.push('enforce-order'),
|
||||||
syncOverlayShortcuts: () => calls.push('sync-shortcuts'),
|
syncOverlayShortcuts: () => calls.push('sync-shortcuts'),
|
||||||
|
isMacOSPlatform: () => true,
|
||||||
|
showOverlayLoadingOsd: () => calls.push('overlay-loading-osd'),
|
||||||
|
resolveFallbackBounds: () => ({ x: 0, y: 0, width: 20, height: 20 }),
|
||||||
})();
|
})();
|
||||||
|
|
||||||
assert.equal(deps.getMainWindow(), mainWindow);
|
assert.equal(deps.getMainWindow(), mainWindow);
|
||||||
assert.equal(deps.getInvisibleWindow(), invisibleWindow);
|
|
||||||
assert.equal(deps.getVisibleOverlayVisible(), true);
|
assert.equal(deps.getVisibleOverlayVisible(), true);
|
||||||
assert.equal(deps.getInvisibleOverlayVisible(), false);
|
|
||||||
assert.equal(deps.getTrackerNotReadyWarningShown(), false);
|
assert.equal(deps.getTrackerNotReadyWarningShown(), false);
|
||||||
deps.setTrackerNotReadyWarningShown(true);
|
deps.setTrackerNotReadyWarningShown(true);
|
||||||
deps.updateVisibleOverlayBounds({ x: 0, y: 0, width: 10, height: 10 });
|
deps.updateVisibleOverlayBounds({ x: 0, y: 0, width: 10, height: 10 });
|
||||||
deps.updateInvisibleOverlayBounds({ x: 0, y: 0, width: 10, height: 10 });
|
|
||||||
deps.ensureOverlayWindowLevel(mainWindow);
|
deps.ensureOverlayWindowLevel(mainWindow);
|
||||||
|
deps.syncPrimaryOverlayWindowLayer('visible');
|
||||||
deps.enforceOverlayLayerOrder();
|
deps.enforceOverlayLayerOrder();
|
||||||
deps.syncOverlayShortcuts();
|
deps.syncOverlayShortcuts();
|
||||||
|
assert.equal(deps.isMacOSPlatform(), true);
|
||||||
|
deps.showOverlayLoadingOsd('Overlay loading...');
|
||||||
|
assert.deepEqual(deps.resolveFallbackBounds(), { x: 0, y: 0, width: 20, height: 20 });
|
||||||
assert.equal(trackerNotReadyWarningShown, true);
|
assert.equal(trackerNotReadyWarningShown, true);
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, [
|
||||||
'tracker-warning:true',
|
'tracker-warning:true',
|
||||||
'visible-bounds',
|
'visible-bounds',
|
||||||
'invisible-bounds',
|
|
||||||
'ensure-level',
|
'ensure-level',
|
||||||
|
'primary-layer:visible',
|
||||||
'enforce-order',
|
'enforce-order',
|
||||||
'sync-shortcuts',
|
'sync-shortcuts',
|
||||||
|
'overlay-loading-osd',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,18 +7,19 @@ export function createBuildOverlayVisibilityRuntimeMainDepsHandler(
|
|||||||
) {
|
) {
|
||||||
return (): OverlayVisibilityRuntimeDeps => ({
|
return (): OverlayVisibilityRuntimeDeps => ({
|
||||||
getMainWindow: () => deps.getMainWindow(),
|
getMainWindow: () => deps.getMainWindow(),
|
||||||
getInvisibleWindow: () => deps.getInvisibleWindow(),
|
|
||||||
getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(),
|
getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(),
|
||||||
getInvisibleOverlayVisible: () => deps.getInvisibleOverlayVisible(),
|
|
||||||
getWindowTracker: () => deps.getWindowTracker(),
|
getWindowTracker: () => deps.getWindowTracker(),
|
||||||
getTrackerNotReadyWarningShown: () => deps.getTrackerNotReadyWarningShown(),
|
getTrackerNotReadyWarningShown: () => deps.getTrackerNotReadyWarningShown(),
|
||||||
setTrackerNotReadyWarningShown: (shown: boolean) => deps.setTrackerNotReadyWarningShown(shown),
|
setTrackerNotReadyWarningShown: (shown: boolean) => deps.setTrackerNotReadyWarningShown(shown),
|
||||||
updateVisibleOverlayBounds: (geometry: WindowGeometry) =>
|
updateVisibleOverlayBounds: (geometry: WindowGeometry) =>
|
||||||
deps.updateVisibleOverlayBounds(geometry),
|
deps.updateVisibleOverlayBounds(geometry),
|
||||||
updateInvisibleOverlayBounds: (geometry: WindowGeometry) =>
|
|
||||||
deps.updateInvisibleOverlayBounds(geometry),
|
|
||||||
ensureOverlayWindowLevel: (window: BrowserWindow) => deps.ensureOverlayWindowLevel(window),
|
ensureOverlayWindowLevel: (window: BrowserWindow) => deps.ensureOverlayWindowLevel(window),
|
||||||
|
syncPrimaryOverlayWindowLayer: (layer: 'visible') =>
|
||||||
|
deps.syncPrimaryOverlayWindowLayer(layer),
|
||||||
enforceOverlayLayerOrder: () => deps.enforceOverlayLayerOrder(),
|
enforceOverlayLayerOrder: () => deps.enforceOverlayLayerOrder(),
|
||||||
syncOverlayShortcuts: () => deps.syncOverlayShortcuts(),
|
syncOverlayShortcuts: () => deps.syncOverlayShortcuts(),
|
||||||
|
isMacOSPlatform: () => deps.isMacOSPlatform(),
|
||||||
|
showOverlayLoadingOsd: (message: string) => deps.showOverlayLoadingOsd(message),
|
||||||
|
resolveFallbackBounds: () => deps.resolveFallbackBounds(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import { createOverlayVisibilityRuntime } from './overlay-visibility-runtime';
|
|||||||
|
|
||||||
test('overlay visibility runtime wires set/toggle handlers through composed deps', () => {
|
test('overlay visibility runtime wires set/toggle handlers through composed deps', () => {
|
||||||
let visible = false;
|
let visible = false;
|
||||||
let invisible = true;
|
|
||||||
let setVisibleCoreCalls = 0;
|
let setVisibleCoreCalls = 0;
|
||||||
let setInvisibleCoreCalls = 0;
|
|
||||||
let lastBoundSubVisibility: boolean | null = null;
|
|
||||||
|
|
||||||
const runtime = createOverlayVisibilityRuntime({
|
const runtime = createOverlayVisibilityRuntime({
|
||||||
setVisibleOverlayVisibleDeps: {
|
setVisibleOverlayVisibleDeps: {
|
||||||
@@ -15,44 +12,17 @@ test('overlay visibility runtime wires set/toggle handlers through composed deps
|
|||||||
setVisibleCoreCalls += 1;
|
setVisibleCoreCalls += 1;
|
||||||
options.setVisibleOverlayVisibleState(options.visible);
|
options.setVisibleOverlayVisibleState(options.visible);
|
||||||
options.updateVisibleOverlayVisibility();
|
options.updateVisibleOverlayVisibility();
|
||||||
options.updateInvisibleOverlayVisibility();
|
|
||||||
options.syncInvisibleOverlayMousePassthrough();
|
|
||||||
if (options.shouldBindVisibleOverlayToMpvSubVisibility() && options.isMpvConnected()) {
|
|
||||||
options.setMpvSubVisibility(options.visible);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setVisibleOverlayVisibleState: (nextVisible) => {
|
setVisibleOverlayVisibleState: (nextVisible) => {
|
||||||
visible = nextVisible;
|
visible = nextVisible;
|
||||||
},
|
},
|
||||||
updateVisibleOverlayVisibility: () => {},
|
updateVisibleOverlayVisibility: () => {},
|
||||||
updateInvisibleOverlayVisibility: () => {},
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => {},
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
|
|
||||||
isMpvConnected: () => true,
|
|
||||||
setMpvSubVisibility: (nextVisible) => {
|
|
||||||
lastBoundSubVisibility = nextVisible;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setInvisibleOverlayVisibleDeps: {
|
|
||||||
setInvisibleOverlayVisibleCore: (options) => {
|
|
||||||
setInvisibleCoreCalls += 1;
|
|
||||||
options.setInvisibleOverlayVisibleState(options.visible);
|
|
||||||
options.updateInvisibleOverlayVisibility();
|
|
||||||
options.syncInvisibleOverlayMousePassthrough();
|
|
||||||
},
|
|
||||||
setInvisibleOverlayVisibleState: (nextVisible) => {
|
|
||||||
invisible = nextVisible;
|
|
||||||
},
|
|
||||||
updateInvisibleOverlayVisibility: () => {},
|
|
||||||
syncInvisibleOverlayMousePassthrough: () => {},
|
|
||||||
},
|
},
|
||||||
getVisibleOverlayVisible: () => visible,
|
getVisibleOverlayVisible: () => visible,
|
||||||
getInvisibleOverlayVisible: () => invisible,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
runtime.setVisibleOverlayVisible(true);
|
runtime.setVisibleOverlayVisible(true);
|
||||||
assert.equal(visible, true);
|
assert.equal(visible, true);
|
||||||
assert.equal(lastBoundSubVisibility, true);
|
|
||||||
|
|
||||||
runtime.toggleVisibleOverlay();
|
runtime.toggleVisibleOverlay();
|
||||||
assert.equal(visible, false);
|
assert.equal(visible, false);
|
||||||
@@ -63,12 +33,5 @@ test('overlay visibility runtime wires set/toggle handlers through composed deps
|
|||||||
runtime.toggleOverlay();
|
runtime.toggleOverlay();
|
||||||
assert.equal(visible, false);
|
assert.equal(visible, false);
|
||||||
|
|
||||||
runtime.setInvisibleOverlayVisible(false);
|
|
||||||
assert.equal(invisible, false);
|
|
||||||
|
|
||||||
runtime.toggleInvisibleOverlay();
|
|
||||||
assert.equal(invisible, true);
|
|
||||||
|
|
||||||
assert.equal(setVisibleCoreCalls, 4);
|
assert.equal(setVisibleCoreCalls, 4);
|
||||||
assert.equal(setInvisibleCoreCalls, 2);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
createSetInvisibleOverlayVisibleHandler,
|
|
||||||
createSetVisibleOverlayVisibleHandler,
|
createSetVisibleOverlayVisibleHandler,
|
||||||
createToggleInvisibleOverlayHandler,
|
|
||||||
createToggleVisibleOverlayHandler,
|
createToggleVisibleOverlayHandler,
|
||||||
} from './overlay-visibility-actions';
|
} from './overlay-visibility-actions';
|
||||||
import {
|
import {
|
||||||
createBuildSetInvisibleOverlayVisibleMainDepsHandler,
|
|
||||||
createBuildSetVisibleOverlayVisibleMainDepsHandler,
|
createBuildSetVisibleOverlayVisibleMainDepsHandler,
|
||||||
createBuildToggleInvisibleOverlayMainDepsHandler,
|
|
||||||
createBuildToggleVisibleOverlayMainDepsHandler,
|
createBuildToggleVisibleOverlayMainDepsHandler,
|
||||||
} from './overlay-visibility-actions-main-deps';
|
} from './overlay-visibility-actions-main-deps';
|
||||||
import { createSetOverlayVisibleHandler, createToggleOverlayHandler } from './overlay-main-actions';
|
import { createSetOverlayVisibleHandler, createToggleOverlayHandler } from './overlay-main-actions';
|
||||||
@@ -19,15 +15,10 @@ import {
|
|||||||
type SetVisibleOverlayVisibleMainDeps = Parameters<
|
type SetVisibleOverlayVisibleMainDeps = Parameters<
|
||||||
typeof createBuildSetVisibleOverlayVisibleMainDepsHandler
|
typeof createBuildSetVisibleOverlayVisibleMainDepsHandler
|
||||||
>[0];
|
>[0];
|
||||||
type SetInvisibleOverlayVisibleMainDeps = Parameters<
|
|
||||||
typeof createBuildSetInvisibleOverlayVisibleMainDepsHandler
|
|
||||||
>[0];
|
|
||||||
|
|
||||||
export type OverlayVisibilityRuntimeDeps = {
|
export type OverlayVisibilityRuntimeDeps = {
|
||||||
setVisibleOverlayVisibleDeps: SetVisibleOverlayVisibleMainDeps;
|
setVisibleOverlayVisibleDeps: SetVisibleOverlayVisibleMainDeps;
|
||||||
setInvisibleOverlayVisibleDeps: SetInvisibleOverlayVisibleMainDeps;
|
|
||||||
getVisibleOverlayVisible: () => boolean;
|
getVisibleOverlayVisible: () => boolean;
|
||||||
getInvisibleOverlayVisible: () => boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createOverlayVisibilityRuntime(deps: OverlayVisibilityRuntimeDeps) {
|
export function createOverlayVisibilityRuntime(deps: OverlayVisibilityRuntimeDeps) {
|
||||||
@@ -38,25 +29,12 @@ export function createOverlayVisibilityRuntime(deps: OverlayVisibilityRuntimeDep
|
|||||||
setVisibleOverlayVisibleMainDeps,
|
setVisibleOverlayVisibleMainDeps,
|
||||||
);
|
);
|
||||||
|
|
||||||
const setInvisibleOverlayVisibleMainDeps = createBuildSetInvisibleOverlayVisibleMainDepsHandler(
|
|
||||||
deps.setInvisibleOverlayVisibleDeps,
|
|
||||||
)();
|
|
||||||
const setInvisibleOverlayVisible = createSetInvisibleOverlayVisibleHandler(
|
|
||||||
setInvisibleOverlayVisibleMainDeps,
|
|
||||||
);
|
|
||||||
|
|
||||||
const toggleVisibleOverlayMainDeps = createBuildToggleVisibleOverlayMainDepsHandler({
|
const toggleVisibleOverlayMainDeps = createBuildToggleVisibleOverlayMainDepsHandler({
|
||||||
getVisibleOverlayVisible: deps.getVisibleOverlayVisible,
|
getVisibleOverlayVisible: deps.getVisibleOverlayVisible,
|
||||||
setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible),
|
setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible),
|
||||||
})();
|
})();
|
||||||
const toggleVisibleOverlay = createToggleVisibleOverlayHandler(toggleVisibleOverlayMainDeps);
|
const toggleVisibleOverlay = createToggleVisibleOverlayHandler(toggleVisibleOverlayMainDeps);
|
||||||
|
|
||||||
const toggleInvisibleOverlayMainDeps = createBuildToggleInvisibleOverlayMainDepsHandler({
|
|
||||||
getInvisibleOverlayVisible: deps.getInvisibleOverlayVisible,
|
|
||||||
setInvisibleOverlayVisible: (visible) => setInvisibleOverlayVisible(visible),
|
|
||||||
})();
|
|
||||||
const toggleInvisibleOverlay = createToggleInvisibleOverlayHandler(toggleInvisibleOverlayMainDeps);
|
|
||||||
|
|
||||||
const setOverlayVisibleMainDeps = createBuildSetOverlayVisibleMainDepsHandler({
|
const setOverlayVisibleMainDeps = createBuildSetOverlayVisibleMainDepsHandler({
|
||||||
setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible),
|
setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible),
|
||||||
})();
|
})();
|
||||||
@@ -69,9 +47,7 @@ export function createOverlayVisibilityRuntime(deps: OverlayVisibilityRuntimeDep
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
setVisibleOverlayVisible,
|
setVisibleOverlayVisible,
|
||||||
setInvisibleOverlayVisible,
|
|
||||||
toggleVisibleOverlay,
|
toggleVisibleOverlay,
|
||||||
toggleInvisibleOverlay,
|
|
||||||
setOverlayVisible,
|
setOverlayVisible,
|
||||||
toggleOverlay,
|
toggleOverlay,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import test from 'node:test';
|
|||||||
import {
|
import {
|
||||||
createBuildEnforceOverlayLayerOrderMainDepsHandler,
|
createBuildEnforceOverlayLayerOrderMainDepsHandler,
|
||||||
createBuildEnsureOverlayWindowLevelMainDepsHandler,
|
createBuildEnsureOverlayWindowLevelMainDepsHandler,
|
||||||
createBuildUpdateInvisibleOverlayBoundsMainDepsHandler,
|
|
||||||
createBuildUpdateVisibleOverlayBoundsMainDepsHandler,
|
createBuildUpdateVisibleOverlayBoundsMainDepsHandler,
|
||||||
} from './overlay-window-layout-main-deps';
|
} from './overlay-window-layout-main-deps';
|
||||||
|
|
||||||
@@ -11,14 +10,9 @@ test('overlay window layout main deps builders map callbacks', () => {
|
|||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
|
|
||||||
const visible = createBuildUpdateVisibleOverlayBoundsMainDepsHandler({
|
const visible = createBuildUpdateVisibleOverlayBoundsMainDepsHandler({
|
||||||
setOverlayWindowBounds: (layer) => calls.push(`visible:${layer}`),
|
setOverlayWindowBounds: () => calls.push('visible'),
|
||||||
})();
|
})();
|
||||||
visible.setOverlayWindowBounds('visible', { x: 0, y: 0, width: 1, height: 1 });
|
visible.setOverlayWindowBounds({ x: 0, y: 0, width: 1, height: 1 });
|
||||||
|
|
||||||
const invisible = createBuildUpdateInvisibleOverlayBoundsMainDepsHandler({
|
|
||||||
setOverlayWindowBounds: (layer) => calls.push(`invisible:${layer}`),
|
|
||||||
})();
|
|
||||||
invisible.setOverlayWindowBounds('invisible', { x: 0, y: 0, width: 1, height: 1 });
|
|
||||||
|
|
||||||
const level = createBuildEnsureOverlayWindowLevelMainDepsHandler({
|
const level = createBuildEnsureOverlayWindowLevelMainDepsHandler({
|
||||||
ensureOverlayWindowLevelCore: () => calls.push('ensure'),
|
ensureOverlayWindowLevelCore: () => calls.push('ensure'),
|
||||||
@@ -28,27 +22,20 @@ test('overlay window layout main deps builders map callbacks', () => {
|
|||||||
const order = createBuildEnforceOverlayLayerOrderMainDepsHandler({
|
const order = createBuildEnforceOverlayLayerOrderMainDepsHandler({
|
||||||
enforceOverlayLayerOrderCore: () => calls.push('order'),
|
enforceOverlayLayerOrderCore: () => calls.push('order'),
|
||||||
getVisibleOverlayVisible: () => true,
|
getVisibleOverlayVisible: () => true,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
getMainWindow: () => ({ kind: 'main' }),
|
getMainWindow: () => ({ kind: 'main' }),
|
||||||
getInvisibleWindow: () => ({ kind: 'invisible' }),
|
|
||||||
ensureOverlayWindowLevel: () => calls.push('order-level'),
|
ensureOverlayWindowLevel: () => calls.push('order-level'),
|
||||||
})();
|
})();
|
||||||
order.enforceOverlayLayerOrderCore({
|
order.enforceOverlayLayerOrderCore({
|
||||||
visibleOverlayVisible: true,
|
visibleOverlayVisible: true,
|
||||||
invisibleOverlayVisible: false,
|
|
||||||
mainWindow: null,
|
mainWindow: null,
|
||||||
invisibleWindow: null,
|
|
||||||
ensureOverlayWindowLevel: () => {},
|
ensureOverlayWindowLevel: () => {},
|
||||||
});
|
});
|
||||||
assert.equal(order.getVisibleOverlayVisible(), true);
|
assert.equal(order.getVisibleOverlayVisible(), true);
|
||||||
assert.equal(order.getInvisibleOverlayVisible(), false);
|
|
||||||
assert.deepEqual(order.getMainWindow(), { kind: 'main' });
|
assert.deepEqual(order.getMainWindow(), { kind: 'main' });
|
||||||
assert.deepEqual(order.getInvisibleWindow(), { kind: 'invisible' });
|
|
||||||
order.ensureOverlayWindowLevel({});
|
order.ensureOverlayWindowLevel({});
|
||||||
|
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, [
|
||||||
'visible:visible',
|
'visible',
|
||||||
'invisible:invisible',
|
|
||||||
'ensure',
|
'ensure',
|
||||||
'order',
|
'order',
|
||||||
'order-level',
|
'order-level',
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import type {
|
import type {
|
||||||
createEnforceOverlayLayerOrderHandler,
|
createEnforceOverlayLayerOrderHandler,
|
||||||
createEnsureOverlayWindowLevelHandler,
|
createEnsureOverlayWindowLevelHandler,
|
||||||
createUpdateInvisibleOverlayBoundsHandler,
|
|
||||||
createUpdateVisibleOverlayBoundsHandler,
|
createUpdateVisibleOverlayBoundsHandler,
|
||||||
} from './overlay-window-layout';
|
} from './overlay-window-layout';
|
||||||
|
|
||||||
type UpdateVisibleOverlayBoundsMainDeps = Parameters<typeof createUpdateVisibleOverlayBoundsHandler>[0];
|
type UpdateVisibleOverlayBoundsMainDeps = Parameters<typeof createUpdateVisibleOverlayBoundsHandler>[0];
|
||||||
type UpdateInvisibleOverlayBoundsMainDeps = Parameters<typeof createUpdateInvisibleOverlayBoundsHandler>[0];
|
|
||||||
type EnsureOverlayWindowLevelMainDeps = Parameters<typeof createEnsureOverlayWindowLevelHandler>[0];
|
type EnsureOverlayWindowLevelMainDeps = Parameters<typeof createEnsureOverlayWindowLevelHandler>[0];
|
||||||
type EnforceOverlayLayerOrderMainDeps = Parameters<typeof createEnforceOverlayLayerOrderHandler>[0];
|
type EnforceOverlayLayerOrderMainDeps = Parameters<typeof createEnforceOverlayLayerOrderHandler>[0];
|
||||||
|
|
||||||
@@ -14,15 +12,7 @@ export function createBuildUpdateVisibleOverlayBoundsMainDepsHandler(
|
|||||||
deps: UpdateVisibleOverlayBoundsMainDeps,
|
deps: UpdateVisibleOverlayBoundsMainDeps,
|
||||||
) {
|
) {
|
||||||
return (): UpdateVisibleOverlayBoundsMainDeps => ({
|
return (): UpdateVisibleOverlayBoundsMainDeps => ({
|
||||||
setOverlayWindowBounds: (layer, geometry) => deps.setOverlayWindowBounds(layer, geometry),
|
setOverlayWindowBounds: (geometry) => deps.setOverlayWindowBounds(geometry),
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createBuildUpdateInvisibleOverlayBoundsMainDepsHandler(
|
|
||||||
deps: UpdateInvisibleOverlayBoundsMainDeps,
|
|
||||||
) {
|
|
||||||
return (): UpdateInvisibleOverlayBoundsMainDeps => ({
|
|
||||||
setOverlayWindowBounds: (layer, geometry) => deps.setOverlayWindowBounds(layer, geometry),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,9 +30,7 @@ export function createBuildEnforceOverlayLayerOrderMainDepsHandler(
|
|||||||
return (): EnforceOverlayLayerOrderMainDeps => ({
|
return (): EnforceOverlayLayerOrderMainDeps => ({
|
||||||
enforceOverlayLayerOrderCore: (params) => deps.enforceOverlayLayerOrderCore(params),
|
enforceOverlayLayerOrderCore: (params) => deps.enforceOverlayLayerOrderCore(params),
|
||||||
getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(),
|
getVisibleOverlayVisible: () => deps.getVisibleOverlayVisible(),
|
||||||
getInvisibleOverlayVisible: () => deps.getInvisibleOverlayVisible(),
|
|
||||||
getMainWindow: () => deps.getMainWindow(),
|
getMainWindow: () => deps.getMainWindow(),
|
||||||
getInvisibleWindow: () => deps.getInvisibleWindow(),
|
|
||||||
ensureOverlayWindowLevel: (window: unknown) => deps.ensureOverlayWindowLevel(window),
|
ensureOverlayWindowLevel: (window: unknown) => deps.ensureOverlayWindowLevel(window),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,17 @@ import assert from 'node:assert/strict';
|
|||||||
import {
|
import {
|
||||||
createEnforceOverlayLayerOrderHandler,
|
createEnforceOverlayLayerOrderHandler,
|
||||||
createEnsureOverlayWindowLevelHandler,
|
createEnsureOverlayWindowLevelHandler,
|
||||||
createUpdateInvisibleOverlayBoundsHandler,
|
|
||||||
createUpdateVisibleOverlayBoundsHandler,
|
createUpdateVisibleOverlayBoundsHandler,
|
||||||
} from './overlay-window-layout';
|
} from './overlay-window-layout';
|
||||||
|
|
||||||
test('visible bounds handler writes visible layer geometry', () => {
|
test('visible bounds handler writes visible layer geometry', () => {
|
||||||
const calls: string[] = [];
|
const calls: Array<{ x: number; y: number; width: number; height: number }> = [];
|
||||||
const handleVisible = createUpdateVisibleOverlayBoundsHandler({
|
const handleVisible = createUpdateVisibleOverlayBoundsHandler({
|
||||||
setOverlayWindowBounds: (layer) => calls.push(layer),
|
setOverlayWindowBounds: (geometry) => calls.push(geometry),
|
||||||
});
|
});
|
||||||
handleVisible({ x: 0, y: 0, width: 100, height: 50 });
|
const geometry = { x: 0, y: 0, width: 100, height: 50 };
|
||||||
assert.deepEqual(calls, ['visible']);
|
handleVisible(geometry);
|
||||||
});
|
assert.deepEqual(calls, [geometry]);
|
||||||
|
|
||||||
test('invisible bounds handler writes invisible layer geometry', () => {
|
|
||||||
const calls: string[] = [];
|
|
||||||
const handleInvisible = createUpdateInvisibleOverlayBoundsHandler({
|
|
||||||
setOverlayWindowBounds: (layer) => calls.push(layer),
|
|
||||||
});
|
|
||||||
handleInvisible({ x: 0, y: 0, width: 100, height: 50 });
|
|
||||||
assert.deepEqual(calls, ['invisible']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ensure overlay window level handler delegates to core', () => {
|
test('ensure overlay window level handler delegates to core', () => {
|
||||||
@@ -39,15 +30,12 @@ test('enforce overlay layer order handler forwards resolved state', () => {
|
|||||||
const enforce = createEnforceOverlayLayerOrderHandler({
|
const enforce = createEnforceOverlayLayerOrderHandler({
|
||||||
enforceOverlayLayerOrderCore: (params) => {
|
enforceOverlayLayerOrderCore: (params) => {
|
||||||
calls.push(params.visibleOverlayVisible ? 'visible-on' : 'visible-off');
|
calls.push(params.visibleOverlayVisible ? 'visible-on' : 'visible-off');
|
||||||
calls.push(params.invisibleOverlayVisible ? 'invisible-on' : 'invisible-off');
|
|
||||||
params.ensureOverlayWindowLevel({});
|
params.ensureOverlayWindowLevel({});
|
||||||
},
|
},
|
||||||
getVisibleOverlayVisible: () => true,
|
getVisibleOverlayVisible: () => true,
|
||||||
getInvisibleOverlayVisible: () => false,
|
|
||||||
getMainWindow: () => ({}),
|
getMainWindow: () => ({}),
|
||||||
getInvisibleWindow: () => ({}),
|
|
||||||
ensureOverlayWindowLevel: () => calls.push('ensure-level'),
|
ensureOverlayWindowLevel: () => calls.push('ensure-level'),
|
||||||
});
|
});
|
||||||
enforce();
|
enforce();
|
||||||
assert.deepEqual(calls, ['visible-on', 'invisible-off', 'ensure-level']);
|
assert.deepEqual(calls, ['visible-on', 'ensure-level']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import type { WindowGeometry } from '../../types';
|
import type { WindowGeometry } from '../../types';
|
||||||
|
|
||||||
export function createUpdateVisibleOverlayBoundsHandler(deps: {
|
export function createUpdateVisibleOverlayBoundsHandler(deps: {
|
||||||
setOverlayWindowBounds: (layer: 'visible' | 'invisible', geometry: WindowGeometry) => void;
|
setOverlayWindowBounds: (geometry: WindowGeometry) => void;
|
||||||
}) {
|
}) {
|
||||||
return (geometry: WindowGeometry): void => {
|
return (geometry: WindowGeometry): void => {
|
||||||
deps.setOverlayWindowBounds('visible', geometry);
|
deps.setOverlayWindowBounds(geometry);
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createUpdateInvisibleOverlayBoundsHandler(deps: {
|
|
||||||
setOverlayWindowBounds: (layer: 'visible' | 'invisible', geometry: WindowGeometry) => void;
|
|
||||||
}) {
|
|
||||||
return (geometry: WindowGeometry): void => {
|
|
||||||
deps.setOverlayWindowBounds('invisible', geometry);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,23 +19,17 @@ export function createEnsureOverlayWindowLevelHandler(deps: {
|
|||||||
export function createEnforceOverlayLayerOrderHandler(deps: {
|
export function createEnforceOverlayLayerOrderHandler(deps: {
|
||||||
enforceOverlayLayerOrderCore: (params: {
|
enforceOverlayLayerOrderCore: (params: {
|
||||||
visibleOverlayVisible: boolean;
|
visibleOverlayVisible: boolean;
|
||||||
invisibleOverlayVisible: boolean;
|
|
||||||
mainWindow: unknown;
|
mainWindow: unknown;
|
||||||
invisibleWindow: unknown;
|
|
||||||
ensureOverlayWindowLevel: (window: unknown) => void;
|
ensureOverlayWindowLevel: (window: unknown) => void;
|
||||||
}) => void;
|
}) => void;
|
||||||
getVisibleOverlayVisible: () => boolean;
|
getVisibleOverlayVisible: () => boolean;
|
||||||
getInvisibleOverlayVisible: () => boolean;
|
|
||||||
getMainWindow: () => unknown;
|
getMainWindow: () => unknown;
|
||||||
getInvisibleWindow: () => unknown;
|
|
||||||
ensureOverlayWindowLevel: (window: unknown) => void;
|
ensureOverlayWindowLevel: (window: unknown) => void;
|
||||||
}) {
|
}) {
|
||||||
return (): void => {
|
return (): void => {
|
||||||
deps.enforceOverlayLayerOrderCore({
|
deps.enforceOverlayLayerOrderCore({
|
||||||
visibleOverlayVisible: deps.getVisibleOverlayVisible(),
|
visibleOverlayVisible: deps.getVisibleOverlayVisible(),
|
||||||
invisibleOverlayVisible: deps.getInvisibleOverlayVisible(),
|
|
||||||
mainWindow: deps.getMainWindow(),
|
mainWindow: deps.getMainWindow(),
|
||||||
invisibleWindow: deps.getInvisibleWindow(),
|
|
||||||
ensureOverlayWindowLevel: deps.ensureOverlayWindowLevel,
|
ensureOverlayWindowLevel: deps.ensureOverlayWindowLevel,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ test('config derived runtime main deps builder maps callbacks', () => {
|
|||||||
const deps = createBuildConfigDerivedRuntimeMainDepsHandler({
|
const deps = createBuildConfigDerivedRuntimeMainDepsHandler({
|
||||||
getResolvedConfig: () => ({ jimaku: {} } as never),
|
getResolvedConfig: () => ({ jimaku: {} } as never),
|
||||||
getRuntimeOptionsManager: () => null,
|
getRuntimeOptionsManager: () => null,
|
||||||
platform: 'darwin',
|
|
||||||
defaultJimakuLanguagePreference: 'ja',
|
defaultJimakuLanguagePreference: 'ja',
|
||||||
defaultJimakuMaxEntryResults: 20,
|
defaultJimakuMaxEntryResults: 20,
|
||||||
defaultJimakuApiBaseUrl: 'https://api.example.com',
|
defaultJimakuApiBaseUrl: 'https://api.example.com',
|
||||||
@@ -72,7 +71,6 @@ test('config derived runtime main deps builder maps callbacks', () => {
|
|||||||
|
|
||||||
assert.deepEqual(deps.getResolvedConfig(), { jimaku: {} });
|
assert.deepEqual(deps.getResolvedConfig(), { jimaku: {} });
|
||||||
assert.equal(deps.getRuntimeOptionsManager(), null);
|
assert.equal(deps.getRuntimeOptionsManager(), null);
|
||||||
assert.equal(deps.platform, 'darwin');
|
|
||||||
assert.equal(deps.defaultJimakuLanguagePreference, 'ja');
|
assert.equal(deps.defaultJimakuLanguagePreference, 'ja');
|
||||||
assert.equal(deps.defaultJimakuMaxEntryResults, 20);
|
assert.equal(deps.defaultJimakuMaxEntryResults, 20);
|
||||||
assert.equal(deps.defaultJimakuApiBaseUrl, 'https://api.example.com');
|
assert.equal(deps.defaultJimakuApiBaseUrl, 'https://api.example.com');
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export function createBuildConfigDerivedRuntimeMainDepsHandler(deps: ConfigDeriv
|
|||||||
return (): ConfigDerivedRuntimeDeps => ({
|
return (): ConfigDerivedRuntimeDeps => ({
|
||||||
getResolvedConfig: () => deps.getResolvedConfig(),
|
getResolvedConfig: () => deps.getResolvedConfig(),
|
||||||
getRuntimeOptionsManager: () => deps.getRuntimeOptionsManager(),
|
getRuntimeOptionsManager: () => deps.getRuntimeOptionsManager(),
|
||||||
platform: deps.platform,
|
|
||||||
defaultJimakuLanguagePreference: deps.defaultJimakuLanguagePreference,
|
defaultJimakuLanguagePreference: deps.defaultJimakuLanguagePreference,
|
||||||
defaultJimakuMaxEntryResults: deps.defaultJimakuMaxEntryResults,
|
defaultJimakuMaxEntryResults: deps.defaultJimakuMaxEntryResults,
|
||||||
defaultJimakuApiBaseUrl: deps.defaultJimakuApiBaseUrl,
|
defaultJimakuApiBaseUrl: deps.defaultJimakuApiBaseUrl,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Keybinding } from '../../types';
|
import type { Keybinding } from '../../types';
|
||||||
import type { RendererContext } from '../context';
|
import type { RendererContext } from '../context';
|
||||||
|
import { hasYomitanPopupIframe, isYomitanPopupIframe } from '../yomitan-popup.js';
|
||||||
|
|
||||||
export function createKeyboardHandlers(
|
export function createKeyboardHandlers(
|
||||||
ctx: RendererContext,
|
ctx: RendererContext,
|
||||||
@@ -14,11 +15,6 @@ export function createKeyboardHandlers(
|
|||||||
fallbackUsed: boolean;
|
fallbackUsed: boolean;
|
||||||
fallbackUnavailable: boolean;
|
fallbackUnavailable: boolean;
|
||||||
}) => void;
|
}) => void;
|
||||||
saveInvisiblePositionEdit: () => void;
|
|
||||||
cancelInvisiblePositionEdit: () => void;
|
|
||||||
setInvisiblePositionEditMode: (enabled: boolean) => void;
|
|
||||||
applyInvisibleSubtitleOffsetPosition: () => void;
|
|
||||||
updateInvisiblePositionEditHud: () => void;
|
|
||||||
appendClipboardVideoToQueue: () => void;
|
appendClipboardVideoToQueue: () => void;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@@ -32,9 +28,6 @@ export function createKeyboardHandlers(
|
|||||||
['KeyS', { type: 'mpv', command: ['script-message', 'subminer-start'] }],
|
['KeyS', { type: 'mpv', command: ['script-message', 'subminer-start'] }],
|
||||||
['Shift+KeyS', { type: 'mpv', command: ['script-message', 'subminer-stop'] }],
|
['Shift+KeyS', { type: 'mpv', command: ['script-message', 'subminer-stop'] }],
|
||||||
['KeyT', { type: 'mpv', command: ['script-message', 'subminer-toggle'] }],
|
['KeyT', { type: 'mpv', command: ['script-message', 'subminer-toggle'] }],
|
||||||
['KeyI', { type: 'mpv', command: ['script-message', 'subminer-toggle-invisible'] }],
|
|
||||||
['Shift+KeyI', { type: 'mpv', command: ['script-message', 'subminer-show-invisible'] }],
|
|
||||||
['KeyU', { type: 'mpv', command: ['script-message', 'subminer-hide-invisible'] }],
|
|
||||||
['KeyO', { type: 'mpv', command: ['script-message', 'subminer-options'] }],
|
['KeyO', { type: 'mpv', command: ['script-message', 'subminer-options'] }],
|
||||||
['KeyR', { type: 'mpv', command: ['script-message', 'subminer-restart'] }],
|
['KeyR', { type: 'mpv', command: ['script-message', 'subminer-restart'] }],
|
||||||
['KeyC', { type: 'mpv', command: ['script-message', 'subminer-status'] }],
|
['KeyC', { type: 'mpv', command: ['script-message', 'subminer-status'] }],
|
||||||
@@ -46,10 +39,9 @@ export function createKeyboardHandlers(
|
|||||||
if (!(target instanceof Element)) return false;
|
if (!(target instanceof Element)) return false;
|
||||||
if (target.closest('.modal')) return true;
|
if (target.closest('.modal')) return true;
|
||||||
if (ctx.dom.subtitleContainer.contains(target)) return true;
|
if (ctx.dom.subtitleContainer.contains(target)) return true;
|
||||||
if (target.tagName === 'IFRAME' && target.id?.startsWith('yomitan-popup')) {
|
if (isYomitanPopupIframe(target)) return true;
|
||||||
|
if (target.closest && target.closest('iframe.yomitan-popup, iframe[id^="yomitan-popup"]'))
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
if (target.closest && target.closest('iframe[id^="yomitan-popup"]')) return true;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,15 +55,6 @@ export function createKeyboardHandlers(
|
|||||||
return parts.join('+');
|
return parts.join('+');
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInvisiblePositionToggleShortcut(e: KeyboardEvent): boolean {
|
|
||||||
return (
|
|
||||||
e.code === ctx.platform.invisiblePositionEditToggleCode &&
|
|
||||||
!e.altKey &&
|
|
||||||
e.shiftKey &&
|
|
||||||
(e.ctrlKey || e.metaKey)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveSessionHelpChordBinding(): {
|
function resolveSessionHelpChordBinding(): {
|
||||||
bindingKey: 'KeyH' | 'KeyK';
|
bindingKey: 'KeyH' | 'KeyK';
|
||||||
fallbackUsed: boolean;
|
fallbackUsed: boolean;
|
||||||
@@ -113,69 +96,6 @@ export function createKeyboardHandlers(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInvisiblePositionEditKeydown(e: KeyboardEvent): boolean {
|
|
||||||
if (!ctx.platform.isInvisibleLayer) return false;
|
|
||||||
|
|
||||||
if (isInvisiblePositionToggleShortcut(e)) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (ctx.state.invisiblePositionEditMode) {
|
|
||||||
options.cancelInvisiblePositionEdit();
|
|
||||||
} else {
|
|
||||||
options.setInvisiblePositionEditMode(true);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ctx.state.invisiblePositionEditMode) return false;
|
|
||||||
|
|
||||||
const step = e.shiftKey
|
|
||||||
? ctx.platform.invisiblePositionStepFastPx
|
|
||||||
: ctx.platform.invisiblePositionStepPx;
|
|
||||||
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
e.preventDefault();
|
|
||||||
options.cancelInvisiblePositionEdit();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === 'Enter' || ((e.ctrlKey || e.metaKey) && e.code === 'KeyS')) {
|
|
||||||
e.preventDefault();
|
|
||||||
options.saveInvisiblePositionEdit();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
e.key === 'ArrowUp' ||
|
|
||||||
e.key === 'ArrowDown' ||
|
|
||||||
e.key === 'ArrowLeft' ||
|
|
||||||
e.key === 'ArrowRight' ||
|
|
||||||
e.key === 'h' ||
|
|
||||||
e.key === 'j' ||
|
|
||||||
e.key === 'k' ||
|
|
||||||
e.key === 'l' ||
|
|
||||||
e.key === 'H' ||
|
|
||||||
e.key === 'J' ||
|
|
||||||
e.key === 'K' ||
|
|
||||||
e.key === 'L'
|
|
||||||
) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (e.key === 'ArrowUp' || e.key === 'k' || e.key === 'K') {
|
|
||||||
ctx.state.invisibleSubtitleOffsetYPx += step;
|
|
||||||
} else if (e.key === 'ArrowDown' || e.key === 'j' || e.key === 'J') {
|
|
||||||
ctx.state.invisibleSubtitleOffsetYPx -= step;
|
|
||||||
} else if (e.key === 'ArrowLeft' || e.key === 'h' || e.key === 'H') {
|
|
||||||
ctx.state.invisibleSubtitleOffsetXPx -= step;
|
|
||||||
} else if (e.key === 'ArrowRight' || e.key === 'l' || e.key === 'L') {
|
|
||||||
ctx.state.invisibleSubtitleOffsetXPx += step;
|
|
||||||
}
|
|
||||||
options.applyInvisibleSubtitleOffsetPosition();
|
|
||||||
options.updateInvisiblePositionEditHud();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetChord(): void {
|
function resetChord(): void {
|
||||||
ctx.state.chordPending = false;
|
ctx.state.chordPending = false;
|
||||||
if (ctx.state.chordTimeout !== null) {
|
if (ctx.state.chordTimeout !== null) {
|
||||||
@@ -188,9 +108,7 @@ export function createKeyboardHandlers(
|
|||||||
updateKeybindings(await window.electronAPI.getKeybindings());
|
updateKeybindings(await window.electronAPI.getKeybindings());
|
||||||
|
|
||||||
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
const yomitanPopup = document.querySelector('iframe[id^="yomitan-popup"]');
|
if (hasYomitanPopupIframe(document)) return;
|
||||||
if (yomitanPopup) return;
|
|
||||||
if (handleInvisiblePositionEditKeydown(e)) return;
|
|
||||||
|
|
||||||
if (ctx.state.runtimeOptionsModalOpen) {
|
if (ctx.state.runtimeOptionsModalOpen) {
|
||||||
options.handleRuntimeOptionsKeydown(e);
|
options.handleRuntimeOptionsKeydown(e);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user