feat(alwaysontop): refactor toolbox

Use the new abstractions, which already take care of the rendering part.
This commit is contained in:
Saúl Ibarra Corretgé 2018-04-17 22:18:02 +02:00 committed by Lyubo Marinov
parent b634f6b200
commit 01cb4ac7c8
6 changed files with 391 additions and 261 deletions

View File

@ -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 (
<div id = 'alwaysOnTop'>
<ToolboxAlwaysOnTop
audioAvailable = { this.state.audioAvailable }
audioMuted = { this.state.audioMuted }
<Toolbar
className = { this.state.visible ? 'fadeIn' : 'fadeOut' }
onMouseOut = { this._onMouseOut }
onMouseOver = { this._onMouseOver }
videoAvailable = { this.state.videoAvailable }
videoMuted = { this.state.videoMuted } />
onMouseOver = { this._onMouseOver } />
{
this._renderVideoNotAvailableScreen()
}

View File

@ -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<Props, State> {
/**
* 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 });
}
}

View File

@ -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<Props, *> {
/**
* 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;
}
}

View File

@ -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<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
className = '',
onMouseOut,
onMouseOver
} = this.props;
return (
<div
className = { `always-on-top-toolbox ${className}` }
onMouseOut = { onMouseOut }
onMouseOver = { onMouseOver }>
<AudioMuteButton />
<HangupButton />
<VideoMuteButton />
</div>
);
}
}

View File

@ -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<Props> {
/**
* 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 (
<div
className = { `always-on-top-toolbox ${className}` }
onMouseOut = { onMouseOut }
onMouseOver = { onMouseOver }>
<ToolbarButton
accessibilityLabel = 'Audio mute'
iconName = { audioMuteIcon }
onClick = { this._onToolbarToggleAudio } />
<ToolbarButton
accessibilityLabel = 'Hangup'
iconName = 'icon-hangup'
onClick = { this._onToolbarHangup } />
<ToolbarButton
accessibilityLabel = 'Video mute'
iconName = { videoMuteIcon }
onClick = { this._onToolbarToggleVideo } />
</div>
);
}
_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');
}
}
}

View File

@ -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<Props, State> {
/**
* 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 });
}
}