diff --git a/ios/app/src/AppDelegate.m b/ios/app/src/AppDelegate.m index 6fe43f6ef1..87e9ecc51d 100644 --- a/ios/app/src/AppDelegate.m +++ b/ios/app/src/AppDelegate.m @@ -40,7 +40,7 @@ jitsiMeet.defaultConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) { // For testing configOverrides a room needs to be set - // builder.room = @"test0988test"; + // builder.room = @"https://meet.jit.si/test0988test"; [builder setFeatureFlag:@"welcomepage.enabled" withBoolean:YES]; [builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES]; diff --git a/react/features/app/middlewares.native.ts b/react/features/app/middlewares.native.ts index c951be56f0..15b72b4bd7 100644 --- a/react/features/app/middlewares.native.ts +++ b/react/features/app/middlewares.native.ts @@ -13,6 +13,7 @@ import '../mobile/react-native-sdk/middleware'; import '../mobile/watchos/middleware'; import '../share-room/middleware'; import '../shared-video/middleware'; +import '../toolbox/middleware.native'; import '../whiteboard/middleware.native'; import './middlewares.any'; diff --git a/react/features/base/devices/functions.web.ts b/react/features/base/devices/functions.web.ts index 908e4e946e..84d48184ad 100644 --- a/react/features/base/devices/functions.web.ts +++ b/react/features/base/devices/functions.web.ts @@ -1,3 +1,5 @@ +/* eslint-disable require-jsdoc */ + import { IReduxState, IStore } from '../../app/types'; import JitsiMeetJS from '../lib-jitsi-meet'; import { updateSettings } from '../settings/actions'; @@ -157,7 +159,8 @@ export function getDevicesFromURL(state: IReduxState) { * @returns {Object} An object with the media devices split by type. The keys * are device type and the values are arrays with devices matching the device * type. - */ +*/ +// @ts-ignore export function groupDevicesByKind(devices: MediaDeviceInfo[]): IDevicesState['availableDevices'] { return { audioInput: devices.filter(device => device.kind === 'audioinput'), @@ -172,7 +175,10 @@ export function groupDevicesByKind(devices: MediaDeviceInfo[]): IDevicesState['a * @param {MediaDeviceInfo[]} devices - The devices to be filtered. * @returns {MediaDeviceInfo[]} - The filtered devices. */ +// @ts-ignore export function filterIgnoredDevices(devices: MediaDeviceInfo[] = []) { + + // @ts-ignore const ignoredDevices: MediaDeviceInfo[] = []; const filteredDevices = devices.filter(device => { if (!device.label) { @@ -201,6 +207,7 @@ export function filterIgnoredDevices(devices: MediaDeviceInfo[] = []) { * @param {MediaDeviceInfo[]} devices2 - Array with devices to be compared. * @returns {boolean} - True if the device arrays are different and false otherwise. */ +// @ts-ignore export function areDevicesDifferent(devices1: MediaDeviceInfo[] = [], devices2: MediaDeviceInfo[] = []) { if (devices1.length !== devices2.length) { return true; @@ -304,6 +311,7 @@ export function getVideoDeviceIds(state: IReduxState) { * @param {MediaDeviceInfo[]} devices - The devices. * @returns {string} */ +// @ts-ignore function devicesToStr(devices?: MediaDeviceInfo[]) { return devices?.map(device => `\t\t${device.label}[${device.deviceId}]`).join('\n'); } @@ -315,6 +323,7 @@ function devicesToStr(devices?: MediaDeviceInfo[]) { * @param {string} title - The title that will be printed in the log. * @returns {void} */ +// @ts-ignore export function logDevices(devices: MediaDeviceInfo[], title = '') { const deviceList = groupDevicesByKind(devices); const audioInputs = devicesToStr(deviceList.audioInput); diff --git a/react/features/base/premeeting/components/web/PreMeetingScreen.tsx b/react/features/base/premeeting/components/web/PreMeetingScreen.tsx index ff3b829d70..e3845d88d6 100644 --- a/react/features/base/premeeting/components/web/PreMeetingScreen.tsx +++ b/react/features/base/premeeting/components/web/PreMeetingScreen.tsx @@ -5,7 +5,7 @@ import { makeStyles } from 'tss-react/mui'; import { IReduxState } from '../../../../app/types'; import DeviceStatus from '../../../../prejoin/components/web/preview/DeviceStatus'; -import { isRoomNameEnabled } from '../../../../prejoin/functions'; +import { isRoomNameEnabled } from '../../../../prejoin/functions.web'; import Toolbox from '../../../../toolbox/components/web/Toolbox'; import { isButtonEnabled } from '../../../../toolbox/functions.web'; import { getConferenceName } from '../../../conference/functions'; diff --git a/react/features/chat/components/web/MessageContainer.tsx b/react/features/chat/components/web/MessageContainer.tsx index dae176a3f6..b85c92964b 100644 --- a/react/features/chat/components/web/MessageContainer.tsx +++ b/react/features/chat/components/web/MessageContainer.tsx @@ -23,7 +23,7 @@ interface IState { /** * The id of the last read message. */ - lastReadMessageId: string; + lastReadMessageId: string | null; } /** diff --git a/react/features/filmstrip/components/native/Filmstrip.tsx b/react/features/filmstrip/components/native/Filmstrip.tsx index 6af180bb57..9f24a8a21a 100644 --- a/react/features/filmstrip/components/native/Filmstrip.tsx +++ b/react/features/filmstrip/components/native/Filmstrip.tsx @@ -8,8 +8,8 @@ import { getLocalParticipant } from '../../../base/participants/functions'; import Platform from '../../../base/react/Platform.native'; import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants'; import { getHideSelfView } from '../../../base/settings/functions.any'; -import { isToolboxVisible } from '../../../toolbox/functions'; -import { setVisibleRemoteParticipants } from '../../actions'; +import { isToolboxVisible } from '../../../toolbox/functions.native'; +import { setVisibleRemoteParticipants } from '../../actions.native'; import { getFilmstripDimensions, isFilmstripVisible, diff --git a/react/features/filmstrip/components/web/Filmstrip.tsx b/react/features/filmstrip/components/web/Filmstrip.tsx index 940a273b58..f5aa1111e1 100644 --- a/react/features/filmstrip/components/web/Filmstrip.tsx +++ b/react/features/filmstrip/components/web/Filmstrip.tsx @@ -26,7 +26,7 @@ import { setUserFilmstripWidth, setUserIsResizing, setVisibleRemoteParticipants -} from '../../actions'; +} from '../../actions.web'; import { ASPECT_RATIO_BREAKPOINT, DEFAULT_FILMSTRIP_WIDTH, @@ -39,10 +39,10 @@ import { } from '../../constants'; import { getVerticalViewMaxWidth, + isFilmstripDisabled, isStageFilmstripTopPanel, shouldRemoteVideosBeVisible -} from '../../functions'; -import { isFilmstripDisabled } from '../../functions.web'; +} from '../../functions.web'; import AudioTracksContainer from './AudioTracksContainer'; import Thumbnail from './Thumbnail'; diff --git a/react/features/reactions/components/native/RaiseHandContainerButtons.tsx b/react/features/reactions/components/native/RaiseHandContainerButtons.tsx new file mode 100644 index 0000000000..5e6d809fe8 --- /dev/null +++ b/react/features/reactions/components/native/RaiseHandContainerButtons.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; + +import { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton'; +import RaiseHandButton from '../../../toolbox/components/native/RaiseHandButton'; +import { shouldDisplayReactionsButtons } from '../../functions.native'; + +import ReactionsMenuButton from './ReactionsMenuButton'; + +const RaiseHandContainerButtons = (props: AbstractButtonProps) => { + const _shouldDisplayReactionsButtons = useSelector(shouldDisplayReactionsButtons); + + return _shouldDisplayReactionsButtons + ? + : ; +}; + +export default RaiseHandContainerButtons; diff --git a/react/features/toolbox/actions.any.ts b/react/features/toolbox/actions.any.ts index a89fa55871..7e4627367b 100644 --- a/react/features/toolbox/actions.any.ts +++ b/react/features/toolbox/actions.any.ts @@ -6,11 +6,13 @@ import { setVideoMuted } from '../base/media/actions'; import { VIDEO_MUTISM_AUTHORITY } from '../base/media/constants'; import { + SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS, SET_TOOLBOX_ENABLED, SET_TOOLBOX_SHIFT_UP, SET_TOOLBOX_VISIBLE, TOGGLE_TOOLBOX_VISIBLE } from './actionTypes'; +import { IMainToolbarButtonThresholds } from './types'; /** * Enables/disables the toolbox. @@ -118,3 +120,54 @@ export function setShiftUp(shiftUp: boolean) { shiftUp }; } + +/** + * Sets the mainToolbarButtonsThresholds. + * + * @param {IMainToolbarButtonThresholds} thresholds - Thresholds for screen size and visible main toolbar buttons. + * @returns {Function} + */ +export function setMainToolbarThresholds(thresholds: IMainToolbarButtonThresholds) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { + const { mainToolbarButtons } = getState()['features/base/config']; + + if (!Array.isArray(mainToolbarButtons) || mainToolbarButtons.length === 0) { + return; + } + + const mainToolbarButtonsThresholds: IMainToolbarButtonThresholds = []; + + const mainToolbarButtonsLengthMap = new Map(); + let orderIsChanged = false; + + mainToolbarButtons.forEach(buttons => { + if (!Array.isArray(buttons) || buttons.length === 0) { + return; + } + + mainToolbarButtonsLengthMap.set(buttons.length, buttons); + }); + + thresholds.forEach(({ width, order }) => { + let finalOrder = mainToolbarButtonsLengthMap.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 + }); + } + }; +} diff --git a/react/features/toolbox/actions.native.ts b/react/features/toolbox/actions.native.ts index 9d0c7d5556..5bfb6f9953 100644 --- a/react/features/toolbox/actions.native.ts +++ b/react/features/toolbox/actions.native.ts @@ -36,7 +36,7 @@ export function setOverflowMenuVisible(_visible: boolean): any { * text: string * }} */ -export function customButtonPressed(id: string, text: string) { +export function customButtonPressed(id: string, text: string | undefined) { return { type: CUSTOM_BUTTON_PRESSED, id, diff --git a/react/features/toolbox/actions.web.ts b/react/features/toolbox/actions.web.ts index ea9c2d073f..7d94aca839 100644 --- a/react/features/toolbox/actions.web.ts +++ b/react/features/toolbox/actions.web.ts @@ -8,16 +8,13 @@ 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'; @@ -124,56 +121,6 @@ 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. * diff --git a/react/features/toolbox/components/native/CustomOptionButton.tsx b/react/features/toolbox/components/native/CustomOptionButton.tsx index c8cd91d105..dc457c9e72 100644 --- a/react/features/toolbox/components/native/CustomOptionButton.tsx +++ b/react/features/toolbox/components/native/CustomOptionButton.tsx @@ -10,12 +10,12 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native'; import styles from './styles'; -interface IProps extends AbstractButtonProps { +export interface ICustomOptionButton extends AbstractButtonProps { backgroundColor?: string; icon: any; id?: string; isToolboxButton?: boolean; - text?: string; + text: string; } /** @@ -23,7 +23,7 @@ interface IProps extends AbstractButtonProps { * * @returns {Component} */ -class CustomOptionButton extends AbstractButton { +class CustomOptionButton extends AbstractButton { backgroundColor = this.props.backgroundColor; iconSrc = this.props.icon; id = this.props.id; diff --git a/react/features/toolbox/components/native/HangupContainerButtons.tsx b/react/features/toolbox/components/native/HangupContainerButtons.tsx new file mode 100644 index 0000000000..16a110f2e3 --- /dev/null +++ b/react/features/toolbox/components/native/HangupContainerButtons.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; + +import { IReduxState } from '../../../app/types'; +import { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton'; +import HangupButton from '../HangupButton'; + +import HangupMenuButton from './HangupMenuButton'; + +const HangupContainerButtons = (props: AbstractButtonProps) => { + const { conference } = useSelector((state: IReduxState) => state['features/base/conference']); + const endConferenceSupported = conference?.isEndConferenceSupported(); + + return endConferenceSupported + + // @ts-ignore + ? + : ; +}; + +export default HangupContainerButtons; diff --git a/react/features/toolbox/components/native/OverflowMenu.tsx b/react/features/toolbox/components/native/OverflowMenu.tsx index 6b0aa86cbf..f9db696cd3 100644 --- a/react/features/toolbox/components/native/OverflowMenu.tsx +++ b/react/features/toolbox/components/native/OverflowMenu.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import { ViewStyle } from 'react-native'; import { Divider } from 'react-native-paper'; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import { IReduxState, IStore } from '../../../app/types'; import { hideSheet } from '../../../base/dialog/actions'; @@ -22,18 +22,17 @@ import { isSharedVideoEnabled } from '../../../shared-video/functions'; import SpeakerStatsButton from '../../../speaker-stats/components/native/SpeakerStatsButton'; import { isSpeakerStatsDisabled } from '../../../speaker-stats/functions'; import ClosedCaptionButton from '../../../subtitles/components/native/ClosedCaptionButton'; -import TileViewButton from '../../../video-layout/components/TileViewButton'; import styles from '../../../video-menu/components/native/styles'; import WhiteboardButton from '../../../whiteboard/components/native/WhiteboardButton'; import { customButtonPressed } from '../../actions.native'; -import { getMovableButtons } from '../../functions.native'; +import { getVisibleNativeButtons } from '../../functions.native'; +import { useNativeToolboxButtons } from '../../hooks.native'; +import { IToolboxNativeButton } from '../../types'; import AudioOnlyButton from './AudioOnlyButton'; -import CustomOptionButton from './CustomOptionButton'; import LinkToSalesforceButton from './LinkToSalesforceButton'; import OpenCarmodeButton from './OpenCarmodeButton'; import RaiseHandButton from './RaiseHandButton'; -import ScreenSharingButton from './ScreenSharingButton'; /** @@ -41,11 +40,6 @@ import ScreenSharingButton from './ScreenSharingButton'; */ interface IProps { - /** - * Custom Toolbar buttons. - */ - _customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>; - /** * True if breakout rooms feature is available, false otherwise. */ @@ -66,6 +60,16 @@ interface IProps { */ _isSpeakerStatsDisabled?: boolean; + /** + * Toolbar buttons. + */ + _mainMenuButtons?: Array; + + /** + * Overflow menu buttons. + */ + _overflowMenuButtons?: Array; + /** * Whether the recoding button should be enabled or not. */ @@ -76,11 +80,6 @@ interface IProps { */ _shouldDisplayReactionsButtons: boolean; - /** - * The width of the screen. - */ - _width: number; - /** * Used for hiding the dialog when the selection was completed. */ @@ -128,11 +127,8 @@ class OverflowMenu extends PureComponent { _isBreakoutRoomsSupported, _isSpeakerStatsDisabled, _isSharedVideoEnabled, - _shouldDisplayReactionsButtons, - _width, dispatch } = this.props; - const toolbarButtons = getMovableButtons(_width); const buttonProps = { afterClick: this._onCancel, @@ -156,18 +152,12 @@ class OverflowMenu extends PureComponent { return ( - { this._renderCustomOverflowMenuButtons(topButtonProps) } + renderFooter = { this._renderReactionMenu }> + - { - !_shouldDisplayReactionsButtons && !toolbarButtons.has('raisehand') - && - } + { this._renderRaiseHandButton(buttonProps) } {/* @ts-ignore */} - @@ -176,9 +166,8 @@ class OverflowMenu extends PureComponent { {/* @ts-ignore */} {_isSharedVideoEnabled && } - {!toolbarButtons.has('screensharing') && } + { this._renderOverflowMenuButtons(topButtonProps) } {!_isSpeakerStatsDisabled && } - {!toolbarButtons.has('tileview') && } {_isBreakoutRoomsSupported && } {/* @ts-ignore */} @@ -205,42 +194,72 @@ class OverflowMenu extends PureComponent { * @returns {React.ReactElement} */ _renderReactionMenu() { - return ( - - ); + const { _mainMenuButtons, _shouldDisplayReactionsButtons } = this.props; + + // @ts-ignore + const isRaiseHandInMainMenu = _mainMenuButtons?.some(item => item.key === 'raisehand'); + + if (_shouldDisplayReactionsButtons && !isRaiseHandInMainMenu) { + return ( + + ); + } + } + + /** + * Function to render the reaction menu as the footer of the bottom sheet. + * + * @param {Object} buttonProps - Styling button properties. + * @returns {React.ReactElement} + */ + _renderRaiseHandButton(buttonProps: Object) { + const { _mainMenuButtons, _shouldDisplayReactionsButtons } = this.props; + + // @ts-ignore + const isRaiseHandInMainMenu = _mainMenuButtons?.some(item => item.key === 'raisehand'); + + if (!_shouldDisplayReactionsButtons && !isRaiseHandInMainMenu) { + return ( + + ); + } } /** * Function to render the custom buttons for the overflow menu. * - * @param {Object} topButtonProps - Button properties. + * @param {Object} topButtonProps - Styling button properties. * @returns {React.ReactElement} */ - _renderCustomOverflowMenuButtons(topButtonProps: Object) { - const { _customToolbarButtons, dispatch } = this.props; + _renderOverflowMenuButtons(topButtonProps: Object) { + const { _overflowMenuButtons, dispatch } = this.props; - if (!_customToolbarButtons?.length) { + if (!_overflowMenuButtons?.length) { return; } return ( <> { - _customToolbarButtons.map(({ id, text, icon, backgroundColor }) => ( - - dispatch(customButtonPressed(id, text)) - } - icon = { icon } - isToolboxButton = { false } - key = { id } - text = { text } /> - )) + _overflowMenuButtons?.map(({ Content, key, text, ...rest }: IToolboxNativeButton) => { + + if (key === 'raisehand') { + return null; + } + + return ( + dispatch(customButtonPressed(key, text)) } + isToolboxButton = { false } + key = { key } + text = { text } /> + ); + }) } @@ -257,16 +276,38 @@ class OverflowMenu extends PureComponent { */ function _mapStateToProps(state: IReduxState) { const { conference } = state['features/base/conference']; - const { customToolbarButtons } = state['features/base/config']; return { - _customToolbarButtons: customToolbarButtons, _isBreakoutRoomsSupported: conference?.getBreakoutRooms()?.isSupported(), _isSharedVideoEnabled: isSharedVideoEnabled(state), _isSpeakerStatsDisabled: isSpeakerStatsDisabled(state), - _shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state), - _width: state['features/base/responsive-ui'].clientWidth + _shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state) }; } -export default connect(_mapStateToProps)(OverflowMenu); +export default connect(_mapStateToProps)(props => { + const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']); + const { customToolbarButtons } = useSelector((state: IReduxState) => state['features/base/config']); + const { + mainToolbarButtonsThresholds, + toolbarButtons + } = useSelector((state: IReduxState) => state['features/toolbox']); + + const allButtons = useNativeToolboxButtons(customToolbarButtons); + + const { mainMenuButtons, overflowMenuButtons } = getVisibleNativeButtons({ + allButtons, + clientWidth, + mainToolbarButtonsThresholds, + toolbarButtons + }); + + return ( + + ); +}); diff --git a/react/features/toolbox/components/native/OverflowMenuButton.ts b/react/features/toolbox/components/native/OverflowMenuButton.ts index 65686f2b03..46bd3bfc03 100644 --- a/react/features/toolbox/components/native/OverflowMenuButton.ts +++ b/react/features/toolbox/components/native/OverflowMenuButton.ts @@ -25,6 +25,8 @@ class OverflowMenuButton extends AbstractButton { * @returns {void} */ _handleClick() { + + // @ts-ignore this.props.dispatch(openSheet(OverflowMenu)); } } diff --git a/react/features/toolbox/components/native/Toolbox.tsx b/react/features/toolbox/components/native/Toolbox.tsx index 93b1054526..2c793af44f 100644 --- a/react/features/toolbox/components/native/Toolbox.tsx +++ b/react/features/toolbox/components/native/Toolbox.tsx @@ -1,55 +1,29 @@ import React from 'react'; import { View, ViewStyle } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import { IReduxState, IStore } from '../../../app/types'; import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry'; import Platform from '../../../base/react/Platform.native'; -import ChatButton from '../../../chat/components/native/ChatButton'; -import ReactionsMenuButton from '../../../reactions/components/native/ReactionsMenuButton'; -import { shouldDisplayReactionsButtons } from '../../../reactions/functions.any'; -import TileViewButton from '../../../video-layout/components/TileViewButton'; import { iAmVisitor } from '../../../visitors/functions'; import { customButtonPressed } from '../../actions.native'; -import { getMovableButtons, isToolboxVisible } from '../../functions.native'; -import HangupButton from '../HangupButton'; +import { getVisibleNativeButtons, isToolboxVisible } from '../../functions.native'; +import { useNativeToolboxButtons } from '../../hooks.native'; +import { IToolboxNativeButton } from '../../types'; -import AudioMuteButton from './AudioMuteButton'; -import CustomOptionButton from './CustomOptionButton'; -import HangupMenuButton from './HangupMenuButton'; -import OverflowMenuButton from './OverflowMenuButton'; -import RaiseHandButton from './RaiseHandButton'; -import ScreenSharingButton from './ScreenSharingButton'; -import VideoMuteButton from './VideoMuteButton'; import styles from './styles'; - /** * The type of {@link Toolbox}'s React {@code Component} props. */ interface IProps { - /** - * Custom Toolbar buttons. - */ - _customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>; - - /** - * Whether the end conference feature is supported. - */ - _endConferenceSupported: boolean; - /** * Whether we are in visitors mode. */ _iAmVisitor: boolean; - /** - * Whether or not any reactions buttons should be visible. - */ - _shouldDisplayReactionsButtons: boolean; - /** * The color-schemed stylesheet of the feature. */ @@ -60,11 +34,6 @@ interface IProps { */ _visible: boolean; - /** - * The width of the screen. - */ - _width: number; - /** * Redux store dispatch method. */ @@ -79,13 +48,9 @@ interface IProps { */ function Toolbox(props: IProps) { const { - _customToolbarButtons, - _endConferenceSupported, _iAmVisitor, - _shouldDisplayReactionsButtons, _styles, _visible, - _width, dispatch } = props; @@ -93,41 +58,47 @@ function Toolbox(props: IProps) { return null; } + const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']); + const { customToolbarButtons } = useSelector((state: IReduxState) => state['features/base/config']); + const { + mainToolbarButtonsThresholds, + toolbarButtons + } = useSelector((state: IReduxState) => state['features/toolbox']); + + const allButtons = useNativeToolboxButtons(customToolbarButtons); + + const { mainMenuButtons } = getVisibleNativeButtons({ + allButtons, + clientWidth, + mainToolbarButtonsThresholds, + toolbarButtons + }); + const bottomEdge = Platform.OS === 'ios' && _visible; - const { buttonStylesBorderless, hangupButtonStyles, toggledButtonStyles } = _styles; - const additionalButtons = getMovableButtons(_width); - const backgroundToggledStyle = { - ...toggledButtonStyles, - style: [ - toggledButtonStyles.style, - _styles.backgroundToggle - ] - }; + const { buttonStylesBorderless, hangupButtonStyles } = _styles; const style = { ...styles.toolbox }; - // we have only hangup and raisehand button in _iAmVisitor mode + // We have only hangup and raisehand button in _iAmVisitor mode if (_iAmVisitor) { - additionalButtons.add('raisehand'); style.justifyContent = 'center'; } - const renderCustomToolboxButtons = () => { - if (!_customToolbarButtons?.length) { + const renderToolboxButtons = () => { + if (!mainMenuButtons?.length) { return; } return ( <> { - _customToolbarButtons.map(({ backgroundColor, id, text, icon }) => ( - ( + dispatch(customButtonPressed(id, text)) } - icon = { icon } + handleClick = { () => dispatch(customButtonPressed(key, text)) } isToolboxButton = { true } - key = { id } /> + key = { key } + styles = { key === 'hangup' ? hangupButtonStyles : buttonStylesBorderless } /> )) } @@ -144,44 +115,7 @@ function Toolbox(props: IProps) { edges = { [ bottomEdge && 'bottom' ].filter(Boolean) } pointerEvents = 'box-none' style = { style as ViewStyle }> - { - _customToolbarButtons - ? <> - { renderCustomToolboxButtons() } - { !_iAmVisitor && } - - : <> - {!_iAmVisitor && } - {!_iAmVisitor && } - {additionalButtons.has('chat') - && } - {!_iAmVisitor && additionalButtons.has('screensharing') - && } - {additionalButtons.has('raisehand') && (_shouldDisplayReactionsButtons - ? - : )} - {additionalButtons.has('tileview') - && } - {!_iAmVisitor && } - { _endConferenceSupported - ? - : } - - } + { renderToolboxButtons() } ); @@ -197,17 +131,10 @@ function Toolbox(props: IProps) { * @returns {IProps} */ function _mapStateToProps(state: IReduxState) { - const { conference } = state['features/base/conference']; - const endConferenceSupported = conference?.isEndConferenceSupported(); - return { - _customToolbarButtons: state['features/base/config']?.customToolbarButtons, - _endConferenceSupported: Boolean(endConferenceSupported), + _iAmVisitor: iAmVisitor(state), _styles: ColorSchemeRegistry.get(state, 'Toolbox'), _visible: isToolboxVisible(state), - _iAmVisitor: iAmVisitor(state), - _width: state['features/base/responsive-ui'].clientWidth, - _shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state) }; } diff --git a/react/features/toolbox/constants.ts b/react/features/toolbox/constants.ts index 8cafff08a5..fc261627d7 100644 --- a/react/features/toolbox/constants.ts +++ b/react/features/toolbox/constants.ts @@ -1,4 +1,4 @@ -import { ToolbarButton } from './types'; +import { NativeToolbarButton, ToolbarButton } from './types'; /** * Thresholds for displaying toolbox buttons. @@ -34,6 +34,32 @@ export const THRESHOLDS = [ } ]; +/** + * Thresholds for displaying native toolbox buttons. + */ +export const NATIVE_THRESHOLDS = [ + { + width: 560, + order: [ 'microphone', 'camera', 'chat', 'screensharing', 'raisehand', 'tileview', 'overflowmenu', 'hangup' ] + }, + { + width: 500, + order: [ 'microphone', 'camera', 'chat', 'raisehand', 'tileview', 'overflowmenu', 'hangup' ] + }, + { + width: 440, + order: [ 'microphone', 'camera', 'chat', 'raisehand', 'overflowmenu', 'hangup' ] + }, + { + width: 380, + order: [ 'microphone', 'camera', 'chat', 'overflowmenu', 'hangup' ] + }, + { + width: 320, + order: [ 'microphone', 'camera', 'overflowmenu', 'hangup' ] + } +]; + /** * Main toolbar buttons priority used to determine which button should be picked to fill empty spaces for disabled * buttons. @@ -47,6 +73,8 @@ export const MAIN_TOOLBAR_BUTTONS_PRIORITY = [ 'reactions', 'participants-pane', 'tileview', + 'overflowmenu', + 'hangup', 'invite', 'toggle-camera', 'videoquality', @@ -128,17 +156,34 @@ export const TOOLBAR_BUTTONS: ToolbarButton[] = [ 'whiteboard' ]; +/** + * The list of all possible native buttons. + * + * @protected + * @type Array + */ +export const NATIVE_TOOLBAR_BUTTONS: NativeToolbarButton[] = [ + 'camera', + 'chat', + 'hangup', + 'microphone', + 'overflowmenu', + 'raisehand', + 'screensharing', + 'tileview' +]; + /** * The toolbar buttons to show when in visitors mode. */ export const VISITORS_MODE_BUTTONS: ToolbarButton[] = [ 'chat', 'closedcaptions', + 'fullscreen', 'hangup', 'raisehand', 'settings', - 'tileview', - 'fullscreen', 'stats', + 'tileview', 'videoquality' ]; diff --git a/react/features/toolbox/functions.any.ts b/react/features/toolbox/functions.any.ts index 55519ea716..957bc1b8b2 100644 --- a/react/features/toolbox/functions.any.ts +++ b/react/features/toolbox/functions.any.ts @@ -1,9 +1,13 @@ import { IReduxState } from '../app/types'; +import { IStateful } from '../base/app/types'; import { isJwtFeatureEnabledStateless } from '../base/jwt/functions'; import { IGUMPendingState } from '../base/media/types'; import { IParticipantFeatures } from '../base/participants/types'; +import { toState } from '../base/redux/functions'; import { iAmVisitor } from '../visitors/functions'; +import { VISITORS_MODE_BUTTONS } from './constants'; + /** * Indicates if the audio mute button is disabled or not. * @@ -57,3 +61,41 @@ export function getJwtDisabledButtons( return acc; } + +/** + * Returns the list of enabled toolbar buttons. + * + * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method. + * @param {string[]} definedToolbarButtons - The list of all possible buttons. + * + * @returns {Array} - The list of enabled toolbar buttons. + */ +export function getToolbarButtons(stateful: IStateful, definedToolbarButtons: string[]): Array { + const state = toState(stateful); + const { toolbarButtons, customToolbarButtons } = state['features/base/config']; + const customButtons = customToolbarButtons?.map(({ id }) => id); + let buttons = Array.isArray(toolbarButtons) ? toolbarButtons : definedToolbarButtons; + + if (iAmVisitor(state)) { + buttons = VISITORS_MODE_BUTTONS.filter(button => buttons.indexOf(button) > -1); + } + + if (customButtons) { + return [ ...buttons, ...customButtons ]; + } + + return buttons; +} + +/** + * Checks if the specified button is enabled. + * + * @param {string} buttonName - The name of the button. See {@link interfaceConfig}. + * @param {Object|Array} state - The redux state or the array with the enabled buttons. + * @returns {boolean} - True if the button is enabled and false otherwise. + */ +export function isButtonEnabled(buttonName: string, state: IReduxState | Array) { + const buttons = Array.isArray(state) ? state : state['features/toolbox'].toolbarButtons || []; + + return buttons.includes(buttonName); +} diff --git a/react/features/toolbox/functions.native.ts b/react/features/toolbox/functions.native.ts index 7fb75582d6..be1f3a2a9d 100644 --- a/react/features/toolbox/functions.native.ts +++ b/react/features/toolbox/functions.native.ts @@ -7,53 +7,12 @@ import { getParticipantCountWithFake } from '../base/participants/functions'; import { toState } from '../base/redux/functions'; import { isLocalVideoTrackDesktop } from '../base/tracks/functions.native'; +import { MAIN_TOOLBAR_BUTTONS_PRIORITY } from './constants'; +import { isButtonEnabled } from './functions.any'; +import { IGetVisibleNativeButtonsParams, IToolboxNativeButton } from './types'; + export * from './functions.any'; -const WIDTH = { - FIT_9_ICONS: 560, - FIT_8_ICONS: 500, - FIT_7_ICONS: 440, - FIT_6_ICONS: 380 -}; - -/** - * Returns a set of the buttons that are shown in the toolbar - * but removed from the overflow menu, based on the width of the screen. - * - * @param {number} width - The width of the screen. - * @returns {Set} - */ -export function getMovableButtons(width: number): Set { - let buttons: string[] = []; - - switch (true) { - case width >= WIDTH.FIT_9_ICONS: { - buttons = [ 'chat', 'togglecamera', 'screensharing', 'raisehand', 'tileview' ]; - break; - } - case width >= WIDTH.FIT_8_ICONS: { - buttons = [ 'chat', 'togglecamera', 'raisehand', 'tileview' ]; - break; - } - - case width >= WIDTH.FIT_7_ICONS: { - buttons = [ 'chat', 'togglecamera', 'raisehand' ]; - break; - } - - case width >= WIDTH.FIT_6_ICONS: { - buttons = [ 'chat', 'togglecamera' ]; - break; - } - - default: { - buttons = [ 'chat' ]; - } - } - - return new Set(buttons); -} - /** * Indicates if the desktop share button is disabled or not. * @@ -99,3 +58,64 @@ export function isVideoMuteButtonDisabled(state: IReduxState) { return !hasAvailableDevices(state, 'videoInput') || (unmuteBlocked && Boolean(muted)); } + + +/** + * Returns all buttons that need to be rendered. + * + * @param {IGetVisibleButtonsParams} params - The parameters needed to extract the visible buttons. + * @returns {Object} - The visible buttons arrays . + */ +export function getVisibleNativeButtons({ allButtons, clientWidth, mainToolbarButtonsThresholds, toolbarButtons +}: IGetVisibleNativeButtonsParams) { + const filteredButtons = Object.keys(allButtons).filter(key => + typeof key !== 'undefined' // filter invalid buttons that may be coming from config.mainToolbarButtons override + && isButtonEnabled(key, toolbarButtons)); + + const { order } = mainToolbarButtonsThresholds.find(({ width }) => clientWidth > width) + || mainToolbarButtonsThresholds[mainToolbarButtonsThresholds.length - 1]; + + 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 mainButtonsKeys = mainToolbarButtonKeysOrder.slice(0, order.length); + const overflowMenuButtons = filteredButtons.reduce((acc, key) => { + if (!mainButtonsKeys.includes(key)) { + acc.push(allButtons[key]); + } + + return acc; + }, [] as IToolboxNativeButton[]); + + // 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; + + button && mainButtonsKeys.push(button); + } + + const mainMenuButtons + = mainButtonsKeys.map(key => allButtons[key]).sort((a, b) => { + + // Native toolbox includes hangup and overflowmenu button keys, too + // hangup goes last, overflowmenu goes second-to-last + if (a.key === 'hangup' || a.key === 'overflowmenu') { + return 1; + } + + if (b.key === 'hangup' || b.key === 'overflowmenu') { + return -1; + } + + return 0; // other buttons are sorted by priority + }); + + return { + mainMenuButtons, + overflowMenuButtons + }; +} diff --git a/react/features/toolbox/functions.web.ts b/react/features/toolbox/functions.web.ts index 9b7b1e005e..57457c83d7 100644 --- a/react/features/toolbox/functions.web.ts +++ b/react/features/toolbox/functions.web.ts @@ -1,5 +1,5 @@ import { IReduxState } from '../app/types'; -import { hasAvailableDevices } from '../base/devices/functions'; +import { hasAvailableDevices } from '../base/devices/functions.web'; import { MEET_FEATURES } from '../base/jwt/constants'; import { isJwtFeatureEnabled } from '../base/jwt/functions'; import { IGUMPendingState } from '../base/media/types'; @@ -7,7 +7,8 @@ import { isScreenMediaShared } from '../screen-share/functions'; import { isWhiteboardVisible } from '../whiteboard/functions'; import { MAIN_TOOLBAR_BUTTONS_PRIORITY, TOOLBAR_TIMEOUT } from './constants'; -import { IMainToolbarButtonThresholds, IToolboxButton, NOTIFY_CLICK_MODE } from './types'; +import { isButtonEnabled } from './functions.any'; +import { IGetVisibleButtonsParams, IToolboxButton, NOTIFY_CLICK_MODE } from './types'; export * from './functions.any'; @@ -22,19 +23,6 @@ export function getToolboxHeight() { return toolbox?.clientHeight || 0; } -/** - * Checks if the specified button is enabled. - * - * @param {string} buttonName - The name of the button. See {@link interfaceConfig}. - * @param {Object|Array} state - The redux state or the array with the enabled buttons. - * @returns {boolean} - True if the button is enabled and false otherwise. - */ -export function isButtonEnabled(buttonName: string, state: IReduxState | Array) { - const buttons = Array.isArray(state) ? state : state['features/toolbox'].toolbarButtons || []; - - return buttons.includes(buttonName); -} - /** * Indicates if the toolbox is visible or not. * @@ -125,26 +113,6 @@ export function showOverflowDrawer(state: IReduxState) { return state['features/toolbox'].overflowDrawer; } -/** - * Returns true if the overflow menu button is displayed and false otherwise. - * - * @param {IReduxState} state - The state from the Redux store. - * @returns {boolean} - True if the overflow menu button is displayed and false otherwise. - */ -export function showOverflowMenu(state: IReduxState) { - return state['features/toolbox'].overflowMenuVisible; -} - -/** - * Indicates whether the toolbox is enabled or not. - * - * @param {IReduxState} state - The state from the Redux store. - * @returns {boolean} - */ -export function isToolboxEnabled(state: IReduxState) { - return state['features/toolbox'].enabled; -} - /** * Returns the toolbar timeout from config or the default value. * @@ -176,15 +144,6 @@ function setButtonsNotifyClickMode(buttons: Object, buttonsWithNotifyClick: Map< }); } -interface IGetVisibleButtonsParams { - allButtons: { [key: string]: IToolboxButton; }; - buttonsWithNotifyClick: Map; - clientWidth: number; - jwtDisabledButtons: string[]; - mainToolbarButtonsThresholds: IMainToolbarButtonThresholds; - toolbarButtons: string[]; -} - /** * Returns all buttons that need to be rendered. * @@ -234,8 +193,10 @@ export function getVisibleButtons({ button && mainButtonsKeys.push(button); } + const mainMenuButtons = mainButtonsKeys.map(key => allButtons[key]); + return { - mainMenuButtons: mainButtonsKeys.map(key => allButtons[key]), + mainMenuButtons, overflowMenuButtons }; } diff --git a/react/features/toolbox/hooks.native.ts b/react/features/toolbox/hooks.native.ts new file mode 100644 index 0000000000..70bb6ea1bc --- /dev/null +++ b/react/features/toolbox/hooks.native.ts @@ -0,0 +1,193 @@ +import { useSelector } from 'react-redux'; + +import ChatButton from '../chat/components/native/ChatButton'; +import RaiseHandContainerButtons from '../reactions/components/native/RaiseHandContainerButtons'; +import TileViewButton from '../video-layout/components/TileViewButton'; +import { iAmVisitor } from '../visitors/functions'; + +import AudioMuteButton from './components/native/AudioMuteButton'; +import CustomOptionButton from './components/native/CustomOptionButton'; +import HangupContainerButtons from './components/native/HangupContainerButtons'; +import OverflowMenuButton from './components/native/OverflowMenuButton'; +import ScreenSharingButton from './components/native/ScreenSharingButton'; +import VideoMuteButton from './components/native/VideoMuteButton'; +import { isDesktopShareButtonDisabled } from './functions.native'; +import { ICustomToolbarButton, IToolboxNativeButton, NativeToolbarButton } from './types'; + + +const microphone = { + key: 'microphone', + Content: AudioMuteButton, + group: 0 +}; + +const camera = { + key: 'camera', + Content: VideoMuteButton, + group: 0 +}; + +const chat = { + key: 'chat', + Content: ChatButton, + group: 1 +}; + +const screensharing = { + key: 'screensharing', + Content: ScreenSharingButton, + group: 1 +}; + +const raisehand = { + key: 'raisehand', + Content: RaiseHandContainerButtons, + group: 2 +}; + +const tileview = { + key: 'tileview', + Content: TileViewButton, + group: 2 +}; + +const overflowmenu = { + key: 'overflowmenu', + Content: OverflowMenuButton, + group: 3 +}; + +const hangup = { + key: 'hangup', + Content: HangupContainerButtons, + group: 3 +}; + +/** + * A hook that returns the audio mute button. + * + * @returns {Object | undefined} + */ +function getAudioMuteButton() { + const _iAmVisitor = useSelector(iAmVisitor); + + if (!_iAmVisitor) { + return microphone; + } +} + +/** + * A hook that returns the video mute button. + * + * @returns {Object | undefined} + */ +function getVideoMuteButton() { + const _iAmVisitor = useSelector(iAmVisitor); + + if (!_iAmVisitor) { + return camera; + } +} + +/** + * A hook that returns the chat button. + * + * @returns {Object | undefined} + */ +function getChatButton() { + const _iAmVisitor = useSelector(iAmVisitor); + + if (!_iAmVisitor) { + return chat; + } +} + +/** + * A hook that returns the screen sharing button. + * + * @returns {Object | undefined} + */ +function getScreenSharingButton() { + const _iAmVisitor = useSelector(iAmVisitor); + const _isScreenShareButtonDisabled = useSelector(isDesktopShareButtonDisabled); + + if (!_isScreenShareButtonDisabled && !_iAmVisitor) { + return screensharing; + } +} + +/** + * A hook that returns the tile view button. + * + * @returns {Object | undefined} + */ +function getTileViewButton() { + const _iAmVisitor = useSelector(iAmVisitor); + + if (!_iAmVisitor) { + return tileview; + } +} + +/** + * A hook that returns the overflow menu button. + * + * @returns {Object | undefined} + */ +function getOverflowMenuButton() { + const _iAmVisitor = useSelector(iAmVisitor); + + if (!_iAmVisitor) { + return overflowmenu; + } +} + +/** + * Returns all buttons that could be rendered. + * + * @param {Object} _customToolbarButtons - An array containing custom buttons objects. + * @returns {Object} The button maps mainMenuButtons and overflowMenuButtons. + */ +export function useNativeToolboxButtons( + _customToolbarButtons?: ICustomToolbarButton[]): { [key: string]: IToolboxNativeButton; } { + const audioMuteButton = getAudioMuteButton(); + const videoMuteButton = getVideoMuteButton(); + const chatButton = getChatButton(); + const screenSharingButton = getScreenSharingButton(); + const tileViewButton = getTileViewButton(); + const overflowMenuButton = getOverflowMenuButton(); + + const buttons: { [key in NativeToolbarButton]?: IToolboxNativeButton; } = { + microphone: audioMuteButton, + camera: videoMuteButton, + chat: chatButton, + screensharing: screenSharingButton, + raisehand, + tileview: tileViewButton, + overflowmenu: overflowMenuButton, + hangup + }; + const buttonKeys = Object.keys(buttons) as NativeToolbarButton[]; + + buttonKeys.forEach( + key => typeof buttons[key] === 'undefined' && delete buttons[key]); + + const customButtons = _customToolbarButtons?.reduce((prev, { backgroundColor, icon, id, text }) => { + prev[id] = { + backgroundColor, + key: id, + id, + Content: CustomOptionButton, + group: 4, + icon, + text + }; + + return prev; + }, {} as { [key: string]: ICustomToolbarButton; }); + + return { + ...buttons, + ...customButtons + }; +} diff --git a/react/features/toolbox/hooks.web.ts b/react/features/toolbox/hooks.web.ts index 7aeeaa94de..73a5881c4f 100644 --- a/react/features/toolbox/hooks.web.ts +++ b/react/features/toolbox/hooks.web.ts @@ -270,7 +270,7 @@ function useHelpButton() { */ export function useToolboxButtons( _customToolbarButtons?: ICustomToolbarButton[]): { [key: string]: IToolboxButton; } { - const dekstopSharing = getDesktopSharingButton(); + const desktopSharing = getDesktopSharingButton(); const toggleCameraButton = useToggleCameraButton(); const _fullscreen = getFullscreenButton(); const security = useSecurityDialogButton(); @@ -297,7 +297,7 @@ export function useToolboxButtons( microphone, camera, profile, - desktop: dekstopSharing, + desktop: desktopSharing, chat, raisehand, reactions, diff --git a/react/features/toolbox/middleware.native.ts b/react/features/toolbox/middleware.native.ts new file mode 100644 index 0000000000..0821c66dd2 --- /dev/null +++ b/react/features/toolbox/middleware.native.ts @@ -0,0 +1,46 @@ +import { OVERWRITE_CONFIG, SET_CONFIG, UPDATE_CONFIG } from '../base/config/actionTypes'; +import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; +import { I_AM_VISITOR_MODE } from '../visitors/actionTypes'; + +import { SET_TOOLBAR_BUTTONS } from './actionTypes'; +import { setMainToolbarThresholds } from './actions.native'; +import { NATIVE_THRESHOLDS, NATIVE_TOOLBAR_BUTTONS } from './constants'; +import { getToolbarButtons } from './functions.native'; + + +/** + * Middleware which intercepts Toolbox actions to handle changes to the + * visibility timeout of the Toolbox. + * + * @param {Store} store - The redux store. + * @returns {Function} + */ + +MiddlewareRegistry.register(store => next => action => { + switch (action.type) { + + case UPDATE_CONFIG: + case OVERWRITE_CONFIG: + case I_AM_VISITOR_MODE: + case SET_CONFIG: { + const result = next(action); + const { dispatch } = store; + const state = store.getState(); + + const toolbarButtons = getToolbarButtons(state, NATIVE_TOOLBAR_BUTTONS); + + if (action.type !== I_AM_VISITOR_MODE) { + dispatch(setMainToolbarThresholds(NATIVE_THRESHOLDS)); + } + + dispatch({ + type: SET_TOOLBAR_BUTTONS, + toolbarButtons + }); + + return result; + } + } + + return next(action); +}); diff --git a/react/features/toolbox/middleware.web.ts b/react/features/toolbox/middleware.web.ts index 43fdbea98b..f52578402f 100644 --- a/react/features/toolbox/middleware.web.ts +++ b/react/features/toolbox/middleware.web.ts @@ -1,12 +1,10 @@ import { batch } from 'react-redux'; import { AnyAction } from 'redux'; -import { IReduxState } from '../app/types'; import { OVERWRITE_CONFIG, SET_CONFIG, UPDATE_CONFIG } from '../base/config/actionTypes'; import { NotifyClickButton } from '../base/config/configType'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; import { I_AM_VISITOR_MODE } from '../visitors/actionTypes'; -import { iAmVisitor } from '../visitors/functions'; import { CLEAR_TOOLBOX_TIMEOUT, @@ -17,7 +15,8 @@ import { SET_TOOLBOX_TIMEOUT } from './actionTypes'; import { setMainToolbarThresholds } from './actions.web'; -import { TOOLBAR_BUTTONS, VISITORS_MODE_BUTTONS } from './constants'; +import { THRESHOLDS, TOOLBAR_BUTTONS } from './constants'; +import { getToolbarButtons } from './functions.web'; import { NOTIFY_CLICK_MODE } from './types'; import './subscriber.web'; @@ -55,7 +54,7 @@ MiddlewareRegistry.register(store => next => action => { batch(() => { if (action.type !== I_AM_VISITOR_MODE) { - dispatch(setMainToolbarThresholds()); + dispatch(setMainToolbarThresholds(THRESHOLDS)); } dispatch({ type: SET_BUTTONS_WITH_NOTIFY_CLICK, @@ -69,7 +68,7 @@ MiddlewareRegistry.register(store => next => action => { }); } - const toolbarButtons = _getToolbarButtons(state); + const toolbarButtons = getToolbarButtons(state, TOOLBAR_BUTTONS); dispatch({ type: SET_TOOLBAR_BUTTONS, @@ -171,25 +170,3 @@ function _buildButtonsArray( return new Map([ ...customButtonsWithNotifyClick, ...buttons ]); } - -/** - * Returns the list of enabled toolbar buttons. - * - * @param {Object} state - The redux state. - * @returns {Array} - The list of enabled toolbar buttons. - */ -function _getToolbarButtons(state: IReduxState): Array { - const { toolbarButtons, customToolbarButtons } = state['features/base/config']; - const customButtons = customToolbarButtons?.map(({ id }) => id); - let buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS; - - if (iAmVisitor(state)) { - buttons = VISITORS_MODE_BUTTONS.filter(button => buttons.indexOf(button) > -1); - } - - if (customButtons) { - return [ ...buttons, ...customButtons ]; - } - - return buttons; -} diff --git a/react/features/toolbox/reducer.ts b/react/features/toolbox/reducer.ts index 23dd00206b..f65d5c6bb4 100644 --- a/react/features/toolbox/reducer.ts +++ b/react/features/toolbox/reducer.ts @@ -18,7 +18,7 @@ import { SET_TOOLBOX_VISIBLE, TOGGLE_TOOLBOX_VISIBLE } from './actionTypes'; -import { THRESHOLDS } from './constants'; +import { NATIVE_THRESHOLDS, THRESHOLDS } from './constants'; import { IMainToolbarButtonThresholds, NOTIFY_CLICK_MODE } from './types'; /** @@ -52,7 +52,7 @@ const INITIAL_STATE = { /** * The thresholds for screen size and visible main toolbar buttons. */ - mainToolbarButtonsThresholds: THRESHOLDS, + mainToolbarButtonsThresholds: navigator.product === 'ReactNative' ? NATIVE_THRESHOLDS : THRESHOLDS, participantMenuButtonsWithNotifyClick: new Map(), diff --git a/react/features/toolbox/types.ts b/react/features/toolbox/types.ts index 33063c9909..746d3b472d 100644 --- a/react/features/toolbox/types.ts +++ b/react/features/toolbox/types.ts @@ -1,13 +1,21 @@ import { ComponentType } from 'react'; -import { CustomOptionButton } from './components'; - export interface IToolboxButton { Content: ComponentType; group: number; key: string; } +export interface IToolboxNativeButton { + Content: ComponentType; + backgroundColor?: string; + group: number; + icon?: string; + id?: string; + key: string; + text?: string; +} + export type ToolbarButton = 'camera' | 'chat' | 'closedcaptions' | @@ -28,6 +36,7 @@ export type ToolbarButton = 'camera' | 'mute-everyone' | 'mute-video-everyone' | 'noisesuppression' | + 'overflowmenu' | 'participants-pane' | 'profile' | 'raisehand' | @@ -52,12 +61,12 @@ export enum NOTIFY_CLICK_MODE { } export type IMainToolbarButtonThresholds = Array<{ - order: Array; + order: Array; width: number; }>; export interface ICustomToolbarButton { - Content?: typeof CustomOptionButton; + Content?: ComponentType; backgroundColor?: string; group?: number; icon: string; @@ -65,3 +74,28 @@ export interface ICustomToolbarButton { key?: string; text: string; } + +export type NativeToolbarButton = 'camera' | + 'chat' | + 'microphone' | + 'raisehand' | + 'screensharing' | + 'tileview' | + 'overflowmenu' | + 'hangup'; + +export interface IGetVisibleNativeButtonsParams { + allButtons: { [key: string]: IToolboxNativeButton; }; + clientWidth: number; + mainToolbarButtonsThresholds: IMainToolbarButtonThresholds; + toolbarButtons: string[]; +} + +export interface IGetVisibleButtonsParams { + allButtons: { [key: string]: IToolboxButton; }; + buttonsWithNotifyClick: Map; + clientWidth: number; + jwtDisabledButtons: string[]; + mainToolbarButtonsThresholds: IMainToolbarButtonThresholds; + toolbarButtons: string[]; +}