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>
This commit is contained in:
parent
86130c1478
commit
bbf1927c70
|
@ -435,7 +435,6 @@ export default {
|
||||||
* the tracks won't exist).
|
* the tracks won't exist).
|
||||||
*/
|
*/
|
||||||
_localTracksInitialized: false,
|
_localTracksInitialized: false,
|
||||||
isModerator: false,
|
|
||||||
isSharingScreen: false,
|
isSharingScreen: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -926,14 +925,6 @@ export default {
|
||||||
this.muteVideo(!this.isLocalVideoMuted(), showUI);
|
this.muteVideo(!this.isLocalVideoMuted(), showUI);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve list of conference participants (without local user).
|
|
||||||
* @returns {JitsiParticipant[]}
|
|
||||||
*/
|
|
||||||
listMembers() {
|
|
||||||
return room.getParticipants();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve list of ids of conference participants (without local user).
|
* Retrieve list of ids of conference participants (without local user).
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
|
@ -1894,9 +1885,6 @@ export default {
|
||||||
|
|
||||||
logger.log(`USER ${id} connnected:`, user);
|
logger.log(`USER ${id} connnected:`, user);
|
||||||
APP.UI.addUser(user);
|
APP.UI.addUser(user);
|
||||||
|
|
||||||
// check the roles for the new user and reflect them
|
|
||||||
APP.UI.updateUserRole(user);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
|
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
|
||||||
|
@ -1927,19 +1915,8 @@ export default {
|
||||||
logger.info(`My role changed, new role: ${role}`);
|
logger.info(`My role changed, new role: ${role}`);
|
||||||
|
|
||||||
APP.store.dispatch(localParticipantRoleChanged(role));
|
APP.store.dispatch(localParticipantRoleChanged(role));
|
||||||
|
|
||||||
if (this.isModerator !== room.isModerator()) {
|
|
||||||
this.isModerator = room.isModerator();
|
|
||||||
APP.UI.updateLocalRole(room.isModerator());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
APP.store.dispatch(participantRoleChanged(id, role));
|
APP.store.dispatch(participantRoleChanged(id, role));
|
||||||
|
|
||||||
const user = room.getParticipantById(id);
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
APP.UI.updateUserRole(user);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -126,10 +126,6 @@ UI.initConference = function() {
|
||||||
const { getState } = APP.store;
|
const { getState } = APP.store;
|
||||||
const { id, name } = getLocalParticipant(getState);
|
const { id, name } = getLocalParticipant(getState);
|
||||||
|
|
||||||
// Update default button states before showing the toolbar
|
|
||||||
// if local role changes buttons state will be again updated.
|
|
||||||
UI.updateLocalRole(APP.conference.isModerator);
|
|
||||||
|
|
||||||
UI.showToolbar();
|
UI.showToolbar();
|
||||||
|
|
||||||
const displayName = config.displayJids ? id : name;
|
const displayName = config.displayJids ? id : name;
|
||||||
|
@ -279,44 +275,6 @@ UI.addUser = function(user) {
|
||||||
UI.onPeerVideoTypeChanged
|
UI.onPeerVideoTypeChanged
|
||||||
= (id, newVideoType) => VideoLayout.onVideoTypeChanged(id, newVideoType);
|
= (id, newVideoType) => VideoLayout.onVideoTypeChanged(id, newVideoType);
|
||||||
|
|
||||||
/**
|
|
||||||
* Update local user role and show notification if user is moderator.
|
|
||||||
* @param {boolean} isModerator if local user is moderator or not
|
|
||||||
*/
|
|
||||||
UI.updateLocalRole = isModerator => {
|
|
||||||
VideoLayout.showModeratorIndicator();
|
|
||||||
|
|
||||||
if (isModerator && !interfaceConfig.DISABLE_FOCUS_INDICATOR) {
|
|
||||||
messageHandler.participantNotification(
|
|
||||||
null, 'notify.me', 'connected', 'notify.moderator');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the role for the user and reflect it in the UI, moderator ui indication
|
|
||||||
* and notifies user who is the moderator
|
|
||||||
* @param user to check for moderator
|
|
||||||
*/
|
|
||||||
UI.updateUserRole = user => {
|
|
||||||
VideoLayout.showModeratorIndicator();
|
|
||||||
|
|
||||||
// We don't need to show moderator notifications when the focus (moderator)
|
|
||||||
// indicator is disabled.
|
|
||||||
if (!user.isModerator() || interfaceConfig.DISABLE_FOCUS_INDICATOR) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayName = user.getDisplayName();
|
|
||||||
|
|
||||||
messageHandler.participantNotification(
|
|
||||||
displayName,
|
|
||||||
'notify.somebody',
|
|
||||||
'connected',
|
|
||||||
'notify.grantedTo',
|
|
||||||
{ to: displayName
|
|
||||||
? UIUtil.escapeHtml(displayName) : '$t(notify.somebody)' });
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the user status.
|
* Updates the user status.
|
||||||
*
|
*
|
||||||
|
|
|
@ -129,6 +129,7 @@ export default class RemoteVideo extends SmallVideo {
|
||||||
this._setThumbnailSize();
|
this._setThumbnailSize();
|
||||||
this.initBrowserSpecificProperties();
|
this.initBrowserSpecificProperties();
|
||||||
this.updateRemoteVideoMenu();
|
this.updateRemoteVideoMenu();
|
||||||
|
this.updateStatusBar();
|
||||||
this.addAudioLevelIndicator();
|
this.addAudioLevelIndicator();
|
||||||
this.addPresenceLabel();
|
this.addPresenceLabel();
|
||||||
|
|
||||||
|
@ -187,7 +188,6 @@ export default class RemoteVideo extends SmallVideo {
|
||||||
// hide volume when in silent mode
|
// hide volume when in silent mode
|
||||||
const onVolumeChange
|
const onVolumeChange
|
||||||
= APP.store.getState()['features/base/config'].startSilent ? undefined : this._setAudioVolume;
|
= APP.store.getState()['features/base/config'].startSilent ? undefined : this._setAudioVolume;
|
||||||
const { isModerator } = APP.conference;
|
|
||||||
const participantID = this.id;
|
const participantID = this.id;
|
||||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||||
let remoteMenuPosition;
|
let remoteMenuPosition;
|
||||||
|
@ -207,7 +207,6 @@ export default class RemoteVideo extends SmallVideo {
|
||||||
<RemoteVideoMenuTriggerButton
|
<RemoteVideoMenuTriggerButton
|
||||||
initialVolumeValue = { initialVolumeValue }
|
initialVolumeValue = { initialVolumeValue }
|
||||||
isAudioMuted = { this.isAudioMuted }
|
isAudioMuted = { this.isAudioMuted }
|
||||||
isModerator = { isModerator }
|
|
||||||
menuPosition = { remoteMenuPosition }
|
menuPosition = { remoteMenuPosition }
|
||||||
onMenuDisplay
|
onMenuDisplay
|
||||||
= {this._onRemoteVideoMenuDisplay.bind(this)}
|
= {this._onRemoteVideoMenuDisplay.bind(this)}
|
||||||
|
|
|
@ -18,11 +18,9 @@ import {
|
||||||
import { ConnectionIndicator } from '../../../react/features/connection-indicator';
|
import { ConnectionIndicator } from '../../../react/features/connection-indicator';
|
||||||
import { DisplayName } from '../../../react/features/display-name';
|
import { DisplayName } from '../../../react/features/display-name';
|
||||||
import {
|
import {
|
||||||
AudioMutedIndicator,
|
|
||||||
DominantSpeakerIndicator,
|
DominantSpeakerIndicator,
|
||||||
ModeratorIndicator,
|
|
||||||
RaisedHandIndicator,
|
RaisedHandIndicator,
|
||||||
VideoMutedIndicator
|
StatusIndicators
|
||||||
} from '../../../react/features/filmstrip';
|
} from '../../../react/features/filmstrip';
|
||||||
import {
|
import {
|
||||||
LAYOUTS,
|
LAYOUTS,
|
||||||
|
@ -84,7 +82,6 @@ export default class SmallVideo {
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
constructor(VideoLayout) {
|
constructor(VideoLayout) {
|
||||||
this._isModerator = false;
|
|
||||||
this.isAudioMuted = false;
|
this.isAudioMuted = false;
|
||||||
this.hasAvatar = false;
|
this.hasAvatar = false;
|
||||||
this.isVideoMuted = false;
|
this.isVideoMuted = false;
|
||||||
|
@ -286,45 +283,18 @@ export default class SmallVideo {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
|
||||||
let tooltipPosition;
|
|
||||||
|
|
||||||
if (currentLayout === LAYOUTS.TILE_VIEW) {
|
|
||||||
tooltipPosition = 'right';
|
|
||||||
} else if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
|
|
||||||
tooltipPosition = 'left';
|
|
||||||
} else {
|
|
||||||
tooltipPosition = 'top';
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<I18nextProvider i18n = { i18next }>
|
<Provider store = { APP.store }>
|
||||||
<div>
|
<I18nextProvider i18n = { i18next }>
|
||||||
{ this.isAudioMuted
|
<StatusIndicators
|
||||||
? <AudioMutedIndicator
|
showAudioMutedIndicator = { this.isAudioMuted }
|
||||||
tooltipPosition = { tooltipPosition } />
|
showVideoMutedIndicator = { this.isVideoMuted }
|
||||||
: null }
|
participantID = { this.id } />
|
||||||
{ this.isVideoMuted
|
</I18nextProvider>
|
||||||
? <VideoMutedIndicator
|
</Provider>,
|
||||||
tooltipPosition = { tooltipPosition } />
|
|
||||||
: null }
|
|
||||||
{ this._isModerator && !interfaceConfig.DISABLE_FOCUS_INDICATOR
|
|
||||||
? <ModeratorIndicator
|
|
||||||
tooltipPosition = { tooltipPosition } />
|
|
||||||
: null }
|
|
||||||
</div>
|
|
||||||
</I18nextProvider>,
|
|
||||||
statusBarContainer);
|
statusBarContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the element indicating the moderator(owner) of the conference.
|
|
||||||
*/
|
|
||||||
addModeratorIndicator() {
|
|
||||||
this._isModerator = true;
|
|
||||||
this.updateStatusBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the element indicating the audio level of the participant.
|
* Adds the element indicating the audio level of the participant.
|
||||||
*
|
*
|
||||||
|
@ -380,14 +350,6 @@ export default class SmallVideo {
|
||||||
return this.container.querySelector('.audioindicator-container');
|
return this.container.querySelector('.audioindicator-container');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the element indicating the moderator(owner) of the conference.
|
|
||||||
*/
|
|
||||||
removeModeratorIndicator() {
|
|
||||||
this._isModerator = false;
|
|
||||||
this.updateStatusBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an especially interesting function. A naive reader might think that
|
* This is an especially interesting function. A naive reader might think that
|
||||||
* it returns this SmallVideo's "video" element. But it is much more exciting.
|
* it returns this SmallVideo's "video" element. But it is much more exciting.
|
||||||
|
|
|
@ -174,9 +174,9 @@ const VideoLayout = {
|
||||||
|
|
||||||
// Make sure track's muted state is reflected
|
// Make sure track's muted state is reflected
|
||||||
if (stream.getType() === 'audio') {
|
if (stream.getType() === 'audio') {
|
||||||
this.onAudioMute(stream.getParticipantId(), stream.isMuted());
|
this.onAudioMute(id, stream.isMuted());
|
||||||
} else {
|
} else {
|
||||||
this.onVideoMute(stream.getParticipantId(), stream.isMuted());
|
this.onVideoMute(id, stream.isMuted());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -204,8 +204,7 @@ const VideoLayout = {
|
||||||
updateMutedForNoTracks(participantId, mediaType) {
|
updateMutedForNoTracks(participantId, mediaType) {
|
||||||
const participant = APP.conference.getParticipantById(participantId);
|
const participant = APP.conference.getParticipantById(participantId);
|
||||||
|
|
||||||
if (participant
|
if (participant && !participant.getTracksByMediaType(mediaType).length) {
|
||||||
&& !participant.getTracksByMediaType(mediaType).length) {
|
|
||||||
if (mediaType === 'audio') {
|
if (mediaType === 'audio') {
|
||||||
APP.UI.setAudioMuted(participantId, true);
|
APP.UI.setAudioMuted(participantId, true);
|
||||||
} else if (mediaType === 'video') {
|
} else if (mediaType === 'video') {
|
||||||
|
@ -328,35 +327,6 @@ const VideoLayout = {
|
||||||
this._updateLargeVideoIfDisplayed(resourceJid, true);
|
this._updateLargeVideoIfDisplayed(resourceJid, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a visual indicator for the moderator of the conference.
|
|
||||||
* On local or remote participants.
|
|
||||||
*/
|
|
||||||
showModeratorIndicator() {
|
|
||||||
const isModerator = APP.conference.isModerator;
|
|
||||||
|
|
||||||
if (isModerator) {
|
|
||||||
localVideoThumbnail.addModeratorIndicator();
|
|
||||||
} else {
|
|
||||||
localVideoThumbnail.removeModeratorIndicator();
|
|
||||||
}
|
|
||||||
|
|
||||||
APP.conference.listMembers().forEach(member => {
|
|
||||||
const id = member.getId();
|
|
||||||
const remoteVideo = remoteVideos[id];
|
|
||||||
|
|
||||||
if (!remoteVideo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (member.isModerator()) {
|
|
||||||
remoteVideo.addModeratorIndicator();
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteVideo.updateRemoteVideoMenu();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On audio muted event.
|
* On audio muted event.
|
||||||
*/
|
*/
|
||||||
|
@ -371,7 +341,7 @@ const VideoLayout = {
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteVideo.showAudioIndicator(isMuted);
|
remoteVideo.showAudioIndicator(isMuted);
|
||||||
remoteVideo.updateRemoteVideoMenu(isMuted);
|
remoteVideo.updateRemoteVideoMenu();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { getLocalParticipant, getParticipantById, PARTICIPANT_ROLE } from '../../../base/participants';
|
||||||
|
import { connect } from '../../../base/redux';
|
||||||
|
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||||
|
|
||||||
|
import AudioMutedIndicator from './AudioMutedIndicator';
|
||||||
|
import ModeratorIndicator from './ModeratorIndicator';
|
||||||
|
import VideoMutedIndicator from './VideoMutedIndicator';
|
||||||
|
|
||||||
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the React {@code Component} props of {@link StatusIndicators}.
|
||||||
|
*/
|
||||||
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current layout of the filmstrip.
|
||||||
|
*/
|
||||||
|
_currentLayout: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the moderator indicator should be visible or not.
|
||||||
|
*/
|
||||||
|
_showModeratorIndicator: Boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the audio muted indicator should be visible or not.
|
||||||
|
*/
|
||||||
|
showAudioMutedIndicator: Boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the video muted indicator should be visible or not.
|
||||||
|
*/
|
||||||
|
showVideoMutedIndicator: Boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the participant for which the status bar is rendered.
|
||||||
|
*/
|
||||||
|
participantID: String
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React {@code Component} for showing the status bar in a thumbnail.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class StatusIndicators extends Component<Props> {
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
_currentLayout,
|
||||||
|
_showModeratorIndicator,
|
||||||
|
showAudioMutedIndicator,
|
||||||
|
showVideoMutedIndicator
|
||||||
|
} = this.props;
|
||||||
|
let tooltipPosition;
|
||||||
|
|
||||||
|
switch (_currentLayout) {
|
||||||
|
case LAYOUTS.TILE_VIEW:
|
||||||
|
tooltipPosition = 'right';
|
||||||
|
break;
|
||||||
|
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
|
||||||
|
tooltipPosition = 'left';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tooltipPosition = 'top';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ showAudioMutedIndicator ? <AudioMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||||
|
{ showVideoMutedIndicator ? <VideoMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||||
|
{ _showModeratorIndicator ? <ModeratorIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated {@code StatusIndicators}'s props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @param {Object} ownProps - The own props of the component.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _currentLayout: string,
|
||||||
|
* _showModeratorIndicator: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state, ownProps) {
|
||||||
|
const { participantID } = ownProps;
|
||||||
|
|
||||||
|
// Only the local participant won't have id for the time when the conference is not yet joined.
|
||||||
|
const participant = participantID ? getParticipantById(state, participantID) : getLocalParticipant(state);
|
||||||
|
|
||||||
|
return {
|
||||||
|
_currentLayout: getCurrentLayout(state),
|
||||||
|
_showModeratorIndicator:
|
||||||
|
!interfaceConfig.DISABLE_FOCUS_INDICATOR && participant && participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(StatusIndicators);
|
|
@ -1,9 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
export { default as AudioMutedIndicator } from './AudioMutedIndicator';
|
export { default as AudioMutedIndicator } from './AudioMutedIndicator';
|
||||||
export { default as DominantSpeakerIndicator }
|
export { default as DominantSpeakerIndicator } from './DominantSpeakerIndicator';
|
||||||
from './DominantSpeakerIndicator';
|
|
||||||
export { default as Filmstrip } from './Filmstrip';
|
export { default as Filmstrip } from './Filmstrip';
|
||||||
export { default as ModeratorIndicator } from './ModeratorIndicator';
|
export { default as ModeratorIndicator } from './ModeratorIndicator';
|
||||||
export { default as RaisedHandIndicator } from './RaisedHandIndicator';
|
export { default as RaisedHandIndicator } from './RaisedHandIndicator';
|
||||||
|
export { default as StatusIndicators } from './StatusIndicators';
|
||||||
export { default as VideoMutedIndicator } from './VideoMutedIndicator';
|
export { default as VideoMutedIndicator } from './VideoMutedIndicator';
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { getCurrentConference } from '../base/conference';
|
||||||
import {
|
import {
|
||||||
PARTICIPANT_JOINED,
|
PARTICIPANT_JOINED,
|
||||||
PARTICIPANT_LEFT,
|
PARTICIPANT_LEFT,
|
||||||
|
PARTICIPANT_ROLE,
|
||||||
|
PARTICIPANT_UPDATED,
|
||||||
getParticipantById,
|
getParticipantById,
|
||||||
getParticipantDisplayName
|
getParticipantDisplayName
|
||||||
} from '../base/participants';
|
} from '../base/participants';
|
||||||
|
@ -29,15 +31,29 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case PARTICIPANT_JOINED: {
|
case PARTICIPANT_JOINED: {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
|
||||||
const { participant: p } = action;
|
const { participant: p } = action;
|
||||||
|
const { dispatch, getState } = store;
|
||||||
|
|
||||||
if (!p.local && !joinLeaveNotificationsDisabled()) {
|
if (!p.local && !joinLeaveNotificationsDisabled()) {
|
||||||
store.dispatch(showParticipantJoinedNotification(
|
dispatch(showParticipantJoinedNotification(
|
||||||
getParticipantDisplayName(store.getState, p.id)
|
getParticipantDisplayName(getState, p.id)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof interfaceConfig === 'object'
|
||||||
|
&& !interfaceConfig.DISABLE_FOCUS_INDICATOR && p.role === PARTICIPANT_ROLE.MODERATOR) {
|
||||||
|
// Do not show the notification for mobile and also when the focus indicator is disabled.
|
||||||
|
const displayName = getParticipantDisplayName(getState, p.id);
|
||||||
|
|
||||||
|
dispatch(showNotification({
|
||||||
|
descriptionArguments: { to: displayName || '$t(notify.somebody)' },
|
||||||
|
descriptionKey: 'notify.grantedTo',
|
||||||
|
titleKey: 'notify.somebody',
|
||||||
|
title: displayName
|
||||||
|
},
|
||||||
|
NOTIFICATION_TIMEOUT));
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
case PARTICIPANT_LEFT: {
|
case PARTICIPANT_LEFT: {
|
||||||
|
@ -60,6 +76,30 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
|
|
||||||
return next(action);
|
return next(action);
|
||||||
}
|
}
|
||||||
|
case PARTICIPANT_UPDATED: {
|
||||||
|
if (typeof interfaceConfig === 'undefined' || interfaceConfig.DISABLE_FOCUS_INDICATOR) {
|
||||||
|
// Do not show the notification for mobile and also when the focus indicator is disabled.
|
||||||
|
return next(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, role } = action.participant;
|
||||||
|
const state = store.getState();
|
||||||
|
const { role: oldRole } = getParticipantById(state, id);
|
||||||
|
|
||||||
|
if (oldRole !== role && role === PARTICIPANT_ROLE.MODERATOR) {
|
||||||
|
const displayName = getParticipantDisplayName(state, id);
|
||||||
|
|
||||||
|
store.dispatch(showNotification({
|
||||||
|
descriptionArguments: { to: displayName || '$t(notify.somebody)' },
|
||||||
|
descriptionKey: 'notify.grantedTo',
|
||||||
|
titleKey: 'notify.somebody',
|
||||||
|
title: displayName
|
||||||
|
},
|
||||||
|
NOTIFICATION_TIMEOUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(action);
|
return next(action);
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { Icon, IconMenuThumb } from '../../../base/icons';
|
import { Icon, IconMenuThumb } from '../../../base/icons';
|
||||||
|
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../../base/participants';
|
||||||
import { Popover } from '../../../base/popover';
|
import { Popover } from '../../../base/popover';
|
||||||
|
import { connect } from '../../../base/redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MuteButton,
|
MuteButton,
|
||||||
|
@ -23,6 +25,11 @@ declare var interfaceConfig: Object;
|
||||||
*/
|
*/
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the participant is a conference moderator.
|
||||||
|
*/
|
||||||
|
_isModerator: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A value between 0 and 1 indicating the volume of the participant's
|
* A value between 0 and 1 indicating the volume of the participant's
|
||||||
* audio element.
|
* audio element.
|
||||||
|
@ -34,11 +41,6 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
isAudioMuted: boolean,
|
isAudioMuted: boolean,
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the participant is a conference moderator.
|
|
||||||
*/
|
|
||||||
isModerator: boolean,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback to invoke when the popover has been displayed.
|
* Callback to invoke when the popover has been displayed.
|
||||||
*/
|
*/
|
||||||
|
@ -154,9 +156,9 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
*/
|
*/
|
||||||
_renderRemoteVideoMenu() {
|
_renderRemoteVideoMenu() {
|
||||||
const {
|
const {
|
||||||
|
_isModerator,
|
||||||
initialVolumeValue,
|
initialVolumeValue,
|
||||||
isAudioMuted,
|
isAudioMuted,
|
||||||
isModerator,
|
|
||||||
onRemoteControlToggle,
|
onRemoteControlToggle,
|
||||||
onVolumeChange,
|
onVolumeChange,
|
||||||
remoteControlState,
|
remoteControlState,
|
||||||
|
@ -165,7 +167,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
|
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
if (isModerator) {
|
if (_isModerator) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<MuteButton
|
<MuteButton
|
||||||
isAudioMuted = { isAudioMuted }
|
isAudioMuted = { isAudioMuted }
|
||||||
|
@ -216,4 +218,22 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RemoteVideoMenuTriggerButton;
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated {@code RemoteVideoMenuTriggerButton}'s props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @param {Object} ownProps - The own props of the component.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _isModerator: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
const participant = getLocalParticipant(state);
|
||||||
|
|
||||||
|
return {
|
||||||
|
_isModerator: Boolean(participant?.role === PARTICIPANT_ROLE.MODERATOR)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(RemoteVideoMenuTriggerButton);
|
||||||
|
|
Loading…
Reference in New Issue