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.

135 lines
3.8 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. hasMore: PropTypes.bool,
  20. prepend: PropTypes.node,
  21. emptyMessage: PropTypes.node,
  22. };
  23. static defaultProps = {
  24. trackScroll: true,
  25. };
  26. intersectionObserverWrapper = new IntersectionObserverWrapper();
  27. handleScroll = (e) => {
  28. const { scrollTop, scrollHeight, clientHeight } = e.target;
  29. const offset = scrollHeight - scrollTop - clientHeight;
  30. this._oldScrollPosition = scrollHeight - scrollTop;
  31. if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
  32. this.props.onScrollToBottom();
  33. } else if (scrollTop < 100 && this.props.onScrollToTop) {
  34. this.props.onScrollToTop();
  35. } else if (this.props.onScroll) {
  36. this.props.onScroll();
  37. }
  38. }
  39. componentDidMount () {
  40. this.attachScrollListener();
  41. this.attachIntersectionObserver();
  42. }
  43. componentDidUpdate (prevProps) {
  44. if ((prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition) && this.node.scrollTop > 0) {
  45. this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
  46. }
  47. }
  48. componentWillUnmount () {
  49. this.detachScrollListener();
  50. this.detachIntersectionObserver();
  51. }
  52. attachIntersectionObserver () {
  53. this.intersectionObserverWrapper.connect({
  54. root: this.node,
  55. rootMargin: '300% 0px',
  56. });
  57. }
  58. detachIntersectionObserver () {
  59. this.intersectionObserverWrapper.disconnect();
  60. }
  61. attachScrollListener () {
  62. this.node.addEventListener('scroll', this.handleScroll);
  63. }
  64. detachScrollListener () {
  65. this.node.removeEventListener('scroll', this.handleScroll);
  66. }
  67. setRef = (c) => {
  68. this.node = c;
  69. }
  70. handleLoadMore = (e) => {
  71. e.preventDefault();
  72. this.props.onScrollToBottom();
  73. }
  74. render () {
  75. const { statusIds, onScrollToBottom, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
  76. let loadMore = null;
  77. let scrollableArea = null;
  78. if (!isLoading && statusIds.size > 0 && hasMore) {
  79. loadMore = <LoadMore onClick={this.handleLoadMore} />;
  80. }
  81. if (isLoading || statusIds.size > 0 || !emptyMessage) {
  82. scrollableArea = (
  83. <div className='scrollable' ref={this.setRef}>
  84. <div className='status-list'>
  85. {prepend}
  86. {statusIds.map((statusId) => {
  87. return <StatusContainer key={statusId} id={statusId} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
  88. })}
  89. {loadMore}
  90. </div>
  91. </div>
  92. );
  93. } else {
  94. scrollableArea = (
  95. <div className='empty-column-indicator' ref={this.setRef}>
  96. {emptyMessage}
  97. </div>
  98. );
  99. }
  100. if (trackScroll) {
  101. return (
  102. <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
  103. {scrollableArea}
  104. </ScrollContainer>
  105. );
  106. } else {
  107. return scrollableArea;
  108. }
  109. }
  110. }
  111. export default StatusList;