feat(jimaku): auto-open modal when a video has no Japanese subtitle (#319)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Ivan Melck <ivan@spec-kitty.ai>
This commit is contained in:
authored and GitHub committed 2026-11-03 23:12:46 -08:00
1 parent d8c2315405
commit 33f5f890d5
24 files changed
+907 -22

No files matched your search

+7 -4
View File
@@ -69,20 +69,22 @@ export function createOverlayModalsAssembly(deps: OverlayModalsAssemblyDeps) {
openModal: (deps: ReturnType<typeof createOverlayHostedModalOpenDeps>) => Promise<boolean>,
unavailableMessage: string,
failureLogMessage: string,
): void {
void openModal(createOverlayHostedModalOpenDeps())
): Promise<boolean> {
return openModal(createOverlayHostedModalOpenDeps())
.then((opened) => {
if (!opened) {
deps.getOverlayNotifications().showConfiguredStatusNotification(unavailableMessage, {
variant: 'warning',
});
}
return opened;
})
.catch((error) => {
logger.error(failureLogMessage, error);
deps.getOverlayNotifications().showConfiguredStatusNotification(unavailableMessage, {
variant: 'error',
});
return false;
});
}
@@ -94,8 +96,9 @@ export function createOverlayModalsAssembly(deps: OverlayModalsAssemblyDeps) {
);
}
function openJimakuOverlay(): void {
openOverlayHostedModalWithOsd(
/** Resolves to whether the modal acknowledged opening; failures are already reported. */
function openJimakuOverlay(): Promise<boolean> {
return openOverlayHostedModalWithOsd(
openJimakuModalRuntime,
'Jimaku overlay unavailable.',
'Failed to open Jimaku overlay.',
+4
View File
@@ -159,6 +159,10 @@ function makeDeps(recorder: Recorder, state: FixtureState) {
getMediaTimingReviewRuntime: spy('getMediaTimingReviewRuntime', () => ({
dispose: spy('mediaTimingReview.dispose', resolved),
})),
getJimakuAutoOpenRuntime: spy('getJimakuAutoOpenRuntime', () => ({
handleJimakuModalOpened: spy('jimakuAutoOpen.handleJimakuModalOpened', noop),
handleJimakuModalClosed: spy('jimakuAutoOpen.handleJimakuModalClosed', noop),
})),
getSubtitleProcessingController: spy('getSubtitleProcessingController', () => ({
refreshCurrentSubtitle: spy('subtitle.refreshCurrent', () => true),
})),
+13
View File
@@ -25,6 +25,7 @@ import { createLinuxOverlayModeRuntime } from '../runtime/linux-overlay-mode-run
import { shouldRunLinuxOverlayZOrderKeepAlive } from '../runtime/linux-overlay-zorder-keepalive';
import { focusMacOSOverlayWindow } from '../runtime/macos-overlay-window-focus';
import type { createMediaTimingReviewRuntime } from '../runtime/media-timing-review';
import type { createJimakuAutoOpenRuntime } from '../runtime/jimaku-auto-open';
import type { createOverlayModalInputState } from '../runtime/overlay-modal-input-state';
import type { createOverlayGeometryRuntime } from '../runtime/overlay-geometry-runtime';
import type { createOverlayNotificationsRuntime } from '../runtime/overlay-notifications-runtime';
@@ -125,6 +126,10 @@ export interface OverlayAssemblyDeps {
ReturnType<typeof createMediaTimingReviewRuntime>,
'dispose'
>;
getJimakuAutoOpenRuntime: () => Pick<
ReturnType<typeof createJimakuAutoOpenRuntime>,
'handleJimakuModalOpened' | 'handleJimakuModalClosed'
>;
getSubtitleProcessingController: () => Pick<
PlaybackAssembly['subtitleProcessingController'],
'refreshCurrentSubtitle'
@@ -331,6 +336,8 @@ export function createOverlayAssembly(deps: OverlayAssemblyDeps) {
return;
}
void deps.getMediaTimingReviewRuntime().dispose();
// A destroyed modal window never reports its modals closed.
deps.getJimakuAutoOpenRuntime().handleJimakuModalClosed();
overlayManager.setModalWindow(null);
}
},
@@ -506,6 +513,9 @@ export function createOverlayAssembly(deps: OverlayAssemblyDeps) {
if (modal === 'media-timing-review') {
void deps.getMediaTimingReviewRuntime().dispose();
}
if (modal === 'jimaku') {
deps.getJimakuAutoOpenRuntime().handleJimakuModalClosed();
}
if (modal === 'subtitle-sidebar' && senderWindow === overlayManager.getMainWindow()) {
deps.setSubtitleSidebarRequestedOpen(false);
}
@@ -525,6 +535,9 @@ export function createOverlayAssembly(deps: OverlayAssemblyDeps) {
if (modal === 'subtitle-sidebar' && senderWindow === overlayManager.getMainWindow()) {
deps.setSubtitleSidebarRequestedOpen(true);
}
if (modal === 'jimaku') {
deps.getJimakuAutoOpenRuntime().handleJimakuModalOpened();
}
deps.overlayModalRuntime.notifyOverlayModalOpened(modal);
},
onOverlayMouseInteractionChanged: (active, senderWindow) => {
+3
View File
@@ -76,6 +76,9 @@ function makeDeps(recorder: Recorder): PlaybackAssemblyDeps {
handleMediaPathChange: spy('localSubtitles.handleMediaPathChange', noop),
handleSubtitleTrackListChange: spy('localSubtitles.handleSubtitleTrackListChange', noop),
},
jimakuAutoOpenRuntime: {
handleMediaPathChange: spy('jimakuAutoOpen.handleMediaPathChange', noop),
},
mediaTimingReviewRuntime: {
deferPlaybackResume: spy('timingReview.deferPlaybackResume', () => false),
cancelPlaybackResume: spy('timingReview.cancelPlaybackResume', noop),
+7
View File
@@ -61,6 +61,7 @@ import type { createDiscordPresenceRuntime } from '../runtime/discord-presence-r
import type { createImmersionMediaRuntime } from '../runtime/domains/startup';
import type { loadSubtitleSourceText } from '../runtime/internal-subtitle-extraction';
import type { createManagedLocalSubtitleSelectionRuntime } from '../runtime/local-subtitle-selection';
import type { createJimakuAutoOpenRuntime } from '../runtime/jimaku-auto-open';
import type { createMediaTimingReviewRuntime } from '../runtime/media-timing-review';
import type { createOverlayNotificationsRuntime } from '../runtime/overlay-notifications-runtime';
import type { createStartupOsdSequencer } from '../runtime/startup-osd-sequencer';
@@ -122,6 +123,10 @@ export interface PlaybackAssemblyDeps {
ReturnType<typeof createManagedLocalSubtitleSelectionRuntime>,
'handleMediaPathChange' | 'handleSubtitleTrackListChange'
>;
jimakuAutoOpenRuntime: Pick<
ReturnType<typeof createJimakuAutoOpenRuntime>,
'handleMediaPathChange'
>;
mediaTimingReviewRuntime: Pick<
ReturnType<typeof createMediaTimingReviewRuntime>,
'deferPlaybackResume' | 'cancelPlaybackResume'
@@ -240,6 +245,7 @@ export function createPlaybackAssembly(deps: PlaybackAssemblyDeps) {
currentMediaTokenizationGate,
startupOsdSequencer,
managedLocalSubtitleSelectionRuntime,
jimakuAutoOpenRuntime,
mediaTimingReviewRuntime,
discordPresenceRuntime,
immersionMediaRuntime,
@@ -683,6 +689,7 @@ export function createPlaybackAssembly(deps: PlaybackAssemblyDeps) {
}
currentMediaTokenizationGate.updateCurrentMediaPath(path);
managedLocalSubtitleSelectionRuntime.handleMediaPathChange(path);
jimakuAutoOpenRuntime.handleMediaPathChange(path);
startupOsdSequencer.reset();
subtitlePrefetchRuntime.clearScheduledSubtitlePrefetchRefresh();
if (!preserveParsedSubtitleCues) {