This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import AbstractAudioMuteButton, { IProps, mapStateToProps } from '../AbstractAudioMuteButton';
|
||||
|
||||
export default translate(connect(mapStateToProps)(AbstractAudioMuteButton<IProps>));
|
||||
96
react/features/toolbox/components/native/AudioOnlyButton.ts
Normal file
96
react/features/toolbox/components/native/AudioOnlyButton.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { setAudioOnly, toggleAudioOnly } from '../../../base/audio-only/actions';
|
||||
import { AUDIO_ONLY_BUTTON_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconAudioOnly, IconAudioOnlyOff } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import {
|
||||
navigate
|
||||
} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AudioOnlyButton}.
|
||||
*/
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* Whether the current conference is in audio only mode or not.
|
||||
*/
|
||||
_audioOnly: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the car mode is enabled.
|
||||
*/
|
||||
_startCarMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a button for toggling the audio-only mode.
|
||||
*/
|
||||
class AudioOnlyButton extends AbstractButton<IProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.audioOnly';
|
||||
override icon = IconAudioOnly;
|
||||
override label = 'toolbar.audioOnlyOn';
|
||||
override toggledIcon = IconAudioOnlyOff;
|
||||
override toggledLabel = 'toolbar.audioOnlyOff';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
const { _audioOnly, _startCarMode, dispatch } = this.props;
|
||||
|
||||
if (!_audioOnly && _startCarMode) {
|
||||
dispatch(setAudioOnly(true));
|
||||
navigate(screen.conference.carmode);
|
||||
} else {
|
||||
dispatch(toggleAudioOnly());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _isToggled() {
|
||||
return this.props._audioOnly;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code AudioOnlyButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component instance.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const enabledInFeatureFlags = getFeatureFlag(state, AUDIO_ONLY_BUTTON_ENABLED, true);
|
||||
const { startCarMode } = state['features/base/settings'];
|
||||
const { visible = enabledInFeatureFlags } = ownProps;
|
||||
|
||||
return {
|
||||
_audioOnly: Boolean(audioOnly),
|
||||
_startCarMode: startCarMode,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(AudioOnlyButton));
|
||||
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { Image, View, ViewStyle } from 'react-native';
|
||||
import { SvgCssUri } from 'react-native-svg/css';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
export interface ICustomOptionButton extends AbstractButtonProps {
|
||||
backgroundColor?: string;
|
||||
icon: any;
|
||||
id?: string;
|
||||
isToolboxButton?: boolean;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders a custom button.
|
||||
*
|
||||
* @returns {Component}
|
||||
*/
|
||||
class CustomOptionButton extends AbstractButton<ICustomOptionButton> {
|
||||
backgroundColor = this.props.backgroundColor;
|
||||
iconSrc = this.props.icon;
|
||||
id = this.props.id;
|
||||
text = this.props.text;
|
||||
|
||||
/**
|
||||
* Custom icon component.
|
||||
*
|
||||
* @returns {React.Component}
|
||||
*/
|
||||
icon = () => {
|
||||
let iconComponent;
|
||||
|
||||
if (!this.iconSrc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.iconSrc?.includes('svg')) {
|
||||
iconComponent = (
|
||||
<SvgCssUri
|
||||
// @ts-ignore
|
||||
height = { BaseTheme.spacing[4] }
|
||||
uri = { this.iconSrc }
|
||||
width = { BaseTheme.spacing[4] } />
|
||||
);
|
||||
} else {
|
||||
iconComponent = (
|
||||
<Image
|
||||
height = { BaseTheme.spacing[4] }
|
||||
resizeMode = { 'contain' }
|
||||
source = {{ uri: this.iconSrc }}
|
||||
width = { BaseTheme.spacing[4] } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { this.props.isToolboxButton && [
|
||||
styles.toolboxButtonIconContainer,
|
||||
{ backgroundColor: this.backgroundColor } ] as ViewStyle[] }>
|
||||
{ iconComponent }
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
label = this.text || '';
|
||||
}
|
||||
|
||||
export default translate(connect()(CustomOptionButton));
|
||||
@@ -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;
|
||||
77
react/features/toolbox/components/native/HangupMenu.tsx
Normal file
77
react/features/toolbox/components/native/HangupMenu.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { createBreakoutRoomsEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { appNavigate } from '../../../app/actions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
|
||||
import { endConference } from '../../../base/conference/actions';
|
||||
import { hideSheet } from '../../../base/dialog/actions';
|
||||
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||
import { PARTICIPANT_ROLE } from '../../../base/participants/constants';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { moveToRoom } from '../../../breakout-rooms/actions';
|
||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
|
||||
/**
|
||||
* Menu presenting options to leave a room or meeting and to end meeting.
|
||||
*
|
||||
* @returns {JSX.Element} - The hangup menu.
|
||||
*/
|
||||
function HangupMenu() {
|
||||
const dispatch = useDispatch();
|
||||
const _styles: any = useSelector((state: IReduxState) => ColorSchemeRegistry.get(state, 'Toolbox'));
|
||||
const inBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||
const isModerator = useSelector((state: IReduxState) =>
|
||||
getLocalParticipant(state)?.role === PARTICIPANT_ROLE.MODERATOR);
|
||||
const { DESTRUCTIVE, SECONDARY } = BUTTON_TYPES;
|
||||
|
||||
const handleEndConference = useCallback(() => {
|
||||
dispatch(hideSheet());
|
||||
sendAnalytics(createToolbarEvent('endmeeting'));
|
||||
dispatch(endConference());
|
||||
}, [ hideSheet ]);
|
||||
|
||||
const handleLeaveConference = useCallback(() => {
|
||||
dispatch(hideSheet());
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
dispatch(appNavigate(undefined));
|
||||
}, [ hideSheet ]);
|
||||
|
||||
const handleLeaveBreakoutRoom = useCallback(() => {
|
||||
dispatch(hideSheet());
|
||||
sendAnalytics(createBreakoutRoomsEvent('leave'));
|
||||
dispatch(moveToRoom());
|
||||
}, [ hideSheet ]);
|
||||
|
||||
return (
|
||||
<BottomSheet>
|
||||
<View style = { _styles.hangupMenuContainer }>
|
||||
{ isModerator && <Button
|
||||
accessibilityLabel = 'toolbar.endConference'
|
||||
labelKey = 'toolbar.endConference'
|
||||
onClick = { handleEndConference }
|
||||
style = { _styles.hangupButton }
|
||||
type = { DESTRUCTIVE } /> }
|
||||
<Button
|
||||
accessibilityLabel = 'toolbar.leaveConference'
|
||||
labelKey = 'toolbar.leaveConference'
|
||||
onClick = { handleLeaveConference }
|
||||
style = { _styles.hangupButton }
|
||||
type = { SECONDARY } />
|
||||
{ inBreakoutRoom && <Button
|
||||
accessibilityLabel = 'breakoutRooms.actions.leaveBreakoutRoom'
|
||||
labelKey = 'breakoutRooms.actions.leaveBreakoutRoom'
|
||||
onClick = { handleLeaveBreakoutRoom }
|
||||
style = { _styles.hangupButton }
|
||||
type = { SECONDARY } /> }
|
||||
</View>
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
||||
|
||||
export default HangupMenu;
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { openSheet } from '../../../base/dialog/actions';
|
||||
import { IconHangup } from '../../../base/icons/svg';
|
||||
import IconButton from '../../../base/ui/components/native/IconButton';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
|
||||
import HangupMenu from './HangupMenu';
|
||||
|
||||
/**
|
||||
* Button for showing the hangup menu.
|
||||
*
|
||||
* @returns {JSX.Element} - The hangup menu button.
|
||||
*/
|
||||
const HangupMenuButton = (): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
dispatch(openSheet(HangupMenu));
|
||||
}, [ dispatch ]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.hangup'
|
||||
onPress = { onSelect }
|
||||
src = { IconHangup }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
);
|
||||
};
|
||||
|
||||
export default HangupMenuButton;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconCloudUpload } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { isSalesforceEnabled } from '../../../salesforce/functions';
|
||||
|
||||
/**
|
||||
* Implementation of a button for opening the Salesforce link dialog.
|
||||
*/
|
||||
class LinkToSalesforceButton extends AbstractButton<AbstractButtonProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.linkToSalesforce';
|
||||
override icon = IconCloudUpload;
|
||||
override label = 'toolbar.linkToSalesforce';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the Salesforce link dialog.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
sendAnalytics(createToolbarEvent('link.to.salesforce'));
|
||||
|
||||
return navigate(screen.conference.salesforce);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that maps parts of Redux state tree into component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
visible: isSalesforceEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(LinkToSalesforceButton));
|
||||
@@ -0,0 +1,49 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { CAR_MODE_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconCar } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the carmode.
|
||||
*/
|
||||
class OpenCarmodeButton extends AbstractButton<AbstractButtonProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.carmode';
|
||||
override icon = IconCar;
|
||||
override label = 'carmode.labels.buttonLabel';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the carmode mode.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
return navigate(screen.conference.carmode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @param {AbstractButtonProps} ownProps - The properties explicitly passed to the component instance.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: AbstractButtonProps): Object {
|
||||
const enabled = getFeatureFlag(state, CAR_MODE_ENABLED, true);
|
||||
const { visible = enabled } = ownProps;
|
||||
|
||||
return {
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(OpenCarmodeButton));
|
||||
316
react/features/toolbox/components/native/OverflowMenu.tsx
Normal file
316
react/features/toolbox/components/native/OverflowMenu.tsx
Normal file
@@ -0,0 +1,316 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { ViewStyle } from 'react-native';
|
||||
import { Divider } from 'react-native-paper';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { hideSheet } from '../../../base/dialog/actions';
|
||||
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||
import { bottomSheetStyles } from '../../../base/dialog/components/native/styles';
|
||||
import SettingsButton from '../../../base/settings/components/native/SettingsButton';
|
||||
import BreakoutRoomsButton
|
||||
from '../../../breakout-rooms/components/native/BreakoutRoomsButton';
|
||||
import SharedDocumentButton from '../../../etherpad/components/SharedDocumentButton.native';
|
||||
import ReactionMenu from '../../../reactions/components/native/ReactionMenu';
|
||||
import { shouldDisplayReactionsButtons } from '../../../reactions/functions.any';
|
||||
import LiveStreamButton from '../../../recording/components/LiveStream/native/LiveStreamButton';
|
||||
import RecordButton from '../../../recording/components/Recording/native/RecordButton';
|
||||
import SecurityDialogButton
|
||||
from '../../../security/components/security-dialog/native/SecurityDialogButton';
|
||||
import SharedVideoButton from '../../../shared-video/components/native/SharedVideoButton';
|
||||
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 styles from '../../../video-menu/components/native/styles';
|
||||
import { iAmVisitor } from '../../../visitors/functions';
|
||||
import WhiteboardButton from '../../../whiteboard/components/native/WhiteboardButton';
|
||||
import { customButtonPressed } from '../../actions.native';
|
||||
import { getVisibleNativeButtons } from '../../functions.native';
|
||||
import { useNativeToolboxButtons } from '../../hooks.native';
|
||||
import { IToolboxNativeButton } from '../../types';
|
||||
|
||||
import AudioOnlyButton from './AudioOnlyButton';
|
||||
import LinkToSalesforceButton from './LinkToSalesforceButton';
|
||||
import OpenCarmodeButton from './OpenCarmodeButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link OverflowMenu}.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* True if breakout rooms feature is available, false otherwise.
|
||||
*/
|
||||
_isBreakoutRoomsSupported?: boolean;
|
||||
|
||||
/**
|
||||
* True if the overflow menu is currently visible, false otherwise.
|
||||
*/
|
||||
_isOpen: boolean;
|
||||
|
||||
/**
|
||||
* Whether the shared video is enabled or not.
|
||||
*/
|
||||
_isSharedVideoEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not speaker stats is disable.
|
||||
*/
|
||||
_isSpeakerStatsDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* Toolbar buttons.
|
||||
*/
|
||||
_mainMenuButtons?: Array<IToolboxNativeButton>;
|
||||
|
||||
/**
|
||||
* Overflow menu buttons.
|
||||
*/
|
||||
_overflowMenuButtons?: Array<IToolboxNativeButton>;
|
||||
|
||||
/**
|
||||
* Whether the recoding button should be enabled or not.
|
||||
*/
|
||||
_recordingEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not any reactions buttons should be displayed.
|
||||
*/
|
||||
_shouldDisplayReactionsButtons: boolean;
|
||||
|
||||
/**
|
||||
* Used for hiding the dialog when the selection was completed.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
/**
|
||||
* True if the bottom sheet is scrolled to the top.
|
||||
*/
|
||||
scrolledToTop: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React {@code Component} with some extra actions in addition to
|
||||
* those in the toolbar.
|
||||
*/
|
||||
class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
/**
|
||||
* Initializes a new {@code OverflowMenu} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
scrolledToTop: true
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._renderReactionMenu = this._renderReactionMenu.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
const {
|
||||
_isBreakoutRoomsSupported,
|
||||
_isSpeakerStatsDisabled,
|
||||
_isSharedVideoEnabled,
|
||||
dispatch
|
||||
} = this.props;
|
||||
|
||||
const buttonProps = {
|
||||
afterClick: this._onCancel,
|
||||
showLabel: true,
|
||||
styles: bottomSheetStyles.buttons
|
||||
};
|
||||
|
||||
const topButtonProps = {
|
||||
afterClick: this._onCancel,
|
||||
dispatch,
|
||||
showLabel: true,
|
||||
styles: {
|
||||
...bottomSheetStyles.buttons,
|
||||
style: {
|
||||
...bottomSheetStyles.buttons.style,
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
renderFooter = { this._renderReactionMenu }>
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
<OpenCarmodeButton { ...topButtonProps } />
|
||||
<AudioOnlyButton { ...buttonProps } />
|
||||
{ this._renderRaiseHandButton(buttonProps) }
|
||||
{/* @ts-ignore */}
|
||||
<SecurityDialogButton { ...buttonProps } />
|
||||
<RecordButton { ...buttonProps } />
|
||||
<LiveStreamButton { ...buttonProps } />
|
||||
<LinkToSalesforceButton { ...buttonProps } />
|
||||
<WhiteboardButton { ...buttonProps } />
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
{_isSharedVideoEnabled && <SharedVideoButton { ...buttonProps } />}
|
||||
{ this._renderOverflowMenuButtons(topButtonProps) }
|
||||
{!_isSpeakerStatsDisabled && <SpeakerStatsButton { ...buttonProps } />}
|
||||
{_isBreakoutRoomsSupported && <BreakoutRoomsButton { ...buttonProps } />}
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
<ClosedCaptionButton { ...buttonProps } />
|
||||
<SharedDocumentButton { ...buttonProps } />
|
||||
<SettingsButton { ...buttonProps } />
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this {@code OverflowMenu}.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
this.props.dispatch(hideSheet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to render the reaction menu as the footer of the bottom sheet.
|
||||
*
|
||||
* @returns {React.ReactElement}
|
||||
*/
|
||||
_renderReactionMenu() {
|
||||
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 - Styling button properties.
|
||||
* @returns {React.ReactElement}
|
||||
*/
|
||||
_renderOverflowMenuButtons(topButtonProps: Object) {
|
||||
const { _overflowMenuButtons, dispatch } = this.props;
|
||||
|
||||
if (!_overflowMenuButtons?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
_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 } />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that maps parts of Redux state tree into component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_isBreakoutRoomsSupported: conference?.getBreakoutRooms()?.isSupported(),
|
||||
_isSharedVideoEnabled: isSharedVideoEnabled(state),
|
||||
_isSpeakerStatsDisabled: isSpeakerStatsDisabled(state),
|
||||
_shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state)
|
||||
};
|
||||
}
|
||||
|
||||
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 _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
const allButtons = useNativeToolboxButtons(customToolbarButtons);
|
||||
|
||||
const { mainMenuButtons, overflowMenuButtons } = getVisibleNativeButtons({
|
||||
allButtons,
|
||||
clientWidth,
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons,
|
||||
iAmVisitor: _iAmVisitor
|
||||
});
|
||||
|
||||
return (
|
||||
<OverflowMenu
|
||||
|
||||
// @ts-ignore
|
||||
{ ... props }
|
||||
_mainMenuButtons = { mainMenuButtons }
|
||||
_overflowMenuButtons = { overflowMenuButtons } />
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { openSheet } from '../../../base/dialog/actions';
|
||||
import { OVERFLOW_MENU_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconDotsHorizontal } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
|
||||
import OverflowMenu from './OverflowMenu';
|
||||
|
||||
/**
|
||||
* An implementation of a button for showing the {@code OverflowMenu}.
|
||||
*/
|
||||
class OverflowMenuButton extends AbstractButton<AbstractButtonProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.moreActions';
|
||||
override icon = IconDotsHorizontal;
|
||||
override label = 'toolbar.moreActions';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing this {@code OverflowMenuButton}.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
|
||||
// @ts-ignore
|
||||
this.props.dispatch(openSheet(OverflowMenu));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code OverflowMenuButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const enabledFlag = getFeatureFlag(state, OVERFLOW_MENU_ENABLED, true);
|
||||
|
||||
return {
|
||||
visible: enabledFlag
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(OverflowMenuButton));
|
||||
99
react/features/toolbox/components/native/RaiseHandButton.ts
Normal file
99
react/features/toolbox/components/native/RaiseHandButton.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../../analytics/functions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { RAISE_HAND_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconRaiseHand } from '../../../base/icons/svg';
|
||||
import { raiseHand } from '../../../base/participants/actions';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
hasRaisedHand
|
||||
} from '../../../base/participants/functions';
|
||||
import { ILocalParticipant } from '../../../base/participants/types';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link RaiseHandButton}.
|
||||
*/
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* The local participant.
|
||||
*/
|
||||
_localParticipant?: ILocalParticipant;
|
||||
|
||||
/**
|
||||
* Whether the participant raised their hand or not.
|
||||
*/
|
||||
_raisedHand: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a button to raise or lower hand.
|
||||
*/
|
||||
class RaiseHandButton extends AbstractButton<IProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
|
||||
override icon = IconRaiseHand;
|
||||
override label = 'toolbar.raiseYourHand';
|
||||
override toggledLabel = 'toolbar.lowerYourHand';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
this._toggleRaisedHand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _isToggled() {
|
||||
return this.props._raisedHand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the rased hand status of the local participant.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_toggleRaisedHand() {
|
||||
const enable = !this.props._raisedHand;
|
||||
|
||||
sendAnalytics(createToolbarEvent('raise.hand', { enable }));
|
||||
|
||||
this.props.dispatch(raiseHand(enable));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component instance.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
const enabled = getFeatureFlag(state, RAISE_HAND_ENABLED, true);
|
||||
const { visible = enabled } = ownProps;
|
||||
|
||||
return {
|
||||
_localParticipant,
|
||||
_raisedHand: hasRaisedHand(_localParticipant),
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(RaiseHandButton));
|
||||
@@ -0,0 +1,91 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { ANDROID_SCREENSHARING_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconScreenshare } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { toggleScreensharing } from '../../../base/tracks/actions.native';
|
||||
import { isLocalVideoTrackDesktop } from '../../../base/tracks/functions.native';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ScreenSharingAndroidButton}.
|
||||
*/
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* True if the button needs to be disabled.
|
||||
*/
|
||||
_disabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether video is currently muted or not.
|
||||
*/
|
||||
_screensharing: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a button for toggling screen sharing.
|
||||
*/
|
||||
class ScreenSharingAndroidButton extends AbstractButton<IProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
|
||||
override icon = IconScreenshare;
|
||||
override label = 'toolbar.startScreenSharing';
|
||||
override toggledLabel = 'toolbar.stopScreenSharing';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
const enable = !this._isToggled();
|
||||
|
||||
this.props.dispatch(toggleScreensharing(enable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean value indicating if this button is disabled or not.
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return this.props._disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _isToggled() {
|
||||
return this.props._screensharing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code ToggleCameraButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _screensharing: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const enabled = getFeatureFlag(state, ANDROID_SCREENSHARING_ENABLED, true);
|
||||
|
||||
return {
|
||||
_screensharing: isLocalVideoTrackDesktop(state),
|
||||
visible: enabled
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ScreenSharingAndroidButton));
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { isDesktopShareButtonDisabled } from '../../functions.native';
|
||||
|
||||
import ScreenSharingAndroidButton from './ScreenSharingAndroidButton';
|
||||
import ScreenSharingIosButton from './ScreenSharingIosButton';
|
||||
|
||||
const ScreenSharingButton = (props: any) => (
|
||||
<>
|
||||
{Platform.OS === 'android'
|
||||
&& <ScreenSharingAndroidButton { ...props } />
|
||||
}
|
||||
{Platform.OS === 'ios'
|
||||
&& <ScreenSharingIosButton { ...props } />
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code ScreenSharingButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
_disabled: isDesktopShareButtonDisabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(ScreenSharingButton);
|
||||
@@ -0,0 +1,148 @@
|
||||
import React from 'react';
|
||||
import { NativeModules, Platform, findNodeHandle } from 'react-native';
|
||||
import { ScreenCapturePickerView } from 'react-native-webrtc';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { IOS_SCREENSHARING_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconScreenshare } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { isLocalVideoTrackDesktop } from '../../../base/tracks/functions.native';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ScreenSharingIosButton}.
|
||||
*/
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* True if the button needs to be disabled.
|
||||
*/
|
||||
_disabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether video is currently muted or not.
|
||||
*/
|
||||
_screensharing: boolean;
|
||||
}
|
||||
|
||||
const styles = {
|
||||
screenCapturePickerView: {
|
||||
display: 'none'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An implementation of a button for toggling screen sharing on iOS.
|
||||
*/
|
||||
class ScreenSharingIosButton extends AbstractButton<IProps> {
|
||||
_nativeComponent: React.Component<any, any> | null;
|
||||
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
|
||||
override icon = IconScreenshare;
|
||||
override label = 'toolbar.startScreenSharing';
|
||||
override toggledLabel = 'toolbar.stopScreenSharing';
|
||||
|
||||
/**
|
||||
* Initializes a new {@code ScreenSharingIosButton} instance.
|
||||
*
|
||||
* @param {Object} props - The React {@code Component} props to initialize
|
||||
* the new {@code ScreenSharingIosButton} instance with.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._nativeComponent = null;
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._setNativeComponent = this._setNativeComponent.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
override render() {
|
||||
return (
|
||||
<>
|
||||
{ super.render() }
|
||||
<ScreenCapturePickerView
|
||||
ref = { this._setNativeComponent } // @ts-ignore
|
||||
style = { styles.screenCapturePickerView } />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the React Component wrapping the
|
||||
* {@code RPSystemBroadcastPickerView} component.
|
||||
*
|
||||
* @param {ReactComponent} component - React Component.
|
||||
* @returns {void}
|
||||
*/
|
||||
_setNativeComponent(component: React.Component<any, any> | null) {
|
||||
this._nativeComponent = component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
const handle = findNodeHandle(this._nativeComponent);
|
||||
|
||||
NativeModules.ScreenCapturePickerViewManager.show(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean value indicating if this button is disabled or not.
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return this.props._disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _isToggled() {
|
||||
return this.props._screensharing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code ScreenSharingIosButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _disabled: boolean,
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const enabled = getFeatureFlag(state, IOS_SCREENSHARING_ENABLED, false);
|
||||
|
||||
return {
|
||||
_screensharing: isLocalVideoTrackDesktop(state),
|
||||
|
||||
// TODO: this should work on iOS 12 too, but our trick to show the picker doesn't work.
|
||||
visible: enabled
|
||||
&& Platform.OS === 'ios'
|
||||
&& Number.parseInt(Platform.Version.split('.')[0], 10) >= 14
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ScreenSharingIosButton));
|
||||
@@ -0,0 +1,79 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconCameraRefresh } from '../../../base/icons/svg';
|
||||
import { toggleCameraFacingMode } from '../../../base/media/actions';
|
||||
import { MEDIA_TYPE } from '../../../base/media/constants';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { isLocalTrackMuted } from '../../../base/tracks/functions.native';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ToggleCameraButton}.
|
||||
*/
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* Whether the current conference is in audio only mode or not.
|
||||
*/
|
||||
_audioOnly: boolean;
|
||||
|
||||
/**
|
||||
* Whether video is currently muted or not.
|
||||
*/
|
||||
_videoMuted: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a button for toggling the camera facing mode.
|
||||
*/
|
||||
class ToggleCameraButton extends AbstractButton<IProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.toggleCamera';
|
||||
override icon = IconCameraRefresh;
|
||||
override label = 'toolbar.toggleCamera';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
this.props.dispatch(toggleCameraFacingMode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is disabled or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return this.props._audioOnly || this.props._videoMuted;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code ToggleCameraButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean,
|
||||
* _videoMuted: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_audioOnly: Boolean(audioOnly),
|
||||
_videoMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ToggleCameraButton));
|
||||
@@ -0,0 +1,74 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconAudioOnlyOff } from '../../../base/icons/svg';
|
||||
import { updateSettings } from '../../../base/settings/actions';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ToggleSelfViewButton}.
|
||||
*/
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* Whether the self view is disabled or not.
|
||||
*/
|
||||
_disableSelfView: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a button for toggling the self view.
|
||||
*/
|
||||
class ToggleSelfViewButton extends AbstractButton<IProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.selfView';
|
||||
override icon = IconAudioOnlyOff;
|
||||
override label = 'videothumbnail.hideSelfView';
|
||||
override toggledLabel = 'videothumbnail.showSelfView';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
const { _disableSelfView, dispatch } = this.props;
|
||||
|
||||
dispatch(updateSettings({
|
||||
disableSelfView: !_disableSelfView
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _isToggled() {
|
||||
return this.props._disableSelfView;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code ToggleSelfViewButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _disableSelfView: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { disableSelfView } = state['features/base/settings'];
|
||||
|
||||
return {
|
||||
_disableSelfView: Boolean(disableSelfView)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ToggleSelfViewButton));
|
||||
142
react/features/toolbox/components/native/Toolbox.tsx
Normal file
142
react/features/toolbox/components/native/Toolbox.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import React from 'react';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
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 { iAmVisitor } from '../../../visitors/functions';
|
||||
import { customButtonPressed } from '../../actions.native';
|
||||
import { getVisibleNativeButtons, isToolboxVisible } from '../../functions.native';
|
||||
import { useNativeToolboxButtons } from '../../hooks.native';
|
||||
import { IToolboxNativeButton } from '../../types';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of {@link Toolbox}'s React {@code Component} props.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Whether we are in visitors mode.
|
||||
*/
|
||||
_iAmVisitor: boolean;
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: any;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the toolbox is visible.
|
||||
*/
|
||||
_visible: boolean;
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the conference Toolbox on React Native.
|
||||
*
|
||||
* @param {Object} props - The props of the component.
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
function Toolbox(props: IProps) {
|
||||
const {
|
||||
_iAmVisitor,
|
||||
_styles,
|
||||
_visible,
|
||||
dispatch
|
||||
} = props;
|
||||
|
||||
if (!_visible) {
|
||||
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,
|
||||
iAmVisitor: _iAmVisitor,
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
});
|
||||
|
||||
const bottomEdge = Platform.OS === 'ios' && _visible;
|
||||
const { buttonStylesBorderless, hangupButtonStyles } = _styles;
|
||||
const style = { ...styles.toolbox };
|
||||
|
||||
// We have only hangup and raisehand button in _iAmVisitor mode
|
||||
if (_iAmVisitor) {
|
||||
style.justifyContent = 'center';
|
||||
}
|
||||
|
||||
const renderToolboxButtons = () => {
|
||||
if (!mainMenuButtons?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
mainMenuButtons?.map(({ Content, key, text, ...rest }: IToolboxNativeButton) => (
|
||||
<Content
|
||||
{ ...rest }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () => dispatch(customButtonPressed(key, text)) }
|
||||
isToolboxButton = { true }
|
||||
key = { key }
|
||||
styles = { key === 'hangup' ? hangupButtonStyles : buttonStylesBorderless } />
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { styles.toolboxContainer as ViewStyle }>
|
||||
<SafeAreaView
|
||||
accessibilityRole = 'toolbar'
|
||||
|
||||
// @ts-ignore
|
||||
edges = { [ bottomEdge && 'bottom' ].filter(Boolean) }
|
||||
pointerEvents = 'box-none'
|
||||
style = { style as ViewStyle }>
|
||||
{ renderToolboxButtons() }
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps parts of the redux state to {@link Toolbox} (React {@code Component})
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux state of which parts are to be mapped to
|
||||
* {@code Toolbox} props.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
_iAmVisitor: iAmVisitor(state),
|
||||
_styles: ColorSchemeRegistry.get(state, 'Toolbox'),
|
||||
_visible: isToolboxVisible(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(Toolbox);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import AbstractVideoMuteButton, { IProps, mapStateToProps } from '../AbstractVideoMuteButton';
|
||||
|
||||
|
||||
export default translate(connect(mapStateToProps)(AbstractVideoMuteButton<IProps>));
|
||||
216
react/features/toolbox/components/native/styles.ts
Normal file
216
react/features/toolbox/components/native/styles.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
|
||||
import { schemeColor } from '../../../base/color-scheme/functions';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
const BUTTON_SIZE = 48;
|
||||
|
||||
// Toolbox, toolbar:
|
||||
|
||||
/**
|
||||
* The style of toolbar buttons.
|
||||
*/
|
||||
const toolbarButton = {
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
borderWidth: 0,
|
||||
flex: 0,
|
||||
flexDirection: 'row',
|
||||
height: BUTTON_SIZE,
|
||||
justifyContent: 'center',
|
||||
marginHorizontal: 6,
|
||||
marginVertical: 6,
|
||||
width: BUTTON_SIZE
|
||||
};
|
||||
|
||||
/**
|
||||
* The icon style of the toolbar buttons.
|
||||
*/
|
||||
const toolbarButtonIcon = {
|
||||
alignSelf: 'center',
|
||||
color: BaseTheme.palette.icon04,
|
||||
fontSize: 24
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The icon style of toolbar buttons which display white icons.
|
||||
*/
|
||||
const whiteToolbarButtonIcon = {
|
||||
...toolbarButtonIcon,
|
||||
color: BaseTheme.palette.icon01
|
||||
};
|
||||
|
||||
/**
|
||||
* The style of reaction buttons.
|
||||
*/
|
||||
const reactionButton = {
|
||||
...toolbarButton,
|
||||
backgroundColor: 'transparent',
|
||||
alignItems: 'center',
|
||||
marginTop: 0,
|
||||
marginHorizontal: 0
|
||||
};
|
||||
|
||||
const gifButton = {
|
||||
...reactionButton,
|
||||
backgroundColor: '#000'
|
||||
};
|
||||
|
||||
/**
|
||||
* The style of the emoji on the reaction buttons.
|
||||
*/
|
||||
const reactionEmoji = {
|
||||
fontSize: 20,
|
||||
color: BaseTheme.palette.icon01
|
||||
};
|
||||
|
||||
const reactionMenu = {
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.ui01
|
||||
};
|
||||
|
||||
/**
|
||||
* The Toolbox and toolbar related styles.
|
||||
*/
|
||||
const styles = {
|
||||
|
||||
sheetGestureRecognizer: {
|
||||
alignItems: 'stretch',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the toolbar.
|
||||
*/
|
||||
toolbox: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.uiBackground,
|
||||
borderTopLeftRadius: 3,
|
||||
borderTopRightRadius: 3,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the root/top-level container of {@link Toolbox}.
|
||||
*/
|
||||
toolboxContainer: {
|
||||
backgroundColor: BaseTheme.palette.uiBackground,
|
||||
flexDirection: 'column',
|
||||
maxWidth: 580,
|
||||
marginHorizontal: 'auto',
|
||||
marginVertical: BaseTheme.spacing[0],
|
||||
paddingHorizontal: BaseTheme.spacing[2],
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
toolboxButtonIconContainer: {
|
||||
alignItems: 'center',
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: BaseTheme.spacing[7],
|
||||
justifyContent: 'center',
|
||||
width: BaseTheme.spacing[7]
|
||||
}
|
||||
};
|
||||
|
||||
export default styles;
|
||||
|
||||
/**
|
||||
* Color schemed styles for the @{Toolbox} component.
|
||||
*/
|
||||
ColorSchemeRegistry.register('Toolbox', {
|
||||
/**
|
||||
* Styles for buttons in the toolbar.
|
||||
*/
|
||||
buttonStyles: {
|
||||
iconStyle: toolbarButtonIcon,
|
||||
style: toolbarButton
|
||||
},
|
||||
|
||||
buttonStylesBorderless: {
|
||||
iconStyle: whiteToolbarButtonIcon,
|
||||
style: {
|
||||
...toolbarButton,
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
underlayColor: 'transparent'
|
||||
},
|
||||
|
||||
backgroundToggle: {
|
||||
backgroundColor: BaseTheme.palette.ui04
|
||||
},
|
||||
|
||||
hangupMenuContainer: {
|
||||
marginHorizontal: BaseTheme.spacing[2],
|
||||
marginVertical: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
hangupButton: {
|
||||
flex: 1,
|
||||
marginHorizontal: BaseTheme.spacing[2],
|
||||
marginVertical: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
hangupButtonStyles: {
|
||||
iconStyle: whiteToolbarButtonIcon,
|
||||
style: {
|
||||
...toolbarButton,
|
||||
backgroundColor: schemeColor('hangup')
|
||||
},
|
||||
underlayColor: BaseTheme.palette.ui04
|
||||
},
|
||||
|
||||
reactionDialog: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
|
||||
overflowReactionMenu: {
|
||||
...reactionMenu,
|
||||
padding: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
reactionMenu: {
|
||||
...reactionMenu,
|
||||
paddingHorizontal: BaseTheme.spacing[3],
|
||||
borderRadius: 3,
|
||||
width: 360
|
||||
},
|
||||
|
||||
reactionRow: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
|
||||
reactionButton: {
|
||||
gifButton,
|
||||
style: reactionButton,
|
||||
underlayColor: BaseTheme.palette.ui04,
|
||||
emoji: reactionEmoji
|
||||
},
|
||||
|
||||
emojiAnimation: {
|
||||
color: BaseTheme.palette.icon01,
|
||||
position: 'absolute',
|
||||
zIndex: 1001,
|
||||
elevation: 2,
|
||||
fontSize: 20,
|
||||
left: '50%',
|
||||
top: '100%'
|
||||
},
|
||||
|
||||
/**
|
||||
* Styles for toggled buttons in the toolbar.
|
||||
*/
|
||||
toggledButtonStyles: {
|
||||
iconStyle: whiteToolbarButtonIcon,
|
||||
style: {
|
||||
...toolbarButton
|
||||
},
|
||||
underlayColor: 'transparent'
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user