mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
[CLA Approved] Remove notification independently (#6079)
* Add closeOverlay and notifications-count attributes to notification-message
* Add "Dismiss notification" button to NotificationMessage
* Add aria-labels to Alert Banner
* Add aria-modal and role dialog to OverlayComponent
* Add ARIA roles to NotificationMessage and NotificationsList
* Add ARIA role alert to NotificationBanner
* Create Notification E2E Test for dismissing the 'Save successful' dialog
* refactor: fix up types for NotificationAPI
* test: Add `createNotification` appAction
* test: add basic test for `createNotification`
* test: add stub for notification functional test
* Create clock using createDomainObjectWithDefaults
* Replace text-selection with button-selection
* Uninstall @types/eventemitter3
* Revert "Uninstall @types/eventemitter3"
This reverts commit 37e4df9a75
.
* fix: remove duplicate dependency
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
This commit is contained in:
parent
22ce817443
commit
902d80c214
@ -45,6 +45,14 @@
|
|||||||
* @property {string} url the relative url to the object (for use with `page.goto()`)
|
* @property {string} url the relative url to the object (for use with `page.goto()`)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines parameters to be used in the creation of a notification.
|
||||||
|
* @typedef {Object} CreateNotificationOptions
|
||||||
|
* @property {string} message the message
|
||||||
|
* @property {'info' | 'alert' | 'error'} severity the severity
|
||||||
|
* @property {import('../src/api/notifications/NotificationAPI').NotificationOptions} [notificationOptions] additional options
|
||||||
|
*/
|
||||||
|
|
||||||
const Buffer = require('buffer').Buffer;
|
const Buffer = require('buffer').Buffer;
|
||||||
const genUuid = require('uuid').v4;
|
const genUuid = require('uuid').v4;
|
||||||
|
|
||||||
@ -112,6 +120,25 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a notification with the given options.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {CreateNotificationOptions} createNotificationOptions
|
||||||
|
*/
|
||||||
|
async function createNotification(page, createNotificationOptions) {
|
||||||
|
await page.evaluate((_createNotificationOptions) => {
|
||||||
|
const { message, severity, options } = _createNotificationOptions;
|
||||||
|
const notificationApi = window.openmct.notifications;
|
||||||
|
if (severity === 'info') {
|
||||||
|
notificationApi.info(message, options);
|
||||||
|
} else if (severity === 'alert') {
|
||||||
|
notificationApi.alert(message, options);
|
||||||
|
} else {
|
||||||
|
notificationApi.error(message, options);
|
||||||
|
}
|
||||||
|
}, createNotificationOptions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
@ -333,6 +360,7 @@ async function setEndOffset(page, offset) {
|
|||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
|
createNotification,
|
||||||
expandTreePaneItemByName,
|
expandTreePaneItemByName,
|
||||||
createPlanFromJSON,
|
createPlanFromJSON,
|
||||||
openObjectTreeContextMenu,
|
openObjectTreeContextMenu,
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
const { test, expect } = require('../../pluginFixtures.js');
|
const { test, expect } = require('../../pluginFixtures.js');
|
||||||
const { createDomainObjectWithDefaults } = require('../../appActions.js');
|
const { createDomainObjectWithDefaults, createNotification } = require('../../appActions.js');
|
||||||
|
|
||||||
test.describe('AppActions', () => {
|
test.describe('AppActions', () => {
|
||||||
test('createDomainObjectsWithDefaults', async ({ page }) => {
|
test('createDomainObjectsWithDefaults', async ({ page }) => {
|
||||||
@ -85,4 +85,28 @@ test.describe('AppActions', () => {
|
|||||||
expect(folder3.url).toBe(`${e2eFolder.url}/${folder1.uuid}/${folder2.uuid}/${folder3.uuid}`);
|
expect(folder3.url).toBe(`${e2eFolder.url}/${folder1.uuid}/${folder2.uuid}/${folder3.uuid}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
test("createNotification", async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
await createNotification(page, {
|
||||||
|
message: 'Test info notification',
|
||||||
|
severity: 'info'
|
||||||
|
});
|
||||||
|
await expect(page.locator('.c-message-banner__message')).toHaveText('Test info notification');
|
||||||
|
await expect(page.locator('.c-message-banner')).toHaveClass(/info/);
|
||||||
|
await page.locator('[aria-label="Dismiss"]').click();
|
||||||
|
await createNotification(page, {
|
||||||
|
message: 'Test alert notification',
|
||||||
|
severity: 'alert'
|
||||||
|
});
|
||||||
|
await expect(page.locator('.c-message-banner__message')).toHaveText('Test alert notification');
|
||||||
|
await expect(page.locator('.c-message-banner')).toHaveClass(/alert/);
|
||||||
|
await page.locator('[aria-label="Dismiss"]').click();
|
||||||
|
await createNotification(page, {
|
||||||
|
message: 'Test error notification',
|
||||||
|
severity: 'error'
|
||||||
|
});
|
||||||
|
await expect(page.locator('.c-message-banner__message')).toHaveText('Test error notification');
|
||||||
|
await expect(page.locator('.c-message-banner')).toHaveClass(/error/);
|
||||||
|
await page.locator('[aria-label="Dismiss"]').click();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
39
e2e/tests/functional/notification.e2e.spec.js
Normal file
39
e2e/tests/functional/notification.e2e.spec.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test suite is dedicated to tests which verify Open MCT's Notification functionality
|
||||||
|
*/
|
||||||
|
|
||||||
|
// FIXME: Remove this eslint exception once tests are implemented
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const { test, expect } = require('../../pluginFixtures');
|
||||||
|
|
||||||
|
test.describe('Notifications List', () => {
|
||||||
|
test.fixme('Notifications can be dismissed individually', async ({ page }) => {
|
||||||
|
// Create some persistent notifications
|
||||||
|
// Verify that they are present in the notifications list
|
||||||
|
// Dismiss one of the notifications
|
||||||
|
// Verify that it is no longer present in the notifications list
|
||||||
|
// Verify that the other notifications are still present in the notifications list
|
||||||
|
});
|
||||||
|
});
|
58
e2e/tests/visual/notification.visual.spec.js
Normal file
58
e2e/tests/visual/notification.visual.spec.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test is dedicated to test notification banner functionality and its accessibility attributes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('../../pluginFixtures');
|
||||||
|
const percySnapshot = require('@percy/playwright');
|
||||||
|
const { createDomainObjectWithDefaults } = require('../../appActions');
|
||||||
|
|
||||||
|
test.describe('Visual - Check Notification Info Banner of \'Save successful\'', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Go to baseURL and Hide Tree
|
||||||
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Create a clock, click on \'Save successful\' banner and dismiss it', async ({ page }) => {
|
||||||
|
// Create a clock domain object
|
||||||
|
await createDomainObjectWithDefaults(page, { type: 'Clock' });
|
||||||
|
// Verify there is a button with aria-label="Review 1 Notification"
|
||||||
|
expect(await page.locator('button[aria-label="Review 1 Notification"]').isVisible()).toBe(true);
|
||||||
|
// Verify there is a button with aria-label="Clear all notifications"
|
||||||
|
expect(await page.locator('button[aria-label="Clear all notifications"]').isVisible()).toBe(true);
|
||||||
|
// Click on the div with role="alert" that has "Save successful" text
|
||||||
|
await page.locator('div[role="alert"]:has-text("Save successful")').click();
|
||||||
|
// Verify there is a div with role="dialog"
|
||||||
|
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(true);
|
||||||
|
// Verify the div with role="dialog" contains text "Save successful"
|
||||||
|
expect(await page.locator('div[role="dialog"]').innerText()).toContain('Save successful');
|
||||||
|
await percySnapshot(page, 'Notification banner');
|
||||||
|
// Verify there is a button with text "Dismiss"
|
||||||
|
expect(await page.locator('button:has-text("Dismiss")').isVisible()).toBe(true);
|
||||||
|
// Click on button with text "Dismiss"
|
||||||
|
await page.locator('button:has-text("Dismiss")').click();
|
||||||
|
// Verify there is no div with role="dialog"
|
||||||
|
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
@ -8,6 +8,7 @@
|
|||||||
"@percy/cli": "1.16.0",
|
"@percy/cli": "1.16.0",
|
||||||
"@percy/playwright": "1.0.4",
|
"@percy/playwright": "1.0.4",
|
||||||
"@playwright/test": "1.29.0",
|
"@playwright/test": "1.29.0",
|
||||||
|
"@types/eventemitter3": "1.2.0",
|
||||||
"@types/jasmine": "4.3.1",
|
"@types/jasmine": "4.3.1",
|
||||||
"@types/lodash": "4.14.191",
|
"@types/lodash": "4.14.191",
|
||||||
"babel-loader": "9.1.0",
|
"babel-loader": "9.1.0",
|
||||||
|
@ -31,7 +31,31 @@
|
|||||||
* @namespace platform/api/notifications
|
* @namespace platform/api/notifications
|
||||||
*/
|
*/
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import EventEmitter from 'EventEmitter';
|
import EventEmitter from 'eventemitter3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} NotificationProperties
|
||||||
|
* @property {function} dismiss Dismiss the notification
|
||||||
|
* @property {NotificationModel} model The Notification model
|
||||||
|
* @property {(progressPerc: number, progressText: string) => void} [progress] Update the progress of the notification
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {EventEmitter & NotificationProperties} Notification
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} NotificationLink
|
||||||
|
* @property {function} onClick The function to be called when the link is clicked
|
||||||
|
* @property {string} cssClass A CSS class name to style the link
|
||||||
|
* @property {string} text The text to be displayed for the link
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} NotificationOptions
|
||||||
|
* @property {number} [autoDismissTimeout] Milliseconds to wait before automatically dismissing the notification
|
||||||
|
* @property {NotificationLink} [link] A link for the notification
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A representation of a banner notification. Banner notifications
|
* A representation of a banner notification. Banner notifications
|
||||||
@ -40,13 +64,17 @@ import EventEmitter from 'EventEmitter';
|
|||||||
* dialogs so that the same information can be provided in a dialog
|
* dialogs so that the same information can be provided in a dialog
|
||||||
* and then minimized to a banner notification if needed, or vice-versa.
|
* and then minimized to a banner notification if needed, or vice-versa.
|
||||||
*
|
*
|
||||||
|
* @see DialogModel
|
||||||
* @typedef {object} NotificationModel
|
* @typedef {object} NotificationModel
|
||||||
* @property {string} message The message to be displayed by the notification
|
* @property {string} message The message to be displayed by the notification
|
||||||
* @property {number | 'unknown'} [progress] The progress of some ongoing task. Should be a number between 0 and 100, or
|
* @property {number | 'unknown'} [progress] The progress of some ongoing task. Should be a number between 0 and 100, or
|
||||||
* with the string literal 'unknown'.
|
* with the string literal 'unknown'.
|
||||||
* @property {string} [progressText] A message conveying progress of some ongoing task.
|
* @property {string} [progressText] A message conveying progress of some ongoing task.
|
||||||
|
* @property {string} [severity] The severity of the notification. Should be one of 'info', 'alert', or 'error'.
|
||||||
* @see DialogModel
|
* @property {string} [timestamp] The time at which the notification was created. Should be a string in ISO 8601 format.
|
||||||
|
* @property {boolean} [minimized] Whether or not the notification has been minimized
|
||||||
|
* @property {boolean} [autoDismiss] Whether the notification should be automatically dismissed after a short period of time.
|
||||||
|
* @property {NotificationOptions} options The notification options
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const DEFAULT_AUTO_DISMISS_TIMEOUT = 3000;
|
const DEFAULT_AUTO_DISMISS_TIMEOUT = 3000;
|
||||||
@ -55,18 +83,19 @@ const MINIMIZE_ANIMATION_TIMEOUT = 300;
|
|||||||
/**
|
/**
|
||||||
* The notification service is responsible for informing the user of
|
* The notification service is responsible for informing the user of
|
||||||
* events via the use of banner notifications.
|
* events via the use of banner notifications.
|
||||||
* @memberof ui/notification
|
*/
|
||||||
* @constructor */
|
|
||||||
|
|
||||||
export default class NotificationAPI extends EventEmitter {
|
export default class NotificationAPI extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
/** @type {Notification[]} */
|
||||||
this.notifications = [];
|
this.notifications = [];
|
||||||
|
/** @type {{severity: "info" | "alert" | "error"}} */
|
||||||
this.highest = { severity: "info" };
|
this.highest = { severity: "info" };
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* A context in which to hold the active notification and a
|
* A context in which to hold the active notification and a
|
||||||
* handle to its timeout.
|
* handle to its timeout.
|
||||||
|
* @type {Notification | undefined}
|
||||||
*/
|
*/
|
||||||
this.activeNotification = undefined;
|
this.activeNotification = undefined;
|
||||||
}
|
}
|
||||||
@ -75,16 +104,12 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* Info notifications are low priority informational messages for the user. They will be auto-destroy after a brief
|
* Info notifications are low priority informational messages for the user. They will be auto-destroy after a brief
|
||||||
* period of time.
|
* period of time.
|
||||||
* @param {string} message The message to display to the user
|
* @param {string} message The message to display to the user
|
||||||
* @param {Object} [options] object with following properties
|
* @param {NotificationOptions} [options] The notification options
|
||||||
* autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
|
* @returns {Notification}
|
||||||
* link: {Object} Add a link to notifications for navigation
|
|
||||||
* onClick: callback function
|
|
||||||
* cssClass: css class name to add style on link
|
|
||||||
* text: text to display for link
|
|
||||||
* @returns {InfoNotification}
|
|
||||||
*/
|
*/
|
||||||
info(message, options = {}) {
|
info(message, options = {}) {
|
||||||
let notificationModel = {
|
/** @type {NotificationModel} */
|
||||||
|
const notificationModel = {
|
||||||
message: message,
|
message: message,
|
||||||
autoDismiss: true,
|
autoDismiss: true,
|
||||||
severity: "info",
|
severity: "info",
|
||||||
@ -97,7 +122,7 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Present an alert to the user.
|
* Present an alert to the user.
|
||||||
* @param {string} message The message to display to the user.
|
* @param {string} message The message to display to the user.
|
||||||
* @param {Object} [options] object with following properties
|
* @param {NotificationOptions} [options] object with following properties
|
||||||
* autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
|
* autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
|
||||||
* link: {Object} Add a link to notifications for navigation
|
* link: {Object} Add a link to notifications for navigation
|
||||||
* onClick: callback function
|
* onClick: callback function
|
||||||
@ -106,7 +131,7 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* @returns {Notification}
|
* @returns {Notification}
|
||||||
*/
|
*/
|
||||||
alert(message, options = {}) {
|
alert(message, options = {}) {
|
||||||
let notificationModel = {
|
const notificationModel = {
|
||||||
message: message,
|
message: message,
|
||||||
severity: "alert",
|
severity: "alert",
|
||||||
options
|
options
|
||||||
@ -147,7 +172,8 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
message: message,
|
message: message,
|
||||||
progressPerc: progressPerc,
|
progressPerc: progressPerc,
|
||||||
progressText: progressText,
|
progressText: progressText,
|
||||||
severity: "info"
|
severity: "info",
|
||||||
|
options: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
return this._notify(notificationModel);
|
return this._notify(notificationModel);
|
||||||
@ -165,8 +191,13 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* dismissed.
|
* dismissed.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
|
* @param {Notification | undefined} notification
|
||||||
*/
|
*/
|
||||||
_minimize(notification) {
|
_minimize(notification) {
|
||||||
|
if (!notification) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Check this is a known notification
|
//Check this is a known notification
|
||||||
let index = this.notifications.indexOf(notification);
|
let index = this.notifications.indexOf(notification);
|
||||||
|
|
||||||
@ -204,8 +235,13 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* dismiss
|
* dismiss
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
|
* @param {Notification | undefined} notification
|
||||||
*/
|
*/
|
||||||
_dismiss(notification) {
|
_dismiss(notification) {
|
||||||
|
if (!notification) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Check this is a known notification
|
//Check this is a known notification
|
||||||
let index = this.notifications.indexOf(notification);
|
let index = this.notifications.indexOf(notification);
|
||||||
|
|
||||||
@ -236,10 +272,11 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
* dismiss or minimize where appropriate.
|
* dismiss or minimize where appropriate.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
|
* @param {Notification | undefined} notification
|
||||||
*/
|
*/
|
||||||
_dismissOrMinimize(notification) {
|
_dismissOrMinimize(notification) {
|
||||||
let model = notification.model;
|
let model = notification?.model;
|
||||||
if (model.severity === "info") {
|
if (model?.severity === "info") {
|
||||||
this._dismiss(notification);
|
this._dismiss(notification);
|
||||||
} else {
|
} else {
|
||||||
this._minimize(notification);
|
this._minimize(notification);
|
||||||
@ -251,10 +288,11 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
_setHighestSeverity() {
|
_setHighestSeverity() {
|
||||||
let severity = {
|
let severity = {
|
||||||
"info": 1,
|
info: 1,
|
||||||
"alert": 2,
|
alert: 2,
|
||||||
"error": 3
|
error: 3
|
||||||
};
|
};
|
||||||
|
|
||||||
this.highest.severity = this.notifications.reduce((previous, notification) => {
|
this.highest.severity = this.notifications.reduce((previous, notification) => {
|
||||||
if (severity[notification.model.severity] > severity[previous]) {
|
if (severity[notification.model.severity] > severity[previous]) {
|
||||||
return notification.model.severity;
|
return notification.model.severity;
|
||||||
@ -312,8 +350,11 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
* @param {NotificationModel} notificationModel
|
||||||
|
* @returns {Notification}
|
||||||
*/
|
*/
|
||||||
_createNotification(notificationModel) {
|
_createNotification(notificationModel) {
|
||||||
|
/** @type {Notification} */
|
||||||
let notification = new EventEmitter();
|
let notification = new EventEmitter();
|
||||||
notification.model = notificationModel;
|
notification.model = notificationModel;
|
||||||
notification.dismiss = () => {
|
notification.dismiss = () => {
|
||||||
@ -333,6 +374,7 @@ export default class NotificationAPI extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
* @param {Notification | undefined} notification
|
||||||
*/
|
*/
|
||||||
_setActiveNotification(notification) {
|
_setActiveNotification(notification) {
|
||||||
this.activeNotification = notification;
|
this.activeNotification = notification;
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
ref="element"
|
ref="element"
|
||||||
class="c-overlay__contents js-notebook-snapshot-item-wrapper"
|
class="c-overlay__contents js-notebook-snapshot-item-wrapper"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
aria-modal="true"
|
||||||
|
role="dialog"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
v-if="buttons"
|
v-if="buttons"
|
||||||
|
@ -5,10 +5,16 @@
|
|||||||
:class="[severityClass]"
|
:class="[severityClass]"
|
||||||
>
|
>
|
||||||
<span class="c-indicator__label">
|
<span class="c-indicator__label">
|
||||||
<button @click="toggleNotificationsList(true)">
|
<button
|
||||||
|
:aria-label="'Review ' + notificationsCountMessage(notifications.length)"
|
||||||
|
@click="toggleNotificationsList(true)"
|
||||||
|
>
|
||||||
{{ notificationsCountMessage(notifications.length) }}
|
{{ notificationsCountMessage(notifications.length) }}
|
||||||
</button>
|
</button>
|
||||||
<button @click="dismissAllNotifications()">
|
<button
|
||||||
|
aria-label="Clear all notifications"
|
||||||
|
@click="dismissAllNotifications()"
|
||||||
|
>
|
||||||
Clear All
|
Clear All
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="c-message"
|
class="c-message"
|
||||||
|
role="listitem"
|
||||||
:class="'message-severity-' + notification.model.severity"
|
:class="'message-severity-' + notification.model.severity"
|
||||||
>
|
>
|
||||||
<div class="c-ne__time-and-content">
|
<div class="c-ne__time-and-content">
|
||||||
@ -20,6 +21,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
:aria-label="'Dismiss notification of ' + notification.model.message"
|
||||||
|
class="c-click-icon c-overlay__close-button icon-x"
|
||||||
|
@click="dismiss()"
|
||||||
|
></button>
|
||||||
<div class="c-overlay__button-bar">
|
<div class="c-overlay__button-bar">
|
||||||
<button
|
<button
|
||||||
v-for="(dialogOption, index) in notification.model.options"
|
v-for="(dialogOption, index) in notification.model.options"
|
||||||
@ -52,6 +58,14 @@ export default {
|
|||||||
notification: {
|
notification: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
closeOverlay: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
notificationsCount: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -79,6 +93,12 @@ export default {
|
|||||||
updateProgressBar(progressPerc, progressText) {
|
updateProgressBar(progressPerc, progressText) {
|
||||||
this.progressPerc = progressPerc;
|
this.progressPerc = progressPerc;
|
||||||
this.progressText = progressText;
|
this.progressText = progressText;
|
||||||
|
},
|
||||||
|
dismiss() {
|
||||||
|
this.notification.dismiss();
|
||||||
|
if (this.notificationsCount === 1) {
|
||||||
|
this.closeOverlay();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -6,11 +6,16 @@
|
|||||||
{{ notificationsCountDisplayMessage(notifications.length) }}
|
{{ notificationsCountDisplayMessage(notifications.length) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-messages c-overlay__messages">
|
<div
|
||||||
|
role="list"
|
||||||
|
class="w-messages c-overlay__messages"
|
||||||
|
>
|
||||||
<notification-message
|
<notification-message
|
||||||
v-for="notification in notifications"
|
v-for="notification in notifications"
|
||||||
:key="notification.model.timestamp"
|
:key="notification.model.timestamp"
|
||||||
|
:close-overlay="closeOverlay"
|
||||||
:notification="notification"
|
:notification="notification"
|
||||||
|
:notifications-count="notifications.length"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -57,6 +62,9 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
closeOverlay() {
|
||||||
|
this.overlay.dismiss();
|
||||||
|
},
|
||||||
notificationsCountDisplayMessage(count) {
|
notificationsCountDisplayMessage(count) {
|
||||||
if (count > 1 || count === 0) {
|
if (count > 1 || count === 0) {
|
||||||
return `Displaying ${count} notifications`;
|
return `Displaying ${count} notifications`;
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
<div
|
<div
|
||||||
v-if="activeModel.message"
|
v-if="activeModel.message"
|
||||||
class="c-message-banner"
|
class="c-message-banner"
|
||||||
|
role="alert"
|
||||||
|
:aria-live="activeModel.severity === 'error' ? 'assertive' : 'polite'"
|
||||||
:class="[
|
:class="[
|
||||||
activeModel.severity,
|
activeModel.severity,
|
||||||
{
|
{
|
||||||
@ -42,6 +44,7 @@
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="c-message-banner__close-button c-click-icon icon-x-in-circle"
|
class="c-message-banner__close-button c-click-icon icon-x-in-circle"
|
||||||
|
aria-label="Dismiss"
|
||||||
@click.stop="dismiss()"
|
@click.stop="dismiss()"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user