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';
11 export default class StatusList
extends ImmutablePureComponent
{
14 scrollKey: PropTypes
.string
.isRequired
,
15 statusIds: ImmutablePropTypes
.list
.isRequired
,
16 onScrollToBottom: PropTypes
.func
,
17 onScrollToTop: PropTypes
.func
,
18 onScroll: PropTypes
.func
,
19 trackScroll: PropTypes
.bool
,
20 shouldUpdateScroll: PropTypes
.func
,
21 isLoading: PropTypes
.bool
,
22 hasMore: PropTypes
.bool
,
23 prepend: PropTypes
.node
,
24 emptyMessage: PropTypes
.node
,
27 static defaultProps
= {
31 intersectionObserverWrapper
= new IntersectionObserverWrapper();
33 handleScroll
= throttle(() => {
35 const { scrollTop
, scrollHeight
, clientHeight
} = this.node
;
36 const offset
= scrollHeight
- scrollTop
- clientHeight
;
37 this._oldScrollPosition
= scrollHeight
- scrollTop
;
39 if (400 > offset
&& this.props
.onScrollToBottom
&& !this.props
.isLoading
) {
40 this.props
.onScrollToBottom();
41 } else if (scrollTop
< 100 && this.props
.onScrollToTop
) {
42 this.props
.onScrollToTop();
43 } else if (this.props
.onScroll
) {
44 this.props
.onScroll();
51 componentDidMount () {
52 this.attachScrollListener();
53 this.attachIntersectionObserver();
55 // Handle initial scroll posiiton
59 componentDidUpdate (prevProps
) {
60 // Reset the scroll position when a new toot comes in in order not to
61 // jerk the scrollbar around if you're already scrolled down the page.
62 if (prevProps
.statusIds
.size
< this.props
.statusIds
.size
&& this._oldScrollPosition
&& this.node
.scrollTop
> 0) {
63 if (prevProps
.statusIds
.first() !== this.props
.statusIds
.first()) {
64 let newScrollTop
= this.node
.scrollHeight
- this._oldScrollPosition
;
65 if (this.node
.scrollTop
!== newScrollTop
) {
66 this.node
.scrollTop
= newScrollTop
;
69 this._oldScrollPosition
= this.node
.scrollHeight
- this.node
.scrollTop
;
74 componentWillUnmount () {
75 this.detachScrollListener();
76 this.detachIntersectionObserver();
79 attachIntersectionObserver () {
80 this.intersectionObserverWrapper
.connect({
82 rootMargin: '300% 0px',
86 detachIntersectionObserver () {
87 this.intersectionObserverWrapper
.disconnect();
90 attachScrollListener () {
91 this.node
.addEventListener('scroll', this.handleScroll
);
94 detachScrollListener () {
95 this.node
.removeEventListener('scroll', this.handleScroll
);
102 handleLoadMore
= (e
) => {
104 this.props
.onScrollToBottom();
108 const { statusIds
, scrollKey
, trackScroll
, shouldUpdateScroll
, isLoading
, hasMore
, prepend
, emptyMessage
} = this.props
;
110 const loadMore
= <LoadMore visible
={!isLoading
&& statusIds
.size
> 0 && hasMore
} onClick
={this.handleLoadMore
} />;
111 let scrollableArea
= null;
113 if (isLoading
|| statusIds
.size
> 0 || !emptyMessage
) {
115 <div className
='scrollable' ref
={this.setRef
}>
116 <div role
='feed' className
='status-list'>
119 {statusIds
.map((statusId
, index
) => {
120 return <StatusContainer key
={statusId
} id
={statusId
} index
={index
} listLength
={statusIds
.size
} intersectionObserverWrapper
={this.intersectionObserverWrapper
} />;
129 <div className
='empty-column-indicator' ref
={this.setRef
}>
137 <ScrollContainer scrollKey
={scrollKey
} shouldUpdateScroll
={shouldUpdateScroll
}>
142 return scrollableArea
;