diff options
Diffstat (limited to 'devtools/client/shared/components/notification-box.js')
-rw-r--r-- | devtools/client/shared/components/notification-box.js | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/devtools/client/shared/components/notification-box.js b/devtools/client/shared/components/notification-box.js new file mode 100644 index 000000000..87fc76cd6 --- /dev/null +++ b/devtools/client/shared/components/notification-box.js @@ -0,0 +1,263 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const React = require("devtools/client/shared/vendor/react"); +const Immutable = require("devtools/client/shared/vendor/immutable"); +const { LocalizationHelper } = require("devtools/shared/l10n"); +const l10n = new LocalizationHelper("devtools/client/locales/components.properties"); + +// Shortcuts +const { PropTypes, createClass, DOM } = React; +const { div, span, button } = DOM; + +// Priority Levels +const PriorityLevels = { + PRIORITY_INFO_LOW: 1, + PRIORITY_INFO_MEDIUM: 2, + PRIORITY_INFO_HIGH: 3, + PRIORITY_WARNING_LOW: 4, + PRIORITY_WARNING_MEDIUM: 5, + PRIORITY_WARNING_HIGH: 6, + PRIORITY_CRITICAL_LOW: 7, + PRIORITY_CRITICAL_MEDIUM: 8, + PRIORITY_CRITICAL_HIGH: 9, + PRIORITY_CRITICAL_BLOCK: 10, +}; + +/** + * This component represents Notification Box - HTML alternative for + * <xul:notifictionbox> binding. + * + * See also MDN for more info about <xul:notificationbox>: + * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox + */ +var NotificationBox = createClass({ + displayName: "NotificationBox", + + propTypes: { + // List of notifications appended into the box. + notifications: PropTypes.arrayOf(PropTypes.shape({ + // label to appear on the notification. + label: PropTypes.string.isRequired, + + // Value used to identify the notification + value: PropTypes.string.isRequired, + + // URL of image to appear on the notification. If "" then an icon + // appropriate for the priority level is used. + image: PropTypes.string.isRequired, + + // Notification priority; see Priority Levels. + priority: PropTypes.number.isRequired, + + // Array of button descriptions to appear on the notification. + buttons: PropTypes.arrayOf(PropTypes.shape({ + // Function to be called when the button is activated. + // This function is passed three arguments: + // 1) the NotificationBox component the button is associated with + // 2) the button description as passed to appendNotification. + // 3) the element which was the target of the button press event. + // If the return value from this function is not True, then the + // notification is closed. The notification is also not closed + // if an error is thrown. + callback: PropTypes.func.isRequired, + + // The label to appear on the button. + label: PropTypes.string.isRequired, + + // The accesskey attribute set on the <button> element. + accesskey: PropTypes.string, + })), + + // A function to call to notify you of interesting things that happen + // with the notification box. + eventCallback: PropTypes.func, + })), + + // Message that should be shown when hovering over the close button + closeButtonTooltip: PropTypes.string + }, + + getDefaultProps() { + return { + closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip") + }; + }, + + getInitialState() { + return { + notifications: new Immutable.OrderedMap() + }; + }, + + /** + * Create a new notification and display it. If another notification is + * already present with a higher priority, the new notification will be + * added behind it. See `propTypes` for arguments description. + */ + appendNotification(label, value, image, priority, buttons = [], + eventCallback) { + // Priority level must be within expected interval + // (see priority levels at the top of this file). + if (priority < PriorityLevels.PRIORITY_INFO_LOW || + priority > PriorityLevels.PRIORITY_CRITICAL_BLOCK) { + throw new Error("Invalid notification priority " + priority); + } + + // Custom image URL is not supported yet. + if (image) { + throw new Error("Custom image URL is not supported yet"); + } + + let type = "warning"; + if (priority >= PriorityLevels.PRIORITY_CRITICAL_LOW) { + type = "critical"; + } else if (priority <= PriorityLevels.PRIORITY_INFO_HIGH) { + type = "info"; + } + + let notifications = this.state.notifications.set(value, { + label: label, + value: value, + image: image, + priority: priority, + type: type, + buttons: buttons, + eventCallback: eventCallback, + }); + + // High priorities must be on top. + notifications = notifications.sortBy((val, key) => { + return -val.priority; + }); + + this.setState({ + notifications: notifications + }); + }, + + /** + * Remove specific notification from the list. + */ + removeNotification(notification) { + this.close(this.state.notifications.get(notification.value)); + }, + + /** + * Returns an object that represents a notification. It can be + * used to close it. + */ + getNotificationWithValue(value) { + let notification = this.state.notifications.get(value); + if (!notification) { + return null; + } + + // Return an object that can be used to remove the notification + // later (using `removeNotification` method) or directly close it. + return Object.assign({}, notification, { + close: () => { + this.close(notification); + } + }); + }, + + getCurrentNotification() { + return this.state.notifications.first(); + }, + + /** + * Close specified notification. + */ + close(notification) { + if (!notification) { + return; + } + + if (notification.eventCallback) { + notification.eventCallback("removed"); + } + + this.setState({ + notifications: this.state.notifications.remove(notification.value) + }); + }, + + /** + * Render a button. A notification can have a set of custom buttons. + * These are used to execute custom callback. + */ + renderButton(props, notification) { + let onClick = event => { + if (props.callback) { + let result = props.callback(this, props, event.target); + if (!result) { + this.close(notification); + } + event.stopPropagation(); + } + }; + + return ( + button({ + key: props.label, + className: "notification-button", + accesskey: props.accesskey, + onClick: onClick}, + props.label + ) + ); + }, + + /** + * Render a notification. + */ + renderNotification(notification) { + return ( + div({ + key: notification.value, + className: "notification", + "data-type": notification.type}, + div({className: "notificationInner"}, + div({className: "details"}, + div({ + className: "messageImage", + "data-type": notification.type}), + span({className: "messageText"}, + notification.label + ), + notification.buttons.map(props => + this.renderButton(props, notification) + ) + ), + div({ + className: "messageCloseButton", + title: this.props.closeButtonTooltip, + onClick: this.close.bind(this, notification)} + ) + ) + ) + ); + }, + + /** + * Render the top (highest priority) notification. Only one + * notification is rendered at a time. + */ + render() { + let notification = this.state.notifications.first(); + let content = notification ? + this.renderNotification(notification) : + null; + + return div({className: "notificationbox"}, + content + ); + }, +}); + +module.exports.NotificationBox = NotificationBox; +module.exports.PriorityLevels = PriorityLevels; |