mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
Fix overlay modal dispatch and restore behavior
This commit is contained in:
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -2,7 +2,3 @@
|
|||||||
path = vendor/texthooker-ui
|
path = vendor/texthooker-ui
|
||||||
url = https://github.com/ksyasuda/texthooker-ui.git
|
url = https://github.com/ksyasuda/texthooker-ui.git
|
||||||
branch = subminer
|
branch = subminer
|
||||||
|
|
||||||
[submodule "vendor/ani-cli"]
|
|
||||||
path = vendor/ani-cli
|
|
||||||
url = https://github.com/pystardust/ani-cli
|
|
||||||
|
|||||||
159
src/main.ts
159
src/main.ts
@@ -176,6 +176,12 @@ if (process.platform === "linux") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TEXTHOOKER_PORT = 5174;
|
const DEFAULT_TEXTHOOKER_PORT = 5174;
|
||||||
|
const DEFAULT_MPV_LOG_FILE = path.join(
|
||||||
|
os.homedir(),
|
||||||
|
".cache",
|
||||||
|
"SubMiner",
|
||||||
|
"mp.log",
|
||||||
|
);
|
||||||
function resolveConfigDir(): string {
|
function resolveConfigDir(): string {
|
||||||
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
|
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
|
||||||
const baseDirs = Array.from(
|
const baseDirs = Array.from(
|
||||||
@@ -212,6 +218,7 @@ function resolveConfigDir(): string {
|
|||||||
|
|
||||||
const CONFIG_DIR = resolveConfigDir();
|
const CONFIG_DIR = resolveConfigDir();
|
||||||
const USER_DATA_PATH = CONFIG_DIR;
|
const USER_DATA_PATH = CONFIG_DIR;
|
||||||
|
const DEFAULT_MPV_LOG_PATH = process.env.SUBMINER_MPV_LOG?.trim() || DEFAULT_MPV_LOG_FILE;
|
||||||
const configService = new ConfigService(CONFIG_DIR);
|
const configService = new ConfigService(CONFIG_DIR);
|
||||||
const isDev =
|
const isDev =
|
||||||
process.argv.includes("--dev") || process.argv.includes("--debug");
|
process.argv.includes("--dev") || process.argv.includes("--debug");
|
||||||
@@ -270,6 +277,7 @@ let currentSubAssText = "";
|
|||||||
let windowTracker: BaseWindowTracker | null = null;
|
let windowTracker: BaseWindowTracker | null = null;
|
||||||
let subtitlePosition: SubtitlePosition | null = null;
|
let subtitlePosition: SubtitlePosition | null = null;
|
||||||
let currentMediaPath: string | null = null;
|
let currentMediaPath: string | null = null;
|
||||||
|
let currentMediaTitle: string | null = null;
|
||||||
let pendingSubtitlePosition: SubtitlePosition | null = null;
|
let pendingSubtitlePosition: SubtitlePosition | null = null;
|
||||||
let mecabTokenizer: MecabTokenizer | null = null;
|
let mecabTokenizer: MecabTokenizer | null = null;
|
||||||
let keybindings: Keybinding[] = [];
|
let keybindings: Keybinding[] = [];
|
||||||
@@ -297,8 +305,10 @@ const overlayContentMeasurementStore = createOverlayContentMeasurementStoreServi
|
|||||||
console.warn(message);
|
console.warn(message);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
type OverlayHostedModal = "runtime-options" | "subsync";
|
type OverlayHostedModal = "runtime-options" | "subsync" | "jimaku";
|
||||||
|
type OverlayHostLayer = "visible" | "invisible";
|
||||||
const restoreVisibleOverlayOnModalClose = new Set<OverlayHostedModal>();
|
const restoreVisibleOverlayOnModalClose = new Set<OverlayHostedModal>();
|
||||||
|
const overlayModalAutoShownLayer = new Map<OverlayHostedModal, OverlayHostLayer>();
|
||||||
|
|
||||||
function getFieldGroupingResolver(): ((choice: KikuFieldGroupingChoice) => void) | null {
|
function getFieldGroupingResolver(): ((choice: KikuFieldGroupingChoice) => void) | null {
|
||||||
return fieldGroupingResolver;
|
return fieldGroupingResolver;
|
||||||
@@ -328,8 +338,11 @@ const fieldGroupingOverlayRuntime = createFieldGroupingOverlayRuntimeService<Ove
|
|||||||
getResolver: () => getFieldGroupingResolver(),
|
getResolver: () => getFieldGroupingResolver(),
|
||||||
setResolver: (resolver) => setFieldGroupingResolver(resolver),
|
setResolver: (resolver) => setFieldGroupingResolver(resolver),
|
||||||
getRestoreVisibleOverlayOnModalClose: () => restoreVisibleOverlayOnModalClose,
|
getRestoreVisibleOverlayOnModalClose: () => restoreVisibleOverlayOnModalClose,
|
||||||
|
sendToVisibleOverlay: (channel, payload) => {
|
||||||
|
sendToActiveOverlayWindow(channel, payload);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const sendToVisibleOverlay = fieldGroupingOverlayRuntime.sendToVisibleOverlay;
|
|
||||||
const createFieldGroupingCallback =
|
const createFieldGroupingCallback =
|
||||||
fieldGroupingOverlayRuntime.createFieldGroupingCallback;
|
fieldGroupingOverlayRuntime.createFieldGroupingCallback;
|
||||||
|
|
||||||
@@ -357,6 +370,79 @@ function broadcastRuntimeOptionsChanged(): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTargetOverlayWindow(): {
|
||||||
|
window: BrowserWindow;
|
||||||
|
layer: OverlayHostLayer;
|
||||||
|
} | null {
|
||||||
|
const visibleMainWindow = overlayManager.getMainWindow();
|
||||||
|
const invisibleWindow = overlayManager.getInvisibleWindow();
|
||||||
|
|
||||||
|
if (visibleMainWindow && !visibleMainWindow.isDestroyed()) {
|
||||||
|
return { window: visibleMainWindow, layer: "visible" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invisibleWindow && !invisibleWindow.isDestroyed()) {
|
||||||
|
return { window: invisibleWindow, layer: "invisible" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOverlayWindowForModal(window: BrowserWindow, layer: OverlayHostLayer): void {
|
||||||
|
if (layer === "invisible" && typeof window.showInactive === "function") {
|
||||||
|
window.showInactive();
|
||||||
|
} else {
|
||||||
|
window.show();
|
||||||
|
}
|
||||||
|
if (!window.isFocused()) {
|
||||||
|
window.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendToActiveOverlayWindow(
|
||||||
|
channel: string,
|
||||||
|
payload?: unknown,
|
||||||
|
runtimeOptions?: { restoreOnModalClose?: OverlayHostedModal },
|
||||||
|
): void {
|
||||||
|
const target = getTargetOverlayWindow();
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const { window: targetWindow, layer } = target;
|
||||||
|
const wasVisible = targetWindow.isVisible();
|
||||||
|
const restoreOnModalClose = runtimeOptions?.restoreOnModalClose;
|
||||||
|
|
||||||
|
const sendNow = (): void => {
|
||||||
|
if (payload === undefined) {
|
||||||
|
targetWindow.webContents.send(channel);
|
||||||
|
} else {
|
||||||
|
targetWindow.webContents.send(channel, payload);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!wasVisible) {
|
||||||
|
showOverlayWindowForModal(targetWindow, layer);
|
||||||
|
}
|
||||||
|
if (!wasVisible && restoreOnModalClose) {
|
||||||
|
restoreVisibleOverlayOnModalClose.add(restoreOnModalClose);
|
||||||
|
overlayModalAutoShownLayer.set(restoreOnModalClose, layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetWindow.webContents.isLoading()) {
|
||||||
|
targetWindow.webContents.once("did-finish-load", () => {
|
||||||
|
if (
|
||||||
|
targetWindow &&
|
||||||
|
!targetWindow.isDestroyed() &&
|
||||||
|
!targetWindow.webContents.isLoading()
|
||||||
|
) {
|
||||||
|
sendNow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNow();
|
||||||
|
}
|
||||||
|
|
||||||
function setOverlayDebugVisualizationEnabled(enabled: boolean): void {
|
function setOverlayDebugVisualizationEnabled(enabled: boolean): void {
|
||||||
setOverlayDebugVisualizationEnabledRuntimeService(
|
setOverlayDebugVisualizationEnabledRuntimeService(
|
||||||
overlayDebugVisualizationEnabled,
|
overlayDebugVisualizationEnabled,
|
||||||
@@ -368,7 +454,11 @@ function setOverlayDebugVisualizationEnabled(enabled: boolean): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openRuntimeOptionsPalette(): void { sendToVisibleOverlay("runtime-options:open", undefined, { restoreOnModalClose: "runtime-options" }); }
|
function openRuntimeOptionsPalette(): void {
|
||||||
|
sendToActiveOverlayWindow("runtime-options:open", undefined, {
|
||||||
|
restoreOnModalClose: "runtime-options",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getResolvedConfig() { return configService.getConfig(); }
|
function getResolvedConfig() { return configService.getConfig(); }
|
||||||
|
|
||||||
@@ -437,6 +527,9 @@ function saveSubtitlePosition(position: SubtitlePosition): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateCurrentMediaPath(mediaPath: unknown): void {
|
function updateCurrentMediaPath(mediaPath: unknown): void {
|
||||||
|
if (typeof mediaPath !== "string" || !isRemoteMediaPath(mediaPath)) {
|
||||||
|
currentMediaTitle = null;
|
||||||
|
}
|
||||||
updateCurrentMediaPathService({
|
updateCurrentMediaPathService({
|
||||||
mediaPath,
|
mediaPath,
|
||||||
currentMediaPath,
|
currentMediaPath,
|
||||||
@@ -458,6 +551,21 @@ function updateCurrentMediaPath(mediaPath: unknown): void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateCurrentMediaTitle(mediaTitle: unknown): void {
|
||||||
|
if (typeof mediaTitle === "string") {
|
||||||
|
const sanitized = mediaTitle.trim();
|
||||||
|
currentMediaTitle = sanitized.length > 0 ? sanitized : null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentMediaTitle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveMediaPathForJimaku(mediaPath: string | null): string | null {
|
||||||
|
return mediaPath && isRemoteMediaPath(mediaPath) && currentMediaTitle
|
||||||
|
? currentMediaTitle
|
||||||
|
: mediaPath;
|
||||||
|
}
|
||||||
|
|
||||||
let subsyncInProgress = false;
|
let subsyncInProgress = false;
|
||||||
let initialArgs: CliArgs;
|
let initialArgs: CliArgs;
|
||||||
let mpvSocketPath = getDefaultSocketPath();
|
let mpvSocketPath = getDefaultSocketPath();
|
||||||
@@ -552,6 +660,9 @@ const startupState = runStartupBootstrapRuntimeService({
|
|||||||
updateCurrentMediaPath: (mediaPath) => {
|
updateCurrentMediaPath: (mediaPath) => {
|
||||||
updateCurrentMediaPath(mediaPath);
|
updateCurrentMediaPath(mediaPath);
|
||||||
},
|
},
|
||||||
|
updateCurrentMediaTitle: (mediaTitle) => {
|
||||||
|
updateCurrentMediaTitle(mediaTitle);
|
||||||
|
},
|
||||||
updateMpvSubtitleRenderMetrics: (patch) => {
|
updateMpvSubtitleRenderMetrics: (patch) => {
|
||||||
updateMpvSubtitleRenderMetrics(patch);
|
updateMpvSubtitleRenderMetrics(patch);
|
||||||
},
|
},
|
||||||
@@ -944,7 +1055,9 @@ function getOverlayShortcutRuntimeHandlers() {
|
|||||||
openRuntimeOptionsPalette();
|
openRuntimeOptionsPalette();
|
||||||
},
|
},
|
||||||
openJimaku: () => {
|
openJimaku: () => {
|
||||||
sendToVisibleOverlay("jimaku:open");
|
sendToActiveOverlayWindow("jimaku:open", undefined, {
|
||||||
|
restoreOnModalClose: "jimaku",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
markAudioCard: () => markLastCardAsAudioCard(),
|
markAudioCard: () => markLastCardAsAudioCard(),
|
||||||
copySubtitleMultiple: (timeoutMs) => {
|
copySubtitleMultiple: (timeoutMs) => {
|
||||||
@@ -994,6 +1107,7 @@ function cycleSecondarySubMode(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showMpvOsd(text: string): void {
|
function showMpvOsd(text: string): void {
|
||||||
|
appendToMpvLog(`[OSD] ${text}`);
|
||||||
showMpvOsdRuntimeService(
|
showMpvOsdRuntimeService(
|
||||||
mpvClient,
|
mpvClient,
|
||||||
text,
|
text,
|
||||||
@@ -1003,6 +1117,19 @@ function showMpvOsd(text: string): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function appendToMpvLog(message: string): void {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.dirname(DEFAULT_MPV_LOG_PATH), { recursive: true });
|
||||||
|
fs.appendFileSync(
|
||||||
|
DEFAULT_MPV_LOG_PATH,
|
||||||
|
`[${new Date().toISOString()}] ${message}\n`,
|
||||||
|
{ encoding: "utf8" },
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// best-effort logging
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const numericShortcutRuntime = createNumericShortcutRuntimeService({
|
const numericShortcutRuntime = createNumericShortcutRuntimeService({
|
||||||
globalShortcut,
|
globalShortcut,
|
||||||
showMpvOsd: (text) => showMpvOsd(text),
|
showMpvOsd: (text) => showMpvOsd(text),
|
||||||
@@ -1022,7 +1149,7 @@ function getSubsyncRuntimeDeps() {
|
|||||||
},
|
},
|
||||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||||
openManualPicker: (payload: SubsyncManualPayload) => {
|
openManualPicker: (payload: SubsyncManualPayload) => {
|
||||||
sendToVisibleOverlay("subsync:open-manual", payload, {
|
sendToActiveOverlayWindow("subsync:open-manual", payload, {
|
||||||
restoreOnModalClose: "subsync",
|
restoreOnModalClose: "subsync",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -1270,8 +1397,24 @@ function toggleOverlay(): void { toggleVisibleOverlay(); }
|
|||||||
function handleOverlayModalClosed(modal: OverlayHostedModal): void {
|
function handleOverlayModalClosed(modal: OverlayHostedModal): void {
|
||||||
if (!restoreVisibleOverlayOnModalClose.has(modal)) return;
|
if (!restoreVisibleOverlayOnModalClose.has(modal)) return;
|
||||||
restoreVisibleOverlayOnModalClose.delete(modal);
|
restoreVisibleOverlayOnModalClose.delete(modal);
|
||||||
if (restoreVisibleOverlayOnModalClose.size === 0) {
|
const layer = overlayModalAutoShownLayer.get(modal);
|
||||||
setVisibleOverlayVisible(false);
|
overlayModalAutoShownLayer.delete(modal);
|
||||||
|
if (!layer) return;
|
||||||
|
const shouldKeepLayerVisible = [...restoreVisibleOverlayOnModalClose].some(
|
||||||
|
(pendingModal) => overlayModalAutoShownLayer.get(pendingModal) === layer,
|
||||||
|
);
|
||||||
|
if (shouldKeepLayerVisible) return;
|
||||||
|
|
||||||
|
if (layer === "visible") {
|
||||||
|
const mainWindow = overlayManager.getMainWindow();
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.hide();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const invisibleWindow = overlayManager.getInvisibleWindow();
|
||||||
|
if (invisibleWindow && !invisibleWindow.isDestroyed()) {
|
||||||
|
invisibleWindow.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1379,7 +1522,7 @@ registerAnkiJimakuIpcRuntimeService(
|
|||||||
broadcastRuntimeOptionsChanged: () => broadcastRuntimeOptionsChanged(),
|
broadcastRuntimeOptionsChanged: () => broadcastRuntimeOptionsChanged(),
|
||||||
getFieldGroupingResolver: () => getFieldGroupingResolver(),
|
getFieldGroupingResolver: () => getFieldGroupingResolver(),
|
||||||
setFieldGroupingResolver: (resolver) => setFieldGroupingResolver(resolver),
|
setFieldGroupingResolver: (resolver) => setFieldGroupingResolver(resolver),
|
||||||
parseMediaInfo: (mediaPath) => parseMediaInfo(mediaPath),
|
parseMediaInfo: (mediaPath) => parseMediaInfo(resolveMediaPathForJimaku(mediaPath)),
|
||||||
getCurrentMediaPath: () => currentMediaPath,
|
getCurrentMediaPath: () => currentMediaPath,
|
||||||
jimakuFetchJson: (endpoint, query) => jimakuFetchJson(endpoint, query),
|
jimakuFetchJson: (endpoint, query) => jimakuFetchJson(endpoint, query),
|
||||||
getJimakuMaxEntryResults: () => getJimakuMaxEntryResults(),
|
getJimakuMaxEntryResults: () => getJimakuMaxEntryResults(),
|
||||||
|
|||||||
1
vendor/ani-cli
vendored
1
vendor/ani-cli
vendored
Submodule vendor/ani-cli deleted from c8aa791982
Reference in New Issue
Block a user