Skip to content

Commit

Permalink
reactions: support removing reaction
Browse files Browse the repository at this point in the history
constants: add REMOVE_REACTION
actions: add removeReaction

reaction:
add decrement prop (via dispatch / connect)
in onClick, decrement if user has reacted, otherwise increment
if userHasReacted, keep background blue

create ReactionsList component

reactions:
refactor, add userHasReacted prop to reaction
replace this.reactionsUsers and this.emojiNames with this.emojis
keep track of all users across all reactions for this message (for
displaying)
pass this.emojis[emoji_code] to Reaction
use connect/redux and config.username to check if userHasReacted
use reactionsList
change forEach to for of loop

package.json: use zulip/zulip-js.git (reactions code has been merged)
update yarn.lock
  • Loading branch information
arpith committed Apr 19, 2018
1 parent 97b7a98 commit 5d980ac
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 48 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"redux-thunk": "2.1.0",
"throng": "4.0.0",
"underscore": "1.8.3",
"zulip-js": "git+https://github.com/arpith/zulip-js.git#reactions-new"
"zulip-js": "git+https://github.com/zulip/zulip-js.git"
},
"devDependencies": {
"eslint": "4.15.0",
Expand Down
18 changes: 18 additions & 0 deletions src/app/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
UPDATE_CURRENT_MESSAGE,
UPDATE_SUBSCRIPTIONS,
ADD_REACTION,
REMOVE_REACTION,
} from '../constants';

export function addReaction(message_id, emoji_name, emoji_code) {
Expand All @@ -27,6 +28,23 @@ export function addReaction(message_id, emoji_name, emoji_code) {
};
}

export function removeReaction(message_id, emoji_name, emoji_code) {
return (dispatch, getState) => {
const { config } = getState();
return zulip(config)
.then(z => z.reactions.remove({ message_id, emoji_name, emoji_code }))
.then(({ result }) => {
if (result === 'success') {
dispatch({
type: REMOVE_REACTION,
message_id,
emoji: emoji_name
});
}
});
};
}

export function updateCurrentMessage(message) {
return (dispatch) => {
dispatch({
Expand Down
27 changes: 21 additions & 6 deletions src/app/components/Reaction.jsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,47 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { replaceColons } from '../emoji';
import { addReaction } from '../actions';
import { addReaction, removeReaction } from '../actions';
import { style, hover } from '../styles/reaction';

class Reaction extends Component {
state = { style };
constructor(props) {
super(props);
if (props.userHasReacted) {
this.state = { style: hover };
} else {
this.state = { style };
}
this.onHover = this.onHover.bind(this);
}

onHover = (shouldHighlight) => {
onHover(shouldHighlight) {
if (shouldHighlight) {
this.setState({ style: hover });
} else {
this.setState({ style });
}
this.props.onHover();
};
}

render() {
const reactionEmoji = replaceColons(`:${this.props.emojiName}:`);
return <button style={this.state.style}
onClick={this.props.increment}
onClick={() => {
if (this.props.userHasReacted) {
this.props.decrement();
} else {
this.props.increment();
}
}}
onMouseEnter={() => this.onHover(true)}
onMouseLeave={() => this.onHover(false)}>
onMouseLeave={() => this.onHover(this.props.userHasReacted)}>
{reactionEmoji}
</button>;
}
}

export default connect(null, (dispatch, { emojiName, messageID, emojiCode }) => ({
increment: () => dispatch(addReaction(messageID, emojiName, emojiCode)),
decrement: () => dispatch(removeReaction(messageID, emojiName, emojiCode)),
}))(Reaction);
71 changes: 39 additions & 32 deletions src/app/components/Reactions.jsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,58 @@
import React, { Component } from 'react';
import Reaction from './Reaction';
import { connect } from 'react-redux';
import ReactionsList from './ReactionsList';
import ReactionUsers from './ReactionUsers';
import style from '../styles/reactions';

export default class Reactions extends Component {
class Reactions extends Component {
constructor(props) {
super(props);
this.reactionsUsers = {};
this.emojiNames = {};
this.props.reactions.forEach(({ emoji_name, user, emoji_code }) => {
this.emojiNames[emoji_code] = emoji_name;
if (!this.reactionsUsers[emoji_code]) {
this.reactionsUsers[emoji_code] = [user];
} else {
this.reactionsUsers[emoji_code].push(user);
this.emojis = {};
this.users = {};
for (({ emoji_name, emoji_code, user }) of props.reactions) {
if (!this.emojis[emoji_code]) {
this.emojis[emoji_code] = {
emojiName: emoji_name,
emojiCode: emoji_code,
userHasReacted: false,
users: [],
};
}
});
if (user.email === this.props.username) {
this.emojis[emoji_code].userHasReacted = true;
}
this.users[user.email] = user;
this.emojis[emoji_code].users.push(user);
}
this.state = {
displayUsers: false,
users: []
};
this.toggleDisplayUsers = () => {
if (!this.state.displayUsers) {
const allUsers = [].concat(...Object.values(this.reactionsUsers));
const uniqueUsers = {};
for (const user of allUsers) {
uniqueUsers[user.email] = user;
}
this.setState({ users: Object.values(uniqueUsers) });
}
this.setState({ displayUsers: !this.state.displayUsers });
};
this.displayUsers = (emojiCode) => this.setState({ users: this.reactionsUsers[emojiCode] });
this.toggleDisplayUsers = this.toggleDisplayUsers.bind(this);
this.displayUsers = this.displayUsers.bind(this);
}

toggleDisplayUsers() {
if (!this.state.displayUsers) {
this.setState({ users: Object.values(this.users) });
}
this.setState({ displayUsers: !this.state.displayUsers });
}

displayUsers(emojiCode) {
this.setState({ users: this.emojis[emojiCode].users });
}

render() {
const reactions = Object.keys(this.reactionsUsers).map((emojiCode) => {
return <Reaction key={emojiCode}
emojiCode={emojiCode}
emojiName={this.emojiNames[emojiCode]}
messageID={this.props.messageID}
onHover={() => this.displayUsers(emojiCode)}
/>;
});
return <div style={style} onMouseEnter={this.toggleDisplayUsers} onMouseLeave={this.toggleDisplayUsers} >
{reactions}
<ReactionsList emojis={Object.values(this.emojis)}
onHover={this.displayUsers}
messageID={this.props.messageID}
/>
<ReactionUsers users={this.state.users} shouldDisplay={this.state.displayUsers} />
</div>;
}
}
export default connect(
({config}) => ({username: config.username}),
)(Reactions);
14 changes: 14 additions & 0 deletions src/app/components/ReactionsList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import Reaction from './Reaction';

function reaction(emoji, onHover, messageID) {
return <Reaction key={emoji.emojiCode}
messageID={messageID}
onHover={() => onHover(emoji.emojiCode)}
{...emoji}
/>;
}

export default ({ emojis, onHover, messageID }) => {
return emojis.map(emoji => reaction(emoji, onHover, messageID));
};
1 change: 1 addition & 0 deletions src/app/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export const UPDATE_POINTER = 'UPDATE_POINTER';
export const UPDATE_CURRENT_MESSAGE = 'UPDATE_CURRENT_MESSAGE';
export const UPDATE_SUBSCRIPTIONS = 'UPDATE_SUBSCRIPTIONS';
export const ADD_REACTION = 'ADD_REACTION';
export const REMOVE_REACTION = 'REMOVE_REACTION';
9 changes: 0 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5054,15 +5054,6 @@ yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"

yargs@~3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
dependencies:
camelcase "^1.0.2"
cliui "^2.1.0"
decamelize "^1.0.0"
window-size "0.1.0"

"zulip-js@git+https://github.com/arpith/zulip-js.git#reactions-new":
version "2.0.2"
resolved "git+https://github.com/arpith/zulip-js.git#48c5c2ea784abe2cb8b2eb55fba9c82a4bed304c"
Expand Down

0 comments on commit 5d980ac

Please sign in to comment.