Factor out generic EventListSummary from MELS
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
4635b1319a
commit
34530843f4
7 changed files with 164 additions and 107 deletions
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
95
src/components/views/elements/EventListSummary.js
Normal file
95
src/components/views/elements/EventListSummary.js
Normal file
|
@ -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 (
|
||||
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (expanded) {
|
||||
return (
|
||||
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
<div className={"mx_EventListSummary_toggle"} onClick={toggleExpanded}>
|
||||
{ _t('collapse') }
|
||||
</div>
|
||||
<div className="mx_EventListSummary_line"> </div>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const avatars = summaryMembers.map((m) => <MemberAvatar key={m.userId} member={m} width={14} height={14} />);
|
||||
return (
|
||||
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
<div className={"mx_EventListSummary_toggle"} onClick={toggleExpanded}>
|
||||
{ _t('expand') }
|
||||
</div>
|
||||
<div className="mx_EventTile_line">
|
||||
<div className="mx_EventTile_info">
|
||||
<span className="mx_EventListSummary_avatars" onClick={toggleExpanded}>
|
||||
{ avatars }
|
||||
</span>
|
||||
<span className="mx_TextualEvent mx_EventListSummary_summary">
|
||||
{ summaryText }
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
|
@ -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 <span> 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 (
|
||||
<span className="mx_TextualEvent mx_MemberEventListSummary_summary">
|
||||
{ summaries.join(", ") }
|
||||
</span>
|
||||
);
|
||||
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 (
|
||||
<MemberAvatar key={m.userId} member={m} width={14} height={14} />
|
||||
);
|
||||
});
|
||||
return (
|
||||
<span className="mx_MemberEventListSummary_avatars" onClick={this._toggleSummary}>
|
||||
{ avatars }
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
||||
_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 (
|
||||
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
|
||||
{ expandedEvents }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 = (
|
||||
<div className="mx_EventTile_line">
|
||||
<div className="mx_EventTile_info">
|
||||
{ this._renderAvatars(avatarMembers) }
|
||||
{ this._renderSummary(aggregate.names, orderedTransitionSequences) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const toggleButton = (
|
||||
<div className={"mx_MemberEventListSummary_toggle"} onClick={this._toggleSummary}>
|
||||
{ expanded ? _t('collapse') : _t('expand') }
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
|
||||
{ toggleButton }
|
||||
{ summaryContainer }
|
||||
{ expanded ? <div className="mx_MemberEventListSummary_line"> </div> : null }
|
||||
{ expandedEvents }
|
||||
</div>
|
||||
);
|
||||
const EventListSummary = sdk.getComponent("views.elements.EventListSummary");
|
||||
return <EventListSummary
|
||||
events={this.props.events}
|
||||
threshold={this.props.threshold}
|
||||
onToggle={this.props.onToggle}
|
||||
startExpanded={this.props.startExpanded}
|
||||
children={this.props.children}
|
||||
summaryMembers={avatarMembers}
|
||||
summaryText={this._generateSummary(aggregate.names, orderedTransitionSequences)} />;
|
||||
},
|
||||
});
|
||||
|
|
27
src/hooks/useStateToggle.js
Normal file
27
src/hooks/useStateToggle.js
Normal file
|
@ -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];
|
||||
};
|
|
@ -1131,6 +1131,8 @@
|
|||
"Yes": "Yes",
|
||||
"No": "No",
|
||||
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> 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.",
|
||||
|
|
|
@ -162,7 +162,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -198,7 +198,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -246,7 +246,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -299,7 +299,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -358,7 +358,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -396,7 +396,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -447,7 +447,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -521,7 +521,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -568,7 +568,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -604,7 +604,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -632,7 +632,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -659,7 +659,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
@ -684,7 +684,7 @@ describe('MemberEventListSummary', function() {
|
|||
<MemberEventListSummary {...props} />,
|
||||
);
|
||||
const summary = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
instance, "mx_MemberEventListSummary_summary",
|
||||
instance, "mx_EventListSummary_summary",
|
||||
);
|
||||
const summaryText = summary.innerText;
|
||||
|
||||
|
|
Loading…
Reference in a new issue