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
+2 -1
View File
@@ -1,4 +1,5 @@
import type { AiFeatureConfig } from './integrations';
import type { NotificationType } from './notification';
import type { NPlusOneMatchMode } from './subtitle';
export interface NotificationOptions {
@@ -94,7 +95,7 @@ export interface AnkiConnectConfig {
overwriteImage?: boolean;
mediaInsertMode?: 'append' | 'prepend';
highlightWord?: boolean;
notificationType?: 'osd' | 'system' | 'both' | 'none';
notificationType?: NotificationType;
autoUpdateNewCards?: boolean;
};
metadata?: {
+10 -2
View File
@@ -36,6 +36,7 @@ import type {
SubtitleSidebarConfig,
SubtitleStyleConfig,
} from './subtitle';
import type { NotificationType, OverlayNotificationPosition } from './notification';
export interface WebSocketConfig {
enabled?: boolean | 'auto';
@@ -83,7 +84,7 @@ export interface StartupWarmupsConfig {
jellyfinRemoteSession?: boolean;
}
export type UpdateNotificationType = 'system' | 'osd' | 'both' | 'none';
export type UpdateNotificationType = NotificationType;
export type UpdateChannel = 'stable' | 'prerelease';
export interface UpdatesConfig {
@@ -93,6 +94,10 @@ export interface UpdatesConfig {
channel?: UpdateChannel;
}
export interface NotificationsConfig {
overlayPosition?: OverlayNotificationPosition;
}
export type LogRotation = number;
export interface LogFilesConfig {
@@ -120,6 +125,7 @@ export interface ShortcutsConfig {
openControllerSelect?: string | null;
openControllerDebug?: string | null;
toggleSubtitleSidebar?: string | null;
toggleNotificationHistory?: string | null;
}
export interface Config {
@@ -149,6 +155,7 @@ export interface Config {
immersionTracking?: ImmersionTrackingConfig;
stats?: StatsConfig;
updates?: UpdatesConfig;
notifications?: NotificationsConfig;
logging?: {
level?: 'debug' | 'info' | 'warn' | 'error';
rotation?: LogRotation;
@@ -247,7 +254,7 @@ export interface ResolvedConfig {
overwriteImage: boolean;
mediaInsertMode: 'append' | 'prepend';
highlightWord: boolean;
notificationType: 'osd' | 'system' | 'both' | 'none';
notificationType: NotificationType;
autoUpdateNewCards: boolean;
};
metadata: {
@@ -379,6 +386,7 @@ export interface ResolvedConfig {
autoOpenBrowser: boolean;
};
updates: Required<UpdatesConfig>;
notifications: Required<NotificationsConfig>;
logging: {
level: 'debug' | 'info' | 'warn' | 'error';
rotation: LogRotation;
+58
View File
@@ -0,0 +1,58 @@
export const SETTINGS_NOTIFICATION_TYPE_VALUES = ['overlay', 'system', 'both', 'none'] as const;
export const NOTIFICATION_TYPE_VALUES = [
...SETTINGS_NOTIFICATION_TYPE_VALUES,
'osd',
'osd-system',
] as const;
export const OVERLAY_NOTIFICATION_POSITION_VALUES = ['top-left', 'top', 'top-right'] as const;
export type SettingsNotificationType = (typeof SETTINGS_NOTIFICATION_TYPE_VALUES)[number];
export type NotificationType = (typeof NOTIFICATION_TYPE_VALUES)[number];
export type OverlayNotificationPosition = (typeof OVERLAY_NOTIFICATION_POSITION_VALUES)[number];
export type OverlayNotificationVariant = 'info' | 'success' | 'warning' | 'error' | 'progress';
export const OPEN_ANKI_CARD_ACTION_ID = 'open-anki-card';
export interface OverlayNotificationAction {
id: string;
label: string;
noteId?: number;
}
export interface OverlayNotificationPayload {
id?: string;
historyId?: string;
title: string;
body?: string;
image?: string;
variant?: OverlayNotificationVariant;
position?: OverlayNotificationPosition;
persistent?: boolean;
timeoutMs?: number;
actions?: OverlayNotificationAction[];
}
export interface OverlayNotificationDismissPayload {
id: string;
dismiss: true;
}
export type OverlayNotificationEventPayload =
| OverlayNotificationPayload
| OverlayNotificationDismissPayload;
export function isNotificationType(value: unknown): value is NotificationType {
return typeof value === 'string' && NOTIFICATION_TYPE_VALUES.includes(value as NotificationType);
}
export function isOverlayNotificationPosition(
value: unknown,
): value is OverlayNotificationPosition {
return (
typeof value === 'string' &&
OVERLAY_NOTIFICATION_POSITION_VALUES.includes(value as OverlayNotificationPosition)
);
}
+13
View File
@@ -41,6 +41,11 @@ import type {
RuntimeOptionState,
RuntimeOptionValue,
} from './runtime-options';
import type {
OverlayNotificationAction,
OverlayNotificationEventPayload,
OverlayNotificationPosition,
} from './notification';
export interface WindowGeometry {
x: number;
@@ -405,6 +410,13 @@ export interface ElectronAPI {
getOverlayLayer: () => 'visible' | 'modal' | null;
onSubtitle: (callback: (data: SubtitleData) => void) => void;
onOverlayPointerRecoveryRequested: (callback: () => void) => void;
onOverlayNotification: (callback: (payload: OverlayNotificationEventPayload) => void) => void;
sendOverlayNotificationAction?: (
notificationId: string,
actionId: string,
options?: Pick<OverlayNotificationAction, 'noteId'>,
) => void;
onNotificationHistoryToggle: (callback: () => void) => void;
onVisibility: (callback: (visible: boolean) => void) => void;
onSubtitlePosition: (callback: (position: SubtitlePosition | null) => void) => void;
getOverlayVisibility: () => Promise<boolean>;
@@ -433,6 +445,7 @@ export interface ElectronAPI {
) => Promise<void>;
getStatsToggleKey: () => Promise<string>;
getMarkWatchedKey: () => Promise<string>;
getOverlayNotificationPosition: () => Promise<OverlayNotificationPosition>;
markActiveVideoWatched: () => Promise<boolean>;
getControllerConfig: () => Promise<ResolvedControllerConfig>;
saveControllerConfig: (update: ControllerConfigUpdate) => Promise<void>;
+1
View File
@@ -13,6 +13,7 @@ export type SessionActionId =
| 'mineSentenceMultiple'
| 'toggleSecondarySub'
| 'toggleSubtitleSidebar'
| 'toggleNotificationHistory'
| 'markAudioCard'
| 'openRuntimeOptions'
| 'openSessionHelp'