feat: Additional setting to order participants in speaker stats (#9751)
* Additional setting to order participants in speaker stats #9742 * Setting to order speaker stats optimisations #9742 * Lint fixes #9742 * Replace APP references #9742 * Lint fixes #9742 * Setting to order speaker stats optimisations 2 #9742 * Lint fixes #9742 * Remove unnecessary param #9742 * Add more speaker-stats reducer _updateStats docs #9742
This commit is contained in:
parent
db473dfef5
commit
5e152b4a42
|
@ -150,6 +150,13 @@ var config = {
|
|||
// Specifies whether there will be a search field in speaker stats or not
|
||||
// disableSpeakerStatsSearch: false,
|
||||
|
||||
// Specifies whether participants in speaker stats should be ordered or not, and with what priority
|
||||
// speakerStatsOrder: [
|
||||
// 'role', <- Moderators on top
|
||||
// 'name', <- Alphabetically by name
|
||||
// 'hasLeft', <- The ones that have left in the bottom
|
||||
// ] <- the order of the array elements determines priority
|
||||
|
||||
// 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,
|
||||
|
|
|
@ -42,6 +42,7 @@ import '../recording/middleware';
|
|||
import '../rejoin/middleware';
|
||||
import '../room-lock/middleware';
|
||||
import '../rtcstats/middleware';
|
||||
import '../speaker-stats/middleware';
|
||||
import '../subtitles/middleware';
|
||||
import '../toolbox/middleware';
|
||||
import '../transcribing/middleware';
|
||||
|
|
|
@ -46,6 +46,7 @@ import '../reactions/reducer';
|
|||
import '../recent-list/reducer';
|
||||
import '../recording/reducer';
|
||||
import '../settings/reducer';
|
||||
import '../speaker-stats/reducer';
|
||||
import '../subtitles/reducer';
|
||||
import '../screen-share/reducer';
|
||||
import '../toolbox/reducer';
|
||||
|
|
|
@ -106,6 +106,7 @@ export default [
|
|||
'disableShowMoreStats',
|
||||
'disableRemoveRaisedHandOnFocus',
|
||||
'disableSpeakerStatsSearch',
|
||||
'speakerStatsOrder',
|
||||
'disableSimulcast',
|
||||
'disableThirdPartyRequests',
|
||||
'disableTileView',
|
||||
|
|
|
@ -185,3 +185,19 @@ function parseShorthandColor(color) {
|
|||
|
||||
return [ r, g, b ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts an object by a sort function, same functionality as array.sort().
|
||||
*
|
||||
* @param {Object} object - The data object.
|
||||
* @param {Function} callback - The sort function.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function objectSort(object: Object, callback: Function) {
|
||||
return Object.entries(object)
|
||||
.sort(([ , a ], [ , b ]) => callback(a, b))
|
||||
.reduce((row, [ key, value ]) => {
|
||||
return { ...row,
|
||||
[key]: value };
|
||||
}, {});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
* Action type to start search.
|
||||
*
|
||||
* {
|
||||
* type: INIT_SEARCH
|
||||
* }
|
||||
*/
|
||||
export const INIT_SEARCH = 'INIT_SEARCH';
|
||||
|
||||
/**
|
||||
* Action type to start stats retrieval.
|
||||
*
|
||||
* {
|
||||
* type: INIT_UPDATE_STATS,
|
||||
* getSpeakerStats: Function
|
||||
* }
|
||||
*/
|
||||
export const INIT_UPDATE_STATS = 'INIT_UPDATE_STATS';
|
||||
|
||||
/**
|
||||
* Action type to update stats.
|
||||
*
|
||||
* {
|
||||
* type: UPDATE_STATS,
|
||||
* stats: Object
|
||||
* }
|
||||
*/
|
||||
export const UPDATE_STATS = 'UPDATE_STATS';
|
||||
|
||||
/**
|
||||
* Action type to initiate reordering of the stats.
|
||||
*
|
||||
* {
|
||||
* type: INIT_REORDER_STATS
|
||||
* }
|
||||
*/
|
||||
export const INIT_REORDER_STATS = 'INIT_REORDER_STATS';
|
|
@ -0,0 +1,58 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
INIT_SEARCH,
|
||||
INIT_UPDATE_STATS,
|
||||
UPDATE_STATS,
|
||||
INIT_REORDER_STATS
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Starts a search by criteria.
|
||||
*
|
||||
* @param {string} criteria - The search criteria.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initSearch(criteria: string) {
|
||||
return {
|
||||
type: INIT_SEARCH,
|
||||
criteria
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the new stats and triggers update.
|
||||
*
|
||||
* @param {Function} getSpeakerStats - Function to get the speaker stats.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initUpdateStats(getSpeakerStats: Function) {
|
||||
return {
|
||||
type: INIT_UPDATE_STATS,
|
||||
getSpeakerStats
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the stats with new stats.
|
||||
*
|
||||
* @param {Object} stats - The new stats.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function updateStats(stats: Object) {
|
||||
return {
|
||||
type: UPDATE_STATS,
|
||||
stats
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates reordering of the stats.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initReorderStats() {
|
||||
return {
|
||||
type: INIT_REORDER_STATS
|
||||
};
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
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 { initUpdateStats, initSearch } from '../actions';
|
||||
import { SPEAKER_STATS_RELOAD_INTERVAL } from '../constants';
|
||||
import { getSpeakerStats, getSearchCriteria } from '../functions';
|
||||
|
||||
import SpeakerStatsItem from './SpeakerStatsItem';
|
||||
import SpeakerStatsLabels from './SpeakerStatsLabels';
|
||||
|
@ -24,39 +28,38 @@ type Props = {
|
|||
*/
|
||||
_localDisplayName: string,
|
||||
|
||||
/**
|
||||
* The speaker paricipant stats.
|
||||
*/
|
||||
_stats: Object,
|
||||
|
||||
/**
|
||||
* The search criteria.
|
||||
*/
|
||||
_criteria: string,
|
||||
|
||||
/**
|
||||
* The JitsiConference from which stats will be pulled.
|
||||
*/
|
||||
conference: Object,
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* The function to translate human-readable text.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link SpeakerStats}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The stats summary provided by the JitsiConference.
|
||||
*/
|
||||
stats: Object,
|
||||
|
||||
/**
|
||||
* The search input criteria.
|
||||
*/
|
||||
criteria: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* React component for displaying a list of speaker stats.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class SpeakerStats extends Component<Props, State> {
|
||||
class SpeakerStats extends Component<Props> {
|
||||
_updateInterval: IntervalID;
|
||||
|
||||
/**
|
||||
|
@ -68,14 +71,11 @@ class SpeakerStats extends Component<Props, State> {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
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);
|
||||
|
||||
this._updateStats();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -84,7 +84,7 @@ class SpeakerStats extends Component<Props, State> {
|
|||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._updateInterval = setInterval(this._updateStats, 1000);
|
||||
this._updateInterval = setInterval(() => this._updateStats(), SPEAKER_STATS_RELOAD_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,14 +104,14 @@ class SpeakerStats extends Component<Props, State> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const userIds = Object.keys(this.state.stats);
|
||||
const userIds = Object.keys(this.props._stats);
|
||||
const items = userIds.map(userId => this._createStatsItem(userId));
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
cancelKey = { 'dialog.close' }
|
||||
cancelKey = 'dialog.close'
|
||||
submitDisabled = { true }
|
||||
titleKey = { 'speakerStats.speakerStats' }>
|
||||
titleKey = 'speakerStats.speakerStats'>
|
||||
<div className = 'speaker-stats'>
|
||||
<SpeakerStatsSearch onSearch = { this._onSearch } />
|
||||
<SpeakerStatsLabels />
|
||||
|
@ -121,32 +121,6 @@ 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.
|
||||
*
|
||||
|
@ -156,9 +130,9 @@ class SpeakerStats extends Component<Props, State> {
|
|||
* @private
|
||||
*/
|
||||
_createStatsItem(userId) {
|
||||
const statsModel = this.state.stats[userId];
|
||||
const statsModel = this.props._stats[userId];
|
||||
|
||||
if (!statsModel) {
|
||||
if (!statsModel || statsModel.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -177,7 +151,7 @@ class SpeakerStats extends Component<Props, State> {
|
|||
= displayName ? `${displayName} (${meString})` : meString;
|
||||
} else {
|
||||
displayName
|
||||
= this.state.stats[userId].getDisplayName()
|
||||
= this.props._stats[userId].getDisplayName()
|
||||
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
|
||||
}
|
||||
|
||||
|
@ -201,10 +175,7 @@ class SpeakerStats extends Component<Props, State> {
|
|||
* @protected
|
||||
*/
|
||||
_onSearch(criteria = '') {
|
||||
this.setState({
|
||||
...this.state,
|
||||
criteria: escapeRegexp(criteria)
|
||||
});
|
||||
this.props.dispatch(initSearch(escapeRegexp(criteria)));
|
||||
}
|
||||
|
||||
_updateStats: () => void;
|
||||
|
@ -216,12 +187,7 @@ class SpeakerStats extends Component<Props, State> {
|
|||
* @private
|
||||
*/
|
||||
_updateStats() {
|
||||
const stats = this._getSpeakerStats();
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
stats
|
||||
});
|
||||
this.props.dispatch(initUpdateStats(() => this.props.conference.getSpeakerStats()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,7 +197,9 @@ class SpeakerStats extends Component<Props, State> {
|
|||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _localDisplayName: ?string
|
||||
* _localDisplayName: ?string,
|
||||
* _stats: Object,
|
||||
* _criteria: string,
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
|
@ -244,7 +212,9 @@ function _mapStateToProps(state) {
|
|||
* @private
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
_localDisplayName: localParticipant && localParticipant.name
|
||||
_localDisplayName: localParticipant && localParticipant.name,
|
||||
_stats: getSpeakerStats(state),
|
||||
_criteria: getSearchCriteria(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const SPEAKER_STATS_RELOAD_INTERVAL = 1000;
|
|
@ -1,5 +1,10 @@
|
|||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getLocalParticipant, getParticipantById, PARTICIPANT_ROLE } from '../base/participants';
|
||||
import { objectSort } from '../base/util';
|
||||
|
||||
/**
|
||||
* Checks if the speaker stats search is disabled.
|
||||
*
|
||||
|
@ -9,3 +14,166 @@
|
|||
export function isSpeakerStatsSearchDisabled(state: Object) {
|
||||
return state['features/base/config']?.disableSpeakerStatsSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether participants in speaker stats should be ordered or not, and with what priority.
|
||||
*
|
||||
* @param {*} state - The redux state.
|
||||
* @returns {Array<string>} - The speaker stats order array or an empty array.
|
||||
*/
|
||||
export function getSpeakerStatsOrder(state: Object) {
|
||||
return state['features/base/config']?.speakerStatsOrder ?? [
|
||||
'role',
|
||||
'name',
|
||||
'hasLeft'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets speaker stats.
|
||||
*
|
||||
* @param {*} state - The redux state.
|
||||
* @returns {Object} - The speaker stats.
|
||||
*/
|
||||
export function getSpeakerStats(state: Object) {
|
||||
return state['features/speaker-stats']?.stats ?? {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets speaker stats search criteria.
|
||||
*
|
||||
* @param {*} state - The redux state.
|
||||
* @returns {string} - The search criteria.
|
||||
*/
|
||||
export function getSearchCriteria(state: Object) {
|
||||
return state['features/speaker-stats']?.criteria ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if speaker stats reorder is pending.
|
||||
*
|
||||
* @param {*} state - The redux state.
|
||||
* @returns {boolean} - The pending reorder flag.
|
||||
*/
|
||||
export function getPendingReorder(state: Object) {
|
||||
return state['features/speaker-stats']?.pendingReorder ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sorted speaker stats based on a configuration setting.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Object} stats - The current speaker stats.
|
||||
* @returns {Object} - Ordered speaker stats.
|
||||
* @public
|
||||
*/
|
||||
export function getSortedSpeakerStats(state: Object, stats: Object) {
|
||||
const orderConfig = getSpeakerStatsOrder(state);
|
||||
|
||||
if (orderConfig) {
|
||||
const enhancedStats = getEnhancedStatsForOrdering(state, stats, orderConfig);
|
||||
const sortedStats = objectSort(enhancedStats, (currentParticipant, nextParticipant) => {
|
||||
if (orderConfig.includes('hasLeft')) {
|
||||
if (nextParticipant.hasLeft() && !currentParticipant.hasLeft()) {
|
||||
return -1;
|
||||
} else if (currentParticipant.hasLeft() && !nextParticipant.hasLeft()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
for (const sortCriteria of orderConfig) {
|
||||
switch (sortCriteria) {
|
||||
case 'role':
|
||||
if (!nextParticipant.isModerator && currentParticipant.isModerator) {
|
||||
result = -1;
|
||||
} else if (!currentParticipant.isModerator && nextParticipant.isModerator) {
|
||||
result = 1;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
break;
|
||||
case 'name':
|
||||
result = (currentParticipant.displayName || '').localeCompare(
|
||||
nextParticipant.displayName || ''
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result !== 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return sortedStats;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance speaker stats to include data needed for ordering.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Object} stats - Speaker stats.
|
||||
* @param {Array<string>} orderConfig - Ordering configuration.
|
||||
* @returns {Object} - Enhanced speaker stats.
|
||||
* @public
|
||||
*/
|
||||
function getEnhancedStatsForOrdering(state, stats, orderConfig) {
|
||||
if (!orderConfig) {
|
||||
return stats;
|
||||
}
|
||||
|
||||
for (const id in stats) {
|
||||
if (stats[id].hasOwnProperty('_hasLeft') && !stats[id].hasLeft()) {
|
||||
if (orderConfig.includes('name')) {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
if (stats[id].isLocalStats()) {
|
||||
stats[id].setDisplayName(localParticipant.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (orderConfig.includes('role')) {
|
||||
const participant = getParticipantById(state, stats[id].getUserId());
|
||||
|
||||
stats[id].isModerator = participant && participant.role === PARTICIPANT_ROLE.MODERATOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter stats by search criteria.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Object | undefined} stats - The unfiltered stats.
|
||||
*
|
||||
* @returns {Object} - Filtered speaker stats.
|
||||
* @public
|
||||
*/
|
||||
export function filterBySearchCriteria(state: Object, stats: ?Object) {
|
||||
const filteredStats = _.cloneDeep(stats ?? getSpeakerStats(state));
|
||||
const criteria = getSearchCriteria(state);
|
||||
|
||||
if (criteria) {
|
||||
const searchRegex = new RegExp(criteria, 'gi');
|
||||
|
||||
for (const id in filteredStats) {
|
||||
if (filteredStats[id].hasOwnProperty('_isLocalStats')) {
|
||||
const name = filteredStats[id].getDisplayName();
|
||||
|
||||
if (!name || !name.match(searchRegex)) {
|
||||
filteredStats[id].hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredStats;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
PARTICIPANT_JOINED,
|
||||
PARTICIPANT_KICKED,
|
||||
PARTICIPANT_LEFT,
|
||||
PARTICIPANT_UPDATED
|
||||
} from '../base/participants/actionTypes';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { INIT_SEARCH, INIT_UPDATE_STATS } from './actionTypes';
|
||||
import { initReorderStats, updateStats } from './actions';
|
||||
import { filterBySearchCriteria, getSortedSpeakerStats, getPendingReorder } from './functions';
|
||||
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case INIT_SEARCH: {
|
||||
const state = getState();
|
||||
const stats = filterBySearchCriteria(state);
|
||||
|
||||
dispatch(updateStats(stats));
|
||||
break;
|
||||
}
|
||||
|
||||
case INIT_UPDATE_STATS:
|
||||
if (action.getSpeakerStats) {
|
||||
const state = getState();
|
||||
const speakerStats = { ...action.getSpeakerStats() };
|
||||
const stats = filterBySearchCriteria(state, speakerStats);
|
||||
const pendingReorder = getPendingReorder(state);
|
||||
|
||||
dispatch(updateStats(pendingReorder ? getSortedSpeakerStats(state, stats) : stats));
|
||||
}
|
||||
break;
|
||||
case PARTICIPANT_JOINED:
|
||||
case PARTICIPANT_LEFT:
|
||||
case PARTICIPANT_KICKED:
|
||||
case PARTICIPANT_UPDATED: {
|
||||
dispatch(initReorderStats());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
|
@ -0,0 +1,119 @@
|
|||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
INIT_SEARCH,
|
||||
UPDATE_STATS,
|
||||
INIT_REORDER_STATS
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* The initial state of the feature speaker-stats.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const INITIAL_STATE = {
|
||||
stats: {},
|
||||
pendingReorder: true,
|
||||
criteria: ''
|
||||
};
|
||||
|
||||
ReducerRegistry.register('features/speaker-stats', (state = _getInitialState(), action) => {
|
||||
switch (action.type) {
|
||||
case INIT_SEARCH:
|
||||
return _updateCriteria(state, action);
|
||||
case UPDATE_STATS:
|
||||
return _updateStats(state, action);
|
||||
case INIT_REORDER_STATS:
|
||||
return _initReorderStats(state);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the initial state of the feature speaker-stats.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _getInitialState() {
|
||||
return INITIAL_STATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action INIT_SEARCH of the feature
|
||||
* speaker-stats.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature speaker-stats.
|
||||
* @param {Action} action - The Redux action INIT_SEARCH to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state after the reduction of the specified action.
|
||||
*/
|
||||
function _updateCriteria(state, { criteria }) {
|
||||
return _.assign(
|
||||
{},
|
||||
state,
|
||||
{ criteria },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action UPDATE_STATS of the feature
|
||||
* speaker-stats.
|
||||
* The speaker stats order is based on the stats object properties.
|
||||
* When updating without reordering, the new stats object properties are reordered
|
||||
* as the last in state, otherwise the order would be lost on each update.
|
||||
* If there was already a pending reorder, the stats object properties already have
|
||||
* the correct order, so the property order is not changing.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature speaker-stats.
|
||||
* @param {Action} action - The Redux action UPDATE_STATS to reduce.
|
||||
* @private
|
||||
* @returns {Object} - The new state after the reduction of the specified action.
|
||||
*/
|
||||
function _updateStats(state, { stats }) {
|
||||
const finalStats = state.pendingReorder ? stats : state.stats;
|
||||
|
||||
if (!state.pendingReorder) {
|
||||
// Avoid reordering the speaker stats object properties
|
||||
const finalKeys = Object.keys(stats);
|
||||
|
||||
finalKeys.forEach(newStatId => {
|
||||
finalStats[newStatId] = _.clone(stats[newStatId]);
|
||||
});
|
||||
|
||||
Object.keys(finalStats).forEach(key => {
|
||||
if (!finalKeys.includes(key)) {
|
||||
delete finalStats[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return _.assign(
|
||||
{},
|
||||
state,
|
||||
{
|
||||
stats: finalStats,
|
||||
pendingReorder: false
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action INIT_REORDER_STATS of the feature
|
||||
* speaker-stats.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature speaker-stats.
|
||||
* @private
|
||||
* @returns {Object} The new state after the reduction of the specified action.
|
||||
*/
|
||||
function _initReorderStats(state) {
|
||||
return _.assign(
|
||||
{},
|
||||
state,
|
||||
{ pendingReorder: true },
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue