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.

143 lines
3.9 KiB

  1. import React from 'react';
  2. import ImmutablePropTypes from 'react-immutable-proptypes';
  3. import { ScrollContainer } from 'react-router-scroll';
  4. import PropTypes from 'prop-types';
  5. import StatusContainer from '../containers/status_container';
  6. import LoadMore from './load_more';
  7. import ImmutablePureComponent from 'react-immutable-pure-component';
  8. import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
  9. class StatusList extends ImmutablePureComponent {
  10. static propTypes = {
  11. scrollKey: PropTypes.string.isRequired,
  12. statusIds: ImmutablePropTypes.list.isRequired,
  13. onScrollToBottom: PropTypes.func,
  14. onScrollToTop: PropTypes.func,
  15. onScroll: PropTypes.func,
  16. trackScroll: PropTypes.bool,
  17. shouldUpdateScroll: PropTypes.func,
  18. isLoading: PropTypes.bool,
  19. isUnread: PropTypes.bool,
  20. hasMore: PropTypes.bool,
  21. prepend: PropTypes.node,
  22. emptyMessage: PropTypes.node,
  23. };
  24. static defaultProps = {
  25. trackScroll: true,
  26. };
  27. intersectionObserverWrapper = new IntersectionObserverWrapper();
  28. handleScroll = (e) => {
  29. const { scrollTop, scrollHeight, clientHeight } = e.target;
  30. const offset = scrollHeight - scrollTop - clientHeight;
  31. this._oldScrollPosition = scrollHeight - scrollTop;
  32. if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
  33. this.props.onScrollToBottom();
  34. } else if (scrollTop < 100 && this.props.onScrollToTop) {
  35. this.props.onScrollToTop();
  36. } else if (this.props.onScroll) {
  37. this.props.onScroll();
  38. }
  39. }
  40. componentDidMount () {
  41. this.attachScrollListener();
  42. this.attachIntersectionObserver();
  43. }
  44. componentDidUpdate (prevProps) {
  45. if ((prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition) && this.node.scrollTop > 0) {
  46. this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
  47. }
  48. }
  49. componentWillUnmount () {
  50. this.detachScrollListener();
  51. this.detachIntersectionObserver();
  52. }
  53. attachIntersectionObserver () {
  54. this.intersectionObserverWrapper.connect({
  55. root: this.node,
  56. rootMargin: '300% 0px',
  57. });
  58. }
  59. detachIntersectionObserver () {
  60. this.intersectionObserverWrapper.disconnect();
  61. }
  62. attachScrollListener () {
  63. this.node.addEventListener('scroll', this.handleScroll);
  64. }
  65. detachScrollListener () {
  66. this.node.removeEventListener('scroll', this.handleScroll);
  67. }
  68. setRef = (c) => {
  69. this.node = c;
  70. }
  71. handleLoadMore = (e) => {
  72. e.preventDefault();
  73. this.props.onScrollToBottom();
  74. }
  75. render () {
  76. const { statusIds, onScrollToBottom, scrollKey, trackScroll, shouldUpdateScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props;
  77. let loadMore = null;
  78. let scrollableArea = null;
  79. let unread = null;
  80. if (!isLoading && statusIds.size > 0 && hasMore) {
  81. loadMore = <LoadMore onClick={this.handleLoadMore} />;
  82. }
  83. if (isUnread) {
  84. unread = <div className='status-list__unread-indicator' />;
  85. }
  86. if (isLoading || statusIds.size > 0 || !emptyMessage) {
  87. scrollableArea = (
  88. <div className='scrollable' ref={this.setRef}>
  89. {unread}
  90. <div className='status-list'>
  91. {prepend}
  92. {statusIds.map((statusId) => {
  93. return <StatusContainer key={statusId} id={statusId} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
  94. })}
  95. {loadMore}
  96. </div>
  97. </div>
  98. );
  99. } else {
  100. scrollableArea = (
  101. <div className='empty-column-indicator' ref={this.setRef}>
  102. {emptyMessage}
  103. </div>
  104. );
  105. }
  106. if (trackScroll) {
  107. return (
  108. <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
  109. {scrollableArea}
  110. </ScrollContainer>
  111. );
  112. } else {
  113. return scrollableArea;
  114. }
  115. }
  116. }
  117. export default StatusList;