diff --git a/lang/main.json b/lang/main.json index 084e7675c..fc5ae7ba9 100644 --- a/lang/main.json +++ b/lang/main.json @@ -214,7 +214,9 @@ "notify": { "disconnected": "disconnected", "moderator": "Moderator rights granted!", - "connected": "connected", + "connectedOneMember": "__name__ connected", + "connectedTwoMembers": "__first__ and __second__ connected", + "connectedThreePlusMembers": "__name__ and __count__ others connected", "somebody": "Somebody", "me": "Me", "focus": "Conference focus", diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 0d89cf202..9dce8f8b6 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -25,7 +25,10 @@ import { } from '../../react/features/device-selection'; import { updateDeviceList } from '../../react/features/base/devices'; import { JitsiTrackErrors } from '../../react/features/base/lib-jitsi-meet'; -import { getLocalParticipant } from '../../react/features/base/participants'; +import { + getLocalParticipant, + showParticipantJoinedNotification +} from '../../react/features/base/participants'; import { openDisplayNamePrompt } from '../../react/features/display-name'; import { maybeShowNotificationWithDoNotDisplay, @@ -473,8 +476,7 @@ UI.addUser = function(user) { const id = user.getId(); const displayName = user.getDisplayName(); - messageHandler.participantNotification( - displayName, 'notify.somebody', 'connected', 'notify.connected'); + APP.store.dispatch(showParticipantJoinedNotification(displayName)); if (!config.startAudioMuted || config.startAudioMuted > APP.conference.membersCount) { diff --git a/modules/UI/util/MessageHandler.js b/modules/UI/util/MessageHandler.js index bc5fc99f6..2e9a63fcb 100644 --- a/modules/UI/util/MessageHandler.js +++ b/modules/UI/util/MessageHandler.js @@ -362,7 +362,7 @@ const messageHandler = { $titleString.attr('data-i18n', titleKey); return $('
').append($titleString) -.html(); + .html(); }, /** @@ -479,7 +479,7 @@ const messageHandler = { * @param displayName the display name of the participant that is * associated with the notification. * @param displayNameKey the key from the language file for the display - * name. Only used if displayName i not provided. + * name. Only used if displayName is not provided. * @param cls css class for the notification * @param messageKey the key from the language file for the text of the * message. diff --git a/react/features/base/participants/actions.js b/react/features/base/participants/actions.js index bf006d6fe..41b2d2bb2 100644 --- a/react/features/base/participants/actions.js +++ b/react/features/base/participants/actions.js @@ -1,3 +1,9 @@ +/* global interfaceConfig */ + +import throttle from 'lodash/throttle'; + +import { Notification, showNotification } from '../../notifications'; + import { DOMINANT_SPEAKER_CHANGED, KICK_PARTICIPANT, @@ -318,3 +324,79 @@ export function pinParticipant(id) { } }; } + +/** + * An array of names of participants that have joined the conference. The array + * is replaced with an empty array as notifications are displayed. + * + * @private + * @type {string[]} + */ +let joinedParticipantsNames = []; + +/** + * A throttled internal function that takes the internal list of participant + * names, {@code joinedParticipantsNames}, and triggers the display of a + * notification informing of their joining. + * + * @private + * @type {Function} + */ +const _throttledNotifyParticipantConnected = throttle(dispatch => { + const joinedParticipantsCount = joinedParticipantsNames.length; + + let notificationProps; + + if (joinedParticipantsCount >= 3) { + notificationProps = { + titleArguments: { + name: joinedParticipantsNames[0], + count: joinedParticipantsCount - 1 + }, + titleKey: 'notify.connectedThreePlusMembers' + }; + } else if (joinedParticipantsCount === 2) { + notificationProps = { + titleArguments: { + first: joinedParticipantsNames[0], + second: joinedParticipantsNames[1] + }, + titleKey: 'notify.connectedTwoMembers' + }; + } else if (joinedParticipantsCount) { + notificationProps = { + titleArguments: { + name: joinedParticipantsNames[0] + }, + titleKey: 'notify.connectedOneMember' + }; + } + + if (notificationProps) { + dispatch( + showNotification( + Notification, + notificationProps, + 2500)); + } + + joinedParticipantsNames = []; + +}, 500, { leading: false }); + +/** + * Queues the display of a notification of a participant having connected to + * the meeting. The notifications are batched so that quick consecutive + * connection events are shown in one notification. + * + * @param {string} displayName - The name of the participant that connected. + * @returns {Function} + */ +export function showParticipantJoinedNotification(displayName) { + joinedParticipantsNames.push( + displayName || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME); + + return dispatch => { + _throttledNotifyParticipantConnected(dispatch); + }; +} diff --git a/react/features/notifications/components/Notification.web.js b/react/features/notifications/components/Notification.web.js index 2f5308e85..45643e343 100644 --- a/react/features/notifications/components/Notification.web.js +++ b/react/features/notifications/components/Notification.web.js @@ -104,6 +104,11 @@ class Notification extends Component<*> { */ title: PropTypes.string, + /** + * The translation arguments that may be necessary for the title. + */ + titleArguments: PropTypes.object, + /** * The translation key to display as the title of the notification if * no title is provided. @@ -138,16 +143,17 @@ class Notification extends Component<*> { */ render() { const { - hideErrorSupportLink, appearance, - titleKey, + description, descriptionArguments, descriptionKey, - description, + hideErrorSupportLink, isDismissAllowed, onDismissed, t, title, + titleArguments, + titleKey, uid } = this.props; @@ -161,7 +167,7 @@ class Notification extends Component<*> { id = { uid } isDismissAllowed = { isDismissAllowed } onDismissed = { onDismissed } - title = { title || t(titleKey) } /> + title = { title || t(titleKey, titleArguments) } /> ); }