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 7.0KB

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