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.

127 lines
3.2 KiB

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