diff --git a/config.js b/config.js index 68e76b77bd..9d16dac2f4 100644 --- a/config.js +++ b/config.js @@ -924,6 +924,11 @@ var config = { // [ 'microphone', 'camera' ] // ], + // Overrides the buttons displayed in the main toolbar for reduced UI. + // When there isn't an override for a certain configuration the default jitsi-meet configuration will be used. + // The order of the buttons in the array is preserved. + // reducedUImainToolbarButtons: [ '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 diff --git a/react/features/base/config/configType.ts b/react/features/base/config/configType.ts index f21190bd27..7351c3d448 100644 --- a/react/features/base/config/configType.ts +++ b/react/features/base/config/configType.ts @@ -559,6 +559,7 @@ export interface IConfig { skipConsentInMeeting?: boolean; suggestRecording?: boolean; }; + reducedUImainToolbarButtons?: Array; remoteVideoMenu?: { disableDemote?: boolean; disableGrantModerator?: boolean; diff --git a/react/features/base/config/configWhitelist.ts b/react/features/base/config/configWhitelist.ts index f86158f545..653ca730a2 100644 --- a/react/features/base/config/configWhitelist.ts +++ b/react/features/base/config/configWhitelist.ts @@ -215,6 +215,7 @@ export default [ 'recordings.showPrejoinWarning', 'recordings.showRecordingLink', 'recordings.suggestRecording', + 'reducedUImainToolbarButtons', 'replaceParticipant', 'resolution', 'screenshotCapture', diff --git a/react/features/base/responsive-ui/actions.ts b/react/features/base/responsive-ui/actions.ts index 12d774d185..7b94afe41f 100644 --- a/react/features/base/responsive-ui/actions.ts +++ b/react/features/base/responsive-ui/actions.ts @@ -24,6 +24,7 @@ import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants'; * determine whether and how to render it. */ const REDUCED_UI_THRESHOLD = 300; +const WEB_REDUCED_UI_THRESHOLD = 320; /** * Indicates a resize of the window. @@ -49,6 +50,8 @@ export function clientResized(clientWidth: number, clientHeight: number) { } availableWidth -= getParticipantsPaneWidth(state); + + dispatch(setReducedUI(availableWidth, clientHeight)); } batch(() => { @@ -106,7 +109,10 @@ export function setAspectRatio(width: number, height: number) { */ export function setReducedUI(width: number, height: number) { return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { - const reducedUI = Math.min(width, height) < REDUCED_UI_THRESHOLD; + const threshold = navigator.product === 'ReactNative' + ? REDUCED_UI_THRESHOLD + : WEB_REDUCED_UI_THRESHOLD; + const reducedUI = Math.min(width, height) < threshold; if (reducedUI !== getState()['features/base/responsive-ui'].reducedUI) { return dispatch({ diff --git a/react/features/chat/components/web/Chat.tsx b/react/features/chat/components/web/Chat.tsx index 821a5e9a8d..8d4fdb8e58 100644 --- a/react/features/chat/components/web/Chat.tsx +++ b/react/features/chat/components/web/Chat.tsx @@ -78,6 +78,11 @@ interface IProps extends AbstractProps { */ _isResizing: boolean; + /** + * The indicator which determines whether the UI is reduced. + */ + _reducedUI: boolean; + /** * Whether or not to block chat access with a nickname input form. */ @@ -227,6 +232,7 @@ const Chat = ({ _focusedTab, _isResizing, _messages, + _reducedUI, _unreadMessagesCount, _unreadPollsCount, _unreadFilesCount, @@ -567,6 +573,10 @@ const Chat = ({ ); } + if (_reducedUI) { + return null; + } + return ( _isOpen ?
{ _layoutClassName, _notificationsVisible, _overflowDrawer, + _reducedUI, _showLobby, _showPrejoin, _showVisitorsQueue, t } = this.props; + if (_reducedUI) { + return ( +
+ +
+ + +
+ +
+ + { t('toolbar.accessibilityLabel.heading') } + + +
+
+ ); + } + return (
{ function _mapStateToProps(state: IReduxState) { const { backgroundAlpha, mouseMoveCallbackInterval } = state['features/base/config']; const { overflowDrawer } = state['features/toolbox']; + const { reducedUI } = state['features/base/responsive-ui']; return { ...abstractMapStateToProps(state), @@ -426,6 +465,7 @@ function _mapStateToProps(state: IReduxState) { _layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state) ?? ''], _mouseMoveCallbackInterval: mouseMoveCallbackInterval, _overflowDrawer: overflowDrawer, + _reducedUI: reducedUI, _roomName: getConferenceNameForTitle(state), _showLobby: getIsLobbyVisible(state), _showPrejoin: isPrejoinPageVisible(state), diff --git a/react/features/conference/components/web/ConferenceInfo.tsx b/react/features/conference/components/web/ConferenceInfo.tsx index a6906590c2..83780cd924 100644 --- a/react/features/conference/components/web/ConferenceInfo.tsx +++ b/react/features/conference/components/web/ConferenceInfo.tsx @@ -34,6 +34,11 @@ interface IProps { autoHide?: string[]; }; + /** + * The indicator which determines whether the UI is reduced. + */ + _reducedUI: boolean; + /** * Indicates whether the component should be visible or not. */ @@ -194,6 +199,12 @@ class ConferenceInfo extends Component { * @returns {ReactElement} */ override render() { + const { _reducedUI } = this.props; + + if (_reducedUI) { + return null; + } + return (
{ * }} */ function _mapStateToProps(state: IReduxState) { + const { reducedUI } = state['features/base/responsive-ui']; + return { + _conferenceInfo: getConferenceInfo(state), + _reducedUI: reducedUI, _visible: isToolboxVisible(state), - _conferenceInfo: getConferenceInfo(state) }; } diff --git a/react/features/conference/components/web/Notice.tsx b/react/features/conference/components/web/Notice.tsx index 7832aaa8b4..b6fec94210 100644 --- a/react/features/conference/components/web/Notice.tsx +++ b/react/features/conference/components/web/Notice.tsx @@ -25,9 +25,10 @@ const useStyles = makeStyles()(theme => { const Notice = () => { const message = useSelector((state: IReduxState) => state['features/base/config'].noticeMessage); + const { reducedUI } = useSelector((state: IReduxState) => state['features/base/responsive-ui']); const { classes } = useStyles(); - if (!message) { + if (!message || reducedUI) { return null; } diff --git a/react/features/toolbox/components/web/Toolbox.tsx b/react/features/toolbox/components/web/Toolbox.tsx index 0c893195b7..01ddd33160 100644 --- a/react/features/toolbox/components/web/Toolbox.tsx +++ b/react/features/toolbox/components/web/Toolbox.tsx @@ -19,6 +19,7 @@ import { import { getJwtDisabledButtons, getVisibleButtons, + getVisibleButtonsForReducedUI, isButtonEnabled, isToolboxVisible } from '../../functions.web'; @@ -82,8 +83,7 @@ export default function Toolbox({ const isNarrowLayout = useSelector((state: IReduxState) => state['features/base/responsive-ui'].isNarrowLayout); const videoSpaceWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].videoSpaceWidth); const isModerator = useSelector(isLocalParticipantModerator); - const customToolbarButtons = useSelector( - (state: IReduxState) => state['features/base/config'].customToolbarButtons); + const customToolbarButtons = useSelector((state: IReduxState) => state['features/base/config'].customToolbarButtons); const iAmRecorder = useSelector((state: IReduxState) => state['features/base/config'].iAmRecorder); const iAmSipGateway = useSelector((state: IReduxState) => state['features/base/config'].iAmSipGateway); const overflowDrawer = useSelector((state: IReduxState) => state['features/toolbox'].overflowDrawer); @@ -110,6 +110,8 @@ export default function Toolbox({ const toolbarVisible = useSelector(isToolboxVisible); const mainToolbarButtonsThresholds = useSelector((state: IReduxState) => state['features/toolbox'].mainToolbarButtonsThresholds); + const { reducedUImainToolbarButtons } = useSelector((state: IReduxState) => state['features/base/config']); + const reducedUI = useSelector((state: IReduxState) => state['features/base/responsive-ui'].reducedUI); const allButtons = useToolboxButtons(customToolbarButtons); const isMobile = isMobileBrowser(); const endConferenceSupported = Boolean(conference?.isEndConferenceSupported() && isModerator); @@ -233,7 +235,7 @@ export default function Toolbox({ const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu'; const containerClassName = `toolbox-content${isMobile || isNarrowLayout ? ' toolbox-content-mobile' : ''}`; - const { mainMenuButtons, overflowMenuButtons } = getVisibleButtons({ + const normalUIButtons = getVisibleButtons({ allButtons, buttonsWithNotifyClick, toolbarButtons: toolbarButtonsToUse, @@ -241,6 +243,20 @@ export default function Toolbox({ jwtDisabledButtons, mainToolbarButtonsThresholds }); + + const reducedUIButtons = getVisibleButtonsForReducedUI({ + allButtons, + buttonsWithNotifyClick, + jwtDisabledButtons, + reducedUImainToolbarButtons, + }); + + const mainMenuButtons = reducedUI + ? reducedUIButtons.mainMenuButtons + : normalUIButtons.mainMenuButtons; + const overflowMenuButtons = reducedUI + ? [] + : normalUIButtons.overflowMenuButtons; const raiseHandInOverflowMenu = overflowMenuButtons.some(({ key }) => key === 'raisehand'); const showReactionsInOverflowMenu = _shouldDisplayReactionsButtons && ( diff --git a/react/features/toolbox/constants.ts b/react/features/toolbox/constants.ts index 37f1e7f7c6..4ed35f942d 100644 --- a/react/features/toolbox/constants.ts +++ b/react/features/toolbox/constants.ts @@ -12,6 +12,8 @@ export const DUMMY_9_BUTTONS_THRESHOLD_VALUE = Symbol('9_BUTTONS_THRESHOLD_VALUE */ export const DUMMY_10_BUTTONS_THRESHOLD_VALUE = Symbol('10_BUTTONS_THRESHOLD_VALUE'); +export const DEFAULT_REDUCED_UI_MAIN_TOOLBAR_BUTTONS = [ 'microphone', 'camera' ]; + /** * Thresholds for displaying toolbox buttons. */ diff --git a/react/features/toolbox/functions.web.ts b/react/features/toolbox/functions.web.ts index 0934429691..0d5e2dcd7f 100644 --- a/react/features/toolbox/functions.web.ts +++ b/react/features/toolbox/functions.web.ts @@ -6,9 +6,9 @@ import { IGUMPendingState } from '../base/media/types'; import { isScreenMediaShared } from '../screen-share/functions'; import { isWhiteboardVisible } from '../whiteboard/functions'; -import { MAIN_TOOLBAR_BUTTONS_PRIORITY, TOOLBAR_TIMEOUT } from './constants'; +import { DEFAULT_REDUCED_UI_MAIN_TOOLBAR_BUTTONS, MAIN_TOOLBAR_BUTTONS_PRIORITY, TOOLBAR_TIMEOUT } from './constants'; import { isButtonEnabled } from './functions.any'; -import { IGetVisibleButtonsParams, IToolboxButton, NOTIFY_CLICK_MODE } from './types'; +import { IGetVisibleButtonsForReducedUIParams, IGetVisibleButtonsParams, IToolboxButton, NOTIFY_CLICK_MODE } from './types'; export * from './functions.any'; @@ -201,6 +201,41 @@ export function getVisibleButtons({ }; } +/** + * Returns buttons that need to be rendered for reduced UI mode. + * + * @param {IGetVisibleButtonsForReducedUIParams} params - The parameters needed to extract the visible buttons. + * @returns {Object} - The visible buttons for reduced ui. + */ +export function getVisibleButtonsForReducedUI({ + allButtons, + buttonsWithNotifyClick, + jwtDisabledButtons, + reducedUImainToolbarButtons +}: IGetVisibleButtonsForReducedUIParams) { + setButtonsNotifyClickMode(allButtons, buttonsWithNotifyClick); + + if (!Array.isArray(reducedUImainToolbarButtons) || reducedUImainToolbarButtons.length === 0) { + const defaultButtons = DEFAULT_REDUCED_UI_MAIN_TOOLBAR_BUTTONS.map(key => allButtons[key]); + + return { + mainMenuButtons: defaultButtons + }; + } + + const filteredButtons = reducedUImainToolbarButtons.filter(key => + typeof key !== 'undefined' + && !jwtDisabledButtons.includes(key) + && isButtonEnabled(key, reducedUImainToolbarButtons) + && allButtons[key]); + + const mainMenuButtons = filteredButtons.map(key => allButtons[key]); + + return { + mainMenuButtons + }; +} + /** * Returns the list of participant menu buttons that have that notify the api when clicked. * diff --git a/react/features/toolbox/types.ts b/react/features/toolbox/types.ts index 84f0dcc600..81ff614212 100644 --- a/react/features/toolbox/types.ts +++ b/react/features/toolbox/types.ts @@ -107,3 +107,10 @@ export interface IGetVisibleButtonsParams { mainToolbarButtonsThresholds: IMainToolbarButtonThresholds; toolbarButtons: string[]; } + +export interface IGetVisibleButtonsForReducedUIParams { + allButtons: { [key: string]: IToolboxButton; }; + buttonsWithNotifyClick: Map; + jwtDisabledButtons: string[]; + reducedUImainToolbarButtons?: string[]; +}