Kaynağa Gözat

fix(thumbnail): Optimize status bar moderator icon (#5076)

* fix(thumbnail): Optimize status bar moderator icon

Moved all moderator functionality to react to optimize the number of
status bar updates.

* fix(RemoteVideoMenuTriggerButton): Use nullish coalescing

Co-Authored-By: Saúl Ibarra Corretgé <saghul@jitsi.org>

* ref(StatusBar): rename to StatusIndicators

* fix(RemoteVideoMenu): isModerator value.

* fix(notification): mobile.

Co-authored-by: Saúl Ibarra Corretgé <s@saghul.net>
master
Hristo Terezov 5 yıl önce
ebeveyn
işleme
bbf1927c70
No account linked to committer's email address

+ 0
- 23
conference.js Dosyayı Görüntüle

@@ -435,7 +435,6 @@ export default {
435 435
      * the tracks won't exist).
436 436
      */
437 437
     _localTracksInitialized: false,
438
-    isModerator: false,
439 438
     isSharingScreen: false,
440 439
 
441 440
     /**
@@ -926,14 +925,6 @@ export default {
926 925
         this.muteVideo(!this.isLocalVideoMuted(), showUI);
927 926
     },
928 927
 
929
-    /**
930
-     * Retrieve list of conference participants (without local user).
931
-     * @returns {JitsiParticipant[]}
932
-     */
933
-    listMembers() {
934
-        return room.getParticipants();
935
-    },
936
-
937 928
     /**
938 929
      * Retrieve list of ids of conference participants (without local user).
939 930
      * @returns {string[]}
@@ -1894,9 +1885,6 @@ export default {
1894 1885
 
1895 1886
             logger.log(`USER ${id} connnected:`, user);
1896 1887
             APP.UI.addUser(user);
1897
-
1898
-            // check the roles for the new user and reflect them
1899
-            APP.UI.updateUserRole(user);
1900 1888
         });
1901 1889
 
1902 1890
         room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
@@ -1927,19 +1915,8 @@ export default {
1927 1915
                 logger.info(`My role changed, new role: ${role}`);
1928 1916
 
1929 1917
                 APP.store.dispatch(localParticipantRoleChanged(role));
1930
-
1931
-                if (this.isModerator !== room.isModerator()) {
1932
-                    this.isModerator = room.isModerator();
1933
-                    APP.UI.updateLocalRole(room.isModerator());
1934
-                }
1935 1918
             } else {
1936 1919
                 APP.store.dispatch(participantRoleChanged(id, role));
1937
-
1938
-                const user = room.getParticipantById(id);
1939
-
1940
-                if (user) {
1941
-                    APP.UI.updateUserRole(user);
1942
-                }
1943 1920
             }
1944 1921
         });
1945 1922
 

+ 0
- 42
modules/UI/UI.js Dosyayı Görüntüle

@@ -126,10 +126,6 @@ UI.initConference = function() {
126 126
     const { getState } = APP.store;
127 127
     const { id, name } = getLocalParticipant(getState);
128 128
 
129
-    // Update default button states before showing the toolbar
130
-    // if local role changes buttons state will be again updated.
131
-    UI.updateLocalRole(APP.conference.isModerator);
132
-
133 129
     UI.showToolbar();
134 130
 
135 131
     const displayName = config.displayJids ? id : name;
@@ -279,44 +275,6 @@ UI.addUser = function(user) {
279 275
 UI.onPeerVideoTypeChanged
280 276
     = (id, newVideoType) => VideoLayout.onVideoTypeChanged(id, newVideoType);
281 277
 
282
-/**
283
- * Update local user role and show notification if user is moderator.
284
- * @param {boolean} isModerator if local user is moderator or not
285
- */
286
-UI.updateLocalRole = isModerator => {
287
-    VideoLayout.showModeratorIndicator();
288
-
289
-    if (isModerator && !interfaceConfig.DISABLE_FOCUS_INDICATOR) {
290
-        messageHandler.participantNotification(
291
-            null, 'notify.me', 'connected', 'notify.moderator');
292
-    }
293
-};
294
-
295
-/**
296
- * Check the role for the user and reflect it in the UI, moderator ui indication
297
- * and notifies user who is the moderator
298
- * @param user to check for moderator
299
- */
300
-UI.updateUserRole = user => {
301
-    VideoLayout.showModeratorIndicator();
302
-
303
-    // We don't need to show moderator notifications when the focus (moderator)
304
-    // indicator is disabled.
305
-    if (!user.isModerator() || interfaceConfig.DISABLE_FOCUS_INDICATOR) {
306
-        return;
307
-    }
308
-
309
-    const displayName = user.getDisplayName();
310
-
311
-    messageHandler.participantNotification(
312
-        displayName,
313
-        'notify.somebody',
314
-        'connected',
315
-        'notify.grantedTo',
316
-        { to: displayName
317
-            ? UIUtil.escapeHtml(displayName) : '$t(notify.somebody)' });
318
-};
319
-
320 278
 /**
321 279
  * Updates the user status.
322 280
  *

+ 1
- 2
modules/UI/videolayout/RemoteVideo.js Dosyayı Görüntüle

@@ -129,6 +129,7 @@ export default class RemoteVideo extends SmallVideo {
129 129
         this._setThumbnailSize();
130 130
         this.initBrowserSpecificProperties();
131 131
         this.updateRemoteVideoMenu();
132
+        this.updateStatusBar();
132 133
         this.addAudioLevelIndicator();
133 134
         this.addPresenceLabel();
134 135
 
@@ -187,7 +188,6 @@ export default class RemoteVideo extends SmallVideo {
187 188
         // hide volume when in silent mode
188 189
         const onVolumeChange
189 190
             = APP.store.getState()['features/base/config'].startSilent ? undefined : this._setAudioVolume;
190
-        const { isModerator } = APP.conference;
191 191
         const participantID = this.id;
192 192
         const currentLayout = getCurrentLayout(APP.store.getState());
193 193
         let remoteMenuPosition;
@@ -207,7 +207,6 @@ export default class RemoteVideo extends SmallVideo {
207 207
                         <RemoteVideoMenuTriggerButton
208 208
                             initialVolumeValue = { initialVolumeValue }
209 209
                             isAudioMuted = { this.isAudioMuted }
210
-                            isModerator = { isModerator }
211 210
                             menuPosition = { remoteMenuPosition }
212 211
                             onMenuDisplay
213 212
                                 = {this._onRemoteVideoMenuDisplay.bind(this)}

+ 9
- 47
modules/UI/videolayout/SmallVideo.js Dosyayı Görüntüle

@@ -18,11 +18,9 @@ import {
18 18
 import { ConnectionIndicator } from '../../../react/features/connection-indicator';
19 19
 import { DisplayName } from '../../../react/features/display-name';
20 20
 import {
21
-    AudioMutedIndicator,
22 21
     DominantSpeakerIndicator,
23
-    ModeratorIndicator,
24 22
     RaisedHandIndicator,
25
-    VideoMutedIndicator
23
+    StatusIndicators
26 24
 } from '../../../react/features/filmstrip';
27 25
 import {
28 26
     LAYOUTS,
@@ -84,7 +82,6 @@ export default class SmallVideo {
84 82
      * Constructor.
85 83
      */
86 84
     constructor(VideoLayout) {
87
-        this._isModerator = false;
88 85
         this.isAudioMuted = false;
89 86
         this.hasAvatar = false;
90 87
         this.isVideoMuted = false;
@@ -286,45 +283,18 @@ export default class SmallVideo {
286 283
             return;
287 284
         }
288 285
 
289
-        const currentLayout = getCurrentLayout(APP.store.getState());
290
-        let tooltipPosition;
291
-
292
-        if (currentLayout === LAYOUTS.TILE_VIEW) {
293
-            tooltipPosition = 'right';
294
-        } else if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
295
-            tooltipPosition = 'left';
296
-        } else {
297
-            tooltipPosition = 'top';
298
-        }
299
-
300 286
         ReactDOM.render(
301
-            <I18nextProvider i18n = { i18next }>
302
-                <div>
303
-                    { this.isAudioMuted
304
-                        ? <AudioMutedIndicator
305
-                            tooltipPosition = { tooltipPosition } />
306
-                        : null }
307
-                    { this.isVideoMuted
308
-                        ? <VideoMutedIndicator
309
-                            tooltipPosition = { tooltipPosition } />
310
-                        : null }
311
-                    { this._isModerator && !interfaceConfig.DISABLE_FOCUS_INDICATOR
312
-                        ? <ModeratorIndicator
313
-                            tooltipPosition = { tooltipPosition } />
314
-                        : null }
315
-                </div>
316
-            </I18nextProvider>,
287
+            <Provider store = { APP.store }>
288
+                <I18nextProvider i18n = { i18next }>
289
+                    <StatusIndicators
290
+                        showAudioMutedIndicator = { this.isAudioMuted }
291
+                        showVideoMutedIndicator = { this.isVideoMuted }
292
+                        participantID = { this.id } />
293
+                </I18nextProvider>
294
+            </Provider>,
317 295
             statusBarContainer);
318 296
     }
319 297
 
320
-    /**
321
-     * Adds the element indicating the moderator(owner) of the conference.
322
-     */
323
-    addModeratorIndicator() {
324
-        this._isModerator = true;
325
-        this.updateStatusBar();
326
-    }
327
-
328 298
     /**
329 299
      * Adds the element indicating the audio level of the participant.
330 300
      *
@@ -380,14 +350,6 @@ export default class SmallVideo {
380 350
         return this.container.querySelector('.audioindicator-container');
381 351
     }
382 352
 
383
-    /**
384
-     * Removes the element indicating the moderator(owner) of the conference.
385
-     */
386
-    removeModeratorIndicator() {
387
-        this._isModerator = false;
388
-        this.updateStatusBar();
389
-    }
390
-
391 353
     /**
392 354
      * This is an especially interesting function. A naive reader might think that
393 355
      * it returns this SmallVideo's "video" element. But it is much more exciting.

+ 4
- 34
modules/UI/videolayout/VideoLayout.js Dosyayı Görüntüle

@@ -174,9 +174,9 @@ const VideoLayout = {
174 174
 
175 175
         // Make sure track's muted state is reflected
176 176
         if (stream.getType() === 'audio') {
177
-            this.onAudioMute(stream.getParticipantId(), stream.isMuted());
177
+            this.onAudioMute(id, stream.isMuted());
178 178
         } else {
179
-            this.onVideoMute(stream.getParticipantId(), stream.isMuted());
179
+            this.onVideoMute(id, stream.isMuted());
180 180
         }
181 181
     },
182 182
 
@@ -204,8 +204,7 @@ const VideoLayout = {
204 204
     updateMutedForNoTracks(participantId, mediaType) {
205 205
         const participant = APP.conference.getParticipantById(participantId);
206 206
 
207
-        if (participant
208
-                && !participant.getTracksByMediaType(mediaType).length) {
207
+        if (participant && !participant.getTracksByMediaType(mediaType).length) {
209 208
             if (mediaType === 'audio') {
210 209
                 APP.UI.setAudioMuted(participantId, true);
211 210
             } else if (mediaType === 'video') {
@@ -328,35 +327,6 @@ const VideoLayout = {
328 327
         this._updateLargeVideoIfDisplayed(resourceJid, true);
329 328
     },
330 329
 
331
-    /**
332
-     * Shows a visual indicator for the moderator of the conference.
333
-     * On local or remote participants.
334
-     */
335
-    showModeratorIndicator() {
336
-        const isModerator = APP.conference.isModerator;
337
-
338
-        if (isModerator) {
339
-            localVideoThumbnail.addModeratorIndicator();
340
-        } else {
341
-            localVideoThumbnail.removeModeratorIndicator();
342
-        }
343
-
344
-        APP.conference.listMembers().forEach(member => {
345
-            const id = member.getId();
346
-            const remoteVideo = remoteVideos[id];
347
-
348
-            if (!remoteVideo) {
349
-                return;
350
-            }
351
-
352
-            if (member.isModerator()) {
353
-                remoteVideo.addModeratorIndicator();
354
-            }
355
-
356
-            remoteVideo.updateRemoteVideoMenu();
357
-        });
358
-    },
359
-
360 330
     /**
361 331
      * On audio muted event.
362 332
      */
@@ -371,7 +341,7 @@ const VideoLayout = {
371 341
             }
372 342
 
373 343
             remoteVideo.showAudioIndicator(isMuted);
374
-            remoteVideo.updateRemoteVideoMenu(isMuted);
344
+            remoteVideo.updateRemoteVideoMenu();
375 345
         }
376 346
     },
377 347
 

+ 112
- 0
react/features/filmstrip/components/web/StatusIndicators.js Dosyayı Görüntüle

@@ -0,0 +1,112 @@
1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+
5
+import { getLocalParticipant, getParticipantById, PARTICIPANT_ROLE } from '../../../base/participants';
6
+import { connect } from '../../../base/redux';
7
+import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
8
+
9
+import AudioMutedIndicator from './AudioMutedIndicator';
10
+import ModeratorIndicator from './ModeratorIndicator';
11
+import VideoMutedIndicator from './VideoMutedIndicator';
12
+
13
+declare var interfaceConfig: Object;
14
+
15
+/**
16
+ * The type of the React {@code Component} props of {@link StatusIndicators}.
17
+ */
18
+type Props = {
19
+
20
+    /**
21
+     * The current layout of the filmstrip.
22
+     */
23
+    _currentLayout: string,
24
+
25
+    /**
26
+     * Indicates if the moderator indicator should be visible or not.
27
+     */
28
+    _showModeratorIndicator: Boolean,
29
+
30
+    /**
31
+     * Indicates if the audio muted indicator should be visible or not.
32
+     */
33
+    showAudioMutedIndicator: Boolean,
34
+
35
+    /**
36
+     * Indicates if the video muted indicator should be visible or not.
37
+     */
38
+    showVideoMutedIndicator: Boolean,
39
+
40
+    /**
41
+     * The ID of the participant for which the status bar is rendered.
42
+     */
43
+    participantID: String
44
+};
45
+
46
+/**
47
+ * React {@code Component} for showing the status bar in a thumbnail.
48
+ *
49
+ * @extends Component
50
+ */
51
+class StatusIndicators extends Component<Props> {
52
+    /**
53
+     * Implements React's {@link Component#render()}.
54
+     *
55
+     * @inheritdoc
56
+     * @returns {ReactElement}
57
+     */
58
+    render() {
59
+        const {
60
+            _currentLayout,
61
+            _showModeratorIndicator,
62
+            showAudioMutedIndicator,
63
+            showVideoMutedIndicator
64
+        } = this.props;
65
+        let tooltipPosition;
66
+
67
+        switch (_currentLayout) {
68
+        case LAYOUTS.TILE_VIEW:
69
+            tooltipPosition = 'right';
70
+            break;
71
+        case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
72
+            tooltipPosition = 'left';
73
+            break;
74
+        default:
75
+            tooltipPosition = 'top';
76
+        }
77
+
78
+        return (
79
+            <div>
80
+                { showAudioMutedIndicator ? <AudioMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
81
+                { showVideoMutedIndicator ? <VideoMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
82
+                { _showModeratorIndicator ? <ModeratorIndicator tooltipPosition = { tooltipPosition } /> : null }
83
+            </div>
84
+        );
85
+    }
86
+}
87
+
88
+/**
89
+ * Maps (parts of) the Redux state to the associated {@code StatusIndicators}'s props.
90
+ *
91
+ * @param {Object} state - The Redux state.
92
+ * @param {Object} ownProps - The own props of the component.
93
+ * @private
94
+ * @returns {{
95
+ *     _currentLayout: string,
96
+ *     _showModeratorIndicator: boolean
97
+ * }}
98
+*/
99
+function _mapStateToProps(state, ownProps) {
100
+    const { participantID } = ownProps;
101
+
102
+    // Only the local participant won't have id for the time when the conference is not yet joined.
103
+    const participant = participantID ? getParticipantById(state, participantID) : getLocalParticipant(state);
104
+
105
+    return {
106
+        _currentLayout: getCurrentLayout(state),
107
+        _showModeratorIndicator:
108
+            !interfaceConfig.DISABLE_FOCUS_INDICATOR && participant && participant.role === PARTICIPANT_ROLE.MODERATOR
109
+    };
110
+}
111
+
112
+export default connect(_mapStateToProps)(StatusIndicators);

+ 2
- 2
react/features/filmstrip/components/web/index.js Dosyayı Görüntüle

@@ -1,9 +1,9 @@
1 1
 // @flow
2 2
 
3 3
 export { default as AudioMutedIndicator } from './AudioMutedIndicator';
4
-export { default as DominantSpeakerIndicator }
5
-    from './DominantSpeakerIndicator';
4
+export { default as DominantSpeakerIndicator } from './DominantSpeakerIndicator';
6 5
 export { default as Filmstrip } from './Filmstrip';
7 6
 export { default as ModeratorIndicator } from './ModeratorIndicator';
8 7
 export { default as RaisedHandIndicator } from './RaisedHandIndicator';
8
+export { default as StatusIndicators } from './StatusIndicators';
9 9
 export { default as VideoMutedIndicator } from './VideoMutedIndicator';

+ 43
- 3
react/features/notifications/middleware.js Dosyayı Görüntüle

@@ -4,6 +4,8 @@ import { getCurrentConference } from '../base/conference';
4 4
 import {
5 5
     PARTICIPANT_JOINED,
6 6
     PARTICIPANT_LEFT,
7
+    PARTICIPANT_ROLE,
8
+    PARTICIPANT_UPDATED,
7 9
     getParticipantById,
8 10
     getParticipantDisplayName
9 11
 } from '../base/participants';
@@ -29,15 +31,29 @@ MiddlewareRegistry.register(store => next => action => {
29 31
     switch (action.type) {
30 32
     case PARTICIPANT_JOINED: {
31 33
         const result = next(action);
32
-
33 34
         const { participant: p } = action;
35
+        const { dispatch, getState } = store;
34 36
 
35 37
         if (!p.local && !joinLeaveNotificationsDisabled()) {
36
-            store.dispatch(showParticipantJoinedNotification(
37
-                getParticipantDisplayName(store.getState, p.id)
38
+            dispatch(showParticipantJoinedNotification(
39
+                getParticipantDisplayName(getState, p.id)
38 40
             ));
39 41
         }
40 42
 
43
+        if (typeof interfaceConfig === 'object'
44
+                && !interfaceConfig.DISABLE_FOCUS_INDICATOR && p.role === PARTICIPANT_ROLE.MODERATOR) {
45
+            // Do not show the notification for mobile and also when the focus indicator is disabled.
46
+            const displayName = getParticipantDisplayName(getState, p.id);
47
+
48
+            dispatch(showNotification({
49
+                descriptionArguments: { to: displayName || '$t(notify.somebody)' },
50
+                descriptionKey: 'notify.grantedTo',
51
+                titleKey: 'notify.somebody',
52
+                title: displayName
53
+            },
54
+            NOTIFICATION_TIMEOUT));
55
+        }
56
+
41 57
         return result;
42 58
     }
43 59
     case PARTICIPANT_LEFT: {
@@ -60,6 +76,30 @@ MiddlewareRegistry.register(store => next => action => {
60 76
 
61 77
         return next(action);
62 78
     }
79
+    case PARTICIPANT_UPDATED: {
80
+        if (typeof interfaceConfig === 'undefined' || interfaceConfig.DISABLE_FOCUS_INDICATOR) {
81
+            // Do not show the notification for mobile and also when the focus indicator is disabled.
82
+            return next(action);
83
+        }
84
+
85
+        const { id, role } = action.participant;
86
+        const state = store.getState();
87
+        const { role: oldRole } = getParticipantById(state, id);
88
+
89
+        if (oldRole !== role && role === PARTICIPANT_ROLE.MODERATOR) {
90
+            const displayName = getParticipantDisplayName(state, id);
91
+
92
+            store.dispatch(showNotification({
93
+                descriptionArguments: { to: displayName || '$t(notify.somebody)' },
94
+                descriptionKey: 'notify.grantedTo',
95
+                titleKey: 'notify.somebody',
96
+                title: displayName
97
+            },
98
+            NOTIFICATION_TIMEOUT));
99
+        }
100
+
101
+        return next(action);
102
+    }
63 103
     }
64 104
 
65 105
     return next(action);

+ 28
- 8
react/features/remote-video-menu/components/web/RemoteVideoMenuTriggerButton.js Dosyayı Görüntüle

@@ -3,7 +3,9 @@
3 3
 import React, { Component } from 'react';
4 4
 
5 5
 import { Icon, IconMenuThumb } from '../../../base/icons';
6
+import { getLocalParticipant, PARTICIPANT_ROLE } from '../../../base/participants';
6 7
 import { Popover } from '../../../base/popover';
8
+import { connect } from '../../../base/redux';
7 9
 
8 10
 import {
9 11
     MuteButton,
@@ -23,6 +25,11 @@ declare var interfaceConfig: Object;
23 25
  */
24 26
 type Props = {
25 27
 
28
+    /**
29
+     * Whether or not the participant is a conference moderator.
30
+     */
31
+    _isModerator: boolean,
32
+
26 33
     /**
27 34
      * A value between 0 and 1 indicating the volume of the participant's
28 35
      * audio element.
@@ -34,11 +41,6 @@ type Props = {
34 41
      */
35 42
     isAudioMuted: boolean,
36 43
 
37
-    /**
38
-     * Whether or not the participant is a conference moderator.
39
-     */
40
-    isModerator: boolean,
41
-
42 44
     /**
43 45
      * Callback to invoke when the popover has been displayed.
44 46
      */
@@ -154,9 +156,9 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
154 156
      */
155 157
     _renderRemoteVideoMenu() {
156 158
         const {
159
+            _isModerator,
157 160
             initialVolumeValue,
158 161
             isAudioMuted,
159
-            isModerator,
160 162
             onRemoteControlToggle,
161 163
             onVolumeChange,
162 164
             remoteControlState,
@@ -165,7 +167,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
165 167
 
166 168
         const buttons = [];
167 169
 
168
-        if (isModerator) {
170
+        if (_isModerator) {
169 171
             buttons.push(
170 172
                 <MuteButton
171 173
                     isAudioMuted = { isAudioMuted }
@@ -216,4 +218,22 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
216 218
     }
217 219
 }
218 220
 
219
-export default RemoteVideoMenuTriggerButton;
221
+/**
222
+ * Maps (parts of) the Redux state to the associated {@code RemoteVideoMenuTriggerButton}'s props.
223
+ *
224
+ * @param {Object} state - The Redux state.
225
+ * @param {Object} ownProps - The own props of the component.
226
+ * @private
227
+ * @returns {{
228
+ *     _isModerator: boolean
229
+ * }}
230
+ */
231
+function _mapStateToProps(state) {
232
+    const participant = getLocalParticipant(state);
233
+
234
+    return {
235
+        _isModerator: Boolean(participant?.role === PARTICIPANT_ROLE.MODERATOR)
236
+    };
237
+}
238
+
239
+export default connect(_mapStateToProps)(RemoteVideoMenuTriggerButton);

Loading…
İptal
Kaydet