add support for custom layout and duration for notifications
This commit is contained in:
51
src/Umbraco.Web.UI.Client/package-lock.json
generated
51
src/Umbraco.Web.UI.Client/package-lock.json
generated
@@ -14,7 +14,8 @@
|
||||
"lit": "^2.2.8",
|
||||
"openapi-typescript-fetch": "^1.1.3",
|
||||
"router-slot": "^1.5.5",
|
||||
"rxjs": "^7.5.6"
|
||||
"rxjs": "^7.5.6",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.9",
|
||||
@@ -26,6 +27,7 @@
|
||||
"@storybook/web-components": "^6.5.9",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@typescript-eslint/parser": "^5.31.0",
|
||||
"@web/dev-server-esbuild": "^0.3.1",
|
||||
@@ -5447,6 +5449,12 @@
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/webpack": {
|
||||
"version": "4.41.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz",
|
||||
@@ -22929,13 +22937,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||
"dev": true,
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "bin/uuid"
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid-browser": {
|
||||
@@ -23618,6 +23624,16 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-log/node_modules/uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"uuid": "bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-sources": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
|
||||
@@ -28434,6 +28450,12 @@
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/webpack": {
|
||||
"version": "4.41.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz",
|
||||
@@ -42112,10 +42134,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"dev": true
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"uuid-browser": {
|
||||
"version": "3.1.0",
|
||||
@@ -43006,6 +43027,14 @@
|
||||
"requires": {
|
||||
"ansi-colors": "^3.0.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"webpack-sources": {
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"lit": "^2.2.8",
|
||||
"openapi-typescript-fetch": "^1.1.3",
|
||||
"router-slot": "^1.5.5",
|
||||
"rxjs": "^7.5.6"
|
||||
"rxjs": "^7.5.6",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.9",
|
||||
@@ -51,6 +52,7 @@
|
||||
"@storybook/web-components": "^6.5.9",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@typescript-eslint/parser": "^5.31.0",
|
||||
"@web/dev-server-esbuild": "^0.3.1",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
|
||||
import { UmbContextProviderMixin } from '../core/context';
|
||||
import { UmbNotificationService } from '../core/services/notification.service';
|
||||
import { UmbNotificationService } from '../core/services/notification/notification.service';
|
||||
import { UmbDataTypeStore } from '../core/stores/data-type.store';
|
||||
import { UmbNodeStore } from '../core/stores/node.store';
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { customElement, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { UmbContextConsumerMixin } from '../../core/context';
|
||||
import { UmbNotificationService } from '../../core/services/notification.service';
|
||||
import { UmbNotificationHandler } from '../../core/services/notification/notification-handler';
|
||||
import { UmbNotificationService } from '../../core/services/notification/notification.service';
|
||||
|
||||
@customElement('umb-backoffice-notification-container')
|
||||
export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -24,7 +25,7 @@ export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin(
|
||||
];
|
||||
|
||||
@state()
|
||||
private _notifications: any[] = [];
|
||||
private _notifications: UmbNotificationHandler[] = [];
|
||||
|
||||
private _notificationService?: UmbNotificationService;
|
||||
private _notificationSubscription?: Subscription;
|
||||
@@ -41,13 +42,15 @@ export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin(
|
||||
private _useNotifications() {
|
||||
this._notificationSubscription?.unsubscribe();
|
||||
|
||||
this._notificationService?.notifications.subscribe((notifications: Array<any>) => {
|
||||
this._notificationService?.notifications.subscribe((notifications: Array<UmbNotificationHandler>) => {
|
||||
this._notifications = notifications;
|
||||
});
|
||||
|
||||
// TODO: listen to close event and remove notification from store.
|
||||
}
|
||||
|
||||
private _closedNotificationHandler(notificationHandler: UmbNotificationHandler) {
|
||||
notificationHandler.close();
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._notificationSubscription?.unsubscribe();
|
||||
@@ -55,12 +58,12 @@ export class UmbBackofficeNotificationContainer extends UmbContextConsumerMixin(
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-toast-notification-container auto-close="7000" bottom-up id="notifications">
|
||||
<uui-toast-notification-container bottom-up id="notifications">
|
||||
${repeat(
|
||||
this._notifications,
|
||||
(notification) => notification.key,
|
||||
(notification) => html` <uui-toast-notification color="positive">
|
||||
<uui-toast-notification-layout .headline=${notification.headline}> </uui-toast-notification-layout>
|
||||
(notification: UmbNotificationHandler) => notification.key,
|
||||
(notification) => html`<uui-toast-notification color="${notification.color}" .autoClose="${notification.duration}" @closed="${() => this._closedNotificationHandler(notification)}">
|
||||
${notification.element}
|
||||
</uui-toast-notification>`
|
||||
)}
|
||||
</uui-toast-notification-container>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { UmbContextConsumerMixin } from '../../core/context';
|
||||
import { UmbNodeStore } from '../../core/stores/node.store';
|
||||
import { map, Subscription } from 'rxjs';
|
||||
import { DocumentNode } from '../../mocks/data/content.data';
|
||||
import { UmbNotificationService } from '../../core/services/notification.service';
|
||||
import { UmbNotificationService } from '../../core/services/notification/notification.service';
|
||||
import { UmbExtensionManifest, UmbExtensionManifestEditorView, UmbExtensionRegistry } from '../../core/extension';
|
||||
import { IRoutingInfo, RouterSlot } from 'router-slot';
|
||||
|
||||
@@ -13,6 +13,7 @@ import { IRoutingInfo, RouterSlot } from 'router-slot';
|
||||
// TODO: Make this dynamic, use load-extensions method to loop over extensions for this node.
|
||||
import '../editor-views/editor-view-node-edit.element';
|
||||
import '../editor-views/editor-view-node-info.element';
|
||||
import { UmbNotificationDefaultData } from '../../core/services/notification/layouts/default/notification-layout-default.element';
|
||||
|
||||
@customElement('umb-node-editor')
|
||||
export class UmbNodeEditor extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -156,7 +157,8 @@ export class UmbNodeEditor extends UmbContextConsumerMixin(LitElement) {
|
||||
// TODO: What if store is not present, what if node is not loaded....
|
||||
if (this._node) {
|
||||
this._nodeStore?.save([this._node]).then(() => {
|
||||
this._notificationService?.peek('Document saved');
|
||||
const data: UmbNotificationDefaultData = { message: 'Document Saved' };
|
||||
this._notificationService?.peek('positive', { data });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin } from '../../core/context';
|
||||
import { UmbNotificationService } from '../../core/services/notification.service';
|
||||
import { UmbNotificationDefaultData } from '../../core/services/notification/layouts/default/notification-layout-default.element';
|
||||
import { UmbNotificationService } from '../../core/services/notification/notification.service';
|
||||
import type { UmbPropertyAction } from './property-action/property-action.model';
|
||||
|
||||
@customElement('umb-property-action-copy')
|
||||
@@ -26,7 +27,8 @@ export default class UmbPropertyActionCopyElement extends UmbContextConsumerMixi
|
||||
}
|
||||
|
||||
private _handleLabelClick () {
|
||||
this._notificationService?.peek('Copied to clipboard');
|
||||
const data: UmbNotificationDefaultData = { message: 'Copied to clipboard' };
|
||||
this._notificationService?.peek('positive', { data });
|
||||
// TODO: how do we want to close the menu? Testing an event based approach
|
||||
this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin } from '../../core/context';
|
||||
import { UmbNotificationService } from '../../core/services/notification.service';
|
||||
import { UmbNotificationDefaultData } from '../../core/services/notification/layouts/default/notification-layout-default.element';
|
||||
import { UmbNotificationService } from '../../core/services/notification/notification.service';
|
||||
|
||||
@customElement('umb-property-editor-context-example')
|
||||
export default class UmbPropertyEditorContextExample extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -15,7 +16,8 @@ export default class UmbPropertyEditorContextExample extends UmbContextConsumerM
|
||||
});
|
||||
}
|
||||
private _onClick = () => {
|
||||
this._notificationService?.peek('Hello from property editor');
|
||||
const data: UmbNotificationDefaultData = { message: 'Hello from property editor' };
|
||||
this._notificationService?.peek('positive', { data });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
type TempNotificationObject = {
|
||||
headline: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
export class UmbNotificationService {
|
||||
private _notifications: BehaviorSubject<Array<TempNotificationObject>> = new BehaviorSubject(
|
||||
<Array<TempNotificationObject>>[]
|
||||
);
|
||||
public readonly notifications: Observable<Array<TempNotificationObject>> = this._notifications.asObservable();
|
||||
|
||||
// TODO: this is just a quick solution to get notifications in POC. (suppose to get much more complex data set for this, enabling description, actions and event custom elements).
|
||||
peek(headline: string) {
|
||||
this._notifications.next([...this._notifications.getValue(), { headline: headline, key: Date.now().toString() }]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { UmbNotificationHandler } from '../../notification-handler';
|
||||
import { ifDefined } from 'lit-html/directives/if-defined.js';
|
||||
|
||||
export interface UmbNotificationDefaultData {
|
||||
message: string;
|
||||
headline?: string;
|
||||
}
|
||||
|
||||
@customElement('umb-notification-layout-default')
|
||||
export class UmbNotificationLayoutDefaultElement extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles
|
||||
];
|
||||
|
||||
@property({ attribute: false })
|
||||
notificationHandler!: UmbNotificationHandler;
|
||||
|
||||
@property({ type: Object })
|
||||
data!: UmbNotificationDefaultData;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-toast-notification-layout headline="${ifDefined(this.data.headline)}" class="uui-text">
|
||||
<div id="message">${this.data.message}</div>
|
||||
</uui-toast-notification-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { UmbNotificationOptions, UmbNotificationData, UmbNotificationColor } from './notification.service';
|
||||
|
||||
/**
|
||||
* @export
|
||||
* @class UmbNotificationHandler
|
||||
*/
|
||||
export class UmbNotificationHandler {
|
||||
private _closeResolver: any;
|
||||
private _closePromise: Promise<any>;
|
||||
private _elementName?: string;
|
||||
private _data: UmbNotificationData;
|
||||
|
||||
private _defaultColor: UmbNotificationColor = 'default';
|
||||
private _defaultDuration = 6000;
|
||||
private _defaultLayout = 'umb-notification-layout-default';
|
||||
|
||||
public key: string;
|
||||
public element: any;
|
||||
public color: UmbNotificationColor;
|
||||
public duration: number | null;
|
||||
|
||||
/**
|
||||
* Creates an instance of UmbNotificationHandler.
|
||||
* @param {UmbNotificationOptions} options
|
||||
* @memberof UmbNotificationHandler
|
||||
*/
|
||||
constructor (options: UmbNotificationOptions<UmbNotificationData>) {
|
||||
this.key = uuidv4();
|
||||
this.color = options.color || this._defaultColor;
|
||||
this.duration = options.duration !== undefined ? options.duration : this._defaultDuration;
|
||||
|
||||
this._elementName = options.elementName || this._defaultLayout;
|
||||
this._data = options.data;
|
||||
|
||||
this._closePromise = new Promise(res => {
|
||||
this._closeResolver = res;
|
||||
});
|
||||
|
||||
this._createLayoutElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @memberof UmbNotificationHandler
|
||||
*/
|
||||
private _createLayoutElement () {
|
||||
if (!this._elementName) return;
|
||||
this.element = document.createElement(this._elementName);
|
||||
this.element.data = this._data;
|
||||
this.element.notificationHandler = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {...any} args
|
||||
* @memberof UmbNotificationHandler
|
||||
*/
|
||||
public close (...args: any) {
|
||||
this._closeResolver(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {*}
|
||||
* @memberof UmbNotificationHandler
|
||||
*/
|
||||
public onClose (): Promise<any> {
|
||||
return this._closePromise;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { UmbNotificationHandler } from './notification-handler';
|
||||
import './layouts/default/notification-layout-default.element';
|
||||
|
||||
export type UmbNotificationData = any;
|
||||
|
||||
/**
|
||||
* @export
|
||||
* @interface UmbNotificationOptions
|
||||
* @template UmbNotificationData
|
||||
*/
|
||||
export interface UmbNotificationOptions<UmbNotificationData> {
|
||||
color?: UmbNotificationColor;
|
||||
duration?: number | null;
|
||||
elementName?: string;
|
||||
data?: UmbNotificationData;
|
||||
}
|
||||
|
||||
export type UmbNotificationColor = '' | 'default' | 'positive' | 'warning' | 'danger';
|
||||
|
||||
export class UmbNotificationService {
|
||||
private _notifications: BehaviorSubject<Array<UmbNotificationHandler>> = new BehaviorSubject(<Array<UmbNotificationHandler>>[]);
|
||||
public readonly notifications: Observable<Array<UmbNotificationHandler>> = this._notifications.asObservable();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {UmbNotificationOptions<UmbNotificationData>} options
|
||||
* @return {*} {UmbNotificationHandler}
|
||||
* @memberof UmbNotificationService
|
||||
*/
|
||||
private _open (options: UmbNotificationOptions<UmbNotificationData>): UmbNotificationHandler {
|
||||
const notificationHandler = new UmbNotificationHandler(options);
|
||||
notificationHandler.onClose().then(() => this._close(notificationHandler.key));
|
||||
|
||||
this._notifications.next([...this._notifications.getValue(), notificationHandler]);
|
||||
|
||||
return notificationHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} key
|
||||
* @memberof UmbNotificationService
|
||||
*/
|
||||
private _close (key: string) {
|
||||
this._notifications.next(this._notifications.getValue().filter(notification => notification.key !== key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a notification that automatically goes away after 6 sek.
|
||||
* @param {UmbNotificationColor} color
|
||||
* @param {UmbNotificationOptions<UmbNotificationData>} options
|
||||
* @return {*}
|
||||
* @memberof UmbNotificationService
|
||||
*/
|
||||
public peek(color: UmbNotificationColor, options: UmbNotificationOptions<UmbNotificationData>): UmbNotificationHandler {
|
||||
return this._open({ ...options, color });
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a notification that stays on the screen until dismissed by the user or custom code
|
||||
* @param {UmbNotificationColor} color
|
||||
* @param {UmbNotificationOptions<UmbNotificationData>} options
|
||||
* @return {*}
|
||||
* @memberof UmbNotificationService
|
||||
*/
|
||||
public stay(color: UmbNotificationColor, options: UmbNotificationOptions<UmbNotificationData>): UmbNotificationHandler {
|
||||
return this._open({ ...options, color, duration: null });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user