blob: 12425418962d5a95edcde6bdaa1d63c76b9b83b4 [file] [log] [blame]
simon26e79f72022-10-05 22:16:08 -04001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
idillonae655dd2022-10-14 18:11:02 -040018import { Divider, Stack, Typography } from '@mui/material';
19import { Account, Conversation, ConversationMember, Message } from 'jami-web-common';
20import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
21import { useTranslation } from 'react-i18next';
simon1170c322022-10-31 14:51:31 -040022import { useNavigate } from 'react-router';
simon07b4eb02022-09-29 17:50:26 -040023
simon35378692022-10-02 23:25:57 -040024import { SocketContext } from '../contexts/Socket';
idillonae655dd2022-10-14 18:11:02 -040025import { useAccountQuery } from '../services/Account';
simon35378692022-10-02 23:25:57 -040026import { useConversationQuery, useMessagesQuery, useSendMessageMutation } from '../services/Conversation';
idillonae655dd2022-10-14 18:11:02 -040027import { translateEnumeration, TranslateEnumerationOptions } from '../utils/translations';
28import { AddParticipantButton, ShowOptionsMenuButton, StartAudioCallButton, StartVideoCallButton } from './Button';
simon6b9ddfb2022-10-03 00:04:50 -040029import LoadingPage from './Loading';
Adrien Béraud35e7d7c2021-04-13 03:28:39 -040030import MessageList from './MessageList';
31import SendMessageForm from './SendMessageForm';
Adrien Béraud35e7d7c2021-04-13 03:28:39 -040032
simon35378692022-10-02 23:25:57 -040033type ConversationViewProps = {
34 accountId: string;
35 conversationId: string;
36};
idillonae655dd2022-10-14 18:11:02 -040037const ConversationView = ({ accountId, conversationId }: ConversationViewProps) => {
simond47ef9e2022-09-28 22:24:28 -040038 const socket = useContext(SocketContext);
idillonae655dd2022-10-14 18:11:02 -040039 const [account, setAccount] = useState<Account | undefined>();
simon35378692022-10-02 23:25:57 -040040 const [conversation, setConversation] = useState<Conversation | undefined>();
41 const [messages, setMessages] = useState<Message[]>([]);
simond47ef9e2022-09-28 22:24:28 -040042 const [isLoading, setIsLoading] = useState(true);
43 const [error, setError] = useState(false);
idillon08f77172022-09-13 19:14:17 -040044
idillonae655dd2022-10-14 18:11:02 -040045 const accountQuery = useAccountQuery(accountId);
simon80b7b3b2022-09-28 17:50:10 -040046 const conversationQuery = useConversationQuery(accountId, conversationId);
47 const messagesQuery = useMessagesQuery(accountId, conversationId);
48 const sendMessageMutation = useSendMessageMutation(accountId, conversationId);
Adrien Béraud35e7d7c2021-04-13 03:28:39 -040049
Adrien Béraud5e9e19b2021-04-22 01:38:53 -040050 useEffect(() => {
idillonae655dd2022-10-14 18:11:02 -040051 if (accountQuery.isSuccess) {
52 setAccount(Account.from(accountQuery.data));
53 }
54 }, [accountQuery.isSuccess, accountQuery.data]);
55
56 useEffect(() => {
idillonea465602022-09-13 19:58:51 -040057 if (conversationQuery.isSuccess) {
simon80b7b3b2022-09-28 17:50:10 -040058 const conversation = Conversation.from(accountId, conversationQuery.data);
simond47ef9e2022-09-28 22:24:28 -040059 setConversation(conversation);
idillon08f77172022-09-13 19:14:17 -040060 }
simon80b7b3b2022-09-28 17:50:10 -040061 }, [accountId, conversationQuery.isSuccess, conversationQuery.data]);
idillon08f77172022-09-13 19:14:17 -040062
63 useEffect(() => {
idillonea465602022-09-13 19:58:51 -040064 if (messagesQuery.isSuccess) {
simond47ef9e2022-09-28 22:24:28 -040065 const sortedMessages = sortMessages(messagesQuery.data);
66 setMessages(sortedMessages);
idillon08f77172022-09-13 19:14:17 -040067 }
simon80b7b3b2022-09-28 17:50:10 -040068 }, [messagesQuery.isSuccess, messagesQuery.data]);
idillon08f77172022-09-13 19:14:17 -040069
70 useEffect(() => {
idillonae655dd2022-10-14 18:11:02 -040071 setIsLoading(accountQuery.isLoading || conversationQuery.isLoading || messagesQuery.isLoading);
72 }, [accountQuery.isLoading, conversationQuery.isLoading, messagesQuery.isLoading]);
idillonea465602022-09-13 19:58:51 -040073
Adrien Béraudabba2e52021-04-24 21:39:56 -040074 useEffect(() => {
idillonae655dd2022-10-14 18:11:02 -040075 setError(accountQuery.isLoading || conversationQuery.isError || messagesQuery.isError);
76 }, [accountQuery.isLoading, conversationQuery.isError, messagesQuery.isError]);
simond47ef9e2022-09-28 22:24:28 -040077
simon35378692022-10-02 23:25:57 -040078 const sendMessage = useCallback((message: string) => sendMessageMutation.mutate(message), [sendMessageMutation]);
simond47ef9e2022-09-28 22:24:28 -040079
80 useEffect(() => {
81 if (!conversation) return;
simon80b7b3b2022-09-28 17:50:10 -040082 console.log(`io set conversation ${conversationId} ` + socket);
simon35378692022-10-02 23:25:57 -040083 if (socket) {
84 socket.emit('conversation', {
85 accountId,
86 conversationId,
87 });
88 socket.off('newMessage');
89 socket.on('newMessage', (data) => {
90 console.log('newMessage');
91 setMessages((messages) => addMessage(messages, data));
92 });
93 }
simon80b7b3b2022-09-28 17:50:10 -040094 }, [accountId, conversation, conversationId, socket]);
Adrien Béraudabba2e52021-04-24 21:39:56 -040095
idillonea465602022-09-13 19:58:51 -040096 if (isLoading) {
simond47ef9e2022-09-28 22:24:28 -040097 return <LoadingPage />;
idillonae655dd2022-10-14 18:11:02 -040098 } else if (error || !account || !conversation) {
simon80b7b3b2022-09-28 17:50:10 -040099 return <div>Error loading {conversationId}</div>;
Adrien Béraud5e9e19b2021-04-22 01:38:53 -0400100 }
idillonbef18a52022-09-01 01:51:40 -0400101
102 return (
idillonae655dd2022-10-14 18:11:02 -0400103 <Stack height="100%">
idillon02f579d2022-11-06 21:26:55 -0500104 <ConversationHeader
105 account={account}
106 members={conversation.getMembers()}
107 adminTitle={conversation.infos.title as string}
108 conversationId={conversationId}
109 />
idillonae655dd2022-10-14 18:11:02 -0400110 <Divider
111 sx={{
112 borderTop: '1px solid #E5E5E5',
113 }}
114 />
idillon02f579d2022-11-06 21:26:55 -0500115 <MessageList account={account} members={conversation.getMembers()} messages={messages} />
idillonae655dd2022-10-14 18:11:02 -0400116 <Divider
117 sx={{
118 margin: '30px 16px 0px 16px',
119 borderTop: '1px solid #E5E5E5',
120 }}
121 />
idillon02f579d2022-11-06 21:26:55 -0500122 <SendMessageForm account={account} members={conversation.getMembers()} onSend={sendMessage} />
idillonbef18a52022-09-01 01:51:40 -0400123 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400124 );
125};
Adrien Béraud35e7d7c2021-04-13 03:28:39 -0400126
idillonae655dd2022-10-14 18:11:02 -0400127type ConversationHeaderProps = {
128 account: Account;
simon1170c322022-10-31 14:51:31 -0400129 conversationId: string;
idillonae655dd2022-10-14 18:11:02 -0400130 members: ConversationMember[];
131 adminTitle: string | undefined;
132};
133
simon1170c322022-10-31 14:51:31 -0400134const ConversationHeader = ({ account, members, adminTitle, conversationId }: ConversationHeaderProps) => {
idillonae655dd2022-10-14 18:11:02 -0400135 const { t } = useTranslation();
simon1170c322022-10-31 14:51:31 -0400136 const navigate = useNavigate();
idillonae655dd2022-10-14 18:11:02 -0400137
138 const title = useMemo(() => {
139 if (adminTitle) {
140 return adminTitle;
141 }
142
143 const options: TranslateEnumerationOptions<ConversationMember> = {
144 elementPartialKey: 'member',
145 getElementValue: (member) => getMemberName(member),
146 translaters: [
147 () =>
148 // The user is chatting with themself
149 t('conversation_title_one', { member0: account?.getDisplayName() }),
150 (interpolations) => t('conversation_title_one', interpolations),
151 (interpolations) => t('conversation_title_two', interpolations),
152 (interpolations) => t('conversation_title_three', interpolations),
153 (interpolations) => t('conversation_title_four', interpolations),
154 (interpolations) => t('conversation_title_more', interpolations),
155 ],
156 };
157
158 return translateEnumeration<ConversationMember>(members, options);
159 }, [account, members, adminTitle, t]);
160
simon1170c322022-10-31 14:51:31 -0400161 const startCall = (withVideo = false) => {
Michelle Sepkap Simeb3dd3122022-11-03 02:12:39 -0400162 let url = `/deprecated-account/${account.getId()}/call/${conversationId}`;
simon1170c322022-10-31 14:51:31 -0400163 if (withVideo) {
164 url += '?video=true';
165 }
166 navigate(url);
167 };
168
idillonae655dd2022-10-14 18:11:02 -0400169 return (
idillon02f579d2022-11-06 21:26:55 -0500170 <Stack direction="row" padding="16px">
idillonae655dd2022-10-14 18:11:02 -0400171 <Stack flex={1} justifyContent="center" whiteSpace="nowrap" overflow="hidden">
172 <Typography variant="h3" textOverflow="ellipsis">
173 {title}
174 </Typography>
175 </Stack>
176 <Stack direction="row" spacing="20px">
simon1170c322022-10-31 14:51:31 -0400177 <StartAudioCallButton onClick={() => startCall(false)} />
178 <StartVideoCallButton onClick={() => startCall(true)} />
idillonae655dd2022-10-14 18:11:02 -0400179 <AddParticipantButton />
180 <ShowOptionsMenuButton />
181 </Stack>
182 </Stack>
183 );
184};
185
186const getMemberName = (member: ConversationMember) => {
187 const contact = member.contact;
188 return contact.getDisplayName();
189};
190
simon35378692022-10-02 23:25:57 -0400191const addMessage = (sortedMessages: Message[], message: Message) => {
idillon08f77172022-09-13 19:14:17 -0400192 if (sortedMessages.length === 0) {
simond47ef9e2022-09-28 22:24:28 -0400193 return [message];
idillon08f77172022-09-13 19:14:17 -0400194 } else if (message.id === sortedMessages[sortedMessages.length - 1].linearizedParent) {
simond47ef9e2022-09-28 22:24:28 -0400195 return [...sortedMessages, message];
idillon08f77172022-09-13 19:14:17 -0400196 } else if (message.linearizedParent === sortedMessages[0].id) {
simond47ef9e2022-09-28 22:24:28 -0400197 return [message, ...sortedMessages];
idillon08f77172022-09-13 19:14:17 -0400198 } else {
simond47ef9e2022-09-28 22:24:28 -0400199 console.log("Can't insert message " + message.id);
simon35378692022-10-02 23:25:57 -0400200 return sortedMessages;
idillon08f77172022-09-13 19:14:17 -0400201 }
simond47ef9e2022-09-28 22:24:28 -0400202};
idillon08f77172022-09-13 19:14:17 -0400203
simon35378692022-10-02 23:25:57 -0400204const sortMessages = (messages: Message[]) => {
205 let sortedMessages: Message[] = [];
simond47ef9e2022-09-28 22:24:28 -0400206 messages.forEach((message) => (sortedMessages = addMessage(sortedMessages, message)));
207 return sortedMessages;
208};
idillon08f77172022-09-13 19:14:17 -0400209
simond47ef9e2022-09-28 22:24:28 -0400210export default ConversationView;