Add UI notify API and notification handling

This commit is contained in:
jelveh
2026-03-04 15:29:50 -08:00
parent 9721943040
commit d802d12a05
6 changed files with 116 additions and 7 deletions
+2 -1
View File
@@ -12,6 +12,7 @@ The UI API provides a comprehensive set of tools for creating rich user interfac
### Dialogs and Alerts
- **[`puter.ui.alert()`](/UI/alert/)** - Show alert dialogs
- **[`puter.ui.notify()`](/UI/notify/)** - Show desktop notifications
- **[`puter.ui.prompt()`](/UI/prompt/)** - Show input prompts
### Window Management
@@ -47,4 +48,4 @@ The UI API provides a comprehensive set of tools for creating rich user interfac
- **[`puter.ui.showColorPicker()`](/UI/showColorPicker/)** - Show color picker
- **[`puter.ui.showFontPicker()`](/UI/showFontPicker/)** - Show font picker
- **[`puter.ui.showSpinner()`](/UI/showSpinner/)** - Show spinner
- **[`puter.ui.socialShare()`](/UI/socialShare/)** - Share content socially
- **[`puter.ui.socialShare()`](/UI/socialShare/)** - Share content socially
+41
View File
@@ -0,0 +1,41 @@
---
title: puter.ui.notify()
description: Displays a desktop notification in Puter.
platforms: [apps]
---
Displays a desktop notification in Puter. Use this to surface app events without interrupting the user.
## Syntax
```js
puter.ui.notify(options)
```
## Parameters
#### `options` (optional)
An object that configures the notification.
- `title` (string): Title shown in the notification.
- `text` (string): Body text shown under the title.
- `icon` (string): Icon URL or Puter icon name (for example `bell.svg`).
- `round_icon` (boolean): If `true`, renders the icon as a circle. `roundIcon` is accepted as an alias.
- `uid` (string): Optional ID to associate with the notification.
- `value` (any): Optional value stored on the notification element.
## Return value
A `Promise` that resolves to the notification UID.
## Examples
```html
<script src="https://js.puter.com/v2/"></script>
<script>
puter.ui.notify({
title: 'Build finished',
text: 'Your export is ready.',
icon: 'bell.svg',
}).then((uid) => {
console.log('Notification UID:', uid);
});
</script>
```
+8
View File
@@ -678,6 +678,14 @@ let sidebar = [
source: '/UI/alert.md',
path: '/UI/alert',
},
{
title: '<code>notify()</code>',
page_title: '<code>puter.ui.notify()</code>',
title_tag: 'puter.ui.notify()',
icon: '/assets/img/function.svg',
source: '/UI/notify.md',
path: '/UI/notify',
},
{
title: '<code>contextMenu()</code>',
page_title: '<code>puter.ui.contextMenu()</code>',
+41 -6
View File
@@ -34,6 +34,7 @@ import UIWindowFontPicker from './UI/UIWindowFontPicker.js';
import UIWindowRequestPermission from './UI/UIWindowRequestPermission.js';
import UIWindowSaveAccount from './UI/UIWindowSaveAccount.js';
import UIWindowSignup from './UI/UIWindowSignup.js';
import UINotification from './UI/UINotification.js';
import { PROCESS_IPC_ATTACHED } from './definitions.js';
import TeePromise from './util/TeePromise.js';
@@ -253,6 +254,38 @@ const ipc_listener = async (event, handled) => {
}, '*');
}
//--------------------------------------------------------
// showNotification
//--------------------------------------------------------
else if ( event.data.msg === 'showNotification' ) {
const options = event.data.options ?? {};
const notification_uid = options.uid ?? `app-${app_uuid}-${msg_id}`;
let icon = window.icons['bell.svg'];
let round_icon = false;
if ( typeof options.icon === 'string' && options.icon.length > 0 ) {
icon = window.icons[options.icon] ?? options.icon;
}
if ( options.round_icon ) {
round_icon = true;
}
UINotification({
title: options.title ?? app_name ?? 'Notification',
text: options.text ?? '',
icon,
round_icon,
uid: notification_uid,
value: options.value,
});
target_iframe.contentWindow.postMessage({
original_msg_id: msg_id,
msg: 'notificationShown',
uid: notification_uid,
}, '*');
}
//--------------------------------------------------------
// getLanguage
//--------------------------------------------------------
else if ( event.data.msg === 'getLanguage' ) {
@@ -1488,12 +1521,14 @@ const ipc_listener = async (event, handled) => {
target_path, el_filedialog_window,
file_to_upload, overwrite,
}) => {
const res = await puter.fs.write(target_path,
file_to_upload,
{
dedupeName: false,
overwrite: overwrite,
});
const res = await puter.fs.write(
target_path,
file_to_upload,
{
dedupeName: false,
overwrite: overwrite,
},
);
await tell_caller_and_update_views({ res, el_filedialog_window, target_path });
};
+13
View File
@@ -499,6 +499,9 @@ class UI extends EventListener {
// execute callback
this.#callbackFunctions[e.data.original_msg_id](e.data.response);
}
else if ( e.data.msg === 'notificationShown' ) {
this.#callbackFunctions[e.data.original_msg_id](e.data.uid);
}
else if ( e.data.msg === 'languageReceived' ) {
// execute callback
this.#callbackFunctions[e.data.original_msg_id](e.data.language);
@@ -722,6 +725,16 @@ class UI extends EventListener {
});
};
notify (options) {
return new Promise((resolve) => {
const normalized = { ...(options ?? {}) };
if ( normalized.roundIcon !== undefined && normalized.round_icon === undefined ) {
normalized.round_icon = normalized.roundIcon;
}
this.#postMessageWithCallback('showNotification', resolve, { options: normalized });
});
};
showDirectoryPicker (options, callback) {
return new Promise((resolve, reject) => {
if ( ! globalThis.open ) {
+11
View File
@@ -74,6 +74,16 @@ export interface DirectoryPickerOptions {
multiple?: boolean;
}
export interface NotificationOptions {
title?: string;
text?: string;
icon?: string;
round_icon?: boolean;
roundIcon?: boolean;
uid?: string;
value?: unknown;
}
export interface AppConnectionCloseEvent {
appInstanceID: string;
statusCode?: number;
@@ -113,6 +123,7 @@ export class AppConnection {
export class UI {
alert (message?: string, buttons?: AlertButton[]): Promise<string>;
prompt (message?: string, placeholder?: string): Promise<string | null>;
notify (options?: NotificationOptions): Promise<string>;
authenticateWithPuter (): Promise<void>;
contextMenu (options: ContextMenuOptions): void;
createWindow (options?: WindowOptions): void;