Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Send and redact reaction events
Browse files Browse the repository at this point in the history
This updates both the reaction row and action bar UIs to send and redact
reaction events as appropriate based on user interactions.

Fixes element-hq/element-web#9574
Fixes element-hq/element-web#9572
  • Loading branch information
jryans committed May 10, 2019
1 parent 39bd0d8 commit dc4fccd
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 47 deletions.
2 changes: 2 additions & 0 deletions src/components/views/messages/MessageActionBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export default class MessageActionBar extends React.PureComponent {
title={_t("Agree or Disagree")}
options={["👍", "👎"]}
reactions={this.props.reactions}
mxEvent={this.props.mxEvent}
/>;
}

Expand All @@ -119,6 +120,7 @@ export default class MessageActionBar extends React.PureComponent {
title={_t("Like or Dislike")}
options={["🙂", "😔"]}
reactions={this.props.reactions}
mxEvent={this.props.mxEvent}
/>;
}

Expand Down
66 changes: 43 additions & 23 deletions src/components/views/messages/ReactionDimension.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';

export default class ReactionDimension extends React.PureComponent {
static propTypes = {
mxEvent: PropTypes.object.isRequired,
// Array of strings containing the emoji for each option
options: PropTypes.array.isRequired,
title: PropTypes.string,
Expand All @@ -32,9 +33,7 @@ export default class ReactionDimension extends React.PureComponent {
constructor(props) {
super(props);

this.state = {
selected: this.getSelection(),
};
this.state = this.getSelection();

if (props.reactions) {
props.reactions.on("Relations.add", this.onReactionsChange);
Expand Down Expand Up @@ -63,35 +62,42 @@ export default class ReactionDimension extends React.PureComponent {
}

onReactionsChange = () => {
this.setState({
selected: this.getSelection(),
});
this.setState(this.getSelection());
}

getSelection() {
const myReactions = this.getMyReactions();
if (!myReactions) {
return null;
return {
selectedOption: null,
selectedReactionEvent: null,
};
}
const { options } = this.props;
let selected = null;
let selectedOption = null;
let selectedReactionEvent = null;
for (const option of options) {
const reactionExists = myReactions.some(mxEvent => {
const reactionForOption = myReactions.find(mxEvent => {
if (mxEvent.isRedacted()) {
return false;
}
return mxEvent.getContent()["m.relates_to"].key === option;
});
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 = option;
if (!reactionForOption) {
continue;
}
if (selectedOption) {
// If there are multiple selected values (only expected to occur via
// non-Riot clients), then act as if none are selected.
return {
selectedOption: null,
selectedReactionEvent: null,
};
}
selectedOption = option;
selectedReactionEvent = reactionForOption;
}
return selected;
return { selectedOption, selectedReactionEvent };
}

getMyReactions() {
Expand All @@ -109,20 +115,34 @@ export default class ReactionDimension extends React.PureComponent {
}

toggleDimension(key) {
const state = this.state.selected;
const newState = state !== key ? key : null;
const { selectedOption, selectedReactionEvent } = this.state;
const newSelectedOption = selectedOption !== key ? key : null;
this.setState({
selected: newState,
selectedOption: newSelectedOption,
});
// TODO: Send the reaction event
if (selectedReactionEvent) {
MatrixClientPeg.get().redactEvent(
this.props.mxEvent.getRoomId(),
selectedReactionEvent.getId(),
);
}
if (newSelectedOption) {
MatrixClientPeg.get().sendEvent(this.props.mxEvent.getRoomId(), "m.reaction", {
"m.relates_to": {
"rel_type": "m.annotation",
"event_id": this.props.mxEvent.getId(),
"key": newSelectedOption,
},
});
}
}

render() {
const { selected } = this.state;
const { selectedOption } = this.state;
const { options } = this.props;

const items = options.map(option => {
const disabled = selected && selected !== option;
const disabled = selectedOption && selectedOption !== option;
const classes = classNames({
mx_ReactionDimension_disabled: disabled,
});
Expand Down
26 changes: 26 additions & 0 deletions src/components/views/messages/ReactionsRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import PropTypes from 'prop-types';

import sdk from '../../../index';
import { isContentActionable } from '../../../utils/EventUtils';
import MatrixClientPeg from '../../../MatrixClientPeg';

export default class ReactionsRow extends React.PureComponent {
static propTypes = {
Expand All @@ -35,6 +36,10 @@ export default class ReactionsRow extends React.PureComponent {
props.reactions.on("Relations.add", this.onReactionsChange);
props.reactions.on("Relations.redaction", this.onReactionsChange);
}

this.state = {
myReactions: this.getMyReactions(),
};
}

componentWillReceiveProps(nextProps) {
Expand All @@ -59,11 +64,24 @@ export default class ReactionsRow extends React.PureComponent {

onReactionsChange = () => {
// TODO: Call `onHeightChanged` as needed
this.setState({
myReactions: this.getMyReactions(),
});
this.forceUpdate();
}

getMyReactions() {
const reactions = this.props.reactions;
if (!reactions) {
return null;
}
const userId = MatrixClientPeg.get().getUserId();
return reactions.getAnnotationsBySender()[userId];
}

render() {
const { mxEvent, reactions } = this.props;
const { myReactions } = this.state;

if (!reactions || !isContentActionable(mxEvent)) {
return null;
Expand All @@ -75,10 +93,18 @@ export default class ReactionsRow extends React.PureComponent {
if (!count) {
return null;
}
const myReactionEvent = myReactions && myReactions.find(mxEvent => {
if (mxEvent.isRedacted()) {
return false;
}
return mxEvent.getContent()["m.relates_to"].key === content;
});
return <ReactionsRowButton
key={content}
content={content}
count={count}
mxEvent={mxEvent}
myReactionEvent={myReactionEvent}
/>;
});

Expand Down
48 changes: 24 additions & 24 deletions src/components/views/messages/ReactionsRowButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,48 +18,48 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import MatrixClientPeg from '../../../MatrixClientPeg';

export default class ReactionsRowButton extends React.PureComponent {
static propTypes = {
// The event we're displaying reactions for
mxEvent: PropTypes.object.isRequired,
content: PropTypes.string.isRequired,
count: PropTypes.number.isRequired,
}

constructor(props) {
super(props);

// TODO: This should be derived from actual reactions you may have sent
// once we have some API to read them.
this.state = {
selected: false,
};
// A possible Matrix event if the current user has voted for this type
myReactionEvent: PropTypes.object,
}

onClick = (ev) => {
const state = this.state.selected;
this.setState({
selected: !state,
});
// TODO: Send the reaction event
const { mxEvent, myReactionEvent, content } = this.props;
if (myReactionEvent) {
MatrixClientPeg.get().redactEvent(
mxEvent.getRoomId(),
myReactionEvent.getId(),
);
} else {
MatrixClientPeg.get().sendEvent(mxEvent.getRoomId(), "m.reaction", {
"m.relates_to": {
"rel_type": "m.annotation",
"event_id": mxEvent.getId(),
"key": content,
},
});
}
};

render() {
const { content, count } = this.props;
const { selected } = this.state;
const { content, count, myReactionEvent } = this.props;

const classes = classNames({
mx_ReactionsRowButton: true,
mx_ReactionsRowButton_selected: selected,
mx_ReactionsRowButton_selected: !!myReactionEvent,
});

let adjustedCount = count;
if (selected) {
adjustedCount++;
}

return <span className={classes}
onClick={this.onClick}
>
{content} {adjustedCount}
{content} {count}
</span>;
}
}

0 comments on commit dc4fccd

Please sign in to comment.