From ed959ca07a4d7f0bfa2696375a1e525b82ea0ea4 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 11 Dec 2017 20:21:27 +0100 Subject: [PATCH] Integrate react-virtualized --- .../src/components/CommentDetails.js | 6 +- .../routes/Moderation/components/Comment.css | 1 + .../routes/Moderation/components/Comment.js | 6 + .../components/ModerationLayout.css | 3 + .../Moderation/components/ModerationQueue.css | 9 +- .../Moderation/components/ModerationQueue.js | 154 +++++++++++++----- .../Moderation/components/ViewOptions.css | 1 + .../Moderation/containers/Moderation.js | 2 +- package.json | 1 + yarn.lock | 16 +- 10 files changed, 148 insertions(+), 51 deletions(-) create mode 100644 client/coral-admin/src/routes/Moderation/components/ModerationLayout.css diff --git a/client/coral-admin/src/components/CommentDetails.js b/client/coral-admin/src/components/CommentDetails.js index cd504a40f..418be0354 100644 --- a/client/coral-admin/src/components/CommentDetails.js +++ b/client/coral-admin/src/components/CommentDetails.js @@ -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 { {showDetail && } @@ -61,6 +64,7 @@ CommentDetails.propTypes = { data: PropTypes.object.isRequired, root: PropTypes.object.isRequired, comment: PropTypes.object.isRequired, + clearHeightCache: PropTypes.func, }; export default CommentDetails; diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css index 10cc707fb..029a8e5d1 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.css +++ b/client/coral-admin/src/routes/Moderation/components/Comment.css @@ -10,6 +10,7 @@ position: relative; transition: all 200ms; padding: 10px 0; + margin-top: 13px; min-height: 0; /* diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index cea49720b..9f913695a 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -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 { @@ -145,6 +147,7 @@ class Comment extends React.Component {
@@ -166,6 +169,7 @@ class Comment extends React.Component {
@@ -176,6 +180,7 @@ class Comment extends React.Component { data={data} root={root} comment={comment} + clearHeightCache={clearHeightCache} /> ); @@ -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, diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationLayout.css b/client/coral-admin/src/routes/Moderation/components/ModerationLayout.css new file mode 100644 index 000000000..7214b4550 --- /dev/null +++ b/client/coral-admin/src/routes/Moderation/components/ModerationLayout.css @@ -0,0 +1,3 @@ +.root { + height: 100%; +} diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.css b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.css index 6f1d4ae95..f9e3aa59a 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.css +++ b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.css @@ -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 { diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js index dc4a8c74c..a2c09c5df 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js @@ -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 ( + +
+ +
+
+ ); + } + const comment = this.props.comments[index]; + return ( + +
+ {this.cache.clear(index); this.listRef.recomputeRowHeights(index); }} + /> +
+
+ ); + }; + 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} /> - - { - view - .map((comment) => { - return ; - }) - } - - - + + {({height, isScrolling, onChildScroll, scrollTop}) => ( + + )} + ); } diff --git a/client/coral-admin/src/routes/Moderation/components/ViewOptions.css b/client/coral-admin/src/routes/Moderation/components/ViewOptions.css index 3e5aae32d..68f089c27 100644 --- a/client/coral-admin/src/routes/Moderation/components/ViewOptions.css +++ b/client/coral-admin/src/routes/Moderation/components/ViewOptions.css @@ -8,6 +8,7 @@ overflow: visible; height: 144px; min-height: auto; + margin-top: 16px; z-index: 10; @media (--tablet) { diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index ca6f0224a..b936b4c30 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -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, diff --git a/package.json b/package.json index 4d556c48b..f16053ab5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index c01e2e85a..81fb50604 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"