From c123ff9e1573a8146ad919cb51d1bab6a86cd7be Mon Sep 17 00:00:00 2001 From: dimitardelchev93 <43634401+dimitardelchev93@users.noreply.github.com> Date: Fri, 13 Aug 2021 19:10:05 +0300 Subject: [PATCH] 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 --- config.js | 3 + lang/main-bg.json | 1 + lang/main-de.json | 1 + lang/main.json | 1 + react/features/base/config/configWhitelist.js | 1 + .../speaker-stats/components/SpeakerStats.js | 65 +++++++++++++++-- .../components/SpeakerStatsSearch.js | 73 +++++++++++++++++++ react/features/speaker-stats/functions.js | 11 +++ 8 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 react/features/speaker-stats/components/SpeakerStatsSearch.js create mode 100644 react/features/speaker-stats/functions.js diff --git a/config.js b/config.js index 91a471a19..d8209441d 100644 --- a/config.js +++ b/config.js @@ -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, diff --git a/lang/main-bg.json b/lang/main-bg.json index 50c5fbd4b..e4f94bd2e 100644 --- a/lang/main-bg.json +++ b/lang/main-bg.json @@ -586,6 +586,7 @@ }, "speaker": "Говорещ", "speakerStats": { + "search": "Търсене", "hours": "{{count}}ч", "minutes": "{{count}}мин", "name": "Име", diff --git a/lang/main-de.json b/lang/main-de.json index dfcd24d61..493586420 100644 --- a/lang/main-de.json +++ b/lang/main-de.json @@ -802,6 +802,7 @@ }, "speaker": "Sprecher/-in", "speakerStats": { + "search": "Suche", "hours": "{{count}} Std. ", "minutes": "{{count}} Min. ", "name": "Name", diff --git a/lang/main.json b/lang/main.json index b6cc52a44..8a33d642c 100644 --- a/lang/main.json +++ b/lang/main.json @@ -802,6 +802,7 @@ }, "speaker": "Speaker", "speakerStats": { + "search": "Search", "hours": "{{count}}h", "minutes": "{{count}}m", "name": "Name", diff --git a/react/features/base/config/configWhitelist.js b/react/features/base/config/configWhitelist.js index 9ff4357c6..9e91c9bb8 100644 --- a/react/features/base/config/configWhitelist.js +++ b/react/features/base/config/configWhitelist.js @@ -98,6 +98,7 @@ export default [ 'disableRtx', 'disableShortcuts', 'disableShowMoreStats', + 'disableSpeakerStatsSearch', 'disableSimulcast', 'disableThirdPartyRequests', 'disableTileView', diff --git a/react/features/speaker-stats/components/SpeakerStats.js b/react/features/speaker-stats/components/SpeakerStats.js index 20d2cc0d1..044b527ab 100644 --- a/react/features/speaker-stats/components/SpeakerStats.js +++ b/react/features/speaker-stats/components/SpeakerStats.js @@ -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 { 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 { + titleKey = { 'speakerStats.speakerStats' }>
+ { items }
@@ -111,6 +121,32 @@ class SpeakerStats extends Component { ); } + /** + * 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 { ); } + _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 { * @private */ _updateStats() { - const stats = this.props.conference.getSpeakerStats(); + const stats = this._getSpeakerStats(); - this.setState({ stats }); + this.setState({ + ...this.state, + stats + }); } } diff --git a/react/features/speaker-stats/components/SpeakerStatsSearch.js b/react/features/speaker-stats/components/SpeakerStatsSearch.js new file mode 100644 index 000000000..2f2cbcafa --- /dev/null +++ b/react/features/speaker-stats/components/SpeakerStatsSearch.js @@ -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} + */ +function SpeakerStatsSearch({ onSearch }: Props) { + const classes = useStyles(); + const { t } = useTranslation(); + const [ searchValue, setSearchValue ] = useState(''); + const onChange = useCallback((evt: Event) => { + const value = getFieldValue(evt); + + setSearchValue(value); + + onSearch && onSearch(value); + }, []); + const disableSpeakerStatsSearch = useSelector(isSpeakerStatsSearchDisabled); + + if (disableSpeakerStatsSearch) { + return null; + } + + return ( +
+ +
+ ); +} + +export default SpeakerStatsSearch; + diff --git a/react/features/speaker-stats/functions.js b/react/features/speaker-stats/functions.js new file mode 100644 index 000000000..6239b0776 --- /dev/null +++ b/react/features/speaker-stats/functions.js @@ -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; +}