From 34530843f433331d69e70cc20c4cbefa19951385 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 12 Sep 2019 10:54:52 +0100
Subject: [PATCH] Factor out generic EventListSummary from MELS
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
res/css/_components.scss | 2 +-
...istSummary.scss => _EventListSummary.scss} | 20 ++--
.../views/elements/EventListSummary.js | 95 ++++++++++++++++++
.../views/elements/MemberEventListSummary.js | 97 +++----------------
src/hooks/useStateToggle.js | 27 ++++++
src/i18n/strings/en_EN.json | 4 +-
.../elements/MemberEventListSummary-test.js | 26 ++---
7 files changed, 164 insertions(+), 107 deletions(-)
rename res/css/views/elements/{_MemberEventListSummary.scss => _EventListSummary.scss} (75%)
create mode 100644 src/components/views/elements/EventListSummary.js
create mode 100644 src/hooks/useStateToggle.js
diff --git a/res/css/_components.scss b/res/css/_components.scss
index f627fe3a29..561b1b4820 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -88,12 +88,12 @@
@import "./views/elements/_Dropdown.scss";
@import "./views/elements/_EditableItemList.scss";
@import "./views/elements/_ErrorBoundary.scss";
+@import "./views/elements/_EventListSummary";
@import "./views/elements/_Field.scss";
@import "./views/elements/_ImageView.scss";
@import "./views/elements/_InlineSpinner.scss";
@import "./views/elements/_InteractiveTooltip.scss";
@import "./views/elements/_ManageIntegsButton.scss";
-@import "./views/elements/_MemberEventListSummary.scss";
@import "./views/elements/_PowerSelector.scss";
@import "./views/elements/_ProgressBar.scss";
@import "./views/elements/_ReplyThread.scss";
diff --git a/res/css/views/elements/_MemberEventListSummary.scss b/res/css/views/elements/_EventListSummary.scss
similarity index 75%
rename from res/css/views/elements/_MemberEventListSummary.scss
rename to res/css/views/elements/_EventListSummary.scss
index 02ecb5d84a..99a5c06a5f 100644
--- a/res/css/views/elements/_MemberEventListSummary.scss
+++ b/res/css/views/elements/_EventListSummary.scss
@@ -14,28 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_MemberEventListSummary {
+.mx_EventListSummary {
position: relative;
}
-.mx_TextualEvent.mx_MemberEventListSummary_summary {
+.mx_TextualEvent.mx_EventListSummary_summary {
font-size: 14px;
display: inline-flex;
}
-.mx_MemberEventListSummary_avatars {
+.mx_EventListSummary_avatars {
display: inline-block;
margin-right: 8px;
padding-top: 8px;
line-height: 12px;
}
-.mx_MemberEventListSummary_avatars .mx_BaseAvatar {
+.mx_EventListSummary_avatars .mx_BaseAvatar {
margin-right: -4px;
cursor: pointer;
}
-.mx_MemberEventListSummary_toggle {
+.mx_EventListSummary_toggle {
color: $accent-color;
cursor: pointer;
float: right;
@@ -43,29 +43,29 @@ limitations under the License.
margin-top: 8px;
}
-.mx_MemberEventListSummary_line {
+.mx_EventListSummary_line {
border-bottom: 1px solid $primary-hairline-color;
margin-left: 63px;
line-height: 30px;
}
.mx_MatrixChat_useCompactLayout {
- .mx_MemberEventListSummary {
+ .mx_EventListSummary {
font-size: 13px;
.mx_EventTile_line {
line-height: 20px;
}
}
- .mx_MemberEventListSummary_line {
+ .mx_EventListSummary_line {
line-height: 22px;
}
- .mx_MemberEventListSummary_toggle {
+ .mx_EventListSummary_toggle {
margin-top: 3px;
}
- .mx_TextualEvent.mx_MemberEventListSummary_summary {
+ .mx_TextualEvent.mx_EventListSummary_summary {
font-size: 13px;
}
}
diff --git a/src/components/views/elements/EventListSummary.js b/src/components/views/elements/EventListSummary.js
new file mode 100644
index 0000000000..d6971334d4
--- /dev/null
+++ b/src/components/views/elements/EventListSummary.js
@@ -0,0 +1,95 @@
+/*
+Copyright 2019 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React, {useEffect} from 'react';
+import PropTypes from 'prop-types';
+import MemberAvatar from '../avatars/MemberAvatar';
+import { _t } from '../../../languageHandler';
+import {MatrixEvent, RoomMember} from "matrix-js-sdk";
+import {useStateToggle} from "../../../hooks/useStateToggle";
+
+const EventListSummary = ({events, children, threshold=3, onToggle, startExpanded, summaryMembers=[], summaryText}) => {
+ const [expanded, toggleExpanded] = useStateToggle(startExpanded);
+
+ // Whenever expanded changes call onToggle
+ useEffect(() => {
+ if (onToggle) {
+ onToggle();
+ }
+ }, [expanded]);
+
+ const eventIds = events.map((e) => e.getId()).join(',');
+
+ // If we are only given few events then just pass them through
+ if (events.length < threshold) {
+ return (
+
+ { children }
+
+ );
+ }
+
+ if (expanded) {
+ return (
+
+
+ { _t('collapse') }
+
+
+ { children }
+
+ );
+ }
+
+ const avatars = summaryMembers.map((m) => );
+ return (
+
+
+ { _t('expand') }
+
+
+
+
+ { avatars }
+
+
+ { summaryText }
+
+
+
+
+ );
+};
+
+EventListSummary.propTypes = {
+ // An array of member events to summarise
+ events: PropTypes.arrayOf(PropTypes.instanceOf(MatrixEvent)).isRequired,
+ // An array of EventTiles to render when expanded
+ children: PropTypes.arrayOf(PropTypes.element).isRequired,
+ // The minimum number of events needed to trigger summarisation
+ threshold: PropTypes.number,
+ // Called when the event list expansion is toggled
+ onToggle: PropTypes.func,
+ // Whether or not to begin with state.expanded=true
+ startExpanded: PropTypes.bool,
+
+ // The list of room members for which to show avatars next to the summary
+ summaryMembers: PropTypes.arrayOf(PropTypes.instanceOf(RoomMember)),
+ // The text to show as the summary of this event list
+ summaryText: PropTypes.string.isRequired,
+};
+
+export default EventListSummary;
diff --git a/src/components/views/elements/MemberEventListSummary.js b/src/components/views/elements/MemberEventListSummary.js
index ba31eb5a38..98adbb2e5c 100644
--- a/src/components/views/elements/MemberEventListSummary.js
+++ b/src/components/views/elements/MemberEventListSummary.js
@@ -19,9 +19,9 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
-import MemberAvatar from '../avatars/MemberAvatar';
import { _t } from '../../../languageHandler';
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
+import sdk from "../../../index";
module.exports = createReactClass({
displayName: 'MemberEventListSummary',
@@ -43,12 +43,6 @@ module.exports = createReactClass({
startExpanded: PropTypes.bool,
},
- getInitialState: function() {
- return {
- expanded: Boolean(this.props.startExpanded),
- };
- },
-
getDefaultProps: function() {
return {
summaryLength: 1,
@@ -57,37 +51,27 @@ module.exports = createReactClass({
};
},
- shouldComponentUpdate: function(nextProps, nextState) {
+ shouldComponentUpdate: function(nextProps) {
// Update if
// - The number of summarised events has changed
- // - or if the summary is currently expanded
// - or if the summary is about to toggle to become collapsed
// - or if there are fewEvents, meaning the child eventTiles are shown as-is
return (
nextProps.events.length !== this.props.events.length ||
- this.state.expanded || nextState.expanded ||
nextProps.events.length < this.props.threshold
);
},
- _toggleSummary: function() {
- this.setState({
- expanded: !this.state.expanded,
- });
- this.props.onToggle();
- },
-
/**
- * Render the JSX for users aggregated by their transition sequences (`eventAggregates`) where
+ * Generate the text for users aggregated by their transition sequences (`eventAggregates`) where
* the sequences are ordered by `orderedTransitionSequences`.
* @param {object[]} eventAggregates a map of transition sequence to array of user display names
* or user IDs.
* @param {string[]} orderedTransitionSequences an array which is some ordering of
* `Object.keys(eventAggregates)`.
- * @returns {ReactElement} a single containing the textual summary of the aggregated
- * events that occurred.
+ * @returns {string} the textual summary of the aggregated events that occurred.
*/
- _renderSummary: function(eventAggregates, orderedTransitionSequences) {
+ _generateSummary: function(eventAggregates, orderedTransitionSequences) {
const summaries = orderedTransitionSequences.map((transitions) => {
const userNames = eventAggregates[transitions];
const nameList = this._renderNameList(userNames);
@@ -118,11 +102,7 @@ module.exports = createReactClass({
return null;
}
- return (
-
- { summaries.join(", ") }
-
- );
+ return summaries.join(", ");
},
/**
@@ -208,7 +188,7 @@ module.exports = createReactClass({
* For a certain transition, t, describe what happened to the users that
* underwent the transition.
* @param {string} t the transition type.
- * @param {integer} userCount number of usernames
+ * @param {number} userCount number of usernames
* @param {number} repeats the number of times the transition was repeated in a row.
* @returns {string} the written Human Readable equivalent of the transition.
*/
@@ -288,19 +268,6 @@ module.exports = createReactClass({
return res;
},
- _renderAvatars: function(roomMembers) {
- const avatars = roomMembers.slice(0, this.props.avatarsMaxLength).map((m) => {
- return (
-
- );
- });
- return (
-
- { avatars }
-
- );
- },
-
_getTransitionSequence: function(events) {
return events.map(this._getTransition);
},
@@ -396,22 +363,6 @@ module.exports = createReactClass({
render: function() {
const eventsToRender = this.props.events;
- const eventIds = eventsToRender.map((e) => e.getId()).join(',');
- const fewEvents = eventsToRender.length < this.props.threshold;
- const expanded = this.state.expanded || fewEvents;
-
- let expandedEvents = null;
- if (expanded) {
- expandedEvents = this.props.children;
- }
-
- if (fewEvents) {
- return (
-
- { expandedEvents }
-
- );
- }
// Map user IDs to an array of objects:
const userEvents = {
@@ -455,30 +406,14 @@ module.exports = createReactClass({
(seq1, seq2) => aggregate.indices[seq1] > aggregate.indices[seq2],
);
- let summaryContainer = null;
- if (!expanded) {
- summaryContainer = (
-
-
- { this._renderAvatars(avatarMembers) }
- { this._renderSummary(aggregate.names, orderedTransitionSequences) }
-
-
- );
- }
- const toggleButton = (
-
- { expanded ? _t('collapse') : _t('expand') }
-
- );
-
- return (
-
- { toggleButton }
- { summaryContainer }
- { expanded ?
: null }
- { expandedEvents }
-
- );
+ const EventListSummary = sdk.getComponent("views.elements.EventListSummary");
+ return ;
},
});
diff --git a/src/hooks/useStateToggle.js b/src/hooks/useStateToggle.js
new file mode 100644
index 0000000000..58cf123bfb
--- /dev/null
+++ b/src/hooks/useStateToggle.js
@@ -0,0 +1,27 @@
+/*
+Copyright 2019 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import {useState} from 'react';
+
+// Hook to simplify toggling of a boolean state value
+// Returns value, method to toggle boolean value and method to set the boolean value
+export const useStateToggle = (initialValue) => {
+ const [value, setValue] = useState(Boolean(initialValue));
+ const toggleValue = () => {
+ setValue(!value);
+ };
+ return [value, toggleValue, setValue];
+};
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 02d1d0e8d6..f7016d7fad 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1131,6 +1131,8 @@
"Yes": "Yes",
"No": "No",
"Please create a new issue on GitHub so that we can investigate this bug.": "Please create a new issue on GitHub so that we can investigate this bug.",
+ "collapse": "collapse",
+ "expand": "expand",
"Communities": "Communities",
"You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)",
"Uploaded on %(date)s by %(user)s": "Uploaded on %(date)s by %(user)s",
@@ -1193,8 +1195,6 @@
"%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)smade no changes",
"%(oneUser)smade no changes %(count)s times|other": "%(oneUser)smade no changes %(count)s times",
"%(oneUser)smade no changes %(count)s times|one": "%(oneUser)smade no changes",
- "collapse": "collapse",
- "expand": "expand",
"Power level": "Power level",
"Custom level": "Custom level",
"Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.",
diff --git a/test/components/views/elements/MemberEventListSummary-test.js b/test/components/views/elements/MemberEventListSummary-test.js
index 09a4739f06..db86b2ffab 100644
--- a/test/components/views/elements/MemberEventListSummary-test.js
+++ b/test/components/views/elements/MemberEventListSummary-test.js
@@ -162,7 +162,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -198,7 +198,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -246,7 +246,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -299,7 +299,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -358,7 +358,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -396,7 +396,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -447,7 +447,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -521,7 +521,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -568,7 +568,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -604,7 +604,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -632,7 +632,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -659,7 +659,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;
@@ -684,7 +684,7 @@ describe('MemberEventListSummary', function() {
,
);
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, "mx_MemberEventListSummary_summary",
+ instance, "mx_EventListSummary_summary",
);
const summaryText = summary.innerText;