]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/components/status_list.js
Summary: fix slowness due to layout thrashing when reloading a large … (#12661)
[mastodon.git] / app / javascript / mastodon / components / status_list.js
1 import { debounce } from 'lodash';
2 import React from 'react';
3 import ImmutablePropTypes from 'react-immutable-proptypes';
4 import PropTypes from 'prop-types';
5 import StatusContainer from '../containers/status_container';
6 import ImmutablePureComponent from 'react-immutable-pure-component';
7 import LoadGap from './load_gap';
8 import ScrollableList from './scrollable_list';
9 import RegenerationIndicator from 'mastodon/components/regeneration_indicator';
10
11 export default class StatusList extends ImmutablePureComponent {
12
13 static propTypes = {
14 scrollKey: PropTypes.string.isRequired,
15 statusIds: ImmutablePropTypes.list.isRequired,
16 featuredStatusIds: ImmutablePropTypes.list,
17 onLoadMore: PropTypes.func,
18 onScrollToTop: PropTypes.func,
19 onScroll: PropTypes.func,
20 trackScroll: PropTypes.bool,
21 shouldUpdateScroll: PropTypes.func,
22 isLoading: PropTypes.bool,
23 isPartial: PropTypes.bool,
24 hasMore: PropTypes.bool,
25 prepend: PropTypes.node,
26 emptyMessage: PropTypes.node,
27 alwaysPrepend: PropTypes.bool,
28 timelineId: PropTypes.string,
29 currentlyViewing: PropTypes.number,
30 updateCurrentlyViewing: PropTypes.func,
31 };
32
33 static defaultProps = {
34 trackScroll: true,
35 };
36
37 getFeaturedStatusCount = () => {
38 return this.props.featuredStatusIds ? this.props.featuredStatusIds.size : 0;
39 }
40
41 getCurrentStatusIndex = (id, featured) => {
42 if (featured) {
43 return this.props.featuredStatusIds.indexOf(id);
44 } else {
45 return this.props.statusIds.indexOf(id) + this.getFeaturedStatusCount();
46 }
47 }
48
49 handleMoveUp = (id, featured) => {
50 const elementIndex = this.getCurrentStatusIndex(id, featured) - 1;
51 this._selectChild(elementIndex, true);
52 }
53
54 handleMoveDown = (id, featured) => {
55 const elementIndex = this.getCurrentStatusIndex(id, featured) + 1;
56 this._selectChild(elementIndex, false);
57 }
58
59 handleLoadOlder = debounce(() => {
60 this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined);
61 }, 300, { leading: true })
62
63 updateCurrentlyViewingWithCache = (id) => {
64 if(this.cachedCurrentlyViewing === id) return;
65 this.cachedCurrentlyViewing = id;
66 this.props.updateCurrentlyViewing(id);
67 }
68
69 _selectChild (index, align_top) {
70 const container = this.node.node;
71 const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
72
73 if (element) {
74 if (align_top && container.scrollTop > element.offsetTop) {
75 element.scrollIntoView(true);
76 } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
77 element.scrollIntoView(false);
78 }
79 element.focus();
80 }
81 }
82
83 setRef = c => {
84 this.node = c;
85 }
86
87 render () {
88 const { statusIds, featuredStatusIds, shouldUpdateScroll, onLoadMore, timelineId, ...other } = this.props;
89 const { isLoading, isPartial } = other;
90 other.updateCurrentlyViewing = this.updateCurrentlyViewingWithCache;
91
92 if (isPartial) {
93 return <RegenerationIndicator />;
94 }
95
96 let scrollableContent = (isLoading || statusIds.size > 0) ? (
97 statusIds.map((statusId, index) => statusId === null ? (
98 <LoadGap
99 key={'gap:' + statusIds.get(index + 1)}
100 disabled={isLoading}
101 maxId={index > 0 ? statusIds.get(index - 1) : null}
102 onClick={onLoadMore}
103 />
104 ) : (
105 <StatusContainer
106 key={statusId}
107 id={statusId}
108 onMoveUp={this.handleMoveUp}
109 onMoveDown={this.handleMoveDown}
110 contextType={timelineId}
111 showThread
112 />
113 ))
114 ) : null;
115
116 if (scrollableContent && featuredStatusIds) {
117 scrollableContent = featuredStatusIds.map(statusId => (
118 <StatusContainer
119 key={`f-${statusId}`}
120 id={statusId}
121 featured
122 onMoveUp={this.handleMoveUp}
123 onMoveDown={this.handleMoveDown}
124 contextType={timelineId}
125 showThread
126 />
127 )).concat(scrollableContent);
128 }
129
130 return (
131 <ScrollableList {...other} showLoading={isLoading && statusIds.size === 0} onLoadMore={onLoadMore && this.handleLoadOlder} shouldUpdateScroll={shouldUpdateScroll} ref={this.setRef}>
132 {scrollableContent}
133 </ScrollableList>
134 );
135 }
136
137 }
This page took 0.090686 seconds and 4 git commands to generate.