# Electron-native Modals Guide This guide documents the Electron-native modal dialogs implemented in this app. These modals are opened from the Electron main process (via a small HTML dialog window) and are configurable at runtime (text, buttons, and colors). ## Why Electron-native modals? - Work even if the Angular UI is busy/frozen - True application-level modal (owned by main process) - One-shot dialogs you can call from anywhere in renderer through IPC - Per-dialog runtime theming (background/text/button colors) ## File Map - `src/main/main.js` - IPC handler `dialog:custom` - Helper `openCustomModalWindow(config)` that creates a modal `BrowserWindow` - `src/main/modal.html` - Self-contained dialog UI (HTML/CSS/JS) - Listens for `custom-modal:config` and renders content/buttons - Sends result back via `custom-modal:result` - `src/preload/preload.js` - Exposes `showNativeModal(config)` to `window.api` - `src/frontend/src/app/ipc.service.ts` - Adds `showNativeModal(config)` convenience wrapper for Angular code ## Runtime API Call from Angular (renderer): ```ts const result = await this.ipc.showNativeModal({ title: 'Confirm Deletion', message: 'Are you sure you want to delete the item?', buttons: [ { id: 'cancel', label: 'Cancel', style: 'secondary' }, { id: 'delete', label: 'Delete', style: 'danger' } ], colors: { background: '#aa1c3a', text: '#ffffff', buttonBg: '#ffffff', buttonText: '#aa1c3a', secondaryBg: 'rgba(255,255,255,0.1)', secondaryText: '#ffffff' }, buttonStyles: { delete: { bg: '#e53935', text: '#fff' }, cancel: { bg: 'rgba(255,255,255,0.1)', text: '#ffffff' } } }); // result => { buttonId: string, buttonIndex: number, buttonLabel?: string } ``` ### Config Schema ```ts interface NativeModalButton { id: string; // identifier returned as buttonId label: string; // text on the button style?: 'primary' | 'secondary' | 'danger'; // default visual style } interface NativeModalConfig { title?: string; // dialog title message?: string; // main message (plain text) buttons?: NativeModalButton[]; // 1..3 buttons (extra are ignored) colors?: { // global color overrides background?: string; // dialog background text?: string; // title/message text color buttonBg?: string; // primary button bg buttonText?: string; // primary button text color secondaryBg?: string; // secondary button bg secondaryText?: string; // secondary button text color }; buttonStyles?: { // per-button overrides by button id [buttonId: string]: { bg?: string; // background + border color text?: string; // text color } }; } ``` ## Color Model The dialog uses CSS variables in `modal.html` with sensible defaults matching the app footer theme: - `--bg` (default `#aa1c3a`) - `--text` (default `#ffffff`) - `--btn-bg` / `--btn-text` (primary buttons) - `--btn-sec-bg` / `--btn-sec-text` (secondary buttons) You can override them per-dialog via `colors` and refine specific buttons through `buttonStyles`. ## Button Styles - `primary`: white background with red text (matches app footer theme) - `secondary`: translucent white background with white text - `danger`: red background (`#e53935`) with white text You can still override any of these via `buttonStyles` per button. ## Result Contract The renderer receives: ```ts { buttonId: string, buttonIndex: number, buttonLabel?: string } ``` If the dialog window is closed without a click, you’ll get `{ buttonId: 'closed', buttonIndex: -1 }`. ## How it Works (Flow) 1. Renderer calls `window.api.showNativeModal(config)` (exposed by preload) 2. Main process handles `dialog:custom`, opens a modal `BrowserWindow` and loads `modal.html` 3. After the page loads, main sends `custom-modal:config` with the payload 4. The page renders content and buttons; on click it emits `custom-modal:result` 5. Main resolves the original IPC with `{ buttonId, buttonIndex, buttonLabel }` ## Example: Yes/No/Cancel With Custom Colors ```ts await this.ipc.showNativeModal({ title: 'Save Changes', message: 'Save before closing?', buttons: [ { id: 'yes', label: 'Yes', style: 'primary' }, { id: 'no', label: 'No', style: 'secondary' }, { id: 'cancel', label: 'Cancel', style: 'danger' } ], colors: { background: '#1e293b', text: '#e2e8f0', buttonBg: '#e2e8f0', buttonText: '#1e293b', secondaryBg: 'rgba(226,232,240,0.12)', secondaryText: '#e2e8f0' } }); ``` ## Notes & Considerations - Maximum 3 buttons are rendered (extra are ignored) - Message is plain text (no HTML injection) - The dialog is frameless and always-on-top, sized 560x340 by default - Parent is the currently focused window, when available - Closing the dialog without a click returns `{ buttonId: 'closed' }` ## Rationale - Keep Angular modal for in-app UX consistency and speed - Add Electron-native modal for cases where UI thread may be busy, or when deep theming and app-level modality are desired