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.
This commit is contained in:
Hristo Terezov
2024-05-17 09:53:08 -05:00
parent 9af0003c63
commit 0913554af9
11 changed files with 205 additions and 50 deletions

View File

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

View File

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

View File

@@ -185,6 +185,7 @@ export default [
'localRecording',
'localSubject',
'logging',
'mainToolbarButtons',
'maxFullResolutionParticipants',
'mouseMoveCallbackInterval',
'notifications',

View File

@@ -59,6 +59,16 @@ export const SET_FULL_SCREEN = 'SET_FULL_SCREEN';
*/
export const SET_HANGUP_MENU_VISIBLE = 'SET_HANGUP_MENU_VISIBLE';
/**
* The type of the (redux) action which sets the main toolbar thresholds.
*
* {
* type: SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
* mainToolbarButtonsThresholds: IMainToolbarButtonThresholds
* }
*/
export const SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS = 'SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS';
/**
* The type of the redux action that toggles whether the overflow menu(s) should be shown as drawers.
*/

View File

@@ -8,13 +8,16 @@ import {
FULL_SCREEN_CHANGED,
SET_FULL_SCREEN,
SET_HANGUP_MENU_VISIBLE,
SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
SET_OVERFLOW_DRAWER,
SET_OVERFLOW_MENU_VISIBLE,
SET_TOOLBAR_HOVERED,
SET_TOOLBOX_TIMEOUT
} from './actionTypes';
import { setToolboxVisible } from './actions.web';
import { THRESHOLDS } from './constants';
import { getToolbarTimeout } from './functions.web';
import { IMainToolbarButtonThresholds } from './types';
export * from './actions.any';
@@ -121,6 +124,56 @@ export function setFullScreen(fullScreen: boolean) {
};
}
/**
* Sets the mainToolbarButtonsThresholds.
*
* @returns {Function}
*/
export function setMainToolbarThresholds() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const { mainToolbarButtons } = getState()['features/base/config'];
if (!mainToolbarButtons || !Array.isArray(mainToolbarButtons) || mainToolbarButtons.length === 0) {
return;
}
const mainToolbarButtonsThresholds: IMainToolbarButtonThresholds = [];
const mainToolbarButtonsLenghtMap = new Map();
let orderIsChanged = false;
mainToolbarButtons.forEach(buttons => {
if (!Array.isArray(buttons) || buttons.length === 0) {
return;
}
mainToolbarButtonsLenghtMap.set(buttons.length, buttons);
});
THRESHOLDS.forEach(({ width, order }) => {
let finalOrder = mainToolbarButtonsLenghtMap.get(order.length);
if (finalOrder) {
orderIsChanged = true;
} else {
finalOrder = order;
}
mainToolbarButtonsThresholds.push({
order: finalOrder,
width
});
});
if (orderIsChanged) {
dispatch({
type: SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
mainToolbarButtonsThresholds
});
}
};
}
/**
* Shows the toolbox for specified timeout.
*

View File

@@ -15,7 +15,7 @@ import {
setToolbarHovered,
showToolbox
} from '../../actions.web';
import { NOT_APPLICABLE, THRESHOLDS } from '../../constants';
import { MAIN_TOOLBAR_BUTTONS_PRIORITY } from '../../constants';
import {
getAllToolboxButtons,
getJwtDisabledButtons,
@@ -23,7 +23,7 @@ import {
isToolboxVisible
} from '../../functions.web';
import { useKeyboardShortcuts } from '../../hooks.web';
import { IToolboxButton, NOTIFY_CLICK_MODE } from '../../types';
import { IToolboxButton, NOTIFY_CLICK_MODE, ToolbarButton } from '../../types';
import HangupButton from '../HangupButton';
import { EndConferenceButton } from './EndConferenceButton';
@@ -92,6 +92,14 @@ interface IProps extends WithTranslation {
*/
_jwtDisabledButtons: string[];
/**
* The main toolbar buttons thresholds used to determine the visible buttons depending on the current screen size.
*/
_mainToolbarButtonsThresholds: Array<{
order: Array<ToolbarButton | string>;
width: number;
}>;
/**
* Whether or not the overflow menu is displayed in a drawer drawer.
*/
@@ -174,6 +182,7 @@ const Toolbox = ({
_isMobile,
_isNarrowLayout,
_jwtDisabledButtons,
_mainToolbarButtonsThresholds,
_overflowDrawer,
_overflowMenuVisible,
_reactionsButtonEnabled,
@@ -280,35 +289,42 @@ const Toolbox = ({
function getVisibleButtons() {
const buttons = getAllToolboxButtons(_customToolbarButtons);
const filteredButtons = Object.keys(buttons).filter(key =>
typeof key !== 'undefined' // filter invalid buttons that may be comming from config.mainToolbarButtons
// override
&& !_jwtDisabledButtons.includes(key)
&& isButtonEnabled(key, _toolbarButtons));
setButtonsNotifyClickMode(buttons);
const isHangupVisible = isButtonEnabled('hangup', _toolbarButtons);
const { order } = THRESHOLDS.find(({ width }) => _clientWidth > width)
|| THRESHOLDS[THRESHOLDS.length - 1];
const { order } = _mainToolbarButtonsThresholds.find(({ width }) => _clientWidth > width)
|| _mainToolbarButtonsThresholds[_mainToolbarButtonsThresholds.length - 1];
const keys = Object.keys(buttons);
const mainToolbarButtonKeysOrder = [
...order.filter(key => filteredButtons.includes(key)),
...MAIN_TOOLBAR_BUTTONS_PRIORITY.filter(key => !order.includes(key) && filteredButtons.includes(key)),
...filteredButtons.filter(key => !order.includes(key) && !MAIN_TOOLBAR_BUTTONS_PRIORITY.includes(key))
];
const filtered = [
...order.map(key => buttons[key as keyof typeof buttons]),
...Object.values(buttons).filter((button, index) => !order.includes(keys[index]))
].filter(({ key, alias = NOT_APPLICABLE }) =>
!_jwtDisabledButtons.includes(key)
&& (isButtonEnabled(key, _toolbarButtons) || isButtonEnabled(alias, _toolbarButtons))
);
const mainButtonsKeys = mainToolbarButtonKeysOrder.slice(0, order.length);
const overflowMenuButtons = filteredButtons.reduce((acc, key) => {
if (!mainButtonsKeys.includes(key)) {
acc.push(buttons[key]);
}
let sliceIndex = _overflowDrawer || _reactionsButtonEnabled ? order.length + 2 : order.length + 1;
return acc;
}, [] as IToolboxButton[]);
if (isHangupVisible) {
sliceIndex -= 1;
}
// if we have 1 button in the overflow menu it is better to directly display it in the main toolbar by replacing
// the "More" menu button with it.
if (overflowMenuButtons.length === 1) {
const button = overflowMenuButtons.shift()?.key;
// This implies that the overflow button will be displayed, so save some space for it.
if (sliceIndex < filtered.length) {
sliceIndex -= 1;
button && mainButtonsKeys.push(button);
}
return {
mainMenuButtons: filtered.slice(0, sliceIndex),
overflowMenuButtons: filtered.slice(sliceIndex)
mainMenuButtons: mainButtonsKeys.map(key => buttons[key]),
overflowMenuButtons
};
}
@@ -505,6 +521,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
_jwtDisabledButtons: getJwtDisabledButtons(state),
_hangupMenuVisible: hangupMenuVisible,
_isNarrowLayout: isNarrowLayout,
_mainToolbarButtonsThresholds: state['features/toolbox'].mainToolbarButtonsThresholds,
_overflowMenuVisible: overflowMenuVisible,
_overflowDrawer: overflowDrawer,
_reactionsButtonEnabled: isReactionsButtonEnabled(state),

View File

@@ -6,23 +6,23 @@ import { ToolbarButton } from './types';
export const THRESHOLDS = [
{
width: 565,
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'reactions', 'participants', 'tileview' ]
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'reactions', 'participants-pane', 'tileview' ]
},
{
width: 520,
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants', 'tileview' ]
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants-pane', 'tileview' ]
},
{
width: 470,
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants' ]
order: [ 'microphone', 'camera', 'desktop', 'chat', 'raisehand', 'participants-pane' ]
},
{
width: 420,
order: [ 'microphone', 'camera', 'desktop', 'chat', 'participants' ]
order: [ 'microphone', 'camera', 'desktop', 'chat', 'participants-pane' ]
},
{
width: 370,
order: [ 'microphone', 'camera', 'chat', 'participants' ]
order: [ 'microphone', 'camera', 'chat', 'participants-pane' ]
},
{
width: 225,
@@ -34,7 +34,43 @@ export const THRESHOLDS = [
}
];
export const NOT_APPLICABLE = 'N/A';
/**
* Main toolbar buttons priority used to determine which button should be picked to fill empty spaces for disabled
* buttons.
*/
export const MAIN_TOOLBAR_BUTTONS_PRIORITY = [
'microphone',
'camera',
'desktop',
'chat',
'raisehand',
'reactions',
'participants-pane',
'tileview',
'invite',
'toggle-camera',
'videoquality',
'fullscreen',
'security',
'closedcaptions',
'recording',
'livestreaming',
'linktosalesforce',
'sharedvideo',
'shareaudio',
'noisesuppression',
'whiteboard',
'etherpad',
'select-background',
'stats',
'settings',
'shortcuts',
'profile',
'embedmeeting',
'feedback',
'download',
'help'
];
export const TOOLBAR_TIMEOUT = 4000;

View File

@@ -271,7 +271,7 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
group: 2
};
const videoQuality = {
const videoquality = {
key: 'videoquality',
Content: VideoQualityButton,
group: 2
@@ -285,12 +285,11 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
const security = {
key: 'security',
alias: 'info',
Content: SecurityDialogButton,
group: 2
};
const cc = {
const closedcaptions = {
key: 'closedcaptions',
Content: ClosedCaptionButton,
group: 2
@@ -308,25 +307,25 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
group: 2
};
const linkToSalesforce = {
const linktosalesforce = {
key: 'linktosalesforce',
Content: LinkToSalesforceButton,
group: 2
};
const shareVideo = {
const sharedvideo = {
key: 'sharedvideo',
Content: SharedVideoButton,
group: 3
};
const shareAudio = {
const shareaudio = {
key: 'shareaudio',
Content: ShareAudioButton,
group: 3
};
const noiseSuppression = {
const noisesuppression = {
key: 'noisesuppression',
Content: NoiseSuppressionButton,
group: 3
@@ -351,7 +350,7 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
group: 3
};
const speakerStats = {
const stats = {
key: 'stats',
Content: SpeakerStatsButton,
group: 3
@@ -369,7 +368,7 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
group: 4
};
const embed = {
const embedmeeting = {
key: 'embedmeeting',
Content: EmbedMeetingButton,
group: 4
@@ -415,27 +414,27 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
chat,
raisehand,
reactions,
participants,
'participants-pane': participants,
invite,
tileview,
toggleCamera,
videoQuality,
'toggle-camera': toggleCamera,
videoquality,
fullscreen,
security,
cc,
closedcaptions,
recording,
livestreaming,
linkToSalesforce,
shareVideo,
shareAudio,
noiseSuppression,
linktosalesforce,
sharedvideo,
shareaudio,
noisesuppression,
whiteboard,
etherpad,
virtualBackground,
speakerStats,
'select-background': virtualBackground,
stats,
settings,
shortcuts,
embed,
embedmeeting,
feedback,
download,
help,

View File

@@ -16,6 +16,7 @@ import {
SET_TOOLBAR_BUTTONS,
SET_TOOLBOX_TIMEOUT
} from './actionTypes';
import { setMainToolbarThresholds } from './actions.web';
import { TOOLBAR_BUTTONS, VISITORS_MODE_BUTTONS } from './constants';
import { NOTIFY_CLICK_MODE } from './types';
@@ -53,6 +54,9 @@ MiddlewareRegistry.register(store => next => action => {
} = state['features/base/config'];
batch(() => {
if (action.type !== I_AM_VISITOR_MODE) {
dispatch(setMainToolbarThresholds());
}
dispatch({
type: SET_BUTTONS_WITH_NOTIFY_CLICK,
buttonsWithNotifyClick: _buildButtonsArray(buttonsWithNotifyClick, customToolbarButtons)

View File

@@ -6,6 +6,7 @@ import {
FULL_SCREEN_CHANGED,
SET_BUTTONS_WITH_NOTIFY_CLICK,
SET_HANGUP_MENU_VISIBLE,
SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
SET_OVERFLOW_DRAWER,
SET_OVERFLOW_MENU_VISIBLE,
SET_PARTICIPANT_MENU_BUTTONS_WITH_NOTIFY_CLICK,
@@ -17,7 +18,8 @@ import {
SET_TOOLBOX_VISIBLE,
TOGGLE_TOOLBOX_VISIBLE
} from './actionTypes';
import { NOTIFY_CLICK_MODE } from './types';
import { THRESHOLDS } from './constants';
import { IMainToolbarButtonThresholds, NOTIFY_CLICK_MODE } from './types';
/**
* Initial state of toolbox's part of Redux store.
@@ -47,6 +49,11 @@ const INITIAL_STATE = {
*/
hovered: false,
/**
* The thresholds for screen size and visible main toolbar buttons.
*/
mainToolbarButtonsThresholds: THRESHOLDS,
participantMenuButtonsWithNotifyClick: new Map(),
/**
@@ -98,6 +105,7 @@ export interface IToolboxState {
fullScreen?: boolean;
hangupMenuVisible: boolean;
hovered: boolean;
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
overflowDrawer: boolean;
overflowMenuVisible: boolean;
participantMenuButtonsWithNotifyClick: Map<string, NOTIFY_CLICK_MODE>;
@@ -151,6 +159,12 @@ ReducerRegistry.register<IToolboxState>(
...state,
buttonsWithNotifyClick: action.buttonsWithNotifyClick
};
case SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS:
return {
...state,
mainToolbarButtonsThresholds: action.mainToolbarButtonsThresholds
};
case SET_TOOLBAR_HOVERED:
return {
...state,

View File

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