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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. import { Notification } from './';
  7. /**
  8. * Implements a React {@link Component} which displays notifications and handles
  9. * automatic dismissmal after a notification is shown for a defined timeout
  10. * period.
  11. *
  12. * @extends {Component}
  13. */
  14. class NotificationsContainer extends Component {
  15. /**
  16. * {@code NotificationsContainer} component's property types.
  17. *
  18. * @static
  19. */
  20. static propTypes = {
  21. /**
  22. * The notifications to be displayed, with the first index being the
  23. * notification at the top and the rest shown below it in order.
  24. */
  25. _notifications: PropTypes.array,
  26. /**
  27. * Whether or not notifications should be displayed at all. If not,
  28. * notifications will be dismissed immediately.
  29. */
  30. _showNotifications: PropTypes.bool,
  31. /**
  32. * Invoked to update the redux store in order to remove notifications.
  33. */
  34. dispatch: PropTypes.func
  35. };
  36. /**
  37. * Initializes a new {@code NotificationsContainer} instance.
  38. *
  39. * @param {Object} props - The read-only React Component props with which
  40. * the new instance is to be initialized.
  41. */
  42. constructor(props) {
  43. super(props);
  44. /**
  45. * The timeout set for automatically dismissing a displayed
  46. * notification. This value is set on the instance and not state to
  47. * avoid additional re-renders.
  48. *
  49. * @type {number|null}
  50. */
  51. this._notificationDismissTimeout = null;
  52. // Bind event handlers so they are only bound once for every instance.
  53. this._onDismissed = this._onDismissed.bind(this);
  54. }
  55. /**
  56. * Sets a timeout if the currently displayed notification has changed.
  57. *
  58. * @inheritdoc
  59. * returns {void}
  60. */
  61. componentDidUpdate() {
  62. const { _notifications, _showNotifications } = this.props;
  63. if (_notifications.length) {
  64. const notification = _notifications[0];
  65. if (!_showNotifications || this._notificationDismissTimeout) {
  66. // No-op because there should already be a notification that
  67. // is waiting for dismissal.
  68. } else {
  69. const { timeout, uid } = notification;
  70. this._notificationDismissTimeout = setTimeout(() => {
  71. // Perform a no-op if a timeout is not specified.
  72. if (Number.isInteger(timeout)) {
  73. this._onDismissed(uid);
  74. }
  75. }, timeout);
  76. }
  77. }
  78. }
  79. /**
  80. * Clear any dismissal timeout that is still active.
  81. *
  82. * @inheritdoc
  83. * returns {void}
  84. */
  85. componentWillUnmount() {
  86. clearTimeout(this._notificationDismissTimeout);
  87. }
  88. /**
  89. * Implements React's {@link Component#render()}.
  90. *
  91. * @inheritdoc
  92. * @returns {ReactElement}
  93. */
  94. render() {
  95. return (
  96. <FlagGroup onDismissed = { this._onDismissed }>
  97. { this._renderFlags() }
  98. </FlagGroup>
  99. );
  100. }
  101. /**
  102. * Emits an action to remove the notification from the redux store so it
  103. * stops displaying.
  104. *
  105. * @param {number} flagUid - The id of the notification to be removed.
  106. * @private
  107. * @returns {void}
  108. */
  109. _onDismissed(flagUid) {
  110. clearTimeout(this._notificationDismissTimeout);
  111. this._notificationDismissTimeout = null;
  112. this.props.dispatch(hideNotification(flagUid));
  113. }
  114. /**
  115. * Renders notifications to display as ReactElements. An empty array will
  116. * be returned if notifications are disabled.
  117. *
  118. * @private
  119. * @returns {ReactElement[]}
  120. */
  121. _renderFlags() {
  122. const { _notifications, _showNotifications } = this.props;
  123. if (!_showNotifications) {
  124. return [];
  125. }
  126. return _notifications.map(notification => {
  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);