fix(reactions) Reactions improvements (#9964)

* Register shortcuts on mount

* Changed icon for reactions menu

* Enable reactions by default

* Fix unreadCount bug

When having unread messages and sending a reaction the unread count now shows the correct count

* Fix overflow menu bottom color when reactions are enabled

* Revert raise hand icon

* Update raise hand functionality

On desktop show raise button with arrow for reactions. Only show raise hand in the reactions menu on mobile

* Fix lint error

Add required prop to ToolboxButtonWithIcon

* Legacy support for enable reactions

If disableReactions is undefined treat it as true

* Remove unnecessary code

* Fix unread counter showing negative count

* Fix unreadCount with reactions

UnreadCount ignores all reactions messages

* Fixed typo

* Fix background color
This commit is contained in:
robertpin 2021-09-21 20:30:24 +03:00 committed by GitHub
parent 5f5cac0e01
commit 584ec7c82e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 154 additions and 208 deletions

View File

@ -77,8 +77,8 @@ var config = {
// Disables moderator indicators. // Disables moderator indicators.
// disableModeratorIndicator: false, // disableModeratorIndicator: false,
// Enables reactions feature. // Disables the reactions feature.
// enableReactions: false, // disableReactions: true,
// Disables polls feature. // Disables polls feature.
// disablePolls: false, // disablePolls: false,

View File

@ -2,7 +2,7 @@
.reactions-menu { .reactions-menu {
width: 280px; width: 280px;
background: #292929; background: $menuBG;
box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25); box-shadow: 0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25);
border-radius: 3px; border-radius: 3px;
padding: 16px; padding: 16px;

View File

@ -100,6 +100,7 @@ export default [
'disableNS', 'disableNS',
'disablePolls', 'disablePolls',
'disableProfile', 'disableProfile',
'disableReactions',
'disableRecordAudioNotification', 'disableRecordAudioNotification',
'disableRemoteControl', 'disableRemoteControl',
'disableRemoteMute', 'disableRemoteMute',
@ -127,7 +128,6 @@ export default [
'enableLayerSuspension', 'enableLayerSuspension',
'enableLipSync', 'enableLipSync',
'enableOpusRed', 'enableOpusRed',
'enableReactions',
'enableRemb', 'enableRemb',
'enableSaveLogs', 'enableSaveLogs',
'enableScreenshotCapture', 'enableScreenshotCapture',

View File

@ -145,6 +145,7 @@ class BottomSheet extends PureComponent<Props> {
renderHeader renderHeader
? _styles.sheetHeader ? _styles.sheetHeader
: _styles.sheet, : _styles.sheet,
renderFooter && _styles.sheetFooter,
style, style,
{ {
maxHeight: _height - 100 maxHeight: _height - 100
@ -154,7 +155,10 @@ class BottomSheet extends PureComponent<Props> {
<ScrollView <ScrollView
bounces = { false } bounces = { false }
showsVerticalScrollIndicator = { false } showsVerticalScrollIndicator = { false }
style = { addScrollViewPadding && styles.scrollView } > style = { [
renderFooter && _styles.sheet,
addScrollViewPadding && styles.scrollView
] } >
{ this.props.children } { this.props.children }
</ScrollView> </ScrollView>
{ renderFooter && renderFooter() } { renderFooter && renderFooter() }

View File

@ -213,6 +213,13 @@ ColorSchemeRegistry.register('BottomSheet', {
*/ */
sheetHeader: { sheetHeader: {
backgroundColor: BaseTheme.palette.ui02 backgroundColor: BaseTheme.palette.ui02
},
/**
* Bottom sheet's background color with footer.
*/
sheetFooter: {
backgroundColor: BaseTheme.palette.bottomSheet
} }
}); });

View File

@ -22,6 +22,8 @@ import {
* "error" or "local" or "remote". * "error" or "local" or "remote".
* @param {string} messageDetails.timestamp - A timestamp to display for when * @param {string} messageDetails.timestamp - A timestamp to display for when
* the message was received. * the message was received.
* @param {string} messageDetails.isReaction - Whether or not the
* message is a reaction message.
* @returns {{ * @returns {{
* type: ADD_MESSAGE, * type: ADD_MESSAGE,
* displayName: string, * displayName: string,
@ -29,6 +31,7 @@ import {
* message: string, * message: string,
* messageType: string, * messageType: string,
* timestamp: string, * timestamp: string,
* isReaction: boolean
* }} * }}
*/ */
export function addMessage(messageDetails: Object) { export function addMessage(messageDetails: Object) {

View File

@ -69,14 +69,30 @@ export function getUnreadCount(state: Object) {
return 0; return 0;
} }
let reactionMessages = 0;
if (navigator.product === 'ReactNative') { if (navigator.product === 'ReactNative') {
// React native stores the messages in a reversed order. // React native stores the messages in a reversed order.
return messages.indexOf(lastReadMessage); const lastReadIndex = messages.indexOf(lastReadMessage);
for (let i = 0; i < lastReadIndex; i++) {
if (messages[i].isReaction) {
reactionMessages++;
}
}
return lastReadIndex - reactionMessages;
} }
const lastReadIndex = messages.lastIndexOf(lastReadMessage); const lastReadIndex = messages.lastIndexOf(lastReadMessage);
return messagesCount - (lastReadIndex + 1); for (let i = lastReadIndex + 1; i < messagesCount; i++) {
if (messages[i].isReaction) {
reactionMessages++;
}
}
return messagesCount - (lastReadIndex + 1) - reactionMessages;
} }
/** /**

View File

@ -68,7 +68,12 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) { switch (action.type) {
case ADD_MESSAGE: case ADD_MESSAGE:
unreadCount = action.hasRead ? 0 : getUnreadCount(getState()) + 1; unreadCount = getUnreadCount(getState());
if (action.isReaction) {
action.hasRead = false;
} else {
unreadCount = action.hasRead ? 0 : unreadCount + 1;
}
isOpen = getState()['features/chat'].isOpen; isOpen = getState()['features/chat'].isOpen;
if (typeof APP !== 'undefined') { if (typeof APP !== 'undefined') {
@ -171,7 +176,7 @@ MiddlewareRegistry.register(store => next => action => {
message: action.message, message: action.message,
privateMessage: false, privateMessage: false,
timestamp: Date.now() timestamp: Date.now()
}, false); }, false, true);
} }
} }
@ -270,7 +275,7 @@ function _addChatMsgListener(conference, store) {
message: getReactionMessageFromBuffer(eventData.reactions), message: getReactionMessageFromBuffer(eventData.reactions),
privateMessage: false, privateMessage: false,
timestamp: eventData.timestamp timestamp: eventData.timestamp
}, false); }, false, true);
} }
} }
}); });
@ -304,11 +309,13 @@ function _handleChatError({ dispatch }, error) {
* @param {Store} store - The Redux store. * @param {Store} store - The Redux store.
* @param {Object} message - The message object. * @param {Object} message - The message object.
* @param {boolean} shouldPlaySound - Whether or not to play the incoming message sound. * @param {boolean} shouldPlaySound - Whether or not to play the incoming message sound.
* @param {boolean} isReaction - Whether or not the message is a reaction message.
* @returns {void} * @returns {void}
*/ */
function _handleReceivedMessage({ dispatch, getState }, function _handleReceivedMessage({ dispatch, getState },
{ id, message, privateMessage, timestamp }, { id, message, privateMessage, timestamp },
shouldPlaySound = true shouldPlaySound = true,
isReaction = false
) { ) {
// Logic for all platforms: // Logic for all platforms:
const state = getState(); const state = getState();
@ -337,7 +344,8 @@ function _handleReceivedMessage({ dispatch, getState },
message, message,
privateMessage, privateMessage,
recipient: getParticipantDisplayName(state, localParticipant.id), recipient: getParticipantDisplayName(state, localParticipant.id),
timestamp: millisecondsTimestamp timestamp: millisecondsTimestamp,
isReaction
})); }));
if (typeof APP !== 'undefined') { if (typeof APP !== 'undefined') {

View File

@ -28,6 +28,7 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
displayName: action.displayName, displayName: action.displayName,
error: action.error, error: action.error,
id: action.id, id: action.id,
isReaction: action.isReaction,
messageType: action.messageType, messageType: action.messageType,
message: action.message, message: action.message,
privateMessage: action.privateMessage, privateMessage: action.privateMessage,

View File

@ -8,6 +8,7 @@ import {
createToolbarEvent, createToolbarEvent,
sendAnalytics sendAnalytics
} from '../../../analytics'; } from '../../../analytics';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { getLocalParticipant, participantUpdated } from '../../../base/participants'; import { getLocalParticipant, participantUpdated } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
@ -21,34 +22,39 @@ import ReactionButton from './ReactionButton';
type Props = { type Props = {
/** /**
* Used for translation. * Docks the toolbox
*/ */
t: Function, _dockToolbox: Function,
/** /**
* Whether or not the local participant's hand is raised. * Whether or not it's a mobile browser.
*/ */
_raisedHand: boolean, _isMobile: boolean,
/** /**
* The ID of the local participant. * The ID of the local participant.
*/ */
_localParticipantID: String, _localParticipantID: String,
/**
* Whether or not the local participant's hand is raised.
*/
_raisedHand: boolean,
/** /**
* The Redux Dispatch function. * The Redux Dispatch function.
*/ */
dispatch: Function, dispatch: Function,
/**
* Docks the toolbox
*/
_dockToolbox: Function,
/** /**
* Whether or not it's displayed in the overflow menu. * Whether or not it's displayed in the overflow menu.
*/ */
overflowMenu: boolean overflowMenu: boolean,
/**
* Used for translation.
*/
t: Function
}; };
declare var APP: Object; declare var APP: Object;
@ -177,25 +183,27 @@ class ReactionsMenu extends Component<Props> {
* @inheritdoc * @inheritdoc
*/ */
render() { render() {
const { _raisedHand, t, overflowMenu } = this.props; const { _raisedHand, t, overflowMenu, _isMobile } = this.props;
return ( return (
<div className = { `reactions-menu ${overflowMenu ? 'overflow' : ''}` }> <div className = { `reactions-menu ${overflowMenu ? 'overflow' : ''}` }>
<div className = 'reactions-row'> <div className = 'reactions-row'>
{ this._getReactionButtons() } { this._getReactionButtons() }
</div> </div>
<div className = 'raise-hand-row'> {_isMobile && (
<ReactionButton <div className = 'raise-hand-row'>
accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') } <ReactionButton
icon = '✋' accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') }
key = 'raisehand' icon = '✋'
label = { key = 'raisehand'
`${t(`toolbar.${_raisedHand ? 'lowerYourHand' : 'raiseYourHand'}`)} label = {
${overflowMenu ? '' : ' (R)'}` `${t(`toolbar.${_raisedHand ? 'lowerYourHand' : 'raiseYourHand'}`)}
} ${overflowMenu ? '' : ' (R)'}`
onClick = { this._onToolbarToggleRaiseHand } }
toggled = { true } /> onClick = { this._onToolbarToggleRaiseHand }
</div> toggled = { true } />
</div>
)}
</div> </div>
); );
} }
@ -212,6 +220,7 @@ function mapStateToProps(state) {
return { return {
_localParticipantID: localParticipant.id, _localParticipantID: localParticipant.id,
_isMobile: isMobileBrowser(),
_raisedHand: localParticipant.raisedHand _raisedHand: localParticipant.raisedHand
}; };
} }

View File

@ -2,14 +2,16 @@
import React from 'react'; import React from 'react';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { IconRaisedHand } from '../../../base/icons'; import { IconArrowUp, 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 { ToolboxButtonWithIcon } from '../../../base/toolbox/components';
import ToolbarButton from '../../../toolbox/components/web/ToolbarButton'; import ToolbarButton from '../../../toolbox/components/web/ToolbarButton';
import { toggleReactionsMenuVisibility } from '../../actions.web'; import { toggleReactionsMenuVisibility } from '../../actions.web';
import { type ReactionEmojiProps } from '../../constants'; import { type ReactionEmojiProps } from '../../constants';
import { getReactionsQueue } from '../../functions.any'; import { getReactionsQueue, isReactionsEnabled } from '../../functions.any';
import { getReactionsMenuVisibility } from '../../functions.web'; import { getReactionsMenuVisibility } from '../../functions.web';
import ReactionEmoji from './ReactionEmoji'; import ReactionEmoji from './ReactionEmoji';
@ -18,34 +20,44 @@ import ReactionsMenuPopup from './ReactionsMenuPopup';
type Props = { type Props = {
/** /**
* Used for translation. * Whether or not reactions are enabled.
*/ */
t: Function, _reactionsEnabled: Boolean,
/** /**
* Whether or not the local participant's hand is raised. * Redux dispatch function.
*/ */
raisedHand: boolean, dispatch: Function,
/** /**
* Click handler for the reaction button. Toggles the reactions menu. * Click handler for raise hand functionality.
*/ */
onReactionsClick: Function, handleClick: Function,
/** /**
* Whether or not the reactions menu is open. * Whether or not the reactions menu is open.
*/ */
isOpen: boolean, isOpen: boolean,
/**
* Whether or not it's a mobile browser.
*/
isMobile: boolean,
/**
* Whether or not the local participant's hand is raised.
*/
raisedHand: boolean,
/** /**
* The array of reactions to be displayed. * The array of reactions to be displayed.
*/ */
reactionsQueue: Array<ReactionEmojiProps>, reactionsQueue: Array<ReactionEmojiProps>,
/** /**
* Redux dispatch function. * Used for translation.
*/ */
dispatch: Function t: Function
}; };
@ -57,11 +69,14 @@ declare var APP: Object;
* @returns {ReactElement} * @returns {ReactElement}
*/ */
function ReactionsMenuButton({ function ReactionsMenuButton({
t, _reactionsEnabled,
raisedHand, dispatch,
handleClick,
isOpen, isOpen,
isMobile,
raisedHand,
reactionsQueue, reactionsQueue,
dispatch t
}: Props) { }: Props) {
/** /**
@ -73,16 +88,32 @@ function ReactionsMenuButton({
dispatch(toggleReactionsMenuVisibility()); dispatch(toggleReactionsMenuVisibility());
} }
const raiseHandButton = (<ToolbarButton
accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') }
icon = { IconRaisedHand }
key = 'raise-hand'
onClick = { handleClick }
toggled = { raisedHand }
tooltip = { t('toolbar.raiseHand') } />);
return ( return (
<div className = 'reactions-menu-popup-container'> <div className = 'reactions-menu-popup-container'>
<ReactionsMenuPopup> <ReactionsMenuPopup>
<ToolbarButton {!_reactionsEnabled || isMobile ? raiseHandButton
accessibilityLabel = { t('toolbar.accessibilityLabel.reactionsMenu') } : (
icon = { IconRaisedHand } <ToolboxButtonWithIcon
key = 'reactions' ariaControls = 'reactions-menu-dialog'
onClick = { toggleReactionsMenu } ariaExpanded = { isOpen }
toggled = { raisedHand } ariaHasPopup = { true }
tooltip = { t(`toolbar.${isOpen ? 'closeReactionsMenu' : 'openReactionsMenu'}`) } /> ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
icon = { IconArrowUp }
iconDisabled = { false }
iconId = 'reactions-menu-button'
iconTooltip = { t(`toolbar.${isOpen ? 'closeReactionsMenu' : 'openReactionsMenu'}`) }
onIconClick = { toggleReactionsMenu }>
{raiseHandButton}
</ToolboxButtonWithIcon>
)}
</ReactionsMenuPopup> </ReactionsMenuPopup>
{reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji {reactionsQueue.map(({ reaction, uid }, index) => (<ReactionEmoji
index = { index } index = { index }
@ -103,7 +134,9 @@ function mapStateToProps(state) {
const localParticipant = getLocalParticipant(state); const localParticipant = getLocalParticipant(state);
return { return {
_reactionsEnabled: isReactionsEnabled(state),
isOpen: getReactionsMenuVisibility(state), isOpen: getReactionsMenuVisibility(state),
isMobile: isMobileBrowser(),
reactionsQueue: getReactionsQueue(state), reactionsQueue: getReactionsQueue(state),
raisedHand: localParticipant?.raisedHand raisedHand: localParticipant?.raisedHand
}; };

View File

@ -151,11 +151,11 @@ export function getReactionsSoundsThresholds(reactions: Array<string>) {
* @returns {boolean} * @returns {boolean}
*/ */
export function isReactionsEnabled(state: Object) { export function isReactionsEnabled(state: Object) {
const { enableReactions } = state['features/base/config']; const { disableReactions } = state['features/base/config'];
if (navigator.product === 'ReactNative') { if (navigator.product === 'ReactNative') {
return enableReactions && getFeatureFlag(state, REACTIONS_ENABLED, true); return !disableReactions && getFeatureFlag(state, REACTIONS_ENABLED, true);
} }
return enableReactions; return !disableReactions;
} }

View File

@ -15,6 +15,11 @@ declare var APP: Object;
export type Props = { export type Props = {
...$Exact<AbstractDialogTabProps>, ...$Exact<AbstractDialogTabProps>,
/**
* Whether or not the reactions feature is enabled.
*/
enableReactions: Boolean,
/** /**
* Whether or not the sound for the incoming message should play. * Whether or not the sound for the incoming message should play.
*/ */
@ -40,11 +45,6 @@ export type Props = {
*/ */
soundsReactions: Boolean, soundsReactions: Boolean,
/**
* Whether or not the reactions feature is enabled.
*/
enableReactions: Boolean,
/** /**
* Invoked to obtain translated strings. * Invoked to obtain translated strings.
*/ */

View File

@ -10,6 +10,7 @@ import {
import { toState } from '../base/redux'; import { toState } from '../base/redux';
import { parseStandardURIString } from '../base/util'; import { parseStandardURIString } from '../base/util';
import { isFollowMeActive } from '../follow-me'; import { isFollowMeActive } from '../follow-me';
import { isReactionsEnabled } from '../reactions/functions.any';
import { SS_DEFAULT_FRAME_RATE, SS_SUPPORTED_FRAMERATES } from './constants'; import { SS_DEFAULT_FRAME_RATE, SS_SUPPORTED_FRAMERATES } from './constants';
@ -174,7 +175,7 @@ export function getSoundsTabProps(stateful: Object | Function) {
soundsTalkWhileMuted, soundsTalkWhileMuted,
soundsReactions soundsReactions
} = state['features/base/settings']; } = state['features/base/settings'];
const { enableReactions } = state['features/base/config']; const enableReactions = isReactionsEnabled(state);
return { return {
soundsIncomingMessage, soundsIncomingMessage,

View File

@ -1,84 +0,0 @@
// @flow
import { translate } from '../../../base/i18n';
import { IconRaisedHand } from '../../../base/icons';
import { getLocalParticipant } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
type Props = AbstractButtonProps & {
/**
* Whether or not the local participant's hand is raised.
*/
_raisedHand: boolean,
};
/**
* Implementation of a button for toggling raise hand functionality.
*/
class RaiseHandButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
icon = IconRaisedHand
label = 'toolbar.raiseYourHand';
toggledLabel = 'toolbar.lowerYourHand'
/**
* Retrieves tooltip dynamically.
*/
get tooltip() {
return this.props._raisedHand ? 'toolbar.lowerYourHand' : 'toolbar.raiseYourHand';
}
/**
* Required by linter due to AbstractButton overwritten prop being writable.
*
* @param {string} value - The value.
*/
set tooltip(value) {
return value;
}
/**
* Handles clicking / pressing the button, and opens the appropriate dialog.
*
* @protected
* @returns {void}
*/
_handleClick() {
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
}
/**
* Indicates whether this button is in toggled state or not.
*
* @override
* @protected
* @returns {boolean}
*/
_isToggled() {
return this.props._raisedHand;
}
}
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @returns {Object}
*/
const mapStateToProps = state => {
const localParticipant = getLocalParticipant(state);
return {
_raisedHand: localParticipant.raisedHand
};
};
export default translate(connect(mapStateToProps)(RaiseHandButton));

View File

@ -17,7 +17,6 @@ import { translate } from '../../../base/i18n';
import JitsiMeetJS from '../../../base/lib-jitsi-meet'; import JitsiMeetJS from '../../../base/lib-jitsi-meet';
import { import {
getLocalParticipant, getLocalParticipant,
getParticipantCount,
haveParticipantWithScreenSharingFeature, haveParticipantWithScreenSharingFeature,
raiseHand raiseHand
} from '../../../base/participants'; } from '../../../base/participants';
@ -87,7 +86,6 @@ import AudioSettingsButton from './AudioSettingsButton';
import FullscreenButton from './FullscreenButton'; import FullscreenButton from './FullscreenButton';
import OverflowMenuButton from './OverflowMenuButton'; import OverflowMenuButton from './OverflowMenuButton';
import ProfileButton from './ProfileButton'; import ProfileButton from './ProfileButton';
import RaiseHandButton from './RaiseHandButton';
import Separator from './Separator'; import Separator from './Separator';
import ShareDesktopButton from './ShareDesktopButton'; import ShareDesktopButton from './ShareDesktopButton';
import ToggleCameraButton from './ToggleCameraButton'; import ToggleCameraButton from './ToggleCameraButton';
@ -180,11 +178,6 @@ type Props = {
*/ */
_overflowMenuVisible: boolean, _overflowMenuVisible: boolean,
/**
* Number of participants in the conference.
*/
_participantCount: number,
/** /**
* Whether or not the participants pane is open. * Whether or not the participants pane is open.
*/ */
@ -254,16 +247,12 @@ type Props = {
declare var APP: Object; declare var APP: Object;
type State = {
reactionsShortcutsRegistered: boolean
};
/** /**
* Implements the conference toolbox on React/Web. * Implements the conference toolbox on React/Web.
* *
* @extends Component * @extends Component
*/ */
class Toolbox extends Component<Props, State> { class Toolbox extends Component<Props> {
/** /**
* Initializes a new {@code Toolbox} instance. * Initializes a new {@code Toolbox} instance.
* *
@ -273,10 +262,6 @@ class Toolbox extends Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = {
reactionsShortcutsRegistered: false
};
// Bind event handlers so they are only bound once per instance. // Bind event handlers so they are only bound once per instance.
this._onMouseOut = this._onMouseOut.bind(this); this._onMouseOut = this._onMouseOut.bind(this);
this._onMouseOver = this._onMouseOver.bind(this); this._onMouseOver = this._onMouseOver.bind(this);
@ -306,7 +291,7 @@ class Toolbox extends Component<Props, State> {
* @returns {void} * @returns {void}
*/ */
componentDidMount() { componentDidMount() {
const { _toolbarButtons, t, dispatch, _reactionsEnabled, _participantCount } = this.props; const { _toolbarButtons, t, dispatch, _reactionsEnabled } = this.props;
const KEYBOARD_SHORTCUTS = [ const KEYBOARD_SHORTCUTS = [
isToolbarButtonEnabled('videoquality', _toolbarButtons) && { isToolbarButtonEnabled('videoquality', _toolbarButtons) && {
character: 'A', character: 'A',
@ -355,7 +340,7 @@ class Toolbox extends Component<Props, State> {
} }
}); });
if (_reactionsEnabled && _participantCount > 1) { if (_reactionsEnabled) {
const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => { const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => {
const onShortcutSendReaction = () => { const onShortcutSendReaction = () => {
dispatch(addReactionToBuffer(key)); dispatch(addReactionToBuffer(key));
@ -389,7 +374,7 @@ class Toolbox extends Component<Props, State> {
* @inheritdoc * @inheritdoc
*/ */
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { _dialog, _reactionsEnabled, _participantCount, dispatch, t } = this.props; const { _dialog, dispatch } = this.props;
if (prevProps._overflowMenuVisible if (prevProps._overflowMenuVisible
@ -398,41 +383,6 @@ class Toolbox extends Component<Props, State> {
this._onSetOverflowVisible(false); this._onSetOverflowVisible(false);
dispatch(setToolbarHovered(false)); dispatch(setToolbarHovered(false));
} }
if (!this.state.reactionsShortcutsRegistered
&& (prevProps._reactionsEnabled !== _reactionsEnabled
|| prevProps._participantCount !== _participantCount)) {
if (_reactionsEnabled && _participantCount > 1) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
reactionsShortcutsRegistered: true
});
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);
});
}
}
} }
/** /**
@ -445,7 +395,7 @@ class Toolbox extends Component<Props, State> {
[ 'A', 'C', 'D', 'R', 'S' ].forEach(letter => [ 'A', 'C', 'D', 'R', 'S' ].forEach(letter =>
APP.keyboardshortcut.unregisterShortcut(letter)); APP.keyboardshortcut.unregisterShortcut(letter));
if (this.props._reactionsEnabled && this.state.reactionsShortcutsRegistered) { if (this.props._reactionsEnabled) {
Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar) Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
.forEach(letter => .forEach(letter =>
APP.keyboardshortcut.unregisterShortcut(letter, true)); APP.keyboardshortcut.unregisterShortcut(letter, true));
@ -613,8 +563,7 @@ class Toolbox extends Component<Props, State> {
const { const {
_feedbackConfigured, _feedbackConfigured,
_isMobile, _isMobile,
_screenSharing, _screenSharing
_reactionsEnabled
} = this.props; } = this.props;
const microphone = { const microphone = {
@ -651,8 +600,8 @@ class Toolbox extends Component<Props, State> {
const raisehand = { const raisehand = {
key: 'raisehand', key: 'raisehand',
Content: _reactionsEnabled ? ReactionsMenuButton : RaiseHandButton, Content: ReactionsMenuButton,
handleClick: _reactionsEnabled ? null : this._onToolbarToggleRaiseHand, handleClick: this._onToolbarToggleRaiseHand,
group: 2 group: 2
}; };
@ -1388,7 +1337,6 @@ function _mapStateToProps(state, ownProps) {
_localParticipantID: localParticipant?.id, _localParticipantID: localParticipant?.id,
_localVideo: localVideo, _localVideo: localVideo,
_overflowMenuVisible: overflowMenuVisible, _overflowMenuVisible: overflowMenuVisible,
_participantCount: getParticipantCount(state),
_participantsPaneOpen: getParticipantsPaneOpen(state), _participantsPaneOpen: getParticipantsPaneOpen(state),
_raisedHand: localParticipant?.raisedHand, _raisedHand: localParticipant?.raisedHand,
_reactionsEnabled: isReactionsEnabled(state), _reactionsEnabled: isReactionsEnabled(state),