Bladeren bron

ref(avatars): remove Avatar.js (#2289)

* ref(avatars): remove Avatar.js

- Rely on redux getting updated with new participant state
and any calls to getAvatarURL passing in the redux
participant state. This way the state within Avatar.js can
be removed.
- Clean up methods on UI.js. Because all state is in the
store, separate methods for updating the avatar aren't as
necessary. Instead centralize accessing of the avatar for
components outside of redux and centralize the call to
update avatars for non-react components.
- Controversial: cache a participant's avatarURL on the
participant state. Currently the participant's avatarURL
that is generated without jwt (which sets the avatarURL directly)
is not cached. Without cache, there can be many redundant
calls to APP.API.notifyAvatarChanged.

* Leverage middleware timing to diff avatars

One alternative implementation is to leverage middleware's
ability to intercept updates before and after redux has
upated and then compare avatarURLs.

* kill UI.getAvatarUrl

* profile button sets its own avatar url (solves update timing)

* remove calls to updating avatar outside of middleware

* update UI.js doc

* remove left over logic from initial implementation

* try to move local user fallback into selector func

* default to id 'local' in selector
master
virtuacoplenny 7 jaren geleden
bovenliggende
commit
28013f6ffa

+ 4
- 5
conference.js Bestand weergeven

@@ -70,6 +70,7 @@ import {
70 70
 } from './react/features/base/media';
71 71
 import {
72 72
     dominantSpeakerChanged,
73
+    getAvatarURLByParticipantId,
73 74
     getLocalParticipant,
74 75
     getParticipantById,
75 76
     localParticipantConnectionStatusChanged,
@@ -2092,7 +2093,6 @@ export default {
2092 2093
                         id: from,
2093 2094
                         avatarURL: data.value
2094 2095
                     }));
2095
-                APP.UI.setUserAvatarUrl(from, data.value);
2096 2096
             });
2097 2097
 
2098 2098
         room.addCommandListener(this.commands.defaults.AVATAR_ID,
@@ -2102,7 +2102,6 @@ export default {
2102 2102
                         id: from,
2103 2103
                         avatarID: data.value
2104 2104
                     }));
2105
-                APP.UI.setUserAvatarID(from, data.value);
2106 2105
             });
2107 2106
 
2108 2107
         APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
@@ -2414,7 +2413,8 @@ export default {
2414 2413
                 formattedDisplayName: appendSuffix(
2415 2414
                     displayName,
2416 2415
                     interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME),
2417
-                avatarURL: APP.UI.getAvatarUrl()
2416
+                avatarURL: getAvatarURLByParticipantId(
2417
+                    APP.store.getState(), this._room.myUserId())
2418 2418
             }
2419 2419
         );
2420 2420
         APP.UI.markVideoInterrupted(false);
@@ -2704,7 +2704,7 @@ export default {
2704 2704
         APP.store.dispatch(participantUpdated({
2705 2705
             id: localId,
2706 2706
             local: true,
2707
-            formattedEmail
2707
+            email: formattedEmail
2708 2708
         }));
2709 2709
 
2710 2710
         APP.settings.setEmail(formattedEmail);
@@ -2732,7 +2732,6 @@ export default {
2732 2732
         }));
2733 2733
 
2734 2734
         APP.settings.setAvatarUrl(url);
2735
-        APP.UI.setUserAvatarUrl(id, url);
2736 2735
         sendData(commands.AVATAR_URL, url);
2737 2736
     },
2738 2737
 

+ 8
- 50
modules/UI/UI.js Bestand weergeven

@@ -6,7 +6,6 @@ const UI = {};
6 6
 
7 7
 import Chat from './side_pannels/chat/Chat';
8 8
 import SidePanels from './side_pannels/SidePanels';
9
-import Avatar from './avatar/Avatar';
10 9
 import SideContainerToggler from './side_pannels/SideContainerToggler';
11 10
 import messageHandler from './util/MessageHandler';
12 11
 import UIUtil from './util/UIUtil';
@@ -255,7 +254,7 @@ UI.setLocalRaisedHandStatus
255 254
  */
256 255
 UI.initConference = function() {
257 256
     const { dispatch, getState } = APP.store;
258
-    const { avatarID, email, id, name } = getLocalParticipant(getState);
257
+    const { email, id, name } = getLocalParticipant(getState);
259 258
 
260 259
     // Update default button states before showing the toolbar
261 260
     // if local role changes buttons state will be again updated.
@@ -272,8 +271,6 @@ UI.initConference = function() {
272 271
     // Make sure we configure our avatar id, before creating avatar for us
273 272
     if (email) {
274 273
         UI.setUserEmail(id, email);
275
-    } else {
276
-        UI.setUserAvatarID(id, avatarID);
277 274
     }
278 275
 
279 276
     dispatch(checkAutoEnableDesktopSharing());
@@ -789,65 +786,26 @@ UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));
789 786
 // Used by torture.
790 787
 UI.dockToolbar = dock => APP.store.dispatch(dockToolbox(dock));
791 788
 
792
-/**
793
- * Updates the avatar for participant.
794
- * @param {string} id user id
795
- * @param {string} avatarUrl the URL for the avatar
796
- */
797
-function changeAvatar(id, avatarUrl) {
798
-    VideoLayout.changeUserAvatar(id, avatarUrl);
799
-    if (APP.conference.isLocalId(id)) {
800
-        Profile.changeAvatar(avatarUrl);
801
-    }
802
-}
803
-
804
-/**
805
- * Returns the avatar URL for a given user.
806
- *
807
- * @param {string} id - The id of the user.
808
- * @returns {string} The avatar URL.
809
- */
810
-UI.getAvatarUrl = function(id) {
811
-    return Avatar.getAvatarUrl(id);
812
-};
813
-
814 789
 /**
815 790
  * Update user email.
816 791
  * @param {string} id user id
817 792
  * @param {string} email user email
818 793
  */
819 794
 UI.setUserEmail = function(id, email) {
820
-    // update avatar
821
-    Avatar.setUserEmail(id, email);
822
-
823
-    changeAvatar(id, Avatar.getAvatarUrl(id));
824 795
     if (APP.conference.isLocalId(id)) {
825 796
         Profile.changeEmail(email);
826 797
     }
827 798
 };
828 799
 
829 800
 /**
830
- * Update user avtar id.
831
- * @param {string} id user id
832
- * @param {string} avatarId user's avatar id
833
- */
834
-UI.setUserAvatarID = function(id, avatarId) {
835
-    // update avatar
836
-    Avatar.setUserAvatarID(id, avatarId);
837
-
838
-    changeAvatar(id, Avatar.getAvatarUrl(id));
839
-};
840
-
841
-/**
842
- * Update user avatar URL.
843
- * @param {string} id user id
844
- * @param {string} url user avatar url
801
+ * Updates the displayed avatar for participant.
802
+ *
803
+ * @param {string} id - User id whose avatar should be updated.
804
+ * @param {string} avatarURL - The URL to avatar image to display.
805
+ * @returns {void}
845 806
  */
846
-UI.setUserAvatarUrl = function(id, url) {
847
-    // update avatar
848
-    Avatar.setUserAvatarUrl(id, url);
849
-
850
-    changeAvatar(id, Avatar.getAvatarUrl(id));
807
+UI.refreshAvatarDisplay = function(id, avatarURL) {
808
+    VideoLayout.changeUserAvatar(id, avatarURL);
851 809
 };
852 810
 
853 811
 /**

+ 0
- 109
modules/UI/avatar/Avatar.js Bestand weergeven

@@ -1,109 +0,0 @@
1
-/*
2
- * Adorable Avatars service used at the end of this file is released under the
3
- * terms of the MIT License.
4
- *
5
- * Copyright (c) 2014 Adorable IO LLC
6
- * Permission is hereby granted, free of charge, to any person obtaining a copy
7
- * of this software and associated documentation files (the "Software"), to deal
8
- * in the Software without restriction, including without limitation the rights
9
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- * copies of the Software, and to permit persons to whom the Software is
11
- * furnished to do so, subject to the following conditions:
12
- * The above copyright notice and this permission notice shall be included in
13
- * all copies or substantial portions of the Software.
14
- *
15
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- * SOFTWARE.
22
- */
23
-
24
-/* global APP */
25
-
26
-import { getAvatarURL } from '../../../react/features/base/participants';
27
-
28
-const users = {};
29
-
30
-export default {
31
-    /**
32
-     * Sets prop in users object.
33
-     * @param id {string} user id or undefined for the local user.
34
-     * @param prop {string} name of the prop
35
-     * @param val {string} value to be set
36
-     */
37
-    _setUserProp(id, prop, val) {
38
-        // FIXME: Fixes the issue with not be able to return avatar for the
39
-        // local user when the conference has been left. Maybe there is beter
40
-        // way to solve it.
41
-        if (!id || APP.conference.isLocalId(id)) {
42
-            id = 'local';// eslint-disable-line no-param-reassign
43
-        }
44
-        if (!val || (users[id] && users[id][prop] === val)) {
45
-            return;
46
-        }
47
-        if (!users[id]) {
48
-            users[id] = {};
49
-        }
50
-        users[id][prop] = val;
51
-        APP.API.notifyAvatarChanged(
52
-            id === 'local' ? APP.conference.getMyUserId() : id,
53
-            this.getAvatarUrl(id)
54
-        );
55
-    },
56
-
57
-    /**
58
-     * Sets the user's avatar in the settings menu(if local user), contact list
59
-     * and thumbnail
60
-     * @param id id of the user
61
-     * @param email email or nickname to be used as a hash
62
-     */
63
-    setUserEmail(id, email) {
64
-        this._setUserProp(id, 'email', email);
65
-    },
66
-
67
-    /**
68
-     * Sets the user's avatar in the settings menu(if local user), contact list
69
-     * and thumbnail
70
-     * @param id id of the user
71
-     * @param url the url for the avatar
72
-     */
73
-    setUserAvatarUrl(id, url) {
74
-        this._setUserProp(id, 'avatarUrl', url);
75
-    },
76
-
77
-    /**
78
-     * Sets the user's avatar id.
79
-     * @param id id of the user
80
-     * @param avatarId an id to be used for the avatar
81
-     */
82
-    setUserAvatarID(id, avatarId) {
83
-        this._setUserProp(id, 'avatarId', avatarId);
84
-    },
85
-
86
-    /**
87
-     * Returns the URL of the image for the avatar of a particular user,
88
-     * identified by its id.
89
-     * @param {string} userId user id
90
-     */
91
-    getAvatarUrl(userId) {
92
-        let user;
93
-
94
-        if (!userId || APP.conference.isLocalId(userId)) {
95
-            user = users.local;
96
-            // eslint-disable-next-line no-param-reassign
97
-            userId = APP.conference.getMyUserId();
98
-        } else {
99
-            user = users[userId];
100
-        }
101
-
102
-        return getAvatarURL({
103
-            avatarID: user ? user.avatarId : undefined,
104
-            avatarURL: user ? user.avatarUrl : undefined,
105
-            email: user ? user.email : undefined,
106
-            id: userId
107
-        });
108
-    }
109
-};

+ 0
- 8
modules/UI/side_pannels/profile/Profile.js Bestand weergeven

@@ -142,14 +142,6 @@ export default {
142 142
         $('#setDisplayName').val(newDisplayName);
143 143
     },
144 144
 
145
-    /**
146
-     * Change user avatar in the settings menu.
147
-     * @param {string} avatarUrl url of the new avatar
148
-     */
149
-    changeAvatar(avatarUrl) {
150
-        $('#avatar').attr('src', avatarUrl);
151
-    },
152
-
153 145
     /**
154 146
      * Change the value of the field for the user email.
155 147
      * @param {string} email the new value that will be displayed in the field.

+ 5
- 3
modules/UI/videolayout/LargeVideoManager.js Bestand weergeven

@@ -12,11 +12,12 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
12 12
 import {
13 13
     JitsiParticipantConnectionStatus
14 14
 } from '../../../react/features/base/lib-jitsi-meet';
15
+import {
16
+    getAvatarURLByParticipantId
17
+} from '../../../react/features/base/participants';
15 18
 import {
16 19
     updateKnownLargeVideoResolution
17 20
 } from '../../../react/features/large-video';
18
-
19
-import Avatar from '../avatar/Avatar';
20 21
 import { createDeferred } from '../../util/helpers';
21 22
 import UIEvents from '../../../service/UI/UIEvents';
22 23
 import UIUtil from '../util/UIUtil';
@@ -219,7 +220,8 @@ export default class LargeVideoManager {
219 220
             container.setStream(id, stream, videoType);
220 221
 
221 222
             // change the avatar url on large
222
-            this.updateAvatar(Avatar.getAvatarUrl(id));
223
+            this.updateAvatar(
224
+                getAvatarURLByParticipantId(APP.store.getState(), id));
223 225
 
224 226
             // If the user's connection is disrupted then the avatar will be
225 227
             // displayed in case we have no video image cached. That is if

+ 9
- 0
modules/UI/videolayout/LocalVideo.js Bestand weergeven

@@ -7,6 +7,9 @@ import { Provider } from 'react-redux';
7 7
 
8 8
 import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
9 9
 import { VideoTrack } from '../../../react/features/base/media';
10
+import {
11
+    getAvatarURLByParticipantId
12
+} from '../../../react/features/base/participants';
10 13
 /* eslint-enable no-unused-vars */
11 14
 
12 15
 const logger = require('jitsi-meet-logger').getLogger(__filename);
@@ -46,6 +49,12 @@ function LocalVideo(VideoLayout, emitter) {
46 49
     // Set default display name.
47 50
     this.setDisplayName();
48 51
 
52
+    // Initialize the avatar display with an avatar url selected from the redux
53
+    // state. Redux stores the local user with a hardcoded participant id of
54
+    // 'local' if no id has been assigned yet.
55
+    this.avatarChanged(
56
+        getAvatarURLByParticipantId(APP.store.getState(), this.id));
57
+
49 58
     this.addAudioLevelIndicator();
50 59
     this.updateIndicators();
51 60
 

+ 4
- 3
modules/UI/videolayout/SmallVideo.js Bestand weergeven

@@ -11,7 +11,8 @@ import { i18next } from '../../../react/features/base/i18n';
11 11
 import { AudioLevelIndicator }
12 12
     from '../../../react/features/audio-level-indicator';
13 13
 import {
14
-    Avatar as AvatarDisplay
14
+    Avatar as AvatarDisplay,
15
+    getAvatarURLByParticipantId
15 16
 } from '../../../react/features/base/participants';
16 17
 import {
17 18
     ConnectionIndicator
@@ -28,7 +29,6 @@ import {
28 29
 
29 30
 const logger = require('jitsi-meet-logger').getLogger(__filename);
30 31
 
31
-import Avatar from '../avatar/Avatar';
32 32
 import UIUtil from '../util/UIUtil';
33 33
 import UIEvents from '../../../service/UI/UIEvents';
34 34
 
@@ -590,7 +590,8 @@ SmallVideo.prototype.updateView = function() {
590 590
     if (!this.hasAvatar) {
591 591
         if (this.id) {
592 592
             // Init avatar
593
-            this.avatarChanged(Avatar.getAvatarUrl(this.id));
593
+            this.avatarChanged(
594
+                getAvatarURLByParticipantId(APP.store.getState(), this.id));
594 595
         } else {
595 596
             logger.error('Unable to init avatar - no id', this);
596 597
 

+ 27
- 1
react/features/base/participants/functions.js Bestand weergeven

@@ -3,7 +3,10 @@ import md5 from 'js-md5';
3 3
 
4 4
 import { toState } from '../redux';
5 5
 
6
-import { DEFAULT_AVATAR_RELATIVE_PATH } from './constants';
6
+import {
7
+    DEFAULT_AVATAR_RELATIVE_PATH,
8
+    LOCAL_PARTICIPANT_DEFAULT_ID
9
+} from './constants';
7 10
 
8 11
 declare var config: Object;
9 12
 declare var interfaceConfig: Object;
@@ -74,6 +77,29 @@ export function getAvatarURL({ avatarID, avatarURL, email, id }: {
74 77
     return urlPrefix + md5.hex(key.trim().toLowerCase()) + urlSuffix;
75 78
 }
76 79
 
80
+/**
81
+ * Returns the avatarURL for the participant associated with the passed in
82
+ * participant ID.
83
+ *
84
+ * @param {(Function|Object|Participant[])} stateful - The redux state
85
+ * features/base/participants, the (whole) redux state, or redux's
86
+ * {@code getState} function to be used to retrieve the state
87
+ * features/base/participants.
88
+ * @param {string} id - The ID of the participant to retrieve.
89
+ * @param {boolean} isLocal - An optional parameter indicating whether or not
90
+ * the partcipant id is for the local user. If true, a different logic flow is
91
+ * used find the local user, ignoring the id value as it can change through the
92
+ * beginning and end of a call.
93
+ * @returns {(string|undefined)}
94
+ */
95
+export function getAvatarURLByParticipantId(
96
+        stateful: Object | Function,
97
+        id: string = LOCAL_PARTICIPANT_DEFAULT_ID) {
98
+    const participant = getParticipantById(stateful, id);
99
+
100
+    return participant && getAvatarURL(participant);
101
+}
102
+
77 103
 /**
78 104
  * Returns local participant from Redux state.
79 105
  *

+ 39
- 2
react/features/base/participants/middleware.js Bestand weergeven

@@ -12,10 +12,15 @@ import { localParticipantIdChanged } from './actions';
12 12
 import {
13 13
     KICK_PARTICIPANT,
14 14
     MUTE_REMOTE_PARTICIPANT,
15
-    PARTICIPANT_DISPLAY_NAME_CHANGED
15
+    PARTICIPANT_DISPLAY_NAME_CHANGED,
16
+    PARTICIPANT_JOINED,
17
+    PARTICIPANT_UPDATED
16 18
 } from './actionTypes';
17 19
 import { LOCAL_PARTICIPANT_DEFAULT_ID } from './constants';
18
-import { getLocalParticipant } from './functions';
20
+import {
21
+    getAvatarURLByParticipantId,
22
+    getLocalParticipant
23
+} from './functions';
19 24
 
20 25
 declare var APP: Object;
21 26
 
@@ -59,6 +64,38 @@ MiddlewareRegistry.register(store => next => action => {
59 64
 
60 65
         break;
61 66
     }
67
+
68
+    case PARTICIPANT_JOINED:
69
+    case PARTICIPANT_UPDATED: {
70
+        if (typeof APP !== 'undefined') {
71
+            const participant = action.participant;
72
+            const { id, local } = participant;
73
+
74
+            const preUpdateAvatarURL
75
+                = getAvatarURLByParticipantId(store.getState(), id);
76
+
77
+            // Allow the redux update to go through and compare the old avatar
78
+            // to the new avatar and emit out change events if necessary.
79
+            const result = next(action);
80
+
81
+            const postUpdateAvatarURL
82
+                = getAvatarURLByParticipantId(store.getState(), id);
83
+
84
+            if (preUpdateAvatarURL !== postUpdateAvatarURL) {
85
+                const currentKnownId = local
86
+                    ? APP.conference.getMyUserId() : id;
87
+
88
+                APP.UI.refreshAvatarDisplay(
89
+                    currentKnownId, postUpdateAvatarURL);
90
+                APP.API.notifyAvatarChanged(
91
+                    currentKnownId, postUpdateAvatarURL);
92
+            }
93
+
94
+            return result;
95
+        }
96
+
97
+        break;
98
+    }
62 99
     }
63 100
 
64 101
     return next(action);

+ 18
- 3
react/features/toolbox/components/ProfileButton.web.js Bestand weergeven

@@ -5,7 +5,10 @@ import React, { Component } from 'react';
5 5
 import { connect } from 'react-redux';
6 6
 
7 7
 import { TOOLBAR_PROFILE_TOGGLED, sendAnalyticsEvent } from '../../analytics';
8
-import { DEFAULT_AVATAR_RELATIVE_PATH } from '../../base/participants';
8
+import {
9
+    getAvatarURL,
10
+    getLocalParticipant
11
+} from '../../base/participants';
9 12
 import UIEvents from '../../../../service/UI/UIEvents';
10 13
 
11 14
 import ToolbarButton from './ToolbarButton';
@@ -39,6 +42,11 @@ class ProfileButton extends Component<*> {
39 42
      * @static
40 43
      */
41 44
     static propTypes = {
45
+        /**
46
+         * The redux representation of the local participant.
47
+         */
48
+        _localParticipant: PropTypes.object,
49
+
42 50
         /**
43 51
          * Whether the button support clicking or not.
44 52
          */
@@ -76,7 +84,12 @@ class ProfileButton extends Component<*> {
76 84
      * @returns {ReactElement}
77 85
      */
78 86
     render() {
79
-        const { _unclickable, tooltipPosition, toggled } = this.props;
87
+        const {
88
+            _localParticipant,
89
+            _unclickable,
90
+            tooltipPosition,
91
+            toggled
92
+        } = this.props;
80 93
         const buttonConfiguration = {
81 94
             ...DEFAULT_BUTTON_CONFIGURATION,
82 95
             unclickable: _unclickable,
@@ -90,7 +103,7 @@ class ProfileButton extends Component<*> {
90 103
                 tooltipPosition = { tooltipPosition }>
91 104
                 <img
92 105
                     id = 'avatar'
93
-                    src = { DEFAULT_AVATAR_RELATIVE_PATH } />
106
+                    src = { getAvatarURL(_localParticipant) } />
94 107
             </ToolbarButton>
95 108
         );
96 109
     }
@@ -115,11 +128,13 @@ class ProfileButton extends Component<*> {
115 128
  * @param {Object} state - The Redux state.
116 129
  * @private
117 130
  * @returns {{
131
+ *     _localParticipant: Object,
118 132
  *     _unclickable: boolean
119 133
  * }}
120 134
  */
121 135
 function _mapStateToProps(state) {
122 136
     return {
137
+        _localParticipant: getLocalParticipant(state),
123 138
         _unclickable: !state['features/base/jwt'].isGuest
124 139
     };
125 140
 }

Laden…
Annuleren
Opslaan