feat(notifications) revisit timeouts and make them configurable

This commit is contained in:
Tudor D. Pop 2021-11-24 13:05:27 +02:00 committed by GitHub
parent 64e504f349
commit a618697e34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 220 additions and 206 deletions

View File

@ -123,7 +123,11 @@ import {
maybeOpenFeedbackDialog,
submitFeedback
} from './react/features/feedback';
import { isModerationNotificationDisplayed, showNotification } from './react/features/notifications';
import {
isModerationNotificationDisplayed,
showNotification,
NOTIFICATION_TIMEOUT_TYPE
} from './react/features/notifications';
import { mediaPermissionPromptVisibilityChanged, toggleSlowGUMOverlay } from './react/features/overlay';
import { suspendDetected } from './react/features/power-monitor';
import {
@ -357,7 +361,10 @@ class ConferenceConnector {
case JitsiConferenceErrors.FOCUS_DISCONNECTED: {
const [ focus, retrySec ] = params;
APP.UI.notifyFocusDisconnected(focus, retrySec);
APP.store.dispatch(showNotification({
descriptionKey: focus,
titleKey: retrySec
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
break;
}
@ -755,7 +762,7 @@ export default {
APP.store.dispatch(showNotification({
descriptionKey: 'notify.startSilentDescription',
titleKey: 'notify.startSilentTitle'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
// XXX The API will take care of disconnecting from the XMPP
@ -2364,7 +2371,12 @@ export default {
}
Promise.allSettled(promises)
.then(() => APP.UI.notifyInitiallyMuted());
.then(() => {
APP.store.dispatch(showNotification({
titleKey: 'notify.mutedTitle',
descriptionKey: 'notify.muted'
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
});
});
room.on(

View File

@ -375,6 +375,13 @@ var config = {
// resizeDesktopForPresenter: false
// },
// Notification timeouts
// notificationTimeouts: {
// short: 2500,
// medium: 5000,
// long: 10000
// },
// // Options for the recording limit notification.
// recordingLimit: {
//

View File

@ -225,13 +225,6 @@ var interfaceConfig = {
*/
// ANDROID_APP_PACKAGE: 'org.jitsi.meet',
/**
* Override the behavior of some notifications to remain displayed until
* explicitly dismissed through a user action. The value is how long, in
* milliseconds, those notifications should remain displayed.
*/
// ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT: 15000,
// List of undocumented settings
/**
INDICATOR_FONT_SIZES

View File

@ -10,7 +10,12 @@ import { isMobileBrowser } from '../../react/features/base/environment/utils';
import { setColorAlpha } from '../../react/features/base/util';
import { setDocumentUrl } from '../../react/features/etherpad';
import { setFilmstripVisible } from '../../react/features/filmstrip';
import { joinLeaveNotificationsDisabled, setNotificationsEnabled } from '../../react/features/notifications';
import {
joinLeaveNotificationsDisabled,
setNotificationsEnabled,
showNotification,
NOTIFICATION_TIMEOUT_TYPE
} from '../../react/features/notifications';
import {
dockToolbox,
setToolboxEnabled,
@ -215,12 +220,10 @@ UI.updateUserStatus = (user, status) => {
const displayName = user.getDisplayName();
messageHandler.participantNotification(
displayName,
'',
'connected',
'dialOut.statusMessage',
{ status: UIUtil.escapeHtml(status) });
APP.store.dispatch(showNotification({
titleKey: `${displayName} connected`,
descriptionKey: 'dialOut.statusMessage'
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
};
/**
@ -333,18 +336,6 @@ UI.notifyMaxUsersLimitReached = function() {
});
};
/**
* Notify user that he was automatically muted when joned the conference.
*/
UI.notifyInitiallyMuted = function() {
messageHandler.participantNotification(
null,
'notify.mutedTitle',
'connected',
'notify.muted',
null);
};
UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
VideoLayout.onLastNEndpointsChanged(leavingIds, enteringIds);
};
@ -363,15 +354,6 @@ UI.notifyTokenAuthFailed = function() {
});
};
UI.notifyFocusDisconnected = function(focus, retrySec) {
messageHandler.participantNotification(
null, 'notify.focus',
'disconnected', 'notify.focusFail',
{ component: focus,
ms: retrySec }
);
};
/**
* Update list of available physical devices.
*/

View File

@ -1,9 +1,8 @@
/* global APP */
import {
NOTIFICATION_TIMEOUT,
NOTIFICATION_TIMEOUT_TYPE,
showErrorNotification,
showNotification,
showWarningNotification
} from '../../../react/features/notifications';
@ -48,7 +47,7 @@ const messageHandler = {
* showErrorNotification action.
*/
showError(props) {
APP.store.dispatch(showErrorNotification(props));
APP.store.dispatch(showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG));
},
/**
@ -58,35 +57,7 @@ const messageHandler = {
* showWarningNotification action.
*/
showWarning(props) {
APP.store.dispatch(showWarningNotification(props));
},
/**
* Displays a notification about participant action.
* @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 is not provided.
* @param cls css class for the notification
* @param messageKey the key from the language file for the text of the
* message.
* @param messageArguments object with the arguments for the message.
* @param optional configurations for the notification (e.g. timeout)
*/
participantNotification( // eslint-disable-line max-params
displayName,
displayNameKey,
cls,
messageKey,
messageArguments,
timeout = NOTIFICATION_TIMEOUT) {
APP.store.dispatch(showNotification({
descriptionArguments: messageArguments,
descriptionKey: messageKey,
titleKey: displayNameKey,
title: displayName
},
timeout));
APP.store.dispatch(showWarningNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
};

View File

@ -25,7 +25,7 @@ import {
toURLString
} from '../base/util';
import { isVpaasMeeting } from '../jaas/functions';
import { clearNotifications, showNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, clearNotifications, showNotification } from '../notifications';
import { setFatalError } from '../overlay';
import {
@ -328,7 +328,7 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
dispatch(showNotification({
titleArguments: { appName: getName() },
titleKey: 'dialog.thankYou'
}));
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
// if Welcome page is enabled redirect to welcome page after 3 sec, if

View File

@ -17,6 +17,7 @@ import {
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import {
NOTIFICATION_TIMEOUT_TYPE,
hideNotification,
showNotification
} from '../notifications';
@ -106,7 +107,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
sticky: true,
titleKey,
uid
}));
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
break;
}
@ -222,7 +223,7 @@ StateListenerRegistry.register(
sticky: true,
customActionNameKey: 'notify.unmute',
customActionHandler: () => dispatch(muteLocal(false, MEDIA_TYPE.AUDIO))
}));
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
dispatch(playSound(ASKED_TO_UNMUTE_SOUND_ID));
}
});

View File

@ -9,7 +9,7 @@ import {
} from '../../analytics';
import { reloadNow } from '../../app/actions';
import { openDisplayNamePrompt } from '../../display-name';
import { showErrorNotification } from '../../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification } from '../../notifications';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
import { validateJwt } from '../jwt';
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
@ -129,7 +129,7 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
dispatch(showErrorNotification({
description: reason,
titleKey: 'dialog.sessTerminated'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
break;
}
@ -138,7 +138,7 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
dispatch(showErrorNotification({
description: 'Restart initiated because of a bridge failure',
titleKey: 'dialog.sessionRestarted'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
break;
@ -151,7 +151,7 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
descriptionArguments: { msg },
descriptionKey: msg ? 'dialog.connectErrorWithMsg' : 'dialog.connectError',
titleKey: 'connection.CONNFAIL'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
break;
}

View File

@ -172,6 +172,7 @@ export default [
'maxFullResolutionParticipants',
'mouseMoveCallbackInterval',
'notifications',
'notificationTimeouts',
'openSharedDocumentOnJoin',
'opusMaxAverageBitrate',
'p2p',

View File

@ -26,7 +26,6 @@ export default [
'DISPLAY_WELCOME_PAGE_CONTENT',
'ENABLE_DIAL_OUT',
'ENABLE_FEEDBACK_ANIMATION',
'ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT',
'FILM_STRIP_MAX_HEIGHT',
'GENERATE_ROOMNAMES_ON_WELCOME_PAGE',
'HIDE_INVITE_MORE_HEADER',

View File

@ -2,7 +2,7 @@
import UIEvents from '../../../../service/UI/UIEvents';
import { processExternalDeviceRequest } from '../../device-selection';
import { showNotification, showWarningNotification } from '../../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification, showWarningNotification } from '../../notifications';
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
import { isPrejoinPageVisible } from '../../prejoin/functions';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
@ -50,8 +50,6 @@ const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
}
};
const WARNING_DISPLAY_TIMER = 4000;
/**
* A listener for device permissions changed reported from lib-jitsi-meet.
*/
@ -134,7 +132,7 @@ MiddlewareRegistry.register(store => next => action => {
description: additionalCameraErrorMsg,
descriptionKey: cameraErrorMsg,
titleKey
}, WARNING_DISPLAY_TIMER));
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(setDeviceStatusWarning(titleKey));
@ -163,7 +161,7 @@ MiddlewareRegistry.register(store => next => action => {
description: additionalMicErrorMsg,
descriptionKey: micErrorMsg,
titleKey
}, WARNING_DISPLAY_TIMER));
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(setDeviceStatusWarning(titleKey));
@ -298,7 +296,7 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
titleKey,
customActionNameKey: 'notify.newDeviceAction',
customActionHandler: _useDevice.bind(undefined, store, devicesArray)
}));
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
}
});
}

View File

@ -1,4 +1,4 @@
import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../notifications';
import { set } from '../redux';
import {
@ -478,7 +478,7 @@ export function participantMutedUs(participant, track) {
titleArguments: {
participantDisplayName: getParticipantDisplayName(getState, participant.getId())
}
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
};
}
@ -510,7 +510,7 @@ export function participantKicked(kicker, kicked) {
getParticipantDisplayName(getState, kicker.getId())
},
titleKey: 'notify.kickParticipant'
}, NOTIFICATION_TIMEOUT * 2)); // leave more time for this
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
};
}

View File

@ -6,7 +6,7 @@ import UIEvents from '../../../../service/UI/UIEvents';
import { approveParticipant } from '../../av-moderation/actions';
import { toggleE2EE } from '../../e2ee/actions';
import { MAX_MODE } from '../../e2ee/constants';
import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../notifications';
import { isForceMuted } from '../../participants-pane/functions';
import { CALLING, INVITED } from '../../presence-status';
import { RAISE_HAND_SOUND_ID } from '../../reactions/constants';
@ -562,7 +562,7 @@ function _raiseHandUpdated({ dispatch, getState }, conference, participantId, ne
raiseHandNotification: true,
concatText: true,
...action
}, NOTIFICATION_TIMEOUT * (shouldDisplayAllowAction ? 2 : 1)));
}, shouldDisplayAllowAction ? NOTIFICATION_TIMEOUT_TYPE.MEDIUM : NOTIFICATION_TIMEOUT_TYPE.SHORT));
dispatch(playSound(RAISE_HAND_SOUND_ID));
}
}

View File

@ -4,7 +4,7 @@ import {
createTrackMutedEvent,
sendAnalytics
} from '../../analytics';
import { showErrorNotification, showNotification } from '../../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showNotification } from '../../notifications';
import { JitsiTrackErrors, JitsiTrackEvents, createLocalTrack } from '../lib-jitsi-meet';
import {
CAMERA_FACING_MODE,
@ -245,7 +245,7 @@ export function showNoDataFromSourceVideoError(jitsiTrack) {
const notificationAction = await dispatch(showErrorNotification({
descriptionKey: 'dialog.cameraNotSendingData',
titleKey: 'dialog.cameraNotSendingDataTitle'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
notificationInfo = {
uid: notificationAction.uid
@ -397,7 +397,7 @@ export function trackAdded(track) {
const notificationAction = await dispatch(showNotification({
descriptionKey: 'dialog.micNotSendingData',
titleKey: 'dialog.micNotSendingDataTitle'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
// Set the notification ID so that other parts of the application know that this was
// displayed in the context of the current device.
@ -406,7 +406,9 @@ export function trackAdded(track) {
noDataFromSourceNotificationInfo = { uid: notificationAction.uid };
} else {
const timeout = setTimeout(() => dispatch(showNoDataFromSourceVideoError(track)), 5000);
const timeout = setTimeout(() => dispatch(
showNoDataFromSourceVideoError(track)),
NOTIFICATION_TIMEOUT_TYPE.MEDIUM);
noDataFromSourceNotificationInfo = { timeout };
}

View File

@ -4,6 +4,7 @@ import type { Dispatch } from 'redux';
import { getParticipantDisplayName } from '../base/participants';
import {
NOTIFICATION_TIMEOUT_TYPE,
NOTIFICATION_TYPE,
showNotification
} from '../notifications';
@ -34,6 +35,6 @@ export function notifyKickedOut(participant: Object, _: ?Function) { // eslint-d
descriptionArguments: args,
titleKey: 'dialog.kickTitle',
titleArguments: args
}));
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
};
}

View File

@ -1,6 +1,6 @@
import { isSuboptimalBrowser } from '../base/environment';
import { translateToHTML } from '../base/i18n';
import { showWarningNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showWarningNotification } from '../notifications';
export * from './functions.any';
@ -24,7 +24,7 @@ export function maybeShowSuboptimalExperienceNotification(dispatch, t) {
recommendedBrowserPageLink: `${window.location.origin}/static/recommendedBrowsers.html`
}
)
}
}, NOTIFICATION_TIMEOUT_TYPE.LONG
)
);
}

View File

@ -4,7 +4,7 @@ import { Component } from 'react';
import { createInviteDialogEvent, sendAnalytics } from '../../../analytics';
import {
NOTIFICATION_TIMEOUT,
NOTIFICATION_TIMEOUT_TYPE,
showNotification
} from '../../../notifications';
import { invite } from '../../actions';
@ -203,7 +203,7 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
if (notificationProps) {
dispatch(
showNotification(notificationProps, NOTIFICATION_TIMEOUT));
showNotification(notificationProps, NOTIFICATION_TIMEOUT_TYPE.SHORT));
}
}

View File

@ -9,7 +9,11 @@ import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/pa
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { isTestModeEnabled } from '../base/testing';
import { NOTIFICATION_TYPE, showNotification } from '../notifications';
import {
NOTIFICATION_TIMEOUT_TYPE,
NOTIFICATION_TYPE,
showNotification
} from '../notifications';
import { shouldAutoKnock } from '../prejoin/functions';
import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED } from './actionTypes';
@ -66,10 +70,12 @@ StateListenerRegistry.register(
conference.on(JitsiConferenceEvents.LOBBY_USER_JOINED, (id, name) => {
batch(() => {
dispatch(participantIsKnockingOrUpdated({
id,
name
}));
dispatch(
participantIsKnockingOrUpdated({
id,
name
})
);
dispatch(playSound(KNOCKING_PARTICIPANT_SOUND_ID));
if (typeof APP !== 'undefined') {
APP.API.notifyKnockingParticipant({
@ -81,10 +87,12 @@ StateListenerRegistry.register(
});
conference.on(JitsiConferenceEvents.LOBBY_USER_UPDATED, (id, participant) => {
dispatch(participantIsKnockingOrUpdated({
...participant,
id
}));
dispatch(
participantIsKnockingOrUpdated({
...participant,
id
})
);
});
conference.on(JitsiConferenceEvents.LOBBY_USER_LEFT, id => {
@ -98,7 +106,8 @@ StateListenerRegistry.register(
})
);
}
});
}
);
/**
* Function to handle the conference failed event and navigate the user to the lobby screen
@ -135,11 +144,13 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
dispatch(hideLobbyScreen());
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
dispatch(showNotification({
appearance: NOTIFICATION_TYPE.ERROR,
hideErrorSupportLink: true,
titleKey: 'lobby.joinRejectedMessage'
}));
dispatch(
showNotification({
appearance: NOTIFICATION_TYPE.ERROR,
hideErrorSupportLink: true,
titleKey: 'lobby.joinRejectedMessage'
}, NOTIFICATION_TIMEOUT_TYPE.LONG)
);
}
return next(action);
@ -174,10 +185,12 @@ function _findLoadableAvatarForKnockingParticipant(store, { id }) {
if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
getFirstLoadableAvatarUrl(updatedParticipant, store).then(loadableAvatarUrl => {
if (loadableAvatarUrl) {
dispatch(participantIsKnockingOrUpdated({
loadableAvatarUrl,
id
}));
dispatch(
participantIsKnockingOrUpdated({
loadableAvatarUrl,
id
})
);
}
});
}
@ -217,5 +230,10 @@ function _maybeSendLobbyNotification(origin, message, { dispatch, getState }) {
break;
}
dispatch(showNotification(notificationProps, isTestModeEnabled(getState()) ? undefined : 5000));
dispatch(
showNotification(
notificationProps,
isTestModeEnabled(getState()) ? NOTIFICATION_TIMEOUT_TYPE.STICKY : NOTIFICATION_TIMEOUT_TYPE.MEDIUM
)
);
}

View File

@ -8,6 +8,7 @@ import { i18next } from '../base/i18n';
import { SET_AUDIO_MUTED } from '../base/media/actionTypes';
import { MiddlewareRegistry } from '../base/redux';
import { SETTINGS_UPDATED } from '../base/settings/actionTypes';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications';
import { showNotification } from '../notifications/actions';
import { localRecordingEngaged, localRecordingUnengaged } from './actions';
@ -48,14 +49,14 @@ MiddlewareRegistry.register(({ getState, dispatch }) => next => action => {
dispatch(showNotification({
titleKey: 'localRecording.localRecording',
description: i18next.t(messageKey, messageParams)
}, 10000));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
};
recordingController.onNotify = (messageKey, messageParams) => {
dispatch(showNotification({
titleKey: 'localRecording.localRecording',
description: i18next.t(messageKey, messageParams)
}, 10000));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
};
typeof APP === 'object' && typeof APP.keyboardshortcut === 'object'

View File

@ -12,7 +12,7 @@ import JitsiMeetJS, { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux';
import { updateSettings } from '../base/settings';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { hideNotification, showNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, hideNotification, showNotification } from '../notifications';
import { setNoAudioSignalNotificationUid } from './actions';
import DialInLink from './components/DialInLink';
@ -114,7 +114,7 @@ async function _handleNoAudioSignalNotification({ dispatch, getState }, action)
descriptionKey,
customActionNameKey,
customActionHandler
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
dispatch(playSound(NO_AUDIO_SIGNAL_SOUND_ID));

View File

@ -5,7 +5,7 @@ import { CONFERENCE_JOINED } from '../base/conference';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { hideNotification, showNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, hideNotification, showNotification } from '../notifications';
import { setNoisyAudioInputNotificationUid } from './actions';
import { NOISY_AUDIO_INPUT_SOUND_ID } from './constants';
@ -41,7 +41,7 @@ MiddlewareRegistry.register(store => next => action => {
const notification = await dispatch(showNotification({
titleKey: 'toolbar.noisyAudioInputTitle',
descriptionKey: 'toolbar.noisyAudioInputDesc'
}));
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
dispatch(playSound(NOISY_AUDIO_INPUT_SOUND_ID));

View File

@ -14,11 +14,31 @@ import {
SHOW_NOTIFICATION
} from './actionTypes';
import {
NOTIFICATION_TIMEOUT_TYPE,
NOTIFICATION_TIMEOUT,
NOTIFICATION_TYPE,
SILENT_JOIN_THRESHOLD
} from './constants';
/**
* Function that returns notification timeout value based on notification timeout type.
*
* @param {string} type - Notification type.
* @param {Object} notificationTimeouts - Config notification timeouts.
* @returns {number}
*/
function getNotificationTimeout(type: ?string, notificationTimeouts: ?Object) {
if (type === NOTIFICATION_TIMEOUT_TYPE.SHORT) {
return notificationTimeouts?.short ?? NOTIFICATION_TIMEOUT.SHORT;
} else if (type === NOTIFICATION_TIMEOUT_TYPE.MEDIUM) {
return notificationTimeouts?.medium ?? NOTIFICATION_TIMEOUT.MEDIUM;
} else if (type === NOTIFICATION_TIMEOUT_TYPE.LONG) {
return notificationTimeouts?.long ?? NOTIFICATION_TIMEOUT.LONG;
}
return NOTIFICATION_TIMEOUT.STICKY;
}
/**
* Clears (removes) all the notifications.
*
@ -82,26 +102,26 @@ export function setNotificationsEnabled(enabled: boolean) {
* Queues an error notification for display.
*
* @param {Object} props - The props needed to show the notification component.
* @param {string} type - Notification type.
* @returns {Object}
*/
export function showErrorNotification(props: Object) {
export function showErrorNotification(props: Object, type: ?string) {
return showNotification({
...props,
appearance: NOTIFICATION_TYPE.ERROR
});
}, type);
}
/**
* Queues a notification for display.
*
* @param {Object} props - The props needed to show the notification component.
* @param {number} timeout - How long the notification should display before
* automatically being hidden.
* @param {string} type - Notification type.
* @returns {Function}
*/
export function showNotification(props: Object = {}, timeout: ?number) {
export function showNotification(props: Object = {}, type: ?string) {
return function(dispatch: Function, getState: Function) {
const { notifications } = getState()['features/base/config'];
const { notifications, notificationTimeouts } = getState()['features/base/config'];
const enabledFlag = getFeatureFlag(getState(), NOTIFICATIONS_ENABLED, true);
const shouldDisplay = enabledFlag
@ -113,7 +133,7 @@ export function showNotification(props: Object = {}, timeout: ?number) {
return dispatch({
type: SHOW_NOTIFICATION,
props,
timeout,
timeout: getNotificationTimeout(type, notificationTimeouts),
uid: props.uid || window.Date.now().toString()
});
}
@ -124,15 +144,15 @@ export function showNotification(props: Object = {}, timeout: ?number) {
* Queues a warning notification for display.
*
* @param {Object} props - The props needed to show the notification component.
* @param {number} timeout - How long the notification should display before
* automatically being hidden.
* @param {string} type - Notification type.
* @returns {Object}
*/
export function showWarningNotification(props: Object, timeout: ?number) {
export function showWarningNotification(props: Object, type: ?string) {
return showNotification({
...props,
appearance: NOTIFICATION_TYPE.WARNING
}, timeout);
}, type);
}
/**
@ -192,7 +212,7 @@ const _throttledNotifyParticipantConnected = throttle((dispatch: Dispatch<any>,
if (notificationProps) {
dispatch(
showNotification(notificationProps, NOTIFICATION_TIMEOUT));
showNotification(notificationProps, NOTIFICATION_TIMEOUT_TYPE.SHORT));
}
joinedParticipantsNames = [];

View File

@ -13,8 +13,6 @@ import { areThereNotifications } from '../../functions';
import Notification from './Notification';
declare var interfaceConfig: Object;
type Props = {
/**
@ -33,12 +31,6 @@ type Props = {
*/
_notifications: Array<Object>,
/**
* The length, in milliseconds, to use as a default timeout for all
* dismissible timeouts that do not have a timeout specified.
*/
autoDismissTimeout: number,
/**
* JSS classes object.
*/
@ -260,14 +252,14 @@ class NotificationsContainer extends Component<Props> {
* @returns {void}
*/
_updateTimeouts() {
const { _notifications, autoDismissTimeout } = this.props;
const { _notifications } = this.props;
for (const notification of _notifications) {
if ((notification.timeout || typeof autoDismissTimeout === 'number')
if (notification.timeout
&& notification.props.isDismissAllowed !== false
&& !this._timeouts.has(notification.uid)) {
const {
timeout = autoDismissTimeout,
timeout,
uid
} = notification;
const timerID = setTimeout(() => {
@ -296,8 +288,7 @@ function _mapStateToProps(state) {
return {
_iAmSipGateway: Boolean(iAmSipGateway),
_isChatOpen: isChatOpen,
_notifications: _visible ? notifications : [],
autoDismissTimeout: interfaceConfig.ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT
_notifications: _visible ? notifications : []
};
}

View File

@ -3,7 +3,22 @@
/**
* The standard time when auto-disappearing notifications should disappear.
*/
export const NOTIFICATION_TIMEOUT = 2500;
export const NOTIFICATION_TIMEOUT = {
SHORT: 2500,
MEDIUM: 5000,
LONG: 10000,
STICKY: false
};
/**
* Notification timeout type.
*/
export const NOTIFICATION_TIMEOUT_TYPE = {
SHORT: 'short',
MEDIUM: 'medium',
LONG: 'long',
STICKY: 'sticky'
};
/**
* The set of possible notification types.

View File

@ -19,7 +19,7 @@ import {
showNotification,
showParticipantJoinedNotification
} from './actions';
import { NOTIFICATION_TIMEOUT } from './constants';
import { NOTIFICATION_TIMEOUT_TYPE } from './constants';
import { joinLeaveNotificationsDisabled } from './functions';
declare var interfaceConfig: Object;
@ -54,15 +54,12 @@ MiddlewareRegistry.register(store => next => action => {
action.participant.id
);
if (typeof interfaceConfig === 'object'
&& participant
&& !participant.local
&& !action.participant.isReplaced) {
if (participant && !participant.local && !action.participant.isReplaced) {
store.dispatch(showNotification({
descriptionKey: 'notify.disconnected',
titleKey: 'notify.somebody',
title: participant.name
}, NOTIFICATION_TIMEOUT));
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
}
}
@ -91,7 +88,7 @@ MiddlewareRegistry.register(store => next => action => {
store.dispatch(showNotification({
titleKey: 'notify.moderator'
},
NOTIFICATION_TIMEOUT));
NOTIFICATION_TIMEOUT_TYPE.SHORT));
}
return next(action);

View File

@ -4,7 +4,7 @@ import React from 'react';
import { APP_WILL_MOUNT } from '../base/app';
import { MiddlewareRegistry } from '../base/redux';
import { showErrorNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification } from '../notifications';
import { OldElectronAPPNotificationDescription } from './components';
import { isOldJitsiMeetElectronApp } from './functions';
@ -36,7 +36,7 @@ function _appWillMount(store, next, action) {
dispatch(showErrorNotification({
titleKey: 'notify.OldElectronAPPTitle',
description: <OldElectronAPPNotificationDescription />
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
return next(action);

View File

@ -4,7 +4,7 @@ import { getCurrentConference } from '../base/conference';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { StateListenerRegistry } from '../base/redux';
import {
NOTIFICATION_TIMEOUT,
NOTIFICATION_TIMEOUT_TYPE,
NOTIFICATION_TYPE,
showNotification
} from '../notifications';
@ -83,7 +83,7 @@ StateListenerRegistry.register(
appearance: NOTIFICATION_TYPE.NORMAL,
titleKey: 'polls.notification.title',
descriptionKey: 'polls.notification.description'
}, NOTIFICATION_TIMEOUT));
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
break;
}

View File

@ -19,7 +19,7 @@ import {
} from '../base/tracks';
import { openURLInBrowser } from '../base/util';
import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions';
import { showErrorNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification } from '../notifications';
import {
PREJOIN_JOINING_IN_PROGRESS,
@ -114,7 +114,7 @@ function pollForStatus(
case DIAL_OUT_STATUS.DISCONNECTED: {
dispatch(showErrorNotification({
titleKey: 'prejoin.errorDialOutDisconnected'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
return onFail();
}
@ -122,7 +122,7 @@ function pollForStatus(
case DIAL_OUT_STATUS.FAILED: {
dispatch(showErrorNotification({
titleKey: 'prejoin.errorDialOutFailed'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
return onFail();
}
@ -130,7 +130,7 @@ function pollForStatus(
} catch (err) {
dispatch(showErrorNotification({
titleKey: 'prejoin.errorDialOutStatus'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
logger.error('Error getting dial out status', err);
onFail();
}
@ -183,7 +183,7 @@ export function dialOut(onSuccess: Function, onFail: Function) {
}
}
dispatch(showErrorNotification(notification));
dispatch(showErrorNotification(notification, NOTIFICATION_TIMEOUT_TYPE.LONG));
logger.error('Error dialing out', err);
onFail();
}

View File

@ -9,7 +9,7 @@ import { MiddlewareRegistry } from '../base/redux';
import { SETTINGS_UPDATED, updateSettings } from '../base/settings';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { getDisabledSounds } from '../base/sounds/functions.any';
import { NOTIFICATION_TIMEOUT, showNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
import {
ADD_REACTION_BUFFER,
@ -169,7 +169,7 @@ MiddlewareRegistry.register(store => next => action => {
customActionHandler: () => dispatch(updateSettings({
soundsReactions: false
}))
}, NOTIFICATION_TIMEOUT));
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
break;
}
}

View File

@ -6,7 +6,7 @@ import { getLocalParticipant, getParticipantDisplayName } from '../base/particip
import { copyText } from '../base/util/helpers';
import { getVpaasTenant, isVpaasMeeting } from '../jaas/functions';
import {
NOTIFICATION_TIMEOUT,
NOTIFICATION_TIMEOUT_TYPE,
hideNotification,
showErrorNotification,
showNotification,
@ -98,7 +98,7 @@ export function showPendingRecordingNotification(streamType: string) {
const notification = await dispatch(showNotification({
isDismissAllowed: false,
...dialogProps
}));
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
if (notification) {
dispatch(_setPendingRecordingNotificationUid(notification.uid, streamType));
@ -113,7 +113,7 @@ export function showPendingRecordingNotification(streamType: string) {
* @returns {showErrorNotification}
*/
export function showRecordingError(props: Object) {
return showErrorNotification(props);
return showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG);
}
/**
@ -149,7 +149,7 @@ export function showStoppedRecordingNotification(streamType: string, participant
titleKey: 'dialog.recording'
};
return showNotification(dialogProps, NOTIFICATION_TIMEOUT);
return showNotification(dialogProps, NOTIFICATION_TIMEOUT_TYPE.SHORT);
}
/**
@ -214,14 +214,14 @@ export function showStartedRecordingNotification(
} catch (err) {
dispatch(showErrorNotification({
titleKey: 'recording.errorFetchingLink'
}));
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
return logger.error('Could not fetch recording link', err);
}
}
}
dispatch(showNotification(dialogProps, NOTIFICATION_TIMEOUT));
dispatch(showNotification(dialogProps, NOTIFICATION_TIMEOUT_TYPE.SHORT));
};
}

View File

@ -1,7 +1,7 @@
// @flow
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { showNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
export * from './actions.any';
@ -37,6 +37,6 @@ export function showRecordingLimitNotification(streamType: string) {
descriptionKey,
titleKey,
maxLines: 2
}, 10000));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
};
}

View File

@ -3,7 +3,7 @@
import React from 'react';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { showNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
import { RecordingLimitNotificationDescription } from './components';
@ -23,5 +23,5 @@ export function showRecordingLimitNotification(streamType: string) {
return showNotification({
description: <RecordingLimitNotificationDescription isLiveStreaming = { isLiveStreaming } />,
titleKey: isLiveStreaming ? 'dialog.liveStreaming' : 'dialog.recording'
}, 10000);
}, NOTIFICATION_TIMEOUT_TYPE.LONG);
}

View File

@ -13,7 +13,7 @@ import {
getNewAccessToken,
updateDropboxToken
} from '../../../dropbox';
import { showErrorNotification } from '../../../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification } from '../../../notifications';
import { toggleRequestingSubtitles } from '../../../subtitles';
import { setSelectedRecordingService } from '../../actions';
import { RECORDING_TYPES } from '../../constants';
@ -298,7 +298,7 @@ class AbstractStartRecordingDialog extends Component<Props, State> {
} else {
dispatch(showErrorNotification({
titleKey: 'dialog.noDropboxToken'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
return;
}

View File

@ -4,7 +4,7 @@ import { openDialog } from '../base/dialog';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { getParticipantDisplayName, getPinnedParticipant, pinParticipant } from '../base/participants';
import { getLocalVideoTrack } from '../base/tracks';
import { showNotification } from '../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
import {
CAPTURE_EVENTS,
@ -190,7 +190,7 @@ export function processPermissionRequestReply(participantId: string, event: Obje
descriptionArguments: { user: getParticipantDisplayName(state, participantId) },
descriptionKey,
titleKey: 'dialog.remoteControlTitle'
}));
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
if (permissionGranted) {
// the remote control permissions has been granted
@ -269,7 +269,7 @@ export function stopController(notifyRemoteParty: boolean = false) {
dispatch(showNotification({
descriptionKey: 'dialog.remoteControlStopMessage',
titleKey: 'dialog.remoteControlTitle'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
};
}
@ -425,7 +425,7 @@ export function stopReceiver(dontNotifyLocalParty: boolean = false, dontNotifyRe
dispatch(showNotification({
descriptionKey: 'dialog.remoteControlStopMessage',
titleKey: 'dialog.remoteControlTitle'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
};
}
@ -564,7 +564,7 @@ export function grant(participantId: string) {
dispatch(showNotification({
descriptionKey: 'dialog.startRemoteControlErrorMessage',
titleKey: 'dialog.remoteControlTitle'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
dispatch(stopReceiver(true));
});

View File

@ -11,7 +11,7 @@ import { hideDialog } from '../base/dialog';
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux';
import {
NOTIFICATION_TIMEOUT,
NOTIFICATION_TIMEOUT_TYPE,
showNotification
} from '../notifications';
@ -54,12 +54,12 @@ MiddlewareRegistry.register(store => next => action => {
store.dispatch(
showNotification({
titleKey: 'notify.passwordSetRemotely'
}, NOTIFICATION_TIMEOUT));
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
} else if (previousLockedState === LOCKED_REMOTELY && !currentLockedState) {
store.dispatch(
showNotification({
titleKey: 'notify.passwordRemovedRemotely'
}, NOTIFICATION_TIMEOUT));
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
}
return result;

View File

@ -9,6 +9,7 @@ import { getCurrentConference } from '../../../base/conference';
import { MEDIA_TYPE } from '../../../base/media';
import { getLocalParticipant } from '../../../base/participants';
import { isLocalTrackMuted } from '../../../base/tracks';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications';
import { showWarningNotification } from '../../../notifications/actions';
import { dockToolbox } from '../../../toolbox/actions.web';
import { muteLocal } from '../../../video-menu/actions.any';
@ -424,7 +425,7 @@ export function _mapDispatchToProps(dispatch: Function): $Shape<Props> {
_displayWarning: () => {
dispatch(showWarningNotification({
titleKey: 'dialog.shareVideoLinkError'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
},
_dockToolbox: value => {
dispatch(dockToolbox(value));

View File

@ -1,5 +1,6 @@
// @flow
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications';
import { showWarningNotification } from '../../notifications/actions';
import { timeout } from '../../virtual-background/functions';
import logger from '../../virtual-background/logger';
@ -62,13 +63,13 @@ export async function createVirtualBackgroundEffect(virtualBackground: Object, d
logger.error('Failed to download tflite model!');
dispatch(showWarningNotification({
titleKey: 'virtualBackground.backgroundEffectError'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
} else {
logger.error('Looks like WebAssembly is disabled or not supported on this browser');
dispatch(showWarningNotification({
titleKey: 'virtualBackground.webAssemblyWarning',
description: 'WebAssembly disabled or not supported by this browser'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
return;

View File

@ -8,6 +8,7 @@ import { getLocalParticipant, raiseHand } from '../base/participants';
import { MiddlewareRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import {
NOTIFICATION_TIMEOUT_TYPE,
hideNotification,
showNotification
} from '../notifications';
@ -50,7 +51,7 @@ MiddlewareRegistry.register(store => next => action => {
titleKey: 'toolbar.talkWhileMutedPopup',
customActionNameKey: forceMuted ? 'notify.raiseHandAction' : 'notify.unmute',
customActionHandler: () => dispatch(forceMuted ? raiseHand(true) : setAudioMuted(false))
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
const { soundsTalkWhileMuted } = getState()['features/base/settings'];

View File

@ -1,7 +1,7 @@
// @flow
import {
NOTIFICATION_TIMEOUT,
NOTIFICATION_TIMEOUT_TYPE,
hideNotification,
showErrorNotification,
showNotification
@ -74,7 +74,7 @@ export function showPendingTranscribingNotification() {
descriptionKey: 'transcribing.pending',
isDismissAllowed: false,
titleKey: 'dialog.transcribing'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
if (notification) {
dispatch(setPendingTranscribingNotificationUid(notification.uid));
@ -127,7 +127,7 @@ export function showStoppedTranscribingNotification() {
return showNotification({
descriptionKey: 'transcribing.off',
titleKey: 'dialog.transcribing'
}, NOTIFICATION_TIMEOUT);
}, NOTIFICATION_TIMEOUT_TYPE.SHORT);
}
@ -140,5 +140,5 @@ export function showTranscribingError() {
return showErrorNotification({
descriptionKey: 'transcribing.error',
titleKey: 'transcribing.failedToStart'
});
}, NOTIFICATION_TIMEOUT_TYPE.LONG);
}

View File

@ -7,6 +7,7 @@ import {
} from '../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../base/redux';
import {
NOTIFICATION_TIMEOUT_TYPE,
showErrorNotification,
showNotification,
showWarningNotification
@ -103,7 +104,7 @@ function _inviteRooms(rooms, conference, dispatch) {
dispatch(showErrorNotification({
descriptionKey: 'videoSIPGW.errorInvite',
titleKey: 'videoSIPGW.errorInviteTitle'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
return;
}
@ -111,7 +112,7 @@ function _inviteRooms(rooms, conference, dispatch) {
dispatch(showWarningNotification({
titleKey: 'videoSIPGW.errorAlreadyInvited',
titleArguments: { displayName }
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
return;
}
@ -148,7 +149,7 @@ function _sessionStateChanged(
titleArguments: {
displayName: event.displayName
}
}, 2000);
}, NOTIFICATION_TIMEOUT_TYPE.SHORT);
}
case JitsiSIPVideoGWStatus.STATE_FAILED: {
return showErrorNotification({
@ -157,14 +158,14 @@ function _sessionStateChanged(
displayName: event.displayName
},
descriptionKey: 'videoSIPGW.errorInviteFailed'
});
}, NOTIFICATION_TIMEOUT_TYPE.LONG);
}
case JitsiSIPVideoGWStatus.STATE_OFF: {
if (event.failureReason === JitsiSIPVideoGWStatus.STATUS_BUSY) {
return showErrorNotification({
descriptionKey: 'videoSIPGW.busy',
titleKey: 'videoSIPGW.busyTitle'
});
}, NOTIFICATION_TIMEOUT_TYPE.LONG);
} else if (event.failureReason) {
logger.error(`Unknown sip videogw error ${event.newState} ${
event.failureReason}`);

View File

@ -16,7 +16,7 @@ import { connect } from '../../base/redux';
import { updateSettings } from '../../base/settings';
import { Tooltip } from '../../base/tooltip';
import { getLocalVideoTrack } from '../../base/tracks';
import { showErrorNotification } from '../../notifications';
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification } from '../../notifications';
import { toggleBackgroundEffect, virtualBackgroundTrackChanged } from '../actions';
import { IMAGES, BACKGROUNDS_LIMIT, VIRTUAL_BACKGROUND_TYPE, type Image } from '../constants';
import { toDataURL } from '../functions';
@ -219,7 +219,7 @@ function VirtualBackground({
if (!isCancelled) {
dispatch(showErrorNotification({
titleKey: 'virtualBackground.desktopShareError'
}));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
logger.error('Could not create desktop share as a virtual background!');
}

View File

@ -10,6 +10,7 @@ import Video from '../../base/media/components/Video';
import { connect, equals } from '../../base/redux';
import { getCurrentCameraDeviceId } from '../../base/settings';
import { createLocalTracksF } from '../../base/tracks/functions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications';
import { showWarningNotification } from '../../notifications/actions';
import { toggleBackgroundEffect } from '../actions';
import { VIRTUAL_BACKGROUND_TYPE } from '../constants';
@ -140,7 +141,7 @@ class VirtualBackgroundPreview extends PureComponent<Props, State> {
showWarningNotification({
titleKey: 'virtualBackground.backgroundEffectError',
description: 'Failed to access camera device.'
})
}, NOTIFICATION_TIMEOUT_TYPE.LONG)
);
logger.error('Failed to access camera device. Error on apply background effect.');