feat(video-label): Add dropdown for toggling audio only

Add a menu that displays when hovering over VideoStatusLabel. The menu's
display is controlled by CSS. As the existing AudioOnlyLabel no longer needs
needs its own tooltip, it has been removed and label display logic has been
moved into VideoStatusLabel.
This commit is contained in:
Leonard Kim 2017-05-15 11:55:08 -07:00 committed by hristoterezov
parent 0f0ff6788c
commit 80989147ad
6 changed files with 173 additions and 139 deletions

View File

@ -543,4 +543,58 @@
.moveToCorner + .moveToCorner {
right: 80px;
}
}
.video-state-indicator-menu {
display: none;
padding: 10px;
position: absolute;
right: -10px;
top: 20px;
.video-state-indicator-menu-options {
background: $popoverBg;
border-radius: 3px;
color: $popoverFontColor;
margin-top: 20px;
padding: 5px 0;
position: relative;
div {
cursor: pointer;
padding: 10px;
padding-right: 30px;
text-align: left;
white-space: nowrap;
&.active {
background: $toolbarToggleBackground;
}
&:hover:not(.active) {
background: $popupMenuSelectedItemBackground;
}
i {
margin-right: 5px;
vertical-align: middle;
}
}
}
.video-state-indicator-menu-options::after {
content: " ";
border-color: transparent transparent $popoverBg transparent;
border-style: solid;
border-width: 5px;
position: absolute;
right: 15px;
top: -10px;
}
}
.video-state-indicator:hover,
.video-state-indicator *:hover {
.video-state-indicator-menu {
display: block;
}
}

View File

@ -16,8 +16,7 @@
"callingName": "__name__",
"audioOnly": {
"audioOnly": "Audio only",
"featureToggleDisabled": "Toggling of __feature__ is disabled while in audio only mode",
"howToDisable": "Audio only mode is currently enabled. Click the audio only button in the toolbar to disable the feature."
"featureToggleDisabled": "Toggling of __feature__ is disabled while in audio only mode"
},
"userMedia": {
"react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
@ -447,5 +446,11 @@
"numbersDisabled": "Dialing in has been disabled",
"showPassword": "Show password",
"unlocked": "This call is unlocked. Any new caller with the link may join the call."
},
"videoStatus": {
"hd": "HD",
"hdVideo": "HD video",
"sd": "SD",
"sdVideo": "SD video"
}
}

View File

@ -242,10 +242,7 @@ function _lockStateChanged(state, action) {
* reduction of the specified action.
*/
function _setAudioOnly(state, action) {
return assign(state, {
audioOnly: action.audioOnly,
isLargeVideoHD: action.audioOnly ? false : state.isLargeVideoHD
});
return set(state, 'audioOnly', action.audioOnly);
}
/**

View File

@ -1,104 +0,0 @@
import React, { Component } from 'react';
import UIUtil from '../../../../modules/UI/util/UIUtil';
import { translate } from '../../base/i18n';
/**
* React {@code Component} for displaying a message to indicate audio only mode
* is active and for triggering a tooltip to provide more information about
* audio only mode.
*
* @extends Component
*/
export class AudioOnlyLabel extends Component {
/**
* {@code AudioOnlyLabel}'s property types.
*
* @static
*/
static propTypes = {
/**
* Invoked to obtain translated strings.
*/
t: React.PropTypes.func
}
/**
* Initializes a new {@code AudioOnlyLabel} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
super(props);
/**
* The internal reference to the DOM/HTML element at the top of the
* React {@code Component}'s DOM/HTML hierarchy. It is necessary for
* setting a tooltip to display when hovering over the component.
*
* @private
* @type {HTMLDivElement}
*/
this._rootElement = null;
// Bind event handlers so they are only bound once for every instance.
this._setRootElement = this._setRootElement.bind(this);
}
/**
* Sets a tooltip on the component to display on hover.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
this._setTooltip();
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<div
className = 'audio-only-label moveToCorner'
ref = { this._setRootElement }>
<i className = 'icon-visibility-off' />
</div>
);
}
/**
* Sets the instance variable for the component's root element so it can be
* accessed directly.
*
* @param {HTMLDivElement} element - The topmost DOM element of the
* component's DOM/HTML hierarchy.
* @private
* @returns {void}
*/
_setRootElement(element) {
this._rootElement = element;
}
/**
* Sets the tooltip on the component's root element.
*
* @private
* @returns {void}
*/
_setTooltip() {
UIUtil.setTooltip(
this._rootElement,
'audioOnly.howToDisable',
'left'
);
}
}
export default translate(AudioOnlyLabel);

View File

@ -1,16 +0,0 @@
import React from 'react';
/**
* A functional React {@code Component} for showing an HD status label.
*
* @returns {ReactElement}
*/
export default function HDVideoLabel() {
return (
<span
className = 'video-state-indicator moveToCorner'
id = 'videoResolutionLabel'>
HD
</span>
);
}

View File

@ -1,8 +1,8 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import AudioOnlyLabel from './AudioOnlyLabel';
import HDVideoLabel from './HDVideoLabel';
import { toggleAudioOnly } from '../../base/conference';
import { translate } from '../../base/i18n';
/**
* React {@code Component} responsible for displaying a label that indicates
@ -23,26 +23,118 @@ export class VideoStatusLabel extends Component {
*/
_audioOnly: React.PropTypes.bool,
/**
* Whether or not a connection to a conference has been established.
*/
_conferenceStarted: React.PropTypes.bool,
/**
* Whether or not a high-definition large video is displayed.
*/
_largeVideoHD: React.PropTypes.bool
_largeVideoHD: React.PropTypes.bool,
/**
* Invoked to request toggling of audio only mode.
*/
dispatch: React.PropTypes.func,
/**
* Invoked to obtain translated strings.
*/
t: React.PropTypes.func
}
/**
* Initializes a new {@code VideoStatusLabel} instance.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
*/
constructor(props) {
super(props);
// Bind event handler so it is only bound once for every instance.
this._toggleAudioOnly = this._toggleAudioOnly.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
* @returns {ReactElement}
*/
render() {
if (this.props._audioOnly) {
return <AudioOnlyLabel />;
} else if (this.props._largeVideoHD) {
return <HDVideoLabel />;
const { _audioOnly, _conferenceStarted, _largeVideoHD, t } = this.props;
// FIXME These truthy checks should not be necessary. The
// _conferenceStarted check is used to be defensive against toggling
// audio only mode while there is no conference and hides the need for
// error handling around audio only mode toggling. The _largeVideoHD
// check is used to prevent the label from displaying while the video
// resolution status is unknown but ties this component to the
// LargeVideoManager.
if (!_conferenceStarted || _largeVideoHD === undefined) {
return null;
}
return null;
let displayedLabel;
if (_audioOnly) {
displayedLabel = <i className = 'icon-visibility-off' />;
} else {
displayedLabel = _largeVideoHD
? t('videoStatus.hd') : t('videoStatus.sd');
}
return (
<div
className = 'video-state-indicator moveToCorner'
id = 'videoResolutionLabel' >
{ displayedLabel }
{ this._renderVideonMenu() }
</div>
);
}
/**
* Renders a dropdown menu for changing video modes.
*
* @private
* @returns {ReactElement}
*/
_renderVideonMenu() {
const { _audioOnly, t } = this.props;
const audioOnlyAttributes = _audioOnly ? { className: 'active' }
: { onClick: this._toggleAudioOnly };
const videoAttributes = _audioOnly ? { onClick: this._toggleAudioOnly }
: { className: 'active' };
return (
<div className = 'video-state-indicator-menu'>
<div className = 'video-state-indicator-menu-options'>
<div { ...audioOnlyAttributes }>
<i className = 'icon-visibility' />
{ t('audioOnly.audioOnly') }
</div>
<div { ...videoAttributes }>
<i className = 'icon-camera' />
{ this.props._largeVideoHD
? t('videoStatus.hdVideo')
: t('videoStatus.sdVideo') }
</div>
</div>
</div>
);
}
/**
* Dispatches an action to toggle the state of audio only mode.
*
* @private
* @returns {void}
*/
_toggleAudioOnly() {
this.props.dispatch(toggleAudioOnly());
}
}
@ -54,16 +146,22 @@ export class VideoStatusLabel extends Component {
* @private
* @returns {{
* _audioOnly: boolean,
* _largeVideoHD: boolean
* _conferenceStarted: boolean,
* _largeVideoHD: (boolean|undefined)
* }}
*/
function _mapStateToProps(state) {
const { audioOnly, isLargeVideoHD } = state['features/base/conference'];
const {
audioOnly,
conference,
isLargeVideoHD
} = state['features/base/conference'];
return {
_audioOnly: audioOnly,
_conferenceStarted: Boolean(conference),
_largeVideoHD: isLargeVideoHD
};
}
export default connect(_mapStateToProps)(VideoStatusLabel);
export default translate(connect(_mapStateToProps)(VideoStatusLabel));