fix(speaker-stats) decouple sort order from speaker stats (#12197)

This commit is contained in:
William Liang 2022-09-28 03:51:53 -04:00 committed by GitHub
parent 2e6f14f872
commit 5d6aec3f3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 112 additions and 74 deletions

View File

@ -29,6 +29,14 @@ export const INIT_UPDATE_STATS = 'INIT_UPDATE_STATS';
*/
export const UPDATE_STATS = 'UPDATE_STATS';
/**
* Action type to update the speaker stats order.
* {
* type: UPDATE_SORTED_SPEAKER_STATS_IDS
* }
*/
export const UPDATE_SORTED_SPEAKER_STATS_IDS = 'UPDATE_SORTED_SPEAKER_STATS_IDS'
/**
* Action type to initiate reordering of the stats.
*

View File

@ -6,6 +6,7 @@ import {
INIT_UPDATE_STATS,
RESET_SEARCH_CRITERIA,
TOGGLE_FACE_EXPRESSIONS,
UPDATE_SORTED_SPEAKER_STATS_IDS,
UPDATE_STATS
} from './actionTypes';
@ -48,6 +49,19 @@ export function updateStats(stats: Object) {
};
}
/**
* Updates the speaker stats order.
*
* @param {Object} participantIds - Participant ids.
* @returns {Object}
*/
export function updateSortedSpeakerStatsIds(participantIds: Array<string>) {
return {
type: UPDATE_SORTED_SPEAKER_STATS_IDS,
participantIds
};
}
/**
* Initiates reordering of the stats.
*

View File

@ -21,7 +21,11 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function, itemStyles?: Objec
const dispatch = useDispatch();
const { t } = useTranslation();
const conference = useSelector(state => state['features/base/conference'].conference);
const { stats: speakerStats, showFaceExpressions } = useSelector(state => state['features/speaker-stats']);
const {
stats: speakerStats,
showFaceExpressions,
sortedSpeakerStatsIds
} = useSelector(state => state['features/speaker-stats']);
const localParticipant = useSelector(getLocalParticipant);
const { defaultRemoteDisplayName } = useSelector(
state => state['features/base/config']) || {};
@ -80,7 +84,10 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function, itemStyles?: Objec
}, [ faceExpressions ]);
const localSpeakerStats = Object.keys(speakerStats).length === 0 ? getSpeakerStats() : speakerStats;
const userIds = Object.keys(localSpeakerStats).filter(id => localSpeakerStats[id] && !localSpeakerStats[id].hidden);
const localSortedSpeakerStatsIds
= sortedSpeakerStatsIds.length === 0 ? Object.keys(localSpeakerStats) : sortedSpeakerStatsIds;
const userIds = localSortedSpeakerStatsIds.filter(id => localSpeakerStats[id] && !localSpeakerStats[id].hidden);
return userIds.map(userId => {
const statsModel = localSpeakerStats[userId];

View File

@ -6,7 +6,6 @@ import {
PARTICIPANT_ROLE,
getParticipantById
} from '../base/participants';
import { objectSort } from '../base/util';
/**
* Checks if the speaker stats search is disabled.
@ -63,19 +62,33 @@ export function getPendingReorder(state: Object) {
}
/**
* Get sorted speaker stats based on a configuration setting.
* Get sorted speaker stats ids based on a configuration setting.
*
* @param {Object} state - The redux state.
* @param {Object} stats - The current speaker stats.
* @returns {Object} - Ordered speaker stats.
* @returns {Object} - Ordered speaker stats ids.
* @public
*/
export function getSortedSpeakerStats(state: Object, stats: Object) {
export function getSortedSpeakerStatsIds(state: Object, stats: Object) {
const orderConfig = getSpeakerStatsOrder(state);
if (orderConfig) {
const enhancedStats = getEnhancedStatsForOrdering(state, stats, orderConfig);
const sortedStats = objectSort(enhancedStats, (currentParticipant, nextParticipant) => {
return Object.entries(enhancedStats)
.sort(([ , a ], [ , b ]) => compareFn(a, b))
.map(el => el[0]);
}
/**
*
* Compares the order of two participants in the speaker stats list.
*
* @param {Object} currentParticipant - The first participant for comparison.
* @param {Object} nextParticipant - The second participant for comparison.
* @returns {number} - The sort order of the two participants.
*/
function compareFn(currentParticipant, nextParticipant) {
if (orderConfig.includes('hasLeft')) {
if (nextParticipant.hasLeft() && !currentParticipant.hasLeft()) {
return -1;
@ -110,9 +123,6 @@ export function getSortedSpeakerStats(state: Object, stats: Object) {
}
return result;
});
return sortedStats;
}
}

View File

@ -13,8 +13,8 @@ import {
INIT_UPDATE_STATS,
RESET_SEARCH_CRITERIA
} from './actionTypes';
import { initReorderStats, updateStats } from './actions';
import { filterBySearchCriteria, getPendingReorder, getSortedSpeakerStats, resetHiddenStats } from './functions';
import { initReorderStats, updateSortedSpeakerStatsIds, updateStats } from './actions';
import { filterBySearchCriteria, getPendingReorder, getSortedSpeakerStatsIds, resetHiddenStats } from './functions';
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const result = next(action);
@ -35,8 +35,13 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const stats = filterBySearchCriteria(state, speakerStats);
const pendingReorder = getPendingReorder(state);
dispatch(updateStats(pendingReorder ? getSortedSpeakerStats(state, stats) : stats));
if (pendingReorder) {
dispatch(updateSortedSpeakerStatsIds(getSortedSpeakerStatsIds(state, stats)));
}
dispatch(updateStats(stats));
}
break;
case RESET_SEARCH_CRITERIA: {

View File

@ -7,6 +7,7 @@ import {
INIT_SEARCH,
RESET_SEARCH_CRITERIA,
TOGGLE_FACE_EXPRESSIONS,
UPDATE_SORTED_SPEAKER_STATS_IDS,
UPDATE_STATS
} from './actionTypes';
@ -20,7 +21,8 @@ const INITIAL_STATE = {
isOpen: false,
pendingReorder: true,
criteria: null,
showFaceExpressions: false
showFaceExpressions: false,
sortedSpeakerStatsIds: []
};
export interface ISpeakerStatsState {
@ -28,6 +30,7 @@ export interface ISpeakerStatsState {
isOpen: boolean;
pendingReorder: boolean;
showFaceExpressions: boolean;
sortedSpeakerStatsIds: Array<string>;
stats: Object;
}
@ -40,6 +43,8 @@ ReducerRegistry.register<ISpeakerStatsState>('features/speaker-stats',
return _updateStats(state, action);
case INIT_REORDER_STATS:
return _initReorderStats(state);
case UPDATE_SORTED_SPEAKER_STATS_IDS:
return _updateSortedSpeakerStats(state, action);
case RESET_SEARCH_CRITERIA:
return _updateCriteria(state, { criteria: null });
case TOGGLE_FACE_EXPRESSIONS: {
@ -71,13 +76,7 @@ function _updateCriteria(state: ISpeakerStatsState, { criteria }: { criteria: st
}
/**
* 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.
* Reduces a specific Redux action UPDATE_STATS of the feature speaker-stats.
*
* @param {Object} state - The Redux state of the feature speaker-stats.
* @param {Action} action - The Redux action UPDATE_STATS to reduce.
@ -85,31 +84,26 @@ function _updateCriteria(state: ISpeakerStatsState, { criteria }: { criteria: st
* @returns {Object} - The new state after the reduction of the specified action.
*/
function _updateStats(state: ISpeakerStatsState, { stats }: { stats: any; }) {
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 {
...state,
stats
};
}
return _.assign(
{},
state,
{
stats: { ...finalStats },
/**
* Reduces a specific Redux action UPDATE_SORTED_SPEAKER_STATS_IDS of the feature speaker-stats.
*
* @param {Object} state - The Redux state of the feature speaker-stats.
* @param {Action} action - The Redux action UPDATE_SORTED_SPEAKER_STATS_IDS to reduce.
* @private
* @returns {Object} The new state after the reduction of the specified action.
*/
function _updateSortedSpeakerStats(state: ISpeakerStatsState, { participantIds }: { participantIds: Array<string>; }) {
return {
...state,
sortedSpeakerStatsIds: participantIds,
pendingReorder: false
}
);
};
}
/**