Browse Source

feat(MainToolbar): implement custom order.

As part of the PR, it also fixes:
 - Removes button aliases
 - Unifies the keys in the object returned by getAllToolboxButtons and the button keys
 - Makes sure that the number of buttons displayed are always the same as the number of buttons specified in the thresholds and removes the exception for not filling up the main toolbar with buttons from overflow menu when reactions button is disabled.
 - Introduces a priority for buttons that will be used to fill empty spaces in the main toolbar.
factor2
Hristo Terezov 1 year ago
parent
commit
0913554af9

+ 16
- 0
config.js View File

@@ -848,6 +848,22 @@ var config = {
848 848
     //     autoHideWhileChatIsOpen: false,
849 849
     // },
850 850
 
851
+    // Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed
852
+    // buttons varies from 2 buttons to 8 buttons. Every array in the mainToolbarButtons array will replace the
853
+    // corresponding default buttons configuration matched by the number of buttons specified in the array. Arrays with
854
+    // more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a cerain
855
+    // configuration (for example when 3 buttons are displayed) the default jitsi-meet configuration will be used.
856
+    // The order of the buttons in the array is preserved.
857
+    // mainToolbarButtons: [
858
+    //     [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'reactions', 'participants-pane', 'tileview' ],
859
+    //     [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants-pane', 'tileview' ],
860
+    //     [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants-pane' ],
861
+    //     [ 'microphone', 'camera', 'desktop', 'chat', 'participants-pane' ],
862
+    //     [ 'microphone', 'camera', 'chat', 'participants-pane' ],
863
+    //     [ 'microphone', 'camera', 'chat' ],
864
+    //     [ 'microphone', 'camera' ]
865
+    // ],
866
+
851 867
     // Toolbar buttons which have their click/tap event exposed through the API on
852 868
     // `toolbarButtonClicked`. Passing a string for the button key will
853 869
     // prevent execution of the click/tap routine; passing an object with `key` and

+ 1
- 0
react/features/base/config/configType.ts View File

@@ -441,6 +441,7 @@ export interface IConfig {
441 441
     };
442 442
     localSubject?: string;
443 443
     locationURL?: URL;
444
+    mainToolbarButtons?: Array<Array<string>>;
444 445
     maxFullResolutionParticipants?: number;
445 446
     microsoftApiApplicationClientID?: string;
446 447
     moderatedRoomServiceUrl?: string;

+ 1
- 0
react/features/base/config/configWhitelist.ts View File

@@ -185,6 +185,7 @@ export default [
185 185
     'localRecording',
186 186
     'localSubject',
187 187
     'logging',
188
+    'mainToolbarButtons',
188 189
     'maxFullResolutionParticipants',
189 190
     'mouseMoveCallbackInterval',
190 191
     'notifications',

+ 10
- 0
react/features/toolbox/actionTypes.ts View File

@@ -59,6 +59,16 @@ export const SET_FULL_SCREEN = 'SET_FULL_SCREEN';
59 59
  */
60 60
 export const SET_HANGUP_MENU_VISIBLE = 'SET_HANGUP_MENU_VISIBLE';
61 61
 
62
+/**
63
+ * The type of the (redux) action which sets the main toolbar thresholds.
64
+ *
65
+ * {
66
+ *     type: SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
67
+ *     mainToolbarButtonsThresholds: IMainToolbarButtonThresholds
68
+ * }
69
+ */
70
+export const SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS = 'SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS';
71
+
62 72
 /**
63 73
  * The type of the redux action that toggles whether the overflow menu(s) should be shown as drawers.
64 74
  */

+ 53
- 0
react/features/toolbox/actions.web.ts View File

@@ -8,13 +8,16 @@ import {
8 8
     FULL_SCREEN_CHANGED,
9 9
     SET_FULL_SCREEN,
10 10
     SET_HANGUP_MENU_VISIBLE,
11
+    SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
11 12
     SET_OVERFLOW_DRAWER,
12 13
     SET_OVERFLOW_MENU_VISIBLE,
13 14
     SET_TOOLBAR_HOVERED,
14 15
     SET_TOOLBOX_TIMEOUT
15 16
 } from './actionTypes';
16 17
 import { setToolboxVisible } from './actions.web';
18
+import { THRESHOLDS } from './constants';
17 19
 import { getToolbarTimeout } from './functions.web';
20
+import { IMainToolbarButtonThresholds } from './types';
18 21
 
19 22
 export * from './actions.any';
20 23
 
@@ -121,6 +124,56 @@ export function setFullScreen(fullScreen: boolean) {
121 124
     };
122 125
 }
123 126
 
127
+/**
128
+ * Sets the mainToolbarButtonsThresholds.
129
+ *
130
+ * @returns {Function}
131
+ */
132
+export function setMainToolbarThresholds() {
133
+    return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
134
+        const { mainToolbarButtons } = getState()['features/base/config'];
135
+
136
+        if (!mainToolbarButtons || !Array.isArray(mainToolbarButtons) || mainToolbarButtons.length === 0) {
137
+            return;
138
+        }
139
+
140
+        const mainToolbarButtonsThresholds: IMainToolbarButtonThresholds = [];
141
+
142
+        const mainToolbarButtonsLenghtMap = new Map();
143
+        let orderIsChanged = false;
144
+
145
+        mainToolbarButtons.forEach(buttons => {
146
+            if (!Array.isArray(buttons) || buttons.length === 0) {
147
+                return;
148
+            }
149
+
150
+            mainToolbarButtonsLenghtMap.set(buttons.length, buttons);
151
+        });
152
+
153
+        THRESHOLDS.forEach(({ width, order }) => {
154
+            let finalOrder = mainToolbarButtonsLenghtMap.get(order.length);
155
+
156
+            if (finalOrder) {
157
+                orderIsChanged = true;
158
+            } else {
159
+                finalOrder = order;
160
+            }
161
+
162
+            mainToolbarButtonsThresholds.push({
163
+                order: finalOrder,
164
+                width
165
+            });
166
+        });
167
+
168
+        if (orderIsChanged) {
169
+            dispatch({
170
+                type: SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
171
+                mainToolbarButtonsThresholds
172
+            });
173
+        }
174
+    };
175
+}
176
+
124 177
 /**
125 178
  * Shows the toolbox for specified timeout.
126 179
  *

+ 41
- 24
react/features/toolbox/components/web/Toolbox.tsx View File

@@ -15,7 +15,7 @@ import {
15 15
     setToolbarHovered,
16 16
     showToolbox
17 17
 } from '../../actions.web';
18
-import { NOT_APPLICABLE, THRESHOLDS } from '../../constants';
18
+import { MAIN_TOOLBAR_BUTTONS_PRIORITY } from '../../constants';
19 19
 import {
20 20
     getAllToolboxButtons,
21 21
     getJwtDisabledButtons,
@@ -23,7 +23,7 @@ import {
23 23
     isToolboxVisible
24 24
 } from '../../functions.web';
25 25
 import { useKeyboardShortcuts } from '../../hooks.web';
26
-import { IToolboxButton, NOTIFY_CLICK_MODE } from '../../types';
26
+import { IToolboxButton, NOTIFY_CLICK_MODE, ToolbarButton } from '../../types';
27 27
 import HangupButton from '../HangupButton';
28 28
 
29 29
 import { EndConferenceButton } from './EndConferenceButton';
@@ -92,6 +92,14 @@ interface IProps extends WithTranslation {
92 92
      */
93 93
     _jwtDisabledButtons: string[];
94 94
 
95
+    /**
96
+     * The main toolbar buttons thresholds used to determine the visible buttons depending on the current screen size.
97
+     */
98
+    _mainToolbarButtonsThresholds: Array<{
99
+        order: Array<ToolbarButton | string>;
100
+        width: number;
101
+    }>;
102
+
95 103
     /**
96 104
      * Whether or not the overflow menu is displayed in a drawer drawer.
97 105
      */
@@ -174,6 +182,7 @@ const Toolbox = ({
174 182
     _isMobile,
175 183
     _isNarrowLayout,
176 184
     _jwtDisabledButtons,
185
+    _mainToolbarButtonsThresholds,
177 186
     _overflowDrawer,
178 187
     _overflowMenuVisible,
179 188
     _reactionsButtonEnabled,
@@ -280,35 +289,42 @@ const Toolbox = ({
280 289
     function getVisibleButtons() {
281 290
         const buttons = getAllToolboxButtons(_customToolbarButtons);
282 291
 
292
+        const filteredButtons = Object.keys(buttons).filter(key =>
293
+            typeof key !== 'undefined' // filter invalid buttons that may be comming from config.mainToolbarButtons
294
+            // override
295
+            && !_jwtDisabledButtons.includes(key)
296
+            && isButtonEnabled(key, _toolbarButtons));
297
+
283 298
         setButtonsNotifyClickMode(buttons);
284
-        const isHangupVisible = isButtonEnabled('hangup', _toolbarButtons);
285
-        const { order } = THRESHOLDS.find(({ width }) => _clientWidth > width)
286
-            || THRESHOLDS[THRESHOLDS.length - 1];
287
-
288
-        const keys = Object.keys(buttons);
289
-
290
-        const filtered = [
291
-            ...order.map(key => buttons[key as keyof typeof buttons]),
292
-            ...Object.values(buttons).filter((button, index) => !order.includes(keys[index]))
293
-        ].filter(({ key, alias = NOT_APPLICABLE }) =>
294
-            !_jwtDisabledButtons.includes(key)
295
-            && (isButtonEnabled(key, _toolbarButtons) || isButtonEnabled(alias, _toolbarButtons))
296
-        );
299
+        const { order } = _mainToolbarButtonsThresholds.find(({ width }) => _clientWidth > width)
300
+            || _mainToolbarButtonsThresholds[_mainToolbarButtonsThresholds.length - 1];
301
+
302
+        const mainToolbarButtonKeysOrder = [
303
+            ...order.filter(key => filteredButtons.includes(key)),
304
+            ...MAIN_TOOLBAR_BUTTONS_PRIORITY.filter(key => !order.includes(key) && filteredButtons.includes(key)),
305
+            ...filteredButtons.filter(key => !order.includes(key) && !MAIN_TOOLBAR_BUTTONS_PRIORITY.includes(key))
306
+        ];
307
+
308
+        const mainButtonsKeys = mainToolbarButtonKeysOrder.slice(0, order.length);
309
+        const overflowMenuButtons = filteredButtons.reduce((acc, key) => {
310
+            if (!mainButtonsKeys.includes(key)) {
311
+                acc.push(buttons[key]);
312
+            }
297 313
 
298
-        let sliceIndex = _overflowDrawer || _reactionsButtonEnabled ? order.length + 2 : order.length + 1;
314
+            return acc;
315
+        }, [] as IToolboxButton[]);
299 316
 
300
-        if (isHangupVisible) {
301
-            sliceIndex -= 1;
302
-        }
317
+        // if we have 1 button in the overflow menu it is better to directly display it in the main toolbar by replacing
318
+        // the "More" menu button with it.
319
+        if (overflowMenuButtons.length === 1) {
320
+            const button = overflowMenuButtons.shift()?.key;
303 321
 
304
-        // This implies that the overflow button will be displayed, so save some space for it.
305
-        if (sliceIndex < filtered.length) {
306
-            sliceIndex -= 1;
322
+            button && mainButtonsKeys.push(button);
307 323
         }
308 324
 
309 325
         return {
310
-            mainMenuButtons: filtered.slice(0, sliceIndex),
311
-            overflowMenuButtons: filtered.slice(sliceIndex)
326
+            mainMenuButtons: mainButtonsKeys.map(key => buttons[key]),
327
+            overflowMenuButtons
312 328
         };
313 329
     }
314 330
 
@@ -505,6 +521,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
505 521
         _jwtDisabledButtons: getJwtDisabledButtons(state),
506 522
         _hangupMenuVisible: hangupMenuVisible,
507 523
         _isNarrowLayout: isNarrowLayout,
524
+        _mainToolbarButtonsThresholds: state['features/toolbox'].mainToolbarButtonsThresholds,
508 525
         _overflowMenuVisible: overflowMenuVisible,
509 526
         _overflowDrawer: overflowDrawer,
510 527
         _reactionsButtonEnabled: isReactionsButtonEnabled(state),

+ 42
- 6
react/features/toolbox/constants.ts View File

@@ -6,23 +6,23 @@ import { ToolbarButton } from './types';
6 6
 export const THRESHOLDS = [
7 7
     {
8 8
         width: 565,
9
-        order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'reactions', 'participants', 'tileview' ]
9
+        order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'reactions', 'participants-pane', 'tileview' ]
10 10
     },
11 11
     {
12 12
         width: 520,
13
-        order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants', 'tileview' ]
13
+        order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants-pane', 'tileview' ]
14 14
     },
15 15
     {
16 16
         width: 470,
17
-        order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants' ]
17
+        order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants-pane' ]
18 18
     },
19 19
     {
20 20
         width: 420,
21
-        order: [ 'microphone', 'camera', 'desktop', 'chat', 'participants' ]
21
+        order: [ 'microphone', 'camera', 'desktop', 'chat', 'participants-pane' ]
22 22
     },
23 23
     {
24 24
         width: 370,
25
-        order: [ 'microphone', 'camera', 'chat', 'participants' ]
25
+        order: [ 'microphone', 'camera', 'chat', 'participants-pane' ]
26 26
     },
27 27
     {
28 28
         width: 225,
@@ -34,7 +34,43 @@ export const THRESHOLDS = [
34 34
     }
35 35
 ];
36 36
 
37
-export const NOT_APPLICABLE = 'N/A';
37
+/**
38
+ * Main toolbar buttons priority used to determine which button should be picked to fill empty spaces for disabled
39
+ * buttons.
40
+ */
41
+export const MAIN_TOOLBAR_BUTTONS_PRIORITY = [
42
+    'microphone',
43
+    'camera',
44
+    'desktop',
45
+    'chat',
46
+    'raisehand',
47
+    'reactions',
48
+    'participants-pane',
49
+    'tileview',
50
+    'invite',
51
+    'toggle-camera',
52
+    'videoquality',
53
+    'fullscreen',
54
+    'security',
55
+    'closedcaptions',
56
+    'recording',
57
+    'livestreaming',
58
+    'linktosalesforce',
59
+    'sharedvideo',
60
+    'shareaudio',
61
+    'noisesuppression',
62
+    'whiteboard',
63
+    'etherpad',
64
+    'select-background',
65
+    'stats',
66
+    'settings',
67
+    'shortcuts',
68
+    'profile',
69
+    'embedmeeting',
70
+    'feedback',
71
+    'download',
72
+    'help'
73
+];
38 74
 
39 75
 export const TOOLBAR_TIMEOUT = 4000;
40 76
 

+ 19
- 20
react/features/toolbox/functions.web.ts View File

@@ -271,7 +271,7 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
271 271
         group: 2
272 272
     };
273 273
 
274
-    const videoQuality = {
274
+    const videoquality = {
275 275
         key: 'videoquality',
276 276
         Content: VideoQualityButton,
277 277
         group: 2
@@ -285,12 +285,11 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
285 285
 
286 286
     const security = {
287 287
         key: 'security',
288
-        alias: 'info',
289 288
         Content: SecurityDialogButton,
290 289
         group: 2
291 290
     };
292 291
 
293
-    const cc = {
292
+    const closedcaptions = {
294 293
         key: 'closedcaptions',
295 294
         Content: ClosedCaptionButton,
296 295
         group: 2
@@ -308,25 +307,25 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
308 307
         group: 2
309 308
     };
310 309
 
311
-    const linkToSalesforce = {
310
+    const linktosalesforce = {
312 311
         key: 'linktosalesforce',
313 312
         Content: LinkToSalesforceButton,
314 313
         group: 2
315 314
     };
316 315
 
317
-    const shareVideo = {
316
+    const sharedvideo = {
318 317
         key: 'sharedvideo',
319 318
         Content: SharedVideoButton,
320 319
         group: 3
321 320
     };
322 321
 
323
-    const shareAudio = {
322
+    const shareaudio = {
324 323
         key: 'shareaudio',
325 324
         Content: ShareAudioButton,
326 325
         group: 3
327 326
     };
328 327
 
329
-    const noiseSuppression = {
328
+    const noisesuppression = {
330 329
         key: 'noisesuppression',
331 330
         Content: NoiseSuppressionButton,
332 331
         group: 3
@@ -351,7 +350,7 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
351 350
         group: 3
352 351
     };
353 352
 
354
-    const speakerStats = {
353
+    const stats = {
355 354
         key: 'stats',
356 355
         Content: SpeakerStatsButton,
357 356
         group: 3
@@ -369,7 +368,7 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
369 368
         group: 4
370 369
     };
371 370
 
372
-    const embed = {
371
+    const embedmeeting = {
373 372
         key: 'embedmeeting',
374 373
         Content: EmbedMeetingButton,
375 374
         group: 4
@@ -415,27 +414,27 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
415 414
         chat,
416 415
         raisehand,
417 416
         reactions,
418
-        participants,
417
+        'participants-pane': participants,
419 418
         invite,
420 419
         tileview,
421
-        toggleCamera,
422
-        videoQuality,
420
+        'toggle-camera': toggleCamera,
421
+        videoquality,
423 422
         fullscreen,
424 423
         security,
425
-        cc,
424
+        closedcaptions,
426 425
         recording,
427 426
         livestreaming,
428
-        linkToSalesforce,
429
-        shareVideo,
430
-        shareAudio,
431
-        noiseSuppression,
427
+        linktosalesforce,
428
+        sharedvideo,
429
+        shareaudio,
430
+        noisesuppression,
432 431
         whiteboard,
433 432
         etherpad,
434
-        virtualBackground,
435
-        speakerStats,
433
+        'select-background': virtualBackground,
434
+        stats,
436 435
         settings,
437 436
         shortcuts,
438
-        embed,
437
+        embedmeeting,
439 438
         feedback,
440 439
         download,
441 440
         help,

+ 4
- 0
react/features/toolbox/middleware.web.ts View File

@@ -16,6 +16,7 @@ import {
16 16
     SET_TOOLBAR_BUTTONS,
17 17
     SET_TOOLBOX_TIMEOUT
18 18
 } from './actionTypes';
19
+import { setMainToolbarThresholds } from './actions.web';
19 20
 import { TOOLBAR_BUTTONS, VISITORS_MODE_BUTTONS } from './constants';
20 21
 import { NOTIFY_CLICK_MODE } from './types';
21 22
 
@@ -53,6 +54,9 @@ MiddlewareRegistry.register(store => next => action => {
53 54
             } = state['features/base/config'];
54 55
 
55 56
             batch(() => {
57
+                if (action.type !== I_AM_VISITOR_MODE) {
58
+                    dispatch(setMainToolbarThresholds());
59
+                }
56 60
                 dispatch({
57 61
                     type: SET_BUTTONS_WITH_NOTIFY_CLICK,
58 62
                     buttonsWithNotifyClick: _buildButtonsArray(buttonsWithNotifyClick, customToolbarButtons)

+ 15
- 1
react/features/toolbox/reducer.ts View File

@@ -6,6 +6,7 @@ import {
6 6
     FULL_SCREEN_CHANGED,
7 7
     SET_BUTTONS_WITH_NOTIFY_CLICK,
8 8
     SET_HANGUP_MENU_VISIBLE,
9
+    SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
9 10
     SET_OVERFLOW_DRAWER,
10 11
     SET_OVERFLOW_MENU_VISIBLE,
11 12
     SET_PARTICIPANT_MENU_BUTTONS_WITH_NOTIFY_CLICK,
@@ -17,7 +18,8 @@ import {
17 18
     SET_TOOLBOX_VISIBLE,
18 19
     TOGGLE_TOOLBOX_VISIBLE
19 20
 } from './actionTypes';
20
-import { NOTIFY_CLICK_MODE } from './types';
21
+import { THRESHOLDS } from './constants';
22
+import { IMainToolbarButtonThresholds, NOTIFY_CLICK_MODE } from './types';
21 23
 
22 24
 /**
23 25
  * Initial state of toolbox's part of Redux store.
@@ -47,6 +49,11 @@ const INITIAL_STATE = {
47 49
      */
48 50
     hovered: false,
49 51
 
52
+    /**
53
+     * The thresholds for screen size and visible main toolbar buttons.
54
+     */
55
+    mainToolbarButtonsThresholds: THRESHOLDS,
56
+
50 57
     participantMenuButtonsWithNotifyClick: new Map(),
51 58
 
52 59
     /**
@@ -98,6 +105,7 @@ export interface IToolboxState {
98 105
     fullScreen?: boolean;
99 106
     hangupMenuVisible: boolean;
100 107
     hovered: boolean;
108
+    mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
101 109
     overflowDrawer: boolean;
102 110
     overflowMenuVisible: boolean;
103 111
     participantMenuButtonsWithNotifyClick: Map<string, NOTIFY_CLICK_MODE>;
@@ -151,6 +159,12 @@ ReducerRegistry.register<IToolboxState>(
151 159
                 ...state,
152 160
                 buttonsWithNotifyClick: action.buttonsWithNotifyClick
153 161
             };
162
+
163
+        case SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS:
164
+            return {
165
+                ...state,
166
+                mainToolbarButtonsThresholds: action.mainToolbarButtonsThresholds
167
+            };
154 168
         case SET_TOOLBAR_HOVERED:
155 169
             return {
156 170
                 ...state,

+ 5
- 1
react/features/toolbox/types.ts View File

@@ -2,7 +2,6 @@ import { ComponentType } from 'react';
2 2
 
3 3
 export interface IToolboxButton {
4 4
     Content: ComponentType<any>;
5
-    alias?: string;
6 5
     group: number;
7 6
     key: string;
8 7
 }
@@ -49,3 +48,8 @@ export enum NOTIFY_CLICK_MODE {
49 48
     ONLY_NOTIFY = 'ONLY_NOTIFY',
50 49
     PREVENT_AND_NOTIFY = 'PREVENT_AND_NOTIFY'
51 50
 }
51
+
52
+export type IMainToolbarButtonThresholds = Array<{
53
+    order: Array<ToolbarButton | string>;
54
+    width: number;
55
+}>;

Loading…
Cancel
Save