From 01cb4ac7c8dd4e1a27f17a1e0c65004d6da433ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Tue, 17 Apr 2018 22:18:02 +0200 Subject: [PATCH] feat(alwaysontop): refactor toolbox Use the new abstractions, which already take care of the rendering part. --- react/features/always-on-top/AlwaysOnTop.js | 105 +----------- .../features/always-on-top/AudioMuteButton.js | 145 ++++++++++++++++ react/features/always-on-top/HangupButton.js | 38 +++++ react/features/always-on-top/Toolbar.js | 60 +++++++ .../always-on-top/ToolboxAlwaysOnTop.js | 159 ------------------ .../features/always-on-top/VideoMuteButton.js | 145 ++++++++++++++++ 6 files changed, 391 insertions(+), 261 deletions(-) create mode 100644 react/features/always-on-top/AudioMuteButton.js create mode 100644 react/features/always-on-top/HangupButton.js create mode 100644 react/features/always-on-top/Toolbar.js delete mode 100644 react/features/always-on-top/ToolboxAlwaysOnTop.js create mode 100644 react/features/always-on-top/VideoMuteButton.js diff --git a/react/features/always-on-top/AlwaysOnTop.js b/react/features/always-on-top/AlwaysOnTop.js index cd5d93981..49c5ef3f9 100644 --- a/react/features/always-on-top/AlwaysOnTop.js +++ b/react/features/always-on-top/AlwaysOnTop.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; -import ToolboxAlwaysOnTop from './ToolboxAlwaysOnTop'; +import Toolbar from './Toolbar'; const { api } = window.alwaysOnTop; @@ -15,13 +15,9 @@ const TOOLBAR_TIMEOUT = 4000; * The type of the React {@code Component} state of {@link FeedbackButton}. */ type State = { - audioAvailable: boolean, - audioMuted: boolean, avatarURL: string, displayName: string, isVideoDisplayed: boolean, - videoAvailable: boolean, - videoMuted: boolean, visible: boolean }; @@ -45,19 +41,12 @@ export default class AlwaysOnTop extends Component<*, State> { this.state = { visible: true, - audioMuted: false, - videoMuted: false, - audioAvailable: false, - videoAvailable: false, displayName: '', isVideoDisplayed: true, avatarURL: '' }; // Bind event handlers so they are only bound once per instance. - this._audioAvailabilityListener - = this._audioAvailabilityListener.bind(this); - this._audioMutedListener = this._audioMutedListener.bind(this); this._avatarChangedListener = this._avatarChangedListener.bind(this); this._largeVideoChangedListener = this._largeVideoChangedListener.bind(this); @@ -66,33 +55,6 @@ export default class AlwaysOnTop extends Component<*, State> { this._mouseMove = this._mouseMove.bind(this); this._onMouseOut = this._onMouseOut.bind(this); this._onMouseOver = this._onMouseOver.bind(this); - this._videoAvailabilityListener - = this._videoAvailabilityListener.bind(this); - this._videoMutedListener = this._videoMutedListener.bind(this); - } - - _audioAvailabilityListener: ({ available: boolean }) => void; - - /** - * Handles audio available api events. - * - * @param {{ available: boolean }} status - The new available status. - * @returns {void} - */ - _audioAvailabilityListener({ available }) { - this.setState({ audioAvailable: available }); - } - - _audioMutedListener: ({ muted: boolean }) => void; - - /** - * Handles audio muted api events. - * - * @param {{ muted: boolean }} status - The new muted status. - * @returns {void} - */ - _audioMutedListener({ muted }) { - this.setState({ audioMuted: muted }); } _avatarChangedListener: () => void; @@ -200,8 +162,6 @@ export default class AlwaysOnTop extends Component<*, State> { this._hovered = true; } - _videoAvailabilityListener: ({ available: boolean }) => void; - /** * Renders display name and avatar for the on stage participant. * @@ -234,28 +194,6 @@ export default class AlwaysOnTop extends Component<*, State> { ); } - /** - * Handles audio available api events. - * - * @param {{ available: boolean }} status - The new available status. - * @returns {void} - */ - _videoAvailabilityListener({ available }) { - this.setState({ videoAvailable: available }); - } - - _videoMutedListener: ({ muted: boolean }) => void; - - /** - * Handles video muted api events. - * - * @param {{ muted: boolean }} status - The new muted status. - * @returns {void} - */ - _videoMutedListener({ muted }) { - this.setState({ videoMuted: muted }); - } - /** * Sets mouse move listener and initial toolbar timeout. * @@ -263,37 +201,12 @@ export default class AlwaysOnTop extends Component<*, State> { * @returns {void} */ componentDidMount() { - api.on('audioMuteStatusChanged', this._audioMutedListener); - api.on('videoMuteStatusChanged', this._videoMutedListener); - api.on('audioAvailabilityChanged', this._audioAvailabilityListener); - api.on('videoAvailabilityChanged', this._videoAvailabilityListener); api.on('largeVideoChanged', this._largeVideoChangedListener); api.on('displayNameChange', this._displayNameChangedListener); api.on('avatarChanged', this._avatarChangedListener); this._largeVideoChangedListener(); - Promise.all([ - api.isAudioMuted(), - api.isVideoMuted(), - api.isAudioAvailable(), - api.isVideoAvailable() - ]) - .then(([ - audioMuted = false, - videoMuted = false, - audioAvailable = false, - videoAvailable = false - ]) => - this.setState({ - audioMuted, - videoMuted, - audioAvailable, - videoAvailable - }) - ) - .catch(console.error); - window.addEventListener('mousemove', this._mouseMove); this._hideToolbarAfterTimeout(); @@ -306,14 +219,6 @@ export default class AlwaysOnTop extends Component<*, State> { * @returns {void} */ componentWillUnmount() { - api.removeListener('audioMuteStatusChanged', - this._audioMutedListener); - api.removeListener('videoMuteStatusChanged', - this._videoMutedListener); - api.removeListener('audioAvailabilityChanged', - this._audioAvailabilityListener); - api.removeListener('videoAvailabilityChanged', - this._videoAvailabilityListener); api.removeListener('largeVideoChanged', this._largeVideoChangedListener); api.removeListener('displayNameChange', @@ -343,14 +248,10 @@ export default class AlwaysOnTop extends Component<*, State> { render() { return (
- + onMouseOver = { this._onMouseOver } /> { this._renderVideoNotAvailableScreen() } diff --git a/react/features/always-on-top/AudioMuteButton.js b/react/features/always-on-top/AudioMuteButton.js new file mode 100644 index 000000000..7235d019a --- /dev/null +++ b/react/features/always-on-top/AudioMuteButton.js @@ -0,0 +1,145 @@ +// @flow + +// XXX: AlwaysOnTop imports the button directly in order to avoid bringing in +// other components that use lib-jitsi-meet, which always on top does not +// import. +import AbstractAudioMuteButton + from '../toolbox/components/buttons/AbstractAudioMuteButton'; +import type { Props } from '../toolbox/components/buttons/AbstractButton'; + +const { api } = window.alwaysOnTop; + +type State = { + + /** + * Whether audio is available is not. + */ + audioAvailable: boolean, + + /** + * Whether audio is muted or not. + */ + audioMuted: boolean +}; + +/** + * Stateless hangup button for the Always-on-Top windows. + */ +export default class AudioMuteButton + extends AbstractAudioMuteButton { + + /** + * Initializes a new {@code AudioMuteButton} instance. + * + * @param {Props} props - The React {@code Component} props to initialize + * the new {@code AudioMuteButton} instance with. + */ + constructor(props: Props) { + super(props); + + this.state = { + audioAvailable: false, + audioMuted: true + }; + + // Bind event handlers so they are only bound once per instance. + this._audioAvailabilityListener + = this._audioAvailabilityListener.bind(this); + this._audioMutedListener = this._audioMutedListener.bind(this); + } + + /** + * Sets mouse move listener and initial toolbar timeout. + * + * @inheritdoc + * @returns {void} + */ + componentDidMount() { + api.on('audioAvailabilityChanged', this._audioAvailabilityListener); + api.on('audioMuteStatusChanged', this._audioMutedListener); + + Promise.all([ + api.isAudioAvailable(), + api.isAudioMuted() + ]) + .then(values => { + const [ audioAvailable, audioMuted ] = values; + + this.setState({ + audioAvailable, + audioMuted + }); + }) + .catch(console.error); + } + + /** + * Removes all listeners. + * + * @inheritdoc + * @returns {void} + */ + componentWillUnmount() { + api.removeListener('audioAvailabilityChanged', + this._audioAvailabilityListener); + api.removeListener('audioMuteStatusChanged', + this._audioMutedListener); + } + + /** + * Indicates whether this button is disabled or not. + * + * @override + * @private + * @returns {boolean} + */ + _isDisabled() { + return !this.state.audioAvailable; + } + + /** + * Indicates if audio is currently muted ot nor. + * + * @override + * @private + * @returns {boolean} + */ + _isAudioMuted() { + return this.state.audioMuted; + } + + /** + * Changes the muted state. + * + * @param {boolean} audioMuted - Whether audio should be muted or not. + * @private + * @returns {void} + */ + _setAudioMuted(audioMuted: boolean) { // eslint-disable-line no-unused-vars + this.state.audioAvailable && api.executeCommand('toggleAudio'); + } + + _audioAvailabilityListener: ({ available: boolean }) => void; + + /** + * Handles audio available api events. + * + * @param {{ available: boolean }} status - The new available status. + * @returns {void} + */ + _audioAvailabilityListener({ available }) { + this.setState({ audioAvailable: available }); + } + + _audioMutedListener: ({ muted: boolean }) => void; + + /** + * Handles audio muted api events. + * + * @param {{ muted: boolean }} status - The new muted status. + * @returns {void} + */ + _audioMutedListener({ muted }) { + this.setState({ audioMuted: muted }); + } +} diff --git a/react/features/always-on-top/HangupButton.js b/react/features/always-on-top/HangupButton.js new file mode 100644 index 000000000..5fe635034 --- /dev/null +++ b/react/features/always-on-top/HangupButton.js @@ -0,0 +1,38 @@ +// @flow + +// XXX: AlwaysOnTop imports the button directly in order to avoid bringing in +// other components that use lib-jitsi-meet, which always on top does not +// import. +import AbstractHangupButton + from '../toolbox/components/buttons/AbstractHangupButton'; +import type { Props } from '../toolbox/components/buttons/AbstractButton'; + +const { api } = window.alwaysOnTop; + +/** + * Stateless hangup button for the Always-on-Top windows. + */ +export default class HangupButton extends AbstractHangupButton { + /** + * Helper function to perform the actual hangup action. + * + * @override + * @private + * @returns {void} + */ + _doHangup() { + api.executeCommand('hangup'); + window.close(); + } + + /** + * Indicates whether this button is disabled or not. + * + * @override + * @private + * @returns {boolean} + */ + _isDisabled() { + return false; + } +} diff --git a/react/features/always-on-top/Toolbar.js b/react/features/always-on-top/Toolbar.js new file mode 100644 index 000000000..bb1613bce --- /dev/null +++ b/react/features/always-on-top/Toolbar.js @@ -0,0 +1,60 @@ +// @flow + +import React, { Component } from 'react'; + +import AudioMuteButton from './AudioMuteButton'; +import HangupButton from './HangupButton'; +import VideoMuteButton from './VideoMuteButton'; + +/** + * The type of the React {@code Component} props of {@link Toolbar}. + */ +type Props = { + + /** + * Additional CSS class names to add to the root of the toolbar. + */ + className: string, + + /** + * Callback invoked when no longer moused over the toolbar. + */ + onMouseOut: Function, + + /** + * Callback invoked when the mouse has moved over the toolbar. + */ + onMouseOver: Function +}; + +/** + * Represents the toolbar in the Always On Top window. + * + * @extends Component + */ +export default class Toolbar extends Component { + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + const { + className = '', + onMouseOut, + onMouseOver + } = this.props; + + return ( +
+ + + +
+ ); + } +} diff --git a/react/features/always-on-top/ToolboxAlwaysOnTop.js b/react/features/always-on-top/ToolboxAlwaysOnTop.js deleted file mode 100644 index d8e5e56cb..000000000 --- a/react/features/always-on-top/ToolboxAlwaysOnTop.js +++ /dev/null @@ -1,159 +0,0 @@ -// @flow - -import React, { Component } from 'react'; - -// FIXME: AlwaysOnTop imports the button directly in order to avoid bringing in -// other components that use lib-jitsi-meet, which always on top does not -// import. -import ToolbarButton from '../toolbox/components/ToolbarButton'; - -const { api } = window.alwaysOnTop; - -/** - * The type of the React {@code Component} props of {@link ToolboxAlwaysOnTop}. - */ -type Props = { - - /** - * Whether or not microphone access is available. - */ - audioAvailable: boolean, - - /** - * Whether or not the user is currently audio muted. - */ - audioMuted: boolean, - - /** - * Additional CSS class names to add to the root of the toolbar. - */ - className: string, - - /** - * Callback invoked when no longer moused over the toolbar. - */ - onMouseOut: Function, - - /** - * Callback invoked when the mouse has moved over the toolbar. - */ - onMouseOver: Function, - - /** - * Whether or not camera access is available. - */ - videoAvailable: boolean, - - /** - * Whether or not the user is currently video muted. - */ - videoMuted: boolean -}; - -/** - * Represents the toolbar in the Always On Top window. - * - * @extends Component - */ -export default class ToolboxAlwaysOnTop extends Component { - /** - * Initializes a new {@code ToolboxAlwaysOnTop} instance. - * - * @param {Props} props - The read-only properties with which the new - * instance is to be initialized. - */ - constructor(props: Props) { - super(props); - - // Bind event handlers so they are only bound once per instance. - this._onToolbarHangup = this._onToolbarHangup.bind(this); - this._onToolbarToggleAudio = this._onToolbarToggleAudio.bind(this); - this._onToolbarToggleVideo = this._onToolbarToggleVideo.bind(this); - } - - /** - * Implements React's {@link Component#render()}. - * - * @inheritdoc - * @returns {ReactElement} - */ - render() { - const { - audioAvailable, - audioMuted, - className = '', - onMouseOut, - onMouseOver, - videoAvailable, - videoMuted - } = this.props; - - const videoMuteIcon = `${videoMuted || !videoAvailable - ? 'icon-camera-disabled toggled' : 'icon-camera'} ${ - videoAvailable ? '' : 'disabled'}`; - const audioMuteIcon = `${audioMuted || !audioAvailable - ? 'icon-mic-disabled toggled' : 'icon-microphone'} ${ - audioAvailable ? '' : 'disabled'}`; - - return ( -
- - - -
- ); - } - - _onToolbarHangup: () => void; - - /** - * Ends the conference call and closes the always on top window. - * - * @private - * @returns {void} - */ - _onToolbarHangup() { - api.executeCommand('hangup'); - window.close(); - } - - _onToolbarToggleAudio: () => void; - - /** - * Toggles audio mute if audio is avaiable. - * - * @private - * @returns {void} - */ - _onToolbarToggleAudio() { - if (this.props.audioAvailable) { - api.executeCommand('toggleAudio'); - } - } - - _onToolbarToggleVideo: () => void; - - /** - * Toggles video mute if video is avaiable. - * - * @private - * @returns {void} - */ - _onToolbarToggleVideo() { - if (this.props.videoAvailable) { - api.executeCommand('toggleVideo'); - } - } -} diff --git a/react/features/always-on-top/VideoMuteButton.js b/react/features/always-on-top/VideoMuteButton.js new file mode 100644 index 000000000..b89cab342 --- /dev/null +++ b/react/features/always-on-top/VideoMuteButton.js @@ -0,0 +1,145 @@ +// @flow + +// XXX: AlwaysOnTop imports the button directly in order to avoid bringing in +// other components that use lib-jitsi-meet, which always on top does not +// import. +import AbstractVideoMuteButton + from '../toolbox/components/buttons/AbstractVideoMuteButton'; +import type { Props } from '../toolbox/components/buttons/AbstractButton'; + +const { api } = window.alwaysOnTop; + +type State = { + + /** + * Whether video is available is not. + */ + videoAvailable: boolean, + + /** + * Whether video is muted or not. + */ + videoMuted: boolean +}; + +/** + * Stateless hangup button for the Always-on-Top windows. + */ +export default class VideoMuteButton + extends AbstractVideoMuteButton { + + /** + * Initializes a new {@code VideoMuteButton} instance. + * + * @param {Props} props - The React {@code Component} props to initialize + * the new {@code VideoMuteButton} instance with. + */ + constructor(props: Props) { + super(props); + + this.state = { + videoAvailable: false, + videoMuted: true + }; + + // Bind event handlers so they are only bound once per instance. + this._videoAvailabilityListener + = this._videoAvailabilityListener.bind(this); + this._videoMutedListener = this._videoMutedListener.bind(this); + } + + /** + * Sets mouse move listener and initial toolbar timeout. + * + * @inheritdoc + * @returns {void} + */ + componentDidMount() { + api.on('videoAvailabilityChanged', this._videoAvailabilityListener); + api.on('videoMuteStatusChanged', this._videoMutedListener); + + Promise.all([ + api.isVideoAvailable(), + api.isVideoMuted() + ]) + .then(values => { + const [ videoAvailable, videoMuted ] = values; + + this.setState({ + videoAvailable, + videoMuted + }); + }) + .catch(console.error); + } + + /** + * Removes all listeners. + * + * @inheritdoc + * @returns {void} + */ + componentWillUnmount() { + api.removeListener('videoAvailabilityChanged', + this._videoAvailabilityListener); + api.removeListener('videoMuteStatusChanged', + this._videoMutedListener); + } + + /** + * Indicates whether this button is disabled or not. + * + * @override + * @private + * @returns {boolean} + */ + _isDisabled() { + return !this.state.videoAvailable; + } + + /** + * Indicates if video is currently muted ot nor. + * + * @override + * @private + * @returns {boolean} + */ + _isVideoMuted() { + return this.state.videoMuted; + } + + /** + * Changes the muted state. + * + * @param {boolean} videoMuted - Whether video should be muted or not. + * @private + * @returns {void} + */ + _setVideoMuted(videoMuted: boolean) { // eslint-disable-line no-unused-vars + this.state.videoAvailable && api.executeCommand('toggleVideo'); + } + + _videoAvailabilityListener: ({ available: boolean }) => void; + + /** + * Handles video available api events. + * + * @param {{ available: boolean }} status - The new available status. + * @returns {void} + */ + _videoAvailabilityListener({ available }) { + this.setState({ videoAvailable: available }); + } + + _videoMutedListener: ({ muted: boolean }) => void; + + /** + * Handles video muted api events. + * + * @param {{ muted: boolean }} status - The new muted status. + * @returns {void} + */ + _videoMutedListener({ muted }) { + this.setState({ videoMuted: muted }); + } +}