From 7f62cdf1249be58c3c8fb2284ccef30588b3ede6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 26 Apr 2019 10:52:36 +0100 Subject: [PATCH 1/3] Add reactions feature flag --- src/i18n/strings/en_EN.json | 1 + src/settings/Settings.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0ee2f2082a..e18dc2761d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -297,6 +297,7 @@ "Show recent room avatars above the room list": "Show recent room avatars above the room list", "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", "Render simple counters in room header": "Render simple counters in room header", + "React to messages with emoji": "React to messages with emoji", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", "Use compact timeline layout": "Use compact timeline layout", "Show a placeholder for removed messages": "Show a placeholder for removed messages", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 35baa718b9..4be1b67227 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -118,6 +118,12 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_reactions": { + isFeature: true, + displayName: _td("React to messages with emoji"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "MessageComposerInput.suggestEmoji": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable Emoji suggestions while typing'), From 00ca930d2e9abe6b51189292a60444d6bf9f13f1 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 29 Apr 2019 16:11:23 +0100 Subject: [PATCH 2/3] Extract actionable content check to helper --- .../views/messages/MessageActionBar.js | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index fa020612c4..3fe3f74a9d 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -87,15 +87,13 @@ export default class MessageActionBar extends React.PureComponent { this.onFocusChange(true); } - render() { + isContentActionable() { const { mxEvent } = this.props; const { status: eventStatus } = mxEvent; // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; - let replyButton; - if (isSent && mxEvent.getType() === 'm.room.message') { const content = mxEvent.getContent(); if ( @@ -103,13 +101,23 @@ export default class MessageActionBar extends React.PureComponent { content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body') ) { - replyButton = ; + return true; } } + return false; + } + + render() { + let replyButton; + + if (this.isContentActionable()) { + replyButton = ; + } + return
{replyButton} Date: Tue, 30 Apr 2019 18:09:10 +0100 Subject: [PATCH 3/3] Add primary reactions to action bar This adds the primary reactions to the action bar. They act as toggles where you can only select one from each group at a time. Note that currently we aren't actually sending the reaction at all. That's left for a separate task. Fixes https://github.com/vector-im/riot-web/issues/9576 --- res/css/views/messages/_MessageActionBar.scss | 35 ++++-- .../views/messages/MessageActionBar.js | 117 +++++++++++++++++- src/i18n/strings/en_EN.json | 2 + 3 files changed, 140 insertions(+), 14 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index f01ef4f04f..877e1916fc 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -20,6 +20,7 @@ limitations under the License. cursor: pointer; display: flex; height: 24px; + line-height: 24px; border-radius: 4px; background: $message-action-bar-bg-color; top: -13px; @@ -49,21 +50,21 @@ limitations under the License. &:only-child { border-radius: 3px; } - - &::after { - content: ''; - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; - mask-repeat: no-repeat; - mask-position: center; - background-color: $message-action-bar-fg-color; - } } } +.mx_MessageActionBar_maskButton::after { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + mask-repeat: no-repeat; + mask-position: center; + background-color: $message-action-bar-fg-color; +} + .mx_MessageActionBar_replyButton::after { mask-image: url('$(res)/img/reply.svg'); } @@ -71,3 +72,13 @@ limitations under the License. .mx_MessageActionBar_optionsButton::after { mask-image: url('$(res)/img/icon_context.svg'); } + +.mx_MessageActionBar_reactionDimension { + width: 42px; + display: flex; + justify-content: space-evenly; +} + +.mx_MessageActionBar_reactionDisabled { + opacity: 0.4; +} diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 3fe3f74a9d..276a142ccb 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -23,6 +23,8 @@ import sdk from '../../../index'; import dis from '../../../dispatcher'; import Modal from '../../../Modal'; import { createMenu } from '../../structures/ContextualMenu'; +import SettingsStore from '../../../settings/SettingsStore'; +import classNames from 'classnames'; export default class MessageActionBar extends React.PureComponent { static propTypes = { @@ -33,6 +35,15 @@ export default class MessageActionBar extends React.PureComponent { onFocusChange: PropTypes.func, }; + constructor(props) { + super(props); + + this.state = { + agreeDimension: null, + likeDimension: null, + }; + } + onFocusChange = (focused) => { if (!this.props.onFocusChange) { return; @@ -48,6 +59,31 @@ export default class MessageActionBar extends React.PureComponent { ); } + onAgreeClick = (ev) => { + this.toggleDimensionValue("agreeDimension", "agree"); + } + + onDisagreeClick = (ev) => { + this.toggleDimensionValue("agreeDimension", "disagree"); + } + + onLikeClick = (ev) => { + this.toggleDimensionValue("likeDimension", "like"); + } + + onDislikeClick = (ev) => { + this.toggleDimensionValue("likeDimension", "dislike"); + } + + toggleDimensionValue(dimension, value) { + const state = this.state[dimension]; + const newState = state !== value ? value : null; + this.setState({ + [dimension]: newState, + }); + // TODO: Send the reaction event + } + onReplyClick = (ev) => { dis.dispatch({ action: 'reply_to_event', @@ -108,19 +144,96 @@ export default class MessageActionBar extends React.PureComponent { return false; } + isReactionsEnabled() { + return SettingsStore.isFeatureEnabled("feature_reactions"); + } + + renderAgreeDimension() { + if (!this.isReactionsEnabled()) { + return null; + } + + const state = this.state.agreeDimension; + const options = [ + { + key: "agree", + content: "👍", + onClick: this.onAgreeClick, + }, + { + key: "disagree", + content: "👎", + onClick: this.onDisagreeClick, + }, + ]; + + return + {this.renderReactionDimensionItems(state, options)} + ; + } + + renderLikeDimension() { + if (!this.isReactionsEnabled()) { + return null; + } + + const state = this.state.likeDimension; + const options = [ + { + key: "like", + content: "🙂", + onClick: this.onLikeClick, + }, + { + key: "dislike", + content: "😔", + onClick: this.onDislikeClick, + }, + ]; + + return + {this.renderReactionDimensionItems(state, options)} + ; + } + + renderReactionDimensionItems(state, options) { + return options.map(option => { + const disabled = state && state !== option.key; + const classes = classNames({ + mx_MessageActionBar_reactionDisabled: disabled, + }); + return + {option.content} + ; + }); + } + render() { + let agreeDimensionReactionButtons; + let likeDimensionReactionButtons; let replyButton; if (this.isContentActionable()) { - replyButton = ; } return
+ {agreeDimensionReactionButtons} + {likeDimensionReactionButtons} {replyButton} - diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e18dc2761d..5cfb61d60b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -890,6 +890,8 @@ "Today": "Today", "Yesterday": "Yesterday", "Error decrypting audio": "Error decrypting audio", + "Agree or Disagree": "Agree or Disagree", + "Like or Dislike": "Like or Dislike", "Reply": "Reply", "Options": "Options", "Attachment": "Attachment",