mirror of https://github.com/jitsi/jitsi-meet
parent
59a74153dc
commit
989161159d
@ -0,0 +1,56 @@ |
||||
.speaker-stats { |
||||
list-style: none; |
||||
padding: 0; |
||||
color: $auiDialogColor; |
||||
width: 100%; |
||||
font-weight: 500; |
||||
|
||||
.speaker-stats-item__status-dot { |
||||
position: relative; |
||||
display: block; |
||||
width: 9px; |
||||
height: 9px; |
||||
border-radius: 50%; |
||||
margin: 0 auto; |
||||
|
||||
&.status-active { |
||||
background: green; |
||||
} |
||||
|
||||
&.status-inactive { |
||||
background: gray; |
||||
} |
||||
} |
||||
|
||||
.status-user-left { |
||||
color: $placeHolderColor; |
||||
} |
||||
|
||||
.speaker-stats-item__status, |
||||
.speaker-stats-item__name, |
||||
.speaker-stats-item__time { |
||||
display: inline-block; |
||||
margin: 5px 0; |
||||
vertical-align: middle; |
||||
} |
||||
.speaker-stats-item__status { |
||||
width: 5%; |
||||
} |
||||
.speaker-stats-item__name { |
||||
width: 40%; |
||||
} |
||||
.speaker-stats-item__time { |
||||
width: 55%; |
||||
} |
||||
|
||||
.speaker-stats-item:nth-child(even) { |
||||
background: whitesmoke; |
||||
} |
||||
|
||||
.speaker-stats-item__name, |
||||
.speaker-stats-item__time { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
/** |
||||
* Counts how many whole hours are included in the given time total. |
||||
* |
||||
* @param {number} milliseconds - The millisecond total to get hours from. |
||||
* @returns {number} |
||||
*/ |
||||
export function getHoursCount(milliseconds) { |
||||
return Math.floor(milliseconds / (60 * 60 * 1000)); |
||||
} |
||||
|
||||
/** |
||||
* Counts how many whole minutes are included in the given time total. |
||||
* |
||||
* @param {number} milliseconds - The millisecond total to get minutes from. |
||||
* @returns {number} |
||||
*/ |
||||
export function getMinutesCount(milliseconds) { |
||||
return Math.floor(milliseconds / (60 * 1000) % 60); |
||||
} |
||||
|
||||
/** |
||||
* Counts how many whole seconds are included in the given time total. |
||||
* |
||||
* @param {number} milliseconds - The millisecond total to get seconds from. |
||||
* @returns {number} |
||||
*/ |
||||
export function getSecondsCount(milliseconds) { |
||||
return Math.floor(milliseconds / 1000 % 60); |
||||
} |
@ -0,0 +1,150 @@ |
||||
/* global APP, interfaceConfig */ |
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { Dialog } from '../../base/dialog'; |
||||
import { translate } from '../../base/i18n'; |
||||
import SpeakerStatsItem from './SpeakerStatsItem'; |
||||
import SpeakerStatsLabels from './SpeakerStatsLabels'; |
||||
|
||||
/** |
||||
* React component for displaying a list of speaker stats. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class SpeakerStats extends Component { |
||||
/** |
||||
* SpeakerStats component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The JitsiConference from which stats will be pulled. |
||||
*/ |
||||
conference: React.PropTypes.object, |
||||
|
||||
/** |
||||
* The function to translate human-readable text. |
||||
*/ |
||||
t: React.PropTypes.func |
||||
} |
||||
|
||||
/** |
||||
* Initializes a new SpeakerStats instance. |
||||
* |
||||
* @param {Object} props - The read-only React Component props with which |
||||
* the new instance is to be initialized. |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
this.state = { |
||||
stats: {} |
||||
}; |
||||
this._updateInterval = null; |
||||
this._updateStats = this._updateStats.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Immediately request for updated speaker stats and begin |
||||
* polling for speaker stats updates. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
*/ |
||||
componentWillMount() { |
||||
this._updateStats(); |
||||
this._updateInterval = setInterval(this._updateStats, 1000); |
||||
} |
||||
|
||||
/** |
||||
* Stop polling for speaker stats updates. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
*/ |
||||
componentWillUnmount() { |
||||
clearInterval(this._updateInterval); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const userIds = Object.keys(this.state.stats); |
||||
const items = userIds.map(userId => this._createStatsItem(userId)); |
||||
|
||||
return ( |
||||
<Dialog |
||||
cancelTitleKey = { 'dialog.close' } |
||||
submitDisabled = { true } |
||||
titleKey = 'speakerStats.speakerStats'> |
||||
<div className = 'speaker-stats'> |
||||
<SpeakerStatsLabels /> |
||||
{ items } |
||||
</div> |
||||
</Dialog> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Update the internal state with the latest speaker stats. |
||||
* |
||||
* @returns {void} |
||||
* @private |
||||
*/ |
||||
_updateStats() { |
||||
const stats = this.props.conference.getSpeakerStats(); |
||||
|
||||
this.setState({ stats }); |
||||
} |
||||
|
||||
/** |
||||
* Create a SpeakerStatsItem instance for the passed in user id. |
||||
* |
||||
* @param {string} userId - User id used to look up the associated |
||||
* speaker stats from the jitsi library. |
||||
* @returns {SpeakerStatsItem|null} |
||||
* @private |
||||
*/ |
||||
_createStatsItem(userId) { |
||||
const statsModel = this.state.stats[userId]; |
||||
|
||||
if (!statsModel) { |
||||
return null; |
||||
} |
||||
|
||||
const isDominantSpeaker = statsModel.isDominantSpeaker(); |
||||
const dominantSpeakerTime = statsModel.getTotalDominantSpeakerTime(); |
||||
const hasLeft = statsModel.hasLeft(); |
||||
|
||||
let displayName = ''; |
||||
|
||||
if (statsModel.isLocalStats()) { |
||||
const { t } = this.props; |
||||
const meString = t('me'); |
||||
|
||||
displayName = APP.settings.getDisplayName(); |
||||
displayName = displayName ? `${displayName} (${meString})` |
||||
: meString; |
||||
} else { |
||||
displayName = this.state.stats[userId].getDisplayName() |
||||
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME; |
||||
} |
||||
|
||||
return ( |
||||
<SpeakerStatsItem |
||||
displayName = { displayName } |
||||
dominantSpeakerTime = { dominantSpeakerTime } |
||||
hasLeft = { hasLeft } |
||||
isDominantSpeaker = { isDominantSpeaker } |
||||
key = { userId } /> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default translate(SpeakerStats); |
@ -0,0 +1,69 @@ |
||||
import React, { Component } from 'react'; |
||||
|
||||
import TimeElapsed from './TimeElapsed'; |
||||
|
||||
/** |
||||
* React component for display an individual user's speaker stats. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class SpeakerStatsItem extends Component { |
||||
/** |
||||
* SpeakerStatsItem component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The name of the participant. |
||||
*/ |
||||
displayName: React.PropTypes.string, |
||||
|
||||
/** |
||||
* The total milliseconds the participant has been dominant speaker. |
||||
*/ |
||||
dominantSpeakerTime: React.PropTypes.number, |
||||
|
||||
/** |
||||
* True if the participant is no longer in the meeting. |
||||
*/ |
||||
hasLeft: React.PropTypes.bool, |
||||
|
||||
/** |
||||
* True if the participant is currently the dominant speaker. |
||||
*/ |
||||
isDominantSpeaker: React.PropTypes.bool |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const hasLeftClass = this.props.hasLeft ? 'status-user-left' : ''; |
||||
const rowDisplayClass = `speaker-stats-item ${hasLeftClass}`; |
||||
|
||||
const dotClass = this.props.isDominantSpeaker |
||||
? 'status-active' : 'status-inactive'; |
||||
const speakerStatusClass = `speaker-stats-item__status-dot ${dotClass}`; |
||||
|
||||
return ( |
||||
<div className = { rowDisplayClass }> |
||||
<div className = 'speaker-stats-item__status'> |
||||
<span className = { speakerStatusClass } /> |
||||
</div> |
||||
<div className = 'speaker-stats-item__name'> |
||||
{ this.props.displayName } |
||||
</div> |
||||
<div className = 'speaker-stats-item__time'> |
||||
<TimeElapsed |
||||
time = { this.props.dominantSpeakerTime } /> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default SpeakerStatsItem; |
@ -0,0 +1,46 @@ |
||||
import React, { Component } from 'react'; |
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
|
||||
/** |
||||
* React component for labeling speaker stats column items. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class SpeakerStatsLabels extends Component { |
||||
/** |
||||
* SpeakerStatsLabels component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The function to translate human-readable text. |
||||
*/ |
||||
t: React.PropTypes.func |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { t } = this.props; |
||||
|
||||
return ( |
||||
<div className = 'speaker-stats-item__labels'> |
||||
<div className = 'speaker-stats-item__status' /> |
||||
<div className = 'speaker-stats-item__name'> |
||||
{ t('speakerStats.name') } |
||||
</div> |
||||
<div className = 'speaker-stats-item__time'> |
||||
{ t('speakerStats.speakerTime') } |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default translate(SpeakerStatsLabels); |
@ -0,0 +1,94 @@ |
||||
import React, { Component } from 'react'; |
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
import { |
||||
getHoursCount, |
||||
getMinutesCount, |
||||
getSecondsCount |
||||
} from '../../base/util/timeUtils'; |
||||
|
||||
/** |
||||
* React component for displaying total time elapsed. Converts a total count of |
||||
* milliseconds into a more humanized form: "# hours, # minutes, # seconds". |
||||
* With a time of 0, "0s" will be displayed. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class TimeElapsed extends Component { |
||||
/** |
||||
* TimeElapsed component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The function to translate human-readable text. |
||||
*/ |
||||
t: React.PropTypes.func, |
||||
|
||||
/** |
||||
* The milliseconds to be converted into a humanized format. |
||||
*/ |
||||
time: React.PropTypes.number |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const hours = getHoursCount(this.props.time); |
||||
const minutes = getMinutesCount(this.props.time); |
||||
const seconds = getSecondsCount(this.props.time); |
||||
const timeElapsed = []; |
||||
|
||||
if (hours) { |
||||
const hourPassed = this._createTimeDisplay(hours, |
||||
'speakerStats.hours', 'hours'); |
||||
|
||||
timeElapsed.push(hourPassed); |
||||
} |
||||
|
||||
if (hours || minutes) { |
||||
const minutesPassed = this._createTimeDisplay(minutes, |
||||
'speakerStats.minutes', 'minutes'); |
||||
|
||||
timeElapsed.push(minutesPassed); |
||||
} |
||||
|
||||
const secondsPassed = this._createTimeDisplay(seconds, |
||||
'speakerStats.seconds', 'seconds'); |
||||
|
||||
timeElapsed.push(secondsPassed); |
||||
|
||||
return ( |
||||
<div> |
||||
{ timeElapsed } |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Returns a ReactElement to display the passed in count and a count noun. |
||||
* |
||||
* @private |
||||
* @param {number} count - The number used for display and to check for |
||||
* count noun plurality. |
||||
* @param {string} countNounKey - Translation key for the time's count noun. |
||||
* @param {string} countType - What is being counted. Used as the element's |
||||
* key for react to iterate upon. |
||||
* @returns {ReactElement} |
||||
*/ |
||||
_createTimeDisplay(count, countNounKey, countType) { |
||||
const { t } = this.props; |
||||
|
||||
return ( |
||||
<span key = { countType } > { t(countNounKey, { count }) } </span> |
||||
); |
||||
} |
||||
|
||||
} |
||||
|
||||
export default translate(TimeElapsed); |
@ -0,0 +1 @@ |
||||
export { default as SpeakerStats } from './SpeakerStats'; |
@ -0,0 +1 @@ |
||||
export * from './components'; |
Loading…
Reference in new issue