From 8926992feb6549935c8f7a0d12a533df5d01962c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 Jun 2019 14:45:01 +0100 Subject: [PATCH 1/7] Add react button to action bar This adds a (temporarily non-functional) react button to the action bar. Part of https://github.com/vector-im/riot-web/issues/9753 --- res/css/views/messages/_MessageActionBar.scss | 4 ++++ res/img/react.svg | 10 ++++++++++ src/components/views/messages/MessageActionBar.js | 13 +++++++++++++ src/i18n/strings/en_EN.json | 1 + 4 files changed, 28 insertions(+) create mode 100644 res/img/react.svg diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 685c2bb018..7ac0e95e81 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -67,6 +67,10 @@ limitations under the License. background-color: $message-action-bar-fg-color; } +.mx_MessageActionBar_reactButton::after { + mask-image: url('$(res)/img/react.svg'); +} + .mx_MessageActionBar_replyButton::after { mask-image: url('$(res)/img/reply.svg'); } diff --git a/res/img/react.svg b/res/img/react.svg new file mode 100644 index 0000000000..dd23c41c2c --- /dev/null +++ b/res/img/react.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 80f0ba538c..3fb82febce 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -131,6 +131,16 @@ export default class MessageActionBar extends React.PureComponent { return SettingsStore.isFeatureEnabled("feature_message_editing"); } + renderReactButton() { + if (!this.isReactionsEnabled()) { + return null; + } + + return ; + } + renderAgreeDimension() { if (!this.isReactionsEnabled()) { return null; @@ -160,12 +170,14 @@ export default class MessageActionBar extends React.PureComponent { } render() { + let reactButton; let agreeDimensionReactionButtons; let likeDimensionReactionButtons; let replyButton; let editButton; if (isContentActionable(this.props.mxEvent)) { + reactButton = this.renderReactButton(); agreeDimensionReactionButtons = this.renderAgreeDimension(); likeDimensionReactionButtons = this.renderLikeDimension(); replyButton = + {reactButton} {agreeDimensionReactionButtons} {likeDimensionReactionButtons} {replyButton} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3edaaf6241..9564c45172 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -930,6 +930,7 @@ "Error decrypting audio": "Error decrypting audio", "Agree or Disagree": "Agree or Disagree", "Like or Dislike": "Like or Dislike", + "React": "React", "Reply": "Reply", "Edit": "Edit", "Options": "Options", From 91f707341a6d54ad7e1bfea85950bc1ccf5e365d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 Jun 2019 15:08:49 +0100 Subject: [PATCH 2/7] Tweak handler name to match others --- src/components/views/messages/MessageActionBar.js | 4 ++-- src/components/views/rooms/EventTile.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 3fb82febce..aaedf46200 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -57,7 +57,7 @@ export default class MessageActionBar extends React.PureComponent { this.props.onFocusChange(focused); } - onCryptoClicked = () => { + onCryptoClick = () => { const event = this.props.mxEvent; Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', import('../../../async-components/views/dialogs/EncryptedEventDialog'), @@ -89,7 +89,7 @@ export default class MessageActionBar extends React.PureComponent { let e2eInfoCallback = null; if (this.props.mxEvent.isEncrypted()) { - e2eInfoCallback = () => this.onCryptoClicked(); + e2eInfoCallback = () => this.onCryptoClick(); } const menuOptions = { diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index a997e7a3de..988bf7eb3c 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -404,7 +404,7 @@ module.exports = withMatrixClient(React.createClass({ }); }, - onCryptoClicked: function(e) { + onCryptoClick: function(e) { const event = this.props.mxEvent; Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', @@ -440,7 +440,7 @@ module.exports = withMatrixClient(React.createClass({ _renderE2EPadlock: function() { const ev = this.props.mxEvent; - const props = {onClick: this.onCryptoClicked}; + const props = {onClick: this.onCryptoClick}; // event could not be decrypted if (ev.getContent().msgtype === 'm.bad.encrypted') { From fd2723585fb22d08f202652d4971c10a85106480 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 15:10:41 +0100 Subject: [PATCH 3/7] Add quick reaction buttons in tooltip This adds the set of quick reactions as buttons in a new tooltip accessed via the react action in the message action bar. Part of https://github.com/vector-im/riot-web/issues/9753 --- res/css/_components.scss | 1 + .../messages/_ReactionTooltipButton.scss | 24 +++ .../views/messages/MessageActionBar.js | 9 +- .../views/messages/ReactMessageAction.js | 97 +++++++++++ .../views/messages/ReactionTooltipButton.js | 67 ++++++++ .../views/messages/ReactionsQuickTooltip.js | 151 ++++++++++++++++++ src/i18n/strings/en_EN.json | 7 +- 7 files changed, 353 insertions(+), 3 deletions(-) create mode 100644 res/css/views/messages/_ReactionTooltipButton.scss create mode 100644 src/components/views/messages/ReactMessageAction.js create mode 100644 src/components/views/messages/ReactionTooltipButton.js create mode 100644 src/components/views/messages/ReactionsQuickTooltip.js diff --git a/res/css/_components.scss b/res/css/_components.scss index fa388c4e6a..fac149380e 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -118,6 +118,7 @@ @import "./views/messages/_MessageActionBar.scss"; @import "./views/messages/_MessageTimestamp.scss"; @import "./views/messages/_ReactionDimension.scss"; +@import "./views/messages/_ReactionTooltipButton.scss"; @import "./views/messages/_ReactionsRow.scss"; @import "./views/messages/_ReactionsRowButton.scss"; @import "./views/messages/_ReactionsRowButtonTooltip.scss"; diff --git a/res/css/views/messages/_ReactionTooltipButton.scss b/res/css/views/messages/_ReactionTooltipButton.scss new file mode 100644 index 0000000000..7cb754a8fd --- /dev/null +++ b/res/css/views/messages/_ReactionTooltipButton.scss @@ -0,0 +1,24 @@ +/* +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. +*/ + +.mx_ReactionTooltipButton { + font-size: 16px; + user-select: none; +} + +.mx_ReactionTooltipButton_selected { + opacity: 0.4; +} diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index aaedf46200..410c0762b3 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -136,8 +136,13 @@ export default class MessageActionBar extends React.PureComponent { return null; } - return ; } diff --git a/src/components/views/messages/ReactMessageAction.js b/src/components/views/messages/ReactMessageAction.js new file mode 100644 index 0000000000..804a154c9c --- /dev/null +++ b/src/components/views/messages/ReactMessageAction.js @@ -0,0 +1,97 @@ +/* +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 from 'react'; +import PropTypes from 'prop-types'; + +import sdk from '../../../index'; + +export default class ReactMessageAction extends React.PureComponent { + static propTypes = { + mxEvent: PropTypes.object.isRequired, + // The Relations model from the JS SDK for reactions to `mxEvent` + reactions: PropTypes.object, + onFocusChange: PropTypes.func, + } + + constructor(props) { + super(props); + + if (props.reactions) { + props.reactions.on("Relations.add", this.onReactionsChange); + props.reactions.on("Relations.remove", this.onReactionsChange); + props.reactions.on("Relations.redaction", this.onReactionsChange); + } + } + + onFocusChange = (focused) => { + if (!this.props.onFocusChange) { + return; + } + this.props.onFocusChange(focused); + } + + componentDidUpdate(prevProps) { + if (prevProps.reactions !== this.props.reactions) { + this.props.reactions.on("Relations.add", this.onReactionsChange); + this.props.reactions.on("Relations.remove", this.onReactionsChange); + this.props.reactions.on("Relations.redaction", this.onReactionsChange); + this.onReactionsChange(); + } + } + + componentWillUnmount() { + if (this.props.reactions) { + this.props.reactions.removeListener( + "Relations.add", + this.onReactionsChange, + ); + this.props.reactions.removeListener( + "Relations.remove", + this.onReactionsChange, + ); + this.props.reactions.removeListener( + "Relations.redaction", + this.onReactionsChange, + ); + } + } + + onReactionsChange = () => { + // Force a re-render of the tooltip because a change in the reactions + // set means the event tile's layout may have changed and possibly + // altered the location where the tooltip should be shown. + this.forceUpdate(); + } + + render() { + const ReactionsQuickTooltip = sdk.getComponent('messages.ReactionsQuickTooltip'); + const InteractiveTooltip = sdk.getComponent('elements.InteractiveTooltip'); + const { mxEvent, reactions } = this.props; + + const content = ; + + return + + ; + } +} diff --git a/src/components/views/messages/ReactionTooltipButton.js b/src/components/views/messages/ReactionTooltipButton.js new file mode 100644 index 0000000000..a3f0256758 --- /dev/null +++ b/src/components/views/messages/ReactionTooltipButton.js @@ -0,0 +1,67 @@ +/* +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 from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import MatrixClientPeg from '../../../MatrixClientPeg'; + +export default class ReactionTooltipButton extends React.PureComponent { + static propTypes = { + mxEvent: PropTypes.object.isRequired, + // The reaction content / key / emoji + content: PropTypes.string.isRequired, + title: PropTypes.string, + // A possible Matrix event if the current user has voted for this type + myReactionEvent: PropTypes.object, + }; + + onClick = (ev) => { + 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, myReactionEvent } = this.props; + + const classes = classNames({ + mx_ReactionTooltipButton: true, + mx_ReactionTooltipButton_selected: !!myReactionEvent, + }); + + return + {content} + ; + } +} diff --git a/src/components/views/messages/ReactionsQuickTooltip.js b/src/components/views/messages/ReactionsQuickTooltip.js new file mode 100644 index 0000000000..74cfe82baf --- /dev/null +++ b/src/components/views/messages/ReactionsQuickTooltip.js @@ -0,0 +1,151 @@ +/* +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 from 'react'; +import PropTypes from 'prop-types'; + +import { _t } from '../../../languageHandler'; +import sdk from '../../../index'; +import MatrixClientPeg from '../../../MatrixClientPeg'; + +export default class ReactionsQuickTooltip extends React.PureComponent { + static propTypes = { + mxEvent: PropTypes.object.isRequired, + // The Relations model from the JS SDK for reactions to `mxEvent` + reactions: PropTypes.object, + }; + + constructor(props) { + super(props); + + if (props.reactions) { + props.reactions.on("Relations.add", this.onReactionsChange); + props.reactions.on("Relations.remove", this.onReactionsChange); + props.reactions.on("Relations.redaction", this.onReactionsChange); + } + + this.state = { + myReactions: this.getMyReactions(), + }; + } + + componentDidUpdate(prevProps) { + if (prevProps.reactions !== this.props.reactions) { + this.props.reactions.on("Relations.add", this.onReactionsChange); + this.props.reactions.on("Relations.remove", this.onReactionsChange); + this.props.reactions.on("Relations.redaction", this.onReactionsChange); + this.onReactionsChange(); + } + } + + componentWillUnmount() { + if (this.props.reactions) { + this.props.reactions.removeListener( + "Relations.add", + this.onReactionsChange, + ); + this.props.reactions.removeListener( + "Relations.remove", + this.onReactionsChange, + ); + this.props.reactions.removeListener( + "Relations.redaction", + this.onReactionsChange, + ); + } + } + + onReactionsChange = () => { + this.setState({ + myReactions: this.getMyReactions(), + }); + } + + getMyReactions() { + const reactions = this.props.reactions; + if (!reactions) { + return null; + } + const userId = MatrixClientPeg.get().getUserId(); + const myReactions = reactions.getAnnotationsBySender()[userId]; + if (!myReactions) { + return null; + } + return [...myReactions.values()]; + } + + render() { + const { mxEvent } = this.props; + const { myReactions } = this.state; + const ReactionTooltipButton = sdk.getComponent('messages.ReactionTooltipButton'); + + const items = [ + { + content: "👍", + title: _t("Agree"), + }, + { + content: "👎", + title: _t("Disagree"), + }, + { + content: "😄", + title: _t("Happy"), + }, + { + content: "🎉", + title: _t("Party Popper"), + }, + { + content: "😕", + title: _t("Confused"), + }, + { + content: "❤️", + title: _t("Heart"), + }, + { + content: "🚀", + title: _t("Rocket"), + }, + { + content: "👀", + title: _t("Eyes"), + }, + ]; + + const buttons = items.map(({ content, title }) => { + const myReactionEvent = myReactions && myReactions.find(mxEvent => { + if (mxEvent.isRedacted()) { + return false; + } + return mxEvent.getRelation().key === content; + }); + + return ; + }); + + return
+ {buttons} +
; + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9564c45172..02c0658bcb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -930,7 +930,6 @@ "Error decrypting audio": "Error decrypting audio", "Agree or Disagree": "Agree or Disagree", "Like or Dislike": "Like or Dislike", - "React": "React", "Reply": "Reply", "Edit": "Edit", "Options": "Options", @@ -941,6 +940,12 @@ "Invalid file%(extra)s": "Invalid file%(extra)s", "Error decrypting image": "Error decrypting image", "Error decrypting video": "Error decrypting video", + "Agree": "Agree", + "Disagree": "Disagree", + "Happy": "Happy", + "Party Popper": "Party Popper", + "Confused": "Confused", + "Eyes": "Eyes", "reacted with %(shortName)s": "reacted with %(shortName)s", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", From c6f2bb4ba7419b6788a35ad0faf25932eb888a67 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 15:40:31 +0100 Subject: [PATCH 4/7] Arrange buttons in a grid --- res/css/_components.scss | 1 + .../views/messages/_ReactionQuickTooltip.scss | 20 +++++++++++++++++++ .../messages/_ReactionTooltipButton.scss | 1 + 3 files changed, 22 insertions(+) create mode 100644 res/css/views/messages/_ReactionQuickTooltip.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index fac149380e..8b86579bf3 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -118,6 +118,7 @@ @import "./views/messages/_MessageActionBar.scss"; @import "./views/messages/_MessageTimestamp.scss"; @import "./views/messages/_ReactionDimension.scss"; +@import "./views/messages/_ReactionQuickTooltip.scss"; @import "./views/messages/_ReactionTooltipButton.scss"; @import "./views/messages/_ReactionsRow.scss"; @import "./views/messages/_ReactionsRowButton.scss"; diff --git a/res/css/views/messages/_ReactionQuickTooltip.scss b/res/css/views/messages/_ReactionQuickTooltip.scss new file mode 100644 index 0000000000..5fb7987b6f --- /dev/null +++ b/res/css/views/messages/_ReactionQuickTooltip.scss @@ -0,0 +1,20 @@ +/* +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. +*/ + +.mx_ReactionsQuickTooltip { + display: grid; + grid-template-columns: repeat(4, auto); +} diff --git a/res/css/views/messages/_ReactionTooltipButton.scss b/res/css/views/messages/_ReactionTooltipButton.scss index 7cb754a8fd..36b8abd2a4 100644 --- a/res/css/views/messages/_ReactionTooltipButton.scss +++ b/res/css/views/messages/_ReactionTooltipButton.scss @@ -16,6 +16,7 @@ limitations under the License. .mx_ReactionTooltipButton { font-size: 16px; + padding: 6px; user-select: none; } From 088bbbfb91e02f13ded6f9ccad9beedef26cfa64 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 15:54:58 +0100 Subject: [PATCH 5/7] Scale up reaction buttons on hover --- res/css/views/messages/_ReactionTooltipButton.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/messages/_ReactionTooltipButton.scss b/res/css/views/messages/_ReactionTooltipButton.scss index 36b8abd2a4..bf1c25e126 100644 --- a/res/css/views/messages/_ReactionTooltipButton.scss +++ b/res/css/views/messages/_ReactionTooltipButton.scss @@ -18,6 +18,11 @@ limitations under the License. font-size: 16px; padding: 6px; user-select: none; + transition: transform 0.25s; + + &:hover { + transform: scale(1.2); + } } .mx_ReactionTooltipButton_selected { From c1821fabd394747c7f273119b5e3ec1ef3e65a97 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 16:17:02 +0100 Subject: [PATCH 6/7] Remove toggling reaction dimensions This removes the v1 Reactions UX which only allowed you to choose only one emoji out of each pair. It is replaced by a different UX inside a tooltip and without these constraints. Part of https://github.com/vector-im/riot-web/issues/9753 --- res/css/_components.scss | 1 - .../views/messages/_ReactionDimension.scss | 25 --- .../views/messages/MessageActionBar.js | 34 ---- .../views/messages/ReactionDimension.js | 176 ------------------ src/i18n/strings/en_EN.json | 2 - 5 files changed, 238 deletions(-) delete mode 100644 res/css/views/messages/_ReactionDimension.scss delete mode 100644 src/components/views/messages/ReactionDimension.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 8b86579bf3..4986ca837f 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -117,7 +117,6 @@ @import "./views/messages/_MTextBody.scss"; @import "./views/messages/_MessageActionBar.scss"; @import "./views/messages/_MessageTimestamp.scss"; -@import "./views/messages/_ReactionDimension.scss"; @import "./views/messages/_ReactionQuickTooltip.scss"; @import "./views/messages/_ReactionTooltipButton.scss"; @import "./views/messages/_ReactionsRow.scss"; diff --git a/res/css/views/messages/_ReactionDimension.scss b/res/css/views/messages/_ReactionDimension.scss deleted file mode 100644 index 9a891d05cf..0000000000 --- a/res/css/views/messages/_ReactionDimension.scss +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -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. -*/ - -.mx_ReactionDimension { - width: 42px; - display: flex; - justify-content: space-evenly; -} - -.mx_ReactionDimension_disabled { - opacity: 0.4; -} diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 410c0762b3..e7843c1505 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -146,45 +146,13 @@ export default class MessageActionBar extends React.PureComponent { />; } - renderAgreeDimension() { - if (!this.isReactionsEnabled()) { - return null; - } - - const ReactionDimension = sdk.getComponent('messages.ReactionDimension'); - return ; - } - - renderLikeDimension() { - if (!this.isReactionsEnabled()) { - return null; - } - - const ReactionDimension = sdk.getComponent('messages.ReactionDimension'); - return ; - } - render() { let reactButton; - let agreeDimensionReactionButtons; - let likeDimensionReactionButtons; let replyButton; let editButton; if (isContentActionable(this.props.mxEvent)) { reactButton = this.renderReactButton(); - agreeDimensionReactionButtons = this.renderAgreeDimension(); - likeDimensionReactionButtons = this.renderLikeDimension(); replyButton = {reactButton} - {agreeDimensionReactionButtons} - {likeDimensionReactionButtons} {replyButton} {editButton} { - this.setState(this.getSelection()); - } - - getSelection() { - const myReactions = this.getMyReactions(); - if (!myReactions) { - return { - selectedOption: null, - selectedReactionEvent: null, - }; - } - const { options } = this.props; - let selectedOption = null; - let selectedReactionEvent = null; - for (const option of options) { - const reactionForOption = myReactions.find(mxEvent => { - if (mxEvent.isRedacted()) { - return false; - } - return mxEvent.getRelation().key === 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 { selectedOption, selectedReactionEvent }; - } - - getMyReactions() { - const reactions = this.props.reactions; - if (!reactions) { - return null; - } - const userId = MatrixClientPeg.get().getUserId(); - const myReactions = reactions.getAnnotationsBySender()[userId]; - if (!myReactions) { - return null; - } - return [...myReactions.values()]; - } - - onOptionClick = (ev) => { - const { key } = ev.target.dataset; - this.toggleDimension(key); - } - - toggleDimension(key) { - const { selectedOption, selectedReactionEvent } = this.state; - const newSelectedOption = selectedOption !== key ? key : null; - this.setState({ - selectedOption: newSelectedOption, - }); - 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 { selectedOption } = this.state; - const { options } = this.props; - - const items = options.map(option => { - const disabled = selectedOption && selectedOption !== option; - const classes = classNames({ - mx_ReactionDimension_disabled: disabled, - }); - return - {option} - ; - }); - - return - {items} - ; - } -} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 02c0658bcb..010ad29da0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -928,8 +928,6 @@ "Today": "Today", "Yesterday": "Yesterday", "Error decrypting audio": "Error decrypting audio", - "Agree or Disagree": "Agree or Disagree", - "Like or Dislike": "Like or Dislike", "Reply": "Reply", "Edit": "Edit", "Options": "Options", From 93384f91f585d898fae9fd5591411128e2d4bac7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 18:15:03 +0100 Subject: [PATCH 7/7] Show reaction title and shortcode on hover This shows the title and shortcode for the hovered reaction at the bottom of the tooltip. If nothing is hovered, a blank space is shown for now, but will eventually become a link to a full emoji picker in future work. Part of https://github.com/vector-im/riot-web/issues/9753 --- .../views/messages/_ReactionQuickTooltip.scss | 11 +++- .../views/messages/ReactionTooltipButton.js | 1 + .../views/messages/ReactionsQuickTooltip.js | 60 ++++++++++++++++--- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/res/css/views/messages/_ReactionQuickTooltip.scss b/res/css/views/messages/_ReactionQuickTooltip.scss index 5fb7987b6f..7b1611483b 100644 --- a/res/css/views/messages/_ReactionQuickTooltip.scss +++ b/res/css/views/messages/_ReactionQuickTooltip.scss @@ -14,7 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ReactionsQuickTooltip { +.mx_ReactionsQuickTooltip_buttons { display: grid; grid-template-columns: repeat(4, auto); } + +.mx_ReactionsQuickTooltip_label { + text-align: center; +} + +.mx_ReactionsQuickTooltip_shortcode { + padding-left: 6px; + opacity: 0.7; +} diff --git a/src/components/views/messages/ReactionTooltipButton.js b/src/components/views/messages/ReactionTooltipButton.js index a3f0256758..e09b9ade69 100644 --- a/src/components/views/messages/ReactionTooltipButton.js +++ b/src/components/views/messages/ReactionTooltipButton.js @@ -57,6 +57,7 @@ export default class ReactionTooltipButton extends React.PureComponent { }); return { + const { key } = ev.target.dataset; + const item = this.items.find(({ content }) => content === key); + this.setState({ + hoveredItem: item, + }); + } - const items = [ + onMouseOut = (ev) => { + this.setState({ + hoveredItem: null, + }); + } + + get items() { + return [ { content: "👍", title: _t("Agree"), @@ -126,8 +138,14 @@ export default class ReactionsQuickTooltip extends React.PureComponent { title: _t("Eyes"), }, ]; + } - const buttons = items.map(({ content, title }) => { + render() { + const { mxEvent } = this.props; + const { myReactions, hoveredItem } = this.state; + const ReactionTooltipButton = sdk.getComponent('messages.ReactionTooltipButton'); + + const buttons = this.items.map(({ content, title }) => { const myReactionEvent = myReactions && myReactions.find(mxEvent => { if (mxEvent.isRedacted()) { return false; @@ -144,8 +162,34 @@ export default class ReactionsQuickTooltip extends React.PureComponent { />; }); - return
- {buttons} + let label = " "; // non-breaking space to keep layout the same when empty + if (hoveredItem) { + const { content, title } = hoveredItem; + + let shortcodeLabel; + const shortcode = unicodeToShortcode(content); + if (shortcode) { + shortcodeLabel = + {shortcode} + ; + } + + label =
+ + {title} + + {shortcodeLabel} +
; + } + + return
+
+ {buttons} +
+ {label}
; } }