You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NotificationsContainer.web.js 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { FlagGroup } from '@atlaskit/flag';
  2. import PropTypes from 'prop-types';
  3. import React, { Component } from 'react';
  4. import { connect } from 'react-redux';
  5. import { hideNotification } from '../actions';
  6. /**
  7. * Implements a React {@link Component} which displays notifications and handles
  8. * automatic dismissmal after a notification is shown for a defined timeout
  9. * period.
  10. *
  11. * @extends {Component}
  12. */
  13. class NotificationsContainer extends Component {
  14. /**
  15. * {@code NotificationsContainer} component's property types.
  16. *
  17. * @static
  18. */
  19. static propTypes = {
  20. /**
  21. * The notifications to be displayed, with the first index being the
  22. * notification at the top and the rest shown below it in order.
  23. */
  24. _notifications: PropTypes.array,
  25. /**
  26. * Whether or not notifications should be displayed at all. If not,
  27. * notifications will be dismissed immediately.
  28. */
  29. _showNotifications: PropTypes.bool,
  30. /**
  31. * Invoked to update the redux store in order to remove notifications.
  32. */
  33. dispatch: PropTypes.func
  34. };
  35. /**
  36. * Initializes a new {@code NotificationsContainer} instance.
  37. *
  38. * @param {Object} props - The read-only React Component props with which
  39. * the new instance is to be initialized.
  40. */
  41. constructor(props) {
  42. super(props);
  43. /**
  44. * The timeout set for automatically dismissing a displayed
  45. * notification. This value is set on the instance and not state to
  46. * avoid additional re-renders.
  47. *
  48. * @type {number|null}
  49. */
  50. this._notificationDismissTimeout = null;
  51. // Bind event handlers so they are only bound once for every instance.
  52. this._onDismissed = this._onDismissed.bind(this);
  53. }
  54. /**
  55. * Sets a timeout if the currently displayed notification has changed.
  56. *
  57. * @inheritdoc
  58. * returns {void}
  59. */
  60. componentDidUpdate() {
  61. const { _notifications, _showNotifications } = this.props;
  62. if (_notifications.length) {
  63. const notification = _notifications[0];
  64. if (!_showNotifications || this._notificationDismissTimeout) {
  65. // No-op because there should already be a notification that
  66. // is waiting for dismissal.
  67. } else {
  68. const { timeout, uid } = notification;
  69. this._notificationDismissTimeout = setTimeout(() => {
  70. // Perform a no-op if a timeout is not specified.
  71. if (Number.isInteger(timeout)) {
  72. this._onDismissed(uid);
  73. }
  74. }, timeout);
  75. }
  76. }
  77. }
  78. /**
  79. * Clear any dismissal timeout that is still active.
  80. *
  81. * @inheritdoc
  82. * returns {void}
  83. */
  84. componentWillUnmount() {
  85. clearTimeout(this._notificationDismissTimeout);
  86. }
  87. /**
  88. * Implements React's {@link Component#render()}.
  89. *
  90. * @inheritdoc
  91. * @returns {ReactElement}
  92. */
  93. render() {
  94. return (
  95. <FlagGroup onDismissed = { this._onDismissed }>
  96. { this._renderFlags() }
  97. </FlagGroup>
  98. );
  99. }
  100. /**
  101. * Emits an action to remove the notification from the redux store so it
  102. * stops displaying.
  103. *
  104. * @param {number} flagUid - The id of the notification to be removed.
  105. * @private
  106. * @returns {void}
  107. */
  108. _onDismissed(flagUid) {
  109. clearTimeout(this._notificationDismissTimeout);
  110. this._notificationDismissTimeout = null;
  111. this.props.dispatch(hideNotification(flagUid));
  112. }
  113. /**
  114. * Renders notifications to display as ReactElements. An empty array will
  115. * be returned if notifications are disabled.
  116. *
  117. * @private
  118. * @returns {ReactElement[]}
  119. */
  120. _renderFlags() {
  121. const { _notifications, _showNotifications } = this.props;
  122. if (!_showNotifications) {
  123. return [];
  124. }
  125. return _notifications.map(notification => {
  126. const Notification = notification.component;
  127. const { props, uid } = notification;
  128. // The id attribute is necessary as {@code FlagGroup} looks for
  129. // either id or key to set a key on notifications, but accessing
  130. // props.key will cause React to print an error.
  131. return (
  132. <Notification
  133. { ...props }
  134. id = { uid }
  135. key = { uid }
  136. uid = { uid } />
  137. );
  138. });
  139. }
  140. }
  141. /**
  142. * Maps (parts of) the Redux state to the associated NotificationsContainer's
  143. * props.
  144. *
  145. * @param {Object} state - The Redux state.
  146. * @private
  147. * @returns {{
  148. * _notifications: Array
  149. * }}
  150. */
  151. function _mapStateToProps(state) {
  152. // TODO: Per existing behavior, notifications should not display when an
  153. // overlay is visible. This logic for checking overlay display can likely be
  154. // simplified.
  155. const {
  156. connectionEstablished,
  157. haveToReload,
  158. isMediaPermissionPromptVisible,
  159. suspendDetected
  160. } = state['features/overlay'];
  161. const isAnyOverlayVisible = (connectionEstablished && haveToReload)
  162. || isMediaPermissionPromptVisible
  163. || suspendDetected
  164. || state['features/base/jwt'].calleeInfoVisible;
  165. const { enabled, notifications } = state['features/notifications'];
  166. return {
  167. _notifications: notifications,
  168. _showNotifications: enabled && !isAnyOverlayVisible
  169. };
  170. }
  171. export default connect(_mapStateToProps)(NotificationsContainer);