feat: Add search to speaker stats (#9510)

* Additional setting to add search to speaker stats

* Add translation for speaker stats search placeholder

* Unset speaker stats search input autocomplete

* Fix lint errors for speaker stats search

* Change setting to disableSpeakerStatsSearch

* Better Object.prototype.hasOwnProperty.call alternative

* Make SpeakerStatsSearch a functional component

* Align header with input and use material-ui styles instead of scss and remove SpeakerStats header and fix dialog close

* Resolve code style remark in SpeakerStats constructor

* Resolve component empty return value remark in SpeakerStatsSearch

* Resolve get config property in outside function remark in SpeakerStatsSearch

* Resolve unnecessary anonymous function remark in SpeakerStatsSearch
This commit is contained in:
dimitardelchev93 2021-08-13 19:10:05 +03:00 committed by GitHub
parent ae33755913
commit c123ff9e15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 151 additions and 5 deletions

View File

@ -144,6 +144,9 @@ var config = {
// Sets the preferred resolution (height) for local video. Defaults to 720.
// resolution: 720,
// Specifies whether there will be a search field in speaker stats or not
// disableSpeakerStatsSearch: false,
// How many participants while in the tile view mode, before the receiving video quality is reduced from HD to SD.
// Use -1 to disable.
// maxFullResolutionParticipants: 2,

View File

@ -586,6 +586,7 @@
},
"speaker": "Говорещ",
"speakerStats": {
"search": "Търсене",
"hours": "{{count}}ч",
"minutes": "{{count}}мин",
"name": "Име",

View File

@ -802,6 +802,7 @@
},
"speaker": "Sprecher/-in",
"speakerStats": {
"search": "Suche",
"hours": "{{count}} Std. ",
"minutes": "{{count}} Min. ",
"name": "Name",

View File

@ -802,6 +802,7 @@
},
"speaker": "Speaker",
"speakerStats": {
"search": "Search",
"hours": "{{count}}h",
"minutes": "{{count}}m",
"name": "Name",

View File

@ -98,6 +98,7 @@ export default [
'disableRtx',
'disableShortcuts',
'disableShowMoreStats',
'disableSpeakerStatsSearch',
'disableSimulcast',
'disableThirdPartyRequests',
'disableTileView',

View File

@ -6,9 +6,11 @@ import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { getLocalParticipant } from '../../base/participants';
import { connect } from '../../base/redux';
import { escapeRegexp } from '../../base/util';
import SpeakerStatsItem from './SpeakerStatsItem';
import SpeakerStatsLabels from './SpeakerStatsLabels';
import SpeakerStatsSearch from './SpeakerStatsSearch';
declare var interfaceConfig: Object;
@ -41,7 +43,12 @@ type State = {
/**
* The stats summary provided by the JitsiConference.
*/
stats: Object
stats: Object,
/**
* The search input criteria.
*/
criteria: string,
};
/**
@ -62,11 +69,13 @@ class SpeakerStats extends Component<Props, State> {
super(props);
this.state = {
stats: this.props.conference.getSpeakerStats()
stats: this._getSpeakerStats(),
criteria: ''
};
// Bind event handlers so they are only bound once per instance.
this._updateStats = this._updateStats.bind(this);
this._onSearch = this._onSearch.bind(this);
}
/**
@ -102,8 +111,9 @@ class SpeakerStats extends Component<Props, State> {
<Dialog
cancelKey = { 'dialog.close' }
submitDisabled = { true }
titleKey = 'speakerStats.speakerStats'>
titleKey = { 'speakerStats.speakerStats' }>
<div className = 'speaker-stats'>
<SpeakerStatsSearch onSearch = { this._onSearch } />
<SpeakerStatsLabels />
{ items }
</div>
@ -111,6 +121,32 @@ class SpeakerStats extends Component<Props, State> {
);
}
/**
* Update the internal state with the latest speaker stats.
*
* @returns {void}
* @private
*/
_getSpeakerStats() {
const stats = { ...this.props.conference.getSpeakerStats() };
if (this.state?.criteria) {
const searchRegex = new RegExp(this.state.criteria, 'gi');
for (const id in stats) {
if (stats[id].hasOwnProperty('_isLocalStats')) {
const name = stats[id].isLocalStats() ? this.props._localDisplayName : stats[id].getDisplayName();
if (!name || !name.match(searchRegex)) {
delete stats[id];
}
}
}
}
return stats;
}
/**
* Create a SpeakerStatsItem instance for the passed in user id.
*
@ -155,6 +191,22 @@ class SpeakerStats extends Component<Props, State> {
);
}
_onSearch: () => void;
/**
* Search the existing participants by name.
*
* @returns {void}
* @param {string} criteria - The search parameter.
* @protected
*/
_onSearch(criteria = '') {
this.setState({
...this.state,
criteria: escapeRegexp(criteria)
});
}
_updateStats: () => void;
/**
@ -164,9 +216,12 @@ class SpeakerStats extends Component<Props, State> {
* @private
*/
_updateStats() {
const stats = this.props.conference.getSpeakerStats();
const stats = this._getSpeakerStats();
this.setState({ stats });
this.setState({
...this.state,
stats
});
}
}

View File

@ -0,0 +1,73 @@
/* @flow */
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
import { makeStyles } from '@material-ui/core/styles';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { getFieldValue } from '../../base/react';
import { isSpeakerStatsSearchDisabled } from '../functions';
const useStyles = makeStyles(() => {
return {
speakerStatsSearch: {
position: 'absolute',
right: '50px',
top: '-5px'
}
};
});
/**
* The type of the React {@code Component} props of {@link SpeakerStatsSearch}.
*/
type Props = {
/**
* The function to initiate the change in the speaker stats table.
*/
onSearch: Function,
};
/**
* React component for display an individual user's speaker stats.
*
* @returns {React$Element<any>}
*/
function SpeakerStatsSearch({ onSearch }: Props) {
const classes = useStyles();
const { t } = useTranslation();
const [ searchValue, setSearchValue ] = useState<string>('');
const onChange = useCallback((evt: Event) => {
const value = getFieldValue(evt);
setSearchValue(value);
onSearch && onSearch(value);
}, []);
const disableSpeakerStatsSearch = useSelector(isSpeakerStatsSearchDisabled);
if (disableSpeakerStatsSearch) {
return null;
}
return (
<div className = { classes.speakerStatsSearch }>
<TextField
autoComplete = 'off'
autoFocus = { false }
compact = { true }
name = 'speakerStatsSearch'
onChange = { onChange }
placeholder = { t('speakerStats.search') }
shouldFitContainer = { false }
type = 'text'
value = { searchValue } />
</div>
);
}
export default SpeakerStatsSearch;

View File

@ -0,0 +1,11 @@
// @flow
/**
* Checks if the speaker stats search is disabled.
*
* @param {*} state - The redux state.
* @returns {boolean} - True if the speaker stats search is disabled and false otherwise.
*/
export function isSpeakerStatsSearchDisabled(state: Object) {
return state['features/base/config']?.disableSpeakerStatsSearch;
}