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.

139 lines
4.9 KiB

  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import PropTypes from 'prop-types';
  4. import ImmutablePropTypes from 'react-immutable-proptypes';
  5. import Column from '../ui/components/column';
  6. import { expandNotifications, clearNotifications, scrollTopNotifications } from '../../actions/notifications';
  7. import NotificationContainer from './containers/notification_container';
  8. import { ScrollContainer } from 'react-router-scroll';
  9. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  10. import ColumnSettingsContainer from './containers/column_settings_container';
  11. import { createSelector } from 'reselect';
  12. import Immutable from 'immutable';
  13. import LoadMore from '../../components/load_more';
  14. import ClearColumnButton from './components/clear_column_button';
  15. import { openModal } from '../../actions/modal';
  16. const messages = defineMessages({
  17. title: { id: 'column.notifications', defaultMessage: 'Notifications' },
  18. clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
  19. clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
  20. });
  21. const getNotifications = createSelector([
  22. state => Immutable.List(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
  23. state => state.getIn(['notifications', 'items'])
  24. ], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type'))));
  25. const mapStateToProps = state => ({
  26. notifications: getNotifications(state),
  27. isLoading: state.getIn(['notifications', 'isLoading'], true),
  28. isUnread: state.getIn(['notifications', 'unread']) > 0
  29. });
  30. class Notifications extends React.PureComponent {
  31. static propTypes = {
  32. notifications: ImmutablePropTypes.list.isRequired,
  33. dispatch: PropTypes.func.isRequired,
  34. shouldUpdateScroll: PropTypes.func,
  35. intl: PropTypes.object.isRequired,
  36. isLoading: PropTypes.bool,
  37. isUnread: PropTypes.bool
  38. };
  39. static defaultProps = {
  40. trackScroll: true
  41. };
  42. handleScroll = (e) => {
  43. const { scrollTop, scrollHeight, clientHeight } = e.target;
  44. const offset = scrollHeight - scrollTop - clientHeight;
  45. this._oldScrollPosition = scrollHeight - scrollTop;
  46. if (250 > offset && !this.props.isLoading) {
  47. this.props.dispatch(expandNotifications());
  48. } else if (scrollTop < 100) {
  49. this.props.dispatch(scrollTopNotifications(true));
  50. } else {
  51. this.props.dispatch(scrollTopNotifications(false));
  52. }
  53. }
  54. componentDidUpdate (prevProps) {
  55. if (this.node.scrollTop > 0 && (prevProps.notifications.size < this.props.notifications.size && prevProps.notifications.first() !== this.props.notifications.first() && !!this._oldScrollPosition)) {
  56. this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
  57. }
  58. }
  59. handleLoadMore = (e) => {
  60. e.preventDefault();
  61. this.props.dispatch(expandNotifications());
  62. }
  63. handleClear = () => {
  64. const { dispatch, intl } = this.props;
  65. dispatch(openModal('CONFIRM', {
  66. message: intl.formatMessage(messages.clearMessage),
  67. confirm: intl.formatMessage(messages.clearConfirm),
  68. onConfirm: () => dispatch(clearNotifications())
  69. }));
  70. }
  71. setRef = (c) => {
  72. this.node = c;
  73. }
  74. render () {
  75. const { intl, notifications, shouldUpdateScroll, isLoading, isUnread } = this.props;
  76. let loadMore = '';
  77. let scrollableArea = '';
  78. let unread = '';
  79. if (!isLoading && notifications.size > 0) {
  80. loadMore = <LoadMore onClick={this.handleLoadMore} />;
  81. }
  82. if (isUnread) {
  83. unread = <div className='notifications__unread-indicator' />;
  84. }
  85. if (isLoading && this.scrollableArea) {
  86. scrollableArea = this.scrollableArea;
  87. } else if (notifications.size > 0) {
  88. scrollableArea = (
  89. <div className='scrollable' onScroll={this.handleScroll} ref={this.setRef}>
  90. {unread}
  91. <div>
  92. {notifications.map(item => <NotificationContainer key={item.get('id')} notification={item} accountId={item.get('account')} />)}
  93. {loadMore}
  94. </div>
  95. </div>
  96. );
  97. } else {
  98. scrollableArea = (
  99. <div className='empty-column-indicator' ref={this.setRef}>
  100. <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />
  101. </div>
  102. );
  103. }
  104. this.scrollableArea = scrollableArea;
  105. return (
  106. <Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}>
  107. <ColumnSettingsContainer />
  108. <ClearColumnButton onClick={this.handleClear} />
  109. <ScrollContainer scrollKey='notifications' shouldUpdateScroll={shouldUpdateScroll}>
  110. {scrollableArea}
  111. </ScrollContainer>
  112. </Column>
  113. );
  114. }
  115. }
  116. export default connect(mapStateToProps)(injectIntl(Notifications));