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

Add existing reactions below message #2940

Merged
merged 3 commits into from
May 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@
@import "./views/messages/_MTextBody.scss";
@import "./views/messages/_MessageActionBar.scss";
@import "./views/messages/_MessageTimestamp.scss";
@import "./views/messages/_ReactionsRow.scss";
@import "./views/messages/_ReactionsRowButton.scss";
@import "./views/messages/_RoomAvatarEvent.scss";
@import "./views/messages/_SenderProfile.scss";
@import "./views/messages/_TextualEvent.scss";
Expand Down
19 changes: 19 additions & 0 deletions res/css/views/messages/_ReactionsRow.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
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_ReactionsRow {
margin: 6px 0;
}
36 changes: 36 additions & 0 deletions res/css/views/messages/_ReactionsRowButton.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
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_ReactionsRowButton {
display: inline-block;
height: 20px;
line-height: 21px;
margin-right: 6px;
padding: 0 6px;
border: 1px solid $reaction-row-button-border-color;
border-radius: 10px;
background-color: $reaction-row-button-bg-color;
cursor: pointer;

&:hover {
border-color: $reaction-row-button-hover-border-color;
}

&.mx_ReactionsRowButton_selected {
background-color: $reaction-row-button-selected-bg-color;
border-color: $reaction-row-button-selected-border-color;
}
}
6 changes: 6 additions & 0 deletions res/themes/dark/css/_dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ $message-action-bar-fg-color: $header-panel-text-primary-color;
$message-action-bar-border-color: #616b7f;
$message-action-bar-hover-border-color: $header-panel-text-primary-color;

$reaction-row-button-bg-color: $header-panel-bg-color;
$reaction-row-button-border-color: #616b7f;
$reaction-row-button-hover-border-color: $header-panel-text-primary-color;
$reaction-row-button-selected-bg-color: #1f6954;
$reaction-row-button-selected-border-color: $accent-color;

// ***** Mixins! *****

@define-mixin mx_DialogButton {
Expand Down
6 changes: 6 additions & 0 deletions res/themes/light/css/_light.scss
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,12 @@ $message-action-bar-fg-color: $primary-fg-color;
$message-action-bar-border-color: #e9edf1;
$message-action-bar-hover-border-color: #b8c1d2;

$reaction-row-button-bg-color: $header-panel-bg-color;
$reaction-row-button-border-color: #e9edf1;
$reaction-row-button-hover-border-color: #bebebe;
$reaction-row-button-selected-bg-color: #e9fff9;
$reaction-row-button-selected-border-color: $accent-color;

// ***** Mixins! *****

@define-mixin mx_DialogButton {
Expand Down
26 changes: 12 additions & 14 deletions src/components/views/context_menus/MessageContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Modal from '../../../Modal';
import Resend from '../../../Resend';
import SettingsStore from '../../../settings/SettingsStore';
import { isUrlPermitted } from '../../../HtmlUtils';
import { isContentActionable } from '../../../utils/EventUtils';

module.exports = React.createClass({
displayName: 'MessageContextMenu',
Expand Down Expand Up @@ -247,22 +248,19 @@ module.exports = React.createClass({
);
}

if (isSent && mxEvent.getType() === 'm.room.message') {
const content = mxEvent.getContent();
if (content.msgtype && content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body')) {
forwardButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onForwardClick}>
{ _t('Forward Message') }
if (isContentActionable(mxEvent)) {
forwardButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onForwardClick}>
{ _t('Forward Message') }
</div>
);

if (this.state.canPin) {
pinButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onPinClick}>
{ this._isPinned() ? _t('Unpin Message') : _t('Pin Message') }
</div>
);

if (this.state.canPin) {
pinButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onPinClick}>
{ this._isPinned() ? _t('Unpin Message') : _t('Pin Message') }
</div>
);
}
}
}

Expand Down
27 changes: 3 additions & 24 deletions src/components/views/messages/MessageActionBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ limitations under the License.

import React from 'react';
import PropTypes from 'prop-types';
import {EventStatus} from 'matrix-js-sdk';
import classNames from 'classnames';

import { _t } from '../../../languageHandler';
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';
import { isContentActionable } from '../../../utils/EventUtils';

export default class MessageActionBar extends React.PureComponent {
static propTypes = {
Expand Down Expand Up @@ -123,27 +123,6 @@ export default class MessageActionBar extends React.PureComponent {
this.onFocusChange(true);
}

isContentActionable() {
const { mxEvent } = this.props;
const { status: eventStatus } = mxEvent;

// status is SENT before remote-echo, null after
const isSent = !eventStatus || eventStatus === EventStatus.SENT;

if (isSent && mxEvent.getType() === 'm.room.message') {
const content = mxEvent.getContent();
if (
content.msgtype &&
content.msgtype !== 'm.bad.encrypted' &&
content.hasOwnProperty('body')
) {
return true;
}
}

return false;
}

isReactionsEnabled() {
return SettingsStore.isFeatureEnabled("feature_reactions");
}
Expand Down Expand Up @@ -220,7 +199,7 @@ export default class MessageActionBar extends React.PureComponent {
let likeDimensionReactionButtons;
let replyButton;

if (this.isContentActionable()) {
if (isContentActionable(this.props.mxEvent)) {
agreeDimensionReactionButtons = this.renderAgreeDimension();
likeDimensionReactionButtons = this.renderLikeDimension();
replyButton = <span className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
Expand Down
65 changes: 65 additions & 0 deletions src/components/views/messages/ReactionsRow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
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.
*/

import React from 'react';
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,
}

render() {
const { mxEvent } = 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") {
return null;
}

const ReactionsRowButton = sdk.getComponent('messages.ReactionsRowButton');
const items = Object.entries(SAMPLE_REACTIONS).map(([content, count]) => {
return <ReactionsRowButton
key={content}
content={content}
count={count}
/>;
});

return <div className="mx_ReactionsRow">
{items}
</div>;
}
}
65 changes: 65 additions & 0 deletions src/components/views/messages/ReactionsRowButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
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.
*/

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

export default class ReactionsRowButton extends React.PureComponent {
static propTypes = {
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,
};
}

onClick = (ev) => {
const state = this.state.selected;
this.setState({
selected: !state,
});
// TODO: Send the reaction event
};

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

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

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

return <span className={classes}
onClick={this.onClick}
>
{content} {adjustedCount}
</span>;
}
}
9 changes: 9 additions & 0 deletions src/components/views/rooms/EventTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,14 @@ module.exports = withMatrixClient(React.createClass({
<ToolTipButton helpText={keyRequestHelpText} />
</div> : null;

let reactions;
if (SettingsStore.isFeatureEnabled("feature_reactions")) {
const ReactionsRow = sdk.getComponent('messages.ReactionsRow');
reactions = <ReactionsRow
mxEvent={this.props.mxEvent}
/>;
}

switch (this.props.tileShape) {
case 'notif': {
const EmojiText = sdk.getComponent('elements.EmojiText');
Expand Down Expand Up @@ -734,6 +742,7 @@ module.exports = withMatrixClient(React.createClass({
showUrlPreview={this.props.showUrlPreview}
onHeightChanged={this.props.onHeightChanged} />
{ keyRequestInfo }
{ reactions }
{ actionBar }
</div>
{
Expand Down
45 changes: 45 additions & 0 deletions src/utils/EventUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
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.
*/

import { EventStatus } from 'matrix-js-sdk';

/**
* Returns whether an event should allow actions like reply, reactions, edit, etc.
* which effectively checks whether it's a regular message that has been sent and that we
* can display.
*
* @param {MatrixEvent} mxEvent The event to check
* @returns {boolean} true if actionable
*/
export function isContentActionable(mxEvent) {
const { status: eventStatus } = mxEvent;

// status is SENT before remote-echo, null after
const isSent = !eventStatus || eventStatus === EventStatus.SENT;

if (isSent && mxEvent.getType() === 'm.room.message') {
const content = mxEvent.getContent();
if (
content.msgtype &&
content.msgtype !== 'm.bad.encrypted' &&
content.hasOwnProperty('body')
) {
return true;
}
}

return false;
}