浏览代码

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
master
robertpin 3 年前
父节点
当前提交
5367d43c26
没有帐户链接到提交者的电子邮件

+ 0
- 5
modules/API/constants.js 查看文件

15
  * The payload name for the datachannel/endpoint text message event
15
  * The payload name for the datachannel/endpoint text message event
16
  */
16
  */
17
 export const ENDPOINT_TEXT_MESSAGE_NAME = 'endpoint-text-message';
17
 export const ENDPOINT_TEXT_MESSAGE_NAME = 'endpoint-text-message';
18
-
19
-/**
20
- * The payload name for the datachannel/endpoint reaction event
21
- */
22
-export const ENDPOINT_REACTION_NAME = 'endpoint-reaction';

+ 2
- 0
react/features/base/participants/middleware.js 查看文件

6
 import { toggleE2EE } from '../../e2ee/actions';
6
 import { toggleE2EE } from '../../e2ee/actions';
7
 import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
7
 import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
8
 import { CALLING, INVITED } from '../../presence-status';
8
 import { CALLING, INVITED } from '../../presence-status';
9
+import { RAISE_HAND_SOUND_ID } from '../../reactions/constants';
9
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
10
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
10
 import {
11
 import {
11
     CONFERENCE_WILL_JOIN,
12
     CONFERENCE_WILL_JOIN,
495
             },
496
             },
496
             titleKey: 'notify.raisedHand'
497
             titleKey: 'notify.raisedHand'
497
         }, NOTIFICATION_TIMEOUT));
498
         }, NOTIFICATION_TIMEOUT));
499
+        dispatch(playSound(RAISE_HAND_SOUND_ID));
498
     }
500
     }
499
 }
501
 }
500
 
502
 

+ 17
- 19
react/features/chat/middleware.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
-import { batch } from 'react-redux';
4
-
5
-import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants';
6
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
3
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
7
 import {
4
 import {
8
     CONFERENCE_JOINED,
5
     CONFERENCE_JOINED,
25
 import { resetNbUnreadPollsMessages } from '../polls/actions';
22
 import { resetNbUnreadPollsMessages } from '../polls/actions';
26
 import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
23
 import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
27
 import { pushReactions } from '../reactions/actions.any';
24
 import { pushReactions } from '../reactions/actions.any';
28
-import { getReactionMessageFromBuffer } from '../reactions/functions.any';
25
+import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
26
+import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any';
29
 import { endpointMessageReceived } from '../subtitles';
27
 import { endpointMessageReceived } from '../subtitles';
30
-import { showToolbox } from '../toolbox/actions';
31
 import {
28
 import {
32
-    hideToolbox,
33
-    setToolboxTimeout,
34
-    setToolboxVisible
35
-} from '../toolbox/actions.web';
29
+    showToolbox
30
+} from '../toolbox/actions';
31
+
36
 
32
 
37
 import { ADD_MESSAGE, SEND_MESSAGE, OPEN_CHAT, CLOSE_CHAT, SET_IS_POLL_TAB_FOCUSED } from './actionTypes';
33
 import { ADD_MESSAGE, SEND_MESSAGE, OPEN_CHAT, CLOSE_CHAT, SET_IS_POLL_TAB_FOCUSED } from './actionTypes';
38
 import { addMessage, clearMessages } from './actions';
34
 import { addMessage, clearMessages } from './actions';
255
     conference.on(
251
     conference.on(
256
         JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
252
         JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
257
         (...args) => {
253
         (...args) => {
254
+            const state = store.getState();
255
+
256
+            if (!isReactionsEnabled(state)) {
257
+                return;
258
+            }
259
+
258
             store.dispatch(endpointMessageReceived(...args));
260
             store.dispatch(endpointMessageReceived(...args));
259
 
261
 
260
             if (args && args.length >= 2) {
262
             if (args && args.length >= 2) {
261
                 const [ { _id }, eventData ] = args;
263
                 const [ { _id }, eventData ] = args;
262
 
264
 
263
                 if (eventData.name === ENDPOINT_REACTION_NAME) {
265
                 if (eventData.name === ENDPOINT_REACTION_NAME) {
264
-                    batch(() => {
265
-                        store.dispatch(setToolboxVisible(true));
266
-                        store.dispatch(setToolboxTimeout(
267
-                                () => store.dispatch(hideToolbox()),
268
-                                5000)
269
-                        );
270
-                        store.dispatch(pushReactions(eventData.reactions));
271
-                    });
266
+                    store.dispatch(pushReactions(eventData.reactions));
272
 
267
 
273
                     _handleReceivedMessage(store, {
268
                     _handleReceivedMessage(store, {
274
                         id: _id,
269
                         id: _id,
318
     // Logic for all platforms:
313
     // Logic for all platforms:
319
     const state = getState();
314
     const state = getState();
320
     const { isOpen: isChatOpen } = state['features/chat'];
315
     const { isOpen: isChatOpen } = state['features/chat'];
321
-    const { disableIncomingMessageSound } = state['features/base/config'];
316
+    const { disableIncomingMessageSound, iAmRecorder } = state['features/base/config'];
322
     const { soundsIncomingMessage: soundEnabled } = state['features/base/settings'];
317
     const { soundsIncomingMessage: soundEnabled } = state['features/base/settings'];
323
 
318
 
324
     if (!disableIncomingMessageSound && soundEnabled && shouldPlaySound && !isChatOpen) {
319
     if (!disableIncomingMessageSound && soundEnabled && shouldPlaySound && !isChatOpen) {
356
             ts: timestamp
351
             ts: timestamp
357
         });
352
         });
358
 
353
 
359
-        dispatch(showToolbox(4000));
354
+        if (!iAmRecorder) {
355
+            dispatch(showToolbox(4000));
356
+        }
357
+
360
     }
358
     }
361
 }
359
 }
362
 
360
 

+ 0
- 0
react/features/reactions/actions.native.js 查看文件


+ 4
- 15
react/features/reactions/components/web/ReactionsMenu.js 查看文件

11
 import { translate } from '../../../base/i18n';
11
 import { translate } from '../../../base/i18n';
12
 import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants';
12
 import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants';
13
 import { connect } from '../../../base/redux';
13
 import { connect } from '../../../base/redux';
14
-import { playSound } from '../../../base/sounds';
15
 import { dockToolbox } from '../../../toolbox/actions.web';
14
 import { dockToolbox } from '../../../toolbox/actions.web';
16
 import { addReactionToBuffer } from '../../actions.any';
15
 import { addReactionToBuffer } from '../../actions.any';
17
 import { toggleReactionsMenuVisibility } from '../../actions.web';
16
 import { toggleReactionsMenuVisibility } from '../../actions.web';
18
-import { RAISE_HAND_SOUND_ID, REACTIONS } from '../../constants';
17
+import { REACTIONS } from '../../constants';
19
 
18
 
20
 import ReactionButton from './ReactionButton';
19
 import ReactionButton from './ReactionButton';
21
 
20
 
54
     /**
53
     /**
55
      * Whether or not it's displayed in the overflow menu.
54
      * Whether or not it's displayed in the overflow menu.
56
      */
55
      */
57
-    overflowMenu: boolean,
58
-
59
-    /**
60
-     * Whether or not reaction sounds are enabled.
61
-     */
62
-    _reactionSounds: boolean
56
+    overflowMenu: boolean
63
 };
57
 };
64
 
58
 
65
 declare var APP: Object;
59
 declare var APP: Object;
112
      * @returns {void}
106
      * @returns {void}
113
      */
107
      */
114
     _onToolbarToggleRaiseHand() {
108
     _onToolbarToggleRaiseHand() {
115
-        const { dispatch, _raisedHand, _reactionSounds } = this.props;
109
+        const { dispatch, _raisedHand } = this.props;
116
 
110
 
117
         sendAnalytics(createToolbarEvent(
111
         sendAnalytics(createToolbarEvent(
118
             'raise.hand',
112
             'raise.hand',
119
             { enable: !_raisedHand }));
113
             { enable: !_raisedHand }));
120
         this._doToggleRaiseHand();
114
         this._doToggleRaiseHand();
121
         dispatch(toggleReactionsMenuVisibility());
115
         dispatch(toggleReactionsMenuVisibility());
122
-        if (_reactionSounds && _raisedHand) {
123
-            dispatch(playSound(RAISE_HAND_SOUND_ID));
124
-        }
125
     }
116
     }
126
 
117
 
127
     /**
118
     /**
223
  */
214
  */
224
 function mapStateToProps(state) {
215
 function mapStateToProps(state) {
225
     const localParticipant = getLocalParticipant(state);
216
     const localParticipant = getLocalParticipant(state);
226
-    const { soundsReactions } = state['features/base/settings'];
227
 
217
 
228
     return {
218
     return {
229
         _localParticipantID: localParticipant.id,
219
         _localParticipantID: localParticipant.id,
230
         _raisedHand: localParticipant.raisedHand,
220
         _raisedHand: localParticipant.raisedHand,
231
-        _participantCount: getParticipantCount(state),
232
-        _reactionSounds: soundsReactions
221
+        _participantCount: getParticipantCount(state)
233
     };
222
     };
234
 }
223
 }
235
 
224
 

+ 5
- 0
react/features/reactions/constants.js 查看文件

9
     SILENCE_SOUND_FILES
9
     SILENCE_SOUND_FILES
10
 } from './sounds';
10
 } from './sounds';
11
 
11
 
12
+/**
13
+ * The payload name for the datachannel/endpoint reaction event
14
+ */
15
+export const ENDPOINT_REACTION_NAME = 'endpoint-reaction';
16
+
12
 /**
17
 /**
13
  * The audio ID prefix of the audio element for which the {@link playAudio} action is
18
  * The audio ID prefix of the audio element for which the {@link playAudio} action is
14
  * triggered when a new laugh reaction is received.
19
  * triggered when a new laugh reaction is received.

+ 17
- 0
react/features/reactions/functions.any.js 查看文件

2
 
2
 
3
 import uuid from 'uuid';
3
 import uuid from 'uuid';
4
 
4
 
5
+import { getFeatureFlag, REACTIONS_ENABLED } from '../base/flags';
5
 import { getLocalParticipant } from '../base/participants';
6
 import { getLocalParticipant } from '../base/participants';
6
 import { extractFqnFromPath } from '../dynamic-branding/functions';
7
 import { extractFqnFromPath } from '../dynamic-branding/functions';
7
 
8
 
142
         };
143
         };
143
     });
144
     });
144
 }
145
 }
146
+
147
+/**
148
+ * Whether or not the reactions are enabled.
149
+ *
150
+ * @param {Object} state - The Redux state object.
151
+ * @returns {boolean}
152
+ */
153
+export function isReactionsEnabled(state: Object) {
154
+    const { enableReactions } = state['features/base/config'];
155
+
156
+    if (navigator.product === 'ReactNative') {
157
+        return enableReactions && getFeatureFlag(state, REACTIONS_ENABLED, true);
158
+    }
159
+
160
+    return enableReactions;
161
+}

+ 3
- 4
react/features/reactions/middleware.js 查看文件

2
 
2
 
3
 import { batch } from 'react-redux';
3
 import { batch } from 'react-redux';
4
 
4
 
5
-import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants';
6
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
5
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
7
 import { MiddlewareRegistry } from '../base/redux';
6
 import { MiddlewareRegistry } from '../base/redux';
8
 import { updateSettings } from '../base/settings';
7
 import { updateSettings } from '../base/settings';
17
     PUSH_REACTIONS,
16
     PUSH_REACTIONS,
18
     SHOW_SOUNDS_NOTIFICATION
17
     SHOW_SOUNDS_NOTIFICATION
19
 } from './actionTypes';
18
 } from './actionTypes';
19
+import { displayReactionSoundsNotification } from './actions';
20
 import {
20
 import {
21
     addReactionsToChat,
21
     addReactionsToChat,
22
     flushReactionBuffer,
22
     flushReactionBuffer,
24
     sendReactions,
24
     sendReactions,
25
     setReactionQueue
25
     setReactionQueue
26
 } from './actions.any';
26
 } from './actions.any';
27
-import { displayReactionSoundsNotification } from './actions.web';
28
-import { RAISE_HAND_SOUND_ID, REACTIONS, SOUNDS_THRESHOLDS } from './constants';
27
+import { ENDPOINT_REACTION_NAME, RAISE_HAND_SOUND_ID, REACTIONS, SOUNDS_THRESHOLDS } from './constants';
29
 import {
28
 import {
30
     getReactionMessageFromBuffer,
29
     getReactionMessageFromBuffer,
31
     getReactionsSoundsThresholds,
30
     getReactionsSoundsThresholds,
128
         const reactions = action.reactions;
127
         const reactions = action.reactions;
129
 
128
 
130
         batch(() => {
129
         batch(() => {
131
-            if (!notificationDisplayed && soundsReactions) {
130
+            if (!notificationDisplayed && soundsReactions && displayReactionSoundsNotification) {
132
                 dispatch(displayReactionSoundsNotification());
131
                 dispatch(displayReactionSoundsNotification());
133
             }
132
             }
134
             if (soundsReactions) {
133
             if (soundsReactions) {

+ 2
- 2
react/features/toolbox/components/native/OverflowMenu.js 查看文件

5
 
5
 
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
 import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog';
7
 import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog';
8
-import { getFeatureFlag, REACTIONS_ENABLED } from '../../../base/flags';
9
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
10
 import { StyleType } from '../../../base/styles';
9
 import { StyleType } from '../../../base/styles';
11
 import { SharedDocumentButton } from '../../../etherpad';
10
 import { SharedDocumentButton } from '../../../etherpad';
12
 import { AudioRouteButton } from '../../../mobile/audio-mode';
11
 import { AudioRouteButton } from '../../../mobile/audio-mode';
13
 import { ParticipantsPaneButton } from '../../../participants-pane/components/native';
12
 import { ParticipantsPaneButton } from '../../../participants-pane/components/native';
14
 import { ReactionMenu } from '../../../reactions/components';
13
 import { ReactionMenu } from '../../../reactions/components';
14
+import { isReactionsEnabled } from '../../../reactions/functions.any';
15
 import { LiveStreamButton, RecordButton } from '../../../recording';
15
 import { LiveStreamButton, RecordButton } from '../../../recording';
16
 import SecurityDialogButton from '../../../security/components/security-dialog/SecurityDialogButton';
16
 import SecurityDialogButton from '../../../security/components/security-dialog/SecurityDialogButton';
17
 import { SharedVideoButton } from '../../../shared-video/components';
17
 import { SharedVideoButton } from '../../../shared-video/components';
205
         _bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
205
         _bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
206
         _isOpen: isDialogOpen(state, OverflowMenu_),
206
         _isOpen: isDialogOpen(state, OverflowMenu_),
207
         _width: state['features/base/responsive-ui'].clientWidth,
207
         _width: state['features/base/responsive-ui'].clientWidth,
208
-        _reactionsEnabled: getFeatureFlag(state, REACTIONS_ENABLED, true)
208
+        _reactionsEnabled: isReactionsEnabled(state)
209
     };
209
     };
210
 }
210
 }
211
 
211
 

+ 2
- 2
react/features/toolbox/components/native/Toolbox.js 查看文件

4
 import { SafeAreaView, View } from 'react-native';
4
 import { SafeAreaView, View } from 'react-native';
5
 
5
 
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
-import { getFeatureFlag, REACTIONS_ENABLED } from '../../../base/flags';
8
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
9
 import { StyleType } from '../../../base/styles';
8
 import { StyleType } from '../../../base/styles';
10
 import { ChatButton } from '../../../chat';
9
 import { ChatButton } from '../../../chat';
11
 import { ParticipantsPaneButton } from '../../../participants-pane/components/native';
10
 import { ParticipantsPaneButton } from '../../../participants-pane/components/native';
12
 import { ReactionsMenuButton } from '../../../reactions/components';
11
 import { ReactionsMenuButton } from '../../../reactions/components';
12
+import { isReactionsEnabled } from '../../../reactions/functions.any';
13
 import { TileViewButton } from '../../../video-layout';
13
 import { TileViewButton } from '../../../video-layout';
14
 import { isToolboxVisible, getMovableButtons } from '../../functions.native';
14
 import { isToolboxVisible, getMovableButtons } from '../../functions.native';
15
 import AudioMuteButton from '../AudioMuteButton';
15
 import AudioMuteButton from '../AudioMuteButton';
133
         _styles: ColorSchemeRegistry.get(state, 'Toolbox'),
133
         _styles: ColorSchemeRegistry.get(state, 'Toolbox'),
134
         _visible: isToolboxVisible(state),
134
         _visible: isToolboxVisible(state),
135
         _width: state['features/base/responsive-ui'].clientWidth,
135
         _width: state['features/base/responsive-ui'].clientWidth,
136
-        _reactionsEnabled: getFeatureFlag(state, REACTIONS_ENABLED, false)
136
+        _reactionsEnabled: isReactionsEnabled(state)
137
     };
137
     };
138
 }
138
 }
139
 
139
 

+ 71
- 20
react/features/toolbox/components/web/Toolbox.js 查看文件

17
 import JitsiMeetJS from '../../../base/lib-jitsi-meet';
17
 import JitsiMeetJS from '../../../base/lib-jitsi-meet';
18
 import {
18
 import {
19
     getLocalParticipant,
19
     getLocalParticipant,
20
+    getParticipantCount,
20
     haveParticipantWithScreenSharingFeature,
21
     haveParticipantWithScreenSharingFeature,
21
     raiseHand
22
     raiseHand
22
 } from '../../../base/participants';
23
 } from '../../../base/participants';
41
 import { addReactionToBuffer } from '../../../reactions/actions.any';
42
 import { addReactionToBuffer } from '../../../reactions/actions.any';
42
 import { ReactionsMenuButton } from '../../../reactions/components';
43
 import { ReactionsMenuButton } from '../../../reactions/components';
43
 import { REACTIONS } from '../../../reactions/constants';
44
 import { REACTIONS } from '../../../reactions/constants';
45
+import { isReactionsEnabled } from '../../../reactions/functions.any';
44
 import {
46
 import {
45
     LiveStreamButton,
47
     LiveStreamButton,
46
     RecordButton
48
     RecordButton
152
      */
154
      */
153
     _isProfileDisabled: boolean,
155
     _isProfileDisabled: boolean,
154
 
156
 
155
-    /**
156
-     * Whether or not the tile view is enabled.
157
-     */
158
-    _tileViewEnabled: boolean,
159
 
157
 
160
     /**
158
     /**
161
      * Whether or not the current meeting belongs to a JaaS user.
159
      * Whether or not the current meeting belongs to a JaaS user.
177
      */
175
      */
178
     _overflowMenuVisible: boolean,
176
     _overflowMenuVisible: boolean,
179
 
177
 
178
+    /**
179
+     * Number of participants in the conference.
180
+     */
181
+    _participantCount: number,
182
+
180
     /**
183
     /**
181
      * Whether or not the participants pane is open.
184
      * Whether or not the participants pane is open.
182
      */
185
      */
187
      */
190
      */
188
     _raisedHand: boolean,
191
     _raisedHand: boolean,
189
 
192
 
193
+    /**
194
+     * Whether or not reactions feature is enabled.
195
+     */
196
+    _reactionsEnabled: boolean,
197
+
190
     /**
198
     /**
191
      * Whether or not the local participant is screenSharing.
199
      * Whether or not the local participant is screenSharing.
192
      */
200
      */
197
      */
205
      */
198
     _sharingVideo: boolean,
206
     _sharingVideo: boolean,
199
 
207
 
208
+    /**
209
+     * Whether or not the tile view is enabled.
210
+     */
211
+    _tileViewEnabled: boolean,
212
+
200
     /**
213
     /**
201
      * The enabled buttons.
214
      * The enabled buttons.
202
      */
215
      */
212
      */
225
      */
213
     _virtualSource: Object,
226
     _virtualSource: Object,
214
 
227
 
215
-    /**
216
-     * Whether or not reactions feature is enabled.
217
-     */
218
-    _reactionsEnabled: boolean,
219
-
220
     /**
228
     /**
221
      * Invoked to active other features of the app.
229
      * Invoked to active other features of the app.
222
      */
230
      */
235
 
243
 
236
 declare var APP: Object;
244
 declare var APP: Object;
237
 
245
 
246
+type State = {
247
+    reactionsShortcutsRegistered: boolean
248
+};
249
+
238
 /**
250
 /**
239
  * Implements the conference toolbox on React/Web.
251
  * Implements the conference toolbox on React/Web.
240
  *
252
  *
241
  * @extends Component
253
  * @extends Component
242
  */
254
  */
243
-class Toolbox extends Component<Props> {
255
+class Toolbox extends Component<Props, State> {
244
     /**
256
     /**
245
      * Initializes a new {@code Toolbox} instance.
257
      * Initializes a new {@code Toolbox} instance.
246
      *
258
      *
250
     constructor(props: Props) {
262
     constructor(props: Props) {
251
         super(props);
263
         super(props);
252
 
264
 
265
+        this.state = {
266
+            reactionsShortcutsRegistered: false
267
+        };
268
+
253
         // Bind event handlers so they are only bound once per instance.
269
         // Bind event handlers so they are only bound once per instance.
254
         this._onMouseOut = this._onMouseOut.bind(this);
270
         this._onMouseOut = this._onMouseOut.bind(this);
255
         this._onMouseOver = this._onMouseOver.bind(this);
271
         this._onMouseOver = this._onMouseOver.bind(this);
279
      * @returns {void}
295
      * @returns {void}
280
      */
296
      */
281
     componentDidMount() {
297
     componentDidMount() {
282
-        const { _toolbarButtons, t, dispatch, _reactionsEnabled } = this.props;
298
+        const { _toolbarButtons, t, dispatch, _reactionsEnabled, _participantCount } = this.props;
283
         const KEYBOARD_SHORTCUTS = [
299
         const KEYBOARD_SHORTCUTS = [
284
             isToolbarButtonEnabled('videoquality', _toolbarButtons) && {
300
             isToolbarButtonEnabled('videoquality', _toolbarButtons) && {
285
                 character: 'A',
301
                 character: 'A',
328
             }
344
             }
329
         });
345
         });
330
 
346
 
331
-        if (_reactionsEnabled) {
347
+        if (_reactionsEnabled && _participantCount > 1) {
332
             const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => {
348
             const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => {
333
                 const onShortcutSendReaction = () => {
349
                 const onShortcutSendReaction = () => {
334
                     dispatch(addReactionToBuffer(key));
350
                     dispatch(addReactionToBuffer(key));
373
             this._onSetOverflowVisible(false);
389
             this._onSetOverflowVisible(false);
374
             this.props.dispatch(setToolbarHovered(false));
390
             this.props.dispatch(setToolbarHovered(false));
375
         }
391
         }
392
+
393
+        if (!this.state.reactionsShortcutsRegistered
394
+            && (prevProps._reactionsEnabled !== this.props._reactionsEnabled
395
+            || prevProps._participantCount !== this.props._participantCount)) {
396
+            if (this.props._reactionsEnabled && this.props._participantCount > 1) {
397
+                // eslint-disable-next-line react/no-did-update-set-state
398
+                this.setState({
399
+                    reactionsShortcutsRegistered: true
400
+                });
401
+                const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => {
402
+                    const onShortcutSendReaction = () => {
403
+                        this.props.dispatch(addReactionToBuffer(key));
404
+                        sendAnalytics(createShortcutEvent(
405
+                            `reaction.${key}`
406
+                        ));
407
+                    };
408
+
409
+                    return {
410
+                        character: REACTIONS[key].shortcutChar,
411
+                        exec: onShortcutSendReaction,
412
+                        helpDescription: this.props.t(`toolbar.reaction${key.charAt(0).toUpperCase()}${key.slice(1)}`),
413
+                        altKey: true
414
+                    };
415
+                });
416
+
417
+                REACTION_SHORTCUTS.forEach(shortcut => {
418
+                    APP.keyboardshortcut.registerShortcut(
419
+                        shortcut.character,
420
+                        null,
421
+                        shortcut.exec,
422
+                        shortcut.helpDescription,
423
+                        shortcut.altKey);
424
+                });
425
+            }
426
+        }
376
     }
427
     }
377
 
428
 
378
     /**
429
     /**
385
         [ 'A', 'C', 'D', 'R', 'S' ].forEach(letter =>
436
         [ 'A', 'C', 'D', 'R', 'S' ].forEach(letter =>
386
             APP.keyboardshortcut.unregisterShortcut(letter));
437
             APP.keyboardshortcut.unregisterShortcut(letter));
387
 
438
 
388
-        if (this.props._reactionsEnabled) {
439
+        if (this.props._reactionsEnabled && this.state.reactionsShortcutsRegistered) {
389
             Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
440
             Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
390
                 .forEach(letter =>
441
                 .forEach(letter =>
391
                     APP.keyboardshortcut.unregisterShortcut(letter, true));
442
                     APP.keyboardshortcut.unregisterShortcut(letter, true));
1262
     const localParticipant = getLocalParticipant(state);
1313
     const localParticipant = getLocalParticipant(state);
1263
     const localVideo = getLocalVideoTrack(state['features/base/tracks']);
1314
     const localVideo = getLocalVideoTrack(state['features/base/tracks']);
1264
     const { clientWidth } = state['features/base/responsive-ui'];
1315
     const { clientWidth } = state['features/base/responsive-ui'];
1265
-    const { enableReactions } = state['features/base/config'];
1266
 
1316
 
1267
     let desktopSharingDisabledTooltipKey;
1317
     let desktopSharingDisabledTooltipKey;
1268
 
1318
 
1285
     }
1335
     }
1286
 
1336
 
1287
     return {
1337
     return {
1338
+        _backgroundType: state['features/virtual-background'].backgroundType,
1288
         _chatOpen: state['features/chat'].isOpen,
1339
         _chatOpen: state['features/chat'].isOpen,
1289
         _clientWidth: clientWidth,
1340
         _clientWidth: clientWidth,
1290
         _conference: conference,
1341
         _conference: conference,
1291
         _desktopSharingEnabled: desktopSharingEnabled,
1342
         _desktopSharingEnabled: desktopSharingEnabled,
1292
-        _backgroundType: state['features/virtual-background'].backgroundType,
1293
-        _virtualSource: state['features/virtual-background'].virtualSource,
1294
         _desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
1343
         _desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
1295
         _dialog: Boolean(state['features/base/dialog'].component),
1344
         _dialog: Boolean(state['features/base/dialog'].component),
1296
         _feedbackConfigured: Boolean(callStatsID),
1345
         _feedbackConfigured: Boolean(callStatsID),
1346
+        _fullScreen: fullScreen,
1297
         _isProfileDisabled: Boolean(state['features/base/config'].disableProfile),
1347
         _isProfileDisabled: Boolean(state['features/base/config'].disableProfile),
1298
         _isMobile: isMobileBrowser(),
1348
         _isMobile: isMobileBrowser(),
1299
         _isVpaasMeeting: isVpaasMeeting(state),
1349
         _isVpaasMeeting: isVpaasMeeting(state),
1300
-        _fullScreen: fullScreen,
1301
-        _tileViewEnabled: shouldDisplayTileView(state),
1302
         _localParticipantID: localParticipant?.id,
1350
         _localParticipantID: localParticipant?.id,
1303
         _localVideo: localVideo,
1351
         _localVideo: localVideo,
1304
         _overflowMenuVisible: overflowMenuVisible,
1352
         _overflowMenuVisible: overflowMenuVisible,
1353
+        _participantCount: getParticipantCount(state),
1305
         _participantsPaneOpen: getParticipantsPaneOpen(state),
1354
         _participantsPaneOpen: getParticipantsPaneOpen(state),
1306
         _raisedHand: localParticipant?.raisedHand,
1355
         _raisedHand: localParticipant?.raisedHand,
1356
+        _reactionsEnabled: isReactionsEnabled(state),
1307
         _screenSharing: isScreenVideoShared(state),
1357
         _screenSharing: isScreenVideoShared(state),
1358
+        _tileViewEnabled: shouldDisplayTileView(state),
1308
         _toolbarButtons: toolbarButtons,
1359
         _toolbarButtons: toolbarButtons,
1309
-        _visible: isToolboxVisible(state),
1310
-        _reactionsEnabled: enableReactions
1360
+        _virtualSource: state['features/virtual-background'].virtualSource,
1361
+        _visible: isToolboxVisible(state)
1311
     };
1362
     };
1312
 }
1363
 }
1313
 
1364
 

正在加载...
取消
保存