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.tsx 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /* eslint-disable lines-around-comment */
  2. import React, { Component } from 'react';
  3. import { WithTranslation } from 'react-i18next';
  4. import { Platform } from 'react-native';
  5. import { Edge, SafeAreaView } from 'react-native-safe-area-context';
  6. import { IReduxState } from '../../../app/types';
  7. import { connect } from '../../../base/redux/functions';
  8. import { hideNotification } from '../../actions';
  9. import { areThereNotifications } from '../../functions';
  10. import NotificationsTransition from '../NotificationsTransition';
  11. import Notification from './Notification';
  12. // @ts-ignore
  13. import styles from './styles';
  14. interface IProps extends WithTranslation {
  15. /**
  16. * The notifications to be displayed, with the first index being the
  17. * notification at the top and the rest shown below it in order.
  18. */
  19. _notifications: Array<Object>;
  20. /**
  21. * Invoked to update the redux store in order to remove notifications.
  22. */
  23. dispatch: Function;
  24. /**
  25. * Checks toolbox visibility.
  26. */
  27. toolboxVisible: boolean;
  28. }
  29. /**
  30. * Implements a React {@link Component} which displays notifications and handles
  31. * automatic dismissal after a notification is shown for a defined timeout
  32. * period.
  33. *
  34. * @augments {Component}
  35. */
  36. class NotificationsContainer extends Component<IProps> {
  37. /**
  38. * A timeout id returned by setTimeout.
  39. */
  40. _notificationDismissTimeout: any;
  41. /**
  42. * Initializes a new {@code NotificationsContainer} instance.
  43. *
  44. * @inheritdoc
  45. */
  46. constructor(props: IProps) {
  47. super(props);
  48. /**
  49. * The timeout set for automatically dismissing a displayed
  50. * notification. This value is set on the instance and not state to
  51. * avoid additional re-renders.
  52. *
  53. * @type {number|null}
  54. */
  55. this._notificationDismissTimeout = null;
  56. // Bind event handlers so they are only bound once for every instance.
  57. this._onDismissed = this._onDismissed.bind(this);
  58. }
  59. /**
  60. * Sets a timeout (if applicable).
  61. *
  62. * @inheritdoc
  63. */
  64. componentDidMount() {
  65. // Set the initial dismiss timeout (if any)
  66. // @ts-ignore
  67. this._manageDismissTimeout();
  68. }
  69. /**
  70. * Sets a timeout if the currently displayed notification has changed.
  71. *
  72. * @inheritdoc
  73. */
  74. componentDidUpdate(prevProps: IProps) {
  75. this._manageDismissTimeout(prevProps);
  76. }
  77. /**
  78. * Sets/clears the dismiss timeout for the top notification.
  79. *
  80. * @param {IProps} [prevProps] - The previous properties (if called from
  81. * {@code componentDidUpdate}).
  82. * @returns {void}
  83. * @private
  84. */
  85. _manageDismissTimeout(prevProps: IProps) {
  86. const { _notifications } = this.props;
  87. if (_notifications.length) {
  88. const notification = _notifications[0];
  89. const previousNotification = prevProps?._notifications.length
  90. ? prevProps._notifications[0] : undefined;
  91. if (notification !== previousNotification) {
  92. this._clearNotificationDismissTimeout();
  93. // @ts-ignore
  94. if (notification?.timeout) {
  95. // @ts-ignore
  96. const { timeout, uid } = notification;
  97. // @ts-ignore
  98. this._notificationDismissTimeout = setTimeout(() => {
  99. // Perform a no-op if a timeout is not specified.
  100. this._onDismissed(uid);
  101. }, timeout);
  102. }
  103. }
  104. } else if (this._notificationDismissTimeout) {
  105. // Clear timeout when all notifications are cleared (e.g external
  106. // call to clear them)
  107. this._clearNotificationDismissTimeout();
  108. }
  109. }
  110. /**
  111. * Clear any dismissal timeout that is still active.
  112. *
  113. * @inheritdoc
  114. */
  115. componentWillUnmount() {
  116. this._clearNotificationDismissTimeout();
  117. }
  118. /**
  119. * Clears the running notification dismiss timeout, if any.
  120. *
  121. * @returns {void}
  122. */
  123. _clearNotificationDismissTimeout() {
  124. this._notificationDismissTimeout && clearTimeout(this._notificationDismissTimeout);
  125. this._notificationDismissTimeout = null;
  126. }
  127. /**
  128. * Emits an action to remove the notification from the redux store so it
  129. * stops displaying.
  130. *
  131. * @param {Object} uid - The id of the notification to be removed.
  132. * @private
  133. * @returns {void}
  134. */
  135. _onDismissed(uid: any) {
  136. const { _notifications } = this.props;
  137. // Clear the timeout only if it's the top notification that's being
  138. // dismissed (the timeout is set only for the top one).
  139. // @ts-ignore
  140. if (!_notifications.length || _notifications[0].uid === uid) {
  141. this._clearNotificationDismissTimeout();
  142. }
  143. this.props.dispatch(hideNotification(uid));
  144. }
  145. /**
  146. * Implements React's {@link Component#render()}.
  147. *
  148. * @inheritdoc
  149. */
  150. render() {
  151. const { _notifications, toolboxVisible } = this.props;
  152. const notificationsContainerStyle
  153. = toolboxVisible ? styles.withToolbox : styles.withoutToolbox;
  154. return (
  155. <SafeAreaView
  156. edges = { [ Platform.OS === 'ios' && 'bottom', 'left', 'right' ].filter(Boolean) as Edge[] }
  157. style = { notificationsContainerStyle as any }>
  158. <NotificationsTransition>
  159. {
  160. _notifications.map(notification => {
  161. // @ts-ignore
  162. const { props, uid } = notification;
  163. return (
  164. <Notification
  165. { ...props }
  166. key = { uid }
  167. onDismissed = { this._onDismissed }
  168. uid = { uid } />
  169. );
  170. })
  171. }
  172. </NotificationsTransition>
  173. </SafeAreaView>
  174. );
  175. }
  176. }
  177. /**
  178. * Maps (parts of) the Redux state to the associated NotificationsContainer's
  179. * props.
  180. *
  181. * @param {Object} state - The Redux state.
  182. * @private
  183. * @returns {Props}
  184. */
  185. export function mapStateToProps(state: IReduxState) {
  186. const { notifications } = state['features/notifications'];
  187. const _visible = areThereNotifications(state);
  188. return {
  189. _notifications: _visible ? notifications : []
  190. };
  191. }
  192. export default connect(mapStateToProps)(NotificationsContainer);