diff --git a/conference.js b/conference.js
index 9469f55a8..dc2d02dc8 100644
--- a/conference.js
+++ b/conference.js
@@ -21,18 +21,13 @@ import analytics from './modules/analytics/analytics';
import EventEmitter from "events";
import {
- CONFERENCE_JOINED,
+ conferenceJoined,
conferenceFailed,
conferenceLeft
} from './react/features/base/conference';
import {
isFatalJitsiConnectionError
} from './react/features/base/lib-jitsi-meet';
-import {
- mediaPermissionPromptVisibilityChanged,
- suspendDetected
-} from './react/features/overlay';
-
import {
changeParticipantAvatarID,
changeParticipantAvatarURL,
@@ -41,6 +36,10 @@ import {
participantLeft,
participantRoleChanged
} from './react/features/base/participants';
+import {
+ mediaPermissionPromptVisibilityChanged,
+ suspendDetected
+} from './react/features/overlay';
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
@@ -164,8 +163,8 @@ function sendData (command, value) {
}
/**
- * Setups initially the properties for the local participant - email, avatarId,
- * avatarUrl, displayName, etc.
+ * Sets up initially the properties of the local participant - email, avatarID,
+ * avatarURL, displayName, etc.
*/
function _setupLocalParticipantProperties() {
const email = APP.settings.getEmail();
@@ -174,7 +173,7 @@ function _setupLocalParticipantProperties() {
const avatarUrl = APP.settings.getAvatarUrl();
avatarUrl && sendData(commands.AVATAR_URL, avatarUrl);
- if(!email && !avatarUrl) {
+ if (!email && !avatarUrl) {
sendData(commands.AVATAR_ID, APP.settings.getAvatarId());
}
@@ -1138,16 +1137,15 @@ export default {
_setupListeners () {
// add local streams when joined to the conference
room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
- APP.store.dispatch({
- type: CONFERENCE_JOINED,
- conference: room
- });
+ APP.store.dispatch(conferenceJoined(room));
+
APP.UI.mucJoined();
APP.API.notifyConferenceJoined(APP.conference.roomName);
APP.UI.markVideoInterrupted(false);
});
- room.on(ConferenceEvents.CONFERENCE_LEFT,
+ room.on(
+ ConferenceEvents.CONFERENCE_LEFT,
(...args) => APP.store.dispatch(conferenceLeft(room, ...args)));
room.on(
diff --git a/modules/UI/avatar/Avatar.js b/modules/UI/avatar/Avatar.js
index d3dbb322f..e29619dee 100644
--- a/modules/UI/avatar/Avatar.js
+++ b/modules/UI/avatar/Avatar.js
@@ -91,6 +91,11 @@ export default {
user = users[userId];
}
- return getAvatarURL(userId, user);
+ return getAvatarURL({
+ avatarID: user ? user.avatarId : undefined,
+ avatarURL: user ? user.avatarUrl : undefined,
+ email: user ? user.email : undefined,
+ id: userId
+ });
}
};
diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js
index f15655020..fe518c025 100644
--- a/react/features/app/components/AbstractApp.js
+++ b/react/features/app/components/AbstractApp.js
@@ -1,5 +1,3 @@
-/* global APP */
-
import React, { Component } from 'react';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
@@ -20,6 +18,8 @@ import {
appWillUnmount
} from '../actions';
+declare var APP: Object;
+
/**
* Base (abstract) class for main App component.
*
@@ -78,11 +78,20 @@ export class AbstractApp extends Component {
dispatch(appWillMount(this));
- dispatch(localParticipantJoined({
- avatarId: APP.settings.getAvatarId(),
- avatarUrl: APP.settings.getAvatarUrl(),
- email: APP.settings.getEmail()
- }));
+ // FIXME I believe it makes more sense for a middleware to dispatch
+ // localParticipantJoined on APP_WILL_MOUNT because the order of actions
+ // is important, not the call site. Moreover, we've got localParticipant
+ // business logic in the React Component (i.e. UI) AbstractApp now.
+ let localParticipant;
+
+ if (typeof APP !== 'undefined') {
+ localParticipant = {
+ avatarID: APP.settings.getAvatarId(),
+ avatarURL: APP.settings.getAvatarUrl(),
+ email: APP.settings.getEmail()
+ };
+ }
+ dispatch(localParticipantJoined(localParticipant));
// If a URL was explicitly specified to this React Component, then open
// it; otherwise, use a default.
diff --git a/react/features/base/conference/actions.js b/react/features/base/conference/actions.js
index 430c6113e..65d301f12 100644
--- a/react/features/base/conference/actions.js
+++ b/react/features/base/conference/actions.js
@@ -35,7 +35,7 @@ function _addConferenceListeners(conference, dispatch) {
(...args) => dispatch(conferenceFailed(conference, ...args)));
conference.on(
JitsiConferenceEvents.CONFERENCE_JOINED,
- (...args) => dispatch(_conferenceJoined(conference, ...args)));
+ (...args) => dispatch(conferenceJoined(conference, ...args)));
conference.on(
JitsiConferenceEvents.CONFERENCE_LEFT,
(...args) => dispatch(conferenceLeft(conference, ...args)));
@@ -103,7 +103,7 @@ export function conferenceFailed(conference, error) {
* joined by the local participant.
* @returns {Function}
*/
-function _conferenceJoined(conference) {
+export function conferenceJoined(conference) {
return (dispatch, getState) => {
const localTracks
= getState()['features/base/tracks']
diff --git a/react/features/base/participants/actions.js b/react/features/base/participants/actions.js
index d3fd91e03..02f8f2c65 100644
--- a/react/features/base/participants/actions.js
+++ b/react/features/base/participants/actions.js
@@ -9,24 +9,24 @@ import {
import { getLocalParticipant } from './functions';
/**
- * Action to update a participant's avatar id.
+ * Action to update a participant's avatar ID.
*
- * @param {string} id - Participant's id.
- * @param {string} avatarId - Participant's avatar id.
+ * @param {string} id - Participant's ID.
+ * @param {string} avatarID - Participant's avatar ID.
* @returns {{
- * type: PARTICIPANT_UPDATED,
- * participant: {
- * id: string,
- * avatarId: string,
- * }
+ * type: PARTICIPANT_UPDATED,
+ * participant: {
+ * id: string,
+ * avatarID: string,
+ * }
* }}
*/
-export function changeParticipantAvatarID(id, avatarId) {
+export function changeParticipantAvatarID(id, avatarID) {
return {
type: PARTICIPANT_UPDATED,
participant: {
id,
- avatarId
+ avatarID
}
};
}
@@ -34,22 +34,22 @@ export function changeParticipantAvatarID(id, avatarId) {
/**
* Action to update a participant's avatar URL.
*
- * @param {string} id - Participant's id.
- * @param {string} url - Participant's avatar url.
+ * @param {string} id - Participant's ID.
+ * @param {string} avatarURL - Participant's avatar URL.
* @returns {{
- * type: PARTICIPANT_UPDATED,
- * participant: {
- * id: string,
- * url: string,
- * }
+ * type: PARTICIPANT_UPDATED,
+ * participant: {
+ * id: string,
+ * avatarURL: string,
+ * }
* }}
*/
-export function changeParticipantAvatarURL(id, url) {
+export function changeParticipantAvatarURL(id, avatarURL) {
return {
type: PARTICIPANT_UPDATED,
participant: {
id,
- url
+ avatarURL
}
};
}
@@ -57,14 +57,14 @@ export function changeParticipantAvatarURL(id, url) {
/**
* Action to update a participant's email.
*
- * @param {string} id - Participant's id.
+ * @param {string} id - Participant's ID.
* @param {string} email - Participant's email.
* @returns {{
- * type: PARTICIPANT_UPDATED,
- * participant: {
- * id: string,
- * email: string
- * }
+ * type: PARTICIPANT_UPDATED,
+ * participant: {
+ * id: string,
+ * email: string
+ * }
* }}
*/
export function changeParticipantEmail(id, email) {
@@ -80,12 +80,12 @@ export function changeParticipantEmail(id, email) {
/**
* Create an action for when dominant speaker changes.
*
- * @param {string} id - Participant id.
+ * @param {string} id - Participant's ID.
* @returns {{
- * type: DOMINANT_SPEAKER_CHANGED,
- * participant: {
- * id: string
- * }
+ * type: DOMINANT_SPEAKER_CHANGED,
+ * participant: {
+ * id: string
+ * }
* }}
*/
export function dominantSpeakerChanged(id) {
@@ -103,9 +103,9 @@ export function dominantSpeakerChanged(id) {
*
* @param {string} id - New ID for local participant.
* @returns {{
- * type: PARTICIPANT_ID_CHANGED,
- * newValue: string,
- * oldValue: string
+ * type: PARTICIPANT_ID_CHANGED,
+ * newValue: string,
+ * oldValue: string
* }}
*/
export function localParticipantIdChanged(id) {
@@ -127,8 +127,8 @@ export function localParticipantIdChanged(id) {
*
* @param {Participant} participant={} - Information about participant.
* @returns {{
- * type: PARTICIPANT_JOINED,
- * participant: Participant
+ * type: PARTICIPANT_JOINED,
+ * participant: Participant
* }}
*/
export function localParticipantJoined(participant = {}) {
@@ -170,14 +170,14 @@ export function participantJoined(participant) {
}
/**
- * Action to handle case when participant lefts.
+ * Action to signal that a participant has left.
*
- * @param {string} id - Participant id.
+ * @param {string} id - Participant's ID.
* @returns {{
- * type: PARTICIPANT_LEFT,
- * participant: {
- * id: string
- * }
+ * type: PARTICIPANT_LEFT,
+ * participant: {
+ * id: string
+ * }
* }}
*/
export function participantLeft(id) {
@@ -190,16 +190,16 @@ export function participantLeft(id) {
}
/**
- * Action to handle case when participant's role changes.
+ * Action to signal that a participant's role has changed.
*
- * @param {string} id - Participant id.
+ * @param {string} id - Participant's ID.
* @param {PARTICIPANT_ROLE} role - Participant's new role.
* @returns {{
- * type: PARTICIPANT_UPDATED,
- * participant: {
- * id: string,
- * role: PARTICIPANT_ROLE
- * }
+ * type: PARTICIPANT_UPDATED,
+ * participant: {
+ * id: string,
+ * role: PARTICIPANT_ROLE
+ * }
* }}
*/
export function participantRoleChanged(id, role) {
@@ -218,10 +218,10 @@ export function participantRoleChanged(id, role) {
* @param {string|null} id - The ID of the conference participant to pin or null
* if none of the conference's participants are to be pinned.
* @returns {{
- * type: PIN_PARTICIPANT,
- * participant: {
- * id: string
- * }
+ * type: PIN_PARTICIPANT,
+ * participant: {
+ * id: string
+ * }
* }}
*/
export function pinParticipant(id) {
diff --git a/react/features/base/participants/components/Avatar.web.js b/react/features/base/participants/components/Avatar.web.js
index 01d9ee2d2..61aaa61f3 100644
--- a/react/features/base/participants/components/Avatar.web.js
+++ b/react/features/base/participants/components/Avatar.web.js
@@ -24,9 +24,6 @@ export default class Avatar extends Component {
* @inheritdoc
*/
render() {
- return (
-
- );
+ return
;
}
}
diff --git a/react/features/base/participants/functions.js b/react/features/base/participants/functions.js
index d74e830d9..28e85a2ff 100644
--- a/react/features/base/participants/functions.js
+++ b/react/features/base/participants/functions.js
@@ -1,7 +1,67 @@
-/* global MD5 */
-
declare var config: Object;
declare var interfaceConfig: Object;
+declare var MD5: Object;
+
+/**
+ * Returns the URL of the image for the avatar of a specific participant.
+ *
+ * @param {Participant} [participant] - The participant to return the avatar URL
+ * of.
+ * @param {string} [participant.avatarID] - Participant's avatar ID.
+ * @param {string} [participant.avatarURL] - Participant's avatar URL.
+ * @param {string} [participant.email] - Participant's e-mail address.
+ * @param {string} [participant.id] - Participant's ID.
+ * @returns {string} The URL of the image for the avatar of the specified
+ * participant.
+ *
+ * @public
+ */
+export function getAvatarURL(participant) {
+ // If disableThirdPartyRequests disables third-party avatar services, we are
+ // restricted to a stock image of ours.
+ if (typeof config === 'object' && config.disableThirdPartyRequests) {
+ return 'images/avatar2.png';
+ }
+
+ const { avatarID, avatarURL, email, id } = participant;
+
+ // If an avatarURL is specified, then obviously there's nothing to generate.
+ if (avatarURL) {
+ return avatarURL;
+ }
+
+ let key = email || avatarID;
+ let urlPrefix;
+ let urlSuffix;
+
+ // If the ID looks like an e-mail address, we'll use Gravatar because it
+ // supports e-mail addresses.
+ if (key && key.indexOf('@') > 0) {
+ urlPrefix = 'https://www.gravatar.com/avatar/';
+ urlSuffix = '?d=wavatar&size=200';
+ } else {
+ // Otherwise, we do not have much a choice but a random avatar (fetched
+ // from a configured avatar service).
+ if (!key) {
+ key = id;
+ }
+
+ // The deployment is allowed to choose the avatar service which is to
+ // generate the random avatars.
+ urlPrefix
+ = typeof interfaceConfig === 'object'
+ && interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
+ if (urlPrefix) {
+ urlSuffix = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
+ } else {
+ // Otherwise, use a default (of course).
+ urlPrefix = 'https://api.adorable.io/avatars/200/';
+ urlSuffix = '.png';
+ }
+ }
+
+ return urlPrefix + MD5.hexdigest(key.trim().toLowerCase()) + urlSuffix;
+}
/**
* Returns local participant from Redux state.
@@ -50,70 +110,3 @@ function _getParticipants(participantsOrGetState) {
return participants || [];
}
-
-/**
- * Returns the URL of the image for the avatar of a particular participant
- * identified by their id and/or e-mail address.
- *
- * @param {string} [participantId] - Participant's id.
- * @param {Object} [options] - The optional arguments.
- * @param {string} [options.avatarId] - Participant's avatar id.
- * @param {string} [options.avatarUrl] - Participant's avatar url.
- * @param {string} [options.email] - Participant's email.
- * @returns {string} The URL of the image for the avatar of the participant
- * identified by the specified participantId and/or email.
- *
- * @public
- */
-export function getAvatarURL(participantId, options = {}) {
- // If disableThirdPartyRequests is enabled we shouldn't use third party
- // avatar services, we are returning one of our images.
- if (typeof config === 'object' && config.disableThirdPartyRequests) {
- return 'images/avatar2.png';
- }
-
- const { avatarId, avatarUrl, email } = options;
-
- // If we have avatarUrl we don't need to generate new one.
- if (avatarUrl) {
- return avatarUrl;
- }
-
- let avatarKey = null;
-
- if (email) {
- avatarKey = email;
- } else {
- avatarKey = avatarId;
- }
-
- // If the ID looks like an email, we'll use gravatar.
- // Otherwise, it's a random avatar, and we'll use the configured
- // URL.
- const isEmail = avatarKey && avatarKey.indexOf('@') > 0;
-
- if (!avatarKey) {
- avatarKey = participantId;
- }
-
- avatarKey = MD5.hexdigest(avatarKey.trim().toLowerCase());
-
- let urlPref = null;
- let urlSuf = null;
-
- // gravatar doesn't support random avatars that's why we need to use other
- // services for the use case when the email is undefined.
- if (isEmail) {
- urlPref = 'https://www.gravatar.com/avatar/';
- urlSuf = '?d=wavatar&size=200';
- } else if (typeof interfaceConfig === 'object'
- && interfaceConfig.RANDOM_AVATAR_URL_PREFIX) { // custom avatar service
- urlPref = interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
- urlSuf = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
- } else { // default avatar service
- urlPref = 'https://api.adorable.io/avatars/200/';
- urlSuf = '.png';
- }
-
- return urlPref + avatarKey + urlSuf;
-}
diff --git a/react/features/base/participants/reducer.js b/react/features/base/participants/reducer.js
index fdd9cc7dc..8878bc638 100644
--- a/react/features/base/participants/reducer.js
+++ b/react/features/base/participants/reducer.js
@@ -63,79 +63,82 @@ function _participant(state, action) {
case PARTICIPANT_ID_CHANGED:
if (state.id === action.oldValue) {
const id = action.newValue;
- const { avatarId, avatarUrl, email } = state;
-
- return {
+ const newState = {
...state,
- id,
- avatar: state.avatar || getAvatarURL(id, {
- avatarId,
- avatarUrl,
- email
- })
+ id
};
- }
- break;
-
- case PARTICIPANT_JOINED: {
- const participant = action.participant; // eslint-disable-line no-shadow
- // XXX The situation of not having an ID for a remote participant should
- // not happen. Maybe we should raise an error in this case or generate a
- // random ID.
- const id
- = participant.id
- || (participant.local && LOCAL_PARTICIPANT_DEFAULT_ID);
- const { avatarId, avatarUrl, email } = participant;
- const avatar
- = participant.avatar
- || getAvatarURL(id, {
- avatarId,
- avatarUrl,
- email
- });
-
- // TODO Get these names from config/localized.
- const name
- = participant.name || (participant.local ? 'me' : 'Fellow Jitster');
-
- return {
- avatar,
- email,
- id,
- local: participant.local || false,
- name,
- pinned: participant.pinned || false,
- role: participant.role || PARTICIPANT_ROLE.NONE,
- dominantSpeaker: participant.dominantSpeaker || false
- };
- }
-
- case PARTICIPANT_UPDATED:
- if (state.id === action.participant.id) {
- const newState = { ...state };
-
- for (const key in action.participant) {
- if (action.participant.hasOwnProperty(key)
- && PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE.indexOf(key)
- === -1) {
- newState[key] = action.participant[key];
- }
- }
if (!newState.avatar) {
- const { avatarId, avatarUrl, email } = newState;
-
- newState.avatar = getAvatarURL(action.participant.id, {
- avatarId,
- avatarUrl,
- email
- });
+ newState.avatar = getAvatarURL(newState);
}
return newState;
}
break;
+ case PARTICIPANT_JOINED: {
+ const participant = action.participant; // eslint-disable-line no-shadow
+ const { avatar, dominantSpeaker, email, local, pinned, role }
+ = participant;
+ let { id, name } = participant;
+
+ // id
+ //
+ // XXX The situation of not having an ID for a remote participant should
+ // not happen. Maybe we should raise an error in this case or generate a
+ // random ID.
+ if (!id && local) {
+ id = LOCAL_PARTICIPANT_DEFAULT_ID;
+ }
+
+ // name
+ if (!name) {
+ // TODO Get the from config and/or localized.
+ name = local ? 'me' : 'Fellow Jitster';
+ }
+
+ const newState = {
+ avatar,
+ dominantSpeaker: dominantSpeaker || false,
+ email,
+ id,
+ local: local || false,
+ name,
+ pinned: pinned || false,
+ role: role || PARTICIPANT_ROLE.NONE
+ };
+
+ if (!newState.avatar) {
+ newState.avatar = getAvatarURL(newState);
+ }
+
+ return newState;
+ }
+
+ case PARTICIPANT_UPDATED: {
+ const participant = action.participant; // eslint-disable-line no-shadow
+ const { id } = participant;
+
+ if (state.id === id) {
+ const newState = { ...state };
+
+ for (const key in participant) {
+ if (participant.hasOwnProperty(key)
+ && PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE.indexOf(key)
+ === -1) {
+ newState[key] = participant[key];
+ }
+ }
+
+ if (!newState.avatar) {
+ newState.avatar = getAvatarURL(newState);
+ }
+
+ return newState;
+ }
+ break;
+ }
+
case PIN_PARTICIPANT:
// Currently, only one pinned participant is allowed.
return (