Explorar el Código

feat(Avatar): Implement Avatar for web

j8
hristoterezov hace 8 años
padre
commit
814bd26c07

+ 60
- 16
conference.js Ver fichero

@@ -20,7 +20,11 @@ import analytics from './modules/analytics/analytics';
20 20
 
21 21
 import EventEmitter from "events";
22 22
 
23
-import { conferenceFailed } from './react/features/base/conference';
23
+import {
24
+    CONFERENCE_JOINED,
25
+    conferenceFailed,
26
+    conferenceLeft
27
+} from './react/features/base/conference';
24 28
 import {
25 29
     isFatalJitsiConnectionError
26 30
 } from './react/features/base/lib-jitsi-meet';
@@ -29,6 +33,15 @@ import {
29 33
     suspendDetected
30 34
 } from './react/features/overlay';
31 35
 
36
+import {
37
+    changeParticipantAvatarID,
38
+    changeParticipantAvatarURL,
39
+    changeParticipantEmail,
40
+    participantJoined,
41
+    participantLeft,
42
+    participantRoleChanged
43
+} from './react/features/base/participants';
44
+
32 45
 const ConnectionEvents = JitsiMeetJS.events.connection;
33 46
 const ConnectionErrors = JitsiMeetJS.errors.connection;
34 47
 
@@ -150,6 +163,29 @@ function sendData (command, value) {
150 163
     room.sendCommand(command, {value: value});
151 164
 }
152 165
 
166
+/**
167
+ * Setups initially the properties for the local participant - email, avatarId,
168
+ * avatarUrl, displayName, etc.
169
+ */
170
+function _setupLocalParticipantProperties() {
171
+    const email = APP.settings.getEmail();
172
+    email && sendData(commands.EMAIL, email);
173
+
174
+    const avatarUrl = APP.settings.getAvatarUrl();
175
+    avatarUrl && sendData(commands.AVATAR_URL, avatarUrl);
176
+
177
+    if(!email && !avatarUrl) {
178
+        sendData(commands.AVATAR_ID, APP.settings.getAvatarId());
179
+    }
180
+
181
+    let nick = APP.settings.getDisplayName();
182
+    if (config.useNicks && !nick) {
183
+        nick = APP.UI.askForNickname();
184
+        APP.settings.setDisplayName(nick);
185
+    }
186
+    nick && room.setDisplayName(nick);
187
+}
188
+
153 189
 /**
154 190
  * Get user nickname by user id.
155 191
  * @param {string} id user id
@@ -875,21 +911,7 @@ export default {
875 911
         this.invite = new Invite(room);
876 912
         this._room = room; // FIXME do not use this
877 913
 
878
-        let email = APP.settings.getEmail();
879
-        email && sendData(this.commands.defaults.EMAIL, email);
880
-
881
-        let avatarUrl = APP.settings.getAvatarUrl();
882
-        avatarUrl && sendData(this.commands.defaults.AVATAR_URL,
883
-            avatarUrl);
884
-        !email && sendData(
885
-             this.commands.defaults.AVATAR_ID, APP.settings.getAvatarId());
886
-
887
-        let nick = APP.settings.getDisplayName();
888
-        if (config.useNicks && !nick) {
889
-            nick = APP.UI.askForNickname();
890
-            APP.settings.setDisplayName(nick);
891
-        }
892
-        nick && room.setDisplayName(nick);
914
+        _setupLocalParticipantProperties();
893 915
 
894 916
         this._setupListeners();
895 917
     },
@@ -1116,11 +1138,18 @@ export default {
1116 1138
     _setupListeners () {
1117 1139
         // add local streams when joined to the conference
1118 1140
         room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
1141
+            APP.store.dispatch({
1142
+                type: CONFERENCE_JOINED,
1143
+                conference: room
1144
+            });
1119 1145
             APP.UI.mucJoined();
1120 1146
             APP.API.notifyConferenceJoined(APP.conference.roomName);
1121 1147
             APP.UI.markVideoInterrupted(false);
1122 1148
         });
1123 1149
 
1150
+        room.on(ConferenceEvents.CONFERENCE_LEFT,
1151
+            (...args) => APP.store.dispatch(conferenceLeft(room, ...args)));
1152
+
1124 1153
         room.on(
1125 1154
             ConferenceEvents.AUTH_STATUS_CHANGED,
1126 1155
             function (authEnabled, authLogin) {
@@ -1134,6 +1163,12 @@ export default {
1134 1163
             if (user.isHidden())
1135 1164
                 return;
1136 1165
 
1166
+            APP.store.dispatch(participantJoined({
1167
+                id,
1168
+                name: user.getDisplayName(),
1169
+                role: user.getRole()
1170
+            }));
1171
+
1137 1172
             logger.log('USER %s connnected', id, user);
1138 1173
             APP.API.notifyUserJoined(id);
1139 1174
             APP.UI.addUser(user);
@@ -1142,6 +1177,7 @@ export default {
1142 1177
             APP.UI.updateUserRole(user);
1143 1178
         });
1144 1179
         room.on(ConferenceEvents.USER_LEFT, (id, user) => {
1180
+            APP.store.dispatch(participantLeft(id, user));
1145 1181
             logger.log('USER %s LEFT', id, user);
1146 1182
             APP.API.notifyUserLeft(id);
1147 1183
             APP.UI.removeUser(id, user.getDisplayName());
@@ -1150,6 +1186,7 @@ export default {
1150 1186
 
1151 1187
 
1152 1188
         room.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
1189
+            APP.store.dispatch(participantRoleChanged(id, role));
1153 1190
             if (this.isLocalId(id)) {
1154 1191
                 logger.info(`My role changed, new role: ${role}`);
1155 1192
                 if (this.isModerator !== room.isModerator()) {
@@ -1411,17 +1448,22 @@ export default {
1411 1448
 
1412 1449
         APP.UI.addListener(UIEvents.EMAIL_CHANGED, this.changeLocalEmail);
1413 1450
         room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
1451
+            APP.store.dispatch(changeParticipantEmail(from, data.value));
1414 1452
             APP.UI.setUserEmail(from, data.value);
1415 1453
         });
1416 1454
 
1417 1455
         room.addCommandListener(
1418 1456
             this.commands.defaults.AVATAR_URL,
1419 1457
             (data, from) => {
1458
+                APP.store.dispatch(
1459
+                    changeParticipantAvatarURL(from, data.value));
1420 1460
                 APP.UI.setUserAvatarUrl(from, data.value);
1421 1461
         });
1422 1462
 
1423 1463
         room.addCommandListener(this.commands.defaults.AVATAR_ID,
1424 1464
             (data, from) => {
1465
+                APP.store.dispatch(
1466
+                    changeParticipantAvatarID(from, data.value));
1425 1467
                 APP.UI.setUserAvatarID(from, data.value);
1426 1468
             });
1427 1469
 
@@ -1832,6 +1874,7 @@ export default {
1832 1874
         if (email === APP.settings.getEmail()) {
1833 1875
             return;
1834 1876
         }
1877
+        APP.store.dispatch(changeParticipantEmail(room.myUserId(), email));
1835 1878
 
1836 1879
         APP.settings.setEmail(email);
1837 1880
         APP.UI.setUserEmail(room.myUserId(), email);
@@ -1848,6 +1891,7 @@ export default {
1848 1891
         if (url === APP.settings.getAvatarUrl()) {
1849 1892
             return;
1850 1893
         }
1894
+        APP.store.dispatch(changeParticipantAvatarURL(room.myUserId(), url));
1851 1895
 
1852 1896
         APP.settings.setAvatarUrl(url);
1853 1897
         APP.UI.setUserAvatarUrl(room.myUserId(), url);

+ 10
- 52
modules/UI/avatar/Avatar.js Ver fichero

@@ -21,8 +21,9 @@
21 21
  * SOFTWARE.
22 22
  */
23 23
 
24
-/* global MD5, config, interfaceConfig, APP */
25
-const logger = require("jitsi-meet-logger").getLogger(__filename);
24
+/* global APP */
25
+
26
+import { getAvatarURL } from '../../../react/features/base/participants';
26 27
 
27 28
 let users = {};
28 29
 
@@ -64,7 +65,7 @@ export default {
64 65
      * @param url the url for the avatar
65 66
      */
66 67
     setUserAvatarUrl: function (id, url) {
67
-        this._setUserProp(id, "url", url);
68
+        this._setUserProp(id, "avatarUrl", url);
68 69
     },
69 70
 
70 71
     /**
@@ -82,57 +83,14 @@ export default {
82 83
      * @param {string} userId user id
83 84
      */
84 85
     getAvatarUrl: function (userId) {
85
-        if (config.disableThirdPartyRequests) {
86
-            return 'images/avatar2.png';
87
-        }
88
-
86
+        let user;
89 87
         if (!userId || APP.conference.isLocalId(userId)) {
90
-            userId = "local";
91
-        }
92
-
93
-        let avatarId = null;
94
-        const user = users[userId];
95
-
96
-        // The priority is url, email and lowest is avatarId
97
-        if(user) {
98
-            if(user.url)
99
-                return user.url;
100
-
101
-            if (user.email)
102
-                avatarId = user.email;
103
-            else {
104
-                avatarId = user.avatarId;
105
-            }
106
-        }
107
-
108
-        // If the ID looks like an email, we'll use gravatar.
109
-        // Otherwise, it's a random avatar, and we'll use the configured
110
-        // URL.
111
-        let random = !avatarId || avatarId.indexOf('@') < 0;
112
-
113
-        if (!avatarId) {
114
-            logger.warn(
115
-                `No avatar stored yet for ${userId} - using ID as avatar ID`);
116
-            avatarId = userId;
117
-        }
118
-        avatarId = MD5.hexdigest(avatarId.trim().toLowerCase());
119
-
120
-
121
-        let urlPref = null;
122
-        let urlSuf = null;
123
-        if (!random) {
124
-            urlPref = 'https://www.gravatar.com/avatar/';
125
-            urlSuf = "?d=wavatar&size=200";
126
-        }
127
-        else if (random && interfaceConfig.RANDOM_AVATAR_URL_PREFIX) {
128
-            urlPref = interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
129
-            urlSuf = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
130
-        }
131
-        else {
132
-            urlPref = 'https://api.adorable.io/avatars/200/';
133
-            urlSuf = ".png";
88
+            user = users.local;
89
+            userId = APP.conference.getMyUserId();
90
+        } else {
91
+            user = users[userId];
134 92
         }
135 93
 
136
-        return urlPref + avatarId + urlSuf;
94
+        return getAvatarURL(userId, user);
137 95
     }
138 96
 };

+ 5
- 1
react/features/app/components/AbstractApp.js Ver fichero

@@ -78,7 +78,11 @@ export class AbstractApp extends Component {
78 78
 
79 79
         dispatch(appWillMount(this));
80 80
 
81
-        dispatch(localParticipantJoined());
81
+        dispatch(localParticipantJoined({
82
+            avatarId: APP.settings.getAvatarId(),
83
+            avatarUrl: APP.settings.getAvatarUrl(),
84
+            email: APP.settings.getEmail()
85
+        }));
82 86
 
83 87
         // If a URL was explicitly specified to this React Component, then open
84 88
         // it; otherwise, use a default.

+ 2
- 2
react/features/base/conference/actions.js Ver fichero

@@ -38,7 +38,7 @@ function _addConferenceListeners(conference, dispatch) {
38 38
             (...args) => dispatch(_conferenceJoined(conference, ...args)));
39 39
     conference.on(
40 40
             JitsiConferenceEvents.CONFERENCE_LEFT,
41
-            (...args) => dispatch(_conferenceLeft(conference, ...args)));
41
+            (...args) => dispatch(conferenceLeft(conference, ...args)));
42 42
 
43 43
     conference.on(
44 44
             JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
@@ -131,7 +131,7 @@ function _conferenceJoined(conference) {
131 131
  *      conference: JitsiConference
132 132
  *  }}
133 133
  */
134
-function _conferenceLeft(conference) {
134
+export function conferenceLeft(conference) {
135 135
     return {
136 136
         type: CONFERENCE_LEFT,
137 137
         conference

+ 46
- 1
react/features/base/participants/actions.js Ver fichero

@@ -8,6 +8,52 @@ import {
8 8
 } from './actionTypes';
9 9
 import { getLocalParticipant } from './functions';
10 10
 
11
+/**
12
+ * Action to update a participant's avatar id.
13
+ *
14
+ * @param {string} id - Participant's id.
15
+ * @param {string} avatarId - Participant's avatar id.
16
+ * @returns {{
17
+ *      type: PARTICIPANT_UPDATED,
18
+ *      participant: {
19
+ *          id: string,
20
+ *          avatarId: string,
21
+ *      }
22
+ * }}
23
+ */
24
+export function changeParticipantAvatarID(id, avatarId) {
25
+    return {
26
+        type: PARTICIPANT_UPDATED,
27
+        participant: {
28
+            id,
29
+            avatarId
30
+        }
31
+    };
32
+}
33
+
34
+/**
35
+ * Action to update a participant's avatar URL.
36
+ *
37
+ * @param {string} id - Participant's id.
38
+ * @param {string} url - Participant's avatar url.
39
+ * @returns {{
40
+ *      type: PARTICIPANT_UPDATED,
41
+ *      participant: {
42
+ *          id: string,
43
+ *          url: string,
44
+ *      }
45
+ * }}
46
+ */
47
+export function changeParticipantAvatarURL(id, url) {
48
+    return {
49
+        type: PARTICIPANT_UPDATED,
50
+        participant: {
51
+            id,
52
+            url
53
+        }
54
+    };
55
+}
56
+
11 57
 /**
12 58
  * Action to update a participant's email.
13 59
  *
@@ -17,7 +63,6 @@ import { getLocalParticipant } from './functions';
17 63
  *      type: PARTICIPANT_UPDATED,
18 64
  *      participant: {
19 65
  *          id: string,
20
- *          avatar: string,
21 66
  *          email: string
22 67
  *      }
23 68
  * }}

+ 32
- 0
react/features/base/participants/components/Avatar.web.js Ver fichero

@@ -0,0 +1,32 @@
1
+import React, { Component } from 'react';
2
+
3
+/**
4
+ * Display a participant avatar.
5
+ */
6
+export default class Avatar extends Component {
7
+    /**
8
+     * Avatar component's property types.
9
+     *
10
+     * @static
11
+     */
12
+    static propTypes = {
13
+        /**
14
+         * The URL for the avatar.
15
+         *
16
+         * @type {string}
17
+         */
18
+        uri: React.PropTypes.string
19
+    }
20
+
21
+    /**
22
+     * Implements React's {@link Component#render()}.
23
+     *
24
+     * @inheritdoc
25
+     */
26
+    render() {
27
+        return (
28
+            <img
29
+                src = { this.props.uri } />
30
+        );
31
+    }
32
+}

+ 1
- 0
react/features/base/participants/components/index.js Ver fichero

@@ -1 +1,2 @@
1
+export { default as Avatar } from './Avatar';
1 2
 export { default as ParticipantView } from './ParticipantView';

+ 72
- 0
react/features/base/participants/functions.js Ver fichero

@@ -1,3 +1,8 @@
1
+/* global MD5 */
2
+
3
+declare var config: Object;
4
+declare var interfaceConfig: Object;
5
+
1 6
 /**
2 7
  * Returns local participant from Redux state.
3 8
  *
@@ -45,3 +50,70 @@ function _getParticipants(participantsOrGetState) {
45 50
 
46 51
     return participants || [];
47 52
 }
53
+
54
+/**
55
+ * Returns the URL of the image for the avatar of a particular participant
56
+ * identified by their id and/or e-mail address.
57
+ *
58
+ * @param {string} [participantId] - Participant's id.
59
+ * @param {Object} [options] - The optional arguments.
60
+ * @param {string} [options.avatarId] - Participant's avatar id.
61
+ * @param {string} [options.avatarUrl] - Participant's avatar url.
62
+ * @param {string} [options.email] - Participant's email.
63
+ * @returns {string} The URL of the image for the avatar of the participant
64
+ * identified by the specified participantId and/or email.
65
+ *
66
+ * @public
67
+ */
68
+export function getAvatarURL(participantId, options = {}) {
69
+    // If disableThirdPartyRequests is enabled we shouldn't use third party
70
+    // avatar services, we are returning one of our images.
71
+    if (typeof config === 'object' && config.disableThirdPartyRequests) {
72
+        return 'images/avatar2.png';
73
+    }
74
+
75
+    const { avatarId, avatarUrl, email } = options;
76
+
77
+    // If we have avatarUrl we don't need to generate new one.
78
+    if (avatarUrl) {
79
+        return avatarUrl;
80
+    }
81
+
82
+    let avatarKey = null;
83
+
84
+    if (email) {
85
+        avatarKey = email;
86
+    } else {
87
+        avatarKey = avatarId;
88
+    }
89
+
90
+    // If the ID looks like an email, we'll use gravatar.
91
+    // Otherwise, it's a random avatar, and we'll use the configured
92
+    // URL.
93
+    const isEmail = avatarKey && avatarKey.indexOf('@') > 0;
94
+
95
+    if (!avatarKey) {
96
+        avatarKey = participantId;
97
+    }
98
+
99
+    avatarKey = MD5.hexdigest(avatarKey.trim().toLowerCase());
100
+
101
+    let urlPref = null;
102
+    let urlSuf = null;
103
+
104
+    // gravatar doesn't support random avatars that's why we need to use other
105
+    // services for the use case when the email is undefined.
106
+    if (isEmail) {
107
+        urlPref = 'https://www.gravatar.com/avatar/';
108
+        urlSuf = '?d=wavatar&size=200';
109
+    } else if (typeof interfaceConfig === 'object'
110
+        && interfaceConfig.RANDOM_AVATAR_URL_PREFIX) { // custom avatar service
111
+        urlPref = interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
112
+        urlSuf = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
113
+    } else { // default avatar service
114
+        urlPref = 'https://api.adorable.io/avatars/200/';
115
+        urlSuf = '.png';
116
+    }
117
+
118
+    return urlPref + avatarKey + urlSuf;
119
+}

+ 22
- 47
react/features/base/participants/reducer.js Ver fichero

@@ -1,5 +1,3 @@
1
-/* global MD5 */
2
-
3 1
 import { ReducerRegistry, setStateProperty } from '../redux';
4 2
 
5 3
 import {
@@ -14,6 +12,7 @@ import {
14 12
     LOCAL_PARTICIPANT_DEFAULT_ID,
15 13
     PARTICIPANT_ROLE
16 14
 } from './constants';
15
+import { getAvatarURL } from './functions';
17 16
 
18 17
 /**
19 18
  * Participant object.
@@ -64,11 +63,16 @@ function _participant(state, action) {
64 63
     case PARTICIPANT_ID_CHANGED:
65 64
         if (state.id === action.oldValue) {
66 65
             const id = action.newValue;
66
+            const { avatarId, avatarUrl, email } = state;
67 67
 
68 68
             return {
69 69
                 ...state,
70 70
                 id,
71
-                avatar: state.avatar || _getAvatarURL(id, state.email)
71
+                avatar: state.avatar || getAvatarURL(id, {
72
+                    avatarId,
73
+                    avatarUrl,
74
+                    email
75
+                })
72 76
             };
73 77
         }
74 78
         break;
@@ -81,8 +85,14 @@ function _participant(state, action) {
81 85
         const id
82 86
             = participant.id
83 87
                 || (participant.local && LOCAL_PARTICIPANT_DEFAULT_ID);
88
+        const { avatarId, avatarUrl, email } = participant;
84 89
         const avatar
85
-            = participant.avatar || _getAvatarURL(id, participant.email);
90
+            = participant.avatar
91
+                || getAvatarURL(id, {
92
+                    avatarId,
93
+                    avatarUrl,
94
+                    email
95
+                });
86 96
 
87 97
         // TODO Get these names from config/localized.
88 98
         const name
@@ -90,7 +100,7 @@ function _participant(state, action) {
90 100
 
91 101
         return {
92 102
             avatar,
93
-            email: participant.email,
103
+            email,
94 104
             id,
95 105
             local: participant.local || false,
96 106
             name,
@@ -113,8 +123,13 @@ function _participant(state, action) {
113 123
             }
114 124
 
115 125
             if (!newState.avatar) {
116
-                newState.avatar
117
-                    = _getAvatarURL(action.participant.id, newState.email);
126
+                const { avatarId, avatarUrl, email } = newState;
127
+
128
+                newState.avatar = getAvatarURL(action.participant.id, {
129
+                    avatarId,
130
+                    avatarUrl,
131
+                    email
132
+                });
118 133
             }
119 134
 
120 135
             return newState;
@@ -162,43 +177,3 @@ ReducerRegistry.register('features/base/participants', (state = [], action) => {
162 177
         return state;
163 178
     }
164 179
 });
165
-
166
-/**
167
- * Returns the URL of the image for the avatar of a particular participant
168
- * identified by their id and/or e-mail address.
169
- *
170
- * @param {string} participantId - Participant's id.
171
- * @param {string} [email] - Participant's email.
172
- * @returns {string} The URL of the image for the avatar of the participant
173
- * identified by the specified participantId and/or email.
174
- */
175
-function _getAvatarURL(participantId, email) {
176
-    // TODO: Use disableThirdPartyRequests config.
177
-
178
-    let avatarId = email || participantId;
179
-
180
-    // If the ID looks like an email, we'll use gravatar. Otherwise, it's a
181
-    // random avatar and we'll use the configured URL.
182
-    const random = !avatarId || avatarId.indexOf('@') < 0;
183
-
184
-    if (!avatarId) {
185
-        avatarId = participantId;
186
-    }
187
-
188
-    // MD5 is provided by Strophe
189
-    avatarId = MD5.hexdigest(avatarId.trim().toLowerCase());
190
-
191
-    let urlPref = null;
192
-    let urlSuf = null;
193
-
194
-    if (random) {
195
-        // TODO: Use RANDOM_AVATAR_URL_PREFIX from interface config.
196
-        urlPref = 'https://robohash.org/';
197
-        urlSuf = '.png?size=200x200';
198
-    } else {
199
-        urlPref = 'https://www.gravatar.com/avatar/';
200
-        urlSuf = '?d=wavatar&size=200';
201
-    }
202
-
203
-    return urlPref + avatarId + urlSuf;
204
-}

Loading…
Cancelar
Guardar