diff --git a/react/features/app/types.ts b/react/features/app/types.ts index da7ca2f595..6e35279870 100644 --- a/react/features/app/types.ts +++ b/react/features/app/types.ts @@ -15,6 +15,7 @@ import { ILibJitsiMeetState } from '../base/lib-jitsi-meet/reducer'; import { ILoggingState } from '../base/logging/reducer'; import { IMediaState } from '../base/media/reducer'; import { INetInfoState } from '../base/net-info/reducer'; +import { IParticipantsState } from '../base/participants/reducer'; import { IResponsiveUIState } from '../base/responsive-ui/reducer'; import { ISettingsState } from '../base/settings/reducer'; import { ISoundsState } from '../base/sounds/reducer'; @@ -48,6 +49,7 @@ export interface IState { 'features/base/media': IMediaState, 'features/base/net-info': INetInfoState, 'features/base/no-src-data': INoSrcDataState, + 'features/base/participants': IParticipantsState, 'features/base/responsive-ui': IResponsiveUIState, 'features/base/settings': ISettingsState, 'features/base/sounds': ISoundsState, diff --git a/react/features/base/participants/constants.js b/react/features/base/participants/constants.ts similarity index 97% rename from react/features/base/participants/constants.js rename to react/features/base/participants/constants.ts index 79746602b3..d50a931435 100644 --- a/react/features/base/participants/constants.js +++ b/react/features/base/participants/constants.ts @@ -1,6 +1,4 @@ -// @flow - -import { IconPhone } from '../icons'; +import { IconPhone } from '../icons/svg/index'; /** * The relative path to the default/stock avatar (image) file used on both diff --git a/react/features/base/participants/reducer.js b/react/features/base/participants/reducer.ts similarity index 85% rename from react/features/base/participants/reducer.js rename to react/features/base/participants/reducer.ts index 12a66b26b4..93cbd80cb4 100644 --- a/react/features/base/participants/reducer.js +++ b/react/features/base/participants/reducer.ts @@ -1,9 +1,9 @@ -// @flow - +/* eslint-disable lines-around-comment */ import { SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED } from '../../video-layout/actionTypes'; -import { ReducerRegistry, set } from '../redux'; +import ReducerRegistry from '../redux/ReducerRegistry'; +import { set } from '../redux/functions'; import { DOMINANT_SPEAKER_CHANGED, @@ -18,6 +18,7 @@ import { SET_LOADABLE_AVATAR_URL } from './actionTypes'; import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants'; +// @ts-ignore import { isParticipantModerator } from './functions'; /** @@ -37,6 +38,43 @@ import { isParticipantModerator } from './functions'; * @property {string} email - Participant email. */ +interface Participant { + avatarURL?: string; + botType?: string; + conference?: Object; + connectionStatus?: string; + dominantSpeaker?: boolean; + e2eeSupported?: boolean; + email?: string; + features?: { + 'screen-sharing'?: boolean; + }; + id: string; + isFakeParticipant?: boolean; + isJigasi?: boolean; + isLocalScreenShare?: boolean; + isReplacing?: number; + isVirtualScreenshareParticipant?: boolean; + loadableAvatarUrl?: string; + loadableAvatarUrlUseCORS?: boolean; + local?: boolean; + name: string; + pinned?: boolean; + presence?: string; + role?: string; + supportsRemoteControl?: boolean; +} + +interface LocalParticipant extends Participant { + audioOutputDeviceId?: string; + cameraDeviceId?: string; + micDeviceId?: string; + startWithAudioMuted?: boolean; + startWithVideoMuted?: boolean; + userSelectedMicDeviceId?: string; + userSelectedMicDeviceLabel?: string; +} + /** * The participant properties which cannot be updated through * {@link PARTICIPANT_UPDATED}. They either identify the participant or can only @@ -74,6 +112,23 @@ const DEFAULT_STATE = { speakersList: new Map() }; +export interface IParticipantsState { + dominantSpeaker?: string; + everyoneIsModerator: boolean; + fakeParticipants: Map; + haveParticipantWithScreenSharingFeature: boolean; + local?: LocalParticipant; + localScreenShare?: Participant; + overwrittenNameList: Object; + pinnedParticipant?: string; + raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number;}>; + remote: Map; + sortedRemoteParticipants: Map; + sortedRemoteScreenshares: Map; + sortedRemoteVirtualScreenshareParticipants: Map; + speakersList: Map; +} + /** * Listen for actions which add, remove, or update the set of participants in * the conference. @@ -85,7 +140,7 @@ const DEFAULT_STATE = { * added/removed/modified. * @returns {Participant[]} */ -ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, action) => { +ReducerRegistry.register('features/base/participants', (state: IParticipantsState = DEFAULT_STATE, action) => { switch (action.type) { case PARTICIPANT_ID_CHANGED: { const { local } = state; @@ -111,7 +166,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a const { id, previousSpeakers = [] } = participant; const { dominantSpeaker, local } = state; const newSpeakers = [ id, ...previousSpeakers ]; - const sortedSpeakersList = []; + const sortedSpeakersList: Array> = []; for (const speaker of newSpeakers) { if (speaker !== local?.id) { @@ -119,7 +174,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a remoteParticipant && sortedSpeakersList.push( - [ speaker, _getDisplayName(state, remoteParticipant.name) ] + [ speaker, _getDisplayName(state, remoteParticipant?.name) ] ); } } @@ -135,7 +190,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) { return { ...state, - dominantSpeaker: id, + dominantSpeaker: id, // @ts-ignore speakersList: new Map(sortedSpeakersList) }; } @@ -179,7 +234,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a id = LOCAL_PARTICIPANT_DEFAULT_ID; } - let newParticipant; + let newParticipant: Participant|null = null; if (state.remote.has(id)) { newParticipant = _participant(state.remote.get(id), action); @@ -352,7 +407,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a if (String(localFeatures['screen-sharing']) !== 'true') { state.haveParticipantWithScreenSharingFeature = false; - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const [ key, participant ] of state.remote) { const { features: f = {} } = participant; @@ -362,8 +417,6 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a } } } - - } if (dominantSpeaker === id) { @@ -411,6 +464,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a // Keep the remote screen share list sorted alphabetically. sortedSharesList.length && sortedSharesList.sort((a, b) => a[1].localeCompare(b[1])); + // @ts-ignore state.sortedRemoteScreenshares = new Map(sortedSharesList); return { ...state }; @@ -438,7 +492,8 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a * @param {string} name - The display name of the participant. * @returns {string} */ -function _getDisplayName(state: Object, name: string): string { +function _getDisplayName(state: Object, name?: string): string { + // @ts-ignore const config = state['features/base/config']; return name ?? (config?.defaultRemoteDisplayName || 'Fellow Jitster'); @@ -450,9 +505,9 @@ function _getDisplayName(state: Object, name: string): string { * @param {Object} state - The local participant redux state. * @returns {boolean} */ -function _isEveryoneModerator(state) { +function _isEveryoneModerator(state: IParticipantsState) { if (isParticipantModerator(state.local)) { - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const [ k, p ] of state.remote) { if (!isParticipantModerator(p)) { return false; @@ -477,7 +532,9 @@ function _isEveryoneModerator(state) { * @private * @returns {Participant} */ -function _participant(state: Object = {}, action) { +function _participant(state: Participant|LocalParticipant = { + id: '', + name: '' }, action: any): Participant|LocalParticipant { switch (action.type) { case SET_LOADABLE_AVATAR_URL: case PARTICIPANT_UPDATED: { @@ -489,6 +546,7 @@ function _participant(state: Object = {}, action) { if (participant.hasOwnProperty(key) && PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE.indexOf(key) === -1) { + // @ts-ignore newState[key] = participant[key]; } } @@ -512,7 +570,7 @@ function _participant(state: Object = {}, action) { * base/participants after the reduction of the specified * {@code action}. */ -function _participantJoined({ participant }) { +function _participantJoined({ participant }: {participant: Participant}) { const { avatarURL, botType, @@ -576,22 +634,25 @@ function _participantJoined({ participant }) { * @param {*} value - The new value. * @returns {boolean} - True if a participant was updated and false otherwise. */ -function _updateParticipantProperty(state, id, property, value) { +function _updateParticipantProperty(state: IParticipantsState, id: string, property: string, value: boolean) { const { remote, local, localScreenShare } = state; if (remote.has(id)) { - remote.set(id, set(remote.get(id), property, value)); + remote.set(id, set(remote.get(id) ?? { + id: '', + name: '' + }, property as keyof Participant, value)); return true; } else if (local?.id === id || local?.id === 'local') { // The local participant's ID can chance from something to "local" when // not in a conference. - state.local = set(local, property, value); + state.local = set(local, property as keyof LocalParticipant, value); return true; } else if (localScreenShare?.id === id) { - state.localScreenShare = set(localScreenShare, property, value); + state.localScreenShare = set(localScreenShare, property as keyof Participant, value); return true; }