mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 03:12:29 +00:00
feat(toolbox/native): reorganizing buttons in the toolbox and overflow menu (#15543)
Configures what buttons can be visible inside Toolbox and OverflowMenu, based on priority and config overrides, just like web does.
This commit is contained in:
@@ -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];
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -23,7 +23,7 @@ interface IState {
|
||||
/**
|
||||
* The id of the last read message.
|
||||
*/
|
||||
lastReadMessageId: string;
|
||||
lastReadMessageId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
? <ReactionsMenuButton
|
||||
{ ...props }
|
||||
showRaiseHand = { true } />
|
||||
: <RaiseHandButton { ...props } />;
|
||||
};
|
||||
|
||||
export default RaiseHandContainerButtons;
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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<IProps> {
|
||||
class CustomOptionButton extends AbstractButton<ICustomOptionButton> {
|
||||
backgroundColor = this.props.backgroundColor;
|
||||
iconSrc = this.props.icon;
|
||||
id = this.props.id;
|
||||
|
||||
@@ -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
|
||||
? <HangupMenuButton { ...props } />
|
||||
: <HangupButton { ...props } />;
|
||||
};
|
||||
|
||||
export default HangupContainerButtons;
|
||||
@@ -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<IToolboxNativeButton>;
|
||||
|
||||
/**
|
||||
* Overflow menu buttons.
|
||||
*/
|
||||
_overflowMenuButtons?: Array<IToolboxNativeButton>;
|
||||
|
||||
/**
|
||||
* 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<IProps, IState> {
|
||||
_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<IProps, IState> {
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
renderFooter = { _shouldDisplayReactionsButtons && !toolbarButtons.has('raisehand')
|
||||
? this._renderReactionMenu
|
||||
: undefined }>
|
||||
{ this._renderCustomOverflowMenuButtons(topButtonProps) }
|
||||
renderFooter = { this._renderReactionMenu }>
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
<OpenCarmodeButton { ...topButtonProps } />
|
||||
<AudioOnlyButton { ...buttonProps } />
|
||||
{
|
||||
!_shouldDisplayReactionsButtons && !toolbarButtons.has('raisehand')
|
||||
&& <RaiseHandButton { ...buttonProps } />
|
||||
}
|
||||
{ this._renderRaiseHandButton(buttonProps) }
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
<SecurityDialogButton { ...buttonProps } />
|
||||
<RecordButton { ...buttonProps } />
|
||||
<LiveStreamButton { ...buttonProps } />
|
||||
@@ -176,9 +166,8 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
{_isSharedVideoEnabled && <SharedVideoButton { ...buttonProps } />}
|
||||
{!toolbarButtons.has('screensharing') && <ScreenSharingButton { ...buttonProps } />}
|
||||
{ this._renderOverflowMenuButtons(topButtonProps) }
|
||||
{!_isSpeakerStatsDisabled && <SpeakerStatsButton { ...buttonProps } />}
|
||||
{!toolbarButtons.has('tileview') && <TileViewButton { ...buttonProps } />}
|
||||
{_isBreakoutRoomsSupported && <BreakoutRoomsButton { ...buttonProps } />}
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
@@ -205,42 +194,72 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
* @returns {React.ReactElement}
|
||||
*/
|
||||
_renderReactionMenu() {
|
||||
return (
|
||||
<ReactionMenu
|
||||
onCancel = { this._onCancel }
|
||||
overflowMenu = { true } />
|
||||
);
|
||||
const { _mainMenuButtons, _shouldDisplayReactionsButtons } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
const isRaiseHandInMainMenu = _mainMenuButtons?.some(item => item.key === 'raisehand');
|
||||
|
||||
if (_shouldDisplayReactionsButtons && !isRaiseHandInMainMenu) {
|
||||
return (
|
||||
<ReactionMenu
|
||||
onCancel = { this._onCancel }
|
||||
overflowMenu = { true } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
<RaiseHandButton { ...buttonProps } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 }) => (
|
||||
<CustomOptionButton
|
||||
{ ...topButtonProps }
|
||||
backgroundColor = { backgroundColor }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () =>
|
||||
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 (
|
||||
<Content
|
||||
{ ...topButtonProps }
|
||||
{ ...rest }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () => dispatch(customButtonPressed(key, text)) }
|
||||
isToolboxButton = { false }
|
||||
key = { key }
|
||||
text = { text } />
|
||||
);
|
||||
})
|
||||
}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
</>
|
||||
@@ -257,16 +276,38 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
*/
|
||||
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 (
|
||||
<OverflowMenu
|
||||
|
||||
// @ts-ignore
|
||||
{ ... props }
|
||||
_mainMenuButtons = { mainMenuButtons }
|
||||
_overflowMenuButtons = { overflowMenuButtons } />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -25,6 +25,8 @@ class OverflowMenuButton extends AbstractButton<AbstractButtonProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
|
||||
// @ts-ignore
|
||||
this.props.dispatch(openSheet(OverflowMenu));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }) => (
|
||||
<CustomOptionButton
|
||||
backgroundColor = { backgroundColor }
|
||||
|
||||
mainMenuButtons?.map(({ Content, key, text, ...rest }: IToolboxNativeButton) => (
|
||||
<Content
|
||||
{ ...rest }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () => 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 && <OverflowMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } /> }
|
||||
</>
|
||||
: <>
|
||||
{!_iAmVisitor && <AudioMuteButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />}
|
||||
{!_iAmVisitor && <VideoMuteButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />}
|
||||
{additionalButtons.has('chat')
|
||||
&& <ChatButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { backgroundToggledStyle } />}
|
||||
{!_iAmVisitor && additionalButtons.has('screensharing')
|
||||
&& <ScreenSharingButton styles = { buttonStylesBorderless } />}
|
||||
{additionalButtons.has('raisehand') && (_shouldDisplayReactionsButtons
|
||||
? <ReactionsMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { backgroundToggledStyle } />
|
||||
: <RaiseHandButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { backgroundToggledStyle } />)}
|
||||
{additionalButtons.has('tileview')
|
||||
&& <TileViewButton styles = { buttonStylesBorderless } />}
|
||||
{!_iAmVisitor && <OverflowMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />}
|
||||
{ _endConferenceSupported
|
||||
? <HangupMenuButton />
|
||||
: <HangupButton styles = { hangupButtonStyles } />}
|
||||
</>
|
||||
}
|
||||
{ renderToolboxButtons() }
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string>
|
||||
*/
|
||||
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'
|
||||
];
|
||||
|
||||
@@ -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<string>} - The list of enabled toolbar buttons.
|
||||
*/
|
||||
export function getToolbarButtons(stateful: IStateful, definedToolbarButtons: string[]): Array<string> {
|
||||
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<string>} 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<string>) {
|
||||
const buttons = Array.isArray(state) ? state : state['features/toolbox'].toolbarButtons || [];
|
||||
|
||||
return buttons.includes(buttonName);
|
||||
}
|
||||
|
||||
@@ -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<string> {
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<string>} 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<string>) {
|
||||
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<string, NOTIFY_CLICK_MODE>;
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
193
react/features/toolbox/hooks.native.ts
Normal file
193
react/features/toolbox/hooks.native.ts
Normal file
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
46
react/features/toolbox/middleware.native.ts
Normal file
46
react/features/toolbox/middleware.native.ts
Normal file
@@ -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);
|
||||
});
|
||||
@@ -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<string>} - The list of enabled toolbar buttons.
|
||||
*/
|
||||
function _getToolbarButtons(state: IReduxState): Array<string> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { CustomOptionButton } from './components';
|
||||
|
||||
export interface IToolboxButton {
|
||||
Content: ComponentType<any>;
|
||||
group: number;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface IToolboxNativeButton {
|
||||
Content: ComponentType<any>;
|
||||
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<ToolbarButton | string>;
|
||||
order: Array<ToolbarButton | NativeToolbarButton | string>;
|
||||
width: number;
|
||||
}>;
|
||||
|
||||
export interface ICustomToolbarButton {
|
||||
Content?: typeof CustomOptionButton;
|
||||
Content?: ComponentType<any>;
|
||||
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<string, NOTIFY_CLICK_MODE>;
|
||||
clientWidth: number;
|
||||
jwtDisabledButtons: string[];
|
||||
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
|
||||
toolbarButtons: string[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user