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.

123 lines
3.3 KiB

  1. import React from 'react';
  2. import Status from './status';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import { ScrollContainer } from 'react-router-scroll';
  5. import PropTypes from 'prop-types';
  6. import StatusContainer from '../containers/status_container';
  7. import LoadMore from './load_more';
  8. import ImmutablePureComponent from 'react-immutable-pure-component';
  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. shouldUpdateScroll: PropTypes.func,
  17. isLoading: PropTypes.bool,
  18. isUnread: PropTypes.bool,
  19. hasMore: PropTypes.bool,
  20. prepend: PropTypes.node,
  21. emptyMessage: PropTypes.node
  22. };
  23. static defaultProps = {
  24. trackScroll: true
  25. };
  26. handleScroll = (e) => {
  27. const { scrollTop, scrollHeight, clientHeight } = e.target;
  28. const offset = scrollHeight - scrollTop - clientHeight;
  29. this._oldScrollPosition = scrollHeight - scrollTop;
  30. if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
  31. this.props.onScrollToBottom();
  32. } else if (scrollTop < 100 && this.props.onScrollToTop) {
  33. this.props.onScrollToTop();
  34. } else if (this.props.onScroll) {
  35. this.props.onScroll();
  36. }
  37. }
  38. componentDidMount () {
  39. this.attachScrollListener();
  40. }
  41. componentDidUpdate (prevProps) {
  42. if (this.node.scrollTop > 0 && (prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition)) {
  43. this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
  44. }
  45. }
  46. componentWillUnmount () {
  47. this.detachScrollListener();
  48. }
  49. attachScrollListener () {
  50. this.node.addEventListener('scroll', this.handleScroll);
  51. }
  52. detachScrollListener () {
  53. this.node.removeEventListener('scroll', this.handleScroll);
  54. }
  55. setRef = (c) => {
  56. this.node = c;
  57. }
  58. handleLoadMore = (e) => {
  59. e.preventDefault();
  60. this.props.onScrollToBottom();
  61. }
  62. render () {
  63. const { statusIds, onScrollToBottom, scrollKey, shouldUpdateScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props;
  64. let loadMore = '';
  65. let scrollableArea = '';
  66. let unread = '';
  67. if (!isLoading && statusIds.size > 0 && hasMore) {
  68. loadMore = <LoadMore onClick={this.handleLoadMore} />;
  69. }
  70. if (isUnread) {
  71. unread = <div className='status-list__unread-indicator' />;
  72. }
  73. if (isLoading || statusIds.size > 0 || !emptyMessage) {
  74. scrollableArea = (
  75. <div className='scrollable' ref={this.setRef}>
  76. {unread}
  77. <div className='status-list'>
  78. {prepend}
  79. {statusIds.map((statusId) => {
  80. return <StatusContainer key={statusId} id={statusId} />;
  81. })}
  82. {loadMore}
  83. </div>
  84. </div>
  85. );
  86. } else {
  87. scrollableArea = (
  88. <div className='empty-column-indicator' ref={this.setRef}>
  89. {emptyMessage}
  90. </div>
  91. );
  92. }
  93. return (
  94. <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
  95. {scrollableArea}
  96. </ScrollContainer>
  97. );
  98. }
  99. }
  100. export default StatusList;