@blinkk/editor
Version:
Structured content editor with live previews.
397 lines • 16.1 kB
JavaScript
"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"
=${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"
=${(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'
: ''}"
=${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"
=${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"
=${(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