From 5367d43c269c5b51a3da659a5c846d050105180c Mon Sep 17 00:00:00 2001 From: robertpin Date: Tue, 31 Aug 2021 14:00:27 +0300 Subject: [PATCH] fix(reactions) fix reactions bugs * Fix: removed web actions from common middlewares * Fixed raise hand sound Fix sound to play on raise not lower and work on keyboard shortcut as well * Fixed reaction keyboard shortcuts Register shortcuts only when there's more than one participant * Enforce reactions feature flag on reaction received * Disable reactions by default on native * Enable reactions on native by default * Sort props alphabetically * Created isreactionsEnabled function * Remove unused imports * Fix. No longer show toolbox on reactions and jibri On message received don't show toolbox for jibri * Fix isReactionsEnabled function for native On native check for flag and config option as well --- modules/API/constants.js | 5 - .../features/base/participants/middleware.js | 2 + react/features/chat/middleware.js | 36 ++++---- react/features/reactions/actions.native.js | 0 .../reactions/components/web/ReactionsMenu.js | 19 +--- react/features/reactions/constants.js | 5 + react/features/reactions/functions.any.js | 17 ++++ react/features/reactions/middleware.js | 7 +- .../toolbox/components/native/OverflowMenu.js | 4 +- .../toolbox/components/native/Toolbox.js | 4 +- .../toolbox/components/web/Toolbox.js | 91 +++++++++++++++---- 11 files changed, 123 insertions(+), 67 deletions(-) create mode 100644 react/features/reactions/actions.native.js diff --git a/modules/API/constants.js b/modules/API/constants.js index 5079dd190..4d5213719 100644 --- a/modules/API/constants.js +++ b/modules/API/constants.js @@ -15,8 +15,3 @@ export const API_ID = parseURLParams(window.location).jitsi_meet_external_api_id * The payload name for the datachannel/endpoint text message event */ export const ENDPOINT_TEXT_MESSAGE_NAME = 'endpoint-text-message'; - -/** - * The payload name for the datachannel/endpoint reaction event - */ -export const ENDPOINT_REACTION_NAME = 'endpoint-reaction'; diff --git a/react/features/base/participants/middleware.js b/react/features/base/participants/middleware.js index 0a465772f..03a54832e 100644 --- a/react/features/base/participants/middleware.js +++ b/react/features/base/participants/middleware.js @@ -6,6 +6,7 @@ import UIEvents from '../../../../service/UI/UIEvents'; import { toggleE2EE } from '../../e2ee/actions'; import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications'; import { CALLING, INVITED } from '../../presence-status'; +import { RAISE_HAND_SOUND_ID } from '../../reactions/constants'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app'; import { CONFERENCE_WILL_JOIN, @@ -495,6 +496,7 @@ function _raiseHandUpdated({ dispatch, getState }, conference, participantId, ne }, titleKey: 'notify.raisedHand' }, NOTIFICATION_TIMEOUT)); + dispatch(playSound(RAISE_HAND_SOUND_ID)); } } diff --git a/react/features/chat/middleware.js b/react/features/chat/middleware.js index b90aa04e2..b32f53db5 100644 --- a/react/features/chat/middleware.js +++ b/react/features/chat/middleware.js @@ -1,8 +1,5 @@ // @flow -import { batch } from 'react-redux'; - -import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; import { CONFERENCE_JOINED, @@ -25,14 +22,13 @@ import { openDisplayNamePrompt } from '../display-name'; import { resetNbUnreadPollsMessages } from '../polls/actions'; import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes'; import { pushReactions } from '../reactions/actions.any'; -import { getReactionMessageFromBuffer } from '../reactions/functions.any'; +import { ENDPOINT_REACTION_NAME } from '../reactions/constants'; +import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any'; import { endpointMessageReceived } from '../subtitles'; -import { showToolbox } from '../toolbox/actions'; import { - hideToolbox, - setToolboxTimeout, - setToolboxVisible -} from '../toolbox/actions.web'; + showToolbox +} from '../toolbox/actions'; + import { ADD_MESSAGE, SEND_MESSAGE, OPEN_CHAT, CLOSE_CHAT, SET_IS_POLL_TAB_FOCUSED } from './actionTypes'; import { addMessage, clearMessages } from './actions'; @@ -255,20 +251,19 @@ function _addChatMsgListener(conference, store) { conference.on( JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, (...args) => { + const state = store.getState(); + + if (!isReactionsEnabled(state)) { + return; + } + store.dispatch(endpointMessageReceived(...args)); if (args && args.length >= 2) { const [ { _id }, eventData ] = args; if (eventData.name === ENDPOINT_REACTION_NAME) { - batch(() => { - store.dispatch(setToolboxVisible(true)); - store.dispatch(setToolboxTimeout( - () => store.dispatch(hideToolbox()), - 5000) - ); - store.dispatch(pushReactions(eventData.reactions)); - }); + store.dispatch(pushReactions(eventData.reactions)); _handleReceivedMessage(store, { id: _id, @@ -318,7 +313,7 @@ function _handleReceivedMessage({ dispatch, getState }, // Logic for all platforms: const state = getState(); const { isOpen: isChatOpen } = state['features/chat']; - const { disableIncomingMessageSound } = state['features/base/config']; + const { disableIncomingMessageSound, iAmRecorder } = state['features/base/config']; const { soundsIncomingMessage: soundEnabled } = state['features/base/settings']; if (!disableIncomingMessageSound && soundEnabled && shouldPlaySound && !isChatOpen) { @@ -356,7 +351,10 @@ function _handleReceivedMessage({ dispatch, getState }, ts: timestamp }); - dispatch(showToolbox(4000)); + if (!iAmRecorder) { + dispatch(showToolbox(4000)); + } + } } diff --git a/react/features/reactions/actions.native.js b/react/features/reactions/actions.native.js new file mode 100644 index 000000000..e69de29bb diff --git a/react/features/reactions/components/web/ReactionsMenu.js b/react/features/reactions/components/web/ReactionsMenu.js index 3aee8be94..5522a143a 100644 --- a/react/features/reactions/components/web/ReactionsMenu.js +++ b/react/features/reactions/components/web/ReactionsMenu.js @@ -11,11 +11,10 @@ import { import { translate } from '../../../base/i18n'; import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants'; import { connect } from '../../../base/redux'; -import { playSound } from '../../../base/sounds'; import { dockToolbox } from '../../../toolbox/actions.web'; import { addReactionToBuffer } from '../../actions.any'; import { toggleReactionsMenuVisibility } from '../../actions.web'; -import { RAISE_HAND_SOUND_ID, REACTIONS } from '../../constants'; +import { REACTIONS } from '../../constants'; import ReactionButton from './ReactionButton'; @@ -54,12 +53,7 @@ type Props = { /** * Whether or not it's displayed in the overflow menu. */ - overflowMenu: boolean, - - /** - * Whether or not reaction sounds are enabled. - */ - _reactionSounds: boolean + overflowMenu: boolean }; declare var APP: Object; @@ -112,16 +106,13 @@ class ReactionsMenu extends Component { * @returns {void} */ _onToolbarToggleRaiseHand() { - const { dispatch, _raisedHand, _reactionSounds } = this.props; + const { dispatch, _raisedHand } = this.props; sendAnalytics(createToolbarEvent( 'raise.hand', { enable: !_raisedHand })); this._doToggleRaiseHand(); dispatch(toggleReactionsMenuVisibility()); - if (_reactionSounds && _raisedHand) { - dispatch(playSound(RAISE_HAND_SOUND_ID)); - } } /** @@ -223,13 +214,11 @@ class ReactionsMenu extends Component { */ function mapStateToProps(state) { const localParticipant = getLocalParticipant(state); - const { soundsReactions } = state['features/base/settings']; return { _localParticipantID: localParticipant.id, _raisedHand: localParticipant.raisedHand, - _participantCount: getParticipantCount(state), - _reactionSounds: soundsReactions + _participantCount: getParticipantCount(state) }; } diff --git a/react/features/reactions/constants.js b/react/features/reactions/constants.js index f5d7156c0..109159aeb 100644 --- a/react/features/reactions/constants.js +++ b/react/features/reactions/constants.js @@ -9,6 +9,11 @@ import { SILENCE_SOUND_FILES } from './sounds'; +/** + * The payload name for the datachannel/endpoint reaction event + */ +export const ENDPOINT_REACTION_NAME = 'endpoint-reaction'; + /** * The audio ID prefix of the audio element for which the {@link playAudio} action is * triggered when a new laugh reaction is received. diff --git a/react/features/reactions/functions.any.js b/react/features/reactions/functions.any.js index 991a32d66..202bd364a 100644 --- a/react/features/reactions/functions.any.js +++ b/react/features/reactions/functions.any.js @@ -2,6 +2,7 @@ import uuid from 'uuid'; +import { getFeatureFlag, REACTIONS_ENABLED } from '../base/flags'; import { getLocalParticipant } from '../base/participants'; import { extractFqnFromPath } from '../dynamic-branding/functions'; @@ -142,3 +143,19 @@ export function getReactionsSoundsThresholds(reactions: Array) { }; }); } + +/** + * Whether or not the reactions are enabled. + * + * @param {Object} state - The Redux state object. + * @returns {boolean} + */ +export function isReactionsEnabled(state: Object) { + const { enableReactions } = state['features/base/config']; + + if (navigator.product === 'ReactNative') { + return enableReactions && getFeatureFlag(state, REACTIONS_ENABLED, true); + } + + return enableReactions; +} diff --git a/react/features/reactions/middleware.js b/react/features/reactions/middleware.js index 7500eb3de..9ae33bea3 100644 --- a/react/features/reactions/middleware.js +++ b/react/features/reactions/middleware.js @@ -2,7 +2,6 @@ import { batch } from 'react-redux'; -import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; import { MiddlewareRegistry } from '../base/redux'; import { updateSettings } from '../base/settings'; @@ -17,6 +16,7 @@ import { PUSH_REACTIONS, SHOW_SOUNDS_NOTIFICATION } from './actionTypes'; +import { displayReactionSoundsNotification } from './actions'; import { addReactionsToChat, flushReactionBuffer, @@ -24,8 +24,7 @@ import { sendReactions, setReactionQueue } from './actions.any'; -import { displayReactionSoundsNotification } from './actions.web'; -import { RAISE_HAND_SOUND_ID, REACTIONS, SOUNDS_THRESHOLDS } from './constants'; +import { ENDPOINT_REACTION_NAME, RAISE_HAND_SOUND_ID, REACTIONS, SOUNDS_THRESHOLDS } from './constants'; import { getReactionMessageFromBuffer, getReactionsSoundsThresholds, @@ -128,7 +127,7 @@ MiddlewareRegistry.register(store => next => action => { const reactions = action.reactions; batch(() => { - if (!notificationDisplayed && soundsReactions) { + if (!notificationDisplayed && soundsReactions && displayReactionSoundsNotification) { dispatch(displayReactionSoundsNotification()); } if (soundsReactions) { diff --git a/react/features/toolbox/components/native/OverflowMenu.js b/react/features/toolbox/components/native/OverflowMenu.js index 713a019ad..60b7bcfbf 100644 --- a/react/features/toolbox/components/native/OverflowMenu.js +++ b/react/features/toolbox/components/native/OverflowMenu.js @@ -5,13 +5,13 @@ import { Divider } from 'react-native-paper'; import { ColorSchemeRegistry } from '../../../base/color-scheme'; import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog'; -import { getFeatureFlag, REACTIONS_ENABLED } from '../../../base/flags'; import { connect } from '../../../base/redux'; import { StyleType } from '../../../base/styles'; import { SharedDocumentButton } from '../../../etherpad'; import { AudioRouteButton } from '../../../mobile/audio-mode'; import { ParticipantsPaneButton } from '../../../participants-pane/components/native'; import { ReactionMenu } from '../../../reactions/components'; +import { isReactionsEnabled } from '../../../reactions/functions.any'; import { LiveStreamButton, RecordButton } from '../../../recording'; import SecurityDialogButton from '../../../security/components/security-dialog/SecurityDialogButton'; import { SharedVideoButton } from '../../../shared-video/components'; @@ -205,7 +205,7 @@ function _mapStateToProps(state) { _bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'), _isOpen: isDialogOpen(state, OverflowMenu_), _width: state['features/base/responsive-ui'].clientWidth, - _reactionsEnabled: getFeatureFlag(state, REACTIONS_ENABLED, true) + _reactionsEnabled: isReactionsEnabled(state) }; } diff --git a/react/features/toolbox/components/native/Toolbox.js b/react/features/toolbox/components/native/Toolbox.js index 0774653ff..0745d9c2b 100644 --- a/react/features/toolbox/components/native/Toolbox.js +++ b/react/features/toolbox/components/native/Toolbox.js @@ -4,12 +4,12 @@ import React from 'react'; import { SafeAreaView, View } from 'react-native'; import { ColorSchemeRegistry } from '../../../base/color-scheme'; -import { getFeatureFlag, REACTIONS_ENABLED } from '../../../base/flags'; import { connect } from '../../../base/redux'; import { StyleType } from '../../../base/styles'; import { ChatButton } from '../../../chat'; import { ParticipantsPaneButton } from '../../../participants-pane/components/native'; import { ReactionsMenuButton } from '../../../reactions/components'; +import { isReactionsEnabled } from '../../../reactions/functions.any'; import { TileViewButton } from '../../../video-layout'; import { isToolboxVisible, getMovableButtons } from '../../functions.native'; import AudioMuteButton from '../AudioMuteButton'; @@ -133,7 +133,7 @@ function _mapStateToProps(state: Object): Object { _styles: ColorSchemeRegistry.get(state, 'Toolbox'), _visible: isToolboxVisible(state), _width: state['features/base/responsive-ui'].clientWidth, - _reactionsEnabled: getFeatureFlag(state, REACTIONS_ENABLED, false) + _reactionsEnabled: isReactionsEnabled(state) }; } diff --git a/react/features/toolbox/components/web/Toolbox.js b/react/features/toolbox/components/web/Toolbox.js index bb1f69217..af14e5f4e 100644 --- a/react/features/toolbox/components/web/Toolbox.js +++ b/react/features/toolbox/components/web/Toolbox.js @@ -17,6 +17,7 @@ import { translate } from '../../../base/i18n'; import JitsiMeetJS from '../../../base/lib-jitsi-meet'; import { getLocalParticipant, + getParticipantCount, haveParticipantWithScreenSharingFeature, raiseHand } from '../../../base/participants'; @@ -41,6 +42,7 @@ import { getParticipantsPaneOpen } from '../../../participants-pane/functions'; import { addReactionToBuffer } from '../../../reactions/actions.any'; import { ReactionsMenuButton } from '../../../reactions/components'; import { REACTIONS } from '../../../reactions/constants'; +import { isReactionsEnabled } from '../../../reactions/functions.any'; import { LiveStreamButton, RecordButton @@ -152,10 +154,6 @@ type Props = { */ _isProfileDisabled: boolean, - /** - * Whether or not the tile view is enabled. - */ - _tileViewEnabled: boolean, /** * Whether or not the current meeting belongs to a JaaS user. @@ -177,6 +175,11 @@ type Props = { */ _overflowMenuVisible: boolean, + /** + * Number of participants in the conference. + */ + _participantCount: number, + /** * Whether or not the participants pane is open. */ @@ -187,6 +190,11 @@ type Props = { */ _raisedHand: boolean, + /** + * Whether or not reactions feature is enabled. + */ + _reactionsEnabled: boolean, + /** * Whether or not the local participant is screenSharing. */ @@ -197,6 +205,11 @@ type Props = { */ _sharingVideo: boolean, + /** + * Whether or not the tile view is enabled. + */ + _tileViewEnabled: boolean, + /** * The enabled buttons. */ @@ -212,11 +225,6 @@ type Props = { */ _virtualSource: Object, - /** - * Whether or not reactions feature is enabled. - */ - _reactionsEnabled: boolean, - /** * Invoked to active other features of the app. */ @@ -235,12 +243,16 @@ type Props = { declare var APP: Object; +type State = { + reactionsShortcutsRegistered: boolean +}; + /** * Implements the conference toolbox on React/Web. * * @extends Component */ -class Toolbox extends Component { +class Toolbox extends Component { /** * Initializes a new {@code Toolbox} instance. * @@ -250,6 +262,10 @@ class Toolbox extends Component { constructor(props: Props) { super(props); + this.state = { + reactionsShortcutsRegistered: false + }; + // Bind event handlers so they are only bound once per instance. this._onMouseOut = this._onMouseOut.bind(this); this._onMouseOver = this._onMouseOver.bind(this); @@ -279,7 +295,7 @@ class Toolbox extends Component { * @returns {void} */ componentDidMount() { - const { _toolbarButtons, t, dispatch, _reactionsEnabled } = this.props; + const { _toolbarButtons, t, dispatch, _reactionsEnabled, _participantCount } = this.props; const KEYBOARD_SHORTCUTS = [ isToolbarButtonEnabled('videoquality', _toolbarButtons) && { character: 'A', @@ -328,7 +344,7 @@ class Toolbox extends Component { } }); - if (_reactionsEnabled) { + if (_reactionsEnabled && _participantCount > 1) { const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => { const onShortcutSendReaction = () => { dispatch(addReactionToBuffer(key)); @@ -373,6 +389,41 @@ class Toolbox extends Component { this._onSetOverflowVisible(false); this.props.dispatch(setToolbarHovered(false)); } + + if (!this.state.reactionsShortcutsRegistered + && (prevProps._reactionsEnabled !== this.props._reactionsEnabled + || prevProps._participantCount !== this.props._participantCount)) { + if (this.props._reactionsEnabled && this.props._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 = () => { + this.props.dispatch(addReactionToBuffer(key)); + sendAnalytics(createShortcutEvent( + `reaction.${key}` + )); + }; + + return { + character: REACTIONS[key].shortcutChar, + exec: onShortcutSendReaction, + helpDescription: this.props.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); + }); + } + } } /** @@ -385,7 +436,7 @@ class Toolbox extends Component { [ 'A', 'C', 'D', 'R', 'S' ].forEach(letter => APP.keyboardshortcut.unregisterShortcut(letter)); - if (this.props._reactionsEnabled) { + if (this.props._reactionsEnabled && this.state.reactionsShortcutsRegistered) { Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar) .forEach(letter => APP.keyboardshortcut.unregisterShortcut(letter, true)); @@ -1262,7 +1313,6 @@ function _mapStateToProps(state, ownProps) { const localParticipant = getLocalParticipant(state); const localVideo = getLocalVideoTrack(state['features/base/tracks']); const { clientWidth } = state['features/base/responsive-ui']; - const { enableReactions } = state['features/base/config']; let desktopSharingDisabledTooltipKey; @@ -1285,29 +1335,30 @@ function _mapStateToProps(state, ownProps) { } return { + _backgroundType: state['features/virtual-background'].backgroundType, _chatOpen: state['features/chat'].isOpen, _clientWidth: clientWidth, _conference: conference, _desktopSharingEnabled: desktopSharingEnabled, - _backgroundType: state['features/virtual-background'].backgroundType, - _virtualSource: state['features/virtual-background'].virtualSource, _desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey, _dialog: Boolean(state['features/base/dialog'].component), _feedbackConfigured: Boolean(callStatsID), + _fullScreen: fullScreen, _isProfileDisabled: Boolean(state['features/base/config'].disableProfile), _isMobile: isMobileBrowser(), _isVpaasMeeting: isVpaasMeeting(state), - _fullScreen: fullScreen, - _tileViewEnabled: shouldDisplayTileView(state), _localParticipantID: localParticipant?.id, _localVideo: localVideo, _overflowMenuVisible: overflowMenuVisible, + _participantCount: getParticipantCount(state), _participantsPaneOpen: getParticipantsPaneOpen(state), _raisedHand: localParticipant?.raisedHand, + _reactionsEnabled: isReactionsEnabled(state), _screenSharing: isScreenVideoShared(state), + _tileViewEnabled: shouldDisplayTileView(state), _toolbarButtons: toolbarButtons, - _visible: isToolboxVisible(state), - _reactionsEnabled: enableReactions + _virtualSource: state['features/virtual-background'].virtualSource, + _visible: isToolboxVisible(state) }; }