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[];
+}