blob: a6c0d35419c5e4f01d045ed688d4cfcd0489d581 [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';
simon07b4eb02022-09-29 17:50:26 -040022
simon35378692022-10-02 23:25:57 -040023import { SocketContext } from '../contexts/Socket';
idillonae655dd2022-10-14 18:11:02 -040024import { useAccountQuery } from '../services/Account';
simon35378692022-10-02 23:25:57 -040025import { useConversationQuery, useMessagesQuery, useSendMessageMutation } from '../services/Conversation';
idillonae655dd2022-10-14 18:11:02 -040026import { translateEnumeration, TranslateEnumerationOptions } from '../utils/translations';
27import { AddParticipantButton, ShowOptionsMenuButton, StartAudioCallButton, StartVideoCallButton } from './Button';
simon6b9ddfb2022-10-03 00:04:50 -040028import LoadingPage from './Loading';
Adrien Béraud35e7d7c2021-04-13 03:28:39 -040029import MessageList from './MessageList';
30import SendMessageForm from './SendMessageForm';
Adrien Béraud35e7d7c2021-04-13 03:28:39 -040031
simon35378692022-10-02 23:25:57 -040032type ConversationViewProps = {
33 accountId: string;
34 conversationId: string;
35};
idillonae655dd2022-10-14 18:11:02 -040036const ConversationView = ({ accountId, conversationId }: ConversationViewProps) => {
simond47ef9e2022-09-28 22:24:28 -040037 const socket = useContext(SocketContext);
idillonae655dd2022-10-14 18:11:02 -040038 const [account, setAccount] = useState<Account | undefined>();
simon35378692022-10-02 23:25:57 -040039 const [conversation, setConversation] = useState<Conversation | undefined>();
40 const [messages, setMessages] = useState<Message[]>([]);
simond47ef9e2022-09-28 22:24:28 -040041 const [isLoading, setIsLoading] = useState(true);
42 const [error, setError] = useState(false);
idillon08f77172022-09-13 19:14:17 -040043
idillonae655dd2022-10-14 18:11:02 -040044 const accountQuery = useAccountQuery(accountId);
simon80b7b3b2022-09-28 17:50:10 -040045 const conversationQuery = useConversationQuery(accountId, conversationId);
46 const messagesQuery = useMessagesQuery(accountId, conversationId);
47 const sendMessageMutation = useSendMessageMutation(accountId, conversationId);
Adrien Béraud35e7d7c2021-04-13 03:28:39 -040048
Adrien Béraud5e9e19b2021-04-22 01:38:53 -040049 useEffect(() => {
idillonae655dd2022-10-14 18:11:02 -040050 if (accountQuery.isSuccess) {
51 setAccount(Account.from(accountQuery.data));
52 }
53 }, [accountQuery.isSuccess, accountQuery.data]);
54
55 useEffect(() => {
idillonea465602022-09-13 19:58:51 -040056 if (conversationQuery.isSuccess) {
simon80b7b3b2022-09-28 17:50:10 -040057 const conversation = Conversation.from(accountId, conversationQuery.data);
simond47ef9e2022-09-28 22:24:28 -040058 setConversation(conversation);
idillon08f77172022-09-13 19:14:17 -040059 }
simon80b7b3b2022-09-28 17:50:10 -040060 }, [accountId, conversationQuery.isSuccess, conversationQuery.data]);
idillon08f77172022-09-13 19:14:17 -040061
62 useEffect(() => {
idillonea465602022-09-13 19:58:51 -040063 if (messagesQuery.isSuccess) {
simond47ef9e2022-09-28 22:24:28 -040064 const sortedMessages = sortMessages(messagesQuery.data);
65 setMessages(sortedMessages);
idillon08f77172022-09-13 19:14:17 -040066 }
simon80b7b3b2022-09-28 17:50:10 -040067 }, [messagesQuery.isSuccess, messagesQuery.data]);
idillon08f77172022-09-13 19:14:17 -040068
69 useEffect(() => {
idillonae655dd2022-10-14 18:11:02 -040070 setIsLoading(accountQuery.isLoading || conversationQuery.isLoading || messagesQuery.isLoading);
71 }, [accountQuery.isLoading, conversationQuery.isLoading, messagesQuery.isLoading]);
idillonea465602022-09-13 19:58:51 -040072
Adrien Béraudabba2e52021-04-24 21:39:56 -040073 useEffect(() => {
idillonae655dd2022-10-14 18:11:02 -040074 setError(accountQuery.isLoading || conversationQuery.isError || messagesQuery.isError);
75 }, [accountQuery.isLoading, conversationQuery.isError, messagesQuery.isError]);
simond47ef9e2022-09-28 22:24:28 -040076
simon35378692022-10-02 23:25:57 -040077 const sendMessage = useCallback((message: string) => sendMessageMutation.mutate(message), [sendMessageMutation]);
simond47ef9e2022-09-28 22:24:28 -040078
79 useEffect(() => {
80 if (!conversation) return;
simon80b7b3b2022-09-28 17:50:10 -040081 console.log(`io set conversation ${conversationId} ` + socket);
simon35378692022-10-02 23:25:57 -040082 if (socket) {
83 socket.emit('conversation', {
84 accountId,
85 conversationId,
86 });
87 socket.off('newMessage');
88 socket.on('newMessage', (data) => {
89 console.log('newMessage');
90 setMessages((messages) => addMessage(messages, data));
91 });
92 }
simon80b7b3b2022-09-28 17:50:10 -040093 }, [accountId, conversation, conversationId, socket]);
Adrien Béraudabba2e52021-04-24 21:39:56 -040094
idillonea465602022-09-13 19:58:51 -040095 if (isLoading) {
simond47ef9e2022-09-28 22:24:28 -040096 return <LoadingPage />;
idillonae655dd2022-10-14 18:11:02 -040097 } else if (error || !account || !conversation) {
simon80b7b3b2022-09-28 17:50:10 -040098 return <div>Error loading {conversationId}</div>;
Adrien Béraud5e9e19b2021-04-22 01:38:53 -040099 }
idillonbef18a52022-09-01 01:51:40 -0400100
101 return (
idillonae655dd2022-10-14 18:11:02 -0400102 <Stack height="100%">
103 <Stack padding="16px">
104 <ConversationHeader
105 account={account}
106 members={conversation.getMembers()}
107 adminTitle={conversation.infos.title as string}
108 />
idillonbef18a52022-09-01 01:51:40 -0400109 </Stack>
idillonae655dd2022-10-14 18:11:02 -0400110 <Divider
111 sx={{
112 borderTop: '1px solid #E5E5E5',
113 }}
114 />
115 <Stack flex={1} overflow="auto" direction="column-reverse" padding="0px 16px">
116 <MessageList account={account} members={conversation.getMembers()} messages={messages} />
idillonbef18a52022-09-01 01:51:40 -0400117 </Stack>
idillonae655dd2022-10-14 18:11:02 -0400118 <Divider
119 sx={{
120 margin: '30px 16px 0px 16px',
121 borderTop: '1px solid #E5E5E5',
122 }}
123 />
124 <Stack padding="16px">
125 <SendMessageForm account={account} members={conversation.getMembers()} onSend={sendMessage} />
idillonbef18a52022-09-01 01:51:40 -0400126 </Stack>
127 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400128 );
129};
Adrien Béraud35e7d7c2021-04-13 03:28:39 -0400130
idillonae655dd2022-10-14 18:11:02 -0400131type ConversationHeaderProps = {
132 account: Account;
133 members: ConversationMember[];
134 adminTitle: string | undefined;
135};
136
137const ConversationHeader = ({ account, members, adminTitle }: ConversationHeaderProps) => {
138 const { t } = useTranslation();
139
140 const title = useMemo(() => {
141 if (adminTitle) {
142 return adminTitle;
143 }
144
145 const options: TranslateEnumerationOptions<ConversationMember> = {
146 elementPartialKey: 'member',
147 getElementValue: (member) => getMemberName(member),
148 translaters: [
149 () =>
150 // The user is chatting with themself
151 t('conversation_title_one', { member0: account?.getDisplayName() }),
152 (interpolations) => t('conversation_title_one', interpolations),
153 (interpolations) => t('conversation_title_two', interpolations),
154 (interpolations) => t('conversation_title_three', interpolations),
155 (interpolations) => t('conversation_title_four', interpolations),
156 (interpolations) => t('conversation_title_more', interpolations),
157 ],
158 };
159
160 return translateEnumeration<ConversationMember>(members, options);
161 }, [account, members, adminTitle, t]);
162
163 return (
164 <Stack direction="row">
165 <Stack flex={1} justifyContent="center" whiteSpace="nowrap" overflow="hidden">
166 <Typography variant="h3" textOverflow="ellipsis">
167 {title}
168 </Typography>
169 </Stack>
170 <Stack direction="row" spacing="20px">
171 <StartAudioCallButton />
172 <StartVideoCallButton />
173 <AddParticipantButton />
174 <ShowOptionsMenuButton />
175 </Stack>
176 </Stack>
177 );
178};
179
180const getMemberName = (member: ConversationMember) => {
181 const contact = member.contact;
182 return contact.getDisplayName();
183};
184
simon35378692022-10-02 23:25:57 -0400185const addMessage = (sortedMessages: Message[], message: Message) => {
idillon08f77172022-09-13 19:14:17 -0400186 if (sortedMessages.length === 0) {
simond47ef9e2022-09-28 22:24:28 -0400187 return [message];
idillon08f77172022-09-13 19:14:17 -0400188 } else if (message.id === sortedMessages[sortedMessages.length - 1].linearizedParent) {
simond47ef9e2022-09-28 22:24:28 -0400189 return [...sortedMessages, message];
idillon08f77172022-09-13 19:14:17 -0400190 } else if (message.linearizedParent === sortedMessages[0].id) {
simond47ef9e2022-09-28 22:24:28 -0400191 return [message, ...sortedMessages];
idillon08f77172022-09-13 19:14:17 -0400192 } else {
simond47ef9e2022-09-28 22:24:28 -0400193 console.log("Can't insert message " + message.id);
simon35378692022-10-02 23:25:57 -0400194 return sortedMessages;
idillon08f77172022-09-13 19:14:17 -0400195 }
simond47ef9e2022-09-28 22:24:28 -0400196};
idillon08f77172022-09-13 19:14:17 -0400197
simon35378692022-10-02 23:25:57 -0400198const sortMessages = (messages: Message[]) => {
199 let sortedMessages: Message[] = [];
simond47ef9e2022-09-28 22:24:28 -0400200 messages.forEach((message) => (sortedMessages = addMessage(sortedMessages, message)));
201 return sortedMessages;
202};
idillon08f77172022-09-13 19:14:17 -0400203
simond47ef9e2022-09-28 22:24:28 -0400204export default ConversationView;