feat(notifications): add overlay notifications with position config (#110)

This commit is contained in:
2026-06-10 22:46:52 -07:00
committed by GitHub
parent c09d009a3e
commit 7be1843c41
177 changed files with 7524 additions and 440 deletions
+53
View File
@@ -53,6 +53,11 @@ export interface IpcServiceDeps {
interactive: boolean,
senderWindow: ElectronBrowserWindow | null,
) => void;
handleOverlayNotificationAction?: (
notificationId: string,
actionId: string,
noteId?: number,
) => void | Promise<void>;
openYomitanSettings: () => void;
quitApp: () => void;
toggleDevTools: () => void;
@@ -80,6 +85,7 @@ export interface IpcServiceDeps {
dispatchSessionAction?: (request: SessionActionDispatchRequest) => void | Promise<void>;
getStatsToggleKey: () => string;
getMarkWatchedKey: () => string;
getOverlayNotificationPosition: () => string;
getControllerConfig: () => ResolvedControllerConfig;
saveControllerConfig: (update: ControllerConfigUpdate) => void | Promise<void>;
saveControllerPreference: (update: ControllerPreferenceUpdate) => void | Promise<void>;
@@ -223,6 +229,25 @@ function parseSubtitleMiningContext(payload: unknown): SubtitleMiningContext | n
return parsed;
}
function parseOverlayNotificationActionPayload(
payload: unknown,
): { notificationId: string; actionId: string; noteId?: number } | null {
if (!payload || typeof payload !== 'object') return null;
const record = payload as Record<string, unknown>;
const notificationId = record.notificationId;
const actionId = record.actionId;
const noteId = record.noteId;
if (typeof notificationId !== 'string' || notificationId.trim().length === 0) return null;
if (typeof actionId !== 'string' || actionId.trim().length === 0) return null;
if (
noteId !== undefined &&
(typeof noteId !== 'number' || !Number.isInteger(noteId) || noteId <= 0)
) {
return null;
}
return { notificationId, actionId, ...(typeof noteId === 'number' ? { noteId } : {}) };
}
export interface IpcDepsRuntimeOptions {
getMainWindow: () => WindowLike | null;
getVisibleOverlayVisibility: () => boolean;
@@ -242,6 +267,11 @@ export interface IpcDepsRuntimeOptions {
interactive: boolean,
senderWindow: ElectronBrowserWindow | null,
) => void;
handleOverlayNotificationAction?: (
notificationId: string,
actionId: string,
noteId?: number,
) => void | Promise<void>;
openYomitanSettings: () => void;
quitApp: () => void;
toggleVisibleOverlay: () => void;
@@ -262,6 +292,7 @@ export interface IpcDepsRuntimeOptions {
dispatchSessionAction?: (request: SessionActionDispatchRequest) => void | Promise<void>;
getStatsToggleKey: () => string;
getMarkWatchedKey: () => string;
getOverlayNotificationPosition: () => string;
getControllerConfig: () => ResolvedControllerConfig;
saveControllerConfig: (update: ControllerConfigUpdate) => void | Promise<void>;
saveControllerPreference: (update: ControllerPreferenceUpdate) => void | Promise<void>;
@@ -312,6 +343,7 @@ export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcService
onOverlayModalOpened: options.onOverlayModalOpened,
onOverlayMouseInteractionChanged: options.onOverlayMouseInteractionChanged,
onOverlayInteractiveHint: options.onOverlayInteractiveHint,
handleOverlayNotificationAction: options.handleOverlayNotificationAction,
openYomitanSettings: options.openYomitanSettings,
recordSubtitleMiningContext: options.recordSubtitleMiningContext,
quitApp: options.quitApp,
@@ -349,6 +381,7 @@ export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcService
dispatchSessionAction: options.dispatchSessionAction ?? (async () => {}),
getStatsToggleKey: options.getStatsToggleKey,
getMarkWatchedKey: options.getMarkWatchedKey,
getOverlayNotificationPosition: options.getOverlayNotificationPosition,
getControllerConfig: options.getControllerConfig,
saveControllerConfig: options.saveControllerConfig,
saveControllerPreference: options.saveControllerPreference,
@@ -473,6 +506,22 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
electron.BrowserWindow?.fromWebContents((event as IpcMainEvent).sender) ?? null;
deps.onOverlayModalOpened(parsedModal, senderWindow);
});
ipc.on(IPC_CHANNELS.command.overlayNotificationAction, (_event: unknown, payload: unknown) => {
const parsedPayload = parseOverlayNotificationActionPayload(payload);
if (!parsedPayload) return;
void Promise.resolve(
deps.handleOverlayNotificationAction?.(
parsedPayload.notificationId,
parsedPayload.actionId,
parsedPayload.noteId,
),
).catch((error) => {
console.warn(
'Failed to handle overlay notification action:',
error instanceof Error ? error.message : String(error),
);
});
});
ipc.handle(
IPC_CHANNELS.request.youtubePickerResolve,
@@ -641,6 +690,10 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
return deps.getMarkWatchedKey();
});
ipc.handle(IPC_CHANNELS.request.getOverlayNotificationPosition, () => {
return deps.getOverlayNotificationPosition();
});
ipc.handle(IPC_CHANNELS.request.getControllerConfig, () => {
return deps.getControllerConfig();
});