feat(toolbox): implement buttons using ToolboxItem
Currently the following are implemented: - AudioMuteButton - HangupButton - VideoMuteButton In order to implement these new buttons a new abstract class was introduced, which abstracts the ToolboxItem into a button with enough hooks so a stateful and a stateless version of it can be created. This patch only adds the stateful implementation of the aforementioned buttons.
This commit is contained in:
parent
8d94cc5cb2
commit
b634f6b200
|
@ -14,6 +14,7 @@ import {
|
|||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../base/responsive-ui';
|
||||
import { ColorPalette } from '../../base/styles';
|
||||
import { InviteButton } from '../../invite';
|
||||
import {
|
||||
EnterPictureInPictureToolbarButton
|
||||
|
@ -159,6 +160,11 @@ class Toolbox extends Component<Props> {
|
|||
_renderPrimaryToolbar() {
|
||||
const audioButtonStyles = this._getMuteButtonStyles(MEDIA_TYPE.AUDIO);
|
||||
const videoButtonStyles = this._getMuteButtonStyles(MEDIA_TYPE.VIDEO);
|
||||
const hangupButtonStyles = {
|
||||
iconStyle: styles.whitePrimaryToolbarButtonIcon,
|
||||
style: styles.hangup,
|
||||
underlayColor: ColorPalette.buttonUnderlay
|
||||
};
|
||||
|
||||
/* eslint-disable react/jsx-handler-names */
|
||||
|
||||
|
@ -168,7 +174,7 @@ class Toolbox extends Component<Props> {
|
|||
pointerEvents = 'box-none'
|
||||
style = { styles.primaryToolbar }>
|
||||
<AudioMuteButton styles = { audioButtonStyles } />
|
||||
<HangupButton />
|
||||
<HangupButton styles = { hangupButtonStyles } />
|
||||
<VideoMuteButton styles = { videoButtonStyles } />
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -354,12 +354,12 @@ class Toolbox extends Component<Props, State> {
|
|||
</div> }
|
||||
</div>
|
||||
<div className = 'button-group-center'>
|
||||
{ this._shouldShowButton('microphone')
|
||||
&& <AudioMuteButton /> }
|
||||
{ this._shouldShowButton('hangup')
|
||||
&& <HangupButton /> }
|
||||
{ this._shouldShowButton('camera')
|
||||
&& <VideoMuteButton /> }
|
||||
<AudioMuteButton
|
||||
visible = { this._shouldShowButton('microphone') } />
|
||||
<HangupButton
|
||||
visible = { this._shouldShowButton('hangup') } />
|
||||
<VideoMuteButton
|
||||
visible = { this._shouldShowButton('camera') } />
|
||||
</div>
|
||||
<div className = 'button-group-right'>
|
||||
{ this._shouldShowButton('invite')
|
||||
|
|
|
@ -1,87 +1,62 @@
|
|||
// @flow
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
|
||||
import {
|
||||
AUDIO_MUTE,
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import {
|
||||
VIDEO_MUTISM_AUTHORITY,
|
||||
setAudioMuted
|
||||
} from '../../../base/media';
|
||||
import AbstractButton from './AbstractButton';
|
||||
import type { Props } from './AbstractButton';
|
||||
|
||||
/**
|
||||
* An abstract implementation of a button for toggling audio mute.
|
||||
*/
|
||||
export default class AbstractAudioMuteButton extends Component<*> {
|
||||
/**
|
||||
* {@code AbstractAudioMuteButton} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Whether or not the local microphone is muted.
|
||||
*/
|
||||
_audioMuted: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Invoked to toggle audio mute.
|
||||
*/
|
||||
dispatch: PropTypes.func
|
||||
};
|
||||
class AbstractAudioMuteButton<P: Props, S: *> extends AbstractButton<P, S> {
|
||||
accessibilityLabel = 'Audio mute';
|
||||
iconName = 'icon-microphone';
|
||||
toggledIconName = 'icon-mic-disabled toggled';
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractAudioMuteButton} instance.
|
||||
*
|
||||
* @param {Props} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once per instance.
|
||||
this._onToolbarToggleAudio = this._onToolbarToggleAudio.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to toggle audio mute.
|
||||
* Handles clicking / pressing the button, and toggles the audio mute state
|
||||
* accordingly.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doToggleAudio() {
|
||||
// The user sees the reality i.e. the state of base/tracks and intends
|
||||
// to change reality by tapping on the respective button i.e. the user
|
||||
// sets the state of base/media. Whether the user's intention will turn
|
||||
// into reality is a whole different story which is of no concern to the
|
||||
// tapping.
|
||||
this.props.dispatch(
|
||||
setAudioMuted(
|
||||
!this.props._audioMuted,
|
||||
VIDEO_MUTISM_AUTHORITY.USER,
|
||||
/* ensureTrack */ true));
|
||||
_handleClick() {
|
||||
this._setAudioMuted(!this._isAudioMuted());
|
||||
}
|
||||
|
||||
_onToolbarToggleAudio: () => void;
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* boolean value indicating if audio is muted or not.
|
||||
*
|
||||
* @abstract
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isAudioMuted() {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an analytics toolbar event and dispatches an action for toggling
|
||||
* audio mute.
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isToggled() {
|
||||
return this._isAudioMuted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform the actual setting of the audio mute / unmute
|
||||
* action.
|
||||
*
|
||||
* @param {boolean} audioMuted - Whether video should be muted or not.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarToggleAudio() {
|
||||
sendAnalytics(createToolbarEvent(
|
||||
AUDIO_MUTE,
|
||||
{
|
||||
enable: !this.props._audioMuted
|
||||
}));
|
||||
|
||||
this._doToggleAudio();
|
||||
_setAudioMuted(audioMuted: boolean) { // eslint-disable-line no-unused-vars
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
}
|
||||
|
||||
export default AbstractAudioMuteButton;
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import ToolboxItem from '../ToolboxItem';
|
||||
import type { Styles } from '../AbstractToolboxItem';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* Whether to show the label or not.
|
||||
*/
|
||||
showLabel: boolean,
|
||||
|
||||
/**
|
||||
* Collection of styles for the button.
|
||||
*/
|
||||
styles: ?Styles,
|
||||
|
||||
/**
|
||||
* Collection of styles for the button, when in toggled state.
|
||||
*/
|
||||
toggledStyles: ?Styles,
|
||||
|
||||
/**
|
||||
* From which direction the tooltip should appear, relative to the
|
||||
* button.
|
||||
*/
|
||||
tooltipPosition: string,
|
||||
|
||||
/**
|
||||
* Whether this button is visible or not.
|
||||
*/
|
||||
visible: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* An abstract implementation of a button.
|
||||
*/
|
||||
export default class AbstractButton<P: Props, S : *> extends Component<P, S> {
|
||||
static defaultProps = {
|
||||
showLabel: false,
|
||||
styles: undefined,
|
||||
toggledStyles: undefined,
|
||||
tooltipPosition: 'top',
|
||||
visible: true
|
||||
};
|
||||
|
||||
/**
|
||||
* A succinct description of what the button does. Used by accessibility
|
||||
* tools and torture tests.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
accessibilityLabel: string;
|
||||
|
||||
/**
|
||||
* The name of the icon of this button.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
iconName: string;
|
||||
|
||||
/**
|
||||
* The text associated with this button. When `showLabel` is set to
|
||||
* {@code true}, it will be displayed alongside the icon.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* The name of the icon of this button, when toggled.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
toggledIconName: string;
|
||||
|
||||
/**
|
||||
* The text to display in the tooltip. Used only on web.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
tooltip: string;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractButton} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractAudioMuteButton} instance with.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which should be used
|
||||
* to handle the button being clicked / pressed.
|
||||
*
|
||||
* @abstract
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current icon name, taking the toggled state into account. If no
|
||||
* toggled icon is provided, the regular icon will also be used in the
|
||||
* toggled state.
|
||||
*
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_getIconName() {
|
||||
return (this._isToggled() ? this.toggledIconName : this.iconName)
|
||||
|| this.iconName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current styles, taking the toggled state into account. If no
|
||||
* toggled styles are provided, the regular styles will also be used in the
|
||||
* toggled state.
|
||||
*
|
||||
* @private
|
||||
* @returns {?Styles}
|
||||
*/
|
||||
_getStyles() {
|
||||
const { styles, toggledStyles } = this.props;
|
||||
|
||||
return (this._isToggled() ? toggledStyles : styles) || styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* boolean value indicating if this button is disabled or not.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* boolean value indicating if this button is toggled or not.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isToggled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
_onClick: (*) => void;
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and toggles the audio mute state
|
||||
* accordingly.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
this._handleClick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const props = {
|
||||
...this.props,
|
||||
accessibilityLabel: this.accessibilityLabel,
|
||||
iconName: this._getIconName(),
|
||||
label: this.label,
|
||||
styles: this._getStyles(),
|
||||
tooltip: this.tooltip
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolboxItem
|
||||
disabled = { this._isDisabled() }
|
||||
onClick = { this._onClick }
|
||||
{ ...props } />
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,51 +1,35 @@
|
|||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
import {
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import AbstractButton from './AbstractButton';
|
||||
import type { Props } from './AbstractButton';
|
||||
|
||||
/**
|
||||
* An abstract implementation of a button for leaving the conference.
|
||||
* An abstract implementation of a button for disconnecting a conference.
|
||||
*/
|
||||
export default class AbstractHangupButton extends Component<*> {
|
||||
/**
|
||||
* Initializes a new {@code AbstractHangupButton} instance.
|
||||
*
|
||||
* @param {Props} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
class AbstractHangupButton<P : Props, S: *> extends AbstractButton<P, S> {
|
||||
accessibilityLabel = 'Hangup';
|
||||
iconName = 'icon-hangup';
|
||||
|
||||
// Bind event handler so it is only bound once per instance.
|
||||
this._onToolbarHangup = this._onToolbarHangup.bind(this);
|
||||
/**
|
||||
* Handles clicking / pressing the button, and disconnects the conference.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
this._doHangup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action for leaving the current conference.
|
||||
* Helper function to perform the actual hangup action.
|
||||
*
|
||||
* @abstract
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doHangup() {
|
||||
/* to be implemented by descendants */
|
||||
}
|
||||
|
||||
_onToolbarHangup: () => void;
|
||||
|
||||
/**
|
||||
* Creates an analytics toolbar event and dispatches an action for leaving
|
||||
* the current conference.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarHangup() {
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
|
||||
this._doHangup();
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
}
|
||||
|
||||
export default AbstractHangupButton;
|
||||
|
|
|
@ -1,88 +1,61 @@
|
|||
// @flow
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
|
||||
import {
|
||||
VIDEO_MUTE,
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import {
|
||||
VIDEO_MUTISM_AUTHORITY,
|
||||
setVideoMuted
|
||||
} from '../../../base/media';
|
||||
import AbstractButton from './AbstractButton';
|
||||
import type { Props } from './AbstractButton';
|
||||
|
||||
/**
|
||||
* An abstract implementation of a button for toggling video mute.
|
||||
*/
|
||||
export default class AbstractVideoMuteButton extends Component<*> {
|
||||
/**
|
||||
* {@code AbstractVideoMuteButton} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Whether or not the local camera is muted.
|
||||
*/
|
||||
_videoMuted: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Invoked to toggle video mute.
|
||||
*/
|
||||
dispatch: PropTypes.func
|
||||
};
|
||||
class AbstractVideoMuteButton<P : Props, S : *> extends AbstractButton<P, S> {
|
||||
accessibilityLabel = 'Video mute';
|
||||
iconName = 'icon-camera';
|
||||
toggledIconName = 'icon-camera-disabled toggled';
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractVideoMuteButton} instance.
|
||||
*
|
||||
* @param {Props} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once per instance.
|
||||
this._onToolbarToggleVideo = this._onToolbarToggleVideo.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to toggle the mute state of the video/camera.
|
||||
* Handles clicking / pressing the button, and toggles the video mute state
|
||||
* accordingly.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doToggleVideo() {
|
||||
// The user sees the reality i.e. the state of base/tracks and intends
|
||||
// to change reality by tapping on the respective button i.e. the user
|
||||
// sets the state of base/media. Whether the user's intention will turn
|
||||
// into reality is a whole different story which is of no concern to the
|
||||
// tapping.
|
||||
this.props.dispatch(
|
||||
setVideoMuted(
|
||||
!this.props._videoMuted,
|
||||
VIDEO_MUTISM_AUTHORITY.USER,
|
||||
/* ensureTrack */ true));
|
||||
_handleClick() {
|
||||
this._setVideoMuted(!this._isVideoMuted());
|
||||
}
|
||||
|
||||
|
||||
_onToolbarToggleVideo: () => void;
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isToggled() {
|
||||
return this._isVideoMuted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an analytics toolbar event and dispatches an action for toggling
|
||||
* video mute.
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* boolean value indicating if video is muted or not.
|
||||
*
|
||||
* @abstract
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isVideoMuted() {
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform the actual setting of the video mute / unmute
|
||||
* action.
|
||||
*
|
||||
* @param {boolean} videoMuted - Whether video should be muted or not.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarToggleVideo() {
|
||||
sendAnalytics(createToolbarEvent(
|
||||
VIDEO_MUTE,
|
||||
{
|
||||
enable: !this.props._videoMuted
|
||||
}));
|
||||
|
||||
this._doToggleVideo();
|
||||
_setVideoMuted(videoMuted: boolean) { // eslint-disable-line no-unused-vars
|
||||
// To be implemented by subclass.
|
||||
}
|
||||
}
|
||||
|
||||
export default AbstractVideoMuteButton;
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
AUDIO_MUTE,
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
setAudioMuted
|
||||
} from '../../../base/media';
|
||||
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||
|
||||
import AbstractAudioMuteButton from './AbstractAudioMuteButton';
|
||||
import type { Props as AbstractButtonProps } from './AbstractButton';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Whether audio is currently muted or not.
|
||||
*/
|
||||
_audioMuted: boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders a toolbar button for toggling audio mute.
|
||||
*
|
||||
* @extends AbstractAudioMuteButton
|
||||
*/
|
||||
class AudioMuteButton extends AbstractAudioMuteButton<Props, *> {
|
||||
label = 'toolbar.mute';
|
||||
tooltip = 'toolbar.mute';
|
||||
|
||||
/**
|
||||
* Indicates if this button should be disabled or not.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if audio is currently muted ot nor.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isAudioMuted() {
|
||||
return this.props._audioMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the muted state.
|
||||
*
|
||||
* @param {boolean} audioMuted - Whether audio should be muted or not.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setAudioMuted(audioMuted: boolean) {
|
||||
sendAnalytics(createToolbarEvent(AUDIO_MUTE, { enable: audioMuted }));
|
||||
this.props.dispatch(setAudioMuted(audioMuted));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code AudioMuteButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioMuted: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_audioMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(AudioMuteButton));
|
|
@ -1,73 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||
|
||||
import AbstractAudioMuteButton from './AbstractAudioMuteButton';
|
||||
import ToolbarButton from '../ToolbarButton';
|
||||
|
||||
/**
|
||||
* Component that renders a toolbar button for toggling audio mute.
|
||||
*
|
||||
* @extends AbstractAudioMuteButton
|
||||
*/
|
||||
export class AudioMuteButton extends AbstractAudioMuteButton {
|
||||
/**
|
||||
* {@code AbstractAudioMuteButton} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
...AbstractAudioMuteButton.propTypes,
|
||||
|
||||
/**
|
||||
* Styles to be applied to the button and the icon to show.
|
||||
*/
|
||||
buttonStyles: PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { buttonStyles } = this.props;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
iconName = { buttonStyles.iconName }
|
||||
iconStyle = { buttonStyles.iconStyle }
|
||||
onClick = { this._onToolbarToggleAudio }
|
||||
style = { buttonStyles.style } />
|
||||
);
|
||||
}
|
||||
|
||||
_onToolbarToggleAudio: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code AudioMuteButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioMuted: boolean,
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_audioMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default connect(_mapStateToProps)(AudioMuteButton);
|
|
@ -1,181 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import UIEvents from '../../../../../service/UI/UIEvents';
|
||||
import {
|
||||
ACTION_SHORTCUT_TRIGGERED,
|
||||
AUDIO_MUTE,
|
||||
createShortcutEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||
|
||||
import AbstractAudioMuteButton from './AbstractAudioMuteButton';
|
||||
import ToolbarButton from '../ToolbarButton';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Component that renders a toolbar button for toggling audio mute.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export class AudioMuteButton extends AbstractAudioMuteButton {
|
||||
/**
|
||||
* Default values for {@code AudioMuteButton} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
tooltipPosition: 'top'
|
||||
};
|
||||
|
||||
/**
|
||||
* {@code AudioMuteButton} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
...AbstractAudioMuteButton.propTypes,
|
||||
|
||||
/**
|
||||
* The {@code JitsiConference} for the current conference.
|
||||
*/
|
||||
_conference: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Invoked to update the audio mute status.
|
||||
*/
|
||||
dispatch: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Where the tooltip should display, relative to the button.
|
||||
*/
|
||||
tooltipPosition: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AudioMuteButton} instance.
|
||||
*
|
||||
* @param {Props} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so it is only bound once per instance.
|
||||
this._onShortcutToggleAudio = this._onShortcutToggleAudio.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a keyboard shortcuts for toggling audio mute.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
'M',
|
||||
null,
|
||||
this._onShortcutToggleAudio,
|
||||
'keyboardShortcuts.mute');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the registered keyboard shortcut handler.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
APP.keyboardshortcut.unregisterShortcut('M');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _audioMuted, _conference, t, tooltipPosition } = this.props;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
accessibilityLabel = 'Audio mute'
|
||||
iconName = { _audioMuted && _conference
|
||||
? 'icon-mic-disabled toggled'
|
||||
: 'icon-microphone' }
|
||||
onClick = { this._onToolbarToggleAudio }
|
||||
tooltip = { t('toolbar.mute') }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
);
|
||||
}
|
||||
|
||||
_doToggleAudio: () => void;
|
||||
|
||||
/**
|
||||
* Emits an event to signal audio mute should be toggled.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doToggleAudio() {
|
||||
// The old conference logic must be used for now as the redux flows do
|
||||
// not handle all cases, such as unmuting when the config
|
||||
// startWithAudioMuted is true.
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_MUTED, !this.props._audioMuted, true);
|
||||
}
|
||||
|
||||
_onShortcutToggleAudio: () => void;
|
||||
|
||||
/**
|
||||
* Creates an analytics keyboard shortcut event and dispatches an action for
|
||||
* toggling audio mute.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onShortcutToggleAudio() {
|
||||
sendAnalytics(createShortcutEvent(
|
||||
AUDIO_MUTE,
|
||||
ACTION_SHORTCUT_TRIGGERED,
|
||||
{ enable: !this.props._audioMuted }));
|
||||
|
||||
this._doToggleAudio();
|
||||
}
|
||||
|
||||
_onToolbarToggleAudio: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code AudioMuteButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioMuted: boolean,
|
||||
* _conference: Object,
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_audioMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO),
|
||||
_conference: state['features/base/conference'].conference
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(AudioMuteButton));
|
|
@ -0,0 +1,61 @@
|
|||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../analytics';
|
||||
import { appNavigate } from '../../../app';
|
||||
|
||||
import { disconnect } from '../../../base/connection';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import AbstractHangupButton from './AbstractHangupButton';
|
||||
import type { Props as AbstractButtonProps } from './AbstractButton';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders a toolbar button for leaving the current conference.
|
||||
*
|
||||
* @extends AbstractHangupButton
|
||||
*/
|
||||
class HangupButton extends AbstractHangupButton<Props, *> {
|
||||
label = 'toolbar.hangup';
|
||||
tooltip = 'toolbar.hangup';
|
||||
|
||||
/**
|
||||
* Helper function to perform the actual hangup action.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doHangup() {
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
|
||||
// FIXME: these should be unified.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
} else {
|
||||
this.props.dispatch(disconnect(true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this button should be disabled or not.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(HangupButton));
|
|
@ -1,63 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { appNavigate } from '../../../app';
|
||||
import { ColorPalette } from '../../../base/styles';
|
||||
|
||||
import AbstractHangupButton from './AbstractHangupButton';
|
||||
import ToolbarButton from '../ToolbarButton';
|
||||
import styles from '../styles';
|
||||
|
||||
/**
|
||||
* Component that renders a toolbar button for leaving the current conference.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class HangupButton extends AbstractHangupButton {
|
||||
/**
|
||||
* {@code HangupButton} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Invoked to leave the conference.
|
||||
*/
|
||||
dispatch: PropTypes.func
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<ToolbarButton
|
||||
accessibilityLabel = 'Hangup'
|
||||
iconName = 'hangup'
|
||||
iconStyle = { styles.whitePrimaryToolbarButtonIcon }
|
||||
onClick = { this._onToolbarHangup }
|
||||
style = { styles.hangup }
|
||||
underlayColor = { ColorPalette.buttonUnderlay } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action for leaving the current conference.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doHangup() {
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
}
|
||||
|
||||
_onToolbarHangup: () => void;
|
||||
}
|
||||
|
||||
export default connect()(HangupButton);
|
|
@ -1,83 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { disconnect } from '../../../base/connection';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import AbstractHangupButton from './AbstractHangupButton';
|
||||
import ToolbarButton from '../ToolbarButton';
|
||||
|
||||
/**
|
||||
* Component that renders a toolbar button for leaving the current conference.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export class HangupButton extends AbstractHangupButton {
|
||||
/**
|
||||
* Default values for {@code HangupButton} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
tooltipPosition: 'top'
|
||||
};
|
||||
|
||||
/**
|
||||
* {@code HangupButton} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Invoked to trigger conference leave.
|
||||
*/
|
||||
dispatch: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Where the tooltip should display, relative to the button.
|
||||
*/
|
||||
tooltipPosition: PropTypes.string
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t, tooltipPosition } = this.props;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
accessibilityLabel = 'Hangup'
|
||||
iconName = 'icon-hangup'
|
||||
onClick = { this._onToolbarHangup }
|
||||
tooltip = { t('toolbar.hangup') }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
);
|
||||
}
|
||||
|
||||
_onToolbarHangup: () => void;
|
||||
|
||||
/**
|
||||
* Dispatches an action for leaving the current conference.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doHangup() {
|
||||
this.props.dispatch(disconnect(true));
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(HangupButton));
|
|
@ -0,0 +1,109 @@
|
|||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
VIDEO_MUTE,
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
VIDEO_MUTISM_AUTHORITY,
|
||||
setVideoMuted
|
||||
} from '../../../base/media';
|
||||
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||
|
||||
import AbstractVideoMuteButton from './AbstractVideoMuteButton';
|
||||
import type { Props as AbstractButtonProps } from './AbstractButton';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Whether the current conference is in audio only mode or not.
|
||||
*/
|
||||
_audioOnly: boolean,
|
||||
|
||||
/**
|
||||
* Whether video is currently muted or not.
|
||||
*/
|
||||
_videoMuted: boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders a toolbar button for toggling video mute.
|
||||
*
|
||||
* @extends AbstractVideoMuteButton
|
||||
*/
|
||||
class VideoMuteButton extends AbstractVideoMuteButton<Props, *> {
|
||||
label = 'toolbar.videomute';
|
||||
tooltip = 'toolbar.videomute';
|
||||
|
||||
/**
|
||||
* Indicates if this button should be disabled or not.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return this.props._audioOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if video is currently muted ot nor.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isVideoMuted() {
|
||||
return this.props._videoMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the muted state.
|
||||
*
|
||||
* @param {boolean} videoMuted - Whether video should be muted or not.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setVideoMuted(videoMuted: boolean) {
|
||||
sendAnalytics(createToolbarEvent(VIDEO_MUTE, { enable: videoMuted }));
|
||||
this.props.dispatch(
|
||||
setVideoMuted(
|
||||
videoMuted,
|
||||
VIDEO_MUTISM_AUTHORITY.USER,
|
||||
/* ensureTrack */ true));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for the
|
||||
* {@code VideoMuteButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean,
|
||||
* _videoMuted: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const { audioOnly } = state['features/base/conference'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_audioOnly: Boolean(audioOnly),
|
||||
_videoMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(VideoMuteButton));
|
|
@ -1,82 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||
|
||||
import AbstractVideoMuteButton from './AbstractVideoMuteButton';
|
||||
import ToolbarButton from '../ToolbarButton';
|
||||
|
||||
/**
|
||||
* Component that renders a toolbar button for toggling video mute.
|
||||
*
|
||||
* @extends AbstractVideoMuteButton
|
||||
*/
|
||||
class VideoMuteButton extends AbstractVideoMuteButton {
|
||||
/**
|
||||
* {@code VideoMuteButton} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
...AbstractVideoMuteButton.propTypes,
|
||||
|
||||
/**
|
||||
* Whether or not the local participant is current in audio only mode.
|
||||
* Video mute toggling is disabled in audio only mode.
|
||||
*/
|
||||
_audioOnly: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Styles to be applied to the button and the icon to show.
|
||||
*/
|
||||
buttonStyles: PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _audioOnly, buttonStyles } = this.props;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
disabled = { _audioOnly }
|
||||
iconName = { buttonStyles.iconName }
|
||||
iconStyle = { buttonStyles.iconStyle }
|
||||
onClick = { this._onToolbarToggleVideo }
|
||||
style = { buttonStyles.style } />
|
||||
);
|
||||
}
|
||||
|
||||
_onToolbarToggleVideo: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code VideoMuteButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean,
|
||||
* _videoMuted: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const conference = state['features/base/conference'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_audioOnly: Boolean(conference.audioOnly),
|
||||
_videoMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(VideoMuteButton);
|
|
@ -1,173 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import UIEvents from '../../../../../service/UI/UIEvents';
|
||||
import {
|
||||
ACTION_SHORTCUT_TRIGGERED,
|
||||
VIDEO_MUTE,
|
||||
createShortcutEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||
|
||||
import AbstractVideoMuteButton from './AbstractVideoMuteButton';
|
||||
import ToolbarButton from '../ToolbarButton';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Component that renders a toolbar button for toggling video mute.
|
||||
*
|
||||
* @extends AbstractVideoMuteButton
|
||||
*/
|
||||
export class VideoMuteButton extends AbstractVideoMuteButton {
|
||||
/**
|
||||
* Default values for {@code VideoMuteButton} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
tooltipPosition: 'top'
|
||||
};
|
||||
|
||||
/**
|
||||
* {@code VideoMuteButton} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
...AbstractVideoMuteButton.propTypes,
|
||||
|
||||
/**
|
||||
* The {@code JitsiConference} for the current conference.
|
||||
*/
|
||||
_conference: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Where the tooltip should display, relative to the button.
|
||||
*/
|
||||
tooltipPosition: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code VideoMuteButton} instance.
|
||||
*
|
||||
* @param {Props} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onShortcutToggleVideo = this._onShortcutToggleVideo.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a keyboard shortcuts for toggling video mute.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
'V',
|
||||
null,
|
||||
this._onShortcutToggleVideo,
|
||||
'keyboardShortcuts.videoMute');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the registered keyboard shortcut handler.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
APP.keyboardshortcut.unregisterShortcut('V');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _conference, _videoMuted, t, tooltipPosition } = this.props;
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
accessibilityLabel = 'Video mute'
|
||||
iconName = { _videoMuted && _conference
|
||||
? 'icon-camera-disabled toggled'
|
||||
: 'icon-camera' }
|
||||
onClick = { this._onToolbarToggleVideo }
|
||||
tooltip = { t('toolbar.videomute') }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
);
|
||||
}
|
||||
|
||||
_doToggleVideo: () => void;
|
||||
|
||||
/**
|
||||
* Emits an event to signal video mute should be toggled.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doToggleVideo() {
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_MUTED, !this.props._videoMuted);
|
||||
}
|
||||
|
||||
_onShortcutToggleVideo: () => void;
|
||||
|
||||
/**
|
||||
* Creates an analytics keyboard shortcut event for and dispatches an action
|
||||
* for toggling video mute.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onShortcutToggleVideo() {
|
||||
sendAnalytics(createShortcutEvent(
|
||||
VIDEO_MUTE,
|
||||
ACTION_SHORTCUT_TRIGGERED,
|
||||
{ enable: !this.props._videoMuted }));
|
||||
|
||||
this._doToggleVideo();
|
||||
}
|
||||
|
||||
_onToolbarToggleVideo: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code AudioMuteButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _conference: Object,
|
||||
* _videoMuted: boolean,
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_conference: state['features/base/conference'].conference,
|
||||
_videoMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(VideoMuteButton));
|
Loading…
Reference in New Issue