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

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