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
This commit is contained in:
robertpin 2021-08-31 14:00:27 +03:00 committed by GitHub
parent aa06e89807
commit 5367d43c26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 123 additions and 67 deletions

View File

@ -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';

View File

@ -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));
}
}

View File

@ -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));
});
_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,8 +351,11 @@ function _handleReceivedMessage({ dispatch, getState },
ts: timestamp
});
if (!iAmRecorder) {
dispatch(showToolbox(4000));
}
}
}
/**

View File

@ -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<Props> {
* @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<Props> {
*/
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)
};
}

View File

@ -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.

View File

@ -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<string>) {
};
});
}
/**
* 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;
}

View File

@ -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) {

View File

@ -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)
};
}

View File

@ -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)
};
}

View File

@ -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<Props> {
class Toolbox extends Component<Props, State> {
/**
* Initializes a new {@code Toolbox} instance.
*
@ -250,6 +262,10 @@ class Toolbox extends Component<Props> {
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<Props> {
* @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<Props> {
}
});
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<Props> {
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<Props> {
[ '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)
};
}