From 5374460b1d21a8e404a62fda76a7018c0d08d200 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Wed, 4 May 2022 16:52:06 +0200 Subject: [PATCH] Simplified API for notifications based on promises MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The old API permitted arbitrary CSS class name that aren't guaranteed to work. Typical usage includes the default class names only therefore we can clamp the value to those. Using promises cleans up the code flow when a “callback” is used. --- ts/WoltLabSuite/Core/Ui/Notification.ts | 93 +++++++++++++------ .../js/WoltLabSuite/Core/Ui/Notification.js | 85 +++++++++++------ 2 files changed, 123 insertions(+), 55 deletions(-) diff --git a/ts/WoltLabSuite/Core/Ui/Notification.ts b/ts/WoltLabSuite/Core/Ui/Notification.ts index 47d7f592ca..425bb96bc9 100644 --- a/ts/WoltLabSuite/Core/Ui/Notification.ts +++ b/ts/WoltLabSuite/Core/Ui/Notification.ts @@ -1,35 +1,39 @@ /** * Simple notification overlay. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module Ui/Notification (alias) - * @module WoltLabSuite/Core/Ui/Notification + * @author Alexander Ebert + * @copyright 2001-2022 WoltLab GmbH + * @license GNU Lesser General Public License + * @module Ui/Notification (alias) + * @module WoltLabSuite/Core/Ui/Notification */ import * as Language from "../Language"; type Callback = () => void; -let _busy = false; -let _callback: Callback | null = null; -let _didInit = false; +const enum NotificationType { + Error = "error", + Info = "info", + Success = "success", + Warning = "warning", +} + let _message: HTMLElement; let _notificationElement: HTMLElement; +let _pendingNotification: Promise | undefined = undefined; let _timeout: number; function init() { - if (_didInit) { + if (_notificationElement instanceof HTMLElement) { return; } - _didInit = true; _notificationElement = document.createElement("div"); _notificationElement.id = "systemNotification"; _message = document.createElement("p"); - _message.addEventListener("click", hide); + _message.addEventListener("click", () => hideNotification()); _notificationElement.appendChild(_message); document.body.appendChild(_notificationElement); @@ -38,33 +42,68 @@ function init() { /** * Hides the notification and invokes the callback if provided. */ -function hide() { - clearTimeout(_timeout); +function hideNotification() { + window.clearTimeout(_timeout); _notificationElement.classList.remove("active"); +} + +async function notify(type: NotificationType, message: string): Promise { + init(); - if (_callback !== null) { - _callback(); + if (_notificationElement.classList.contains("active")) { + await _pendingNotification!; } - _busy = false; + _message.className = type; + _message.textContent = Language.get(message); + + _notificationElement.classList.add("active"); + + _pendingNotification = new Promise((resolve) => { + _timeout = window.setTimeout(() => { + resolve(); + }, 2_000); + }).then(() => hideNotification()); + + return _pendingNotification; } /** - * Displays a notification. + * @deprecated 5.5 Use `notifyError()`, `notifyInfo()`, `notifySuccess()` or `notifyWarning()` instead */ -export function show(message?: string, callback?: Callback | null, cssClassName?: string): void { - if (_busy) { - return; +export function show(message = "", callback: Callback | null = null, cssClassName = ""): void { + const validTypes = [ + NotificationType.Error, + NotificationType.Info, + NotificationType.Success, + NotificationType.Warning, + ]; + + if (!validTypes.includes(cssClassName as NotificationType)) { + cssClassName = NotificationType.Success; } - _busy = true; - init(); + const notification = notify(cssClassName as NotificationType, message); + void notification.then(() => { + if (typeof callback === "function") { + callback(); + } + }); +} - _callback = typeof callback === "function" ? callback : null; - _message.className = cssClassName || "success"; - _message.textContent = Language.get(message || "wcf.global.success"); +export async function notifyError(message = ""): Promise { + return notify(NotificationType.Error, message); +} - _notificationElement.classList.add("active"); - _timeout = setTimeout(hide, 2000); +export async function notifyInfo(message = ""): Promise { + return notify(NotificationType.Info, message); +} + +export async function notifySuccess(message = ""): Promise { + return notify(NotificationType.Success, message); +} + +export async function notifyWarning(message = ""): Promise { + return notify(NotificationType.Warning, message); } diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Notification.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Notification.js index 3b5d89d471..3033f2df52 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Notification.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Notification.js @@ -1,60 +1,89 @@ /** * Simple notification overlay. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module Ui/Notification (alias) - * @module WoltLabSuite/Core/Ui/Notification + * @author Alexander Ebert + * @copyright 2001-2022 WoltLab GmbH + * @license GNU Lesser General Public License + * @module Ui/Notification (alias) + * @module WoltLabSuite/Core/Ui/Notification */ define(["require", "exports", "tslib", "../Language"], function (require, exports, tslib_1, Language) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); - exports.show = void 0; + exports.notifyWarning = exports.notifySuccess = exports.notifyInfo = exports.notifyError = exports.show = void 0; Language = tslib_1.__importStar(Language); - let _busy = false; - let _callback = null; - let _didInit = false; let _message; let _notificationElement; + let _pendingNotification = undefined; let _timeout; function init() { - if (_didInit) { + if (_notificationElement instanceof HTMLElement) { return; } - _didInit = true; _notificationElement = document.createElement("div"); _notificationElement.id = "systemNotification"; _message = document.createElement("p"); - _message.addEventListener("click", hide); + _message.addEventListener("click", () => hideNotification()); _notificationElement.appendChild(_message); document.body.appendChild(_notificationElement); } /** * Hides the notification and invokes the callback if provided. */ - function hide() { - clearTimeout(_timeout); + function hideNotification() { + window.clearTimeout(_timeout); _notificationElement.classList.remove("active"); - if (_callback !== null) { - _callback(); + } + async function notify(type, message) { + init(); + if (_notificationElement.classList.contains("active")) { + await _pendingNotification; } - _busy = false; + _message.className = type; + _message.textContent = Language.get(message); + _notificationElement.classList.add("active"); + _pendingNotification = new Promise((resolve) => { + _timeout = window.setTimeout(() => { + resolve(); + }, 2000); + }).then(() => hideNotification()); + return _pendingNotification; } /** - * Displays a notification. + * @deprecated 5.5 Use `notifyError()`, `notifyInfo()`, `notifySuccess()` or `notifyWarning()` instead */ - function show(message, callback, cssClassName) { - if (_busy) { - return; + function show(message = "", callback = null, cssClassName = "") { + const validTypes = [ + "error" /* Error */, + "info" /* Info */, + "success" /* Success */, + "warning" /* Warning */, + ]; + if (!validTypes.includes(cssClassName)) { + cssClassName = "success" /* Success */; } - _busy = true; - init(); - _callback = typeof callback === "function" ? callback : null; - _message.className = cssClassName || "success"; - _message.textContent = Language.get(message || "wcf.global.success"); - _notificationElement.classList.add("active"); - _timeout = setTimeout(hide, 2000); + const notification = notify(cssClassName, message); + void notification.then(() => { + if (typeof callback === "function") { + callback(); + } + }); } exports.show = show; + async function notifyError(message = "") { + return notify("error" /* Error */, message); + } + exports.notifyError = notifyError; + async function notifyInfo(message = "") { + return notify("info" /* Info */, message); + } + exports.notifyInfo = notifyInfo; + async function notifySuccess(message = "") { + return notify("success" /* Success */, message); + } + exports.notifySuccess = notifySuccess; + async function notifyWarning(message = "") { + return notify("warning" /* Warning */, message); + } + exports.notifyWarning = notifyWarning; }); -- 2.20.1