jiti-meet/react/features/notifications/actions.ts

341 lines
9.8 KiB
TypeScript

import throttle from 'lodash/throttle';
import { IStore } from '../app/types';
import { NOTIFICATIONS_ENABLED } from '../base/flags/constants';
import { getFeatureFlag } from '../base/flags/functions';
import { getParticipantCount } from '../base/participants/functions';
import {
CLEAR_NOTIFICATIONS,
HIDE_NOTIFICATION,
SET_NOTIFICATIONS_ENABLED,
SHOW_NOTIFICATION
} from './actionTypes';
import {
NOTIFICATION_ICON,
NOTIFICATION_TIMEOUT,
NOTIFICATION_TIMEOUT_TYPE,
NOTIFICATION_TYPE,
SILENT_JOIN_THRESHOLD,
SILENT_LEFT_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?: {
long?: number;
medium?: number;
short?: number;
}) {
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.
*
* @returns {{
* type: CLEAR_NOTIFICATIONS
* }}
*/
export function clearNotifications() {
return {
type: CLEAR_NOTIFICATIONS
};
}
/**
* Removes the notification with the passed in id.
*
* @param {string} uid - The unique identifier for the notification to be
* removed.
* @returns {{
* type: HIDE_NOTIFICATION,
* uid: string
* }}
*/
export function hideNotification(uid: string) {
return {
type: HIDE_NOTIFICATION,
uid
};
}
/**
* Stops notifications from being displayed.
*
* @param {boolean} enabled - Whether or not notifications should display.
* @returns {{
* type: SET_NOTIFICATIONS_ENABLED,
* enabled: boolean
* }}
*/
export function setNotificationsEnabled(enabled: boolean) {
return {
type: SET_NOTIFICATIONS_ENABLED,
enabled
};
}
interface INotificationProps {
appearance?: string;
concatText?: boolean;
customActionHandler?: Function[];
customActionNameKey?: string[];
description?: string;
descriptionKey?: string;
icon?: string;
title?: string;
titleArguments?: {
[key: string]: string;
};
titleKey?: string;
uid?: string;
}
/**
* 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: INotificationProps, 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 {string} type - Timeout type.
* @returns {Function}
*/
export function showNotification(props: INotificationProps = {}, type?: string) {
return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
const { disabledNotifications = [], notifications, notificationTimeouts } = getState()['features/base/config'];
const enabledFlag = getFeatureFlag(getState(), NOTIFICATIONS_ENABLED, true);
const shouldDisplay = enabledFlag
&& !(disabledNotifications.includes(props.descriptionKey ?? '')
|| disabledNotifications.includes(props.titleKey ?? ''))
&& (!notifications
|| notifications.includes(props.descriptionKey ?? '')
|| notifications.includes(props.titleKey ?? ''));
if (shouldDisplay) {
return dispatch({
type: SHOW_NOTIFICATION,
props,
timeout: getNotificationTimeout(type, notificationTimeouts),
uid: props.uid || window.Date.now().toString()
});
}
};
}
/**
* Queues a warning notification for display.
*
* @param {Object} props - The props needed to show the notification component.
* @param {string} type - Notification type.
* @returns {Object}
*/
export function showWarningNotification(props: INotificationProps, type?: string) {
return showNotification({
...props,
appearance: NOTIFICATION_TYPE.WARNING
}, type);
}
/**
* Queues a message notification for display.
*
* @param {Object} props - The props needed to show the notification component.
* @param {string} type - Notification type.
* @returns {Object}
*/
export function showMessageNotification(props: INotificationProps, type?: string) {
return showNotification({
...props,
concatText: true,
titleKey: 'notify.chatMessages',
appearance: NOTIFICATION_TYPE.NORMAL,
icon: NOTIFICATION_ICON.MESSAGE
}, type);
}
/**
* 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: string[] = [];
/**
* 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: IStore['dispatch'], getState: IStore['getState']) => {
const participantCount = getParticipantCount(getState());
// Skip join notifications altogether for large meetings.
if (participantCount > SILENT_JOIN_THRESHOLD) {
joinedParticipantsNames = [];
return;
}
const joinedParticipantsCount = joinedParticipantsNames.length;
let notificationProps;
if (joinedParticipantsCount >= 3) {
notificationProps = {
titleArguments: {
name: joinedParticipantsNames[0]
},
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(notificationProps, NOTIFICATION_TIMEOUT_TYPE.SHORT));
}
joinedParticipantsNames = [];
}, 2000, { leading: false });
/**
* An array of names of participants that have left the conference. The array
* is replaced with an empty array as notifications are displayed.
*
* @private
* @type {string[]}
*/
let leftParticipantsNames: string[] = [];
/**
* A throttled internal function that takes the internal list of participant
* names, {@code leftParticipantsNames}, and triggers the display of a
* notification informing of their leaving.
*
* @private
* @type {Function}
*/
const _throttledNotifyParticipantLeft = throttle((dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const participantCount = getParticipantCount(getState());
// Skip left notifications altogether for large meetings.
if (participantCount > SILENT_LEFT_THRESHOLD) {
leftParticipantsNames = [];
return;
}
const leftParticipantsCount = leftParticipantsNames.length;
let notificationProps;
if (leftParticipantsCount >= 3) {
notificationProps = {
titleArguments: {
name: leftParticipantsNames[0]
},
titleKey: 'notify.leftThreePlusMembers'
};
} else if (leftParticipantsCount === 2) {
notificationProps = {
titleArguments: {
first: leftParticipantsNames[0],
second: leftParticipantsNames[1]
},
titleKey: 'notify.leftTwoMembers'
};
} else if (leftParticipantsCount) {
notificationProps = {
titleArguments: {
name: leftParticipantsNames[0]
},
titleKey: 'notify.leftOneMember'
};
}
if (notificationProps) {
dispatch(
showNotification(notificationProps, NOTIFICATION_TIMEOUT_TYPE.SHORT));
}
leftParticipantsNames = [];
}, 2000, { 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: string) {
joinedParticipantsNames.push(displayName);
return (dispatch: IStore['dispatch'], getState: IStore['getState']) =>
_throttledNotifyParticipantConnected(dispatch, getState);
}
/**
* Queues the display of a notification of a participant having left 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 left.
* @returns {Function}
*/
export function showParticipantLeftNotification(displayName: string) {
leftParticipantsNames.push(displayName);
return (dispatch: IStore['dispatch'], getState: IStore['getState']) =>
_throttledNotifyParticipantLeft(dispatch, getState);
}