The code powering m.abunchtell.com https://m.abunchtell.com
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.
 
 
 
 

147 lines
4.3 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. import { throttle } from 'lodash';
  10. export default class StatusList extends ImmutablePureComponent {
  11. static propTypes = {
  12. scrollKey: PropTypes.string.isRequired,
  13. statusIds: ImmutablePropTypes.list.isRequired,
  14. onScrollToBottom: PropTypes.func,
  15. onScrollToTop: PropTypes.func,
  16. onScroll: PropTypes.func,
  17. trackScroll: PropTypes.bool,
  18. shouldUpdateScroll: PropTypes.func,
  19. isLoading: 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 = throttle(() => {
  29. if (this.node) {
  30. const { scrollTop, scrollHeight, clientHeight } = this.node;
  31. const offset = scrollHeight - scrollTop - clientHeight;
  32. this._oldScrollPosition = scrollHeight - scrollTop;
  33. if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
  34. this.props.onScrollToBottom();
  35. } else if (scrollTop < 100 && this.props.onScrollToTop) {
  36. this.props.onScrollToTop();
  37. } else if (this.props.onScroll) {
  38. this.props.onScroll();
  39. }
  40. }
  41. }, 150, {
  42. trailing: true,
  43. });
  44. componentDidMount () {
  45. this.attachScrollListener();
  46. this.attachIntersectionObserver();
  47. // Handle initial scroll posiiton
  48. this.handleScroll();
  49. }
  50. componentDidUpdate (prevProps) {
  51. // Reset the scroll position when a new toot comes in in order not to
  52. // jerk the scrollbar around if you're already scrolled down the page.
  53. if (prevProps.statusIds.size < this.props.statusIds.size && this._oldScrollPosition && this.node.scrollTop > 0) {
  54. if (prevProps.statusIds.first() !== this.props.statusIds.first()) {
  55. let newScrollTop = this.node.scrollHeight - this._oldScrollPosition;
  56. if (this.node.scrollTop !== newScrollTop) {
  57. this.node.scrollTop = newScrollTop;
  58. }
  59. } else {
  60. this._oldScrollPosition = this.node.scrollHeight - this.node.scrollTop;
  61. }
  62. }
  63. }
  64. componentWillUnmount () {
  65. this.detachScrollListener();
  66. this.detachIntersectionObserver();
  67. }
  68. attachIntersectionObserver () {
  69. this.intersectionObserverWrapper.connect({
  70. root: this.node,
  71. rootMargin: '300% 0px',
  72. });
  73. }
  74. detachIntersectionObserver () {
  75. this.intersectionObserverWrapper.disconnect();
  76. }
  77. attachScrollListener () {
  78. this.node.addEventListener('scroll', this.handleScroll);
  79. }
  80. detachScrollListener () {
  81. this.node.removeEventListener('scroll', this.handleScroll);
  82. }
  83. setRef = (c) => {
  84. this.node = c;
  85. }
  86. handleLoadMore = (e) => {
  87. e.preventDefault();
  88. this.props.onScrollToBottom();
  89. }
  90. render () {
  91. const { statusIds, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
  92. const loadMore = <LoadMore visible={!isLoading && statusIds.size > 0 && hasMore} onClick={this.handleLoadMore} />;
  93. let scrollableArea = null;
  94. if (isLoading || statusIds.size > 0 || !emptyMessage) {
  95. scrollableArea = (
  96. <div className='scrollable' ref={this.setRef}>
  97. <div role='feed' className='status-list'>
  98. {prepend}
  99. {statusIds.map((statusId, index) => {
  100. return <StatusContainer key={statusId} id={statusId} index={index} listLength={statusIds.size} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
  101. })}
  102. {loadMore}
  103. </div>
  104. </div>
  105. );
  106. } else {
  107. scrollableArea = (
  108. <div className='empty-column-indicator' ref={this.setRef}>
  109. {emptyMessage}
  110. </div>
  111. );
  112. }
  113. if (trackScroll) {
  114. return (
  115. <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
  116. {scrollableArea}
  117. </ScrollContainer>
  118. );
  119. } else {
  120. return scrollableArea;
  121. }
  122. }
  123. }