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,
+1
View File
@@ -167,6 +167,7 @@
"react-test-renderer": "15.5",
"react-toastify": "^1.5.0",
"react-transition-group": "^1.1.3",
"react-virtualized": "9.9.0",
"recompose": "^0.23.1",
"redux": "^3.6.0",
"redux-thunk": "^2.1.0",
+13 -3
View File
@@ -1020,7 +1020,7 @@ babel-register@^6.26.0:
mkdirp "^0.5.1"
source-map-support "^0.4.15"
babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.6.1:
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.6.1:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
@@ -2402,7 +2402,7 @@ doctypes@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
dom-helpers@^3.2.0:
"dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.2.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a"
@@ -5451,7 +5451,7 @@ longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
dependencies:
@@ -7464,6 +7464,16 @@ react-transition-group@^1.1.2, react-transition-group@^1.1.3:
prop-types "^15.5.6"
warning "^3.0.0"
react-virtualized@9.9.0:
version "9.9.0"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.9.0.tgz#799a6f23819eeb82860d59b82fad33d1d420325e"
dependencies:
babel-runtime "^6.11.6"
classnames "^2.2.3"
dom-helpers "^2.4.0 || ^3.0.0"
loose-envify "^1.3.0"
prop-types "^15.5.4"
react@^15.3.1, react@^15.4.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"