refactor(core): normalize service naming across app runtime

This commit is contained in:
2026-02-17 19:00:27 -08:00
parent e38a1c945e
commit 1233e3630f
87 changed files with 2813 additions and 1636 deletions

View File

@@ -24,13 +24,28 @@ export function createKeyboardHandlers(
// Timeout for the modal chord capture window (e.g. Y followed by H/K).
const CHORD_TIMEOUT_MS = 1000;
const CHORD_MAP = new Map<string, { type: "mpv" | "electron"; command?: string[]; action?: () => 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"] }],
[
"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"] }],
[
"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"] }],
@@ -48,7 +63,8 @@ export function createKeyboardHandlers(
if (target.tagName === "IFRAME" && target.id?.startsWith("yomitan-popup")) {
return true;
}
if (target.closest && target.closest('iframe[id^="yomitan-popup"]')) return true;
if (target.closest && target.closest('iframe[id^="yomitan-popup"]'))
return true;
return false;
}
@@ -193,7 +209,9 @@ export function createKeyboardHandlers(
}
document.addEventListener("keydown", (e: KeyboardEvent) => {
const yomitanPopup = document.querySelector('iframe[id^="yomitan-popup"]');
const yomitanPopup = document.querySelector(
'iframe[id^="yomitan-popup"]',
);
if (yomitanPopup) return;
if (handleInvisiblePositionEditKeydown(e)) return;

View File

@@ -4,7 +4,10 @@ export function createMouseHandlers(
ctx: RendererContext,
options: {
modalStateReader: ModalStateReader;
applyInvisibleSubtitleLayoutFromMpvMetrics: (metrics: any, source: string) => void;
applyInvisibleSubtitleLayoutFromMpvMetrics: (
metrics: any,
source: string,
) => void;
applyYPercent: (yPercent: number) => void;
getCurrentYPercent: () => number;
persistSubtitlePositionPatch: (patch: { yPercent: number }) => void;
@@ -26,7 +29,11 @@ export function createMouseHandlers(
function handleMouseLeave(): void {
ctx.state.isOverSubtitle = false;
const yomitanPopup = document.querySelector('iframe[id^="yomitan-popup"]');
if (!yomitanPopup && !options.modalStateReader.isAnyModalOpen() && !ctx.state.invisiblePositionEditMode) {
if (
!yomitanPopup &&
!options.modalStateReader.isAnyModalOpen() &&
!ctx.state.invisiblePositionEditMode
) {
ctx.dom.overlay.classList.remove("interactive");
if (ctx.platform.shouldToggleMouseIgnore) {
window.electronAPI.setIgnoreMouseEvents(true, { forward: true });
@@ -70,7 +77,10 @@ export function createMouseHandlers(
});
}
function getCaretTextPointRange(clientX: number, clientY: number): Range | null {
function getCaretTextPointRange(
clientX: number,
clientY: number,
): Range | null {
const documentWithCaretApi = document as Document & {
caretRangeFromPoint?: (x: number, y: number) => Range | null;
caretPositionFromPoint?: (
@@ -84,7 +94,10 @@ export function createMouseHandlers(
}
if (typeof documentWithCaretApi.caretPositionFromPoint === "function") {
const caretPosition = documentWithCaretApi.caretPositionFromPoint(clientX, clientY);
const caretPosition = documentWithCaretApi.caretPositionFromPoint(
clientX,
clientY,
);
if (!caretPosition) return null;
const range = document.createRange();
range.setStart(caretPosition.offsetNode, caretPosition.offset);
@@ -103,7 +116,9 @@ export function createMouseHandlers(
const clampedOffset = Math.max(0, Math.min(offset, text.length));
const probeIndex =
clampedOffset >= text.length ? Math.max(0, text.length - 1) : clampedOffset;
clampedOffset >= text.length
? Math.max(0, text.length - 1)
: clampedOffset;
if (wordSegmenter) {
for (const part of wordSegmenter.segment(text)) {
@@ -117,7 +132,9 @@ export function createMouseHandlers(
}
const isBoundary = (char: string): boolean =>
/[\s\u3000.,!?;:()[\]{}"'`~<>/\\|@#$%^&*+=\-、。・「」『』【】〈〉《》]/.test(char);
/[\s\u3000.,!?;:()[\]{}"'`~<>/\\|@#$%^&*+=\-、。・「」『』【】〈〉《》]/.test(
char,
);
const probeChar = text[probeIndex];
if (!probeChar || isBoundary(probeChar)) return null;
@@ -148,7 +165,10 @@ export function createMouseHandlers(
if (!ctx.dom.subtitleRoot.contains(caretRange.startContainer)) return;
const textNode = caretRange.startContainer as Text;
const wordBounds = getWordBoundsAtOffset(textNode.data, caretRange.startOffset);
const wordBounds = getWordBoundsAtOffset(
textNode.data,
caretRange.startOffset,
);
if (!wordBounds) return;
const selectionKey = `${wordBounds.start}:${wordBounds.end}:${textNode.data.slice(
@@ -242,10 +262,15 @@ export function createMouseHandlers(
element.id &&
element.id.startsWith("yomitan-popup")
) {
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
if (
!ctx.state.isOverSubtitle &&
!options.modalStateReader.isAnyModalOpen()
) {
ctx.dom.overlay.classList.remove("interactive");
if (ctx.platform.shouldToggleMouseIgnore) {
window.electronAPI.setIgnoreMouseEvents(true, { forward: true });
window.electronAPI.setIgnoreMouseEvents(true, {
forward: true,
});
}
}
}

View File

@@ -158,7 +158,10 @@ export function createJimakuModal(
}
}
async function loadFiles(entryId: number, episode: number | null): Promise<void> {
async function loadFiles(
entryId: number,
episode: number | null,
): Promise<void> {
setJimakuStatus("Loading files...");
ctx.state.jimakuFiles = [];
ctx.state.selectedFileIndex = 0;
@@ -224,11 +227,12 @@ export function createJimakuModal(
const file = ctx.state.jimakuFiles[index];
setJimakuStatus("Downloading subtitle...");
const result: JimakuDownloadResult = await window.electronAPI.jimakuDownloadFile({
entryId: ctx.state.currentEntryId,
url: file.url,
name: file.name,
});
const result: JimakuDownloadResult =
await window.electronAPI.jimakuDownloadFile({
entryId: ctx.state.currentEntryId,
url: file.url,
name: file.name,
});
if (result.ok) {
setJimakuStatus(`Downloaded and loaded: ${result.path}`);
@@ -265,8 +269,12 @@ export function createJimakuModal(
.getJimakuMediaInfo()
.then((info: JimakuMediaInfo) => {
ctx.dom.jimakuTitleInput.value = info.title || "";
ctx.dom.jimakuSeasonInput.value = info.season ? String(info.season) : "";
ctx.dom.jimakuEpisodeInput.value = info.episode ? String(info.episode) : "";
ctx.dom.jimakuSeasonInput.value = info.season
? String(info.season)
: "";
ctx.dom.jimakuEpisodeInput.value = info.episode
? String(info.episode)
: "";
ctx.state.currentEpisodeFilter = info.episode ?? null;
if (info.confidence === "high" && info.title && info.episode) {
@@ -291,7 +299,10 @@ export function createJimakuModal(
ctx.dom.jimakuModal.setAttribute("aria-hidden", "true");
window.electronAPI.notifyOverlayModalClosed("jimaku");
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
if (
!ctx.state.isOverSubtitle &&
!options.modalStateReader.isAnyModalOpen()
) {
ctx.dom.overlay.classList.remove("interactive");
}
@@ -334,10 +345,16 @@ export function createJimakuModal(
if (e.key === "ArrowUp") {
e.preventDefault();
if (ctx.state.jimakuFiles.length > 0) {
ctx.state.selectedFileIndex = Math.max(0, ctx.state.selectedFileIndex - 1);
ctx.state.selectedFileIndex = Math.max(
0,
ctx.state.selectedFileIndex - 1,
);
renderFiles();
} else if (ctx.state.jimakuEntries.length > 0) {
ctx.state.selectedEntryIndex = Math.max(0, ctx.state.selectedEntryIndex - 1);
ctx.state.selectedEntryIndex = Math.max(
0,
ctx.state.selectedEntryIndex - 1,
);
renderEntries();
}
return true;

View File

@@ -20,8 +20,14 @@ export function createKikuModal(
}
function updateKikuCardSelection(): void {
ctx.dom.kikuCard1.classList.toggle("active", ctx.state.kikuSelectedCard === 1);
ctx.dom.kikuCard2.classList.toggle("active", ctx.state.kikuSelectedCard === 2);
ctx.dom.kikuCard1.classList.toggle(
"active",
ctx.state.kikuSelectedCard === 1,
);
ctx.dom.kikuCard2.classList.toggle(
"active",
ctx.state.kikuSelectedCard === 2,
);
}
function setKikuModalStep(step: "select" | "preview"): void {
@@ -50,7 +56,9 @@ export function createKikuModal(
ctx.state.kikuPreviewMode === "compact"
? ctx.state.kikuPreviewCompactData
: ctx.state.kikuPreviewFullData;
ctx.dom.kikuPreviewJson.textContent = payload ? JSON.stringify(payload, null, 2) : "{}";
ctx.dom.kikuPreviewJson.textContent = payload
? JSON.stringify(payload, null, 2)
: "{}";
updateKikuPreviewToggle();
}
@@ -78,7 +86,8 @@ export function createKikuModal(
ctx.state.kikuSelectedCard = 1;
ctx.dom.kikuCard1Expression.textContent = data.original.expression;
ctx.dom.kikuCard1Sentence.textContent = data.original.sentencePreview || "(no sentence)";
ctx.dom.kikuCard1Sentence.textContent =
data.original.sentencePreview || "(no sentence)";
ctx.dom.kikuCard1Meta.textContent = formatMediaMeta(data.original);
ctx.dom.kikuCard2Expression.textContent = data.duplicate.expression;
@@ -123,7 +132,10 @@ export function createKikuModal(
ctx.state.kikuOriginalData = null;
ctx.state.kikuDuplicateData = null;
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
if (
!ctx.state.isOverSubtitle &&
!options.modalStateReader.isAnyModalOpen()
) {
ctx.dom.overlay.classList.remove("interactive");
}
}

View File

@@ -26,7 +26,8 @@ export function createSubsyncModal(
option.textContent = track.label;
ctx.dom.subsyncSourceSelect.appendChild(option);
}
ctx.dom.subsyncSourceSelect.disabled = ctx.state.subsyncSourceTracks.length === 0;
ctx.dom.subsyncSourceSelect.disabled =
ctx.state.subsyncSourceTracks.length === 0;
}
function closeSubsyncModal(): void {
@@ -39,7 +40,10 @@ export function createSubsyncModal(
ctx.dom.subsyncModal.setAttribute("aria-hidden", "true");
window.electronAPI.notifyOverlayModalClosed("subsync");
if (!ctx.state.isOverSubtitle && !options.modalStateReader.isAnyModalOpen()) {
if (
!ctx.state.isOverSubtitle &&
!options.modalStateReader.isAnyModalOpen()
) {
ctx.dom.overlay.classList.remove("interactive");
}
}

View File

@@ -26,7 +26,10 @@ function toMeasuredRect(rect: DOMRect): OverlayContentRect | null {
};
}
function unionRects(a: OverlayContentRect, b: OverlayContentRect): OverlayContentRect {
function unionRects(
a: OverlayContentRect,
b: OverlayContentRect,
): OverlayContentRect {
const left = Math.min(a.x, b.x);
const top = Math.min(a.y, b.y);
const right = Math.max(a.x + a.width, b.x + b.width);
@@ -48,7 +51,9 @@ function collectContentRect(ctx: RendererContext): OverlayContentRect | null {
const subtitleHasContent = hasVisibleTextContent(ctx.dom.subtitleRoot);
if (subtitleHasContent) {
const subtitleRect = toMeasuredRect(ctx.dom.subtitleRoot.getBoundingClientRect());
const subtitleRect = toMeasuredRect(
ctx.dom.subtitleRoot.getBoundingClientRect(),
);
if (subtitleRect) {
combinedRect = subtitleRect;
}

View File

@@ -32,7 +32,8 @@ export function createPositioningController(
{
applyInvisibleSubtitleOffsetPosition:
invisibleOffset.applyInvisibleSubtitleOffsetPosition,
updateInvisiblePositionEditHud: invisibleOffset.updateInvisiblePositionEditHud,
updateInvisiblePositionEditHud:
invisibleOffset.updateInvisiblePositionEditHud,
},
);

View File

@@ -6,12 +6,15 @@ const INVISIBLE_MACOS_LINE_HEIGHT_SINGLE = "0.92";
const INVISIBLE_MACOS_LINE_HEIGHT_MULTI = "1.2";
const INVISIBLE_MACOS_LINE_HEIGHT_MULTI_DENSE = "1.3";
export function applyContainerBaseLayout(ctx: RendererContext, params: {
horizontalAvailable: number;
leftInset: number;
marginX: number;
hAlign: 0 | 1 | 2;
}): void {
export function applyContainerBaseLayout(
ctx: RendererContext,
params: {
horizontalAvailable: number;
leftInset: number;
marginX: number;
hAlign: 0 | 1 | 2;
},
): void {
const { horizontalAvailable, leftInset, marginX, hAlign } = params;
ctx.dom.subtitleContainer.style.position = "absolute";
@@ -42,19 +45,26 @@ export function applyContainerBaseLayout(ctx: RendererContext, params: {
ctx.dom.subtitleRoot.style.pointerEvents = "auto";
}
export function applyVerticalPosition(ctx: RendererContext, params: {
metrics: MpvSubtitleRenderMetrics;
renderAreaHeight: number;
topInset: number;
bottomInset: number;
marginY: number;
effectiveFontSize: number;
vAlign: 0 | 1 | 2;
}): void {
export function applyVerticalPosition(
ctx: RendererContext,
params: {
metrics: MpvSubtitleRenderMetrics;
renderAreaHeight: number;
topInset: number;
bottomInset: number;
marginY: number;
effectiveFontSize: number;
vAlign: 0 | 1 | 2;
},
): void {
const lineCount = Math.max(1, ctx.state.currentInvisibleSubtitleLineCount);
const multiline = lineCount > 1;
const baselineCompensationFactor = lineCount >= 3 ? 0.46 : multiline ? 0.58 : 0.7;
const baselineCompensationPx = Math.max(0, params.effectiveFontSize * baselineCompensationFactor);
const baselineCompensationFactor =
lineCount >= 3 ? 0.46 : multiline ? 0.58 : 0.7;
const baselineCompensationPx = Math.max(
0,
params.effectiveFontSize * baselineCompensationFactor,
);
if (params.vAlign === 2) {
ctx.dom.subtitleContainer.style.top = `${Math.max(
@@ -72,7 +82,8 @@ export function applyVerticalPosition(ctx: RendererContext, params: {
return;
}
const subPosMargin = ((100 - params.metrics.subPos) / 100) * params.renderAreaHeight;
const subPosMargin =
((100 - params.metrics.subPos) / 100) * params.renderAreaHeight;
const effectiveMargin = Math.max(params.marginY, subPosMargin);
const bottomPx = Math.max(
0,
@@ -96,7 +107,10 @@ function resolveFontFamily(rawFont: string): string {
: `"${rawFont}", sans-serif`;
}
function resolveLineHeight(lineCount: number, isMacOSPlatform: boolean): string {
function resolveLineHeight(
lineCount: number,
isMacOSPlatform: boolean,
): string {
if (!isMacOSPlatform) return "normal";
if (lineCount >= 3) return INVISIBLE_MACOS_LINE_HEIGHT_MULTI_DENSE;
if (lineCount >= 2) return INVISIBLE_MACOS_LINE_HEIGHT_MULTI;
@@ -115,8 +129,13 @@ function resolveLetterSpacing(
return isMacOSPlatform ? "-0.02em" : "0px";
}
function applyComputedLineHeightCompensation(ctx: RendererContext, effectiveFontSize: number): void {
const computedLineHeight = parseFloat(getComputedStyle(ctx.dom.subtitleRoot).lineHeight);
function applyComputedLineHeightCompensation(
ctx: RendererContext,
effectiveFontSize: number,
): void {
const computedLineHeight = parseFloat(
getComputedStyle(ctx.dom.subtitleRoot).lineHeight,
);
if (
!Number.isFinite(computedLineHeight) ||
computedLineHeight <= effectiveFontSize
@@ -151,11 +170,14 @@ function applyMacOSAdjustments(ctx: RendererContext): void {
)}px`;
}
export function applyTypography(ctx: RendererContext, params: {
metrics: MpvSubtitleRenderMetrics;
pxPerScaledPixel: number;
effectiveFontSize: number;
}): void {
export function applyTypography(
ctx: RendererContext,
params: {
metrics: MpvSubtitleRenderMetrics;
pxPerScaledPixel: number;
effectiveFontSize: number;
},
): void {
const lineCount = Math.max(1, ctx.state.currentInvisibleSubtitleLineCount);
const isMacOSPlatform = ctx.platform.isMacOSPlatform;
@@ -164,7 +186,9 @@ export function applyTypography(ctx: RendererContext, params: {
resolveLineHeight(lineCount, isMacOSPlatform),
isMacOSPlatform ? "important" : "",
);
ctx.dom.subtitleRoot.style.fontFamily = resolveFontFamily(params.metrics.subFont);
ctx.dom.subtitleRoot.style.fontFamily = resolveFontFamily(
params.metrics.subFont,
);
ctx.dom.subtitleRoot.style.setProperty(
"letter-spacing",
resolveLetterSpacing(
@@ -175,8 +199,12 @@ export function applyTypography(ctx: RendererContext, params: {
isMacOSPlatform ? "important" : "",
);
ctx.dom.subtitleRoot.style.fontKerning = isMacOSPlatform ? "auto" : "none";
ctx.dom.subtitleRoot.style.fontWeight = params.metrics.subBold ? "700" : "400";
ctx.dom.subtitleRoot.style.fontStyle = params.metrics.subItalic ? "italic" : "normal";
ctx.dom.subtitleRoot.style.fontWeight = params.metrics.subBold
? "700"
: "400";
ctx.dom.subtitleRoot.style.fontStyle = params.metrics.subItalic
? "italic"
: "normal";
ctx.dom.subtitleRoot.style.transform = "";
ctx.dom.subtitleRoot.style.transformOrigin = "";

View File

@@ -74,7 +74,10 @@ export function applyPlatformFontCompensation(
function calculateGeometry(
metrics: MpvSubtitleRenderMetrics,
osdToCssScale: number,
): Omit<SubtitleLayoutGeometry, "marginY" | "marginX" | "pxPerScaledPixel" | "effectiveFontSize"> {
): Omit<
SubtitleLayoutGeometry,
"marginY" | "marginX" | "pxPerScaledPixel" | "effectiveFontSize"
> {
const dims = metrics.osdDimensions;
const renderAreaHeight = dims ? dims.h / osdToCssScale : window.innerHeight;
const renderAreaWidth = dims ? dims.w / osdToCssScale : window.innerWidth;
@@ -88,7 +91,10 @@ function calculateGeometry(
const rightInset = anchorToVideoArea ? videoRightInset : 0;
const topInset = anchorToVideoArea ? videoTopInset : 0;
const bottomInset = anchorToVideoArea ? videoBottomInset : 0;
const horizontalAvailable = Math.max(0, renderAreaWidth - leftInset - rightInset);
const horizontalAvailable = Math.max(
0,
renderAreaWidth - leftInset - rightInset,
);
return {
renderAreaHeight,
@@ -113,11 +119,16 @@ export function calculateSubtitleMetrics(
window.devicePixelRatio || 1,
);
const geometry = calculateGeometry(metrics, osdToCssScale);
const videoHeight = geometry.renderAreaHeight - geometry.topInset - geometry.bottomInset;
const scaleRefHeight = metrics.subScaleByWindow ? geometry.renderAreaHeight : videoHeight;
const videoHeight =
geometry.renderAreaHeight - geometry.topInset - geometry.bottomInset;
const scaleRefHeight = metrics.subScaleByWindow
? geometry.renderAreaHeight
: videoHeight;
const pxPerScaledPixel = Math.max(0.1, scaleRefHeight / 720);
const computedFontSize =
metrics.subFontSize * metrics.subScale * (ctx.platform.isLinuxPlatform ? 1 : pxPerScaledPixel);
metrics.subFontSize *
metrics.subScale *
(ctx.platform.isLinuxPlatform ? 1 : pxPerScaledPixel);
const effectiveFontSize = applyPlatformFontCompensation(
computedFontSize,
ctx.platform.isMacOSPlatform,

View File

@@ -11,7 +11,10 @@ import {
} from "./invisible-layout-metrics.js";
export type MpvSubtitleLayoutController = {
applyInvisibleSubtitleLayoutFromMpvMetrics: (metrics: MpvSubtitleRenderMetrics, source: string) => void;
applyInvisibleSubtitleLayoutFromMpvMetrics: (
metrics: MpvSubtitleRenderMetrics,
source: string,
) => void;
};
export function createMpvSubtitleLayoutController(
@@ -29,10 +32,15 @@ export function createMpvSubtitleLayoutController(
ctx.state.mpvSubtitleRenderMetrics = metrics;
const geometry = calculateSubtitleMetrics(ctx, metrics);
const alignment = calculateSubtitlePosition(metrics, geometry.pxPerScaledPixel, 2);
const alignment = calculateSubtitlePosition(
metrics,
geometry.pxPerScaledPixel,
2,
);
applySubtitleFontSize(geometry.effectiveFontSize);
const effectiveBorderSize = metrics.subBorderSize * geometry.pxPerScaledPixel;
const effectiveBorderSize =
metrics.subBorderSize * geometry.pxPerScaledPixel;
document.documentElement.style.setProperty(
"--sub-border-size",
@@ -81,7 +89,10 @@ export function createMpvSubtitleLayoutController(
options.applyInvisibleSubtitleOffsetPosition();
options.updateInvisiblePositionEditHud();
console.log("[invisible-overlay] Applied mpv subtitle render metrics from", source);
console.log(
"[invisible-overlay] Applied mpv subtitle render metrics from",
source,
);
}
return {

View File

@@ -2,7 +2,10 @@ import type { SubtitlePosition } from "../../types";
import type { ModalStateReader, RendererContext } from "../context";
export type InvisibleOffsetController = {
applyInvisibleStoredSubtitlePosition: (position: SubtitlePosition | null, source: string) => void;
applyInvisibleStoredSubtitlePosition: (
position: SubtitlePosition | null,
source: string,
) => void;
applyInvisibleSubtitleOffsetPosition: () => void;
updateInvisiblePositionEditHud: () => void;
setInvisiblePositionEditMode: (enabled: boolean) => void;
@@ -15,9 +18,7 @@ function formatEditHudText(offsetX: number, offsetY: number): string {
return `Position Edit Ctrl/Cmd+Shift+P toggle Arrow keys move Enter/Ctrl+S save Esc cancel x:${Math.round(offsetX)} y:${Math.round(offsetY)}`;
}
function createEditPositionText(
ctx: RendererContext,
): string {
function createEditPositionText(ctx: RendererContext): string {
return formatEditHudText(
ctx.state.invisibleSubtitleOffsetXPx,
ctx.state.invisibleSubtitleOffsetYPx,
@@ -32,7 +33,8 @@ function applyOffsetByBasePosition(ctx: RendererContext): void {
if (ctx.state.invisibleLayoutBaseBottomPx !== null) {
ctx.dom.subtitleContainer.style.bottom = `${Math.max(
0,
ctx.state.invisibleLayoutBaseBottomPx + ctx.state.invisibleSubtitleOffsetYPx,
ctx.state.invisibleLayoutBaseBottomPx +
ctx.state.invisibleSubtitleOffsetYPx,
)}px`;
ctx.dom.subtitleContainer.style.top = "";
return;
@@ -59,14 +61,19 @@ export function createInvisibleOffsetController(
document.body.classList.toggle("invisible-position-edit", enabled);
if (enabled) {
ctx.state.invisiblePositionEditStartX = ctx.state.invisibleSubtitleOffsetXPx;
ctx.state.invisiblePositionEditStartY = ctx.state.invisibleSubtitleOffsetYPx;
ctx.state.invisiblePositionEditStartX =
ctx.state.invisibleSubtitleOffsetXPx;
ctx.state.invisiblePositionEditStartY =
ctx.state.invisibleSubtitleOffsetYPx;
ctx.dom.overlay.classList.add("interactive");
if (ctx.platform.shouldToggleMouseIgnore) {
window.electronAPI.setIgnoreMouseEvents(false);
}
} else {
if (!ctx.state.isOverSubtitle && !modalStateReader.isAnySettingsModalOpen()) {
if (
!ctx.state.isOverSubtitle &&
!modalStateReader.isAnySettingsModalOpen()
) {
ctx.dom.overlay.classList.remove("interactive");
if (ctx.platform.shouldToggleMouseIgnore) {
window.electronAPI.setIgnoreMouseEvents(true, { forward: true });
@@ -79,14 +86,18 @@ export function createInvisibleOffsetController(
function updateInvisiblePositionEditHud(): void {
if (!ctx.state.invisiblePositionEditHud) return;
ctx.state.invisiblePositionEditHud.textContent = createEditPositionText(ctx);
ctx.state.invisiblePositionEditHud.textContent =
createEditPositionText(ctx);
}
function applyInvisibleSubtitleOffsetPosition(): void {
applyOffsetByBasePosition(ctx);
}
function applyInvisibleStoredSubtitlePosition(position: SubtitlePosition | null, source: string): void {
function applyInvisibleStoredSubtitlePosition(
position: SubtitlePosition | null,
source: string,
): void {
if (
position &&
typeof position.yPercent === "number" &&
@@ -100,11 +111,13 @@ export function createInvisibleOffsetController(
if (position) {
const nextX =
typeof position.invisibleOffsetXPx === "number" && Number.isFinite(position.invisibleOffsetXPx)
typeof position.invisibleOffsetXPx === "number" &&
Number.isFinite(position.invisibleOffsetXPx)
? position.invisibleOffsetXPx
: 0;
const nextY =
typeof position.invisibleOffsetYPx === "number" && Number.isFinite(position.invisibleOffsetYPx)
typeof position.invisibleOffsetYPx === "number" &&
Number.isFinite(position.invisibleOffsetYPx)
? position.invisibleOffsetYPx
: 0;
ctx.state.invisibleSubtitleOffsetXPx = nextX;
@@ -135,8 +148,10 @@ export function createInvisibleOffsetController(
}
function cancelInvisiblePositionEdit(): void {
ctx.state.invisibleSubtitleOffsetXPx = ctx.state.invisiblePositionEditStartX;
ctx.state.invisibleSubtitleOffsetYPx = ctx.state.invisiblePositionEditStartY;
ctx.state.invisibleSubtitleOffsetXPx =
ctx.state.invisiblePositionEditStartX;
ctx.state.invisibleSubtitleOffsetYPx =
ctx.state.invisiblePositionEditStartY;
applyOffsetByBasePosition(ctx);
setInvisiblePositionEditMode(false);
}

View File

@@ -5,21 +5,31 @@ const PREFERRED_Y_PERCENT_MIN = 2;
const PREFERRED_Y_PERCENT_MAX = 80;
export type SubtitlePositionController = {
applyStoredSubtitlePosition: (position: SubtitlePosition | null, source: string) => void;
applyStoredSubtitlePosition: (
position: SubtitlePosition | null,
source: string,
) => void;
getCurrentYPercent: () => number;
applyYPercent: (yPercent: number) => void;
persistSubtitlePositionPatch: (patch: Partial<SubtitlePosition>) => void;
};
function clampYPercent(yPercent: number): number {
return Math.max(PREFERRED_Y_PERCENT_MIN, Math.min(PREFERRED_Y_PERCENT_MAX, yPercent));
return Math.max(
PREFERRED_Y_PERCENT_MIN,
Math.min(PREFERRED_Y_PERCENT_MAX, yPercent),
);
}
function getPersistedYPercent(
ctx: RendererContext,
position: SubtitlePosition | null,
): number {
if (!position || typeof position.yPercent !== "number" || !Number.isFinite(position.yPercent)) {
if (
!position ||
typeof position.yPercent !== "number" ||
!Number.isFinite(position.yPercent)
) {
return ctx.state.persistedSubtitlePosition.yPercent;
}
@@ -66,12 +76,12 @@ function getNextPersistedPosition(
typeof patch.invisibleOffsetXPx === "number" &&
Number.isFinite(patch.invisibleOffsetXPx)
? patch.invisibleOffsetXPx
: ctx.state.persistedSubtitlePosition.invisibleOffsetXPx ?? 0,
: (ctx.state.persistedSubtitlePosition.invisibleOffsetXPx ?? 0),
invisibleOffsetYPx:
typeof patch.invisibleOffsetYPx === "number" &&
Number.isFinite(patch.invisibleOffsetYPx)
? patch.invisibleOffsetYPx
: ctx.state.persistedSubtitlePosition.invisibleOffsetYPx ?? 0,
: (ctx.state.persistedSubtitlePosition.invisibleOffsetYPx ?? 0),
};
}
@@ -83,8 +93,11 @@ export function createInMemorySubtitlePositionController(
return ctx.state.currentYPercent;
}
const marginBottom = parseFloat(ctx.dom.subtitleContainer.style.marginBottom) || 60;
ctx.state.currentYPercent = clampYPercent((marginBottom / window.innerHeight) * 100);
const marginBottom =
parseFloat(ctx.dom.subtitleContainer.style.marginBottom) || 60;
ctx.state.currentYPercent = clampYPercent(
(marginBottom / window.innerHeight) * 100,
);
return ctx.state.currentYPercent;
}
@@ -101,13 +114,18 @@ export function createInMemorySubtitlePositionController(
ctx.dom.subtitleContainer.style.marginBottom = `${marginBottom}px`;
}
function persistSubtitlePositionPatch(patch: Partial<SubtitlePosition>): void {
function persistSubtitlePositionPatch(
patch: Partial<SubtitlePosition>,
): void {
const nextPosition = getNextPersistedPosition(ctx, patch);
ctx.state.persistedSubtitlePosition = nextPosition;
window.electronAPI.saveSubtitlePosition(nextPosition);
}
function applyStoredSubtitlePosition(position: SubtitlePosition | null, source: string): void {
function applyStoredSubtitlePosition(
position: SubtitlePosition | null,
source: string,
): void {
updatePersistedSubtitlePosition(ctx, position);
if (position && position.yPercent !== undefined) {
applyYPercent(position.yPercent);

View File

@@ -132,7 +132,10 @@ async function init(): Promise<void> {
window.electronAPI.onSubtitlePosition((position: SubtitlePosition | null) => {
if (ctx.platform.isInvisibleLayer) {
positioning.applyInvisibleStoredSubtitlePosition(position, "media-change");
positioning.applyInvisibleStoredSubtitlePosition(
position,
"media-change",
);
} else {
positioning.applyStoredSubtitlePosition(position, "media-change");
}
@@ -140,10 +143,15 @@ async function init(): Promise<void> {
});
if (ctx.platform.isInvisibleLayer) {
window.electronAPI.onMpvSubtitleRenderMetrics((metrics: MpvSubtitleRenderMetrics) => {
positioning.applyInvisibleSubtitleLayoutFromMpvMetrics(metrics, "event");
measurementReporter.schedule();
});
window.electronAPI.onMpvSubtitleRenderMetrics(
(metrics: MpvSubtitleRenderMetrics) => {
positioning.applyInvisibleSubtitleLayoutFromMpvMetrics(
metrics,
"event",
);
measurementReporter.schedule();
},
);
window.electronAPI.onOverlayDebugVisualization((enabled: boolean) => {
document.body.classList.toggle("debug-invisible-visualization", enabled);
});
@@ -162,8 +170,12 @@ async function init(): Promise<void> {
measurementReporter.schedule();
});
subtitleRenderer.updateSecondarySubMode(await window.electronAPI.getSecondarySubMode());
subtitleRenderer.renderSecondarySub(await window.electronAPI.getCurrentSecondarySub());
subtitleRenderer.updateSecondarySubMode(
await window.electronAPI.getSecondarySubMode(),
);
subtitleRenderer.renderSecondarySub(
await window.electronAPI.getCurrentSecondarySub(),
);
measurementReporter.schedule();
const hoverTarget = ctx.platform.isInvisibleLayer
@@ -171,8 +183,14 @@ async function init(): Promise<void> {
: ctx.dom.subtitleContainer;
hoverTarget.addEventListener("mouseenter", mouseHandlers.handleMouseEnter);
hoverTarget.addEventListener("mouseleave", mouseHandlers.handleMouseLeave);
ctx.dom.secondarySubContainer.addEventListener("mouseenter", mouseHandlers.handleMouseEnter);
ctx.dom.secondarySubContainer.addEventListener("mouseleave", mouseHandlers.handleMouseLeave);
ctx.dom.secondarySubContainer.addEventListener(
"mouseenter",
mouseHandlers.handleMouseEnter,
);
ctx.dom.secondarySubContainer.addEventListener(
"mouseleave",
mouseHandlers.handleMouseLeave,
);
mouseHandlers.setupInvisibleHoverSelection();
positioning.setupInvisiblePositionEditHud();
@@ -189,9 +207,11 @@ async function init(): Promise<void> {
subsyncModal.wireDomEvents();
sessionHelpModal.wireDomEvents();
window.electronAPI.onRuntimeOptionsChanged((options: RuntimeOptionState[]) => {
runtimeOptionsModal.updateRuntimeOptions(options);
});
window.electronAPI.onRuntimeOptionsChanged(
(options: RuntimeOptionState[]) => {
runtimeOptionsModal.updateRuntimeOptions(options);
},
);
window.electronAPI.onOpenRuntimeOptions(() => {
runtimeOptionsModal.openRuntimeOptionsModal().catch(() => {
runtimeOptionsModal.setRuntimeOptionsStatus(
@@ -209,7 +229,10 @@ async function init(): Promise<void> {
subsyncModal.openSubsyncModal(payload);
});
window.electronAPI.onKikuFieldGroupingRequest(
(data: { original: KikuDuplicateCardInfo; duplicate: KikuDuplicateCardInfo }) => {
(data: {
original: KikuDuplicateCardInfo;
duplicate: KikuDuplicateCardInfo;
}) => {
kikuModal.openKikuFieldGroupingModal(data);
},
);
@@ -220,7 +243,9 @@ async function init(): Promise<void> {
await keyboardHandlers.setupMpvInputForwarding();
subtitleRenderer.applySubtitleStyle(await window.electronAPI.getSubtitleStyle());
subtitleRenderer.applySubtitleStyle(
await window.electronAPI.getSubtitleStyle(),
);
if (ctx.platform.isInvisibleLayer) {
positioning.applyInvisibleStoredSubtitlePosition(

View File

@@ -95,7 +95,13 @@ test("computeWordClass does not add frequency class to known or N+1 terms", () =
topX: 100,
mode: "single",
singleColor: "#000000",
bandedColors: ["#000000", "#000000", "#000000", "#000000", "#000000"] as const,
bandedColors: [
"#000000",
"#000000",
"#000000",
"#000000",
"#000000",
] as const,
}),
"word word-known",
);
@@ -105,7 +111,13 @@ test("computeWordClass does not add frequency class to known or N+1 terms", () =
topX: 100,
mode: "single",
singleColor: "#000000",
bandedColors: ["#000000", "#000000", "#000000", "#000000", "#000000"] as const,
bandedColors: [
"#000000",
"#000000",
"#000000",
"#000000",
"#000000",
] as const,
}),
"word word-n-plus-one",
);
@@ -115,7 +127,13 @@ test("computeWordClass does not add frequency class to known or N+1 terms", () =
topX: 100,
mode: "single",
singleColor: "#000000",
bandedColors: ["#000000", "#000000", "#000000", "#000000", "#000000"] as const,
bandedColors: [
"#000000",
"#000000",
"#000000",
"#000000",
"#000000",
] as const,
}),
"word word-frequency-single",
);
@@ -127,16 +145,19 @@ test("computeWordClass adds frequency class for single mode when rank is within
frequencyRank: 50,
});
const actual = computeWordClass(
token,
{
enabled: true,
topX: 100,
mode: "single",
singleColor: "#000000",
bandedColors: ["#000000", "#000000", "#000000", "#000000", "#000000"] as const,
},
);
const actual = computeWordClass(token, {
enabled: true,
topX: 100,
mode: "single",
singleColor: "#000000",
bandedColors: [
"#000000",
"#000000",
"#000000",
"#000000",
"#000000",
] as const,
});
assert.equal(actual, "word word-frequency-single");
});
@@ -147,16 +168,19 @@ test("computeWordClass adds frequency class when rank equals topX", () => {
frequencyRank: 100,
});
const actual = computeWordClass(
token,
{
enabled: true,
topX: 100,
mode: "single",
singleColor: "#000000",
bandedColors: ["#000000", "#000000", "#000000", "#000000", "#000000"] as const,
},
);
const actual = computeWordClass(token, {
enabled: true,
topX: 100,
mode: "single",
singleColor: "#000000",
bandedColors: [
"#000000",
"#000000",
"#000000",
"#000000",
"#000000",
] as const,
});
assert.equal(actual, "word word-frequency-single");
});
@@ -167,17 +191,19 @@ test("computeWordClass adds frequency class for banded mode", () => {
frequencyRank: 250,
});
const actual = computeWordClass(
token,
{
enabled: true,
topX: 1000,
mode: "banded",
singleColor: "#000000",
bandedColors:
["#111111", "#222222", "#333333", "#444444", "#555555"] as const,
},
);
const actual = computeWordClass(token, {
enabled: true,
topX: 1000,
mode: "banded",
singleColor: "#000000",
bandedColors: [
"#111111",
"#222222",
"#333333",
"#444444",
"#555555",
] as const,
});
assert.equal(actual, "word word-frequency-band-2");
});
@@ -193,13 +219,7 @@ test("computeWordClass uses configured band count for banded mode", () => {
topX: 4,
mode: "banded",
singleColor: "#000000",
bandedColors: [
"#111111",
"#222222",
"#333333",
"#444444",
"#555555",
],
bandedColors: ["#111111", "#222222", "#333333", "#444444", "#555555"],
} as any);
assert.equal(actual, "word word-frequency-band-3");
@@ -211,16 +231,19 @@ test("computeWordClass skips frequency class when rank is out of topX", () => {
frequencyRank: 1200,
});
const actual = computeWordClass(
token,
{
enabled: true,
topX: 1000,
mode: "single",
singleColor: "#000000",
bandedColors: ["#000000", "#000000", "#000000", "#000000", "#000000"] as const,
},
);
const actual = computeWordClass(token, {
enabled: true,
topX: 1000,
mode: "single",
singleColor: "#000000",
bandedColors: [
"#000000",
"#000000",
"#000000",
"#000000",
"#000000",
] as const,
});
assert.equal(actual, "word");
});
@@ -229,9 +252,7 @@ test("JLPT CSS rules use underline-only styling in renderer stylesheet", () => {
const distCssPath = path.join(process.cwd(), "dist", "renderer", "style.css");
const srcCssPath = path.join(process.cwd(), "src", "renderer", "style.css");
const cssPath = fs.existsSync(distCssPath)
? distCssPath
: srcCssPath;
const cssPath = fs.existsSync(distCssPath) ? distCssPath : srcCssPath;
if (!fs.existsSync(cssPath)) {
assert.fail(
"JLPT CSS file missing. Run `pnpm run build` first, or ensure src/renderer/style.css exists.",
@@ -259,7 +280,10 @@ test("JLPT CSS rules use underline-only styling in renderer stylesheet", () => {
? "#subtitleRoot .word.word-frequency-single"
: `#subtitleRoot .word.word-frequency-band-${band}`,
);
assert.ok(block.length > 0, `frequency class word-frequency-${band === 1 ? "single" : `band-${band}`} should exist`);
assert.ok(
block.length > 0,
`frequency class word-frequency-${band === 1 ? "single" : `band-${band}`} should exist`,
);
assert.match(block, /color:\s*var\(/);
}
});

View File

@@ -72,12 +72,18 @@ function getFrequencyDictionaryClass(
return "";
}
if (typeof token.frequencyRank !== "number" || !Number.isFinite(token.frequencyRank)) {
if (
typeof token.frequencyRank !== "number" ||
!Number.isFinite(token.frequencyRank)
) {
return "";
}
const rank = Math.max(1, Math.floor(token.frequencyRank));
const topX = sanitizeFrequencyTopX(settings.topX, DEFAULT_FREQUENCY_RENDER_SETTINGS.topX);
const topX = sanitizeFrequencyTopX(
settings.topX,
DEFAULT_FREQUENCY_RENDER_SETTINGS.topX,
);
if (rank > topX) {
return "";
}
@@ -121,16 +127,16 @@ function renderWithTokens(
if (surface.includes("\n")) {
const parts = surface.split("\n");
for (let i = 0; i < parts.length; i += 1) {
if (parts[i]) {
const span = document.createElement("span");
span.className = computeWordClass(
token,
resolvedFrequencyRenderSettings,
);
span.textContent = parts[i];
if (token.reading) span.dataset.reading = token.reading;
if (token.headword) span.dataset.headword = token.headword;
for (let i = 0; i < parts.length; i += 1) {
if (parts[i]) {
const span = document.createElement("span");
span.className = computeWordClass(
token,
resolvedFrequencyRenderSettings,
);
span.textContent = parts[i];
if (token.reading) span.dataset.reading = token.reading;
if (token.headword) span.dataset.headword = token.headword;
fragment.appendChild(span);
}
if (i < parts.length - 1) {
@@ -214,7 +220,10 @@ function renderCharacterLevel(root: HTMLElement, text: string): void {
root.appendChild(fragment);
}
function renderPlainTextPreserveLineBreaks(root: HTMLElement, text: string): void {
function renderPlainTextPreserveLineBreaks(
root: HTMLElement,
text: string,
): void {
const lines = text.split("\n");
const fragment = document.createDocumentFragment();
@@ -255,7 +264,10 @@ export function createSubtitleRenderer(ctx: RendererContext) {
1,
normalizedInvisible.split("\n").length,
);
renderPlainTextPreserveLineBreaks(ctx.dom.subtitleRoot, normalizedInvisible);
renderPlainTextPreserveLineBreaks(
ctx.dom.subtitleRoot,
normalizedInvisible,
);
return;
}
@@ -331,10 +343,13 @@ export function createSubtitleRenderer(ctx: RendererContext) {
function applySubtitleStyle(style: SubtitleStyleConfig | null): void {
if (!style) return;
if (style.fontFamily) ctx.dom.subtitleRoot.style.fontFamily = style.fontFamily;
if (style.fontSize) ctx.dom.subtitleRoot.style.fontSize = `${style.fontSize}px`;
if (style.fontFamily)
ctx.dom.subtitleRoot.style.fontFamily = style.fontFamily;
if (style.fontSize)
ctx.dom.subtitleRoot.style.fontSize = `${style.fontSize}px`;
if (style.fontColor) ctx.dom.subtitleRoot.style.color = style.fontColor;
if (style.fontWeight) ctx.dom.subtitleRoot.style.fontWeight = style.fontWeight;
if (style.fontWeight)
ctx.dom.subtitleRoot.style.fontWeight = style.fontWeight;
if (style.fontStyle) ctx.dom.subtitleRoot.style.fontStyle = style.fontStyle;
if (style.backgroundColor) {
ctx.dom.subtitleContainer.style.background = style.backgroundColor;
@@ -352,12 +367,12 @@ export function createSubtitleRenderer(ctx: RendererContext) {
N5: ctx.state.jlptN5Color ?? "#8aadf4",
...(style.jlptColors
? {
N1: sanitizeHexColor(style.jlptColors?.N1, ctx.state.jlptN1Color),
N2: sanitizeHexColor(style.jlptColors?.N2, ctx.state.jlptN2Color),
N3: sanitizeHexColor(style.jlptColors?.N3, ctx.state.jlptN3Color),
N4: sanitizeHexColor(style.jlptColors?.N4, ctx.state.jlptN4Color),
N5: sanitizeHexColor(style.jlptColors?.N5, ctx.state.jlptN5Color),
}
N1: sanitizeHexColor(style.jlptColors?.N1, ctx.state.jlptN1Color),
N2: sanitizeHexColor(style.jlptColors?.N2, ctx.state.jlptN2Color),
N3: sanitizeHexColor(style.jlptColors?.N3, ctx.state.jlptN3Color),
N4: sanitizeHexColor(style.jlptColors?.N4, ctx.state.jlptN4Color),
N5: sanitizeHexColor(style.jlptColors?.N5, ctx.state.jlptN5Color),
}
: {}),
};
@@ -367,20 +382,39 @@ export function createSubtitleRenderer(ctx: RendererContext) {
"--subtitle-known-word-color",
knownWordColor,
);
ctx.dom.subtitleRoot.style.setProperty("--subtitle-n-plus-one-color", nPlusOneColor);
ctx.dom.subtitleRoot.style.setProperty(
"--subtitle-n-plus-one-color",
nPlusOneColor,
);
ctx.state.jlptN1Color = jlptColors.N1;
ctx.state.jlptN2Color = jlptColors.N2;
ctx.state.jlptN3Color = jlptColors.N3;
ctx.state.jlptN4Color = jlptColors.N4;
ctx.state.jlptN5Color = jlptColors.N5;
ctx.dom.subtitleRoot.style.setProperty("--subtitle-jlpt-n1-color", jlptColors.N1);
ctx.dom.subtitleRoot.style.setProperty("--subtitle-jlpt-n2-color", jlptColors.N2);
ctx.dom.subtitleRoot.style.setProperty("--subtitle-jlpt-n3-color", jlptColors.N3);
ctx.dom.subtitleRoot.style.setProperty("--subtitle-jlpt-n4-color", jlptColors.N4);
ctx.dom.subtitleRoot.style.setProperty("--subtitle-jlpt-n5-color", jlptColors.N5);
ctx.dom.subtitleRoot.style.setProperty(
"--subtitle-jlpt-n1-color",
jlptColors.N1,
);
ctx.dom.subtitleRoot.style.setProperty(
"--subtitle-jlpt-n2-color",
jlptColors.N2,
);
ctx.dom.subtitleRoot.style.setProperty(
"--subtitle-jlpt-n3-color",
jlptColors.N3,
);
ctx.dom.subtitleRoot.style.setProperty(
"--subtitle-jlpt-n4-color",
jlptColors.N4,
);
ctx.dom.subtitleRoot.style.setProperty(
"--subtitle-jlpt-n5-color",
jlptColors.N5,
);
const frequencyDictionarySettings = style.frequencyDictionary ?? {};
const frequencyEnabled =
frequencyDictionarySettings.enabled ?? ctx.state.frequencyDictionaryEnabled;
frequencyDictionarySettings.enabled ??
ctx.state.frequencyDictionaryEnabled;
const frequencyTopX = sanitizeFrequencyTopX(
frequencyDictionarySettings.topX,
ctx.state.frequencyDictionaryTopX,
@@ -458,7 +492,8 @@ export function createSubtitleRenderer(ctx: RendererContext) {
ctx.dom.secondarySubRoot.style.fontStyle = secondaryStyle.fontStyle;
}
if (secondaryStyle.backgroundColor) {
ctx.dom.secondarySubContainer.style.background = secondaryStyle.backgroundColor;
ctx.dom.secondarySubContainer.style.background =
secondaryStyle.backgroundColor;
}
}

View File

@@ -77,8 +77,9 @@ export function resolveRendererDom(): RendererDom {
subtitleRoot: getRequiredElement<HTMLElement>("subtitleRoot"),
subtitleContainer: getRequiredElement<HTMLElement>("subtitleContainer"),
overlay: getRequiredElement<HTMLElement>("overlay"),
secondarySubContainer:
getRequiredElement<HTMLElement>("secondarySubContainer"),
secondarySubContainer: getRequiredElement<HTMLElement>(
"secondarySubContainer",
),
secondarySubRoot: getRequiredElement<HTMLElement>("secondarySubRoot"),
jimakuModal: getRequiredElement<HTMLDivElement>("jimakuModal"),
@@ -88,60 +89,89 @@ export function resolveRendererDom(): RendererDom {
jimakuSearchButton: getRequiredElement<HTMLButtonElement>("jimakuSearch"),
jimakuCloseButton: getRequiredElement<HTMLButtonElement>("jimakuClose"),
jimakuStatus: getRequiredElement<HTMLDivElement>("jimakuStatus"),
jimakuEntriesSection: getRequiredElement<HTMLDivElement>("jimakuEntriesSection"),
jimakuEntriesSection: getRequiredElement<HTMLDivElement>(
"jimakuEntriesSection",
),
jimakuEntriesList: getRequiredElement<HTMLUListElement>("jimakuEntries"),
jimakuFilesSection: getRequiredElement<HTMLDivElement>("jimakuFilesSection"),
jimakuFilesSection:
getRequiredElement<HTMLDivElement>("jimakuFilesSection"),
jimakuFilesList: getRequiredElement<HTMLUListElement>("jimakuFiles"),
jimakuBroadenButton: getRequiredElement<HTMLButtonElement>("jimakuBroaden"),
kikuModal: getRequiredElement<HTMLDivElement>("kikuFieldGroupingModal"),
kikuCard1: getRequiredElement<HTMLDivElement>("kikuCard1"),
kikuCard2: getRequiredElement<HTMLDivElement>("kikuCard2"),
kikuCard1Expression: getRequiredElement<HTMLDivElement>("kikuCard1Expression"),
kikuCard2Expression: getRequiredElement<HTMLDivElement>("kikuCard2Expression"),
kikuCard1Expression: getRequiredElement<HTMLDivElement>(
"kikuCard1Expression",
),
kikuCard2Expression: getRequiredElement<HTMLDivElement>(
"kikuCard2Expression",
),
kikuCard1Sentence: getRequiredElement<HTMLDivElement>("kikuCard1Sentence"),
kikuCard2Sentence: getRequiredElement<HTMLDivElement>("kikuCard2Sentence"),
kikuCard1Meta: getRequiredElement<HTMLDivElement>("kikuCard1Meta"),
kikuCard2Meta: getRequiredElement<HTMLDivElement>("kikuCard2Meta"),
kikuConfirmButton: getRequiredElement<HTMLButtonElement>("kikuConfirmButton"),
kikuConfirmButton:
getRequiredElement<HTMLButtonElement>("kikuConfirmButton"),
kikuCancelButton: getRequiredElement<HTMLButtonElement>("kikuCancelButton"),
kikuDeleteDuplicateCheckbox:
getRequiredElement<HTMLInputElement>("kikuDeleteDuplicate"),
kikuDeleteDuplicateCheckbox: getRequiredElement<HTMLInputElement>(
"kikuDeleteDuplicate",
),
kikuSelectionStep: getRequiredElement<HTMLDivElement>("kikuSelectionStep"),
kikuPreviewStep: getRequiredElement<HTMLDivElement>("kikuPreviewStep"),
kikuPreviewJson: getRequiredElement<HTMLPreElement>("kikuPreviewJson"),
kikuPreviewCompactButton:
getRequiredElement<HTMLButtonElement>("kikuPreviewCompact"),
kikuPreviewFullButton: getRequiredElement<HTMLButtonElement>("kikuPreviewFull"),
kikuPreviewFullButton:
getRequiredElement<HTMLButtonElement>("kikuPreviewFull"),
kikuPreviewError: getRequiredElement<HTMLDivElement>("kikuPreviewError"),
kikuBackButton: getRequiredElement<HTMLButtonElement>("kikuBackButton"),
kikuFinalConfirmButton:
getRequiredElement<HTMLButtonElement>("kikuFinalConfirmButton"),
kikuFinalCancelButton:
getRequiredElement<HTMLButtonElement>("kikuFinalCancelButton"),
kikuFinalConfirmButton: getRequiredElement<HTMLButtonElement>(
"kikuFinalConfirmButton",
),
kikuFinalCancelButton: getRequiredElement<HTMLButtonElement>(
"kikuFinalCancelButton",
),
kikuHint: getRequiredElement<HTMLDivElement>("kikuHint"),
runtimeOptionsModal: getRequiredElement<HTMLDivElement>("runtimeOptionsModal"),
runtimeOptionsClose: getRequiredElement<HTMLButtonElement>("runtimeOptionsClose"),
runtimeOptionsList: getRequiredElement<HTMLUListElement>("runtimeOptionsList"),
runtimeOptionsStatus: getRequiredElement<HTMLDivElement>("runtimeOptionsStatus"),
runtimeOptionsModal: getRequiredElement<HTMLDivElement>(
"runtimeOptionsModal",
),
runtimeOptionsClose: getRequiredElement<HTMLButtonElement>(
"runtimeOptionsClose",
),
runtimeOptionsList:
getRequiredElement<HTMLUListElement>("runtimeOptionsList"),
runtimeOptionsStatus: getRequiredElement<HTMLDivElement>(
"runtimeOptionsStatus",
),
subsyncModal: getRequiredElement<HTMLDivElement>("subsyncModal"),
subsyncCloseButton: getRequiredElement<HTMLButtonElement>("subsyncClose"),
subsyncEngineAlass: getRequiredElement<HTMLInputElement>("subsyncEngineAlass"),
subsyncEngineFfsubsync:
getRequiredElement<HTMLInputElement>("subsyncEngineFfsubsync"),
subsyncSourceLabel: getRequiredElement<HTMLLabelElement>("subsyncSourceLabel"),
subsyncSourceSelect: getRequiredElement<HTMLSelectElement>("subsyncSourceSelect"),
subsyncEngineAlass:
getRequiredElement<HTMLInputElement>("subsyncEngineAlass"),
subsyncEngineFfsubsync: getRequiredElement<HTMLInputElement>(
"subsyncEngineFfsubsync",
),
subsyncSourceLabel:
getRequiredElement<HTMLLabelElement>("subsyncSourceLabel"),
subsyncSourceSelect: getRequiredElement<HTMLSelectElement>(
"subsyncSourceSelect",
),
subsyncRunButton: getRequiredElement<HTMLButtonElement>("subsyncRun"),
subsyncStatus: getRequiredElement<HTMLDivElement>("subsyncStatus"),
sessionHelpModal: getRequiredElement<HTMLDivElement>("sessionHelpModal"),
sessionHelpClose: getRequiredElement<HTMLButtonElement>("sessionHelpClose"),
sessionHelpShortcut: getRequiredElement<HTMLDivElement>("sessionHelpShortcut"),
sessionHelpWarning: getRequiredElement<HTMLDivElement>("sessionHelpWarning"),
sessionHelpShortcut: getRequiredElement<HTMLDivElement>(
"sessionHelpShortcut",
),
sessionHelpWarning:
getRequiredElement<HTMLDivElement>("sessionHelpWarning"),
sessionHelpStatus: getRequiredElement<HTMLDivElement>("sessionHelpStatus"),
sessionHelpFilter: getRequiredElement<HTMLInputElement>("sessionHelpFilter"),
sessionHelpContent: getRequiredElement<HTMLDivElement>("sessionHelpContent"),
sessionHelpFilter:
getRequiredElement<HTMLInputElement>("sessionHelpFilter"),
sessionHelpContent:
getRequiredElement<HTMLDivElement>("sessionHelpContent"),
};
}