2019-08-09 02:56:52 +08:00
|
|
|
import React, { Fragment, PureComponent } from 'react';
|
2017-10-28 03:29:48 +08:00
|
|
|
import PropTypes from 'prop-types';
|
2018-05-30 00:43:11 +08:00
|
|
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
|
|
import _ from 'lodash';
|
|
|
|
import UnreadMessages from '/imports/ui/services/unread-messages';
|
2018-08-10 01:02:18 +08:00
|
|
|
import ChatAudioAlert from './audio-alert/component';
|
|
|
|
import ChatPushAlert from './push-alert/component';
|
2018-05-30 00:43:11 +08:00
|
|
|
import Service from '../service';
|
|
|
|
import { styles } from '../styles';
|
2017-10-28 03:29:48 +08:00
|
|
|
|
2021-05-18 04:25:07 +08:00
|
|
|
const CHAT_CONFIG = Meteor.settings.public.chat;
|
|
|
|
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
|
|
|
|
2017-10-28 03:29:48 +08:00
|
|
|
const propTypes = {
|
2019-01-14 21:23:35 +08:00
|
|
|
pushAlertDisabled: PropTypes.bool.isRequired,
|
|
|
|
activeChats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
|
|
audioAlertDisabled: PropTypes.bool.isRequired,
|
|
|
|
joinTimestamp: PropTypes.number.isRequired,
|
2019-08-09 02:56:52 +08:00
|
|
|
idChatOpen: PropTypes.string.isRequired,
|
2021-04-10 04:35:05 +08:00
|
|
|
intl: PropTypes.shape({
|
|
|
|
formatMessage: PropTypes.func.isRequired,
|
|
|
|
}).isRequired,
|
2017-10-28 03:29:48 +08:00
|
|
|
};
|
|
|
|
|
2018-05-30 00:43:11 +08:00
|
|
|
const intlMessages = defineMessages({
|
|
|
|
appToastChatPublic: {
|
|
|
|
id: 'app.toast.chat.public',
|
|
|
|
description: 'when entry various message',
|
|
|
|
},
|
2018-06-13 01:21:31 +08:00
|
|
|
appToastChatPrivate: {
|
|
|
|
id: 'app.toast.chat.private',
|
|
|
|
description: 'when entry various message',
|
|
|
|
},
|
2018-05-30 00:43:11 +08:00
|
|
|
appToastChatSystem: {
|
|
|
|
id: 'app.toast.chat.system',
|
|
|
|
description: 'system for use',
|
|
|
|
},
|
|
|
|
publicChatClear: {
|
|
|
|
id: 'app.chat.clearPublicChatMessage',
|
|
|
|
description: 'message of when clear the public chat',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2019-01-14 21:23:35 +08:00
|
|
|
const ALERT_INTERVAL = 5000; // 5 seconds
|
|
|
|
const ALERT_DURATION = 4000; // 4 seconds
|
2018-05-30 00:43:11 +08:00
|
|
|
|
2018-12-17 19:48:34 +08:00
|
|
|
class ChatAlert extends PureComponent {
|
2017-10-28 03:29:48 +08:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2019-08-09 02:56:52 +08:00
|
|
|
|
|
|
|
const { joinTimestamp } = props;
|
|
|
|
|
2018-05-30 00:43:11 +08:00
|
|
|
this.state = {
|
2019-08-09 02:56:52 +08:00
|
|
|
alertEnabledTimestamp: joinTimestamp,
|
2019-01-14 21:23:35 +08:00
|
|
|
lastAlertTimestampByChat: {},
|
|
|
|
pendingNotificationsByChat: {},
|
2018-05-30 00:43:11 +08:00
|
|
|
};
|
2017-10-28 03:29:48 +08:00
|
|
|
}
|
|
|
|
|
2019-01-14 21:23:35 +08:00
|
|
|
componentDidUpdate(prevProps) {
|
2017-10-28 03:29:48 +08:00
|
|
|
const {
|
2019-01-14 21:23:35 +08:00
|
|
|
activeChats,
|
2019-08-09 02:56:52 +08:00
|
|
|
idChatOpen,
|
2019-01-14 21:23:35 +08:00
|
|
|
joinTimestamp,
|
2019-08-09 02:56:52 +08:00
|
|
|
pushAlertDisabled,
|
2021-04-10 04:35:05 +08:00
|
|
|
messages,
|
2017-10-28 03:29:48 +08:00
|
|
|
} = this.props;
|
|
|
|
|
2019-01-14 21:23:35 +08:00
|
|
|
const {
|
2019-01-15 00:23:11 +08:00
|
|
|
alertEnabledTimestamp,
|
2019-01-14 21:23:35 +08:00
|
|
|
lastAlertTimestampByChat,
|
|
|
|
pendingNotificationsByChat,
|
|
|
|
} = this.state;
|
2019-08-09 02:56:52 +08:00
|
|
|
|
2019-01-14 21:23:35 +08:00
|
|
|
// Avoid alerting messages received before enabling alerts
|
|
|
|
if (prevProps.pushAlertDisabled && !pushAlertDisabled) {
|
2021-05-18 04:25:07 +08:00
|
|
|
const newAlertEnabledTimestamp = Service
|
|
|
|
.getLastMessageTimestampFromChatList(activeChats, messages);
|
2019-01-16 21:08:20 +08:00
|
|
|
this.setAlertEnabledTimestamp(newAlertEnabledTimestamp);
|
2017-11-03 00:47:49 +08:00
|
|
|
return;
|
2017-10-28 03:29:48 +08:00
|
|
|
}
|
|
|
|
|
2019-01-14 21:23:35 +08:00
|
|
|
// Keep track of messages that was not alerted yet
|
|
|
|
const unalertedMessagesByChatId = {};
|
|
|
|
|
|
|
|
activeChats
|
2021-05-18 04:25:07 +08:00
|
|
|
.filter((chat) => chat.chatId !== idChatOpen)
|
|
|
|
.filter((chat) => chat.unreadCounter > 0)
|
2019-01-14 21:23:35 +08:00
|
|
|
.forEach((chat) => {
|
2021-05-18 04:25:07 +08:00
|
|
|
const chatId = (chat.chatId === 'public') ? PUBLIC_CHAT_ID : chat.chatId;
|
2021-04-10 04:35:05 +08:00
|
|
|
const thisChatUnreadMessages = UnreadMessages.getUnreadMessages(chatId, messages);
|
2019-01-14 21:23:35 +08:00
|
|
|
|
|
|
|
unalertedMessagesByChatId[chatId] = thisChatUnreadMessages.filter((msg) => {
|
|
|
|
const retorno = (msg
|
2019-01-15 00:23:11 +08:00
|
|
|
&& msg.timestamp > alertEnabledTimestamp
|
2019-01-14 21:23:35 +08:00
|
|
|
&& msg.timestamp > joinTimestamp
|
2021-04-10 04:35:05 +08:00
|
|
|
&& msg.timestamp > (lastAlertTimestampByChat[chatId] || 0)
|
2021-04-13 22:38:44 +08:00
|
|
|
&& !pushAlertDisabled
|
2019-01-14 21:23:35 +08:00
|
|
|
);
|
|
|
|
return retorno;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!unalertedMessagesByChatId[chatId].length) delete unalertedMessagesByChatId[chatId];
|
2017-11-17 18:11:46 +08:00
|
|
|
});
|
2017-10-28 03:29:48 +08:00
|
|
|
|
2019-01-14 21:23:35 +08:00
|
|
|
const lastUnalertedMessageTimestampByChat = {};
|
|
|
|
Object.keys(unalertedMessagesByChatId).forEach((chatId) => {
|
|
|
|
lastUnalertedMessageTimestampByChat[chatId] = unalertedMessagesByChatId[chatId]
|
|
|
|
.reduce(Service.maxTimestampReducer, 0);
|
2018-05-30 00:43:11 +08:00
|
|
|
});
|
2019-01-14 21:23:35 +08:00
|
|
|
|
|
|
|
// Keep track of chats that need to be alerted now (considering alert interval)
|
|
|
|
const chatsWithPendingAlerts = Object.keys(lastUnalertedMessageTimestampByChat)
|
2021-05-18 04:25:07 +08:00
|
|
|
.filter((chatId) => lastUnalertedMessageTimestampByChat[chatId]
|
2019-01-14 21:23:35 +08:00
|
|
|
> ((lastAlertTimestampByChat[chatId] || 0) + ALERT_INTERVAL)
|
|
|
|
&& !(chatId in pendingNotificationsByChat));
|
|
|
|
|
2019-08-09 02:56:52 +08:00
|
|
|
if (idChatOpen !== prevProps.idChatOpen) {
|
|
|
|
this.setChatMessagesState({}, { ...lastAlertTimestampByChat });
|
|
|
|
}
|
|
|
|
|
2019-01-14 21:23:35 +08:00
|
|
|
if (!chatsWithPendingAlerts.length) return;
|
|
|
|
|
|
|
|
const newPendingNotificationsByChat = Object.assign({},
|
2021-05-18 04:25:07 +08:00
|
|
|
...chatsWithPendingAlerts.map((chatId) => ({ [chatId]: unalertedMessagesByChatId[chatId] })));
|
2019-01-14 21:23:35 +08:00
|
|
|
|
|
|
|
// Mark messages as alerted
|
|
|
|
const newLastAlertTimestampByChat = { ...lastAlertTimestampByChat };
|
|
|
|
|
|
|
|
chatsWithPendingAlerts.forEach(
|
|
|
|
(chatId) => {
|
|
|
|
newLastAlertTimestampByChat[chatId] = lastUnalertedMessageTimestampByChat[chatId];
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2019-04-10 01:04:33 +08:00
|
|
|
this.setChatMessagesState(newPendingNotificationsByChat, newLastAlertTimestampByChat);
|
2019-01-14 21:23:35 +08:00
|
|
|
}
|
|
|
|
|
2019-01-16 21:08:20 +08:00
|
|
|
setAlertEnabledTimestamp(newAlertEnabledTimestamp) {
|
2019-01-15 00:23:11 +08:00
|
|
|
const { alertEnabledTimestamp } = this.state;
|
2019-01-16 21:08:20 +08:00
|
|
|
if (newAlertEnabledTimestamp > 0 && alertEnabledTimestamp !== newAlertEnabledTimestamp) {
|
|
|
|
this.setState({ alertEnabledTimestamp: newAlertEnabledTimestamp });
|
2019-01-14 21:23:35 +08:00
|
|
|
}
|
2017-10-28 03:29:48 +08:00
|
|
|
}
|
|
|
|
|
2019-01-14 21:23:35 +08:00
|
|
|
setChatMessagesState(pendingNotificationsByChat, lastAlertTimestampByChat) {
|
|
|
|
this.setState({ pendingNotificationsByChat, lastAlertTimestampByChat });
|
|
|
|
}
|
|
|
|
|
2018-06-13 01:21:31 +08:00
|
|
|
mapContentText(message) {
|
2018-05-30 00:43:11 +08:00
|
|
|
const {
|
|
|
|
intl,
|
|
|
|
} = this.props;
|
|
|
|
const contentMessage = message
|
2018-06-13 01:21:31 +08:00
|
|
|
.map((content) => {
|
|
|
|
if (content.text === 'PUBLIC_CHAT_CLEAR') return intl.formatMessage(intlMessages.publicChatClear);
|
2019-01-14 21:23:35 +08:00
|
|
|
/* this code is to remove html tags that come in the server's messages */
|
2018-06-13 01:21:31 +08:00
|
|
|
const tempDiv = document.createElement('div');
|
|
|
|
tempDiv.innerHTML = content.text;
|
|
|
|
const textWithoutTag = tempDiv.innerText;
|
|
|
|
return textWithoutTag;
|
|
|
|
});
|
2019-01-14 21:23:35 +08:00
|
|
|
|
2018-05-30 00:43:11 +08:00
|
|
|
return contentMessage;
|
|
|
|
}
|
|
|
|
|
2018-06-13 01:21:31 +08:00
|
|
|
createMessage(name, message) {
|
|
|
|
return (
|
|
|
|
<div className={styles.pushMessageContent}>
|
|
|
|
<h3 className={styles.userNameMessage}>{name}</h3>
|
|
|
|
<div className={styles.contentMessage}>
|
|
|
|
{
|
|
|
|
this.mapContentText(message)
|
2019-01-14 21:23:35 +08:00
|
|
|
.reduce((acc, text) => [...acc, (<br key={_.uniqueId('br_')} />), text], [])
|
2018-06-13 01:21:31 +08:00
|
|
|
}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-01-14 21:23:35 +08:00
|
|
|
render() {
|
2017-10-28 03:29:48 +08:00
|
|
|
const {
|
2019-01-14 21:23:35 +08:00
|
|
|
audioAlertDisabled,
|
2019-08-09 02:56:52 +08:00
|
|
|
idChatOpen,
|
2019-01-14 21:23:35 +08:00
|
|
|
pushAlertDisabled,
|
2018-05-30 00:43:11 +08:00
|
|
|
intl,
|
2021-05-18 04:25:07 +08:00
|
|
|
newLayoutContextDispatch,
|
2021-05-06 19:19:12 +08:00
|
|
|
activeChats,
|
2017-10-28 03:29:48 +08:00
|
|
|
} = this.props;
|
2019-08-09 02:56:52 +08:00
|
|
|
|
2018-05-30 00:43:11 +08:00
|
|
|
const {
|
2019-01-14 21:23:35 +08:00
|
|
|
pendingNotificationsByChat,
|
|
|
|
} = this.state;
|
2018-07-06 22:23:47 +08:00
|
|
|
|
2019-08-02 00:47:11 +08:00
|
|
|
const notCurrentTabOrMinimized = document.hidden;
|
2019-08-09 02:56:52 +08:00
|
|
|
const hasPendingNotifications = Object.keys(pendingNotificationsByChat).length > 0;
|
2019-08-02 00:47:11 +08:00
|
|
|
|
2021-05-06 19:19:12 +08:00
|
|
|
const unreadMessages = activeChats.reduce((a, b) => a + b.unreadCounter, 0);
|
|
|
|
|
|
|
|
const shouldPlayChatAlert = (notCurrentTabOrMinimized && unreadMessages > 0)
|
2019-08-09 02:56:52 +08:00
|
|
|
|| (hasPendingNotifications && !idChatOpen);
|
2018-07-06 22:23:47 +08:00
|
|
|
|
2018-05-30 00:43:11 +08:00
|
|
|
return (
|
2021-05-18 04:25:07 +08:00
|
|
|
<>
|
2019-08-02 00:47:11 +08:00
|
|
|
{
|
2019-08-09 02:56:52 +08:00
|
|
|
!audioAlertDisabled || (!audioAlertDisabled && notCurrentTabOrMinimized)
|
2019-08-02 00:47:11 +08:00
|
|
|
? <ChatAudioAlert play={shouldPlayChatAlert} />
|
|
|
|
: null
|
|
|
|
}
|
2018-05-30 00:43:11 +08:00
|
|
|
{
|
2019-01-14 21:23:35 +08:00
|
|
|
!pushAlertDisabled
|
|
|
|
? Object.keys(pendingNotificationsByChat)
|
|
|
|
.map((chatId) => {
|
|
|
|
// Only display the latest group of messages (up to 5 messages)
|
|
|
|
const reducedMessage = Service
|
|
|
|
.reduceAndMapGroupMessages(pendingNotificationsByChat[chatId].slice(-5)).pop();
|
|
|
|
|
2019-08-24 04:50:37 +08:00
|
|
|
if (!reducedMessage || !reducedMessage.sender) return null;
|
2019-01-14 21:23:35 +08:00
|
|
|
|
|
|
|
const content = this
|
|
|
|
.createMessage(reducedMessage.sender.name, reducedMessage.content);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ChatPushAlert
|
|
|
|
key={chatId}
|
|
|
|
chatId={chatId}
|
|
|
|
content={content}
|
|
|
|
title={
|
|
|
|
(chatId === 'MAIN-PUBLIC-GROUP-CHAT')
|
|
|
|
? <span>{intl.formatMessage(intlMessages.appToastChatPublic)}</span>
|
|
|
|
: <span>{intl.formatMessage(intlMessages.appToastChatPrivate)}</span>
|
|
|
|
}
|
|
|
|
onOpen={
|
|
|
|
() => {
|
|
|
|
let pendingNotifications = pendingNotificationsByChat;
|
|
|
|
delete pendingNotifications[chatId];
|
|
|
|
pendingNotifications = { ...pendingNotifications };
|
|
|
|
this.setState({ pendingNotificationsByChat: pendingNotifications });
|
2021-05-18 04:25:07 +08:00
|
|
|
}
|
|
|
|
}
|
2019-01-14 21:23:35 +08:00
|
|
|
alertDuration={ALERT_DURATION}
|
2021-05-18 04:25:07 +08:00
|
|
|
newLayoutContextDispatch={newLayoutContextDispatch}
|
2019-01-14 21:23:35 +08:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
})
|
|
|
|
: null
|
2017-10-28 03:29:48 +08:00
|
|
|
}
|
2021-05-18 04:25:07 +08:00
|
|
|
</>
|
2017-10-28 03:29:48 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2018-08-10 01:02:18 +08:00
|
|
|
ChatAlert.propTypes = propTypes;
|
2017-10-28 03:29:48 +08:00
|
|
|
|
2018-08-10 01:02:18 +08:00
|
|
|
export default injectIntl(ChatAlert);
|