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"