adde desktop-angular
This commit is contained in:
154
desktop-angular/ELECTRON_NATIVE_MODALS.md
Normal file
154
desktop-angular/ELECTRON_NATIVE_MODALS.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# 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
|
Reference in New Issue
Block a user