Integrate react-virtualized

This commit is contained in:
Chi Vinh Le
2017-12-11 20:21:27 +01:00
parent 2c9eae9264
commit ed959ca07a
10 changed files with 148 additions and 51 deletions
@@ -21,10 +21,11 @@ class CommentDetails extends Component {
this.setState((state) => ({
showDetail: !state.showDetail
}));
this.props.clearHeightCache && this.props.clearHeightCache();
}
render() {
const {data, root, comment} = this.props;
const {data, root, comment, clearHeightCache} = this.props;
const {showDetail} = this.state;
const queryData = {
root,
@@ -44,12 +45,14 @@ class CommentDetails extends Component {
<Slot
fill="adminCommentDetailArea"
data={data}
clearHeightCache={clearHeightCache}
queryData={queryData}
more={showDetail}
/>
{showDetail && <Slot
fill="adminCommentMoreDetails"
data={data}
clearHeightCache={clearHeightCache}
queryData={queryData}
/>}
</div>
@@ -61,6 +64,7 @@ CommentDetails.propTypes = {
data: PropTypes.object.isRequired,
root: PropTypes.object.isRequired,
comment: PropTypes.object.isRequired,
clearHeightCache: PropTypes.func,
};
export default CommentDetails;
@@ -10,6 +10,7 @@
position: relative;
transition: all 200ms;
padding: 10px 0;
margin-top: 13px;
min-height: 0;
/*
@@ -65,6 +65,7 @@ class Comment extends React.Component {
root: {settings},
currentUserId,
currentAsset,
clearHeightCache,
} = this.props;
const selectionStateCSS = selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp';
@@ -114,6 +115,7 @@ class Comment extends React.Component {
<Slot
fill="adminCommentInfoBar"
data={data}
clearHeightCache={clearHeightCache}
queryData={queryData}
/>
</div>
@@ -145,6 +147,7 @@ class Comment extends React.Component {
<Slot
fill="adminCommentContent"
data={data}
clearHeightCache={clearHeightCache}
queryData={queryData}
/>
<div className={styles.sideActions}>
@@ -166,6 +169,7 @@ class Comment extends React.Component {
<Slot
fill="adminSideActions"
data={data}
clearHeightCache={clearHeightCache}
queryData={queryData}
/>
</div>
@@ -176,6 +180,7 @@ class Comment extends React.Component {
data={data}
root={root}
comment={comment}
clearHeightCache={clearHeightCache}
/>
</li>
);
@@ -191,6 +196,7 @@ Comment.propTypes = {
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
currentUserId: PropTypes.string.isRequired,
clearHeightCache: PropTypes.func,
comment: PropTypes.shape({
id: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
@@ -0,0 +1,3 @@
.root {
height: 100%;
}
@@ -2,7 +2,10 @@
padding: 8px 0;
list-style: none;
display: block;
margin-top: 16px;
}
:global(html) {
height: inherit;
}
.list {
@@ -15,12 +18,12 @@
}
.commentLeaveActive {
opacity: 0;
opacity: 0.1;
transition: opacity 800ms;
}
.commentEnter {
opacity: 0;
opacity: 0.1;
}
.commentEnterActive {
@@ -7,7 +7,8 @@ import EmptyCard from '../../../components/EmptyCard';
import LoadMore from '../../../components/LoadMore';
import ViewMore from './ViewMore';
import t from 'coral-framework/services/i18n';
import {CSSTransitionGroup} from 'react-transition-group';
import {WindowScroller, CellMeasurer, CellMeasurerCache, List} from 'react-virtualized';
import throttle from 'lodash/throttle';
const hasComment = (nodes, id) => nodes.some((node) => node.id === id);
@@ -43,14 +44,37 @@ function invalidateCursor(invalidated, state, props) {
return {idCursors};
}
let keyMapper = null;
// In this example, average cell height is assumed to be about 50px.
// This value will be used for the initial `Grid` layout.
// Width is not dynamic.
const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 250,
keyMapper: (index) => keyMapper(index),
});
class ModerationQueue extends React.Component {
isLoadingMore = false;
cache = cache;
listRef = null;
constructor(props) {
super(props);
this.state = {
...resetCursors(this.state, props),
};
keyMapper = (index) => {
const view = this.getVisibleComments();
if (index < view.length) {
return view[index].id;
}
else if (index === view.length) {
return 'loadMore';
}
throw new Error(`unknown index ${index}`);
};
}
componentDidUpdate (prev) {
@@ -64,6 +88,10 @@ class ModerationQueue extends React.Component {
}
}
handleListRef = (list) => {
this.listRef = list;
};
componentWillReceiveProps(next) {
const {comments: prevComments} = this.props;
const {comments: nextComments} = next;
@@ -94,6 +122,11 @@ class ModerationQueue extends React.Component {
this.setState(resetCursors);
};
reflowList = throttle(() => {
this.cache.clearAll();
this.listRef.recomputeRowHeights();
}, 500);
// getVisibileComments returns a list containing comments
// which comes after the `idCursor`.
getVisibleComments() {
@@ -117,14 +150,68 @@ class ModerationQueue extends React.Component {
return view;
}
rowRenderer = ({
index, // Index of row within collection
parent,
style // Style object to be applied to row (to position it)
}) => {
if (index === parent.props.rowCount - 1) {
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={'loadMore'}
parent={parent}
rowIndex={index}
>
<div
style={style}
>
<LoadMore
loadMore={this.props.loadMore}
showLoadMore={this.props.comments.length < this.props.commentCount}
/>
</div>
</CellMeasurer>
);
}
const comment = this.props.comments[index];
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={comment.id}
parent={parent}
rowIndex={index}
>
<div
style={style}
>
<Comment
data={this.props.data}
root={this.props.root}
comment={comment}
selected={comment.id === this.props.selectedCommentId}
viewUserDetail={this.props.viewUserDetail}
showBanUserDialog={this.props.showBanUserDialog}
showSuspendUserDialog={this.props.showSuspendUserDialog}
acceptComment={this.props.acceptComment}
rejectComment={this.props.rejectComment}
currentAsset={this.props.currentAsset}
currentUserId={this.props.currentUserId}
clearHeightCache={() => {this.cache.clear(index); this.listRef.recomputeRowHeights(index); }}
/>
</div>
</CellMeasurer>
);
};
render () {
const {
comments,
selectedCommentId,
commentCount,
singleView,
viewUserDetail,
activeTab,
...props
} = this.props;
@@ -167,46 +254,27 @@ class ModerationQueue extends React.Component {
viewMore={this.viewNewComments}
count={comments.length - view.length}
/>
<CSSTransitionGroup
key={activeTab}
component={'ul'}
className={styles.list}
transitionName={{
enter: styles.commentEnter,
enterActive: styles.commentEnterActive,
leave: styles.commentLeave,
leaveActive: styles.commentLeaveActive,
}}
transitionEnter={true}
transitionLeave={true}
transitionEnterTimeout={1000}
transitionLeaveTimeout={1000}
>
{
view
.map((comment) => {
return <Comment
data={this.props.data}
root={this.props.root}
key={comment.id}
comment={comment}
selected={comment.id === selectedCommentId}
viewUserDetail={viewUserDetail}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
currentAsset={props.currentAsset}
currentUserId={this.props.currentUserId}
/>;
})
}
</CSSTransitionGroup>
<LoadMore
loadMore={this.props.loadMore}
showLoadMore={comments.length < commentCount}
/>
<WindowScroller onResize={this.reflowList}>
{({height, isScrolling, onChildScroll, scrollTop}) => (
<List
ref={this.handleListRef}
autoHeight
style={{
width: '100%',
outline: 'none',
}}
height={height}
width={1280}
scrollTop={scrollTop}
isScrolling={isScrolling}
onScroll={onChildScroll}
rowCount={view.length + 1}
deferredMeasurementCache={this.cache}
rowRenderer={this.rowRenderer}
rowHeight={this.cache.rowHeight}
/>
)}
</WindowScroller>
</div>
);
}
@@ -8,6 +8,7 @@
overflow: visible;
height: 144px;
min-height: auto;
margin-top: 16px;
z-index: 10;
@media (--tablet) {
@@ -178,7 +178,7 @@ class ModerationContainer extends Component {
loadMore = (tab) => {
const variables = {
limit: 10,
limit: 50,
cursor: this.props.root[tab].endCursor,
sortOrder: this.props.data.variables.sortOrder,
asset_id: this.props.data.variables.asset_id,