notification-box.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const React = require("devtools/client/shared/vendor/react");
  6. const Immutable = require("devtools/client/shared/vendor/immutable");
  7. const { LocalizationHelper } = require("devtools/shared/l10n");
  8. const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
  9. // Shortcuts
  10. const { PropTypes, createClass, DOM } = React;
  11. const { div, span, button } = DOM;
  12. // Priority Levels
  13. const PriorityLevels = {
  14. PRIORITY_INFO_LOW: 1,
  15. PRIORITY_INFO_MEDIUM: 2,
  16. PRIORITY_INFO_HIGH: 3,
  17. PRIORITY_WARNING_LOW: 4,
  18. PRIORITY_WARNING_MEDIUM: 5,
  19. PRIORITY_WARNING_HIGH: 6,
  20. PRIORITY_CRITICAL_LOW: 7,
  21. PRIORITY_CRITICAL_MEDIUM: 8,
  22. PRIORITY_CRITICAL_HIGH: 9,
  23. PRIORITY_CRITICAL_BLOCK: 10,
  24. };
  25. /**
  26. * This component represents Notification Box - HTML alternative for
  27. * <xul:notifictionbox> binding.
  28. *
  29. * See also MDN for more info about <xul:notificationbox>:
  30. * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox
  31. */
  32. var NotificationBox = createClass({
  33. displayName: "NotificationBox",
  34. propTypes: {
  35. // List of notifications appended into the box.
  36. notifications: PropTypes.arrayOf(PropTypes.shape({
  37. // label to appear on the notification.
  38. label: PropTypes.string.isRequired,
  39. // Value used to identify the notification
  40. value: PropTypes.string.isRequired,
  41. // URL of image to appear on the notification. If "" then an icon
  42. // appropriate for the priority level is used.
  43. image: PropTypes.string.isRequired,
  44. // Notification priority; see Priority Levels.
  45. priority: PropTypes.number.isRequired,
  46. // Array of button descriptions to appear on the notification.
  47. buttons: PropTypes.arrayOf(PropTypes.shape({
  48. // Function to be called when the button is activated.
  49. // This function is passed three arguments:
  50. // 1) the NotificationBox component the button is associated with
  51. // 2) the button description as passed to appendNotification.
  52. // 3) the element which was the target of the button press event.
  53. // If the return value from this function is not True, then the
  54. // notification is closed. The notification is also not closed
  55. // if an error is thrown.
  56. callback: PropTypes.func.isRequired,
  57. // The label to appear on the button.
  58. label: PropTypes.string.isRequired,
  59. // The accesskey attribute set on the <button> element.
  60. accesskey: PropTypes.string,
  61. })),
  62. // A function to call to notify you of interesting things that happen
  63. // with the notification box.
  64. eventCallback: PropTypes.func,
  65. })),
  66. // Message that should be shown when hovering over the close button
  67. closeButtonTooltip: PropTypes.string
  68. },
  69. getDefaultProps() {
  70. return {
  71. closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip")
  72. };
  73. },
  74. getInitialState() {
  75. return {
  76. notifications: new Immutable.OrderedMap()
  77. };
  78. },
  79. /**
  80. * Create a new notification and display it. If another notification is
  81. * already present with a higher priority, the new notification will be
  82. * added behind it. See `propTypes` for arguments description.
  83. */
  84. appendNotification(label, value, image, priority, buttons = [],
  85. eventCallback) {
  86. // Priority level must be within expected interval
  87. // (see priority levels at the top of this file).
  88. if (priority < PriorityLevels.PRIORITY_INFO_LOW ||
  89. priority > PriorityLevels.PRIORITY_CRITICAL_BLOCK) {
  90. throw new Error("Invalid notification priority " + priority);
  91. }
  92. // Custom image URL is not supported yet.
  93. if (image) {
  94. throw new Error("Custom image URL is not supported yet");
  95. }
  96. let type = "warning";
  97. if (priority >= PriorityLevels.PRIORITY_CRITICAL_LOW) {
  98. type = "critical";
  99. } else if (priority <= PriorityLevels.PRIORITY_INFO_HIGH) {
  100. type = "info";
  101. }
  102. let notifications = this.state.notifications.set(value, {
  103. label: label,
  104. value: value,
  105. image: image,
  106. priority: priority,
  107. type: type,
  108. buttons: buttons,
  109. eventCallback: eventCallback,
  110. });
  111. // High priorities must be on top.
  112. notifications = notifications.sortBy((val, key) => {
  113. return -val.priority;
  114. });
  115. this.setState({
  116. notifications: notifications
  117. });
  118. },
  119. /**
  120. * Remove specific notification from the list.
  121. */
  122. removeNotification(notification) {
  123. this.close(this.state.notifications.get(notification.value));
  124. },
  125. /**
  126. * Returns an object that represents a notification. It can be
  127. * used to close it.
  128. */
  129. getNotificationWithValue(value) {
  130. let notification = this.state.notifications.get(value);
  131. if (!notification) {
  132. return null;
  133. }
  134. // Return an object that can be used to remove the notification
  135. // later (using `removeNotification` method) or directly close it.
  136. return Object.assign({}, notification, {
  137. close: () => {
  138. this.close(notification);
  139. }
  140. });
  141. },
  142. getCurrentNotification() {
  143. return this.state.notifications.first();
  144. },
  145. /**
  146. * Close specified notification.
  147. */
  148. close(notification) {
  149. if (!notification) {
  150. return;
  151. }
  152. if (notification.eventCallback) {
  153. notification.eventCallback("removed");
  154. }
  155. this.setState({
  156. notifications: this.state.notifications.remove(notification.value)
  157. });
  158. },
  159. /**
  160. * Render a button. A notification can have a set of custom buttons.
  161. * These are used to execute custom callback.
  162. */
  163. renderButton(props, notification) {
  164. let onClick = event => {
  165. if (props.callback) {
  166. let result = props.callback(this, props, event.target);
  167. if (!result) {
  168. this.close(notification);
  169. }
  170. event.stopPropagation();
  171. }
  172. };
  173. return (
  174. button({
  175. key: props.label,
  176. className: "notification-button",
  177. accesskey: props.accesskey,
  178. onClick: onClick},
  179. props.label
  180. )
  181. );
  182. },
  183. /**
  184. * Render a notification.
  185. */
  186. renderNotification(notification) {
  187. return (
  188. div({
  189. key: notification.value,
  190. className: "notification",
  191. "data-type": notification.type},
  192. div({className: "notificationInner"},
  193. div({className: "details"},
  194. div({
  195. className: "messageImage",
  196. "data-type": notification.type}),
  197. span({className: "messageText"},
  198. notification.label
  199. ),
  200. notification.buttons.map(props =>
  201. this.renderButton(props, notification)
  202. )
  203. ),
  204. div({
  205. className: "messageCloseButton",
  206. title: this.props.closeButtonTooltip,
  207. onClick: this.close.bind(this, notification)}
  208. )
  209. )
  210. )
  211. );
  212. },
  213. /**
  214. * Render the top (highest priority) notification. Only one
  215. * notification is rendered at a time.
  216. */
  217. render() {
  218. let notification = this.state.notifications.first();
  219. let content = notification ?
  220. this.renderNotification(notification) :
  221. null;
  222. return div({className: "notificationbox"},
  223. content
  224. );
  225. },
  226. });
  227. module.exports.NotificationBox = NotificationBox;
  228. module.exports.PriorityLevels = PriorityLevels;