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: $toolbarPadding;
padding-bottom: 0; padding-bottom: 0;
.connection-indicator-container,
.connection-indicator, .connection-indicator,
span.indicator { span.indicator {
margin-right: em(5, 8); margin-right: em(5, 8);
@ -302,7 +301,7 @@
margin: 0px 0px 0px 5px; margin: 0px 0px 0px 5px;
} }
#raisehandindicator { .raisehandindicator {
background: $raiseHandBg; 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 * Returns font size for indicators according to current
* height of thumbnail * height of thumbnail
* @param {Number} - height - current height of thumbnail * @param {Number} [thumbnailHeight] - current height of thumbnail
* @returns {Number} - font size for current height * @returns {Number} - font size for current height
*/ */
getIndicatorFontSize(height) { getIndicatorFontSize(thumbnailHeight) {
const height = typeof thumbnailHeight === 'undefined'
? $('#localVideoContainer').height() : thumbnailHeight;
const { SMALL, MEDIUM } = ThumbnailSizes; const { SMALL, MEDIUM } = ThumbnailSizes;
let fontSize = IndicatorFontSizes.NORMAL; let fontSize = IndicatorFontSizes.NORMAL;

View File

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

View File

@ -18,7 +18,9 @@ import {
import { DisplayName } from '../../../react/features/display-name'; import { DisplayName } from '../../../react/features/display-name';
import { import {
AudioMutedIndicator, AudioMutedIndicator,
DominantSpeakerIndicator,
ModeratorIndicator, ModeratorIndicator,
RaisedHandIndicator,
VideoMutedIndicator VideoMutedIndicator
} from '../../../react/features/filmstrip'; } from '../../../react/features/filmstrip';
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
@ -99,6 +101,30 @@ function SmallVideo(VideoLayout) {
*/ */
this._popoverIsHovered = false; 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. // Bind event handlers so they are only bound once for every instance.
this._onPopoverHover = this._onPopoverHover.bind(this); this._onPopoverHover = this._onPopoverHover.bind(this);
this.updateView = this.updateView.bind(this); this.updateView = this.updateView.bind(this);
@ -252,12 +278,9 @@ SmallVideo.prototype.updateConnectionStats = function (percent, object) {
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.removeConnectionIndicator = function () { SmallVideo.prototype.removeConnectionIndicator = function () {
const connectionIndicatorContainer this._showConnectionIndicator = false;
= this.container.querySelector('.connection-indicator-container');
if (connectionIndicatorContainer) { this.updateIndicators();
ReactDOM.unmountComponentAtNode(connectionIndicatorContainer);
}
}; };
/** /**
@ -643,17 +666,9 @@ SmallVideo.prototype.showDominantSpeakerIndicator = function (show) {
return; return;
} }
let indicatorSpanId = "dominantspeakerindicator"; this._showDominantSpeaker = show;
let content = `<i id="indicatoricon"
class="indicatoricon fa fa-bullhorn"></i>`;
let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({
videoSpanId: this.videoSpanId,
indicatorId: indicatorSpanId,
content,
tooltip: 'speaker'
});
UIUtil.setVisible(indicatorSpan, show); this.updateIndicators();
}; };
/** /**
@ -667,17 +682,9 @@ SmallVideo.prototype.showRaisedHandIndicator = function (show) {
return; return;
} }
let indicatorSpanId = "raisehandindicator"; this._showRaisedHand = show;
let content = `<i id="indicatoricon"
class="icon-raised-hand indicatoricon"></i>`;
let indicatorSpan = UIUtil.getVideoThumbnailIndicatorSpan({
indicatorId: indicatorSpanId,
videoSpanId: this.videoSpanId,
content,
tooltip: 'raisedHand'
});
UIUtil.setVisible(indicatorSpan, show); this.updateIndicators();
}; };
/** /**
@ -746,21 +753,60 @@ SmallVideo.prototype.updateConnectionIndicator = function (newStats = {}) {
this._cachedConnectionStats this._cachedConnectionStats
= Object.assign({}, this._cachedConnectionStats, newStats); = Object.assign({}, this._cachedConnectionStats, newStats);
const connectionIndicatorContainer this.updateIndicators();
= this.container.querySelector('.connection-indicator-container'); };
/**
* 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 */ /* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<ConnectionIndicator <div>
{ this._showConnectionIndicator
? <ConnectionIndicator
iconSize = { iconSize }
isLocalVideo = { this.isLocal } isLocalVideo = { this.isLocal }
onHover = { this._onPopoverHover } onHover = { this._onPopoverHover }
showMoreLink = { this.isLocal } showMoreLink = { this.isLocal }
stats = { this._cachedConnectionStats } />, stats = { this._cachedConnectionStats } />
connectionIndicatorContainer : null }
{ this._showRaisedHand
? <RaisedHandIndicator iconSize = { iconSize } /> : null }
{ this._showDominantSpeaker
? <DominantSpeakerIndicator iconSize = { iconSize } /> : null }
</div>,
indicatorToolbar
); );
/* jshint ignore:end */ /* 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. * Updates the current state of the connection indicator popover being hovered.
* If hovered, display the small video as if it is 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' id = 'localAudio'
muted = { true } /> muted = { true } />
<div className = 'videocontainer__toolbar' /> <div className = 'videocontainer__toolbar' />
<div className = 'videocontainer__toptoolbar'> <div className = 'videocontainer__toptoolbar' />
<span
className
= 'connection-indicator-container' />
</div>
<div className = 'videocontainer__hoverOverlay' /> <div className = 'videocontainer__hoverOverlay' />
<div className = 'displayNameContainer' /> <div className = 'displayNameContainer' />
<div className = 'avatar-container' /> <div className = 'avatar-container' />

View File

@ -1,23 +1,26 @@
import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator'; import BaseIndicator from './BaseIndicator';
/** /**
* React {@code Component} for showing an audio muted icon with a tooltip. * 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 * @inheritdoc
* the new instance is to be initialized. * @returns {ReactElement}
*/ */
constructor(props) { render() {
super(props); return (
<BaseIndicator
this._classNames = 'audioMuted toolbar-icon'; className = 'audioMuted toolbar-icon'
this._iconClass = 'icon-mic-disabled'; iconClassName = 'icon-mic-disabled'
this._tooltipKey = 'videothumbnail.mute'; tooltipKey = 'videothumbnail.mute' />
);
} }
} }

View File

@ -8,6 +8,41 @@ import UIUtil from '../../../../../modules/UI/util/UIUtil';
* @extends Component * @extends Component
*/ */
class BaseIndicator 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. * Initializes a new {@code BaseIndicator} instance.
* *
@ -17,20 +52,6 @@ class BaseIndicator extends Component {
constructor(props) { constructor(props) {
super(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 * An internal reference to the HTML element at the top of the
* component's DOM hierarchy. The reference is needed for attaching a * component's DOM hierarchy. The reference is needed for attaching a
@ -40,13 +61,6 @@ class BaseIndicator extends Component {
*/ */
this._rootElement = null; 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. // Bind event handler so it is only bound once for every instance.
this._setRootElementRef = this._setRootElementRef.bind(this); this._setRootElementRef = this._setRootElementRef.bind(this);
} }
@ -69,10 +83,13 @@ class BaseIndicator extends Component {
*/ */
render() { render() {
return ( return (
<span className = { this._classNames }> <span
className = { this.props.className }
id = { this.props.id }
ref = { this._setRootElementRef }>
<i <i
className = { this._iconClass } className = { this.props.iconClassName }
ref = { this._setRootElementRef } /> style = {{ fontSize: this.props.iconSize }} />
</span> </span>
); );
} }
@ -101,7 +118,7 @@ class BaseIndicator extends Component {
// becomes available for tooltips. // becomes available for tooltips.
UIUtil.setTooltip( UIUtil.setTooltip(
this._rootElement, this._rootElement,
this._tooltipKey, this.props.tooltipKey,
'top' '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'; import BaseIndicator from './BaseIndicator';
/** /**
* React {@code Component} for showing a moderator icon with a tooltip. * 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 * @inheritdoc
* the new instance is to be initialized. * @returns {ReactElement}
*/ */
constructor(props) { render() {
super(props); return (
<BaseIndicator
this._classNames = 'focusindicator toolbar-icon right'; className = 'focusindicator toolbar-icon right'
this._iconClass = 'icon-star'; iconClassName = 'icon-star'
this._tooltipKey = 'videothumbnail.moderator'; 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'; import BaseIndicator from './BaseIndicator';
/** /**
* React {@code Component} for showing a video muted icon with a tooltip. * 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 * @inheritdoc
* the new instance is to be initialized.
*/ */
constructor(props) { render() {
super(props); return (
<BaseIndicator
this._classNames = 'videoMuted toolbar-icon'; className = 'videoMuted toolbar-icon'
this._iconClass = 'icon-camera-disabled'; iconClassName = 'icon-camera-disabled'
this._tooltipKey = 'videothumbnail.videomute'; tooltipKey = 'videothumbnail.videomute' />
);
} }
} }

View File

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