mirror of https://github.com/jitsi/jitsi-meet
- Create a new ConnectionIndicator component for displaying an icon for connection quality and for triggering a popover. The popover handling has been left in ConnectionIndicator for now, which follows the existing implementation. - Remove the unused method "connectionIndicatorShowMore" - Change the implementation of existing methods that update the connection indicator to call the same method which will rerender the indicator completely.pull/1710/head jitsi-meet_2193
parent
35f79dd2b4
commit
4ce5888b4c
@ -1,273 +0,0 @@ |
|||||||
/* global $, interfaceConfig, JitsiMeetJS */ |
|
||||||
/* jshint -W101 */ |
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */ |
|
||||||
import React from 'react'; |
|
||||||
import ReactDOM from 'react-dom'; |
|
||||||
|
|
||||||
import { ConnectionStatsTable } from '../../../react/features/connection-stats'; |
|
||||||
/* eslint-enable no-unused-vars */ |
|
||||||
|
|
||||||
import JitsiPopover from "../util/JitsiPopover"; |
|
||||||
import UIUtil from "../util/UIUtil"; |
|
||||||
|
|
||||||
const ParticipantConnectionStatus |
|
||||||
= JitsiMeetJS.constants.participantConnectionStatus; |
|
||||||
|
|
||||||
/** |
|
||||||
* Maps a connection quality value (in percent) to the width of the "full" icon. |
|
||||||
*/ |
|
||||||
const qualityToWidth = [ |
|
||||||
// Full (5 bars)
|
|
||||||
{percent: 80, width: "100%"}, |
|
||||||
// 4 bars
|
|
||||||
{percent: 60, width: "80%"}, |
|
||||||
// 3 bars
|
|
||||||
{percent: 40, width: "55%"}, |
|
||||||
// 2 bars
|
|
||||||
{percent: 20, width: "40%"}, |
|
||||||
// 1 bar
|
|
||||||
{percent: 0, width: "20%"} |
|
||||||
// Note: we never show 0 bars.
|
|
||||||
]; |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructs new connection indicator. |
|
||||||
* @param videoContainer the video container associated with the indicator. |
|
||||||
* @param videoId the identifier of the video |
|
||||||
* @constructor |
|
||||||
*/ |
|
||||||
function ConnectionIndicator(videoContainer, videoId) { |
|
||||||
this.videoContainer = videoContainer; |
|
||||||
this.bandwidth = null; |
|
||||||
this.packetLoss = null; |
|
||||||
this.bitrate = null; |
|
||||||
this.showMoreValue = false; |
|
||||||
this.resolution = null; |
|
||||||
this.transport = []; |
|
||||||
this.framerate = null; |
|
||||||
this.popover = null; |
|
||||||
this.id = videoId; |
|
||||||
this.create(); |
|
||||||
|
|
||||||
this.isLocalVideo |
|
||||||
= this.videoContainer.videoSpanId === 'localVideoContainer'; |
|
||||||
this.showMore = this.showMore.bind(this); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Generates the html content. |
|
||||||
* @returns {string} the html content. |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.generateText = function () { |
|
||||||
/* jshint ignore:start */ |
|
||||||
return ( |
|
||||||
<ConnectionStatsTable |
|
||||||
bandwidth = { this.bandwidth } |
|
||||||
bitrate = { this.bitrate } |
|
||||||
isLocalVideo = { this.isLocalVideo } |
|
||||||
framerate = { this.framerate } |
|
||||||
onShowMore = { this.showMore } |
|
||||||
packetLoss = { this.packetLoss} |
|
||||||
resolution = { this.resolution } |
|
||||||
shouldShowMore = { this.showMoreValue } |
|
||||||
transport = { this.transport } /> |
|
||||||
); |
|
||||||
/* jshint ignore:end */ |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Shows or hide the additional information. |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.showMore = function () { |
|
||||||
this.showMoreValue = !this.showMoreValue; |
|
||||||
this.updatePopoverData(); |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
function createIcon(classes, iconClass) { |
|
||||||
var icon = document.createElement("span"); |
|
||||||
for(var i in classes) { |
|
||||||
icon.classList.add(classes[i]); |
|
||||||
} |
|
||||||
icon.appendChild( |
|
||||||
document.createElement("i")).classList.add(iconClass); |
|
||||||
return icon; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates the indicator |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.create = function () { |
|
||||||
let indicatorId = 'connectionindicator'; |
|
||||||
let element = UIUtil.getVideoThumbnailIndicatorSpan({ |
|
||||||
videoSpanId: this.videoContainer.videoSpanId, |
|
||||||
indicatorId |
|
||||||
}); |
|
||||||
element.classList.add('show'); |
|
||||||
this.connectionIndicatorContainer = element; |
|
||||||
|
|
||||||
let popoverContent = ( |
|
||||||
`<div class="connection-info" data-i18n="${indicatorId}.na"></div>` |
|
||||||
); |
|
||||||
this.popover = new JitsiPopover($(element), { |
|
||||||
content: popoverContent, |
|
||||||
skin: "black", |
|
||||||
position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top' |
|
||||||
}); |
|
||||||
|
|
||||||
// override popover show method to make sure we will update the content
|
|
||||||
// before showing the popover
|
|
||||||
var origShowFunc = this.popover.show; |
|
||||||
this.popover.show = function () { |
|
||||||
// update content by forcing it, to finish even if popover
|
|
||||||
// is not visible
|
|
||||||
this.updatePopoverData(true); |
|
||||||
// call the original show, passing its actual this
|
|
||||||
origShowFunc.call(this.popover); |
|
||||||
}.bind(this); |
|
||||||
|
|
||||||
let connectionIconContainer = document.createElement('div'); |
|
||||||
connectionIconContainer.className = 'connection indicatoricon'; |
|
||||||
|
|
||||||
|
|
||||||
this.emptyIcon = connectionIconContainer.appendChild( |
|
||||||
createIcon(["connection_empty"], "icon-connection")); |
|
||||||
this.fullIcon = connectionIconContainer.appendChild( |
|
||||||
createIcon(["connection_full"], "icon-connection")); |
|
||||||
this.interruptedIndicator = connectionIconContainer.appendChild( |
|
||||||
createIcon(["connection_lost"],"icon-connection-lost")); |
|
||||||
this.ninjaIndicator = connectionIconContainer.appendChild( |
|
||||||
createIcon(["connection_ninja"],"icon-ninja")); |
|
||||||
|
|
||||||
$(this.interruptedIndicator).hide(); |
|
||||||
$(this.ninjaIndicator).hide(); |
|
||||||
this.connectionIndicatorContainer.appendChild(connectionIconContainer); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Removes the indicator |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.remove = function() { |
|
||||||
if (this.connectionIndicatorContainer.parentNode) { |
|
||||||
this.connectionIndicatorContainer.parentNode.removeChild( |
|
||||||
this.connectionIndicatorContainer); |
|
||||||
} |
|
||||||
this.popover.forceHide(); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Updates the UI which displays or not a warning about user's connectivity |
|
||||||
* problems. |
|
||||||
* |
|
||||||
* @param {ParticipantConnectionStatus} connectionStatus |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.updateConnectionStatusIndicator |
|
||||||
= function (connectionStatus) { |
|
||||||
this.connectionStatus = connectionStatus; |
|
||||||
if (connectionStatus === ParticipantConnectionStatus.INTERRUPTED) { |
|
||||||
$(this.interruptedIndicator).show(); |
|
||||||
$(this.emptyIcon).hide(); |
|
||||||
$(this.fullIcon).hide(); |
|
||||||
$(this.ninjaIndicator).hide(); |
|
||||||
} else if (connectionStatus === ParticipantConnectionStatus.INACTIVE) { |
|
||||||
$(this.interruptedIndicator).hide(); |
|
||||||
$(this.emptyIcon).hide(); |
|
||||||
$(this.fullIcon).hide(); |
|
||||||
$(this.ninjaIndicator).show(); |
|
||||||
} else { |
|
||||||
$(this.interruptedIndicator).hide(); |
|
||||||
$(this.emptyIcon).show(); |
|
||||||
$(this.fullIcon).show(); |
|
||||||
$(this.ninjaIndicator).hide(); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Updates the data of the indicator |
|
||||||
* @param percent the percent of connection quality |
|
||||||
* @param object the statistics data. |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.updateConnectionQuality = |
|
||||||
function (percent, object) { |
|
||||||
if (!percent) { |
|
||||||
this.connectionIndicatorContainer.style.display = "none"; |
|
||||||
this.popover.forceHide(); |
|
||||||
return; |
|
||||||
} else { |
|
||||||
if(this.connectionIndicatorContainer.style.display == "none") { |
|
||||||
this.connectionIndicatorContainer.style.display = "block"; |
|
||||||
} |
|
||||||
} |
|
||||||
if (object) { |
|
||||||
this.bandwidth = object.bandwidth; |
|
||||||
this.bitrate = object.bitrate; |
|
||||||
this.packetLoss = object.packetLoss; |
|
||||||
this.transport = object.transport; |
|
||||||
if (object.resolution) { |
|
||||||
this.resolution = object.resolution; |
|
||||||
} |
|
||||||
if (object.framerate) |
|
||||||
this.framerate = object.framerate; |
|
||||||
} |
|
||||||
|
|
||||||
let width = qualityToWidth.find(x => percent >= x.percent); |
|
||||||
this.fullIcon.style.width = width.width; |
|
||||||
|
|
||||||
this.updatePopoverData(); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Updates the resolution |
|
||||||
* @param resolution the new resolution |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.updateResolution = function (resolution) { |
|
||||||
this.resolution = resolution; |
|
||||||
this.updatePopoverData(); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Updates the framerate |
|
||||||
* @param framerate the new resolution |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.updateFramerate = function (framerate) { |
|
||||||
this.framerate = framerate; |
|
||||||
this.updatePopoverData(); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Updates the content of the popover if its visible |
|
||||||
* @param force to work even if popover is not visible |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.updatePopoverData = function (force) { |
|
||||||
// generate content, translate it and add it to document only if
|
|
||||||
// popover is visible or we force to do so.
|
|
||||||
if(this.popover.popoverShown || force) { |
|
||||||
this.popover.updateContent(this.generateText()); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Hides the popover |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.hide = function () { |
|
||||||
this.popover.forceHide(); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Hides the indicator |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.hideIndicator = function () { |
|
||||||
this.connectionIndicatorContainer.style.display = "none"; |
|
||||||
if(this.popover) |
|
||||||
this.popover.forceHide(); |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a hover listener to the popover. |
|
||||||
*/ |
|
||||||
ConnectionIndicator.prototype.addPopoverHoverListener = function (listener) { |
|
||||||
this.popover.addOnHoverPopover(listener); |
|
||||||
}; |
|
||||||
|
|
||||||
export default ConnectionIndicator; |
|
@ -0,0 +1,284 @@ |
|||||||
|
import React, { Component } from 'react'; |
||||||
|
|
||||||
|
import JitsiPopover from '../../../../modules/UI/util/JitsiPopover'; |
||||||
|
|
||||||
|
import { JitsiParticipantConnectionStatus } from '../../base/lib-jitsi-meet'; |
||||||
|
import { ConnectionStatsTable } from '../../connection-stats'; |
||||||
|
|
||||||
|
declare var $: Object; |
||||||
|
declare var interfaceConfig: Object; |
||||||
|
|
||||||
|
// Converts the percent for connection quality into a string recognized for CSS.
|
||||||
|
const QUALITY_TO_WIDTH = [ |
||||||
|
|
||||||
|
// Full (5 bars)
|
||||||
|
{ |
||||||
|
percent: 80, |
||||||
|
width: '100%' |
||||||
|
}, |
||||||
|
|
||||||
|
// 4 bars
|
||||||
|
{ |
||||||
|
percent: 60, |
||||||
|
width: '80%' |
||||||
|
}, |
||||||
|
|
||||||
|
// 3 bars
|
||||||
|
{ |
||||||
|
percent: 40, |
||||||
|
width: '55%' |
||||||
|
}, |
||||||
|
|
||||||
|
// 2 bars
|
||||||
|
{ |
||||||
|
percent: 20, |
||||||
|
width: '40%' |
||||||
|
}, |
||||||
|
|
||||||
|
// 1 bar
|
||||||
|
{ |
||||||
|
percent: 0, |
||||||
|
width: '20%' |
||||||
|
} |
||||||
|
|
||||||
|
// Note: we never show 0 bars.
|
||||||
|
]; |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements a React {@link Component} which displays the current connection |
||||||
|
* quality percentage and has a popover to show more detailed connection stats. |
||||||
|
* |
||||||
|
* @extends {Component} |
||||||
|
*/ |
||||||
|
class ConnectionIndicator extends Component { |
||||||
|
/** |
||||||
|
* {@code ConnectionIndicator} component's property types. |
||||||
|
* |
||||||
|
* @static |
||||||
|
*/ |
||||||
|
static propTypes = { |
||||||
|
/** |
||||||
|
* Whether or not the displays stats are for local video. |
||||||
|
*/ |
||||||
|
isLocalVideo: React.PropTypes.bool, |
||||||
|
|
||||||
|
/** |
||||||
|
* The callback to invoke when the hover state over the popover changes. |
||||||
|
*/ |
||||||
|
onHover: React.PropTypes.func, |
||||||
|
|
||||||
|
/** |
||||||
|
* Whether or not the popover should display a link that can toggle |
||||||
|
* a more detailed view of the stats. |
||||||
|
*/ |
||||||
|
showMoreLink: React.PropTypes.bool, |
||||||
|
|
||||||
|
/** |
||||||
|
* An object that contains statistics related to connection quality. |
||||||
|
* |
||||||
|
* { |
||||||
|
* bandwidth: Object, |
||||||
|
* bitrate: Object, |
||||||
|
* connectionStatus: String, |
||||||
|
* framerate: Object, |
||||||
|
* packetLoss: Object, |
||||||
|
* percent: Number, |
||||||
|
* resolution: Object, |
||||||
|
* transport: Array |
||||||
|
* } |
||||||
|
*/ |
||||||
|
stats: React.PropTypes.object, |
||||||
|
|
||||||
|
/** |
||||||
|
* Invoked to obtain translated strings. |
||||||
|
*/ |
||||||
|
t: React.PropTypes.func |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Initializes a new {@code ConnectionIndicator} 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 topmost DOM/HTML element backing the React |
||||||
|
* {@code Component}. Accessed directly for associating an element as |
||||||
|
* the trigger for a popover. |
||||||
|
* |
||||||
|
* @private |
||||||
|
* @type {HTMLDivElement} |
||||||
|
*/ |
||||||
|
this._rootElement = null; |
||||||
|
|
||||||
|
this.state = { |
||||||
|
/** |
||||||
|
* Whether or not the popover content should display additional |
||||||
|
* statistics. |
||||||
|
* |
||||||
|
* @type {boolean} |
||||||
|
*/ |
||||||
|
showMoreStats: false |
||||||
|
}; |
||||||
|
|
||||||
|
// Bind event handlers so they are only bound once for every instance.
|
||||||
|
this._onToggleShowMore = this._onToggleShowMore.bind(this); |
||||||
|
this._setRootElement = this._setRootElement.bind(this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a popover instance to display when the component is hovered. |
||||||
|
* |
||||||
|
* @inheritdoc |
||||||
|
* returns {void} |
||||||
|
*/ |
||||||
|
componentDidMount() { |
||||||
|
this.popover = new JitsiPopover($(this._rootElement), { |
||||||
|
content: this._renderStatisticsTable(), |
||||||
|
skin: 'black', |
||||||
|
position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top' |
||||||
|
}); |
||||||
|
|
||||||
|
this.popover.addOnHoverPopover(this.props.onHover); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates the contents of the popover. This is done manually because the |
||||||
|
* popover is not a React Component yet and so is not automatiucally aware |
||||||
|
* of changed data. |
||||||
|
* |
||||||
|
* @inheritdoc |
||||||
|
* returns {void} |
||||||
|
*/ |
||||||
|
componentDidUpdate() { |
||||||
|
this.popover.updateContent(this._renderStatisticsTable()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Cleans up any popover instance that is linked to the component. |
||||||
|
* |
||||||
|
* @inheritdoc |
||||||
|
* returns {void} |
||||||
|
*/ |
||||||
|
componentWillUnmount() { |
||||||
|
this.popover.forceHide(); |
||||||
|
this.popover.remove(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements React's {@link Component#render()}. |
||||||
|
* |
||||||
|
* @inheritdoc |
||||||
|
* @returns {ReactElement} |
||||||
|
*/ |
||||||
|
render() { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
className = 'connection-indicator indicator' |
||||||
|
ref = { this._setRootElement }> |
||||||
|
<div className = 'connection indicatoricon'> |
||||||
|
{ this._renderIcon() } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Callback to invoke when the show more link in the popover content is |
||||||
|
* clicked. Sets the state which will determine if the popover should show |
||||||
|
* additional statistics about the connection. |
||||||
|
* |
||||||
|
* @returns {void} |
||||||
|
*/ |
||||||
|
_onToggleShowMore() { |
||||||
|
this.setState({ showMoreStats: !this.state.showMoreStats }); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a ReactElement for displaying an icon that represents the current |
||||||
|
* connection quality. |
||||||
|
* |
||||||
|
* @returns {ReactElement} |
||||||
|
*/ |
||||||
|
_renderIcon() { |
||||||
|
switch (this.props.stats.connectionStatus) { |
||||||
|
case JitsiParticipantConnectionStatus.INTERRUPTED: |
||||||
|
return ( |
||||||
|
<span className = 'connection_lost'> |
||||||
|
<i className = 'icon-connection-lost' /> |
||||||
|
</span> |
||||||
|
); |
||||||
|
case JitsiParticipantConnectionStatus.INACTIVE: |
||||||
|
return ( |
||||||
|
<span className = 'connection_ninja'> |
||||||
|
<i className = 'icon-ninja' /> |
||||||
|
</span> |
||||||
|
); |
||||||
|
default: { |
||||||
|
const { percent } = this.props.stats; |
||||||
|
const width = QUALITY_TO_WIDTH.find(x => percent >= x.percent); |
||||||
|
const iconWidth = width && width.width |
||||||
|
? { width: width && width.width } : {}; |
||||||
|
|
||||||
|
return [ |
||||||
|
<span |
||||||
|
className = 'connection_empty' |
||||||
|
key = 'icon-empty'> |
||||||
|
<i className = 'icon-connection' /> |
||||||
|
</span>, |
||||||
|
<span |
||||||
|
className = 'connection_full' |
||||||
|
key = 'icon-full' |
||||||
|
style = { iconWidth }> |
||||||
|
<i className = 'icon-connection' /> |
||||||
|
</span> |
||||||
|
]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a {@code ConnectionStatisticsTable} instance. |
||||||
|
* |
||||||
|
* @returns {ReactElement} |
||||||
|
*/ |
||||||
|
_renderStatisticsTable() { |
||||||
|
const { |
||||||
|
bandwidth, |
||||||
|
bitrate, |
||||||
|
framerate, |
||||||
|
packetLoss, |
||||||
|
resolution, |
||||||
|
transport |
||||||
|
} = this.props.stats; |
||||||
|
|
||||||
|
return ( |
||||||
|
<ConnectionStatsTable |
||||||
|
bandwidth = { bandwidth } |
||||||
|
bitrate = { bitrate } |
||||||
|
framerate = { framerate } |
||||||
|
isLocalVideo = { this.props.isLocalVideo } |
||||||
|
onShowMore = { this._onToggleShowMore } |
||||||
|
packetLoss = { packetLoss } |
||||||
|
resolution = { resolution } |
||||||
|
shouldShowMore = { this.state.showMoreStats } |
||||||
|
transport = { transport } /> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets an internal reference to the component's root element. |
||||||
|
* |
||||||
|
* @param {Object} element - The highest DOM element in the component. |
||||||
|
* @private |
||||||
|
* @returns {void} |
||||||
|
*/ |
||||||
|
_setRootElement(element) { |
||||||
|
this._rootElement = element; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default ConnectionIndicator; |
@ -0,0 +1 @@ |
|||||||
|
export { default as ConnectionIndicator } from './ConnectionIndicator'; |
@ -0,0 +1 @@ |
|||||||
|
export * from './components'; |
Loading…
Reference in new issue