From b634f6b20078c4ac40097820cc24b2aaa6322907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Fri, 13 Apr 2018 15:03:12 +0200 Subject: [PATCH] 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. --- .../toolbox/components/Toolbox.native.js | 8 +- .../toolbox/components/Toolbox.web.js | 12 +- .../buttons/AbstractAudioMuteButton.js | 105 ++++------ .../components/buttons/AbstractButton.js | 195 ++++++++++++++++++ .../buttons/AbstractHangupButton.js | 54 ++--- .../buttons/AbstractVideoMuteButton.js | 105 ++++------ .../components/buttons/AudioMuteButton.js | 96 +++++++++ .../buttons/AudioMuteButton.native.js | 73 ------- .../components/buttons/AudioMuteButton.web.js | 181 ---------------- .../components/buttons/HangupButton.js | 61 ++++++ .../components/buttons/HangupButton.native.js | 63 ------ .../components/buttons/HangupButton.web.js | 83 -------- .../components/buttons/VideoMuteButton.js | 109 ++++++++++ .../buttons/VideoMuteButton.native.js | 82 -------- .../components/buttons/VideoMuteButton.web.js | 173 ---------------- 15 files changed, 572 insertions(+), 828 deletions(-) create mode 100644 react/features/toolbox/components/buttons/AbstractButton.js create mode 100644 react/features/toolbox/components/buttons/AudioMuteButton.js delete mode 100644 react/features/toolbox/components/buttons/AudioMuteButton.native.js delete mode 100644 react/features/toolbox/components/buttons/AudioMuteButton.web.js create mode 100644 react/features/toolbox/components/buttons/HangupButton.js delete mode 100644 react/features/toolbox/components/buttons/HangupButton.native.js delete mode 100644 react/features/toolbox/components/buttons/HangupButton.web.js create mode 100644 react/features/toolbox/components/buttons/VideoMuteButton.js delete mode 100644 react/features/toolbox/components/buttons/VideoMuteButton.native.js delete mode 100644 react/features/toolbox/components/buttons/VideoMuteButton.web.js diff --git a/react/features/toolbox/components/Toolbox.native.js b/react/features/toolbox/components/Toolbox.native.js index 289eb46e1..4acc02101 100644 --- a/react/features/toolbox/components/Toolbox.native.js +++ b/react/features/toolbox/components/Toolbox.native.js @@ -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 { _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 { pointerEvents = 'box-none' style = { styles.primaryToolbar }> - + ); diff --git a/react/features/toolbox/components/Toolbox.web.js b/react/features/toolbox/components/Toolbox.web.js index 6271ad4b7..cefcdd95f 100644 --- a/react/features/toolbox/components/Toolbox.web.js +++ b/react/features/toolbox/components/Toolbox.web.js @@ -354,12 +354,12 @@ class Toolbox extends Component { }
- { this._shouldShowButton('microphone') - && } - { this._shouldShowButton('hangup') - && } - { this._shouldShowButton('camera') - && } + + +
{ this._shouldShowButton('invite') diff --git a/react/features/toolbox/components/buttons/AbstractAudioMuteButton.js b/react/features/toolbox/components/buttons/AbstractAudioMuteButton.js index c86e9a127..f462aca84 100644 --- a/react/features/toolbox/components/buttons/AbstractAudioMuteButton.js +++ b/react/features/toolbox/components/buttons/AbstractAudioMuteButton.js @@ -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 extends AbstractButton { + 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; diff --git a/react/features/toolbox/components/buttons/AbstractButton.js b/react/features/toolbox/components/buttons/AbstractButton.js new file mode 100644 index 000000000..c068b463d --- /dev/null +++ b/react/features/toolbox/components/buttons/AbstractButton.js @@ -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 extends Component { + 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 ( + + ); + } +} diff --git a/react/features/toolbox/components/buttons/AbstractHangupButton.js b/react/features/toolbox/components/buttons/AbstractHangupButton.js index 0f6d765ff..b55903bcd 100644 --- a/react/features/toolbox/components/buttons/AbstractHangupButton.js +++ b/react/features/toolbox/components/buttons/AbstractHangupButton.js @@ -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

extends AbstractButton { + 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; diff --git a/react/features/toolbox/components/buttons/AbstractVideoMuteButton.js b/react/features/toolbox/components/buttons/AbstractVideoMuteButton.js index 43f0230f3..f8ed25abe 100644 --- a/react/features/toolbox/components/buttons/AbstractVideoMuteButton.js +++ b/react/features/toolbox/components/buttons/AbstractVideoMuteButton.js @@ -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

extends AbstractButton { + 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; diff --git a/react/features/toolbox/components/buttons/AudioMuteButton.js b/react/features/toolbox/components/buttons/AudioMuteButton.js new file mode 100644 index 000000000..5d4456a5d --- /dev/null +++ b/react/features/toolbox/components/buttons/AudioMuteButton.js @@ -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 { + 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)); diff --git a/react/features/toolbox/components/buttons/AudioMuteButton.native.js b/react/features/toolbox/components/buttons/AudioMuteButton.native.js deleted file mode 100644 index b866ad82a..000000000 --- a/react/features/toolbox/components/buttons/AudioMuteButton.native.js +++ /dev/null @@ -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 ( - - ); - } - - _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); diff --git a/react/features/toolbox/components/buttons/AudioMuteButton.web.js b/react/features/toolbox/components/buttons/AudioMuteButton.web.js deleted file mode 100644 index 8c9e2451f..000000000 --- a/react/features/toolbox/components/buttons/AudioMuteButton.web.js +++ /dev/null @@ -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 ( - - ); - } - - _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)); diff --git a/react/features/toolbox/components/buttons/HangupButton.js b/react/features/toolbox/components/buttons/HangupButton.js new file mode 100644 index 000000000..da368d61f --- /dev/null +++ b/react/features/toolbox/components/buttons/HangupButton.js @@ -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 { + 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)); diff --git a/react/features/toolbox/components/buttons/HangupButton.native.js b/react/features/toolbox/components/buttons/HangupButton.native.js deleted file mode 100644 index d4d23f7b0..000000000 --- a/react/features/toolbox/components/buttons/HangupButton.native.js +++ /dev/null @@ -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 ( - - ); - } - - /** - * Dispatches an action for leaving the current conference. - * - * @private - * @returns {void} - */ - _doHangup() { - this.props.dispatch(appNavigate(undefined)); - } - - _onToolbarHangup: () => void; -} - -export default connect()(HangupButton); diff --git a/react/features/toolbox/components/buttons/HangupButton.web.js b/react/features/toolbox/components/buttons/HangupButton.web.js deleted file mode 100644 index 98db61802..000000000 --- a/react/features/toolbox/components/buttons/HangupButton.web.js +++ /dev/null @@ -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 ( - - ); - } - - _onToolbarHangup: () => void; - - /** - * Dispatches an action for leaving the current conference. - * - * @private - * @returns {void} - */ - _doHangup() { - this.props.dispatch(disconnect(true)); - } -} - -export default translate(connect()(HangupButton)); diff --git a/react/features/toolbox/components/buttons/VideoMuteButton.js b/react/features/toolbox/components/buttons/VideoMuteButton.js new file mode 100644 index 000000000..3b8e49408 --- /dev/null +++ b/react/features/toolbox/components/buttons/VideoMuteButton.js @@ -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 { + 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)); diff --git a/react/features/toolbox/components/buttons/VideoMuteButton.native.js b/react/features/toolbox/components/buttons/VideoMuteButton.native.js deleted file mode 100644 index 313e9ac02..000000000 --- a/react/features/toolbox/components/buttons/VideoMuteButton.native.js +++ /dev/null @@ -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 ( - - ); - } - - _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); diff --git a/react/features/toolbox/components/buttons/VideoMuteButton.web.js b/react/features/toolbox/components/buttons/VideoMuteButton.web.js deleted file mode 100644 index 3c0e542c5..000000000 --- a/react/features/toolbox/components/buttons/VideoMuteButton.web.js +++ /dev/null @@ -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 ( - - ); - } - - _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));