fix(filmstrip): Move thumbnails reordering behind a config.js flag.

enableThumbnailReordering flag (enabled by default) will be used to check if the thumbnails needs to be reodred in the UI.
This commit is contained in:
Jaya Allamsetty 2021-08-19 17:56:45 -04:00 committed by Jaya Allamsetty
parent 751d9a9b8e
commit 7827c3d1ad
8 changed files with 147 additions and 77 deletions

View File

@ -61,7 +61,7 @@ const DEFAULT_STATE = {
pinnedParticipant: undefined,
remote: new Map(),
sortedRemoteParticipants: new Map(),
speakersList: []
speakersList: new Map()
};
/**
@ -96,12 +96,28 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
case DOMINANT_SPEAKER_CHANGED: {
const { participant } = action;
const { id, previousSpeakers = [] } = participant;
const { dominantSpeaker, local } = state;
const speakersList = [];
const { dominantSpeaker, local, speakersList } = state;
const newSpeakers = [ id, ...previousSpeakers ];
const sortedSpeakersList = Array.from(speakersList);
// Update the speakers list.
id !== local?.id && speakersList.push(id);
speakersList.push(...previousSpeakers.filter(p => p !== local?.id));
for (const speaker of newSpeakers) {
if (!state.speakersList.has(speaker) && speaker !== local?.id) {
const remoteParticipant = state.remote.get(speaker);
remoteParticipant && sortedSpeakersList.push([ speaker, _getDisplayName(remoteParticipant.name) ]);
}
}
// Also check if any of the existing speakers have been kicked off the list.
for (const existingSpeaker of sortedSpeakersList.keys()) {
if (!newSpeakers.find(s => s === existingSpeaker)) {
sortedSpeakersList.filter(sortedSpeaker => sortedSpeaker[0] !== existingSpeaker);
}
}
// Keep the remote speaker list sorted alphabetically.
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
// Only one dominant speaker is allowed.
if (dominantSpeaker) {
@ -112,7 +128,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
return {
...state,
dominantSpeaker: id,
speakersList
speakersList: new Map(sortedSpeakersList)
};
}
@ -227,8 +243,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
state.remote.set(id, participant);
// Insert the new participant.
const displayName = name
?? (typeof interfaceConfig === 'object' ? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME : 'Fellow Jitser');
const displayName = _getDisplayName(name);
const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);
sortedRemoteParticipants.push([ id, displayName ]);
@ -297,7 +312,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
}
// Remove the participant from the list of speakers.
state.speakersList = state.speakersList.filter(speaker => speaker !== id);
state.speakersList.has(id) && state.speakersList.delete(id);
if (pinnedParticipant === id) {
state.pinnedParticipant = undefined;
@ -314,6 +329,17 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
return state;
});
/**
* Returns the participant's display name, default string if display name is not set on the participant.
*
* @param {string} name - The display name of the participant.
* @returns {string}
*/
function _getDisplayName(name) {
return name
?? (typeof interfaceConfig === 'object' ? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME : 'Fellow Jitser');
}
/**
* Loops trough the participants in the state in order to check if all participants are moderators.
*
@ -335,32 +361,6 @@ function _isEveryoneModerator(state) {
return false;
}
/**
* Updates a specific property for a participant.
*
* @param {State} state - The redux state.
* @param {string} id - The ID of the participant.
* @param {string} property - The property to update.
* @param {*} value - The new value.
* @returns {boolean} - True if a participant was updated and false otherwise.
*/
function _updateParticipantProperty(state, id, property, value) {
const { remote, local } = state;
if (remote.has(id)) {
remote.set(id, set(remote.get(id), property, value));
return true;
} else if (local?.id === id) {
state.local = set(local, property, value);
return true;
}
return false;
}
/**
* Reducer function for a single participant.
*
@ -458,3 +458,28 @@ function _participantJoined({ participant }) {
role: role || PARTICIPANT_ROLE.NONE
};
}
/**
* Updates a specific property for a participant.
*
* @param {State} state - The redux state.
* @param {string} id - The ID of the participant.
* @param {string} property - The property to update.
* @param {*} value - The new value.
* @returns {boolean} - True if a participant was updated and false otherwise.
*/
function _updateParticipantProperty(state, id, property, value) {
const { remote, local } = state;
if (remote.has(id)) {
remote.set(id, set(remote.get(id), property, value));
return true;
} else if (local?.id === id) {
state.local = set(local, property, value);
return true;
}
return false;
}

View File

@ -132,7 +132,7 @@ export function clickOnVideo(n: number) {
const state = getState();
const { id: localId } = getLocalParticipant(state);
// Use the reordered list of participants.
// Use the list that correctly represents the current order of the participants as visible in the UI.
const { remoteParticipants } = state['features/filmstrip'];
const participants = [ localId, ...remoteParticipants ];
const { id, pinned } = getParticipantById(state, participants[n]);

View File

@ -94,6 +94,11 @@ type Props = {
*/
_thumbnailWidth: number,
/**
* Flag that indicates whether the thumbnails will be reordered.
*/
_thumbnailsReordered: Boolean,
/**
* Additional CSS class names to add to the container of all the thumbnails.
*/
@ -222,6 +227,33 @@ class Filmstrip extends PureComponent <Props> {
);
}
/**
* Calculates the start and stop indices based on whether the thumbnails need to be reordered in the filmstrip.
*
* @param {number} startIndex - The start index.
* @param {number} stopIndex - The stop index.
* @returns {Object}
*/
_calculateIndices(startIndex, stopIndex) {
const { _currentLayout, _thumbnailsReordered } = this.props;
let start = startIndex;
let stop = stopIndex;
if (_thumbnailsReordered) {
// In tile view, the start index needs to be offset by 1 because the first thumbnail is that of the local
// endpoint. The remote participants start from index 1.
if (_currentLayout === LAYOUTS.TILE_VIEW) {
start = startIndex > 0 ? startIndex - 1 : 0;
stop = stopIndex - 1;
}
}
return {
startIndex: start,
stopIndex: stop
};
}
_onTabIn: () => void;
/**
@ -262,18 +294,22 @@ class Filmstrip extends PureComponent <Props> {
* @returns {string} - The key.
*/
_gridItemKey({ columnIndex, rowIndex }) {
const { _columns, _remoteParticipants, _remoteParticipantsLength } = this.props;
const { _columns, _remoteParticipants, _remoteParticipantsLength, _thumbnailsReordered } = this.props;
const index = (rowIndex * _columns) + columnIndex;
// When the thumbnails are reordered, local participant is inserted at index 0.
const localIndex = _thumbnailsReordered ? 0 : _remoteParticipantsLength;
const remoteIndex = _thumbnailsReordered ? index - 1 : index;
if (index > _remoteParticipantsLength) {
return `empty-${index}`;
}
if (index === 0) {
if (index === localIndex) {
return 'local';
}
return _remoteParticipants[index - 1];
return _remoteParticipants[remoteIndex];
}
_onListItemsRendered: Object => void;
@ -286,8 +322,9 @@ class Filmstrip extends PureComponent <Props> {
*/
_onListItemsRendered({ visibleStartIndex, visibleStopIndex }) {
const { dispatch } = this.props;
const { startIndex, stopIndex } = this._calculateIndices(visibleStartIndex, visibleStopIndex);
dispatch(setVisibleRemoteParticipants(visibleStartIndex, visibleStopIndex + 1));
dispatch(setVisibleRemoteParticipants(startIndex, stopIndex));
}
_onGridItemsRendered: Object => void;
@ -305,13 +342,11 @@ class Filmstrip extends PureComponent <Props> {
visibleRowStopIndex
}) {
const { _columns, dispatch } = this.props;
let startIndex = (visibleRowStartIndex * _columns) + visibleColumnStartIndex;
const endIndex = (visibleRowStopIndex * _columns) + visibleColumnStopIndex;
const start = (visibleRowStartIndex * _columns) + visibleColumnStartIndex;
const stop = (visibleRowStopIndex * _columns) + visibleColumnStopIndex;
const { startIndex, stopIndex } = this._calculateIndices(start, stop);
// In tile view, the start index needs to be offset by 1 because the first participant is the local
// participant.
startIndex = startIndex > 0 ? startIndex - 1 : 0;
dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
dispatch(setVisibleRemoteParticipants(startIndex, stopIndex));
}
/**
@ -493,6 +528,7 @@ class Filmstrip extends PureComponent <Props> {
*/
function _mapStateToProps(state) {
const toolbarButtons = getToolbarButtons(state);
const { enableThumbnailReordering = true } = state['features/base/config'];
const { visible, remoteParticipants } = state['features/filmstrip'];
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
@ -565,6 +601,7 @@ function _mapStateToProps(state) {
_rows: gridDimensions.rows,
_thumbnailWidth: _thumbnailSize?.width,
_thumbnailHeight: _thumbnailSize?.height,
_thumbnailsReordered: enableThumbnailReordering,
_videosClassName: videosClassName,
_visible: visible,
_isToolboxVisible: isToolboxVisible(state)

View File

@ -104,6 +104,7 @@ function _mapStateToProps(state, ownProps) {
const _currentLayout = getCurrentLayout(state);
const { remoteParticipants } = state['features/filmstrip'];
const remoteParticipantsLength = remoteParticipants.length;
const { enableThumbnailReordering = true } = state['features/base/config'];
if (_currentLayout === LAYOUTS.TILE_VIEW) {
const { columnIndex, rowIndex } = ownProps;
@ -126,8 +127,11 @@ function _mapStateToProps(state, ownProps) {
return {};
}
// Make the local participant as the first thumbnail (top left corner) in tile view.
if (index === 0) {
// When the thumbnails are reordered, local participant is inserted at index 0.
const localIndex = enableThumbnailReordering ? 0 : remoteParticipantsLength;
const remoteIndex = enableThumbnailReordering ? index - 1 : index;
if (index === localIndex) {
return {
_participantID: 'local',
_horizontalOffset: horizontalOffset
@ -135,7 +139,7 @@ function _mapStateToProps(state, ownProps) {
}
return {
_participantID: remoteParticipants[index - 1],
_participantID: remoteParticipants[remoteIndex],
_horizontalOffset: horizontalOffset
};
}

View File

@ -6,31 +6,52 @@ import { setRemoteParticipants } from './actions';
* Computes the reorderd list of the remote participants.
*
* @param {*} store - The redux store.
* @param {string} participantId - The endpoint id of the participant that joined the call.
* @returns {void}
* @private
*/
export function updateRemoteParticipants(store: Object) {
export function updateRemoteParticipants(store: Object, participantId: ?number) {
const state = store.getState();
const { enableThumbnailReordering = true } = state['features/base/config'];
let reorderedParticipants = [];
if (!enableThumbnailReordering) {
if (participantId) {
const { remoteParticipants } = state['features/filmstrip'];
reorderedParticipants = [ ...remoteParticipants, participantId ];
store.dispatch(setRemoteParticipants(reorderedParticipants));
}
return;
}
const { fakeParticipants, sortedRemoteParticipants, speakersList } = state['features/base/participants'];
const { remoteScreenShares } = state['features/video-layout'];
const screenShares = (remoteScreenShares || []).slice();
let speakers = (speakersList || []).slice();
const speakers = new Map(speakersList);
const remoteParticipants = new Map(sortedRemoteParticipants);
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
for (const screenshare of screenShares) {
remoteParticipants.delete(screenshare);
speakers = speakers.filter(speaker => speaker !== screenshare);
speakers.delete(screenshare);
}
for (const sharedVideo of sharedVideos) {
remoteParticipants.delete(sharedVideo);
speakers = speakers.filter(speaker => speaker !== sharedVideo);
speakers.delete(sharedVideo);
}
for (const speaker of speakers) {
for (const speaker of speakers.keys()) {
remoteParticipants.delete(speaker);
}
const reorderedParticipants
= [ ...screenShares.reverse(), ...sharedVideos, ...speakers, ...Array.from(remoteParticipants.keys()) ];
// Always update the order of the thumnails.
reorderedParticipants = [
...screenShares.reverse(),
...sharedVideos,
...Array.from(speakers.keys()),
...Array.from(remoteParticipants.keys())
];
store.dispatch(setRemoteParticipants(reorderedParticipants));
}

View File

@ -47,7 +47,7 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case PARTICIPANT_JOINED: {
updateRemoteParticipants(store);
updateRemoteParticipants(store, action.participant?.id);
break;
}
case PARTICIPANT_LEFT: {

View File

@ -155,7 +155,8 @@ ReducerRegistry.register(
...state,
visibleParticipantsStartIndex: action.startIndex,
visibleParticipantsEndIndex: action.endIndex,
visibleRemoteParticipants: new Set(state.remoteParticipants.slice(action.startIndex, action.endIndex))
visibleRemoteParticipants:
new Set(state.remoteParticipants.slice(action.startIndex, action.endIndex + 1))
};
}
case PARTICIPANT_LEFT: {

View File

@ -17,22 +17,4 @@ StateListenerRegistry.register(
*/
StateListenerRegistry.register(
/* selector */ state => state['features/base/participants'].dominantSpeaker,
/* listener */ (dominantSpeaker, store) => _reorderDominantSpeakers(store));
/**
* Private helper function that reorders the remote participants based on dominant speaker changes.
*
* @param {*} store - The redux store.
* @returns {void}
* @private
*/
function _reorderDominantSpeakers(store) {
const state = store.getState();
const { dominantSpeaker, local } = state['features/base/participants'];
const { visibleRemoteParticipants } = state['features/filmstrip'];
// Reorder the participants if the new dominant speaker is currently not visible.
if (dominantSpeaker !== local?.id && !visibleRemoteParticipants.has(dominantSpeaker)) {
updateRemoteParticipants(store);
}
}
/* listener */ (dominantSpeaker, store) => updateRemoteParticipants(store));