feat(reactions): add user reaction

add user-reaction collection
add emoji picker for user reaction in the user list
add options to enable/disable user-reaction
add a way to pass style to emoji-picker component
This commit is contained in:
Max Franke 2021-07-15 15:43:38 -03:00 committed by Lucas Fialho Zawacki
parent e79ebb720b
commit d28b93a586
14 changed files with 198 additions and 4 deletions

View File

@ -29,6 +29,7 @@ import clearVoiceCallStates from '/imports/api/voice-call-states/server/modifier
import clearVideoStreams from '/imports/api/video-streams/server/modifiers/clearVideoStreams';
import clearAuthTokenValidation from '/imports/api/auth-token-validation/server/modifiers/clearAuthTokenValidation';
import clearUsersPersistentData from '/imports/api/users-persistent-data/server/modifiers/clearUsersPersistentData';
import clearReactions from '/imports/api/user-reaction/server/modifiers/clearReactions';
import clearWhiteboardMultiUser from '/imports/api/whiteboard-multi-user/server/modifiers/clearWhiteboardMultiUser';
import Metrics from '/imports/startup/server/metrics';
@ -67,6 +68,7 @@ export default async function meetingHasEnded(meetingId) {
clearWhiteboardMultiUser(meetingId),
clearScreenshare(meetingId),
clearUsersPersistentData(meetingId),
clearReactions(meetingId),
]);
await Metrics.removeMeeting(meetingId);
return Logger.info(`Cleared Meetings with id ${meetingId}`);

View File

@ -0,0 +1,14 @@
import { Meteor } from 'meteor/meteor';
const expireSeconds = Meteor.settings.public.userReaction.expire;
const UserReaction = new Mongo.Collection('user-reaction');
if (Meteor.isServer) {
// TTL indexes are special single-field indexes to automatically remove documents
// from a collection after a certain amount of time.
// A single-field with only a date is necessary to this special single-field index, because
// compound indexes do not support TTL.
UserReaction._ensureIndex({ creationDate: 1 }, { expireAfterSeconds: expireSeconds });
}
export default UserReaction;

View File

@ -0,0 +1,2 @@
import './methods';
import './publishers';

View File

@ -0,0 +1,6 @@
import { Meteor } from 'meteor/meteor';
import setUserReaction from './methods/setUserReaction';
Meteor.methods({
setUserReaction,
});

View File

@ -0,0 +1,13 @@
import { check } from 'meteor/check';
import { extractCredentials } from '/imports/api/common/server/helpers';
import addUserReaction from '/imports/api/user-reaction/server/modifiers/addUserReaction';
export default function setUserReaction(reaction) {
check(reaction, String);
const { meetingId, requesterUserId } = extractCredentials(this.userId);
check(meetingId, String);
check(requesterUserId, String);
addUserReaction(meetingId, requesterUserId, reaction);
}

View File

@ -0,0 +1,34 @@
import UserReaction from '/imports/api/user-reaction';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
export default function addUserReaction(meetingId, userId, reaction) {
check(meetingId, String);
check(userId, String);
check(reaction, String);
const selector = {
creationDate: new Date(),
meetingId,
userId,
};
const modifier = {
$set: {
meetingId,
userId,
reaction,
},
};
try {
UserReaction.remove({ meetingId, userId });
const { numberAffected } = UserReaction.upsert(selector, modifier);
if (numberAffected) {
Logger.verbose(`Added user reaction meetingId=${meetingId} userId=${userId}`);
}
} catch (err) {
Logger.error(`Adding user reaction: ${err}`);
}
}

View File

@ -0,0 +1,26 @@
import UserReaction from '/imports/api/user-reaction';
import Logger from '/imports/startup/server/logger';
export default function clearReactions(meetingId) {
const selector = {};
if (meetingId) {
selector.meetingId = meetingId;
}
try {
const numberAffected = UserReaction.remove(selector);
if (numberAffected) {
if (meetingId) {
Logger.info(`Removed UserReaction (${meetingId})`);
} else {
Logger.info('Removed UserReaction (all)');
}
} else {
Logger.warn('Removing UserReaction nonaffected');
}
} catch (err) {
Logger.error(`Removing UserReaction: ${err}`);
}
}

View File

@ -0,0 +1,27 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import UserReaction from '/imports/api/user-reaction';
import { extractCredentials } from '/imports/api/common/server/helpers';
function userReaction() {
if (!this.userId) {
return UserReaction.find({ meetingId: '' });
}
const { meetingId, requesterUserId } = extractCredentials(this.userId);
check(meetingId, String);
check(requesterUserId, String);
Logger.info(`Publishing user reaction for ${meetingId} ${requesterUserId}`);
return UserReaction.find({ meetingId });
}
function publish(...args) {
const boundUserReaction = userReaction.bind(this);
return boundUserReaction(...args);
}
Meteor.publish('user-reaction', publish);

View File

@ -10,6 +10,7 @@ const propTypes = {
description: PropTypes.string,
accessKey: PropTypes.string,
tabIndex: PropTypes.number,
disabled: PropTypes.bool,
};
const defaultProps = {
@ -18,6 +19,7 @@ const defaultProps = {
description: '',
tabIndex: 0,
accessKey: null,
disabled: false,
};
const messages = defineMessages({
@ -62,6 +64,7 @@ class DropdownListItem extends Component {
className,
style,
intl,
disabled,
'data-test': dataTest,
} = this.props;
@ -71,8 +74,8 @@ class DropdownListItem extends Component {
<Styled.Item
id={id}
ref={injectRef}
onClick={onClick}
onKeyDown={onKeyDown}
onClick={disabled ? () => {} : onClick}
onKeyDown={disabled ? () => {} : onKeyDown}
tabIndex={tabIndex}
aria-labelledby={this.labelID}
aria-describedby={this.descID}

View File

@ -23,7 +23,7 @@ const SUBSCRIPTIONS = [
'local-settings', 'users-typing', 'record-meetings', 'video-streams',
'connection-status', 'voice-call-states', 'external-video-meetings', 'breakouts', 'breakouts-history',
'pads', 'pads-sessions', 'pads-updates', 'notifications', 'audio-captions',
'layout-meetings',
'layout-meetings', 'user-reaction',
];
const {
localBreakoutsSync,

View File

@ -4,6 +4,7 @@ import VoiceUsers from '/imports/api/voice-users';
import GroupChat from '/imports/api/group-chat';
import Breakouts from '/imports/api/breakouts';
import Meetings from '/imports/api/meetings';
import UserReaction from '/imports/api/user-reaction';
import Auth from '/imports/ui/services/auth';
import Storage from '/imports/ui/services/storage/session';
import { EMOJI_STATUSES } from '/imports/utils/statuses';
@ -198,6 +199,25 @@ const addIsSharingWebcam = (users) => {
});
};
const addUserReaction = (users) => {
const usersReactions = UserReaction.find({
meetingId: Auth.meetingID,
}).fetch();
return users.map((user) => {
let reaction = '';
const obj = usersReactions.find(us => us.userId === user.userId);
if (obj !== undefined) {
({ reaction } = obj);
}
return {
...user,
reaction,
};
});
};
const getUsers = () => {
let users = Users
.find({
@ -215,7 +235,7 @@ const getUsers = () => {
}
}
return addIsSharingWebcam(addWhiteboardAccess(users)).sort(sortUsers);
return addIsSharingWebcam(addUserReaction(addWhiteboardAccess(users))).sort(sortUsers);
};
const formatUsers = (contextUsers, videoUsers, whiteboardUsers) => {

View File

@ -0,0 +1,43 @@
import UserReaction from '/imports/api/user-reaction';
import Auth from '/imports/ui/services/auth';
import { makeCall } from '/imports/ui/services/api';
import { getFromMeetingSettingsAsBoolean } from '/imports/ui/services/meeting-settings';
const ENABLED = Meteor.settings.public.userReaction.enabled;
const isEnabled = () => getFromMeetingSettingsAsBoolean('enable-user-reaction', ENABLED);
const setUserReaction = (reaction) => {
if (isEnabled()) {
makeCall('setUserReaction', reaction);
}
};
const getUserReaction = (userId) => {
const reaction = UserReaction.findOne(
{
meetingId: Auth.meetingID,
userId,
},
{
fields:
{
reaction: 1,
},
},
);
if (!reaction) {
return {
reaction: 'none',
};
}
return reaction;
};
export default {
getUserReaction,
setUserReaction,
isEnabled,
};

View File

@ -546,6 +546,9 @@ public:
frequentEmojiSortOnClick: false
# e.g.: disableEmojis: ['1F595','1F922']
disableEmojis: []
userReaction:
enabled: true
expire: 60
notes:
enabled: true
id: notes

View File

@ -29,6 +29,7 @@ import '/imports/api/pads/server';
import '/imports/api/guest-users/server';
import '/imports/api/local-settings/server';
import '/imports/api/voice-call-states/server';
import '/imports/api/user-reaction/server';
// Commons
import '/imports/api/log-client/server';