ref(TS) Convert some features to TS (#12149)

Convert AV Moderation and Breakout Rooms to TS
This commit is contained in:
Robert Pintilii 2022-09-14 14:32:58 +03:00 committed by GitHub
parent 95084e1004
commit a1d20dc188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 216 additions and 208 deletions

View File

@ -55,6 +55,7 @@ import { INoiseDetectionState } from '../noise-detection/reducer';
import { INoiseSuppressionState } from '../noise-suppression/reducer';
import { INotificationsState } from '../notifications/reducer';
import { IOverlayState } from '../overlay/reducer';
import { IParticipantsPaneState } from '../participants-pane/reducer';
import { IPollsState } from '../polls/reducer';
import { IPowerMonitorState } from '../power-monitor/reducer';
import { IPrejoinState } from '../prejoin/reducer';
@ -135,7 +136,7 @@ export interface IState {
'features/noise-suppression': INoiseSuppressionState;
'features/notifications': INotificationsState;
'features/overlay': IOverlayState;
'features/participants-pane': IParticipantsState;
'features/participants-pane': IParticipantsPaneState;
'features/polls': IPollsState;
'features/power-monitor': IPowerMonitorState;
'features/prejoin': IPrejoinState;

View File

@ -1,8 +1,10 @@
// @flow
import { IStore } from '../app/types';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { getConferenceState } from '../base/conference';
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
import { getParticipantById, isParticipantModerator } from '../base/participants';
import { getParticipantById, isParticipantModerator } from '../base/participants/functions';
import { Participant } from '../base/participants/types';
import { isForceMuted } from '../participants-pane/functions';
import {
@ -28,7 +30,7 @@ import { isEnabledFromState } from './functions';
* @param {staring} id - The id of the participant to be approved.
* @returns {void}
*/
export const approveParticipantAudio = (id: string) => (dispatch: Function, getState: Function) => {
export const approveParticipantAudio = (id: string) => (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { conference } = getConferenceState(state);
const participant = getParticipantById(state, id);
@ -48,7 +50,7 @@ export const approveParticipantAudio = (id: string) => (dispatch: Function, getS
* @param {staring} id - The id of the participant to be approved.
* @returns {void}
*/
export const approveParticipantVideo = (id: string) => (dispatch: Function, getState: Function) => {
export const approveParticipantVideo = (id: string) => (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { conference } = getConferenceState(state);
const participant = getParticipantById(state, id);
@ -67,7 +69,7 @@ export const approveParticipantVideo = (id: string) => (dispatch: Function, getS
* @param {staring} id - The id of the participant to be approved.
* @returns {void}
*/
export const approveParticipant = (id: string) => (dispatch: Function) => {
export const approveParticipant = (id: string) => (dispatch: IStore['dispatch']) => {
dispatch(approveParticipantAudio(id));
dispatch(approveParticipantVideo(id));
};
@ -78,7 +80,7 @@ export const approveParticipant = (id: string) => (dispatch: Function) => {
* @param {staring} id - The id of the participant to be rejected.
* @returns {void}
*/
export const rejectParticipantAudio = (id: string) => (dispatch: Function, getState: Function) => {
export const rejectParticipantAudio = (id: string) => (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { conference } = getConferenceState(state);
const audioModeration = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
@ -98,7 +100,7 @@ export const rejectParticipantAudio = (id: string) => (dispatch: Function, getSt
* @param {staring} id - The id of the participant to be rejected.
* @returns {void}
*/
export const rejectParticipantVideo = (id: string) => (dispatch: Function, getState: Function) => {
export const rejectParticipantVideo = (id: string) => (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { conference } = getConferenceState(state);
const videoModeration = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
@ -133,10 +135,10 @@ export const disableModeration = (mediaType: MediaType, actor: Object) => {
/**
* Hides the notification with the participant that asked to unmute audio.
*
* @param {Object} participant - The participant for which the notification to be hidden.
* @param {Participant} participant - The participant for which the notification to be hidden.
* @returns {Object}
*/
export function dismissPendingAudioParticipant(participant: Object) {
export function dismissPendingAudioParticipant(participant: Participant) {
return dismissPendingParticipant(participant.id, MEDIA_TYPE.AUDIO);
}
@ -270,10 +272,10 @@ export function showModeratedNotification(mediaType: MediaType) {
/**
* Shows a notification with the participant that asked to audio unmute.
*
* @param {Object} participant - The participant for which is the notification.
* @param {Participant} participant - The participant for which is the notification.
* @returns {Object}
*/
export function participantPendingAudio(participant: Object) {
export function participantPendingAudio(participant: Participant) {
return {
type: PARTICIPANT_PENDING_AUDIO,
participant

View File

@ -1,7 +1,7 @@
// @flow
import { IState } from '../app/types';
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
import { isLocalParticipantModerator } from '../base/participants/functions';
import { Participant } from '../base/participants/types';
import { isInBreakoutRoom } from '../breakout-rooms/functions';
import { MEDIA_TYPE_TO_WHITELIST_STORE_KEY, MEDIA_TYPE_TO_PENDING_STORE_KEY } from './constants';
@ -9,27 +9,27 @@ import { MEDIA_TYPE_TO_WHITELIST_STORE_KEY, MEDIA_TYPE_TO_PENDING_STORE_KEY } fr
/**
* Returns this feature's root state.
*
* @param {Object} state - Global state.
* @param {IState} state - Global state.
* @returns {Object} Feature state.
*/
const getState = state => state['features/av-moderation'];
const getState = (state: IState) => state['features/av-moderation'];
/**
* We use to construct once the empty array so we can keep the same instance between calls
* of getParticipantsAskingToAudioUnmute.
*
* @type {*[]}
* @type {any[]}
*/
const EMPTY_ARRAY = [];
const EMPTY_ARRAY: any[] = [];
/**
* Returns whether moderation is enabled per media type.
*
* @param {MEDIA_TYPE} mediaType - The media type to check.
* @param {Object} state - Global state.
* @returns {null|boolean|*}
* @param {IState} state - Global state.
* @returns {boolean}
*/
export const isEnabledFromState = (mediaType: MediaType, state: Object) =>
export const isEnabledFromState = (mediaType: MediaType, state: IState) =>
(mediaType === MEDIA_TYPE.AUDIO
? getState(state)?.audioModerationEnabled
: getState(state)?.videoModerationEnabled) === true;
@ -38,16 +38,16 @@ export const isEnabledFromState = (mediaType: MediaType, state: Object) =>
* Returns whether moderation is enabled per media type.
*
* @param {MEDIA_TYPE} mediaType - The media type to check.
* @returns {null|boolean|*}
* @returns {boolean}
*/
export const isEnabled = (mediaType: MediaType) => (state: Object) => isEnabledFromState(mediaType, state);
export const isEnabled = (mediaType: MediaType) => (state: IState) => isEnabledFromState(mediaType, state);
/**
* Returns whether moderation is supported by the backend.
*
* @returns {null|boolean}
* @returns {boolean}
*/
export const isSupported = () => (state: Object) => {
export const isSupported = () => (state: IState) => {
const { conference } = state['features/base/conference'];
return Boolean(!isInBreakoutRoom(state) && conference?.isAVModerationSupported());
@ -57,10 +57,10 @@ export const isSupported = () => (state: Object) => {
* Returns whether local participant is approved to unmute a media type.
*
* @param {MEDIA_TYPE} mediaType - The media type to check.
* @param {Object} state - Global state.
* @param {IState} state - Global state.
* @returns {boolean}
*/
export const isLocalParticipantApprovedFromState = (mediaType: MediaType, state: Object) => {
export const isLocalParticipantApprovedFromState = (mediaType: MediaType, state: IState) => {
const approved = (mediaType === MEDIA_TYPE.AUDIO
? getState(state).audioUnmuteApproved
: getState(state).videoUnmuteApproved) === true;
@ -72,10 +72,10 @@ export const isLocalParticipantApprovedFromState = (mediaType: MediaType, state:
* Returns whether local participant is approved to unmute a media type.
*
* @param {MEDIA_TYPE} mediaType - The media type to check.
* @returns {null|boolean|*}
* @returns {boolean}
*/
export const isLocalParticipantApproved = (mediaType: MediaType) =>
(state: Object) =>
(state: IState) =>
isLocalParticipantApprovedFromState(mediaType, state);
/**
@ -85,10 +85,13 @@ export const isLocalParticipantApproved = (mediaType: MediaType) =>
* @param {MEDIA_TYPE} mediaType - The media type to check.
* @returns {boolean}
*/
export const isParticipantApproved = (id: string, mediaType: MediaType) => (state: Object) => {
export const isParticipantApproved = (id: string, mediaType: MediaType) => (state: IState) => {
const storeKey = MEDIA_TYPE_TO_WHITELIST_STORE_KEY[mediaType];
return Boolean(getState(state)[storeKey][id]);
const avModerationState = getState(state);
const stateForMediaType = avModerationState[storeKey as keyof typeof avModerationState];
return Boolean(stateForMediaType && stateForMediaType[id as keyof typeof stateForMediaType]);
};
/**
@ -98,7 +101,7 @@ export const isParticipantApproved = (id: string, mediaType: MediaType) => (stat
* @param {MEDIA_TYPE} mediaType - The media type to check.
* @returns {boolean}
*/
export const isParticipantPending = (participant: Object, mediaType: MediaType) => (state: Object) => {
export const isParticipantPending = (participant: Participant, mediaType: MediaType) => (state: IState) => {
const storeKey = MEDIA_TYPE_TO_PENDING_STORE_KEY[mediaType];
const arr = getState(state)[storeKey];
@ -112,7 +115,7 @@ export const isParticipantPending = (participant: Object, mediaType: MediaType)
* @param {Object} state - The global state.
* @returns {Array<Object>}
*/
export const getParticipantsAskingToAudioUnmute = (state: Object) => {
export const getParticipantsAskingToAudioUnmute = (state: IState) => {
if (isLocalParticipantModerator(state)) {
return getState(state).pendingAudio;
}
@ -128,6 +131,6 @@ export const getParticipantsAskingToAudioUnmute = (state: Object) => {
* @param {Object} state - The global state.
* @returns {boolean}
*/
export const shouldShowModeratedNotification = (mediaType: MediaType, state: Object) =>
export const shouldShowModeratedNotification = (mediaType: MediaType, state: IState) =>
isEnabledFromState(mediaType, state)
&& !isLocalParticipantApprovedFromState(mediaType, state);

View File

@ -1,26 +1,28 @@
// @flow
/* eslint-disable lines-around-comment */
import { batch } from 'react-redux';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
// @ts-ignore
import { getConferenceState } from '../base/conference';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../base/media';
import { MediaType, MEDIA_TYPE } from '../base/media/constants';
import { PARTICIPANT_UPDATED } from '../base/participants/actionTypes';
import { raiseHand } from '../base/participants/actions';
import {
getLocalParticipant,
getRemoteParticipants,
hasRaisedHand,
isLocalParticipantModerator,
isParticipantModerator,
PARTICIPANT_UPDATED,
raiseHand
} from '../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
isParticipantModerator
} from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
// @ts-ignore
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import {
NOTIFICATION_TIMEOUT_TYPE,
hideNotification,
showNotification
} from '../notifications';
// @ts-ignore
import { hideNotification, showNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
// @ts-ignore
import { muteLocal } from '../video-menu/actions.any';
import {
@ -61,7 +63,7 @@ import {
} from './functions';
import { ASKED_TO_UNMUTE_FILE } from './sounds';
declare var APP: Object;
declare const APP: any;
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const { type } = action;
@ -79,7 +81,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
case LOCAL_PARTICIPANT_MODERATION_NOTIFICATION: {
let descriptionKey;
let titleKey;
let uid;
let uid: string | undefined;
const localParticipant = getLocalParticipant(getState);
const raisedHand = hasRaisedHand(localParticipant);
@ -149,7 +151,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
isParticipantPending(participant, MEDIA_TYPE.AUDIO)(state)
&& dispatch(dismissPendingAudioParticipant(participant));
}
} else if (participant.id === getLocalParticipant(state).id
} else if (participant.id === getLocalParticipant(state)?.id
&& /* the new role */ isParticipantModerator(participant)) {
// this is the granted moderator case
@ -178,7 +180,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
if (typeof APP !== 'undefined') {
const local = getLocalParticipant(getState());
APP.API.notifyParticipantApproved(local.id, action.mediaType);
APP.API.notifyParticipantApproved(local?.id, action.mediaType);
}
break;
}
@ -192,7 +194,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
if (typeof APP !== 'undefined') {
const local = getLocalParticipant(getState());
APP.API.notifyParticipantRejected(local.id, action.mediaType);
APP.API.notifyParticipantRejected(local?.id, action.mediaType);
}
break;
}
@ -216,7 +218,7 @@ StateListenerRegistry.register(
(conference, { dispatch }, previousConference) => {
if (conference && !previousConference) {
// local participant is allowed to unmute
conference.on(JitsiConferenceEvents.AV_MODERATION_APPROVED, ({ mediaType }) => {
conference.on(JitsiConferenceEvents.AV_MODERATION_APPROVED, ({ mediaType }: { mediaType: MediaType; }) => {
dispatch(localParticipantApproved(mediaType));
// Audio & video moderation are both enabled at the same time.
@ -233,18 +235,20 @@ StateListenerRegistry.register(
}
});
conference.on(JitsiConferenceEvents.AV_MODERATION_REJECTED, ({ mediaType }) => {
conference.on(JitsiConferenceEvents.AV_MODERATION_REJECTED, ({ mediaType }: { mediaType: MediaType; }) => {
dispatch(localParticipantRejected(mediaType));
});
conference.on(JitsiConferenceEvents.AV_MODERATION_CHANGED, ({ enabled, mediaType, actor }) => {
conference.on(JitsiConferenceEvents.AV_MODERATION_CHANGED, ({ enabled, mediaType, actor }: {
actor: Object; enabled: boolean; mediaType: MediaType;
}) => {
enabled ? dispatch(enableModeration(mediaType, actor)) : dispatch(disableModeration(mediaType, actor));
});
// this is received by moderators
conference.on(
JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_APPROVED,
({ participant, mediaType }) => {
({ participant, mediaType }: { mediaType: MediaType; participant: { _id: string; }; }) => {
const { _id: id } = participant;
batch(() => {
@ -259,7 +263,7 @@ StateListenerRegistry.register(
// this is received by moderators
conference.on(
JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_REJECTED,
({ participant, mediaType }) => {
({ participant, mediaType }: { mediaType: MediaType; participant: { _id: string; }; }) => {
const { _id: id } = participant;
dispatch(participantRejected(id, mediaType));

View File

@ -409,7 +409,7 @@ export function getParticipantPresenceStatus(stateful: IStateful, id: string) {
* features/base/participants.
* @returns {Map<string, Object>}
*/
export function getRemoteParticipants(stateful: IStateful) {
export function getRemoteParticipants(stateful: IStateful): Map<string, Participant> {
return toState(stateful)['features/base/participants'].remote;
}
@ -635,7 +635,7 @@ async function _getFirstLoadableAvatarUrl(participant: Participant, store: IStor
* features/base/participants.
* @returns {Array<Object>}
*/
export function getRaiseHandsQueue(stateful: IStateful): Array<Object> {
export function getRaiseHandsQueue(stateful: IStateful): Array<{ id: string; raisedHandTimestamp: number; }> {
const { raisedHandsQueue } = toState(stateful)['features/base/participants'];
return raisedHandsQueue;

View File

@ -80,7 +80,7 @@ export interface IParticipantsState {
fakeParticipants: Map<string, Participant>;
local?: LocalParticipant;
localScreenShare?: Participant;
overwrittenNameList: Object;
overwrittenNameList: { [id: string]: string; };
pinnedParticipant?: string;
raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
remote: Map<string, Participant>;

View File

@ -3,6 +3,7 @@ export interface Participant {
botType?: string;
conference?: Object;
connectionStatus?: string;
displayName?: string;
dominantSpeaker?: boolean;
e2eeSupported?: boolean;
email?: string;

View File

@ -1,5 +1,7 @@
import { applyMiddleware, Middleware } from 'redux';
import { IState } from '../../app/types';
/**
* A registry for Redux middleware, allowing features to register their
* middleware without needing to create additional inter-feature dependencies.
@ -40,7 +42,7 @@ class MiddlewareRegistry {
* @param {Middleware} middleware - A Redux middleware.
* @returns {void}
*/
register(middleware: Middleware<any, any>) {
register(middleware: Middleware<any, IState>) {
this._elements.push(middleware);
}
}

View File

@ -1,5 +1,7 @@
import { Store } from 'redux';
import { IState } from '../../app/types';
import { equals } from './functions';
import logger from './logger';
@ -34,7 +36,7 @@ type Listener
* {@code prevSelection}. The associated {@code Listener} will only be invoked
* if the returned value is other than {@code prevSelection}.
*/
type Selector = (state: Object, prevSelection: any) => any;
type Selector = (state: IState, prevSelection: any) => any;
/**
* Options that can be passed to the register method.

View File

@ -1,34 +1,36 @@
// @flow
/* eslint-disable lines-around-comment */
import i18next from 'i18next';
import _ from 'lodash';
import type { Dispatch } from 'redux';
import { createBreakoutRoomsEvent, sendAnalytics } from '../analytics';
import { createBreakoutRoomsEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
import { IStore } from '../app/types';
import {
CONFERENCE_LEAVE_REASONS,
conferenceLeft,
conferenceWillLeave,
createConference,
getCurrentConference
// @ts-ignore
} from '../base/conference';
import { CONFERENCE_LEAVE_REASONS } from '../base/conference/constants';
import {
MEDIA_TYPE,
setAudioMuted,
setVideoMuted
// @ts-ignore
} from '../base/media';
import { getRemoteParticipants } from '../base/participants';
import { MEDIA_TYPE } from '../base/media/constants';
import { getRemoteParticipants } from '../base/participants/functions';
import {
getLocalTracks,
isLocalCameraTrackMuted,
isLocalTrackMuted
// @ts-ignore
} from '../base/tracks';
// @ts-ignore
import { createDesiredLocalTracks } from '../base/tracks/actions';
import {
NOTIFICATION_TIMEOUT_TYPE,
clearNotifications,
showNotification
} from '../notifications';
// @ts-ignore
import { clearNotifications, showNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { _RESET_BREAKOUT_ROOMS, _UPDATE_ROOM_COUNTER } from './actionTypes';
import { FEATURE_KEY } from './constants';
@ -39,7 +41,7 @@ import {
} from './functions';
import logger from './logger';
declare var APP: Object;
declare let APP: any;
/**
* Action to create a breakout room.
@ -48,7 +50,7 @@ declare var APP: Object;
* @returns {Function}
*/
export function createBreakoutRoom(name?: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
let { roomCounter } = state[FEATURE_KEY];
const subject = name || i18next.t('breakoutRooms.defaultName', { index: ++roomCounter });
@ -60,7 +62,6 @@ export function createBreakoutRoom(name?: string) {
roomCounter
});
// $FlowExpectedError
getCurrentConference(state)?.getBreakoutRooms()
?.createBreakoutRoom(subject);
};
@ -73,7 +74,7 @@ export function createBreakoutRoom(name?: string) {
* @returns {Function}
*/
export function closeBreakoutRoom(roomId: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const rooms = getBreakoutRooms(getState);
const room = rooms[roomId];
const mainRoom = getMainRoom(getState);
@ -82,8 +83,6 @@ export function closeBreakoutRoom(roomId: string) {
if (room && mainRoom) {
Object.values(room.participants).forEach(p => {
// $FlowExpectedError
dispatch(sendParticipantToRoom(p.jid, mainRoom.id));
});
}
@ -97,7 +96,7 @@ export function closeBreakoutRoom(roomId: string) {
* @returns {Function}
*/
export function removeBreakoutRoom(breakoutRoomJid: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
sendAnalytics(createBreakoutRoomsEvent('remove'));
const room = getRoomByJid(getState, breakoutRoomJid);
@ -110,8 +109,6 @@ export function removeBreakoutRoom(breakoutRoomJid: string) {
if (Object.keys(room.participants).length > 0) {
dispatch(closeBreakoutRoom(room.id));
}
// $FlowExpectedError
getCurrentConference(getState)?.getBreakoutRooms()
?.removeBreakoutRoom(breakoutRoomJid);
};
@ -123,9 +120,9 @@ export function removeBreakoutRoom(breakoutRoomJid: string) {
* @returns {Function}
*/
export function autoAssignToBreakoutRooms() {
return (dispatch: Dispatch<any>, getState: Function) => {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const rooms = getBreakoutRooms(getState);
const breakoutRooms = _.filter(rooms, (room: Object) => !room.isMainRoom);
const breakoutRooms = _.filter(rooms, room => !room.isMainRoom);
if (breakoutRooms) {
sendAnalytics(createBreakoutRoomsEvent('auto.assign'));
@ -149,7 +146,7 @@ export function autoAssignToBreakoutRooms() {
* @returns {Function}
*/
export function sendParticipantToRoom(participantId: string, roomId: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const rooms = getBreakoutRooms(getState);
const room = rooms[roomId];
@ -169,7 +166,6 @@ export function sendParticipantToRoom(participantId: string, roomId: string) {
return;
}
// $FlowExpectedError
getCurrentConference(getState)?.getBreakoutRooms()
?.sendParticipantToRoom(participantJid, room.jid);
};
@ -182,14 +178,12 @@ export function sendParticipantToRoom(participantId: string, roomId: string) {
* @returns {Function}
*/
export function moveToRoom(roomId?: string) {
return async (dispatch: Dispatch<any>, getState: Function) => {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const mainRoomId = getMainRoom(getState)?.id;
let _roomId = roomId || mainRoomId;
let _roomId: string | undefined | String = roomId || mainRoomId;
// Check if we got a full JID.
// $FlowExpectedError
if (_roomId?.indexOf('@') !== -1) {
// $FlowExpectedError
if (_roomId && _roomId?.indexOf('@') !== -1) {
const [ id, ...domainParts ] = _roomId.split('@');
// On mobile we first store the room and the connection is created
@ -198,16 +192,14 @@ export function moveToRoom(roomId?: string) {
// eslint-disable-next-line no-new-wrappers
_roomId = new String(id);
// $FlowExpectedError
// @ts-ignore
_roomId.domain = domainParts.join('@');
}
// $FlowExpectedError
const roomIdStr = _roomId?.toString();
const goToMainRoom = roomIdStr === mainRoomId;
const rooms = getBreakoutRooms(getState);
const targetRoom = rooms[roomIdStr];
const targetRoom = rooms[roomIdStr ?? ''];
if (!targetRoom) {
logger.warn(`Unknown room: ${targetRoom}`);
@ -302,7 +294,6 @@ function _findParticipantJid(getState: Function, participantId: string) {
if (!participantId.includes('@')) {
const p = conference.getParticipantById(participantId);
// $FlowExpectedError
_participantId = p?.getJid(); // This will be the room JID.
}
@ -310,11 +301,8 @@ function _findParticipantJid(getState: Function, participantId: string) {
const rooms = getBreakoutRooms(getState);
for (const room of Object.values(rooms)) {
// $FlowExpectedError
const participants = room.participants || {};
const p = participants[_participantId]
// $FlowExpectedError
|| Object.values(participants).find(item => item.jid === _participantId);
if (p) {

View File

@ -1,5 +1,3 @@
// @flow
/**
* Key for this feature.
*/

View File

@ -1,36 +1,38 @@
// @flow
import _ from 'lodash';
import { IStateful } from '../base/app/types';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { getCurrentConference } from '../base/conference';
import { getParticipantById, getParticipantCount, isLocalParticipantModerator } from '../base/participants';
import { toState } from '../base/redux';
import { getParticipantById, getParticipantCount, isLocalParticipantModerator } from '../base/participants/functions';
import { toState } from '../base/redux/functions';
import { FEATURE_KEY } from './constants';
import { IRoom, IRooms } from './types';
/**
* Returns the rooms object for breakout rooms.
*
* @param {Function|Object} stateful - The redux store, the redux
* @param {IStateful} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {Object} Object of rooms.
*/
export const getBreakoutRooms = (stateful: Function | Object) => toState(stateful)[FEATURE_KEY].rooms;
export const getBreakoutRooms = (stateful: IStateful): IRooms => toState(stateful)[FEATURE_KEY].rooms;
/**
* Returns the main room.
*
* @param {Function|Object} stateful - The redux store, the redux
* @param {IStateful} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {Object|undefined} The main room object, or undefined.
* @returns {IRoom|undefined} The main room object, or undefined.
*/
export const getMainRoom = (stateful: Function | Object) => {
export const getMainRoom = (stateful: IStateful) => {
const rooms = getBreakoutRooms(stateful);
return _.find(rooms, (room: Object) => room.isMainRoom);
return _.find(rooms, room => Boolean(room.isMainRoom));
};
export const getRoomsInfo = (stateful: Function | Object) => {
export const getRoomsInfo = (stateful: IStateful) => {
const breakoutRooms = getBreakoutRooms(stateful);
const conference = getCurrentConference(stateful);
@ -46,7 +48,7 @@ export const getRoomsInfo = (stateful: Function | Object) => {
isMainRoom: true,
id: conference?.room?.roomjid,
jid: conference?.room?.myroomjid,
participants: conference && conference.participants && Object.keys(conference.participants).length
participants: conference?.participants && Object.keys(conference.participants).length
? Object.keys(conference.participants).map(participantId => {
const participantItem = conference?.participants[participantId];
const storeParticipant = getParticipantById(stateful, participantItem._id);
@ -96,42 +98,40 @@ export const getRoomsInfo = (stateful: Function | Object) => {
/**
* Returns the room by Jid.
*
* @param {Function|Object} stateful - The redux store, the redux
* @param {IStateful} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @param {string} roomJid - The jid of the room.
* @returns {Object|undefined} The main room object, or undefined.
* @returns {IRoom|undefined} The main room object, or undefined.
*/
export const getRoomByJid = (stateful: Function | Object, roomJid: string): Object => {
export const getRoomByJid = (stateful: IStateful, roomJid: string) => {
const rooms = getBreakoutRooms(stateful);
return _.find(rooms, (room: Object) => room.jid === roomJid);
return _.find(rooms, (room: IRoom) => room.jid === roomJid);
};
/**
* Returns the id of the current room.
*
* @param {Function|Object} stateful - The redux store, the redux
* @param {IStateful} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {string} Room id or undefined.
*/
export const getCurrentRoomId = (stateful: Function | Object) => {
export const getCurrentRoomId = (stateful: IStateful) => {
const conference = getCurrentConference(stateful);
// $FlowExpectedError
return conference?.getName();
};
/**
* Determines whether the local participant is in a breakout room.
*
* @param {Function|Object} stateful - The redux store, the redux
* @param {IStateful} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {boolean}
*/
export const isInBreakoutRoom = (stateful: Function | Object) => {
export const isInBreakoutRoom = (stateful: IStateful) => {
const conference = getCurrentConference(stateful);
// $FlowExpectedError
return conference?.getBreakoutRooms()
?.isBreakoutRoom();
};
@ -139,11 +139,11 @@ export const isInBreakoutRoom = (stateful: Function | Object) => {
/**
* Returns the breakout rooms config.
*
* @param {Function|Object} stateful - The redux store, the redux
* @param {IStateful} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {Object}
*/
export const getBreakoutRoomsConfig = (stateful: Function | Object) => {
export const getBreakoutRoomsConfig = (stateful: IStateful) => {
const state = toState(stateful);
const { breakoutRooms = {} } = state['features/base/config'];
@ -153,10 +153,10 @@ export const getBreakoutRoomsConfig = (stateful: Function | Object) => {
/**
* Returns whether the add breakout room button is visible.
*
* @param {Function | Object} stateful - Global state.
* @param {IStateful} stateful - Global state.
* @returns {boolean}
*/
export const isAddBreakoutRoomButtonVisible = (stateful: Function | Object) => {
export const isAddBreakoutRoomButtonVisible = (stateful: IStateful) => {
const state = toState(stateful);
const isLocalModerator = isLocalParticipantModerator(state);
const { conference } = state['features/base/conference'];
@ -169,10 +169,10 @@ export const isAddBreakoutRoomButtonVisible = (stateful: Function | Object) => {
/**
* Returns whether the auto assign participants to breakout rooms button is visible.
*
* @param {Function | Object} stateful - Global state.
* @param {IStateful} stateful - Global state.
* @returns {boolean}
*/
export const isAutoAssignParticipantsVisible = (stateful: Function | Object) => {
export const isAutoAssignParticipantsVisible = (stateful: IStateful) => {
const state = toState(stateful);
const rooms = getBreakoutRooms(state);
const inBreakoutRoom = isInBreakoutRoom(state);

View File

@ -1,5 +1,3 @@
// @flow
import { getLogger } from '../base/logging/functions';
import { FEATURE_KEY } from './constants';

View File

@ -1,15 +1,18 @@
// @flow
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { getParticipantById } from '../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { editMessage, MESSAGE_TYPE_REMOTE } from '../chat';
import { getParticipantById } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { editMessage } from '../chat';
import { MESSAGE_TYPE_REMOTE } from '../chat/constants';
import { UPDATE_BREAKOUT_ROOMS } from './actionTypes';
import { moveToRoom } from './actions';
import logger from './logger';
import { IRooms } from './types';
declare var APP: Object;
declare const APP: any;
/**
* Registers a change handler for state['features/base/conference'].conference to
@ -19,12 +22,14 @@ StateListenerRegistry.register(
state => state['features/base/conference'].conference,
(conference, { dispatch }, previousConference) => {
if (conference && !previousConference) {
conference.on(JitsiConferenceEvents.BREAKOUT_ROOMS_MOVE_TO_ROOM, roomId => {
conference.on(JitsiConferenceEvents.BREAKOUT_ROOMS_MOVE_TO_ROOM, (roomId: string) => {
logger.debug(`Moving to room: ${roomId}`);
dispatch(moveToRoom(roomId));
});
conference.on(JitsiConferenceEvents.BREAKOUT_ROOMS_UPDATED, ({ rooms, roomCounter }) => {
conference.on(JitsiConferenceEvents.BREAKOUT_ROOMS_UPDATED, ({ rooms, roomCounter }: {
roomCounter: number; rooms: IRooms;
}) => {
logger.debug('Room list updated');
if (typeof APP !== 'undefined') {
APP.API.notifyBreakoutRoomsUpdated(rooms);
@ -48,9 +53,9 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const { overwrittenNameList } = getState()['features/base/participants'];
if (Object.keys(overwrittenNameList).length > 0) {
const newRooms = {};
const newRooms: IRooms = {};
Object.entries(action.rooms).forEach(([ key, r ]) => {
Object.entries(action.rooms as IRooms).forEach(([ key, r ]) => {
let participants = r?.participants || {};
let jid;
@ -62,7 +67,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
...participants,
[jid]: {
...participants[jid],
displayName: overwrittenNameList[id]
displayName: overwrittenNameList[id as keyof typeof overwrittenNameList]
}
};
}
@ -81,12 +86,11 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
// edit the chat history to match names for participants in breakout rooms
const { messages } = getState()['features/chat'];
messages && messages.forEach(m => {
messages?.forEach(m => {
if (m.messageType === MESSAGE_TYPE_REMOTE && !getParticipantById(getState(), m.id)) {
const rooms = action.rooms;
const rooms: IRooms = action.rooms;
for (const room of Object.values(rooms)) {
// $FlowExpectedError
const participants = room.participants || {};
const matchedJid = Object.keys(participants).find(jid => jid.endsWith(m.id));

View File

@ -6,6 +6,7 @@ import {
UPDATE_BREAKOUT_ROOMS
} from './actionTypes';
import { FEATURE_KEY } from './constants';
import { IRooms } from './types';
const DEFAULT_STATE = {
rooms: {},
@ -14,21 +15,7 @@ const DEFAULT_STATE = {
export interface IBreakoutRoomsState {
roomCounter: number;
rooms: {
[id: string]: {
id: string;
isMainRoom?: boolean;
jid: string;
name: string;
participants: {
[jid: string]: {
displayName: string;
jid: string;
role: string;
};
};
};
};
rooms: IRooms;
}
/**

View File

@ -0,0 +1,17 @@
export interface IRoom {
id: string;
isMainRoom?: boolean;
jid: string;
name: string;
participants: {
[jid: string]: {
displayName: string;
jid: string;
role: string;
};
};
}
export interface IRooms {
[jid: string]: IRoom;
}

View File

@ -15,7 +15,6 @@ import { withPixelLineHeight } from '../../../../../base/styles/functions.web';
// @ts-ignore
import { showOverflowDrawer } from '../../../../../toolbox/functions.web';
import { ACTION_TRIGGER } from '../../../../constants';
// @ts-ignore
import { participantMatchesSearch } from '../../../../functions';
import ParticipantActionEllipsis from '../../../web/ParticipantActionEllipsis';
import ParticipantItem from '../../../web/ParticipantItem';

View File

@ -118,8 +118,6 @@ function MeetingParticipants({
<>
<div className = { styles.heading }>
{currentRoom?.name
// $FlowExpectedError
? `${currentRoom.name} (${participantsCount})`
: t('participantsPane.headings.participantsList', { count: participantsCount })}
</div>

View File

@ -1,11 +1,13 @@
// @flow
/* eslint-disable lines-around-comment */
import { IState } from '../app/types';
import {
isParticipantApproved,
isEnabledFromState,
isLocalParticipantApprovedFromState,
isSupported
} from '../av-moderation/functions';
import { IStateful } from '../base/app/types';
// @ts-ignore
import { getFeatureFlag, INVITE_ENABLED } from '../base/flags';
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
import {
@ -16,7 +18,9 @@ import {
getRemoteParticipantsSorted,
getRaiseHandsQueue
} from '../base/participants/functions';
import { toState } from '../base/redux';
import { Participant } from '../base/participants/types';
import { toState } from '../base/redux/functions';
// @ts-ignore
import { normalizeAccents } from '../base/util/strings';
import { isInBreakoutRoom } from '../breakout-rooms/functions';
@ -25,11 +29,11 @@ import { QUICK_ACTION_BUTTON, REDUCER_KEY, MEDIA_STATE } from './constants';
/**
* Find the first styled ancestor component of an element.
*
* @param {Element} target - Element to look up.
* @param {HTMLElement|null} target - Element to look up.
* @param {string} cssClass - Styled component reference.
* @returns {Element|null} Ancestor.
* @returns {HTMLElement|null} Ancestor.
*/
export const findAncestorByClass = (target: Object, cssClass: string) => {
export const findAncestorByClass = (target: HTMLElement | null, cssClass: string): HTMLElement | null => {
if (!target || target.classList.contains(cssClass)) {
return target;
}
@ -40,14 +44,14 @@ export const findAncestorByClass = (target: Object, cssClass: string) => {
/**
* Checks if a participant is force muted.
*
* @param {Object} participant - The participant.
* @param {Participant|undefined} participant - The participant.
* @param {MediaType} mediaType - The media type.
* @param {Object} state - The redux state.
* @param {IState} state - The redux state.
* @returns {MediaState}
*/
export function isForceMuted(participant: Object, mediaType: MediaType, state: Object) {
export function isForceMuted(participant: Participant | undefined, mediaType: MediaType, state: IState) {
if (isEnabledFromState(mediaType, state)) {
if (participant.local) {
if (participant?.local) {
return !isLocalParticipantApprovedFromState(mediaType, state);
}
@ -56,7 +60,7 @@ export function isForceMuted(participant: Object, mediaType: MediaType, state: O
return false;
}
return !isParticipantApproved(participant.id, mediaType)(state);
return !isParticipantApproved(participant?.id ?? '', mediaType)(state);
}
return false;
@ -65,12 +69,12 @@ export function isForceMuted(participant: Object, mediaType: MediaType, state: O
/**
* Determines the audio media state (the mic icon) for a participant.
*
* @param {Object} participant - The participant.
* @param {Participant} participant - The participant.
* @param {boolean} muted - The mute state of the participant.
* @param {Object} state - The redux state.
* @param {IState} state - The redux state.
* @returns {MediaState}
*/
export function getParticipantAudioMediaState(participant: Object, muted: Boolean, state: Object) {
export function getParticipantAudioMediaState(participant: Participant, muted: Boolean, state: IState) {
const dominantSpeaker = getDominantSpeakerParticipant(state);
if (muted) {
@ -91,12 +95,12 @@ export function getParticipantAudioMediaState(participant: Object, muted: Boolea
/**
* Determines the video media state (the mic icon) for a participant.
*
* @param {Object} participant - The participant.
* @param {Participant} participant - The participant.
* @param {boolean} muted - The mute state of the participant.
* @param {Object} state - The redux state.
* @param {IState} state - The redux state.
* @returns {MediaState}
*/
export function getParticipantVideoMediaState(participant: Object, muted: Boolean, state: Object) {
export function getParticipantVideoMediaState(participant: Participant, muted: Boolean, state: IState) {
if (muted) {
if (isForceMuted(participant, MEDIA_TYPE.VIDEO, state)) {
return MEDIA_STATE.FORCE_MUTED;
@ -116,7 +120,7 @@ export function getParticipantVideoMediaState(participant: Object, muted: Boolea
* @param {string} name - Property name.
* @returns {number} Float value.
*/
export const getFloatStyleProperty = (styles: Object, name: string) =>
export const getFloatStyleProperty = (styles: CSSStyleDeclaration, name: string) =>
parseFloat(styles.getPropertyValue(name));
/**
@ -136,19 +140,19 @@ export const getComputedOuterHeight = (element: HTMLElement) => {
/**
* Returns this feature's root state.
*
* @param {Object} state - Global state.
* @param {IState} state - Global state.
* @returns {Object} Feature state.
*/
const getState = (state: Object) => state[REDUCER_KEY];
const getState = (state: IState) => state[REDUCER_KEY];
/**
* Returns the participants pane config.
*
* @param {Function|Object} stateful - The redux store, the redux
* @param {IStateful} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {Object}
*/
export const getParticipantsPaneConfig = (stateful: Function | Object) => {
export const getParticipantsPaneConfig = (stateful: IStateful) => {
const state = toState(stateful);
const { participantsPane = {} } = state['features/base/config'];
@ -158,21 +162,21 @@ export const getParticipantsPaneConfig = (stateful: Function | Object) => {
/**
* Is the participants pane open.
*
* @param {Object} state - Global state.
* @param {IState} state - Global state.
* @returns {boolean} Is the participants pane open.
*/
export const getParticipantsPaneOpen = (state: Object) => Boolean(getState(state)?.isOpen);
export const getParticipantsPaneOpen = (state: IState) => Boolean(getState(state)?.isOpen);
/**
* Returns the type of quick action button to be displayed for a participant.
* The button is displayed when hovering a participant from the participant list.
*
* @param {Object} participant - The participant.
* @param {Participant} participant - The participant.
* @param {boolean} isAudioMuted - If audio is muted for the participant.
* @param {Object} state - The redux state.
* @param {IState} state - The redux state.
* @returns {string} - The type of the quick action button.
*/
export function getQuickActionButtonType(participant: Object, isAudioMuted: Boolean, state: Object) {
export function getQuickActionButtonType(participant: Participant, isAudioMuted: Boolean, state: IState) {
// handled only by moderators
if (isLocalParticipantModerator(state)) {
if (!isAudioMuted) {
@ -189,10 +193,10 @@ export function getQuickActionButtonType(participant: Object, isAudioMuted: Bool
/**
* Returns true if the invite button should be rendered.
*
* @param {Object} state - Global state.
* @param {IState} state - Global state.
* @returns {boolean}
*/
export const shouldRenderInviteButton = (state: Object) => {
export const shouldRenderInviteButton = (state: IState) => {
const { disableInviteFunctions } = toState(state)['features/base/config'];
const flagEnabled = getFeatureFlag(state, INVITE_ENABLED, true);
const inBreakoutRoom = isInBreakoutRoom(state);
@ -211,12 +215,12 @@ export const shouldRenderInviteButton = (state: Object) => {
* 6. Recent speakers sorted alphabetically by their display name.
* 7. Rest of the participants sorted alphabetically by their display name.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* @param {IStateful} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state features/base/participants.
* @returns {Array<string>}
*/
export function getSortedParticipantIds(stateful: Object | Function): Array<string> {
const { id } = getLocalParticipant(stateful);
export function getSortedParticipantIds(stateful: IStateful) {
const id = getLocalParticipant(stateful)?.id;
const remoteParticipants = getRemoteParticipantsSorted(stateful);
const reorderedParticipants = new Set(remoteParticipants);
const raisedHandParticipants = getRaiseHandsQueue(stateful).map(({ id: particId }) => particId);
@ -232,7 +236,7 @@ export function getSortedParticipantIds(stateful: Object | Function): Array<stri
const dominant = [];
const dominantId = dominantSpeaker?.id;
const local = remoteRaisedHandParticipants.has(id) ? [] : [ id ];
const local = remoteRaisedHandParticipants.has(id ?? '') ? [] : [ id ];
// In case dominat speaker has raised hand, keep the order in the raised hand queue.
// In case they don't have raised hand, goes first in the participants list.
@ -257,7 +261,8 @@ export function getSortedParticipantIds(stateful: Object | Function): Array<stri
* @param {string} searchString - The participants search string.
* @returns {boolean}
*/
export function participantMatchesSearch(participant: Object, searchString: string) {
export function participantMatchesSearch(participant: { displayName: string; jid: string; name?: string; },
searchString: string) {
if (searchString === '') {
return true;
}
@ -279,10 +284,10 @@ export function participantMatchesSearch(participant: Object, searchString: stri
/**
* Returns whether the more actions button is visible.
*
* @param {Object} state - Global state.
* @param {IState} state - Global state.
* @returns {boolean}
*/
export const isMoreActionsVisible = (state: Object) => {
export const isMoreActionsVisible = (state: IState) => {
const isLocalModerator = isLocalParticipantModerator(state);
const inBreakoutRoom = isInBreakoutRoom(state);
const { hideMoreActionsButton } = getParticipantsPaneConfig(state);
@ -293,10 +298,10 @@ export const isMoreActionsVisible = (state: Object) => {
/**
* Returns whether the mute all button is visible.
*
* @param {Object} state - Global state.
* @param {IState} state - Global state.
* @returns {boolean}
*/
export const isMuteAllVisible = (state: Object) => {
export const isMuteAllVisible = (state: IState) => {
const isLocalModerator = isLocalParticipantModerator(state);
const inBreakoutRoom = isInBreakoutRoom(state);
const { hideMuteAllButton } = getParticipantsPaneConfig(state);

View File

@ -7,7 +7,7 @@ import {
} from './actionTypes';
import { REDUCER_KEY } from './constants';
export interface IParticipantsPane {
export interface IParticipantsPaneState {
isOpen: boolean;
participantsVolume: {
[participantId: string]: number;
@ -23,7 +23,7 @@ const DEFAULT_STATE = {
* Listen for actions that mutate the participants pane state.
*/
ReducerRegistry.register(
REDUCER_KEY, (state: IParticipantsPane = DEFAULT_STATE, action) => {
REDUCER_KEY, (state: IParticipantsPaneState = DEFAULT_STATE, action) => {
switch (action.type) {
case PARTICIPANTS_PANE_CLOSE:
return {

View File

@ -27,7 +27,6 @@ import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../b
import { setVolume } from '../../../filmstrip/actions.web';
// @ts-ignore
import { isStageFilmstripAvailable } from '../../../filmstrip/functions.web';
// @ts-ignore
import { isForceMuted } from '../../../participants-pane/functions';
// @ts-ignore
import { requestRemoteControl, stopController } from '../../../remote-control';
@ -152,9 +151,9 @@ const ParticipantContextMenu = ({
const localParticipant = useSelector(getLocalParticipant);
const _isModerator = Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR);
const _isAudioForceMuted = useSelector(state =>
const _isAudioForceMuted = useSelector<IState>(state =>
isForceMuted(participant, MEDIA_TYPE.AUDIO, state));
const _isVideoForceMuted = useSelector(state =>
const _isVideoForceMuted = useSelector<IState>(state =>
isForceMuted(participant, MEDIA_TYPE.VIDEO, state));
const _isAudioMuted = useSelector(state => isParticipantAudioMuted(participant, state));
const _overflowDrawer: boolean = useSelector(showOverflowDrawer);