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 fa020612c4..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', @@ -87,15 +123,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,16 +137,103 @@ export default class MessageActionBar extends React.PureComponent { content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body') ) { - replyButton = ; + return true; } } + 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()) { + agreeDimensionReactionButtons = this.renderAgreeDimension(); + likeDimensionReactionButtons = this.renderLikeDimension(); + replyButton = ; + } + return