mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-10 15:13:32 -07:00
feat(notifications): add overlay notifications with position config
- Add Catppuccin Macchiato overlay notification stack with 3s transient timeout - Add `notifications.overlayPosition` config (top-left | top | top-right) - Route startup tokenization and subtitle annotation status through configured surfaces - Deduplicate rapid subtitle mode toggle notifications - Change `both` to mean overlay + system; add `osd-system` as legacy alias for old behavior - Keep `osd`/`osd-system` as config-file-only legacy values; Settings UI offers overlay/system/both/none
This commit is contained in:
+185
-42
@@ -140,6 +140,9 @@ import type {
|
||||
SubtitleData,
|
||||
SubtitleMiningContext,
|
||||
SubtitlePosition,
|
||||
OverlayNotificationPayload,
|
||||
OverlayNotificationEventPayload,
|
||||
NotificationType,
|
||||
UpdateChannel,
|
||||
WindowGeometry,
|
||||
} from './types';
|
||||
@@ -602,6 +605,12 @@ import {
|
||||
import { shouldFetchReleaseMetadataForPlatform } from './main/runtime/update/release-metadata-policy';
|
||||
import { updateLauncherFromRelease } from './main/runtime/update/launcher-updater';
|
||||
import { notifyUpdateAvailable } from './main/runtime/update/update-notifications';
|
||||
import { withConfiguredOverlayNotificationPosition } from './main/runtime/overlay-notification-position';
|
||||
import {
|
||||
getPlaybackFeedbackNotificationOptions,
|
||||
notifyConfiguredStatus,
|
||||
type ConfiguredStatusNotificationOptions,
|
||||
} from './main/runtime/configured-status-notification';
|
||||
import { createUpdateDialogPresenter } from './main/runtime/update/update-dialogs';
|
||||
import {
|
||||
runUpdateCliCommand,
|
||||
@@ -1234,7 +1243,7 @@ const youtubeFlowRuntime = createYoutubeFlowRuntime({
|
||||
mainWindow.webContents.focus();
|
||||
}
|
||||
},
|
||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||
showMpvOsd: (text: string) => showYoutubeFlowStatusNotification(text),
|
||||
reportSubtitleFailure: (message: string) => reportYoutubeSubtitleFailure(message),
|
||||
notifyPrimarySubtitleLoaded: () =>
|
||||
youtubePrimarySubtitleNotificationRuntime.markCurrentMediaPrimarySubtitleLoaded(),
|
||||
@@ -1469,6 +1478,9 @@ function getMpvPluginRuntimeConfig() {
|
||||
autoStart: config.mpv.autoStartSubMiner,
|
||||
autoStartVisibleOverlay: config.auto_start_overlay,
|
||||
autoStartPauseUntilReady: config.mpv.pauseUntilOverlayReady,
|
||||
osdMessages:
|
||||
config.ankiConnect.behavior.notificationType === 'osd' ||
|
||||
config.ankiConnect.behavior.notificationType === 'osd-system',
|
||||
texthookerEnabled: config.texthooker.launchAtStartup,
|
||||
};
|
||||
}
|
||||
@@ -1714,7 +1726,7 @@ const buildMainSubsyncRuntimeMainDepsHandler = createBuildMainSubsyncRuntimeMain
|
||||
setSubsyncInProgress: (inProgress) => {
|
||||
appState.subsyncInProgress = inProgress;
|
||||
},
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showSubsyncStatusNotification(text),
|
||||
openManualPicker: (payload) => {
|
||||
openOverlayHostedModalWithOsd(
|
||||
(deps) => openSubsyncManualModalRuntime(deps, payload),
|
||||
@@ -1736,7 +1748,10 @@ const configDerivedRuntime = createConfigDerivedRuntime(buildConfigDerivedRuntim
|
||||
const subsyncRuntime = createMainSubsyncRuntime(buildMainSubsyncRuntimeMainDepsHandler());
|
||||
const currentMediaTokenizationGate = createCurrentMediaTokenizationGate();
|
||||
const startupOsdSequencer = createStartupOsdSequencer({
|
||||
getNotificationType: () => getConfiguredStatusNotificationType(),
|
||||
showOsd: (message) => showMpvOsd(message),
|
||||
showOverlayNotification,
|
||||
showDesktopNotification: (title, options) => showDesktopNotification(title, options),
|
||||
});
|
||||
const youtubePrimarySubtitleNotificationRuntime = createYoutubePrimarySubtitleNotificationRuntime({
|
||||
getPrimarySubtitleLanguages: () => getResolvedConfig().youtube.primarySubLanguages,
|
||||
@@ -1767,11 +1782,21 @@ function isYoutubePlaybackActiveNow(): boolean {
|
||||
}
|
||||
|
||||
function reportYoutubeSubtitleFailure(message: string): void {
|
||||
const type = getResolvedConfig().ankiConnect.behavior.notificationType;
|
||||
if (type === 'osd' || type === 'both') {
|
||||
const type = getConfiguredStatusNotificationType();
|
||||
if (type === 'none') {
|
||||
return;
|
||||
}
|
||||
if (type === 'overlay' || type === 'both') {
|
||||
showOverlayNotification({
|
||||
title: 'SubMiner',
|
||||
body: message,
|
||||
variant: 'warning',
|
||||
});
|
||||
}
|
||||
if (type === 'osd' || type === 'osd-system') {
|
||||
showMpvOsd(message);
|
||||
}
|
||||
if (type === 'system' || type === 'both') {
|
||||
if (type === 'system' || type === 'both' || type === 'osd-system') {
|
||||
try {
|
||||
showDesktopNotification('SubMiner', { body: message });
|
||||
} catch {
|
||||
@@ -1782,13 +1807,22 @@ function reportYoutubeSubtitleFailure(message: string): void {
|
||||
|
||||
async function openYoutubeTrackPickerFromPlayback(): Promise<void> {
|
||||
if (youtubeFlowRuntime.hasActiveSession()) {
|
||||
showMpvOsd('YouTube subtitle flow already in progress.');
|
||||
showConfiguredStatusNotification('YouTube subtitle flow already in progress.', {
|
||||
title: 'YouTube subtitles',
|
||||
variant: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const currentMediaPath =
|
||||
appState.currentMediaPath?.trim() || appState.mpvClient?.currentVideoPath?.trim() || '';
|
||||
if (!isYoutubePlaybackActiveNow() || !currentMediaPath) {
|
||||
showMpvOsd('YouTube subtitle picker is only available during YouTube playback.');
|
||||
showConfiguredStatusNotification(
|
||||
'YouTube subtitle picker is only available during YouTube playback.',
|
||||
{
|
||||
title: 'YouTube subtitles',
|
||||
variant: 'warning',
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
await youtubeFlowRuntime.openManualPicker({
|
||||
@@ -2134,7 +2168,7 @@ const overlayShortcutsRuntime = createOverlayShortcutsRuntimeService(
|
||||
|
||||
return windowTracker.isTargetWindowFocused();
|
||||
},
|
||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||
showMpvOsd: (text: string) => showConfiguredStatusNotification(text),
|
||||
openRuntimeOptionsPalette: () => {
|
||||
openRuntimeOptionsPalette();
|
||||
},
|
||||
@@ -2177,7 +2211,9 @@ syncOverlayShortcutsForModal = (isActive: boolean): void => {
|
||||
|
||||
const buildConfigHotReloadMessageMainDepsHandler = createBuildConfigHotReloadMessageMainDepsHandler(
|
||||
{
|
||||
getNotificationType: () => getConfiguredStatusNotificationType(),
|
||||
showMpvOsd: (message) => showMpvOsd(message),
|
||||
showOverlayNotification,
|
||||
showDesktopNotification: (title, options) => showDesktopNotification(title, options),
|
||||
},
|
||||
);
|
||||
@@ -2536,8 +2572,9 @@ const characterDictionaryAutoSyncRuntime = createCharacterDictionaryAutoSyncRunt
|
||||
logWarn: (message) => logger.warn(message),
|
||||
onSyncStatus: (event) => {
|
||||
notifyCharacterDictionaryAutoSyncStatus(event, {
|
||||
getNotificationType: () => getResolvedConfig().ankiConnect.behavior.notificationType,
|
||||
getNotificationType: () => getConfiguredStatusNotificationType(),
|
||||
showOsd: (message) => showMpvOsd(message),
|
||||
showOverlayNotification,
|
||||
showDesktopNotification: (title, options) => showDesktopNotification(title, options),
|
||||
startupOsdSequencer,
|
||||
});
|
||||
@@ -2614,7 +2651,7 @@ const overlayVisibilityRuntime = createOverlayVisibilityRuntimeService(
|
||||
isMacOSPlatform: () => process.platform === 'darwin',
|
||||
isWindowsPlatform: () => process.platform === 'win32',
|
||||
showOverlayLoadingOsd: (message: string) => {
|
||||
showMpvOsd(message);
|
||||
showOverlayLoadingStatusNotification(message);
|
||||
},
|
||||
hideNonNativeOverlayWhenTargetUnfocused: () =>
|
||||
shouldRunLinuxOverlayZOrderKeepAlive() &&
|
||||
@@ -3296,6 +3333,93 @@ function broadcastToOverlayWindows(channel: string, ...args: unknown[]): void {
|
||||
overlayManager.broadcastToOverlayWindows(channel, ...args);
|
||||
}
|
||||
|
||||
function isVisibleOverlayContentReady(): boolean {
|
||||
const overlayWindow = overlayManager.getMainWindow();
|
||||
return Boolean(overlayWindow && isOverlayWindowContentReady(overlayWindow));
|
||||
}
|
||||
|
||||
function getConfiguredStatusNotificationType(): NotificationType {
|
||||
const configuredType = getResolvedConfig().ankiConnect.behavior.notificationType;
|
||||
if (configuredType === 'none' || isVisibleOverlayContentReady()) {
|
||||
return configuredType;
|
||||
}
|
||||
return 'osd';
|
||||
}
|
||||
|
||||
function sendOverlayNotificationEvent(payload: OverlayNotificationEventPayload): void {
|
||||
broadcastToOverlayWindows(IPC_CHANNELS.event.overlayNotification, payload);
|
||||
}
|
||||
|
||||
function showOverlayNotification(payload: OverlayNotificationPayload): void {
|
||||
sendOverlayNotificationEvent(
|
||||
withConfiguredOverlayNotificationPosition(payload, getResolvedConfig()),
|
||||
);
|
||||
}
|
||||
|
||||
function dismissOverlayNotification(id: string): void {
|
||||
sendOverlayNotificationEvent({ id, dismiss: true });
|
||||
}
|
||||
|
||||
function showConfiguredStatusNotification(
|
||||
message: string,
|
||||
options: ConfiguredStatusNotificationOptions = {},
|
||||
): void {
|
||||
notifyConfiguredStatus(
|
||||
message,
|
||||
{
|
||||
getNotificationType: () => getResolvedConfig().ankiConnect.behavior.notificationType,
|
||||
isOverlayReady: () => isVisibleOverlayContentReady(),
|
||||
showOsd: (text) => showMpvOsd(text),
|
||||
showOverlayNotification,
|
||||
showDesktopNotification: (title, notificationOptions) =>
|
||||
showDesktopNotification(title, notificationOptions),
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
function showConfiguredPlaybackFeedback(
|
||||
message: string,
|
||||
options: ConfiguredStatusNotificationOptions = {},
|
||||
): void {
|
||||
showConfiguredStatusNotification(message, {
|
||||
...getPlaybackFeedbackNotificationOptions(message),
|
||||
...options,
|
||||
delivery: 'feedback',
|
||||
});
|
||||
}
|
||||
|
||||
function showSubsyncStatusNotification(message: string): void {
|
||||
const syncing = message.startsWith('Subsync: syncing');
|
||||
const failed = message.toLowerCase().includes('failed');
|
||||
showConfiguredStatusNotification(message, {
|
||||
id: 'subsync-status',
|
||||
title: 'Subsync',
|
||||
variant: failed ? 'error' : syncing ? 'progress' : 'info',
|
||||
persistent: syncing,
|
||||
desktop: !syncing,
|
||||
});
|
||||
}
|
||||
|
||||
function showYoutubeFlowStatusNotification(message: string): void {
|
||||
const progress =
|
||||
message.startsWith('Downloading subtitles') ||
|
||||
message.startsWith('Loading subtitles') ||
|
||||
message.startsWith('Getting subtitles') ||
|
||||
message === 'Opening YouTube video';
|
||||
showConfiguredStatusNotification(message, {
|
||||
id: 'youtube-subtitles-status',
|
||||
title: 'YouTube subtitles',
|
||||
variant: progress ? 'progress' : 'info',
|
||||
persistent: progress,
|
||||
desktop: !progress,
|
||||
});
|
||||
}
|
||||
|
||||
function showOverlayLoadingStatusNotification(message: string): void {
|
||||
showMpvOsd(message);
|
||||
}
|
||||
|
||||
const buildBroadcastRuntimeOptionsChangedMainDepsHandler =
|
||||
createBuildBroadcastRuntimeOptionsChangedMainDepsHandler({
|
||||
broadcastRuntimeOptionsChangedRuntime,
|
||||
@@ -3386,12 +3510,12 @@ function openOverlayHostedModalWithOsd(
|
||||
void openModal(createOverlayHostedModalOpenDeps())
|
||||
.then((opened) => {
|
||||
if (!opened) {
|
||||
showMpvOsd(unavailableMessage);
|
||||
showConfiguredStatusNotification(unavailableMessage, { variant: 'warning' });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(failureLogMessage, error);
|
||||
showMpvOsd(unavailableMessage);
|
||||
showConfiguredStatusNotification(unavailableMessage, { variant: 'error' });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3422,7 +3546,7 @@ function openSessionHelpOverlay(): void {
|
||||
function openCharacterDictionaryManagerOverlay(): void {
|
||||
openCharacterDictionaryManagerWithConfigGate({
|
||||
isCharacterDictionaryEnabled: () => getResolvedConfig().subtitleStyle.nameMatchEnabled,
|
||||
getNotificationType: () => getResolvedConfig().ankiConnect.behavior.notificationType,
|
||||
getNotificationType: () => getConfiguredStatusNotificationType(),
|
||||
openManager: () => {
|
||||
openOverlayHostedModalWithOsd(
|
||||
openCharacterDictionaryManagerModalRuntime,
|
||||
@@ -3431,6 +3555,7 @@ function openCharacterDictionaryManagerOverlay(): void {
|
||||
);
|
||||
},
|
||||
showOsd: (message) => showMpvOsd(message),
|
||||
showOverlayNotification,
|
||||
showDesktopNotification: (title, options) => showDesktopNotification(title, options),
|
||||
logWarn: (message, error) => logger.warn(message, error),
|
||||
});
|
||||
@@ -3454,7 +3579,10 @@ function openControllerDebugOverlay(): void {
|
||||
|
||||
function openPlaylistBrowser(): void {
|
||||
if (!appState.mpvClient?.connected) {
|
||||
showMpvOsd('Playlist browser requires active playback.');
|
||||
showConfiguredStatusNotification('Playlist browser requires active playback.', {
|
||||
title: 'Playlist browser',
|
||||
variant: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
openOverlayHostedModalWithOsd(
|
||||
@@ -3636,7 +3764,7 @@ const {
|
||||
void appState.jellyfinRemoteSession?.reportPlaying(payload);
|
||||
},
|
||||
showMpvOsd: (text) => {
|
||||
showMpvOsd(text);
|
||||
showConfiguredStatusNotification(text, { title: 'Jellyfin' });
|
||||
},
|
||||
updateCurrentMediaTitle: (title) => {
|
||||
mediaRuntime.updateCurrentMediaTitle(title);
|
||||
@@ -3770,7 +3898,7 @@ const {
|
||||
}),
|
||||
logInfo: (message) => logger.info(message),
|
||||
logError: (message, error) => logger.error(message, error),
|
||||
showMpvOsd: (message) => showMpvOsd(message),
|
||||
showMpvOsd: (message) => showConfiguredStatusNotification(message, { title: 'Jellyfin' }),
|
||||
clearSetupWindow: () => {
|
||||
appState.jellyfinSetupWindow = null;
|
||||
},
|
||||
@@ -3938,8 +4066,10 @@ const {
|
||||
registerSubminerProtocolClient,
|
||||
} = composeAnilistSetupHandlers({
|
||||
notifyDeps: {
|
||||
getNotificationType: () => getConfiguredStatusNotificationType(),
|
||||
hasMpvClient: () => Boolean(appState.mpvClient),
|
||||
showMpvOsd: (message) => showMpvOsd(message),
|
||||
showMpvOsd: (message) => showConfiguredStatusNotification(message, { title: 'AniList' }),
|
||||
showOverlayNotification,
|
||||
showDesktopNotification: (title, options) => showDesktopNotification(title, options),
|
||||
logInfo: (message) => logger.info(message),
|
||||
},
|
||||
@@ -4266,7 +4396,7 @@ const {
|
||||
rememberAttemptedUpdateKey: (key) => {
|
||||
rememberAnilistAttemptedUpdate(key);
|
||||
},
|
||||
showMpvOsd: (message) => showMpvOsd(message),
|
||||
showMpvOsd: (message) => showConfiguredStatusNotification(message, { title: 'AniList' }),
|
||||
logInfo: (message) => logger.info(message),
|
||||
logWarn: (message) => logger.warn(message),
|
||||
minWatchSeconds: ANILIST_UPDATE_MIN_WATCH_SECONDS,
|
||||
@@ -5017,7 +5147,7 @@ let signalAutoplayReadyFromWarmTokenization: ((path: string | null | undefined)
|
||||
const {
|
||||
createMpvClientRuntimeService: createMpvClientRuntimeServiceHandler,
|
||||
updateMpvSubtitleRenderMetrics: updateMpvSubtitleRenderMetricsHandler,
|
||||
tokenizeSubtitle,
|
||||
tokenizeSubtitle: tokenizeSubtitleRuntime,
|
||||
createMecabTokenizerAndCheck,
|
||||
prewarmSubtitleDictionaries,
|
||||
startBackgroundWarmups,
|
||||
@@ -5332,13 +5462,13 @@ const {
|
||||
ensureJlptDictionaryLookup: () => jlptDictionaryRuntime.ensureJlptDictionaryLookup(),
|
||||
ensureFrequencyDictionaryLookup: () =>
|
||||
frequencyDictionaryRuntime.ensureFrequencyDictionaryLookup(),
|
||||
showMpvOsd: (message: string) => showMpvOsd(message),
|
||||
showMpvOsd: (message: string) => showConfiguredStatusNotification(message),
|
||||
showLoadingOsd: (message: string) => startupOsdSequencer.showAnnotationLoading(message),
|
||||
showLoadedOsd: (message: string) =>
|
||||
startupOsdSequencer.markAnnotationLoadingComplete(message),
|
||||
shouldShowOsdNotification: () => {
|
||||
const type = getResolvedConfig().ankiConnect.behavior.notificationType;
|
||||
return type === 'osd' || type === 'both';
|
||||
const type = getConfiguredStatusNotificationType();
|
||||
return type === 'osd' || type === 'osd-system';
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -5391,6 +5521,14 @@ const {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function tokenizeSubtitle(text: string): Promise<SubtitleData> {
|
||||
if (!isTokenizationWarmupReady()) {
|
||||
startupOsdSequencer.showTokenizationLoading('Loading subtitle tokenization...');
|
||||
}
|
||||
return await tokenizeSubtitleRuntime(text);
|
||||
}
|
||||
|
||||
signalAutoplayReadyFromWarmTokenization = createAutoplayTokenizationWarmRelease({
|
||||
isTokenizationWarmupReady: () => isTokenizationWarmupReady(),
|
||||
startTokenizationWarmups: async () => {
|
||||
@@ -5891,8 +6029,7 @@ function openYomitanSettings(): boolean {
|
||||
logger.warn(
|
||||
'Yomitan settings window disabled while yomitan.externalProfilePath is configured because external profile mode is read-only.',
|
||||
);
|
||||
showDesktopNotification('SubMiner', { body: message });
|
||||
showMpvOsd(message);
|
||||
showConfiguredStatusNotification(message, { variant: 'warning' });
|
||||
return false;
|
||||
}
|
||||
openYomitanSettingsHandler();
|
||||
@@ -5979,7 +6116,7 @@ const {
|
||||
},
|
||||
numericShortcutRuntimeMainDeps: {
|
||||
globalShortcut,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredStatusNotification(text),
|
||||
setTimer: (handler, timeoutMs) => setTimeout(handler, timeoutMs),
|
||||
clearTimer: (timer) => clearTimeout(timer),
|
||||
},
|
||||
@@ -6214,6 +6351,7 @@ function getUpdateService() {
|
||||
{ notificationType: getResolvedConfig().updates.notificationType, version },
|
||||
{
|
||||
showSystemNotification: (title, body) => showDesktopNotification(title, { body }),
|
||||
showOverlayNotification,
|
||||
showOsdNotification: (message) => {
|
||||
showMpvOsd(message);
|
||||
},
|
||||
@@ -6238,7 +6376,7 @@ const cycleSecondarySubMode = createCycleSecondarySubModeRuntimeHandler({
|
||||
broadcastToOverlayWindows: (channel, mode) => {
|
||||
broadcastToOverlayWindows(channel, mode);
|
||||
},
|
||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||
showMpvOsd: (text: string) => showConfiguredPlaybackFeedback(text),
|
||||
},
|
||||
cycleSecondarySubMode: (deps) => cycleSecondarySubModeCore(deps),
|
||||
});
|
||||
@@ -6275,7 +6413,7 @@ const buildUpdateLastCardFromClipboardMainDepsHandler =
|
||||
createBuildUpdateLastCardFromClipboardMainDepsHandler({
|
||||
getAnkiIntegration: () => appState.ankiIntegration,
|
||||
readClipboardText: () => clipboard.readText(),
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredStatusNotification(text),
|
||||
updateLastCardFromClipboardCore,
|
||||
});
|
||||
const updateLastCardFromClipboardMainDeps = buildUpdateLastCardFromClipboardMainDepsHandler();
|
||||
@@ -6294,7 +6432,7 @@ const refreshKnownWordCacheHandler = createRefreshKnownWordCacheHandler(
|
||||
|
||||
const buildTriggerFieldGroupingMainDepsHandler = createBuildTriggerFieldGroupingMainDepsHandler({
|
||||
getAnkiIntegration: () => appState.ankiIntegration,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredStatusNotification(text),
|
||||
triggerFieldGroupingCore,
|
||||
});
|
||||
const triggerFieldGroupingMainDeps = buildTriggerFieldGroupingMainDepsHandler();
|
||||
@@ -6303,7 +6441,7 @@ const triggerFieldGroupingHandler = createTriggerFieldGroupingHandler(triggerFie
|
||||
const buildMarkLastCardAsAudioCardMainDepsHandler =
|
||||
createBuildMarkLastCardAsAudioCardMainDepsHandler({
|
||||
getAnkiIntegration: () => appState.ankiIntegration,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredStatusNotification(text),
|
||||
markLastCardAsAudioCardCore,
|
||||
});
|
||||
const markLastCardAsAudioCardMainDeps = buildMarkLastCardAsAudioCardMainDepsHandler();
|
||||
@@ -6314,7 +6452,7 @@ const markLastCardAsAudioCardHandler = createMarkLastCardAsAudioCardHandler(
|
||||
const buildMineSentenceCardMainDepsHandler = createBuildMineSentenceCardMainDepsHandler({
|
||||
getAnkiIntegration: () => appState.ankiIntegration,
|
||||
getMpvClient: () => appState.mpvClient,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredStatusNotification(text),
|
||||
mineSentenceCardCore,
|
||||
recordCardsMined: (count, noteIds) => {
|
||||
ensureImmersionTrackerStarted();
|
||||
@@ -6328,7 +6466,7 @@ const mineSentenceCardHandler = createMineSentenceCardHandler(
|
||||
const buildHandleMultiCopyDigitMainDepsHandler = createBuildHandleMultiCopyDigitMainDepsHandler({
|
||||
getSubtitleTimingTracker: () => appState.subtitleTimingTracker,
|
||||
writeClipboardText: (text) => clipboard.writeText(text),
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredPlaybackFeedback(text),
|
||||
handleMultiCopyDigitCore,
|
||||
});
|
||||
const handleMultiCopyDigitMainDeps = buildHandleMultiCopyDigitMainDepsHandler();
|
||||
@@ -6337,7 +6475,7 @@ const handleMultiCopyDigitHandler = createHandleMultiCopyDigitHandler(handleMult
|
||||
const buildCopyCurrentSubtitleMainDepsHandler = createBuildCopyCurrentSubtitleMainDepsHandler({
|
||||
getSubtitleTimingTracker: () => appState.subtitleTimingTracker,
|
||||
writeClipboardText: (text) => clipboard.writeText(text),
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredStatusNotification(text),
|
||||
copyCurrentSubtitleCore,
|
||||
});
|
||||
const copyCurrentSubtitleMainDeps = buildCopyCurrentSubtitleMainDepsHandler();
|
||||
@@ -6348,7 +6486,7 @@ const buildHandleMineSentenceDigitMainDepsHandler =
|
||||
getSubtitleTimingTracker: () => appState.subtitleTimingTracker,
|
||||
getAnkiIntegration: () => appState.ankiIntegration,
|
||||
getCurrentSecondarySubText: () => appState.mpvClient?.currentSecondarySubText || undefined,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredStatusNotification(text),
|
||||
logError: (message, err) => {
|
||||
logger.error(message, err);
|
||||
},
|
||||
@@ -6391,7 +6529,7 @@ const buildAppendClipboardVideoToQueueMainDepsHandler =
|
||||
appendClipboardVideoToQueueRuntime,
|
||||
getMpvClient: () => appState.mpvClient,
|
||||
readClipboardText: () => clipboard.readText(),
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredStatusNotification(text),
|
||||
sendMpvCommand: (command) => {
|
||||
sendMpvCommandRuntime(appState.mpvClient, command);
|
||||
},
|
||||
@@ -6530,7 +6668,7 @@ const shiftSubtitleDelayToAdjacentCueHandler = createShiftSubtitleDelayToAdjacen
|
||||
logger.warn('Failed to save Jellyfin subtitle delay.');
|
||||
}
|
||||
},
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredPlaybackFeedback(text),
|
||||
});
|
||||
|
||||
async function dispatchSessionAction(request: SessionActionDispatchRequest): Promise<void> {
|
||||
@@ -6587,12 +6725,12 @@ async function dispatchSessionAction(request: SessionActionDispatchRequest): Pro
|
||||
}
|
||||
return applyRuntimeOptionResultRuntime(
|
||||
appState.runtimeOptionsManager.cycleOption(id, direction),
|
||||
(text) => showMpvOsd(text),
|
||||
(text) => showConfiguredPlaybackFeedback(text),
|
||||
);
|
||||
},
|
||||
playNextPlaylistItem: () =>
|
||||
sendMpvCommandRuntime(appState.mpvClient, ['playlist-next', 'force']),
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
showMpvOsd: (text) => showConfiguredPlaybackFeedback(text),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6614,10 +6752,11 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
|
||||
}
|
||||
return applyRuntimeOptionResultRuntime(
|
||||
appState.runtimeOptionsManager.cycleOption(id, direction),
|
||||
(text) => showMpvOsd(text),
|
||||
(text) => showConfiguredPlaybackFeedback(text),
|
||||
);
|
||||
},
|
||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||
showMpvOsd: (text: string) => showConfiguredStatusNotification(text),
|
||||
showPlaybackFeedback: (text: string) => showConfiguredPlaybackFeedback(text),
|
||||
replayCurrentSubtitle: () => replayCurrentSubtitleRuntime(appState.mpvClient),
|
||||
playNextSubtitle: () => playNextSubtitleRuntime(appState.mpvClient),
|
||||
shiftSubDelayToAdjacentSubtitle: (direction) =>
|
||||
@@ -6633,7 +6772,7 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
|
||||
registration: {
|
||||
runtimeOptions: {
|
||||
getRuntimeOptionsManager: () => appState.runtimeOptionsManager,
|
||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||
showMpvOsd: (text: string) => showConfiguredPlaybackFeedback(text),
|
||||
},
|
||||
mainDeps: {
|
||||
getMainWindow: () => overlayManager.getMainWindow(),
|
||||
@@ -6970,6 +7109,7 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
|
||||
},
|
||||
getKnownWordCacheStatePath: () => path.join(USER_DATA_PATH, 'known-words-cache.json'),
|
||||
showDesktopNotification,
|
||||
showOverlayNotification,
|
||||
createFieldGroupingCallback: () => createFieldGroupingCallback(),
|
||||
broadcastRuntimeOptionsChanged: () => broadcastRuntimeOptionsChanged(),
|
||||
getFieldGroupingResolver: () => getFieldGroupingResolver(),
|
||||
@@ -7006,7 +7146,7 @@ const { handleCliCommand, handleInitialArgs } = composeCliStartupHandlers({
|
||||
openExternal: (url: string) => shell.openExternal(url),
|
||||
logBrowserOpenError: (url: string, error: unknown) =>
|
||||
logger.error(`Failed to open browser for texthooker URL: ${url}`, error),
|
||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||
showMpvOsd: (text: string) => showConfiguredStatusNotification(text),
|
||||
initializeOverlayRuntime: () => initializeOverlayRuntime(),
|
||||
toggleVisibleOverlay: () => toggleVisibleOverlay(),
|
||||
togglePrimarySubtitleBar: () => togglePrimarySubtitleBar(),
|
||||
@@ -7232,6 +7372,7 @@ const { createMainWindow: createMainWindowHandler, createModalWindow: createModa
|
||||
onVisibleWindowBlurred: () => scheduleVisibleOverlayBlurRefresh(),
|
||||
onVisibleWindowFocused: () => requestLinuxOverlayZOrderFollow(),
|
||||
onWindowContentReady: () => {
|
||||
dismissOverlayNotification('overlay-loading-status');
|
||||
overlayVisibilityRuntime.updateVisibleOverlayVisibility();
|
||||
if (appState.currentSubText.trim()) {
|
||||
subtitleProcessingController.refreshCurrentSubtitle(appState.currentSubText);
|
||||
@@ -7274,7 +7415,8 @@ function getJellyfinTrayDiscoveryDeps() {
|
||||
startRemoteSession: (options: { explicit: true }) => startJellyfinRemoteSession(options),
|
||||
refreshTrayMenu: () => refreshTrayMenuIfPresent(),
|
||||
logger,
|
||||
showMpvOsd: (message: string) => showMpvOsd(message),
|
||||
showMpvOsd: (message: string) =>
|
||||
showConfiguredStatusNotification(message, { title: 'Jellyfin' }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7421,6 +7563,7 @@ const { initializeOverlayRuntime: initializeOverlayRuntimeHandler } =
|
||||
getOverlayWindows: () => getOverlayWindows(),
|
||||
getResolvedConfig: () => getResolvedConfig(),
|
||||
showDesktopNotification,
|
||||
showOverlayNotification,
|
||||
createFieldGroupingCallback: () => createFieldGroupingCallback(),
|
||||
getKnownWordCacheStatePath: () => path.join(USER_DATA_PATH, 'known-words-cache.json'),
|
||||
shouldStartAnkiIntegration: () =>
|
||||
|
||||
Reference in New Issue
Block a user