fix(speaker-stats) decouple sort order from speaker stats (#12197)
This commit is contained in:
parent
2e6f14f872
commit
5d6aec3f3c
|
@ -29,6 +29,14 @@ export const INIT_UPDATE_STATS = 'INIT_UPDATE_STATS';
|
||||||
*/
|
*/
|
||||||
export const UPDATE_STATS = '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.
|
* Action type to initiate reordering of the stats.
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
INIT_UPDATE_STATS,
|
INIT_UPDATE_STATS,
|
||||||
RESET_SEARCH_CRITERIA,
|
RESET_SEARCH_CRITERIA,
|
||||||
TOGGLE_FACE_EXPRESSIONS,
|
TOGGLE_FACE_EXPRESSIONS,
|
||||||
|
UPDATE_SORTED_SPEAKER_STATS_IDS,
|
||||||
UPDATE_STATS
|
UPDATE_STATS
|
||||||
} from './actionTypes';
|
} 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.
|
* Initiates reordering of the stats.
|
||||||
*
|
*
|
||||||
|
|
|
@ -21,7 +21,11 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function, itemStyles?: Objec
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const conference = useSelector(state => state['features/base/conference'].conference);
|
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 localParticipant = useSelector(getLocalParticipant);
|
||||||
const { defaultRemoteDisplayName } = useSelector(
|
const { defaultRemoteDisplayName } = useSelector(
|
||||||
state => state['features/base/config']) || {};
|
state => state['features/base/config']) || {};
|
||||||
|
@ -80,7 +84,10 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function, itemStyles?: Objec
|
||||||
}, [ faceExpressions ]);
|
}, [ faceExpressions ]);
|
||||||
|
|
||||||
const localSpeakerStats = Object.keys(speakerStats).length === 0 ? getSpeakerStats() : speakerStats;
|
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 => {
|
return userIds.map(userId => {
|
||||||
const statsModel = localSpeakerStats[userId];
|
const statsModel = localSpeakerStats[userId];
|
||||||
|
|
|
@ -6,7 +6,6 @@ import {
|
||||||
PARTICIPANT_ROLE,
|
PARTICIPANT_ROLE,
|
||||||
getParticipantById
|
getParticipantById
|
||||||
} from '../base/participants';
|
} from '../base/participants';
|
||||||
import { objectSort } from '../base/util';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the speaker stats search is disabled.
|
* Checks if the speaker stats search is disabled.
|
||||||
|
@ -63,56 +62,67 @@ 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} state - The redux state.
|
||||||
* @param {Object} stats - The current speaker stats.
|
* @param {Object} stats - The current speaker stats.
|
||||||
* @returns {Object} - Ordered speaker stats.
|
* @returns {Object} - Ordered speaker stats ids.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function getSortedSpeakerStats(state: Object, stats: Object) {
|
export function getSortedSpeakerStatsIds(state: Object, stats: Object) {
|
||||||
const orderConfig = getSpeakerStatsOrder(state);
|
const orderConfig = getSpeakerStatsOrder(state);
|
||||||
|
|
||||||
if (orderConfig) {
|
if (orderConfig) {
|
||||||
const enhancedStats = getEnhancedStatsForOrdering(state, stats, orderConfig);
|
const enhancedStats = getEnhancedStatsForOrdering(state, stats, orderConfig);
|
||||||
const sortedStats = objectSort(enhancedStats, (currentParticipant, nextParticipant) => {
|
|
||||||
if (orderConfig.includes('hasLeft')) {
|
return Object.entries(enhancedStats)
|
||||||
if (nextParticipant.hasLeft() && !currentParticipant.hasLeft()) {
|
.sort(([ , a ], [ , b ]) => compareFn(a, b))
|
||||||
return -1;
|
.map(el => el[0]);
|
||||||
} else if (currentParticipant.hasLeft() && !nextParticipant.hasLeft()) {
|
}
|
||||||
return 1;
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result;
|
if (result !== 0) {
|
||||||
|
break;
|
||||||
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 result;
|
||||||
});
|
|
||||||
|
|
||||||
return sortedStats;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,8 @@ import {
|
||||||
INIT_UPDATE_STATS,
|
INIT_UPDATE_STATS,
|
||||||
RESET_SEARCH_CRITERIA
|
RESET_SEARCH_CRITERIA
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { initReorderStats, updateStats } from './actions';
|
import { initReorderStats, updateSortedSpeakerStatsIds, updateStats } from './actions';
|
||||||
import { filterBySearchCriteria, getPendingReorder, getSortedSpeakerStats, resetHiddenStats } from './functions';
|
import { filterBySearchCriteria, getPendingReorder, getSortedSpeakerStatsIds, resetHiddenStats } from './functions';
|
||||||
|
|
||||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
@ -35,8 +35,13 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||||
const stats = filterBySearchCriteria(state, speakerStats);
|
const stats = filterBySearchCriteria(state, speakerStats);
|
||||||
const pendingReorder = getPendingReorder(state);
|
const pendingReorder = getPendingReorder(state);
|
||||||
|
|
||||||
dispatch(updateStats(pendingReorder ? getSortedSpeakerStats(state, stats) : stats));
|
if (pendingReorder) {
|
||||||
|
dispatch(updateSortedSpeakerStatsIds(getSortedSpeakerStatsIds(state, stats)));
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(updateStats(stats));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RESET_SEARCH_CRITERIA: {
|
case RESET_SEARCH_CRITERIA: {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
INIT_SEARCH,
|
INIT_SEARCH,
|
||||||
RESET_SEARCH_CRITERIA,
|
RESET_SEARCH_CRITERIA,
|
||||||
TOGGLE_FACE_EXPRESSIONS,
|
TOGGLE_FACE_EXPRESSIONS,
|
||||||
|
UPDATE_SORTED_SPEAKER_STATS_IDS,
|
||||||
UPDATE_STATS
|
UPDATE_STATS
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
|
||||||
|
@ -20,7 +21,8 @@ const INITIAL_STATE = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
pendingReorder: true,
|
pendingReorder: true,
|
||||||
criteria: null,
|
criteria: null,
|
||||||
showFaceExpressions: false
|
showFaceExpressions: false,
|
||||||
|
sortedSpeakerStatsIds: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ISpeakerStatsState {
|
export interface ISpeakerStatsState {
|
||||||
|
@ -28,6 +30,7 @@ export interface ISpeakerStatsState {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
pendingReorder: boolean;
|
pendingReorder: boolean;
|
||||||
showFaceExpressions: boolean;
|
showFaceExpressions: boolean;
|
||||||
|
sortedSpeakerStatsIds: Array<string>;
|
||||||
stats: Object;
|
stats: Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +43,8 @@ ReducerRegistry.register<ISpeakerStatsState>('features/speaker-stats',
|
||||||
return _updateStats(state, action);
|
return _updateStats(state, action);
|
||||||
case INIT_REORDER_STATS:
|
case INIT_REORDER_STATS:
|
||||||
return _initReorderStats(state);
|
return _initReorderStats(state);
|
||||||
|
case UPDATE_SORTED_SPEAKER_STATS_IDS:
|
||||||
|
return _updateSortedSpeakerStats(state, action);
|
||||||
case RESET_SEARCH_CRITERIA:
|
case RESET_SEARCH_CRITERIA:
|
||||||
return _updateCriteria(state, { criteria: null });
|
return _updateCriteria(state, { criteria: null });
|
||||||
case TOGGLE_FACE_EXPRESSIONS: {
|
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
|
* Reduces a specific Redux action UPDATE_STATS of the feature speaker-stats.
|
||||||
* 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 {Object} state - The Redux state of the feature speaker-stats.
|
||||||
* @param {Action} action - The Redux action UPDATE_STATS to reduce.
|
* @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.
|
* @returns {Object} - The new state after the reduction of the specified action.
|
||||||
*/
|
*/
|
||||||
function _updateStats(state: ISpeakerStatsState, { stats }: { stats: any; }) {
|
function _updateStats(state: ISpeakerStatsState, { stats }: { stats: any; }) {
|
||||||
const finalStats = state.pendingReorder ? stats : state.stats;
|
return {
|
||||||
|
...state,
|
||||||
|
stats
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!state.pendingReorder) {
|
/**
|
||||||
// Avoid reordering the speaker stats object properties
|
* Reduces a specific Redux action UPDATE_SORTED_SPEAKER_STATS_IDS of the feature speaker-stats.
|
||||||
const finalKeys = Object.keys(stats);
|
*
|
||||||
|
* @param {Object} state - The Redux state of the feature speaker-stats.
|
||||||
finalKeys.forEach(newStatId => {
|
* @param {Action} action - The Redux action UPDATE_SORTED_SPEAKER_STATS_IDS to reduce.
|
||||||
finalStats[newStatId] = _.clone(stats[newStatId]);
|
* @private
|
||||||
});
|
* @returns {Object} The new state after the reduction of the specified action.
|
||||||
|
*/
|
||||||
Object.keys(finalStats).forEach(key => {
|
function _updateSortedSpeakerStats(state: ISpeakerStatsState, { participantIds }: { participantIds: Array<string>; }) {
|
||||||
if (!finalKeys.includes(key)) {
|
return {
|
||||||
delete finalStats[key];
|
...state,
|
||||||
}
|
sortedSpeakerStatsIds: participantIds,
|
||||||
});
|
pendingReorder: false
|
||||||
}
|
};
|
||||||
|
|
||||||
return _.assign(
|
|
||||||
{},
|
|
||||||
state,
|
|
||||||
{
|
|
||||||
stats: { ...finalStats },
|
|
||||||
pendingReorder: false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue