fix(reactions) Batch events before sending

This commit is contained in:
robertpin 2021-07-20 14:31:49 +03:00 committed by GitHub
parent 4276f82c03
commit 251eec19cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 216 additions and 182 deletions

View File

@ -823,8 +823,8 @@
"hangup": "Leave the meeting", "hangup": "Leave the meeting",
"help": "Help", "help": "Help",
"invite": "Invite people", "invite": "Invite people",
"joy": "Laugh",
"kick": "Kick participant", "kick": "Kick participant",
"laugh": "Laugh",
"like": "Thumbs Up", "like": "Thumbs Up",
"lobbyButton": "Enable/disable lobby mode", "lobbyButton": "Enable/disable lobby mode",
"localRecording": "Toggle local recording controls", "localRecording": "Toggle local recording controls",
@ -892,7 +892,7 @@
"hangup": "Leave the meeting", "hangup": "Leave the meeting",
"help": "Help", "help": "Help",
"invite": "Invite people", "invite": "Invite people",
"joy": "Laugh", "laugh": "Laugh",
"like": "Thumbs Up", "like": "Thumbs Up",
"lobbyButtonDisable": "Disable lobby mode", "lobbyButtonDisable": "Disable lobby mode",
"lobbyButtonEnable": "Enable lobby mode", "lobbyButtonEnable": "Enable lobby mode",
@ -922,7 +922,7 @@
"raiseYourHand": "Raise your hand", "raiseYourHand": "Raise your hand",
"reactionBoo": "Send boo reaction", "reactionBoo": "Send boo reaction",
"reactionClap": "Send clap reaction", "reactionClap": "Send clap reaction",
"reactionJoy": "Send laugh reaction", "reactionLaugh": "Send laugh reaction",
"reactionLike": "Send thumbs up reaction", "reactionLike": "Send thumbs up reaction",
"reactionParty": "Send party popper reaction", "reactionParty": "Send party popper reaction",
"reactionSurprised": "Send surprised reaction", "reactionSurprised": "Send surprised reaction",

View File

@ -795,6 +795,23 @@ export function createToolbarEvent(buttonName, attributes = {}) {
}; };
} }
/**
* Creates an event associated with a reaction button being clicked/pressed.
*
* @param {string} buttonName - The identifier of the reaction button which was
* clicked/pressed.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createReactionMenuEvent(buttonName) {
return {
action: 'clicked',
actionSubject: buttonName,
source: 'reaction.button',
type: TYPE_UI
};
}
/** /**
* Creates an event which indicates that a local track was muted. * Creates an event which indicates that a local track was muted.
* *

View File

@ -22,11 +22,9 @@ import {
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux'; import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds'; import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { openDisplayNamePrompt } from '../display-name'; import { openDisplayNamePrompt } from '../display-name';
import { ADD_REACTIONS_MESSAGE } from '../reactions/actionTypes'; import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
import { import { pushReactions } from '../reactions/actions.any';
pushReaction import { getReactionMessageFromBuffer } from '../reactions/functions.any';
} from '../reactions/actions.any';
import { REACTIONS } from '../reactions/constants';
import { endpointMessageReceived } from '../subtitles'; import { endpointMessageReceived } from '../subtitles';
import { showToolbox } from '../toolbox/actions'; import { showToolbox } from '../toolbox/actions';
import { import {
@ -158,7 +156,7 @@ MiddlewareRegistry.register(store => next => action => {
break; break;
} }
case ADD_REACTIONS_MESSAGE: { case ADD_REACTION_MESSAGE: {
_handleReceivedMessage(store, { _handleReceivedMessage(store, {
id: localParticipant.id, id: localParticipant.id,
message: action.message, message: action.message,
@ -212,8 +210,6 @@ StateListenerRegistry.register(
* @returns {void} * @returns {void}
*/ */
function _addChatMsgListener(conference, store) { function _addChatMsgListener(conference, store) {
const reactions = {};
if (store.getState()['features/base/config'].iAmRecorder) { if (store.getState()['features/base/config'].iAmRecorder) {
// We don't register anything on web if we are in iAmRecorder mode // We don't register anything on web if we are in iAmRecorder mode
return; return;
@ -252,30 +248,21 @@ function _addChatMsgListener(conference, store) {
const [ { _id }, eventData ] = args; const [ { _id }, eventData ] = args;
if (eventData.name === ENDPOINT_REACTION_NAME) { if (eventData.name === ENDPOINT_REACTION_NAME) {
reactions[_id] = reactions[_id] ?? {
timeout: null,
message: ''
};
batch(() => { batch(() => {
store.dispatch(pushReaction(eventData.reaction));
store.dispatch(setToolboxVisible(true)); store.dispatch(setToolboxVisible(true));
store.dispatch(setToolboxTimeout( store.dispatch(setToolboxTimeout(
() => store.dispatch(hideToolbox()), () => store.dispatch(hideToolbox()),
5000) 5000)
); );
store.dispatch(pushReactions(eventData.reactions));
}); });
clearTimeout(reactions[_id].timeout); _handleReceivedMessage(store, {
reactions[_id].message = `${reactions[_id].message}${REACTIONS[eventData.reaction].message}`; id: _id,
reactions[_id].timeout = setTimeout(() => { message: getReactionMessageFromBuffer(eventData.reactions),
_handleReceivedMessage(store, { privateMessage: false,
id: _id, timestamp: eventData.timestamp
message: reactions[_id].message, }, false);
privateMessage: false,
timestamp: eventData.timestamp
}, false);
delete reactions[_id];
}, 500);
} }
} }
}); });

View File

@ -13,21 +13,31 @@ export const TOGGLE_REACTIONS_VISIBLE = 'TOGGLE_REACTIONS_VISIBLE';
* a new timeout. * a new timeout.
* *
* { * {
* type: SET_REACTION_MESSAGE, * type: ADD_REACTION_BUFFER,
* message: string, * message: string,
* timeoutID: number * timeoutID: number
* } * }
*/ */
export const SET_REACTIONS_MESSAGE = 'SET_REACTIONS_MESSAGE'; export const ADD_REACTION_BUFFER = 'ADD_REACTION_BUFFER';
/** /**
* The type of the action which resets the reactions message and timeout. * The type of the action which sends the reaction buffer and resets it.
* *
* { * {
* type: CLEAR_REACTION_MESSAGE * type: FLUSH_REACTION_BUFFER
* } * }
*/ */
export const CLEAR_REACTIONS_MESSAGE = 'CLEAR_REACTIONS_MESSAGE'; export const FLUSH_REACTION_BUFFER = 'FLUSH_REACTION_BUFFER';
/**
* The type of the action which adds a new reaction message to the chat.
*
* {
* type: ADD_REACTION_MESSAGE,
* message: string,
* }
*/
export const ADD_REACTION_MESSAGE = 'ADD_REACTION_MESSAGE';
/** /**
* The type of the action which sets the reactions queue. * The type of the action which sets the reactions queue.
@ -42,14 +52,9 @@ export const SET_REACTION_QUEUE = 'SET_REACTION_QUEUE';
/** /**
* The type of the action which signals a send reaction to everyone in the conference. * The type of the action which signals a send reaction to everyone in the conference.
*/ */
export const SEND_REACTION = 'SEND_REACTION'; export const SEND_REACTIONS = 'SEND_REACTIONS';
/** /**
* The type of the action to add a reaction message to the chat. * The type of action to adds reactions to the queue.
*/ */
export const ADD_REACTIONS_MESSAGE = 'ADD_REACTIONS_MESSAGE'; export const PUSH_REACTIONS = 'PUSH_REACTIONS';
/**
* The type of action to add a reaction to the queue.
*/
export const PUSH_REACTION = 'PUSH_REACTION';

View File

@ -1,11 +1,11 @@
// @flow // @flow
import { import {
ADD_REACTIONS_MESSAGE, ADD_REACTION_BUFFER,
CLEAR_REACTIONS_MESSAGE, ADD_REACTION_MESSAGE,
PUSH_REACTION, FLUSH_REACTION_BUFFER,
SEND_REACTION, PUSH_REACTIONS,
SET_REACTIONS_MESSAGE, SEND_REACTIONS,
SET_REACTION_QUEUE SET_REACTION_QUEUE
} from './actionTypes'; } from './actionTypes';
import { type ReactionEmojiProps } from './constants'; import { type ReactionEmojiProps } from './constants';
@ -23,42 +23,6 @@ export function setReactionQueue(value: Array<ReactionEmojiProps>) {
}; };
} }
/**
* Appends the reactions message to the chat and resets the state.
*
* @returns {void}
*/
export function flushReactionsToChat() {
return {
type: CLEAR_REACTIONS_MESSAGE
};
}
/**
* Adds a new reaction to the reactions message.
*
* @param {boolean} value - The new reaction.
* @returns {Object}
*/
export function addReactionsMessage(value: string) {
return {
type: SET_REACTIONS_MESSAGE,
reaction: value
};
}
/**
* Adds a new reaction to the reactions message.
*
* @param {boolean} value - Reaction to be added to queue.
* @returns {Object}
*/
export function pushReaction(value: string) {
return {
type: PUSH_REACTION,
reaction: value
};
}
/** /**
* Removes a reaction from the queue. * Removes a reaction from the queue.
@ -76,33 +40,75 @@ export function removeReaction(uid: number) {
/** /**
* Sends a reaction message to everyone in the conference. * Sends the reactions buffer to everyone in the conference.
* *
* @param {string} reaction - The reaction to send out.
* @returns {{ * @returns {{
* type: SEND_REACTION, * type: SEND_REACTION
* }}
*/
export function sendReactions() {
return {
type: SEND_REACTIONS
};
}
/**
* Adds a reaction to the local buffer.
*
* @param {string} reaction - The reaction to be added.
* @returns {{
* type: ADD_REACTION_BUFFER,
* reaction: string * reaction: string
* }} * }}
*/ */
export function sendReaction(reaction: string) { export function addReactionToBuffer(reaction: string) {
return { return {
type: SEND_REACTION, type: ADD_REACTION_BUFFER,
reaction reaction
}; };
} }
/** /**
* Adds a reactions message to the chat. * Clears the reaction buffer.
* *
* @param {string} message - The reactions message to add to chat.
* @returns {{ * @returns {{
* type: ADD_REACTIONS_MESSAGE, * type: FLUSH_REACTION_BUFFER
* }}
*/
export function flushReactionBuffer() {
return {
type: FLUSH_REACTION_BUFFER
};
}
/**
* Adds a reaction message to the chat.
*
* @param {string} message - The reaction message.
* @returns {{
* type: ADD_REACTION_MESSAGE,
* message: string * message: string
* }} * }}
*/ */
export function addReactionsMessageToChat(message: string) { export function addReactionsToChat(message: string) {
return { return {
type: ADD_REACTIONS_MESSAGE, type: ADD_REACTION_MESSAGE,
message message
}; };
} }
/**
* Adds reactions to the animation queue.
*
* @param {Array} reactions - The reactions to be animated.
* @returns {{
* type: PUSH_REACTIONS,
* reactions: Array
* }}
*/
export function pushReactions(reactions: Array<string>) {
return {
type: PUSH_REACTIONS,
reactions
};
}

View File

@ -4,9 +4,10 @@ import React from 'react';
import { Text, TouchableHighlight } from 'react-native'; import { Text, TouchableHighlight } from 'react-native';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { createReactionMenuEvent, sendAnalytics } from '../../../analytics';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import type { StyleType } from '../../../base/styles'; import type { StyleType } from '../../../base/styles';
import { sendReaction } from '../../actions.any'; import { addReactionToBuffer } from '../../actions.any';
import { REACTIONS } from '../../constants'; import { REACTIONS } from '../../constants';
@ -78,7 +79,8 @@ function ReactionButton({
* @returns {void} * @returns {void}
*/ */
function _onClick() { function _onClick() {
dispatch(sendReaction(reaction)); dispatch(addReactionToBuffer(reaction));
sendAnalytics(createReactionMenuEvent(reaction));
} }
return ( return (

View File

@ -4,6 +4,7 @@ import React, { Component } from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { import {
createReactionMenuEvent,
createToolbarEvent, createToolbarEvent,
sendAnalytics sendAnalytics
} from '../../../analytics'; } from '../../../analytics';
@ -11,7 +12,7 @@ import { translate } from '../../../base/i18n';
import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants'; import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { dockToolbox } from '../../../toolbox/actions.web'; import { dockToolbox } from '../../../toolbox/actions.web';
import { sendReaction } from '../../actions.any'; import { addReactionToBuffer } from '../../actions.any';
import { toggleReactionsMenuVisibility } from '../../actions.web'; import { toggleReactionsMenuVisibility } from '../../actions.web';
import { REACTIONS } from '../../constants'; import { REACTIONS } from '../../constants';
@ -144,6 +145,13 @@ class ReactionsMenu extends Component<Props> {
*/ */
_getReactionButtons() { _getReactionButtons() {
const { t, dispatch } = this.props; const { t, dispatch } = this.props;
let modifierKey = 'Alt';
if (window.navigator?.platform) {
if (window.navigator.platform.indexOf('Mac') !== -1) {
modifierKey = '⌥';
}
}
return Object.keys(REACTIONS).map(key => { return Object.keys(REACTIONS).map(key => {
/** /**
@ -151,17 +159,18 @@ class ReactionsMenu extends Component<Props> {
* *
* @returns {void} * @returns {void}
*/ */
function sendMessage() { function doSendReaction() {
dispatch(sendReaction(key)); dispatch(addReactionToBuffer(key));
sendAnalytics(createReactionMenuEvent(key));
} }
return (<ReactionButton return (<ReactionButton
accessibilityLabel = { t(`toolbar.accessibilityLabel.${key}`) } accessibilityLabel = { t(`toolbar.accessibilityLabel.${key}`) }
icon = { REACTIONS[key].emoji } icon = { REACTIONS[key].emoji }
key = { key } key = { key }
onClick = { sendMessage } onClick = { doSendReaction }
toggled = { false } toggled = { false }
tooltip = { t(`toolbar.${key}`) } />); tooltip = { `${t(`toolbar.${key}`)} (${modifierKey} + ${REACTIONS[key].shortcutChar})` } />);
}); });
} }

View File

@ -1,15 +1,14 @@
// @flow // @flow
import React, { useEffect } from 'react'; import React from 'react';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { IconRaisedHand } from '../../../base/icons'; import { IconRaisedHand } from '../../../base/icons';
import { getLocalParticipant } from '../../../base/participants'; import { getLocalParticipant } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import ToolbarButton from '../../../toolbox/components/web/ToolbarButton'; import ToolbarButton from '../../../toolbox/components/web/ToolbarButton';
import { sendReaction } from '../../actions.any';
import { toggleReactionsMenuVisibility } from '../../actions.web'; import { toggleReactionsMenuVisibility } from '../../actions.web';
import { REACTIONS, type ReactionEmojiProps } from '../../constants'; import { type ReactionEmojiProps } from '../../constants';
import { getReactionsQueue } from '../../functions.any'; import { getReactionsQueue } from '../../functions.any';
import { getReactionsMenuVisibility } from '../../functions.web'; import { getReactionsMenuVisibility } from '../../functions.web';
@ -65,32 +64,6 @@ function ReactionsMenuButton({
dispatch dispatch
}: Props) { }: Props) {
useEffect(() => {
const KEYBOARD_SHORTCUTS = Object.keys(REACTIONS).map(key => {
return {
character: REACTIONS[key].shortcutChar,
exec: () => dispatch(sendReaction(key)),
helpDescription: t(`toolbar.reaction${key.charAt(0).toUpperCase()}${key.slice(1)}`),
altKey: true
};
});
KEYBOARD_SHORTCUTS.forEach(shortcut => {
APP.keyboardshortcut.registerShortcut(
shortcut.character,
null,
shortcut.exec,
shortcut.helpDescription,
shortcut.altKey);
});
return () => {
Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
.forEach(letter =>
APP.keyboardshortcut.unregisterShortcut(letter, true));
};
}, []);
/** /**
* Toggles the reactions menu visibility. * Toggles the reactions menu visibility.
* *

View File

@ -11,7 +11,7 @@ export const REACTIONS = {
emoji: '👏', emoji: '👏',
shortcutChar: 'C' shortcutChar: 'C'
}, },
joy: { laugh: {
message: ':grinning_face:', message: ':grinning_face:',
emoji: '😀', emoji: '😀',
shortcutChar: 'L' shortcutChar: 'L'

View File

@ -1,5 +1,7 @@
// @flow // @flow
import uuid from 'uuid';
import { getLocalParticipant } from '../base/participants'; import { getLocalParticipant } from '../base/participants';
import { extractFqnFromPath } from '../dynamic-branding/functions'; import { extractFqnFromPath } from '../dynamic-branding/functions';
@ -17,28 +19,28 @@ export function getReactionsQueue(state: Object) {
} }
/** /**
* Returns reaction key from the reaction message. * Returns chat message from reactions buffer.
* *
* @param {string} message - The reaction message. * @param {Array} buffer - The reactions buffer.
* @returns {string} * @returns {string}
*/ */
export function getReactionKeyByMessage(message: string): ?string { export function getReactionMessageFromBuffer(buffer: Array<string>) {
return Object.keys(REACTIONS).find(key => REACTIONS[key].message === `:${message}:`); return buffer.map(reaction => REACTIONS[reaction].message).reduce((acc, val) => `${acc}${val}`);
} }
/** /**
* Gets reactions key array from concatenated message. * Returns reactions array with uid.
* *
* @param {string} message - The reaction message. * @param {Array} buffer - The reactions buffer.
* @returns {Array} * @returns {Array}
*/ */
export function messageToKeyArray(message: string) { export function getReactionsWithId(buffer: Array<string>) {
let formattedMessage = message.replace(/::/g, '-'); return buffer.map<Object>(reaction => {
return {
formattedMessage = formattedMessage.replace(/:/g, ''); reaction,
const messageArray = formattedMessage.split('-'); uid: uuid.v4()
};
return messageArray.map<?string>(getReactionKeyByMessage); });
} }
/** /**

View File

@ -1,24 +1,25 @@
// @flow // @flow
import { batch } from 'react-redux';
import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants'; import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import { isVpaasMeeting } from '../jaas/functions'; import { isVpaasMeeting } from '../jaas/functions';
import { import {
SET_REACTIONS_MESSAGE, ADD_REACTION_BUFFER,
CLEAR_REACTIONS_MESSAGE, FLUSH_REACTION_BUFFER,
SEND_REACTION, SEND_REACTIONS,
PUSH_REACTION PUSH_REACTIONS
} from './actionTypes'; } from './actionTypes';
import { import {
addReactionsMessage, addReactionsToChat,
addReactionsMessageToChat, flushReactionBuffer,
flushReactionsToChat, pushReactions,
pushReaction, sendReactions,
setReactionQueue setReactionQueue
} from './actions.any'; } from './actions.any';
import { REACTIONS } from './constants'; import { getReactionMessageFromBuffer, getReactionsWithId, sendReactionsWebhook } from './functions.any';
import { messageToKeyArray, sendReactionsWebhook } from './functions.any';
declare var APP: Object; declare var APP: Object;
@ -34,56 +35,57 @@ MiddlewareRegistry.register(store => next => action => {
const { dispatch, getState } = store; const { dispatch, getState } = store;
switch (action.type) { switch (action.type) {
case SET_REACTIONS_MESSAGE: { case ADD_REACTION_BUFFER: {
const { timeoutID, message } = getState()['features/reactions']; const { timeoutID, buffer } = getState()['features/reactions'];
const { reaction } = action; const { reaction } = action;
clearTimeout(timeoutID); clearTimeout(timeoutID);
action.message = `${message}${reaction}`; buffer.push(reaction);
action.buffer = buffer;
action.timeoutID = setTimeout(() => { action.timeoutID = setTimeout(() => {
dispatch(flushReactionsToChat()); dispatch(flushReactionBuffer());
}, 500); }, 500);
break; break;
} }
case CLEAR_REACTIONS_MESSAGE: { case FLUSH_REACTION_BUFFER: {
const state = getState(); const state = getState();
const { message } = state['features/reactions']; const { buffer } = state['features/reactions'];
batch(() => {
dispatch(sendReactions());
dispatch(addReactionsToChat(getReactionMessageFromBuffer(buffer)));
dispatch(pushReactions(buffer));
});
if (isVpaasMeeting(state)) { if (isVpaasMeeting(state)) {
sendReactionsWebhook(state, messageToKeyArray(message)); sendReactionsWebhook(state, buffer);
} }
dispatch(addReactionsMessageToChat(message));
break; break;
} }
case SEND_REACTION: { case SEND_REACTIONS: {
const state = store.getState(); const state = getState();
const { buffer } = state['features/reactions'];
const { conference } = state['features/base/conference']; const { conference } = state['features/base/conference'];
if (conference) { if (conference) {
conference.sendEndpointMessage('', { conference.sendEndpointMessage('', {
name: ENDPOINT_REACTION_NAME, name: ENDPOINT_REACTION_NAME,
reaction: action.reaction, reactions: buffer,
timestamp: Date.now() timestamp: Date.now()
}); });
dispatch(addReactionsMessage(REACTIONS[action.reaction].message));
dispatch(pushReaction(action.reaction));
} }
break; break;
} }
case PUSH_REACTION: { case PUSH_REACTIONS: {
const queue = store.getState()['features/reactions'].queue; const queue = store.getState()['features/reactions'].queue;
const reaction = action.reaction; const reactions = action.reactions;
dispatch(setReactionQueue([ ...queue, { dispatch(setReactionQueue([ ...queue, ...getReactionsWithId(reactions) ]));
reaction,
uid: window.Date.now()
} ]));
} }
} }

View File

@ -4,9 +4,9 @@ import { ReducerRegistry } from '../base/redux';
import { import {
TOGGLE_REACTIONS_VISIBLE, TOGGLE_REACTIONS_VISIBLE,
SET_REACTIONS_MESSAGE, SET_REACTION_QUEUE,
CLEAR_REACTIONS_MESSAGE, ADD_REACTION_BUFFER,
SET_REACTION_QUEUE FLUSH_REACTION_BUFFER
} from './actionTypes'; } from './actionTypes';
/** /**
@ -30,11 +30,11 @@ function _getInitialState() {
visible: false, visible: false,
/** /**
* A string that contains the message to be added to the chat. * An array that contains the reactions buffer to be sent.
* *
* @type {string} * @type {Array}
*/ */
message: '', buffer: [],
/** /**
* A number, non-zero value which identifies the timer created by a call * A number, non-zero value which identifies the timer created by a call
@ -64,17 +64,17 @@ ReducerRegistry.register(
visible: !state.visible visible: !state.visible
}; };
case SET_REACTIONS_MESSAGE: case ADD_REACTION_BUFFER:
return { return {
...state, ...state,
message: action.message, buffer: action.buffer,
timeoutID: action.timeoutID timeoutID: action.timeoutID
}; };
case CLEAR_REACTIONS_MESSAGE: case FLUSH_REACTION_BUFFER:
return { return {
...state, ...state,
message: '', buffer: [],
timeoutID: null timeoutID: null
}; };

View File

@ -36,7 +36,9 @@ import {
} from '../../../participants-pane/actions'; } from '../../../participants-pane/actions';
import ParticipantsPaneButton from '../../../participants-pane/components/ParticipantsPaneButton'; import ParticipantsPaneButton from '../../../participants-pane/components/ParticipantsPaneButton';
import { getParticipantsPaneOpen } from '../../../participants-pane/functions'; import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
import { addReactionToBuffer } from '../../../reactions/actions.any';
import { ReactionsMenuButton } from '../../../reactions/components'; import { ReactionsMenuButton } from '../../../reactions/components';
import { REACTIONS } from '../../../reactions/constants';
import { import {
LiveStreamButton, LiveStreamButton,
RecordButton RecordButton
@ -268,7 +270,7 @@ class Toolbox extends Component<Props> {
* @returns {void} * @returns {void}
*/ */
componentDidMount() { componentDidMount() {
const { _toolbarButtons } = this.props; const { _toolbarButtons, t, dispatch } = this.props;
const KEYBOARD_SHORTCUTS = [ const KEYBOARD_SHORTCUTS = [
isToolbarButtonEnabled('videoquality', _toolbarButtons) && { isToolbarButtonEnabled('videoquality', _toolbarButtons) && {
character: 'A', character: 'A',
@ -316,6 +318,31 @@ class Toolbox extends Component<Props> {
shortcut.helpDescription); shortcut.helpDescription);
} }
}); });
const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => {
const onShortcutSendReaction = () => {
dispatch(addReactionToBuffer(key));
sendAnalytics(createShortcutEvent(
`reaction.${key}`
));
};
return {
character: REACTIONS[key].shortcutChar,
exec: onShortcutSendReaction,
helpDescription: t(`toolbar.reaction${key.charAt(0).toUpperCase()}${key.slice(1)}`),
altKey: true
};
});
REACTION_SHORTCUTS.forEach(shortcut => {
APP.keyboardshortcut.registerShortcut(
shortcut.character,
null,
shortcut.exec,
shortcut.helpDescription,
shortcut.altKey);
});
} }
/** /**
@ -346,6 +373,10 @@ class Toolbox extends Component<Props> {
componentWillUnmount() { componentWillUnmount() {
[ 'A', 'C', 'D', 'R', 'S' ].forEach(letter => [ 'A', 'C', 'D', 'R', 'S' ].forEach(letter =>
APP.keyboardshortcut.unregisterShortcut(letter)); APP.keyboardshortcut.unregisterShortcut(letter));
Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
.forEach(letter =>
APP.keyboardshortcut.unregisterShortcut(letter, true));
} }
/** /**