bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/chat/alert/component.jsx
Arthurk12 333b6727b9 fix(chat): exported presentation notification
Adds a specific function to handle the creation of the exported presentatio's
toast notification that were missing.
2023-12-04 17:39:32 -03:00

270 lines
8.9 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Meteor } from 'meteor/meteor';
import { defineMessages, injectIntl } from 'react-intl';
import injectNotify from '/imports/ui/components/common/toast/inject-notify/component';
import AudioService from '/imports/ui/components/audio/service';
import ChatPushAlert from './push-alert/component';
import { stripTags, unescapeHtml, uniqueId } from '/imports/utils/string-utils';
import Service from '../service';
import Styled from './styles';
import { usePreviousValue } from '/imports/ui/components/utils/hooks';
import { Session } from 'meteor/session';
import { isEqual } from 'radash';
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_CLEAR = CHAT_CONFIG.chat_clear;
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
const POLL_RESULT_KEY = CHAT_CONFIG.system_messages_keys.chat_poll_result;
const EXPORTED_PRESENTATION_KEY = CHAT_CONFIG.system_messages_keys.chat_exported_presentation;
const propTypes = {
pushAlertEnabled: PropTypes.bool.isRequired,
audioAlertEnabled: PropTypes.bool.isRequired,
unreadMessagesCountByChat: PropTypes.arrayOf(PropTypes.object),
unreadMessagesByChat: PropTypes.arrayOf(PropTypes.array),
idChatOpen: PropTypes.string.isRequired,
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
};
const defaultProps = {
unreadMessagesCountByChat: null,
unreadMessagesByChat: null,
};
const intlMessages = defineMessages({
appToastChatPublic: {
id: 'app.toast.chat.public',
description: 'when entry various message',
},
appToastChatPrivate: {
id: 'app.toast.chat.private',
description: 'when entry various message',
},
appToastChatSystem: {
id: 'app.toast.chat.system',
description: 'system for use',
},
publicChatClear: {
id: 'app.chat.clearPublicChatMessage',
description: 'message of when clear the public chat',
},
publicChatMsg: {
id: 'app.toast.chat.public',
description: 'public chat toast message title',
},
privateChatMsg: {
id: 'app.toast.chat.private',
description: 'private chat toast message title',
},
pollResults: {
id: 'app.toast.chat.poll',
description: 'chat toast message for polls',
},
pollResultsClick: {
id: 'app.toast.chat.pollClick',
description: 'chat toast click message for polls',
},
exportedPresentation: {
id: 'app.toast.chat.exportedPresentation',
description: 'chat toast message for exportedPresentation',
}
});
const ALERT_INTERVAL = 5000; // 5 seconds
const ALERT_DURATION = 4000; // 4 seconds
const ChatAlert = (props) => {
const {
audioAlertEnabled,
pushAlertEnabled,
idChatOpen,
unreadMessagesCountByChat,
unreadMessagesByChat,
intl,
layoutContextDispatch,
} = props;
const [unreadMessagesCount, setUnreadMessagesCount] = useState(0);
const [unreadMessages, setUnreadMessages] = useState([]);
const [lastAlertTimestampByChat, setLastAlertTimestampByChat] = useState({});
const [alertEnabledTimestamp, setAlertEnabledTimestamp] = useState(null);
const prevUnreadMessages = usePreviousValue(unreadMessages);
// audio alerts
useEffect(() => {
if (audioAlertEnabled) {
const unreadObject = unreadMessagesCountByChat;
const unreadCount = document.hidden
? unreadObject.reduce((a, b) => a + b.unreadCounter, 0)
: unreadObject.filter((chat) => chat.chatId !== idChatOpen)
.reduce((a, b) => a + b.unreadCounter, 0);
if (unreadCount > unreadMessagesCount) {
AudioService.playAlertSound(`${Meteor.settings.public.app.cdn
+ Meteor.settings.public.app.basename
+ Meteor.settings.public.app.instanceId}`
+ '/resources/sounds/notify.mp3');
}
setUnreadMessagesCount(unreadCount);
}
}, [unreadMessagesCountByChat]);
// push alerts
useEffect(() => {
if (pushAlertEnabled) {
setAlertEnabledTimestamp(new Date().getTime());
}
}, [pushAlertEnabled]);
useEffect(() => {
if (pushAlertEnabled) {
const alertsObject = unreadMessagesByChat;
let timewindowsToAlert = [];
let filteredTimewindows = [];
alertsObject.forEach((chat) => {
filteredTimewindows = filteredTimewindows.concat(
chat.filter((timeWindow) => timeWindow.timestamp > alertEnabledTimestamp),
);
});
filteredTimewindows.forEach((timeWindow) => {
const durationDiff = ALERT_DURATION - (new Date().getTime() - timeWindow.timestamp);
if ((timeWindow.lastTimestamp > timeWindow.timestamp && durationDiff > 0
&& timeWindow.lastTimestamp > (lastAlertTimestampByChat[timeWindow.chatId] || 0))
|| timeWindow.timestamp
> (lastAlertTimestampByChat[timeWindow.chatId] || 0) + ALERT_INTERVAL) {
timewindowsToAlert = timewindowsToAlert
.filter((item) => item.chatId !== timeWindow.chatId);
const newTimeWindow = { ...timeWindow };
newTimeWindow.durationDiff = durationDiff;
timewindowsToAlert.push(newTimeWindow);
const newLastAlertTimestampByChat = { ...lastAlertTimestampByChat };
if (timeWindow.timestamp > (lastAlertTimestampByChat[timeWindow.chatId] || 0)) {
newLastAlertTimestampByChat[timeWindow.chatId] = timeWindow.timestamp;
setLastAlertTimestampByChat(newLastAlertTimestampByChat);
}
}
});
setUnreadMessages(timewindowsToAlert);
}
}, [unreadMessagesByChat]);
const mapContentText = (message) => {
const contentMessage = message
.map((content) => {
if (content.text === PUBLIC_CHAT_CLEAR) {
return intl.formatMessage(intlMessages.publicChatClear);
}
return unescapeHtml(stripTags(content.text));
});
return contentMessage;
};
const createMessage = (name, message) => (
<Styled.PushMessageContent>
<Styled.UserNameMessage>{name}</Styled.UserNameMessage>
<Styled.ContentMessage>
{
mapContentText(message)
.reduce((acc, text) => [...acc, (<br key={uniqueId('br_')} />), text], [])
}
</Styled.ContentMessage>
</Styled.PushMessageContent>
);
const createPollMessage = () => (
<Styled.PushMessageContent>
<Styled.UserNameMessage>
{intl.formatMessage(intlMessages.pollResults)}
</Styled.UserNameMessage>
<Styled.ContentMessagePoll>
{intl.formatMessage(intlMessages.pollResultsClick)}
</Styled.ContentMessagePoll>
</Styled.PushMessageContent>
);
const createExportedPresentationMessage = (filename) => (
<Styled.PushMessageContent>
<Styled.UserNameMessage>{intl.formatMessage(intlMessages.exportedPresentation)}</Styled.UserNameMessage>
<Styled.ContentMessageExportedPresentation>{filename}</Styled.ContentMessageExportedPresentation>
</Styled.PushMessageContent>
);
if (isEqual(prevUnreadMessages, unreadMessages)) {
return null;
}
return pushAlertEnabled
? unreadMessages.map((timeWindow) => {
const mappedMessage = Service.mapGroupMessage(timeWindow);
let content = null;
let isPollResult = false;
if (mappedMessage) {
if (mappedMessage.id.includes(POLL_RESULT_KEY)) {
content = createPollMessage();
isPollResult = true;
} else if (mappedMessage.id.includes(EXPORTED_PRESENTATION_KEY)) {
content = createExportedPresentationMessage(mappedMessage.extra.filename);
} else {
content = createMessage(mappedMessage.sender.name, mappedMessage.content.slice(-5));
}
}
const messageChatId = mappedMessage.chatId === 'MAIN-PUBLIC-GROUP-CHAT' ? PUBLIC_CHAT_ID : mappedMessage.chatId;
const newUnreadMessages = unreadMessages
.filter((message) => message.key !== mappedMessage.key);
return content
? (
<ChatPushAlert
key={messageChatId}
chatId={messageChatId}
content={content}
title={
(mappedMessage.chatId === 'MAIN-PUBLIC-GROUP-CHAT')
? <span>{intl.formatMessage(intlMessages.appToastChatPublic)}</span>
: <span>{intl.formatMessage(intlMessages.appToastChatPrivate)}</span>
}
onOpen={
() => {
if (isPollResult) {
Session.set('ignorePollNotifications', true);
}
setUnreadMessages(newUnreadMessages);
}
}
onClose={
() => {
if (isPollResult) {
Session.set('ignorePollNotifications', false);
}
setUnreadMessages(newUnreadMessages);
}
}
alertDuration={timeWindow.durationDiff}
layoutContextDispatch={layoutContextDispatch}
/>
) : null;
})
: null;
};
ChatAlert.propTypes = propTypes;
ChatAlert.defaultProps = defaultProps;
export default injectNotify(injectIntl(ChatAlert));