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