瀏覽代碼

fix(reactions) Batch events before sending

master
robertpin 4 年之前
父節點
當前提交
251eec19cd
No account linked to committer's email address

+ 3
- 3
lang/main.json 查看文件

@@ -823,8 +823,8 @@
823 823
             "hangup": "Leave the meeting",
824 824
             "help": "Help",
825 825
             "invite": "Invite people",
826
-            "joy": "Laugh",
827 826
             "kick": "Kick participant",
827
+            "laugh": "Laugh",
828 828
             "like": "Thumbs Up",
829 829
             "lobbyButton": "Enable/disable lobby mode",
830 830
             "localRecording": "Toggle local recording controls",
@@ -892,7 +892,7 @@
892 892
         "hangup": "Leave the meeting",
893 893
         "help": "Help",
894 894
         "invite": "Invite people",
895
-        "joy": "Laugh",
895
+        "laugh": "Laugh",
896 896
         "like": "Thumbs Up",
897 897
         "lobbyButtonDisable": "Disable lobby mode",
898 898
         "lobbyButtonEnable": "Enable lobby mode",
@@ -922,7 +922,7 @@
922 922
         "raiseYourHand": "Raise your hand",
923 923
         "reactionBoo": "Send boo reaction",
924 924
         "reactionClap": "Send clap reaction",
925
-        "reactionJoy": "Send laugh reaction",
925
+        "reactionLaugh": "Send laugh reaction",
926 926
         "reactionLike": "Send thumbs up reaction",
927 927
         "reactionParty": "Send party popper reaction",
928 928
         "reactionSurprised": "Send surprised reaction",

+ 17
- 0
react/features/analytics/AnalyticsEvents.js 查看文件

@@ -795,6 +795,23 @@ export function createToolbarEvent(buttonName, attributes = {}) {
795 795
     };
796 796
 }
797 797
 
798
+/**
799
+ * Creates an event associated with a reaction button being clicked/pressed.
800
+ *
801
+ * @param {string} buttonName - The identifier of the reaction button which was
802
+ * clicked/pressed.
803
+ * @returns {Object} The event in a format suitable for sending via
804
+ * sendAnalytics.
805
+ */
806
+export function createReactionMenuEvent(buttonName) {
807
+    return {
808
+        action: 'clicked',
809
+        actionSubject: buttonName,
810
+        source: 'reaction.button',
811
+        type: TYPE_UI
812
+    };
813
+}
814
+
798 815
 /**
799 816
  * Creates an event which indicates that a local track was muted.
800 817
  *

+ 11
- 24
react/features/chat/middleware.js 查看文件

@@ -22,11 +22,9 @@ import {
22 22
 import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
23 23
 import { playSound, registerSound, unregisterSound } from '../base/sounds';
24 24
 import { openDisplayNamePrompt } from '../display-name';
25
-import { ADD_REACTIONS_MESSAGE } from '../reactions/actionTypes';
26
-import {
27
-    pushReaction
28
-} from '../reactions/actions.any';
29
-import { REACTIONS } from '../reactions/constants';
25
+import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
26
+import { pushReactions } from '../reactions/actions.any';
27
+import { getReactionMessageFromBuffer } from '../reactions/functions.any';
30 28
 import { endpointMessageReceived } from '../subtitles';
31 29
 import { showToolbox } from '../toolbox/actions';
32 30
 import {
@@ -158,7 +156,7 @@ MiddlewareRegistry.register(store => next => action => {
158 156
         break;
159 157
     }
160 158
 
161
-    case ADD_REACTIONS_MESSAGE: {
159
+    case ADD_REACTION_MESSAGE: {
162 160
         _handleReceivedMessage(store, {
163 161
             id: localParticipant.id,
164 162
             message: action.message,
@@ -212,8 +210,6 @@ StateListenerRegistry.register(
212 210
  * @returns {void}
213 211
  */
214 212
 function _addChatMsgListener(conference, store) {
215
-    const reactions = {};
216
-
217 213
     if (store.getState()['features/base/config'].iAmRecorder) {
218 214
         // We don't register anything on web if we are in iAmRecorder mode
219 215
         return;
@@ -252,30 +248,21 @@ function _addChatMsgListener(conference, store) {
252 248
                 const [ { _id }, eventData ] = args;
253 249
 
254 250
                 if (eventData.name === ENDPOINT_REACTION_NAME) {
255
-                    reactions[_id] = reactions[_id] ?? {
256
-                        timeout: null,
257
-                        message: ''
258
-                    };
259 251
                     batch(() => {
260
-                        store.dispatch(pushReaction(eventData.reaction));
261 252
                         store.dispatch(setToolboxVisible(true));
262 253
                         store.dispatch(setToolboxTimeout(
263 254
                                 () => store.dispatch(hideToolbox()),
264 255
                                 5000)
265 256
                         );
257
+                        store.dispatch(pushReactions(eventData.reactions));
266 258
                     });
267 259
 
268
-                    clearTimeout(reactions[_id].timeout);
269
-                    reactions[_id].message = `${reactions[_id].message}${REACTIONS[eventData.reaction].message}`;
270
-                    reactions[_id].timeout = setTimeout(() => {
271
-                        _handleReceivedMessage(store, {
272
-                            id: _id,
273
-                            message: reactions[_id].message,
274
-                            privateMessage: false,
275
-                            timestamp: eventData.timestamp
276
-                        }, false);
277
-                        delete reactions[_id];
278
-                    }, 500);
260
+                    _handleReceivedMessage(store, {
261
+                        id: _id,
262
+                        message: getReactionMessageFromBuffer(eventData.reactions),
263
+                        privateMessage: false,
264
+                        timestamp: eventData.timestamp
265
+                    }, false);
279 266
                 }
280 267
             }
281 268
         });

+ 18
- 13
react/features/reactions/actionTypes.js 查看文件

@@ -13,21 +13,31 @@ export const TOGGLE_REACTIONS_VISIBLE = 'TOGGLE_REACTIONS_VISIBLE';
13 13
  * a new timeout.
14 14
  *
15 15
  * {
16
- *     type: SET_REACTION_MESSAGE,
16
+ *     type: ADD_REACTION_BUFFER,
17 17
  *     message: string,
18 18
  *     timeoutID: number
19 19
  * }
20 20
  */
21
-export const SET_REACTIONS_MESSAGE = 'SET_REACTIONS_MESSAGE';
21
+export const ADD_REACTION_BUFFER = 'ADD_REACTION_BUFFER';
22 22
 
23 23
 /**
24
- * The type of the action which resets the reactions message and timeout.
24
+ * The type of the action which sends the reaction buffer and resets it.
25 25
  *
26 26
  * {
27
- *     type: CLEAR_REACTION_MESSAGE
27
+ *     type: FLUSH_REACTION_BUFFER
28 28
  * }
29 29
  */
30
-export const CLEAR_REACTIONS_MESSAGE = 'CLEAR_REACTIONS_MESSAGE';
30
+export const FLUSH_REACTION_BUFFER = 'FLUSH_REACTION_BUFFER';
31
+
32
+/**
33
+ * The type of the action which adds a new reaction message to the chat.
34
+ *
35
+ * {
36
+ *     type: ADD_REACTION_MESSAGE,
37
+ *     message: string,
38
+ * }
39
+ */
40
+export const ADD_REACTION_MESSAGE = 'ADD_REACTION_MESSAGE';
31 41
 
32 42
 /**
33 43
  * The type of the action which sets the reactions queue.
@@ -42,14 +52,9 @@ export const SET_REACTION_QUEUE = 'SET_REACTION_QUEUE';
42 52
 /**
43 53
  * The type of the action which signals a send reaction to everyone in the conference.
44 54
  */
45
-export const SEND_REACTION = 'SEND_REACTION';
46
-
47
-/**
48
- * The type of the action to add a reaction message to the chat.
49
- */
50
-export const ADD_REACTIONS_MESSAGE = 'ADD_REACTIONS_MESSAGE';
55
+export const SEND_REACTIONS = 'SEND_REACTIONS';
51 56
 
52 57
 /**
53
- * The type of action to add a reaction to the queue.
58
+ * The type of action to adds reactions to the queue.
54 59
  */
55
-export const PUSH_REACTION = 'PUSH_REACTION';
60
+export const PUSH_REACTIONS = 'PUSH_REACTIONS';

+ 50
- 44
react/features/reactions/actions.any.js 查看文件

@@ -1,11 +1,11 @@
1 1
 // @flow
2 2
 
3 3
 import {
4
-    ADD_REACTIONS_MESSAGE,
5
-    CLEAR_REACTIONS_MESSAGE,
6
-    PUSH_REACTION,
7
-    SEND_REACTION,
8
-    SET_REACTIONS_MESSAGE,
4
+    ADD_REACTION_BUFFER,
5
+    ADD_REACTION_MESSAGE,
6
+    FLUSH_REACTION_BUFFER,
7
+    PUSH_REACTIONS,
8
+    SEND_REACTIONS,
9 9
     SET_REACTION_QUEUE
10 10
 } from './actionTypes';
11 11
 import { type ReactionEmojiProps } from './constants';
@@ -23,86 +23,92 @@ export function setReactionQueue(value: Array<ReactionEmojiProps>) {
23 23
     };
24 24
 }
25 25
 
26
+
26 27
 /**
27
- * Appends the reactions message to the chat and resets the state.
28
+ * Removes a reaction from the queue.
28 29
  *
30
+ * @param {number} uid - Id of the reaction to be removed.
29 31
  * @returns {void}
30 32
  */
31
-export function flushReactionsToChat() {
32
-    return {
33
-        type: CLEAR_REACTIONS_MESSAGE
33
+export function removeReaction(uid: number) {
34
+    return (dispatch: Function, getState: Function) => {
35
+        const queue = getState()['features/reactions'].queue;
36
+
37
+        dispatch(setReactionQueue(queue.filter(reaction => reaction.uid !== uid)));
34 38
     };
35 39
 }
36 40
 
41
+
37 42
 /**
38
- * Adds a new reaction to the reactions message.
43
+ * Sends the reactions buffer to everyone in the conference.
39 44
  *
40
- * @param {boolean} value - The new reaction.
41
- * @returns {Object}
45
+ * @returns {{
46
+ *     type: SEND_REACTION
47
+ * }}
42 48
  */
43
-export function addReactionsMessage(value: string) {
49
+export function sendReactions() {
44 50
     return {
45
-        type: SET_REACTIONS_MESSAGE,
46
-        reaction: value
51
+        type: SEND_REACTIONS
47 52
     };
48 53
 }
49 54
 
50 55
 /**
51
- * Adds a new reaction to the reactions message.
56
+ * Adds a reaction to the local buffer.
52 57
  *
53
- * @param {boolean} value - Reaction to be added to queue.
54
- * @returns {Object}
58
+ * @param {string} reaction - The reaction to be added.
59
+ * @returns {{
60
+ *     type: ADD_REACTION_BUFFER,
61
+ *     reaction: string
62
+ * }}
55 63
  */
56
-export function pushReaction(value: string) {
64
+export function addReactionToBuffer(reaction: string) {
57 65
     return {
58
-        type: PUSH_REACTION,
59
-        reaction: value
66
+        type: ADD_REACTION_BUFFER,
67
+        reaction
60 68
     };
61 69
 }
62 70
 
63 71
 /**
64
- * Removes a reaction from the queue.
72
+ * Clears the reaction buffer.
65 73
  *
66
- * @param {number} uid - Id of the reaction to be removed.
67
- * @returns {void}
74
+ * @returns {{
75
+ *     type: FLUSH_REACTION_BUFFER
76
+ * }}
68 77
  */
69
-export function removeReaction(uid: number) {
70
-    return (dispatch: Function, getState: Function) => {
71
-        const queue = getState()['features/reactions'].queue;
72
-
73
-        dispatch(setReactionQueue(queue.filter(reaction => reaction.uid !== uid)));
78
+export function flushReactionBuffer() {
79
+    return {
80
+        type: FLUSH_REACTION_BUFFER
74 81
     };
75 82
 }
76 83
 
77
-
78 84
 /**
79
- * Sends a reaction message to everyone in the conference.
85
+ * Adds a reaction message to the chat.
80 86
  *
81
- * @param {string} reaction - The reaction to send out.
87
+ * @param {string} message - The reaction message.
82 88
  * @returns {{
83
- *     type: SEND_REACTION,
84
- *     reaction: string
89
+ *     type: ADD_REACTION_MESSAGE,
90
+ *     message: string
85 91
  * }}
86 92
  */
87
-export function sendReaction(reaction: string) {
93
+export function addReactionsToChat(message: string) {
88 94
     return {
89
-        type: SEND_REACTION,
90
-        reaction
95
+        type: ADD_REACTION_MESSAGE,
96
+        message
91 97
     };
92 98
 }
93 99
 
94 100
 /**
95
- * Adds a reactions message to the chat.
101
+ * Adds reactions to the animation queue.
96 102
  *
97
- * @param {string} message - The reactions message to add to chat.
103
+ * @param {Array} reactions - The reactions to be animated.
98 104
  * @returns {{
99
- *     type: ADD_REACTIONS_MESSAGE,
100
- *     message: string
105
+ *     type: PUSH_REACTIONS,
106
+ *     reactions: Array
101 107
  * }}
102 108
  */
103
-export function addReactionsMessageToChat(message: string) {
109
+export function pushReactions(reactions: Array<string>) {
104 110
     return {
105
-        type: ADD_REACTIONS_MESSAGE,
106
-        message
111
+        type: PUSH_REACTIONS,
112
+        reactions
107 113
     };
108 114
 }

+ 4
- 2
react/features/reactions/components/native/ReactionButton.js 查看文件

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

+ 14
- 5
react/features/reactions/components/web/ReactionsMenu.js 查看文件

@@ -4,6 +4,7 @@ import React, { Component } from 'react';
4 4
 import { bindActionCreators } from 'redux';
5 5
 
6 6
 import {
7
+    createReactionMenuEvent,
7 8
     createToolbarEvent,
8 9
     sendAnalytics
9 10
 } from '../../../analytics';
@@ -11,7 +12,7 @@ import { translate } from '../../../base/i18n';
11 12
 import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants';
12 13
 import { connect } from '../../../base/redux';
13 14
 import { dockToolbox } from '../../../toolbox/actions.web';
14
-import { sendReaction } from '../../actions.any';
15
+import { addReactionToBuffer } from '../../actions.any';
15 16
 import { toggleReactionsMenuVisibility } from '../../actions.web';
16 17
 import { REACTIONS } from '../../constants';
17 18
 
@@ -144,6 +145,13 @@ class ReactionsMenu extends Component<Props> {
144 145
      */
145 146
     _getReactionButtons() {
146 147
         const { t, dispatch } = this.props;
148
+        let modifierKey = 'Alt';
149
+
150
+        if (window.navigator?.platform) {
151
+            if (window.navigator.platform.indexOf('Mac') !== -1) {
152
+                modifierKey = '⌥';
153
+            }
154
+        }
147 155
 
148 156
         return Object.keys(REACTIONS).map(key => {
149 157
             /**
@@ -151,17 +159,18 @@ class ReactionsMenu extends Component<Props> {
151 159
              *
152 160
              * @returns {void}
153 161
              */
154
-            function sendMessage() {
155
-                dispatch(sendReaction(key));
162
+            function doSendReaction() {
163
+                dispatch(addReactionToBuffer(key));
164
+                sendAnalytics(createReactionMenuEvent(key));
156 165
             }
157 166
 
158 167
             return (<ReactionButton
159 168
                 accessibilityLabel = { t(`toolbar.accessibilityLabel.${key}`) }
160 169
                 icon = { REACTIONS[key].emoji }
161 170
                 key = { key }
162
-                onClick = { sendMessage }
171
+                onClick = { doSendReaction }
163 172
                 toggled = { false }
164
-                tooltip = { t(`toolbar.${key}`) } />);
173
+                tooltip = { `${t(`toolbar.${key}`)} (${modifierKey} + ${REACTIONS[key].shortcutChar})` } />);
165 174
         });
166 175
     }
167 176
 

+ 2
- 29
react/features/reactions/components/web/ReactionsMenuButton.js 查看文件

@@ -1,15 +1,14 @@
1 1
 // @flow
2 2
 
3
-import React, { useEffect } from 'react';
3
+import React from 'react';
4 4
 
5 5
 import { translate } from '../../../base/i18n';
6 6
 import { IconRaisedHand } from '../../../base/icons';
7 7
 import { getLocalParticipant } from '../../../base/participants';
8 8
 import { connect } from '../../../base/redux';
9 9
 import ToolbarButton from '../../../toolbox/components/web/ToolbarButton';
10
-import { sendReaction } from '../../actions.any';
11 10
 import { toggleReactionsMenuVisibility } from '../../actions.web';
12
-import { REACTIONS, type ReactionEmojiProps } from '../../constants';
11
+import { type ReactionEmojiProps } from '../../constants';
13 12
 import { getReactionsQueue } from '../../functions.any';
14 13
 import { getReactionsMenuVisibility } from '../../functions.web';
15 14
 
@@ -65,32 +64,6 @@ function ReactionsMenuButton({
65 64
     dispatch
66 65
 }: Props) {
67 66
 
68
-    useEffect(() => {
69
-        const KEYBOARD_SHORTCUTS = Object.keys(REACTIONS).map(key => {
70
-            return {
71
-                character: REACTIONS[key].shortcutChar,
72
-                exec: () => dispatch(sendReaction(key)),
73
-                helpDescription: t(`toolbar.reaction${key.charAt(0).toUpperCase()}${key.slice(1)}`),
74
-                altKey: true
75
-            };
76
-        });
77
-
78
-        KEYBOARD_SHORTCUTS.forEach(shortcut => {
79
-            APP.keyboardshortcut.registerShortcut(
80
-                shortcut.character,
81
-                null,
82
-                shortcut.exec,
83
-                shortcut.helpDescription,
84
-                shortcut.altKey);
85
-        });
86
-
87
-        return () => {
88
-            Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
89
-                .forEach(letter =>
90
-                    APP.keyboardshortcut.unregisterShortcut(letter, true));
91
-        };
92
-    }, []);
93
-
94 67
     /**
95 68
      * Toggles the reactions menu visibility.
96 69
      *

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

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

+ 15
- 13
react/features/reactions/functions.any.js 查看文件

@@ -1,5 +1,7 @@
1 1
 // @flow
2 2
 
3
+import uuid from 'uuid';
4
+
3 5
 import { getLocalParticipant } from '../base/participants';
4 6
 import { extractFqnFromPath } from '../dynamic-branding/functions';
5 7
 
@@ -17,28 +19,28 @@ export function getReactionsQueue(state: Object) {
17 19
 }
18 20
 
19 21
 /**
20
- * Returns reaction key from the reaction message.
22
+ * Returns chat message from reactions buffer.
21 23
  *
22
- * @param {string} message - The reaction message.
24
+ * @param {Array} buffer - The reactions buffer.
23 25
  * @returns {string}
24 26
  */
25
-export function getReactionKeyByMessage(message: string): ?string {
26
-    return Object.keys(REACTIONS).find(key => REACTIONS[key].message === `:${message}:`);
27
+export function getReactionMessageFromBuffer(buffer: Array<string>) {
28
+    return buffer.map(reaction => REACTIONS[reaction].message).reduce((acc, val) => `${acc}${val}`);
27 29
 }
28 30
 
29 31
 /**
30
- * Gets reactions key array from concatenated message.
32
+ * Returns reactions array with uid.
31 33
  *
32
- * @param {string} message - The reaction message.
34
+ * @param {Array} buffer - The reactions buffer.
33 35
  * @returns {Array}
34 36
  */
35
-export function messageToKeyArray(message: string) {
36
-    let formattedMessage = message.replace(/::/g, '-');
37
-
38
-    formattedMessage = formattedMessage.replace(/:/g, '');
39
-    const messageArray = formattedMessage.split('-');
40
-
41
-    return messageArray.map<?string>(getReactionKeyByMessage);
37
+export function getReactionsWithId(buffer: Array<string>) {
38
+    return buffer.map<Object>(reaction => {
39
+        return {
40
+            reaction,
41
+            uid: uuid.v4()
42
+        };
43
+    });
42 44
 }
43 45
 
44 46
 /**

+ 32
- 30
react/features/reactions/middleware.js 查看文件

@@ -1,24 +1,25 @@
1 1
 // @flow
2 2
 
3
+import { batch } from 'react-redux';
4
+
3 5
 import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants';
4 6
 import { MiddlewareRegistry } from '../base/redux';
5 7
 import { isVpaasMeeting } from '../jaas/functions';
6 8
 
7 9
 import {
8
-    SET_REACTIONS_MESSAGE,
9
-    CLEAR_REACTIONS_MESSAGE,
10
-    SEND_REACTION,
11
-    PUSH_REACTION
10
+    ADD_REACTION_BUFFER,
11
+    FLUSH_REACTION_BUFFER,
12
+    SEND_REACTIONS,
13
+    PUSH_REACTIONS
12 14
 } from './actionTypes';
13 15
 import {
14
-    addReactionsMessage,
15
-    addReactionsMessageToChat,
16
-    flushReactionsToChat,
17
-    pushReaction,
16
+    addReactionsToChat,
17
+    flushReactionBuffer,
18
+    pushReactions,
19
+    sendReactions,
18 20
     setReactionQueue
19 21
 } from './actions.any';
20
-import { REACTIONS } from './constants';
21
-import { messageToKeyArray, sendReactionsWebhook } from './functions.any';
22
+import { getReactionMessageFromBuffer, getReactionsWithId, sendReactionsWebhook } from './functions.any';
22 23
 
23 24
 
24 25
 declare var APP: Object;
@@ -34,56 +35,57 @@ MiddlewareRegistry.register(store => next => action => {
34 35
     const { dispatch, getState } = store;
35 36
 
36 37
     switch (action.type) {
37
-    case SET_REACTIONS_MESSAGE: {
38
-        const { timeoutID, message } = getState()['features/reactions'];
38
+    case ADD_REACTION_BUFFER: {
39
+        const { timeoutID, buffer } = getState()['features/reactions'];
39 40
         const { reaction } = action;
40 41
 
41 42
         clearTimeout(timeoutID);
42
-        action.message = `${message}${reaction}`;
43
+        buffer.push(reaction);
44
+        action.buffer = buffer;
43 45
         action.timeoutID = setTimeout(() => {
44
-            dispatch(flushReactionsToChat());
46
+            dispatch(flushReactionBuffer());
45 47
         }, 500);
46 48
 
47 49
         break;
48 50
     }
49 51
 
50
-    case CLEAR_REACTIONS_MESSAGE: {
52
+    case FLUSH_REACTION_BUFFER: {
51 53
         const state = getState();
52
-        const { message } = state['features/reactions'];
54
+        const { buffer } = state['features/reactions'];
55
+
56
+        batch(() => {
57
+            dispatch(sendReactions());
58
+            dispatch(addReactionsToChat(getReactionMessageFromBuffer(buffer)));
59
+            dispatch(pushReactions(buffer));
60
+        });
53 61
 
54 62
         if (isVpaasMeeting(state)) {
55
-            sendReactionsWebhook(state, messageToKeyArray(message));
63
+            sendReactionsWebhook(state, buffer);
56 64
         }
57 65
 
58
-        dispatch(addReactionsMessageToChat(message));
59
-
60 66
         break;
61 67
     }
62 68
 
63
-    case SEND_REACTION: {
64
-        const state = store.getState();
69
+    case SEND_REACTIONS: {
70
+        const state = getState();
71
+        const { buffer } = state['features/reactions'];
65 72
         const { conference } = state['features/base/conference'];
66 73
 
67 74
         if (conference) {
68 75
             conference.sendEndpointMessage('', {
69 76
                 name: ENDPOINT_REACTION_NAME,
70
-                reaction: action.reaction,
77
+                reactions: buffer,
71 78
                 timestamp: Date.now()
72 79
             });
73
-            dispatch(addReactionsMessage(REACTIONS[action.reaction].message));
74
-            dispatch(pushReaction(action.reaction));
75 80
         }
76 81
         break;
77 82
     }
78 83
 
79
-    case PUSH_REACTION: {
84
+    case PUSH_REACTIONS: {
80 85
         const queue = store.getState()['features/reactions'].queue;
81
-        const reaction = action.reaction;
86
+        const reactions = action.reactions;
82 87
 
83
-        dispatch(setReactionQueue([ ...queue, {
84
-            reaction,
85
-            uid: window.Date.now()
86
-        } ]));
88
+        dispatch(setReactionQueue([ ...queue, ...getReactionsWithId(reactions) ]));
87 89
     }
88 90
     }
89 91
 

+ 10
- 10
react/features/reactions/reducer.js 查看文件

@@ -4,9 +4,9 @@ import { ReducerRegistry } from '../base/redux';
4 4
 
5 5
 import {
6 6
     TOGGLE_REACTIONS_VISIBLE,
7
-    SET_REACTIONS_MESSAGE,
8
-    CLEAR_REACTIONS_MESSAGE,
9
-    SET_REACTION_QUEUE
7
+    SET_REACTION_QUEUE,
8
+    ADD_REACTION_BUFFER,
9
+    FLUSH_REACTION_BUFFER
10 10
 } from './actionTypes';
11 11
 
12 12
 /**
@@ -30,11 +30,11 @@ function _getInitialState() {
30 30
         visible: false,
31 31
 
32 32
         /**
33
-         * A string that contains the message to be added to the chat.
33
+         * An array that contains the reactions buffer to be sent.
34 34
          *
35
-         * @type {string}
35
+         * @type {Array}
36 36
          */
37
-        message: '',
37
+        buffer: [],
38 38
 
39 39
         /**
40 40
          * A number, non-zero value which identifies the timer created by a call
@@ -64,17 +64,17 @@ ReducerRegistry.register(
64 64
                 visible: !state.visible
65 65
             };
66 66
 
67
-        case SET_REACTIONS_MESSAGE:
67
+        case ADD_REACTION_BUFFER:
68 68
             return {
69 69
                 ...state,
70
-                message: action.message,
70
+                buffer: action.buffer,
71 71
                 timeoutID: action.timeoutID
72 72
             };
73 73
 
74
-        case CLEAR_REACTIONS_MESSAGE:
74
+        case FLUSH_REACTION_BUFFER:
75 75
             return {
76 76
                 ...state,
77
-                message: '',
77
+                buffer: [],
78 78
                 timeoutID: null
79 79
             };
80 80
 

+ 32
- 1
react/features/toolbox/components/web/Toolbox.js 查看文件

@@ -36,7 +36,9 @@ import {
36 36
 } from '../../../participants-pane/actions';
37 37
 import ParticipantsPaneButton from '../../../participants-pane/components/ParticipantsPaneButton';
38 38
 import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
39
+import { addReactionToBuffer } from '../../../reactions/actions.any';
39 40
 import { ReactionsMenuButton } from '../../../reactions/components';
41
+import { REACTIONS } from '../../../reactions/constants';
40 42
 import {
41 43
     LiveStreamButton,
42 44
     RecordButton
@@ -268,7 +270,7 @@ class Toolbox extends Component<Props> {
268 270
      * @returns {void}
269 271
      */
270 272
     componentDidMount() {
271
-        const { _toolbarButtons } = this.props;
273
+        const { _toolbarButtons, t, dispatch } = this.props;
272 274
         const KEYBOARD_SHORTCUTS = [
273 275
             isToolbarButtonEnabled('videoquality', _toolbarButtons) && {
274 276
                 character: 'A',
@@ -316,6 +318,31 @@ class Toolbox extends Component<Props> {
316 318
                     shortcut.helpDescription);
317 319
             }
318 320
         });
321
+
322
+        const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => {
323
+            const onShortcutSendReaction = () => {
324
+                dispatch(addReactionToBuffer(key));
325
+                sendAnalytics(createShortcutEvent(
326
+                    `reaction.${key}`
327
+                ));
328
+            };
329
+
330
+            return {
331
+                character: REACTIONS[key].shortcutChar,
332
+                exec: onShortcutSendReaction,
333
+                helpDescription: t(`toolbar.reaction${key.charAt(0).toUpperCase()}${key.slice(1)}`),
334
+                altKey: true
335
+            };
336
+        });
337
+
338
+        REACTION_SHORTCUTS.forEach(shortcut => {
339
+            APP.keyboardshortcut.registerShortcut(
340
+                shortcut.character,
341
+                null,
342
+                shortcut.exec,
343
+                shortcut.helpDescription,
344
+                shortcut.altKey);
345
+        });
319 346
     }
320 347
 
321 348
     /**
@@ -346,6 +373,10 @@ class Toolbox extends Component<Props> {
346 373
     componentWillUnmount() {
347 374
         [ 'A', 'C', 'D', 'R', 'S' ].forEach(letter =>
348 375
             APP.keyboardshortcut.unregisterShortcut(letter));
376
+
377
+        Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
378
+            .forEach(letter =>
379
+                APP.keyboardshortcut.unregisterShortcut(letter, true));
349 380
     }
350 381
 
351 382
     /**

Loading…
取消
儲存