diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index b0f6699bc1..2037217710 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -92,6 +92,9 @@ module.exports = React.createClass({ // show timestamps always alwaysShowTimestamps: PropTypes.bool, + + // helper function to access relations for an event + getRelationsForEvent: PropTypes.func, }, componentWillMount: function() { @@ -529,6 +532,7 @@ module.exports = React.createClass({ permalinkCreator={this.props.permalinkCreator} last={last} isSelectedEvent={highlight} + getRelationsForEvent={this.props.getRelationsForEvent} /> , ); diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index e065c5719e..17a062be98 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -1168,6 +1168,10 @@ const TimelinePanel = React.createClass({ }); }, + getRelationsForEvent(...args) { + return this.props.timelineSet.getRelationsForEvent(...args); + }, + render: function() { const MessagePanel = sdk.getComponent("structures.MessagePanel"); const Loader = sdk.getComponent("elements.Spinner"); @@ -1239,6 +1243,7 @@ const TimelinePanel = React.createClass({ className={this.props.className} tileShape={this.props.tileShape} resizeNotifier={this.props.resizeNotifier} + getRelationsForEvent={this.getRelationsForEvent} /> ); }, diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 9a482c9e6e..d2ac9adcf0 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -28,6 +28,8 @@ import { isContentActionable } from '../../../utils/EventUtils'; export default class MessageActionBar extends React.PureComponent { static propTypes = { mxEvent: PropTypes.object.isRequired, + // The Relations model from the JS SDK for reactions to `mxEvent` + reactions: PropTypes.object, permalinkCreator: PropTypes.object, getTile: PropTypes.func, getReplyThread: PropTypes.func, @@ -113,6 +115,7 @@ export default class MessageActionBar extends React.PureComponent { return ; } @@ -135,6 +138,7 @@ export default class MessageActionBar extends React.PureComponent { return ; } diff --git a/src/components/views/messages/ReactionDimension.js b/src/components/views/messages/ReactionDimension.js index 3b72aabe15..675d99e187 100644 --- a/src/components/views/messages/ReactionDimension.js +++ b/src/components/views/messages/ReactionDimension.js @@ -18,20 +18,56 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import MatrixClientPeg from '../../../MatrixClientPeg'; + export default class ReactionDimension extends React.PureComponent { static propTypes = { options: PropTypes.array.isRequired, title: PropTypes.string, + // The Relations model from the JS SDK for reactions + reactions: PropTypes.object, }; constructor(props) { super(props); this.state = { - selected: null, + selected: this.getInitialSelection(), }; } + getInitialSelection() { + const myReactions = this.getMyReactions(); + if (!myReactions) { + return null; + } + const { options } = this.props; + let selected = null; + for (const { key, content } of options) { + const reactionExists = myReactions.some(mxEvent => { + return mxEvent.getContent()["m.relates_to"].key === content; + }); + if (reactionExists) { + if (selected) { + // If there are multiple selected values (only expected to occur via + // non-Riot clients), then act as if none are selected. + return null; + } + selected = key; + } + } + return selected; + } + + getMyReactions() { + const reactions = this.props.reactions; + if (!reactions) { + return null; + } + const userId = MatrixClientPeg.get().getUserId(); + return reactions.getAnnotationsBySender()[userId]; + } + onOptionClick = (ev) => { const { key } = ev.target.dataset; this.toggleDimensionValue(key); diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.js index a4299b9853..7242fa9144 100644 --- a/src/components/views/messages/ReactionsRow.js +++ b/src/components/views/messages/ReactionsRow.js @@ -20,37 +20,24 @@ import PropTypes from 'prop-types'; import sdk from '../../../index'; import { isContentActionable } from '../../../utils/EventUtils'; -// TODO: Actually load reactions from the timeline -// Since we don't yet load reactions, let's inject some dummy data for testing the UI -// only. The UI assumes these are already sorted into the order we want to present, -// presumably highest vote first. -const SAMPLE_REACTIONS = { - "👍": 4, - "👎": 2, - "🙂": 1, -}; - export default class ReactionsRow extends React.PureComponent { static propTypes = { // The event we're displaying reactions for mxEvent: PropTypes.object.isRequired, + // The Relations model from the JS SDK for reactions to `mxEvent` + reactions: PropTypes.object, } render() { - const { mxEvent } = this.props; + const { mxEvent, reactions } = this.props; - if (!isContentActionable(mxEvent)) { - return null; - } - - const content = mxEvent.getContent(); - // TODO: Remove this once we load real reactions - if (!content.body || content.body !== "reactions test") { + if (!reactions || !isContentActionable(mxEvent)) { return null; } const ReactionsRowButton = sdk.getComponent('messages.ReactionsRowButton'); - const items = Object.entries(SAMPLE_REACTIONS).map(([content, count]) => { + const items = reactions.getSortedAnnotationsByKey().map(([content, events]) => { + const count = events.length; return : null; - let reactions; + let reactionsRow; if (SettingsStore.isFeatureEnabled("feature_reactions")) { const ReactionsRow = sdk.getComponent('messages.ReactionsRow'); - reactions = ; } @@ -750,7 +767,7 @@ module.exports = withMatrixClient(React.createClass({ showUrlPreview={this.props.showUrlPreview} onHeightChanged={this.props.onHeightChanged} /> { keyRequestInfo } - { reactions } + { reactionsRow } { actionBar } {