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.

AbstractNotificationsContainer.js 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // @flow
  2. import { Component } from 'react';
  3. import { getOverlayToRender } from '../../overlay';
  4. import { hideNotification } from '../actions';
  5. export type Props = {
  6. /**
  7. * The notifications to be displayed, with the first index being the
  8. * notification at the top and the rest shown below it in order.
  9. */
  10. _notifications: Array<Object>,
  11. /**
  12. * Invoked to update the redux store in order to remove notifications.
  13. */
  14. dispatch: Function
  15. };
  16. /**
  17. * Abstract class for {@code NotificationsContainer} component.
  18. */
  19. export default class AbstractNotificationsContainer<P: Props>
  20. extends Component<P> {
  21. /**
  22. * A timeout id returned by setTimeout.
  23. */
  24. _notificationDismissTimeout: ?TimeoutID;
  25. /**
  26. * Initializes a new {@code AbstractNotificationsContainer} instance.
  27. *
  28. * @inheritdoc
  29. */
  30. constructor(props: P) {
  31. super(props);
  32. /**
  33. * The timeout set for automatically dismissing a displayed
  34. * notification. This value is set on the instance and not state to
  35. * avoid additional re-renders.
  36. *
  37. * @type {number|null}
  38. */
  39. this._notificationDismissTimeout = null;
  40. // Bind event handlers so they are only bound once for every instance.
  41. this._onDismissed = this._onDismissed.bind(this);
  42. }
  43. /**
  44. * Sets a timeout if the currently displayed notification has changed.
  45. *
  46. * @inheritdoc
  47. */
  48. componentDidUpdate(prevProps: P) {
  49. const { _notifications } = this.props;
  50. if (_notifications.length) {
  51. const notification = _notifications[0];
  52. const previousNotification
  53. = prevProps._notifications.length
  54. ? prevProps._notifications[0]
  55. : undefined;
  56. if (notification !== previousNotification) {
  57. this._clearNotificationDismissTimeout();
  58. if (notification) {
  59. const { timeout, uid } = notification;
  60. this._notificationDismissTimeout = setTimeout(() => {
  61. // Perform a no-op if a timeout is not specified.
  62. if (Number.isInteger(timeout)) {
  63. this._onDismissed(uid);
  64. }
  65. }, timeout);
  66. }
  67. }
  68. } else if (this._notificationDismissTimeout) {
  69. // Clear timeout when all notifications are cleared (e.g external
  70. // call to clear them)
  71. this._clearNotificationDismissTimeout();
  72. }
  73. }
  74. /**
  75. * Clear any dismissal timeout that is still active.
  76. *
  77. * @inheritdoc
  78. * returns {void}
  79. */
  80. componentWillUnmount() {
  81. this._clearNotificationDismissTimeout();
  82. }
  83. _onDismissed: number => void;
  84. /**
  85. * Clears the running notification dismiss timeout, if any.
  86. *
  87. * @returns {void}
  88. */
  89. _clearNotificationDismissTimeout() {
  90. this._notificationDismissTimeout
  91. && clearTimeout(this._notificationDismissTimeout);
  92. this._notificationDismissTimeout = null;
  93. }
  94. /**
  95. * Emits an action to remove the notification from the redux store so it
  96. * stops displaying.
  97. *
  98. * @param {number} uid - The id of the notification to be removed.
  99. * @private
  100. * @returns {void}
  101. */
  102. _onDismissed(uid) {
  103. this._clearNotificationDismissTimeout();
  104. this.props.dispatch(hideNotification(uid));
  105. }
  106. }
  107. /**
  108. * Maps (parts of) the Redux state to the associated NotificationsContainer's
  109. * props.
  110. *
  111. * @param {Object} state - The Redux state.
  112. * @private
  113. * @returns {{
  114. * _notifications: Array
  115. * }}
  116. */
  117. export function _abstractMapStateToProps(state: Object) {
  118. const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
  119. const { enabled, notifications } = state['features/notifications'];
  120. return {
  121. _notifications: enabled && !isAnyOverlayVisible ? notifications : []
  122. };
  123. }