Fix renderer overlay loading and modularize renderer

This commit is contained in:
2026-02-11 18:27:29 -08:00
parent ee21c77fd0
commit 8a82a1b5f9
29 changed files with 3150 additions and 2741 deletions

View File

@@ -0,0 +1,238 @@
import type { Keybinding } from "../../types";
import type { RendererContext } from "../context";
export function createKeyboardHandlers(
ctx: RendererContext,
options: {
handleRuntimeOptionsKeydown: (e: KeyboardEvent) => boolean;
handleSubsyncKeydown: (e: KeyboardEvent) => boolean;
handleKikuKeydown: (e: KeyboardEvent) => boolean;
handleJimakuKeydown: (e: KeyboardEvent) => boolean;
saveInvisiblePositionEdit: () => void;
cancelInvisiblePositionEdit: () => void;
setInvisiblePositionEditMode: (enabled: boolean) => void;
applyInvisibleSubtitleOffsetPosition: () => void;
updateInvisiblePositionEditHud: () => void;
},
) {
const CHORD_MAP = new Map<string, { type: "mpv" | "electron"; command?: string[]; action?: () => void }>([
["KeyS", { type: "mpv", command: ["script-message", "subminer-start"] }],
["Shift+KeyS", { type: "mpv", command: ["script-message", "subminer-stop"] }],
["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"] }],
["KeyR", { type: "mpv", command: ["script-message", "subminer-restart"] }],
["KeyC", { type: "mpv", command: ["script-message", "subminer-status"] }],
["KeyY", { type: "mpv", command: ["script-message", "subminer-menu"] }],
[
"KeyD",
{ type: "electron", action: () => window.electronAPI.toggleDevTools() },
],
]);
function isInteractiveTarget(target: EventTarget | null): boolean {
if (!(target instanceof Element)) return false;
if (target.closest(".modal")) return true;
if (ctx.dom.subtitleContainer.contains(target)) return true;
if (target.tagName === "IFRAME" && target.id?.startsWith("yomitan-popup")) {
return true;
}
if (target.closest && target.closest('iframe[id^="yomitan-popup"]')) return true;
return false;
}
function keyEventToString(e: KeyboardEvent): string {
const parts: string[] = [];
if (e.ctrlKey) parts.push("Ctrl");
if (e.altKey) parts.push("Alt");
if (e.shiftKey) parts.push("Shift");
if (e.metaKey) parts.push("Meta");
parts.push(e.code);
return parts.join("+");
}
function isInvisiblePositionToggleShortcut(e: KeyboardEvent): boolean {
return (
e.code === ctx.platform.invisiblePositionEditToggleCode &&
!e.altKey &&
e.shiftKey &&
(e.ctrlKey || e.metaKey)
);
}
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 {
ctx.state.chordPending = false;
if (ctx.state.chordTimeout !== null) {
clearTimeout(ctx.state.chordTimeout);
ctx.state.chordTimeout = null;
}
}
async function setupMpvInputForwarding(): Promise<void> {
const keybindings: Keybinding[] = await window.electronAPI.getKeybindings();
ctx.state.keybindingsMap = new Map();
for (const binding of keybindings) {
if (binding.command) {
ctx.state.keybindingsMap.set(binding.key, binding.command);
}
}
document.addEventListener("keydown", (e: KeyboardEvent) => {
const yomitanPopup = document.querySelector('iframe[id^="yomitan-popup"]');
if (yomitanPopup) return;
if (handleInvisiblePositionEditKeydown(e)) return;
if (ctx.state.runtimeOptionsModalOpen) {
options.handleRuntimeOptionsKeydown(e);
return;
}
if (ctx.state.subsyncModalOpen) {
options.handleSubsyncKeydown(e);
return;
}
if (ctx.state.kikuModalOpen) {
options.handleKikuKeydown(e);
return;
}
if (ctx.state.jimakuModalOpen) {
options.handleJimakuKeydown(e);
return;
}
if (ctx.state.chordPending) {
const modifierKeys = [
"ShiftLeft",
"ShiftRight",
"ControlLeft",
"ControlRight",
"AltLeft",
"AltRight",
"MetaLeft",
"MetaRight",
];
if (modifierKeys.includes(e.code)) {
return;
}
e.preventDefault();
const secondKey = keyEventToString(e);
const action = CHORD_MAP.get(secondKey);
resetChord();
if (action) {
if (action.type === "mpv" && action.command) {
window.electronAPI.sendMpvCommand(action.command);
} else if (action.type === "electron" && action.action) {
action.action();
}
}
return;
}
if (
e.code === "KeyY" &&
!e.ctrlKey &&
!e.altKey &&
!e.shiftKey &&
!e.metaKey &&
!e.repeat
) {
e.preventDefault();
ctx.state.chordPending = true;
ctx.state.chordTimeout = setTimeout(() => {
resetChord();
}, 1000);
return;
}
const keyString = keyEventToString(e);
const command = ctx.state.keybindingsMap.get(keyString);
if (command) {
e.preventDefault();
window.electronAPI.sendMpvCommand(command);
}
});
document.addEventListener("mousedown", (e: MouseEvent) => {
if (e.button === 2 && !isInteractiveTarget(e.target)) {
e.preventDefault();
window.electronAPI.sendMpvCommand(["cycle", "pause"]);
}
});
document.addEventListener("contextmenu", (e: Event) => {
if (!isInteractiveTarget(e.target)) {
e.preventDefault();
}
});
}
return {
setupMpvInputForwarding,
};
}