feat(indicators): move the "top toolbar" indicators to react (#1699)

* feat(indicators): move the "top toolbar" indicators to react

* wrap baseindicator
This commit is contained in:
virtuacoplenny 2017-07-10 15:29:44 -07:00 committed by hristoterezov
parent fcda36a8e0
commit 0481e4cf00
12 changed files with 257 additions and 162 deletions

View File

@ -58,7 +58,6 @@
padding: $toolbarPadding;
padding-bottom: 0;
.connection-indicator-container,
.connection-indicator,
span.indicator {
margin-right: em(5, 8);
@ -302,7 +301,7 @@
margin: 0px 0px 0px 5px;
}
#raisehandindicator {
.raisehandindicator {
background: $raiseHandBg;
}

View File

@ -446,71 +446,16 @@ const IndicatorFontSizes = {
}
},
/**
* Gets an "indicator" span for a video thumbnail.
* If element doesn't exist then creates it and appends
* video span container.
*
* @param {object} opts
* @param opts.indicatorId {String} - identificator of indicator
* @param opts.videoSpanId {String} - identificator of video span
* @param opts.content {String} HTML content of indicator
* @param opts.tooltip {String} - tooltip key for translation
*
* @returns {HTMLSpanElement} indicatorSpan
*/
getVideoThumbnailIndicatorSpan(opts = {}) {
let indicatorId = opts.indicatorId;
let videoSpanId = opts.videoSpanId;
let indicators = $(`#${videoSpanId} [id="${indicatorId}"]`);
let indicatorSpan;
if (indicators.length <= 0) {
indicatorSpan = document.createElement('span');
indicatorSpan.className = 'indicator';
indicatorSpan.id = indicatorId;
if(opts.content) {
indicatorSpan.innerHTML = opts.content;
}
if (opts.tooltip) {
this.setTooltip(indicatorSpan, opts.tooltip, "top");
APP.translation.translateElement($(indicatorSpan));
}
this._resizeIndicator(indicatorSpan);
document.getElementById(videoSpanId)
.querySelector('.videocontainer__toptoolbar')
.appendChild(indicatorSpan);
} else {
indicatorSpan = indicators[0];
}
return indicatorSpan;
},
/**
* Resizing indicator element passing via argument
* according to the current thumbnail size
* @param {HTMLElement} indicator - indicator element
* @private
*/
_resizeIndicator(indicator) {
let height = $('#localVideoContainer').height();
let fontSize = this.getIndicatorFontSize(height);
$(indicator).css('font-size', fontSize);
},
/**
* Returns font size for indicators according to current
* height of thumbnail
* @param {Number} - height - current height of thumbnail
* @param {Number} [thumbnailHeight] - current height of thumbnail
* @returns {Number} - font size for current height
*/
getIndicatorFontSize(height) {
getIndicatorFontSize(thumbnailHeight) {
const height = typeof thumbnailHeight === 'undefined'
? $('#localVideoContainer').height() : thumbnailHeight;
const { SMALL, MEDIUM } = ThumbnailSizes;
let fontSize = IndicatorFontSizes.NORMAL;

View File

@ -529,6 +529,8 @@ RemoteVideo.prototype.remove = function () {
this.removeAvatar();
this._unmountIndicators();
// Make sure that the large video is updated if are removing its
// corresponding small video.
this.VideoLayout.updateAfterThumbRemoved(this.id);
@ -688,10 +690,6 @@ RemoteVideo.createContainer = function (spanId) {
indicatorBar.className = "videocontainer__toptoolbar";
container.appendChild(indicatorBar);
const connectionIndicatorContainer = document.createElement('span');
connectionIndicatorContainer.className = 'connection-indicator-container';
indicatorBar.appendChild(connectionIndicatorContainer);
let toolbar = document.createElement('div');
toolbar.className = "videocontainer__toolbar";
container.appendChild(toolbar);

View File

@ -18,7 +18,9 @@ import {
import { DisplayName } from '../../../react/features/display-name';
import {
AudioMutedIndicator,
DominantSpeakerIndicator,
ModeratorIndicator,
RaisedHandIndicator,
VideoMutedIndicator
} from '../../../react/features/filmstrip';
/* eslint-enable no-unused-vars */
@ -99,6 +101,30 @@ function SmallVideo(VideoLayout) {
*/
this._popoverIsHovered = false;
/**
* Whether or not the connection indicator should be displayed.
*
* @private
* @type {boolean}
*/
this._showConnectionIndicator = true;
/**
* Whether or not the dominant speaker indicator should be displayed.
*
* @private
* @type {boolean}
*/
this._showDominantSpeaker = false;
/**
* Whether or not the raised hand indicator should be displayed.
*
* @private
* @type {boolean}
*/
this._showRaisedHand = false;
// Bind event handlers so they are only bound once for every instance.
this._onPopoverHover = this._onPopoverHover.bind(this);
this.updateView = this.updateView.bind(this);
@ -252,12 +278,9 @@ SmallVideo.prototype.updateConnectionStats = function (percent, object) {
* @returns {void}
*/
SmallVideo.prototype.removeConnectionIndicator = function () {
const connectionIndicatorContainer
= this.container.querySelector('.connection-indicator-container');
this._showConnectionIndicator = false;
if (connectionIndicatorContainer) {
ReactDOM.unmountComponentAtNode(connectionIndicatorContainer);
}
this.updateIndicators();
};
/**
@ -643,17 +666,9 @@ SmallVideo.prototype.showDominantSpeakerIndicator = function (show) {
return;
}
let indicatorSpanId = "dominantspeakerindicator";
let content = `<i id="indicatoricon"
class="indicatoricon fa fa-bullhorn"></i>`;
let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({
videoSpanId: this.videoSpanId,
indicatorId: indicatorSpanId,
content,
tooltip: 'speaker'
});
this._showDominantSpeaker = show;
UIUtil.setVisible(indicatorSpan, show);
this.updateIndicators();
};
/**
@ -667,17 +682,9 @@ SmallVideo.prototype.showRaisedHandIndicator = function (show) {
return;
}
let indicatorSpanId = "raisehandindicator";
let content = `<i id="indicatoricon"
class="icon-raised-hand indicatoricon"></i>`;
let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({
indicatorId: indicatorSpanId,
videoSpanId: this.videoSpanId,
content,
tooltip: 'raisedHand'
});
this._showRaisedHand = show;
UIUtil.setVisible(indicatorSpan, show);
this.updateIndicators();
};
/**
@ -746,21 +753,60 @@ SmallVideo.prototype.updateConnectionIndicator = function (newStats = {}) {
this._cachedConnectionStats
= Object.assign({}, this._cachedConnectionStats, newStats);
const connectionIndicatorContainer
= this.container.querySelector('.connection-indicator-container');
this.updateIndicators();
};
/**
* Updates the React element responsible for showing connection status, dominant
* speaker, and raised hand icons. Uses instance variables to get the necessary
* state to display. Will create the React element if not already created.
*
* @private
* @returns {void}
*/
SmallVideo.prototype.updateIndicators = function () {
const indicatorToolbar
= this.container.querySelector('.videocontainer__toptoolbar');
const iconSize = UIUtil.getIndicatorFontSize();
/* jshint ignore:start */
ReactDOM.render(
<ConnectionIndicator
isLocalVideo = { this.isLocal }
onHover = { this._onPopoverHover }
showMoreLink = { this.isLocal }
stats = { this._cachedConnectionStats } />,
connectionIndicatorContainer
<div>
{ this._showConnectionIndicator
? <ConnectionIndicator
iconSize = { iconSize }
isLocalVideo = { this.isLocal }
onHover = { this._onPopoverHover }
showMoreLink = { this.isLocal }
stats = { this._cachedConnectionStats } />
: null }
{ this._showRaisedHand
? <RaisedHandIndicator iconSize = { iconSize } /> : null }
{ this._showDominantSpeaker
? <DominantSpeakerIndicator iconSize = { iconSize } /> : null }
</div>,
indicatorToolbar
);
/* jshint ignore:end */
};
/**
* Removes the React element responsible for showing connection status, dominant
* speaker, and raised hand icons.
*
* @private
* @returns {void}
*/
SmallVideo.prototype._unmountIndicators = function () {
const indicatorToolbar
= this.container.querySelector('.videocontainer__toptoolbar');
if (indicatorToolbar) {
ReactDOM.unmountComponentAtNode(indicatorToolbar);
}
};
/**
* Updates the current state of the connection indicator popover being hovered.
* If hovered, display the small video as if it is hovered.

View File

@ -44,11 +44,7 @@ export default class Filmstrip extends Component {
id = 'localAudio'
muted = { true } />
<div className = 'videocontainer__toolbar' />
<div className = 'videocontainer__toptoolbar'>
<span
className
= 'connection-indicator-container' />
</div>
<div className = 'videocontainer__toptoolbar' />
<div className = 'videocontainer__hoverOverlay' />
<div className = 'displayNameContainer' />
<div className = 'avatar-container' />

View File

@ -1,23 +1,26 @@
import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator';
/**
* React {@code Component} for showing an audio muted icon with a tooltip.
*
* @extends BaseIndicator
* @extends Component
*/
class AudioMutedIndicator extends BaseIndicator {
class AudioMutedIndicator extends Component {
/**
* Initializes a new AudioMutedIcon instance.
* Implements React's {@link Component#render()}.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
* @inheritdoc
* @returns {ReactElement}
*/
constructor(props) {
super(props);
this._classNames = 'audioMuted toolbar-icon';
this._iconClass = 'icon-mic-disabled';
this._tooltipKey = 'videothumbnail.mute';
render() {
return (
<BaseIndicator
className = 'audioMuted toolbar-icon'
iconClassName = 'icon-mic-disabled'
tooltipKey = 'videothumbnail.mute' />
);
}
}

View File

@ -8,6 +8,41 @@ import UIUtil from '../../../../../modules/UI/util/UIUtil';
* @extends Component
*/
class BaseIndicator extends Component {
static defaultProps = {
className: '',
iconClassName: '',
iconSize: 'auto',
id: ''
};
static propTypes = {
/**
* The CSS class names to set on the root element of the component.
*/
className: React.PropTypes.string,
/**
* The CSS classnames to set on the icon element of the component.
*/
iconClassName: React.PropTypes.string,
/**
* The front size for the icon.
*/
iconSize: React.PropTypes.string,
/**
* The ID attribue to set on the root element of the component.
*/
id: React.PropTypes.string,
/**
* The translation key to use for displaying a tooltip when hovering
* over the component.
*/
tooltipKey: React.PropTypes.string
};
/**
* Initializes a new {@code BaseIndicator} instance.
*
@ -17,20 +52,6 @@ class BaseIndicator extends Component {
constructor(props) {
super(props);
/**
* The CSS classes to apply to the root HTML element of the component.
*
* @type {string}
*/
this._classNames = '';
/**
* The CSS class which will display an icon.
*
* @type {string}
*/
this._iconClass = '';
/**
* An internal reference to the HTML element at the top of the
* component's DOM hierarchy. The reference is needed for attaching a
@ -40,13 +61,6 @@ class BaseIndicator extends Component {
*/
this._rootElement = null;
/**
* The translation key for the text to display in the tooltip.
*
* @type {string}
*/
this._tooltipKey = '';
// Bind event handler so it is only bound once for every instance.
this._setRootElementRef = this._setRootElementRef.bind(this);
}
@ -69,10 +83,13 @@ class BaseIndicator extends Component {
*/
render() {
return (
<span className = { this._classNames }>
<span
className = { this.props.className }
id = { this.props.id }
ref = { this._setRootElementRef }>
<i
className = { this._iconClass }
ref = { this._setRootElementRef } />
className = { this.props.iconClassName }
style = {{ fontSize: this.props.iconSize }} />
</span>
);
}
@ -101,7 +118,7 @@ class BaseIndicator extends Component {
// becomes available for tooltips.
UIUtil.setTooltip(
this._rootElement,
this._tooltipKey,
this.props.tooltipKey,
'top'
);
}

View File

@ -0,0 +1,43 @@
import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator';
/**
* Thumbnail badge showing that the participant is the dominant speaker in
* the conference.
*
* @extends Component
*/
class DominantSpeakerIndicator extends Component {
/**
* {@code DominantSpeakerIndicator} component's property types.
*
* @static
*/
static propTypes = {
/**
* The font-size for the icon.
*
* @type {number}
*/
iconSize: React.PropTypes.number
};
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
return (
<BaseIndicator
className = 'indicator show-inline'
iconClassName = 'indicatoricon fa fa-bullhorn'
iconSize = { `${this.props.iconSize}px` }
id = 'dominantspeakerindicator'
tooltipKey = 'speaker' />
);
}
}
export default DominantSpeakerIndicator;

View File

@ -1,23 +1,26 @@
import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator';
/**
* React {@code Component} for showing a moderator icon with a tooltip.
*
* @extends BaseIndicator
* @extends Component
*/
class ModeratorIndicator extends BaseIndicator {
class ModeratorIndicator extends Component {
/**
* Initializes a new ModeratorIndicator instance.
* Implements React's {@link Component#render()}.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
* @inheritdoc
* @returns {ReactElement}
*/
constructor(props) {
super(props);
this._classNames = 'focusindicator toolbar-icon right';
this._iconClass = 'icon-star';
this._tooltipKey = 'videothumbnail.moderator';
render() {
return (
<BaseIndicator
className = 'focusindicator toolbar-icon right'
iconClassName = 'icon-star'
tooltipKey = 'videothumbnail.moderator' />
);
}
}

View File

@ -0,0 +1,41 @@
import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator';
/**
* Thumbnail badge showing that the participant would like to speak.
*
* @extends Component
*/
class RaisedHandIndicator extends Component {
/**
* {@code RaisedHandIndicator} component's property types.
*
* @static
*/
static propTypes = {
/**
* The font-size for the icon.
*
* @type {number}
*/
iconSize: React.PropTypes.number
};
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
return (
<BaseIndicator
className = 'raisehandindicator indicator show-inline'
iconClassName = 'icon-raised-hand indicatoricon'
iconSize = { `${this.props.iconSize}px` }
tooltipKey = 'raisedHand' />
);
}
}
export default RaisedHandIndicator;

View File

@ -1,23 +1,24 @@
import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator';
/**
* React {@code Component} for showing a video muted icon with a tooltip.
*
* @extends BaseIndicator
* @extends Component
*/
class VideoMutedIndicator extends BaseIndicator {
class VideoMutedIndicator extends Component {
/**
* Initializes a new VideoMutedIndicator instance.
* Implements React's {@link Component#render()}.
*
* @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized.
* @inheritdoc
*/
constructor(props) {
super(props);
this._classNames = 'videoMuted toolbar-icon';
this._iconClass = 'icon-camera-disabled';
this._tooltipKey = 'videothumbnail.videomute';
render() {
return (
<BaseIndicator
className = 'videoMuted toolbar-icon'
iconClassName = 'icon-camera-disabled'
tooltipKey = 'videothumbnail.videomute' />
);
}
}

View File

@ -1,3 +1,6 @@
export { default as AudioMutedIndicator } from './AudioMutedIndicator';
export { default as DominantSpeakerIndicator }
from './DominantSpeakerIndicator';
export { default as ModeratorIndicator } from './ModeratorIndicator';
export { default as RaisedHandIndicator } from './RaisedHandIndicator';
export { default as VideoMutedIndicator } from './VideoMutedIndicator';