UNPKG

@blinkk/editor

Version:

Structured content editor with live previews.

397 lines 16.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.showNotification = exports.readNotification = exports.announceNotification = exports.NotificationsPart = exports.NotificationLevel = void 0; const _1 = require("."); const modal_1 = require("../ui/modal"); const events_1 = require("../events"); const selective_edit_1 = require("@blinkk/selective-edit"); const toasts_1 = require("./toasts"); const MODAL_KEY_NOTIFICATION = 'notification'; const MODAL_KEY_NOTIFICATIONS = 'notifications'; var NotificationLevel; (function (NotificationLevel) { NotificationLevel[NotificationLevel["Debug"] = 0] = "Debug"; NotificationLevel[NotificationLevel["Info"] = 1] = "Info"; NotificationLevel[NotificationLevel["Warning"] = 2] = "Warning"; NotificationLevel[NotificationLevel["Error"] = 3] = "Error"; })(NotificationLevel = exports.NotificationLevel || (exports.NotificationLevel = {})); /** * Modals are centralized in the display to be outside of other * modals and structures. Modal windows live as siblings in the * DOM. * * This helps to prevent issues where one modal is clipping * another without having to pass the modal through the template * stack to be outside of another modal. * * This also allows reuse of modals across parts of the editor. */ class NotificationsPart extends _1.BasePart { constructor() { super(); this.notifications = new Set(); this.hasNewError = false; document.addEventListener(events_1.EVENT_NOTIFICATION_ADD, (evt) => { this.addInfo(evt.detail); this.render(); }); document.addEventListener(events_1.EVENT_NOTIFICATION_READ, (evt) => { const notification = evt.detail; this.readNotification(notification); }); document.addEventListener(events_1.EVENT_NOTIFICATION_SHOW, (evt) => { const notification = evt.detail; this.showNotification(notification); }); } addDebug(notification) { this.addNotification(notification, NotificationLevel.Debug); } addError(notification, isDisplayed = false) { this.addNotification(notification, NotificationLevel.Error, isDisplayed); } addInfo(notification) { this.addNotification(notification, NotificationLevel.Info); } addNotification(notification, defaultLevel = NotificationLevel.Info, isDisplayed = false) { this.notifications.add(this.scrubNewNotification(notification, defaultLevel)); if (notification.level === NotificationLevel.Error && !isDisplayed) { this.hasNewError = true; } else { toasts_1.showToast({ notification: notification, }); } } addWarning(notification) { this.addNotification(notification, NotificationLevel.Warning); } classesForNotification(notification) { return { ls__part__notifications__notification: true, 'ls__part__notifications__notification--error': notification.level === NotificationLevel.Error, 'ls__part__notifications__notification--warning': notification.level === NotificationLevel.Warning, }; } classesForPart() { return { le__part__notifications: true, le__clickable: true, 'le__part__notifications--errors': this.hasUnreadNotificationsAtLevel(NotificationLevel.Error), 'le__tooltip--bottom-left': true, }; } getOrCreateModalNotificationSingle(editor) { if (!editor.parts.modals.modals[MODAL_KEY_NOTIFICATION]) { const modal = new modal_1.DialogModal({ title: 'Notification', priority: modal_1.DialogPriorityLevel.High, }); modal.templateModal = this.templateNotificationSingle.bind(this); // When the modal is hidden, remove the current notification so it does // not open again instantly. modal.addListener('hide', () => { this.currentNotification = undefined; this.render(); }); editor.parts.modals.modals[MODAL_KEY_NOTIFICATION] = modal; } return editor.parts.modals.modals[MODAL_KEY_NOTIFICATION]; } getOrCreateModalNotifications(editor) { if (!editor.parts.modals.modals[MODAL_KEY_NOTIFICATIONS]) { const modal = new modal_1.DialogModal({ title: 'Notifications', priority: modal_1.DialogPriorityLevel.High, }); modal.templateModal = this.templateNotifications.bind(this); modal.actions.push({ label: 'Mark all read', level: modal_1.DialogActionLevel.Primary, isDisabledFunc: () => false, onClick: () => { this.markAllAsRead(); modal.hide(); }, }); modal.addCancelAction('Close'); editor.parts.modals.modals[MODAL_KEY_NOTIFICATIONS] = modal; } return editor.parts.modals.modals[MODAL_KEY_NOTIFICATIONS]; } getIconForNotificationLevel(level, isRead) { if (level === NotificationLevel.Error) { return 'notification_important'; } if (!isRead) { return 'notifications_active'; } return 'notifications'; } get hasUnreadNotifications() { for (const notification of this.notifications) { if (!notification.isRead) { return true; } } return false; } hasUnreadNotificationsAtLevel(level) { for (const notification of this.notifications) { if (!notification.level) { continue; } if (!notification.isRead && notification.level >= level) { return true; } } return false; } markAllAsRead() { for (const notification of this.notifications) { notification.isRead = true; } } readNotification(notification, defaultLevel = NotificationLevel.Info) { // Only scrub the notification if it has not been added before. if (!this.notifications.has(notification)) { notification = this.scrubNewNotification(notification, defaultLevel); this.notifications.add(notification); } notification.isRead = true; this.render(); } scrubNewNotification(notification, defaultLevel) { const internalNotification = notification; if (!internalNotification.level) { internalNotification.level = defaultLevel; } internalNotification.addedOn = new Date(); internalNotification.isRead = false; return internalNotification; } showNotification(notification, defaultLevel = NotificationLevel.Info) { const newNotification = this.scrubNewNotification(notification, defaultLevel); this.notifications.add(notification); this.currentNotification = newNotification; } template(editor) { let icon = 'notifications'; if (this.hasUnreadNotificationsAtLevel(NotificationLevel.Error)) { icon = 'notification_important'; // Show the modal when there are unread error notifications. // Just shows the first time it has a new error, can close // without needing to mark the notification as read. if (this.hasNewError) { const modal = this.getOrCreateModalNotifications(editor); modal.isVisible = true; this.hasNewError = false; } } else if (this.hasUnreadNotifications) { icon = 'notifications_active'; } if (this.currentNotification) { this.currentNotification.isRead = true; const modal = this.getOrCreateModalNotificationSingle(editor); modal.config.title = this.currentNotification.title || modal.config.title; // Reset the actions to match the notification. modal.actions = []; for (const action of this.currentNotification.actions || []) { modal.actions.push({ isDisabledFunc: () => false, label: action.label, level: modal_1.DialogActionLevel.Primary, onClick: (evt) => { evt.preventDefault(); document.dispatchEvent(new CustomEvent(action.customEvent, { detail: action.details, })); modal.hide(); }, }); } modal.addCancelAction('Close'); modal.isVisible = true; } const handleOpenNotifications = () => { const modal = this.getOrCreateModalNotifications(editor); modal.show(); }; return selective_edit_1.html `<div class=${selective_edit_1.classMap(this.classesForPart())} data-tip="Notifications" @click=${handleOpenNotifications} > <span class="material-icons">${icon}</span> </div>`; } templateNotification(editor, notification) { let markReadButton = selective_edit_1.html ``; if (!notification.isRead) { markReadButton = selective_edit_1.html `<div class="ls__part__notifications__notification__mark le__clickable" @click=${(evt) => { evt.stopPropagation(); notification.isRead = true; this.render(); }} > Mark read </div>`; } const hasExtra = notification.description || notification.meta; const handleClick = (evt) => { if (!hasExtra) { return; } evt.stopPropagation(); notification.isExpanded = !notification.isExpanded; this.render(); }; return selective_edit_1.html `<div class=${selective_edit_1.classMap(this.classesForNotification(notification))} > <div class="ls__part__notifications__notification__status"> <span class="material-icons" >${this.getIconForNotificationLevel(notification.level || NotificationLevel.Info, notification.isRead || false)}</span > </div> <div class="ls__part__notifications__notification__label ${hasExtra ? 'le__clickable' : ''}" @click=${handleClick} > ${hasExtra ? selective_edit_1.html `<span class="material-icons" >${notification.isExpanded ? 'keyboard_arrow_down' : 'keyboard_arrow_right'}</span >` : ''} ${notification.message} </div> ${markReadButton} ${notification.isExpanded ? this.templateNotificationDescription(editor, notification) : ''} ${notification.isExpanded ? this.templateNotificationMeta(editor, notification) : ''} <div class="ls__part__notifications__notification__actions"> ${this.templateNotificationActions(editor, notification)} </div> </div>`; } templateNotificationActions(editor, notification) { let additionalDetails = selective_edit_1.html ``; if (notification.actions) { additionalDetails = selective_edit_1.html ` ${selective_edit_1.repeat(notification.actions, action => action.label, action => { const handleClick = (evt) => { evt.preventDefault(); evt.stopPropagation(); document.dispatchEvent(new CustomEvent(action.customEvent, { detail: action.details, })); const modal = this.getOrCreateModalNotifications(editor); modal.hide(); }; return selective_edit_1.html `<button class="le__button le__clickable" @click=${handleClick} > ${action.label} </button>`; })}`; } return additionalDetails; } templateNotificationDescription(editor, notification) { if (!notification.description) { return selective_edit_1.html ``; } return selective_edit_1.html `<div class="ls__part__notifications__notification__description"> ${notification.description} </div>`; } templateNotificationMeta(editor, notification) { if (!notification.meta) { return selective_edit_1.html ``; } return selective_edit_1.html `<div class="ls__part__notifications__notification__meta"> <pre><code>${JSON.stringify(notification.meta, null, 2)}</code></pre> </div>`; } templateNotificationSingle(editor) { if (!this.currentNotification) { return selective_edit_1.html ``; } let expandMeta = selective_edit_1.html ``; if (!this.currentNotification.isExpanded && this.currentNotification.meta) { expandMeta = selective_edit_1.html `<div class="ls__part__notifications__notification__expand" @click=${(evt) => { if (!this.currentNotification) { return; } evt.stopPropagation(); this.currentNotification.isExpanded = true; this.render(); }} > Show more </div>`; } return selective_edit_1.html `<div class="le__part__notifications__modal"> <div class="ls__part__notifications__notification__message"> <div class="ls__part__notifications__notification__label"> ${this.currentNotification?.message} </div> ${this.templateNotificationDescription(editor, this.currentNotification)} ${this.currentNotification.isExpanded ? this.templateNotificationMeta(editor, this.currentNotification) : expandMeta} </div> </div>`; } templateNotifications(editor) { if (!this.notifications.size) { return selective_edit_1.html `<div class="le__part__notifications__modal"> <div class="le__list"> <div class="le__list__item le__list__item--pad"> <div class="le__list__item__icon"> <span class="material-icons">check</span> </div> <div class="le__list__item__label"> No notifications yet. Please check back later. </div> </div> </div> </div>`; } // Sort notifications by the timestamp in latest first. const notifications = Array.from(this.notifications); notifications.sort((a, b) => (b.addedOn?.getTime() || 0) - (a.addedOn?.getTime() || 0)); return selective_edit_1.html `<div class="le__part__notifications__modal"> <div class="ls__part__notifications__notifications"> ${selective_edit_1.repeat(notifications, notification => notification.addedOn?.getUTCDate(), (notification) => this.templateNotification(editor, notification))} </div> </div>`; } } exports.NotificationsPart = NotificationsPart; function announceNotification(notification) { document.dispatchEvent(new CustomEvent(events_1.EVENT_NOTIFICATION_ADD, { detail: notification })); } exports.announceNotification = announceNotification; function readNotification(notification) { document.dispatchEvent(new CustomEvent(events_1.EVENT_NOTIFICATION_READ, { detail: notification })); } exports.readNotification = readNotification; function showNotification(notification) { document.dispatchEvent(new CustomEvent(events_1.EVENT_NOTIFICATION_SHOW, { detail: notification })); } exports.showNotification = showNotification; //# sourceMappingURL=notifications.js.map