blob: e96b3f87d6ed01abb7d1f526c0e0a93b2222d30c [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 */
simon07b4eb02022-09-29 17:50:26 -040018import { Box, Stack, Typography } from '@mui/material';
simond47ef9e2022-09-28 22:24:28 -040019import { useCallback, useContext, useEffect, useState } from 'react';
simon07b4eb02022-09-29 17:50:26 -040020
simon35378692022-10-02 23:25:57 -040021import Conversation, { Message } from '../../../model/Conversation';
22import { SocketContext } from '../contexts/Socket';
23import { useConversationQuery, useMessagesQuery, useSendMessageMutation } from '../services/Conversation';
simon07b4eb02022-09-29 17:50:26 -040024import ConversationAvatar from './ConversationAvatar';
simon6b9ddfb2022-10-03 00:04:50 -040025import LoadingPage from './Loading';
Adrien Béraud35e7d7c2021-04-13 03:28:39 -040026import MessageList from './MessageList';
27import SendMessageForm from './SendMessageForm';
Adrien Béraud35e7d7c2021-04-13 03:28:39 -040028
simon35378692022-10-02 23:25:57 -040029type ConversationViewProps = {
30 accountId: string;
31 conversationId: string;
32};
33const ConversationView = ({ accountId, conversationId, ...props }: ConversationViewProps) => {
simond47ef9e2022-09-28 22:24:28 -040034 const socket = useContext(SocketContext);
simon35378692022-10-02 23:25:57 -040035 const [conversation, setConversation] = useState<Conversation | undefined>();
36 const [messages, setMessages] = useState<Message[]>([]);
simond47ef9e2022-09-28 22:24:28 -040037 const [isLoading, setIsLoading] = useState(true);
38 const [error, setError] = useState(false);
idillon08f77172022-09-13 19:14:17 -040039
simon80b7b3b2022-09-28 17:50:10 -040040 const conversationQuery = useConversationQuery(accountId, conversationId);
41 const messagesQuery = useMessagesQuery(accountId, conversationId);
42 const sendMessageMutation = useSendMessageMutation(accountId, conversationId);
Adrien Béraud35e7d7c2021-04-13 03:28:39 -040043
Adrien Béraud5e9e19b2021-04-22 01:38:53 -040044 useEffect(() => {
idillonea465602022-09-13 19:58:51 -040045 if (conversationQuery.isSuccess) {
simon80b7b3b2022-09-28 17:50:10 -040046 const conversation = Conversation.from(accountId, conversationQuery.data);
simond47ef9e2022-09-28 22:24:28 -040047 setConversation(conversation);
idillon08f77172022-09-13 19:14:17 -040048 }
simon80b7b3b2022-09-28 17:50:10 -040049 }, [accountId, conversationQuery.isSuccess, conversationQuery.data]);
idillon08f77172022-09-13 19:14:17 -040050
51 useEffect(() => {
idillonea465602022-09-13 19:58:51 -040052 if (messagesQuery.isSuccess) {
simond47ef9e2022-09-28 22:24:28 -040053 const sortedMessages = sortMessages(messagesQuery.data);
54 setMessages(sortedMessages);
idillon08f77172022-09-13 19:14:17 -040055 }
simon80b7b3b2022-09-28 17:50:10 -040056 }, [messagesQuery.isSuccess, messagesQuery.data]);
idillon08f77172022-09-13 19:14:17 -040057
58 useEffect(() => {
simond47ef9e2022-09-28 22:24:28 -040059 setIsLoading(conversationQuery.isLoading || messagesQuery.isLoading);
60 }, [conversationQuery.isLoading, messagesQuery.isLoading]);
idillonea465602022-09-13 19:58:51 -040061
Adrien Béraudabba2e52021-04-24 21:39:56 -040062 useEffect(() => {
simond47ef9e2022-09-28 22:24:28 -040063 setError(conversationQuery.isError || messagesQuery.isError);
64 }, [conversationQuery.isError, messagesQuery.isError]);
65
simon35378692022-10-02 23:25:57 -040066 const sendMessage = useCallback((message: string) => sendMessageMutation.mutate(message), [sendMessageMutation]);
simond47ef9e2022-09-28 22:24:28 -040067
68 useEffect(() => {
69 if (!conversation) return;
simon80b7b3b2022-09-28 17:50:10 -040070 console.log(`io set conversation ${conversationId} ` + socket);
simon35378692022-10-02 23:25:57 -040071 if (socket) {
72 socket.emit('conversation', {
73 accountId,
74 conversationId,
75 });
76 socket.off('newMessage');
77 socket.on('newMessage', (data) => {
78 console.log('newMessage');
79 setMessages((messages) => addMessage(messages, data));
80 });
81 }
simon80b7b3b2022-09-28 17:50:10 -040082 }, [accountId, conversation, conversationId, socket]);
Adrien Béraudabba2e52021-04-24 21:39:56 -040083
idillonea465602022-09-13 19:58:51 -040084 if (isLoading) {
simond47ef9e2022-09-28 22:24:28 -040085 return <LoadingPage />;
idillon08f77172022-09-13 19:14:17 -040086 } else if (error) {
simon80b7b3b2022-09-28 17:50:10 -040087 return <div>Error loading {conversationId}</div>;
Adrien Béraud5e9e19b2021-04-22 01:38:53 -040088 }
idillonbef18a52022-09-01 01:51:40 -040089
90 return (
simond47ef9e2022-09-28 22:24:28 -040091 <Stack flexGrow={1} height="100%">
idillonbef18a52022-09-01 01:51:40 -040092 <Stack direction="row" flexGrow={0}>
93 <Box style={{ margin: 16, flexShrink: 0 }}>
idillon08f77172022-09-13 19:14:17 -040094 <ConversationAvatar displayName={conversation?.getDisplayNameNoFallback()} />
idillonbef18a52022-09-01 01:51:40 -040095 </Box>
simond47ef9e2022-09-28 22:24:28 -040096 <Box style={{ flex: '1 1 auto', overflow: 'hidden' }}>
97 <Typography className="title" variant="h6">
98 {conversation?.getDisplayName()}
99 </Typography>
100 <Typography className="subtitle" variant="subtitle1">
simon80b7b3b2022-09-28 17:50:10 -0400101 {conversationId}
simond47ef9e2022-09-28 22:24:28 -0400102 </Typography>
idillonbef18a52022-09-01 01:51:40 -0400103 </Box>
104 </Stack>
idillonaedab942022-09-01 14:29:43 -0400105 <Stack flexGrow={1} overflow="auto" direction="column-reverse">
simond47ef9e2022-09-28 22:24:28 -0400106 <MessageList messages={messages} />
idillonbef18a52022-09-01 01:51:40 -0400107 </Stack>
108 <Stack flexGrow={0}>
109 <SendMessageForm onSend={sendMessage} />
110 </Stack>
111 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400112 );
113};
Adrien Béraud35e7d7c2021-04-13 03:28:39 -0400114
simon35378692022-10-02 23:25:57 -0400115const addMessage = (sortedMessages: Message[], message: Message) => {
idillon08f77172022-09-13 19:14:17 -0400116 if (sortedMessages.length === 0) {
simond47ef9e2022-09-28 22:24:28 -0400117 return [message];
idillon08f77172022-09-13 19:14:17 -0400118 } else if (message.id === sortedMessages[sortedMessages.length - 1].linearizedParent) {
simond47ef9e2022-09-28 22:24:28 -0400119 return [...sortedMessages, message];
idillon08f77172022-09-13 19:14:17 -0400120 } else if (message.linearizedParent === sortedMessages[0].id) {
simond47ef9e2022-09-28 22:24:28 -0400121 return [message, ...sortedMessages];
idillon08f77172022-09-13 19:14:17 -0400122 } else {
simond47ef9e2022-09-28 22:24:28 -0400123 console.log("Can't insert message " + message.id);
simon35378692022-10-02 23:25:57 -0400124 return sortedMessages;
idillon08f77172022-09-13 19:14:17 -0400125 }
simond47ef9e2022-09-28 22:24:28 -0400126};
idillon08f77172022-09-13 19:14:17 -0400127
simon35378692022-10-02 23:25:57 -0400128const sortMessages = (messages: Message[]) => {
129 let sortedMessages: Message[] = [];
simond47ef9e2022-09-28 22:24:28 -0400130 messages.forEach((message) => (sortedMessages = addMessage(sortedMessages, message)));
131 return sortedMessages;
132};
idillon08f77172022-09-13 19:14:17 -0400133
simond47ef9e2022-09-28 22:24:28 -0400134export default ConversationView;