blob: 6c3c8bfb02f34f3776833f885c77bee358b6e32d [file] [log] [blame]
idillon-sfl9d956ab2022-10-20 16:33:24 -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 */
18import { Stack } from '@mui/system';
19import dayjs, { Dayjs } from 'dayjs';
20import dayOfYear from 'dayjs/plugin/dayOfYear';
21import isBetween from 'dayjs/plugin/isBetween';
22import { Account, ConversationMember, Message } from 'jami-web-common';
23import { ReactNode, useMemo } from 'react';
24
25import {
26 MessageBubblesGroup,
27 MessageCall,
28 MessageDate,
29 MessageInitial,
30 MessageMember,
31 MessageMerge,
32 MessageTime,
33} from './Message';
34
35dayjs.extend(dayOfYear);
36dayjs.extend(isBetween);
37
38interface MessageListProps {
39 account: Account;
40 members: ConversationMember[];
41 messages: Message[];
42}
43
44export default function MessageList({ account, members, messages }: MessageListProps) {
45 const messageComponents = useMemo(() => buildMessagesList(account, members, messages), [account, members, messages]);
46 return <Stack direction="column-reverse">{messageComponents}</Stack>;
47}
48
49const buildMessagesList = (account: Account, members: ConversationMember[], messages: Message[]) => {
50 if (messages.length === 0) {
51 return [];
52 }
53
54 const messageComponents: ReactNode[] = [];
55 let lastTime = dayjs.unix(Number(messages[0].timestamp));
56 let lastAuthor = messages[0].author;
57 let messagesGroup: Message[] = [];
58
59 const pushMessageBubblesGroup = () => {
60 if (messagesGroup.length === 0) {
61 return;
62 }
63 const props = { account, members, messages: messagesGroup };
64 messageComponents.push(<MessageBubblesGroup key={`group-${messagesGroup[0].id}`} {...props} />);
65 messagesGroup = [];
66 };
67
68 const pushMessageCall = (message: Message) => {
69 const props = { message };
70 messageComponents.push(<MessageCall key={`call-${message.id}`} {...props} />);
71 };
72
73 const pushMessageMember = (message: Message) => {
74 const props = { message };
75 messageComponents.push(<MessageMember key={`member-${message.id}`} {...props} />);
76 };
77
78 const pushMessageMerge = (message: Message) => {
79 const props = { message };
80 messageComponents.push(<MessageMerge key={`merge-${message.id}`} {...props} />);
81 };
82
83 const pushMessageTime = (message: Message, time: Dayjs, hasDateOnTop = false) => {
84 const props = { time, hasDateOnTop };
85 messageComponents.push(<MessageTime key={`time-${message.id}`} {...props} />);
86 };
87
88 const pushMessageDate = (message: Message, time: Dayjs) => {
89 const props = { time };
90 messageComponents.push(<MessageDate key={`date-${message.id}`} {...props} />);
91 };
92
93 const pushMessageInitial = (message: Message) => {
94 const props = { message };
95 messageComponents.push(<MessageInitial key={`initial-${message.id}`} {...props} />);
96 };
97
98 messages.forEach((message) => {
99 // most recent messages first
100 switch (message.type) {
101 case 'text/plain':
102 case 'application/data-transfer+json':
103 if (lastAuthor !== message.author) {
104 pushMessageBubblesGroup();
105 }
106 messagesGroup.push(message);
107 break;
108 case 'application/call-history+json':
109 pushMessageBubblesGroup();
110 pushMessageCall(message);
111 break;
112 case 'member':
113 pushMessageBubblesGroup();
114 pushMessageMember(message);
115 break;
116 case 'merge':
117 pushMessageBubblesGroup();
118 pushMessageMerge(message);
119 break;
120 case 'initial':
121 default:
122 break;
123 }
124
125 const time = dayjs.unix(Number(message.timestamp));
126 if (message.type === 'initial') {
127 pushMessageBubblesGroup();
128 pushMessageTime(message, time, true);
129 pushMessageDate(message, time);
130 pushMessageInitial(message);
131 } else {
132 if (
133 // If the date is different
134 lastTime?.year() !== time.year() ||
135 lastTime?.dayOfYear() !== time.dayOfYear()
136 ) {
137 pushMessageBubblesGroup();
138 pushMessageTime(message, time, true);
139 pushMessageDate(message, time);
140 } else if (
141 // If more than 5 minutes have passed since the last message
142 !lastTime.isBetween(time, time?.add(5, 'minute'))
143 ) {
144 pushMessageBubblesGroup();
145 pushMessageTime(message, time);
146 }
147
148 lastTime = time;
149 lastAuthor = message.author;
150 }
151 });
152
153 return messageComponents;
154};