convert Message and MessageList to Typescript
Change-Id: Idbca1335dccbf910c2e1715769ff46c575472d00
diff --git a/client/src/components/Message.jsx b/client/src/components/Message.tsx
similarity index 66%
rename from client/src/components/Message.jsx
rename to client/src/components/Message.tsx
index 7e14d10..bfc442f 100644
--- a/client/src/components/Message.jsx
+++ b/client/src/components/Message.tsx
@@ -15,44 +15,69 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-import { Box, Chip, Divider, List, ListItemButton, ListItemText, Stack, Tooltip, Typography } from '@mui/material';
+import {
+ Box,
+ Chip,
+ Divider,
+ List,
+ ListItemButton,
+ ListItemText,
+ Stack,
+ Theme,
+ Tooltip,
+ Typography,
+} from '@mui/material';
import { styled } from '@mui/material/styles';
-import dayjs from 'dayjs';
+import dayjs, { Dayjs } from 'dayjs';
import isToday from 'dayjs/plugin/isToday';
import isYesterday from 'dayjs/plugin/isYesterday';
-import { useCallback, useMemo, useState } from 'react';
+import { Account, ConversationMember, Message } from 'jami-web-common';
+import { ReactElement } from 'react';
+import { ReactNode, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { EmojiButton, MoreButton, ReplyMessageButton } from './Button.tsx';
+import { EmojiButton, MoreButton, ReplyMessageButton } from './Button';
import ConversationAvatar from './ConversationAvatar';
-import { OppositeArrowsIcon, TrashBinIcon, TwoSheetsIcon } from './SvgIcon.tsx';
+import { OppositeArrowsIcon, TrashBinIcon, TwoSheetsIcon } from './SvgIcon';
dayjs.extend(isToday);
dayjs.extend(isYesterday);
-export const MessageCall = (props) => {
+type MessagePosition = 'start' | 'end';
+
+export const MessageCall = () => {
return <Stack alignItems="center">"Appel"</Stack>;
};
-export const MessageInitial = (props) => {
+export const MessageInitial = () => {
const { t } = useTranslation();
return <Stack alignItems="center">{t('message_swarm_created')}</Stack>;
};
-export const MessageDataTransfer = (props) => {
+interface MessageDataTransferProps {
+ position: MessagePosition;
+ isFirstOfGroup: boolean;
+ isLastOfGroup: boolean;
+}
+
+export const MessageDataTransfer = ({ position, isFirstOfGroup, isLastOfGroup }: MessageDataTransferProps) => {
return (
<MessageBubble
backgroundColor={'#E5E5E5'}
- position={props.position}
- isFirstOfGroup={props.isFirstOfGroup}
- isLastOfGroup={props.isLastOfGroup}
+ position={position}
+ isFirstOfGroup={isFirstOfGroup}
+ isLastOfGroup={isLastOfGroup}
>
"data-transfer"
</MessageBubble>
);
};
-export const MessageMember = (props) => {
+interface MessageMemberProps {
+ message: Message;
+}
+
+export const MessageMember = ({ message }: MessageMemberProps) => {
const { t } = useTranslation();
return (
<Stack alignItems="center">
@@ -60,32 +85,52 @@
sx={{
width: 'fit-content',
}}
- label={t('message_user_joined', { user: props.message.author })}
+ label={t('message_user_joined', { user: message.author })}
/>
</Stack>
);
};
-export const MessageMerge = (props) => {
+export const MessageMerge = () => {
return <Stack alignItems="center">"merge"</Stack>;
};
-export const MessageText = (props) => {
+interface MessageTextProps {
+ message: Message;
+ position: MessagePosition;
+ isFirstOfGroup: boolean;
+ isLastOfGroup: boolean;
+ textColor: string;
+ bubbleColor: string;
+}
+
+export const MessageText = ({
+ message,
+ position,
+ isFirstOfGroup,
+ isLastOfGroup,
+ textColor,
+ bubbleColor,
+}: MessageTextProps) => {
return (
<MessageBubble
- backgroundColor={props.bubbleColor}
- position={props.position}
- isFirstOfGroup={props.isFirstOfGroup}
- isLastOfGroup={props.isLastOfGroup}
+ backgroundColor={bubbleColor}
+ position={position}
+ isFirstOfGroup={isFirstOfGroup}
+ isLastOfGroup={isLastOfGroup}
>
- <Typography variant="body1" color={props.textColor} textAlign={props.position}>
- {props.message.body}
+ <Typography variant="body1" color={textColor} textAlign={position}>
+ {message.body}
</Typography>
</MessageBubble>
);
};
-export const MessageDate = ({ time }) => {
+interface MessageDateProps {
+ time: Dayjs;
+}
+
+export const MessageDate = ({ time }: MessageDateProps) => {
let textDate;
if (time.isToday()) {
@@ -128,7 +173,12 @@
);
};
-export const MessageTime = ({ time, hasDateOnTop }) => {
+interface MessageTimeProps {
+ time: Dayjs;
+ hasDateOnTop: boolean;
+}
+
+export const MessageTime = ({ time, hasDateOnTop }: MessageTimeProps) => {
const hour = time.hour().toString().padStart(2, '0');
const minute = time.minute().toString().padStart(2, '0');
const textTime = `${hour}:${minute}`;
@@ -142,19 +192,24 @@
);
};
-export const MessageBubblesGroup = (props) => {
- const isUser = props.messages[0]?.author === props.account.getUri();
+interface MessageBubblesGroupProps {
+ account: Account;
+ messages: Message[];
+ members: ConversationMember[];
+}
+
+export const MessageBubblesGroup = ({ account, messages, members }: MessageBubblesGroupProps) => {
+ const isUser = messages[0]?.author === account.getUri();
const position = isUser ? 'end' : 'start';
const bubbleColor = isUser ? '#005699' : '#E5E5E5';
const textColor = isUser ? 'white' : 'black';
let authorName;
if (isUser) {
- authorName = props.account.getDisplayName();
+ authorName = account.getDisplayName();
} else {
- const member = props.members.find((member) => props.messages[0]?.author === member.contact.getUri());
- const contact = member.contact;
- authorName = contact.getDisplayName();
+ const member = members.find((member) => messages[0]?.author === member.contact.getUri());
+ authorName = member?.contact?.getDisplayName() || '';
}
return (
@@ -171,14 +226,14 @@
width="66.66%"
alignItems={position}
>
- <ParticipantName name={authorName} position={position} />
+ <ParticipantName name={authorName} />
<Stack // Container for a group of message bubbles
spacing="6px"
alignItems={position}
direction="column-reverse"
>
- {props.messages.map((message, index) => {
- let Component;
+ {messages.map((message, index) => {
+ let Component: typeof MessageText | typeof MessageDataTransfer;
switch (message.type) {
case 'text/plain':
Component = MessageText;
@@ -186,6 +241,8 @@
case 'application/data-transfer+json':
Component = MessageDataTransfer;
break;
+ default:
+ return null;
}
return (
<Component // Single message
@@ -194,8 +251,8 @@
textColor={textColor}
position={position}
bubbleColor={bubbleColor}
- isFirstOfGroup={index == props.messages.length - 1}
- isLastOfGroup={index == 0}
+ isFirstOfGroup={index === messages.length - 1}
+ isLastOfGroup={index === 0}
/>
);
})}
@@ -205,7 +262,13 @@
);
};
-const MessageTooltip = styled(({ className, ...props }) => {
+interface MessageTooltipProps {
+ className?: string;
+ position: MessagePosition;
+ children: ReactElement;
+}
+
+const MessageTooltip = styled(({ className, position, children }: MessageTooltipProps) => {
const [open, setOpen] = useState(false);
const emojis = ['😎', '😄', '😍']; // Should be last three used emojis
const additionalOptions = [
@@ -234,9 +297,8 @@
return (
<Tooltip
- {...props}
classes={{ tooltip: className }} // Required for styles. Don't know why
- placement={props.position == 'start' ? 'right-start' : 'left-start'}
+ placement={position === 'start' ? 'right-start' : 'left-start'}
PopperProps={{
modifiers: [
{
@@ -250,7 +312,6 @@
onClose={onClose}
title={
<Stack>
- {' '}
{/* Whole tooltip's content */}
<Stack // Main options
direction="row"
@@ -281,7 +342,7 @@
sx={{
height: '16px',
margin: 0,
- color: (theme) => theme.palette.primary.dark,
+ color: (theme: Theme) => theme?.palette?.primary?.dark,
}}
/>
<ListItemText
@@ -303,9 +364,11 @@
)}
</Stack>
}
- />
+ >
+ {children}
+ </Tooltip>
);
-})(({ theme, position }) => {
+})(({ position }) => {
const largeRadius = '20px';
const smallRadius = '5px';
return {
@@ -313,52 +376,64 @@
padding: '16px',
boxShadow: '3px 3px 7px #00000029',
borderRadius: largeRadius,
- borderStartStartRadius: position == 'start' ? smallRadius : largeRadius,
- borderStartEndRadius: position == 'end' ? smallRadius : largeRadius,
+ borderStartStartRadius: position === 'start' ? smallRadius : largeRadius,
+ borderStartEndRadius: position === 'end' ? smallRadius : largeRadius,
};
});
-const MessageBubble = (props) => {
+interface MessageBubbleProps {
+ position: MessagePosition;
+ isFirstOfGroup: boolean;
+ isLastOfGroup: boolean;
+ backgroundColor: string;
+ children: ReactNode;
+}
+
+const MessageBubble = ({ position, isFirstOfGroup, isLastOfGroup, backgroundColor, children }: MessageBubbleProps) => {
const largeRadius = '20px';
const smallRadius = '5px';
const radius = useMemo(() => {
- if (props.position == 'start') {
+ if (position === 'start') {
return {
- borderStartStartRadius: props.isFirstOfGroup ? largeRadius : smallRadius,
+ borderStartStartRadius: isFirstOfGroup ? largeRadius : smallRadius,
borderStartEndRadius: largeRadius,
- borderEndStartRadius: props.isLastOfGroup ? largeRadius : smallRadius,
+ borderEndStartRadius: isLastOfGroup ? largeRadius : smallRadius,
borderEndEndRadius: largeRadius,
};
}
return {
borderStartStartRadius: largeRadius,
- borderStartEndRadius: props.isFirstOfGroup ? largeRadius : smallRadius,
+ borderStartEndRadius: isFirstOfGroup ? largeRadius : smallRadius,
borderEndStartRadius: largeRadius,
- borderEndEndRadius: props.isLastOfGroup ? largeRadius : smallRadius,
+ borderEndEndRadius: isLastOfGroup ? largeRadius : smallRadius,
};
- }, [props.isFirstOfGroup, props.isLastOfGroup, props.position]);
+ }, [isFirstOfGroup, isLastOfGroup, position]);
return (
- <MessageTooltip position={props.position}>
+ <MessageTooltip position={position}>
<Box
sx={{
width: 'fit-content',
- backgroundColor: props.backgroundColor,
+ backgroundColor: backgroundColor,
padding: '16px',
...radius,
}}
>
- {props.children}
+ {children}
</Box>
</MessageTooltip>
);
};
-const ParticipantName = (props) => {
+interface ParticipantNameProps {
+ name: string;
+}
+
+const ParticipantName = ({ name }: ParticipantNameProps) => {
return (
<Box marginBottom="6px" marginLeft="16px" marginRight="16px">
<Typography variant="caption" color="#A7A7A7" fontWeight={700}>
- {props.name}
+ {name}
</Typography>
</Box>
);
diff --git a/client/src/components/MessageList.jsx b/client/src/components/MessageList.jsx
deleted file mode 100644
index 513af54..0000000
--- a/client/src/components/MessageList.jsx
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2022 Savoir-faire Linux Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation; either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this program. If not, see
- * <https://www.gnu.org/licenses/>.
- */
-import { Stack } from '@mui/system';
-import dayjs from 'dayjs';
-import dayOfYear from 'dayjs/plugin/dayOfYear';
-import isBetween from 'dayjs/plugin/isBetween';
-import { useMemo } from 'react';
-
-import {
- MessageBubblesGroup,
- MessageCall,
- MessageDate,
- MessageInitial,
- MessageMember,
- MessageMerge,
- MessageTime,
-} from './Message';
-
-dayjs.extend(dayOfYear);
-dayjs.extend(isBetween);
-
-export default function MessageList(props) {
- const messagesComponents = useMemo(
- () => buildMessagesList(props.account, props.members, props.messages),
- [props.account, props.members, props.messages]
- );
-
- return (
- <Stack direction="column-reverse">
- {messagesComponents?.map(({ Component, id, props }) => (
- <Component key={id} {...props} />
- ))}
- </Stack>
- );
-}
-
-const buildMessagesList = (account, members, messages) => {
- if (messages.length == 0) {
- return null;
- }
-
- const components = [];
- let lastTime = dayjs.unix(messages[0].timestamp);
- let lastAuthor = messages[0].author;
- let messageBubblesGroup = [];
-
- const pushMessageBubblesGroup = () => {
- if (messageBubblesGroup.length == 0) {
- return;
- }
- components.push({
- id: `group-${messageBubblesGroup[0].id}`,
- Component: MessageBubblesGroup,
- props: { account, members, messages: messageBubblesGroup },
- });
- messageBubblesGroup = [];
- };
-
- const pushMessageCall = (message) => {
- components.push({
- id: `call-${message.id}`,
- Component: MessageCall,
- props: { message },
- });
- };
-
- const pushMessageMember = (message) => {
- components.push({
- id: `member-${message.id}`,
- Component: MessageMember,
- props: { message },
- });
- };
-
- const pushMessageMerge = (message) => {
- components.push({
- id: `merge-${message.id}`,
- Component: MessageMerge,
- props: { message },
- });
- };
-
- const pushMessageTime = (message, time, hasDateOnTop = false) => {
- components.push({
- id: `time-${message.id}`,
- Component: MessageTime,
- props: { time, hasDateOnTop },
- });
- };
-
- const pushMessageDate = (message, time) => {
- components.push({
- id: `date-${message.id}`,
- Component: MessageDate,
- props: { time },
- });
- };
-
- const pushMessageInitial = (message) => {
- components.push({
- id: `initial-${message.id}`,
- Component: MessageInitial,
- props: { message },
- });
- };
-
- messages.forEach((message) => {
- // most recent messages first
- switch (message.type) {
- case 'text/plain':
- case 'application/data-transfer+json':
- if (lastAuthor != message.author) {
- pushMessageBubblesGroup();
- }
- messageBubblesGroup.push(message);
- break;
- case 'application/call-history+json':
- pushMessageBubblesGroup();
- pushMessageCall(message);
- break;
- case 'member':
- pushMessageBubblesGroup();
- pushMessageMember(message);
- break;
- case 'merge':
- pushMessageBubblesGroup();
- pushMessageMerge(message);
- break;
- case 'initial':
- default:
- break;
- }
-
- const time = dayjs.unix(message.timestamp);
- if (message.type == 'initial') {
- pushMessageBubblesGroup();
- pushMessageTime(message, time, true);
- pushMessageDate(message, time);
- pushMessageInitial(message);
- } else {
- if (
- // If the date is different
- lastTime?.year() != time.year() ||
- lastTime?.dayOfYear() != time.dayOfYear()
- ) {
- pushMessageBubblesGroup();
- pushMessageTime(message, time, true);
- pushMessageDate(message, time);
- } else if (
- // If more than 5 minutes have passed since the last message
- !lastTime.isBetween(time, time?.add(5, 'minute'))
- ) {
- pushMessageBubblesGroup();
- pushMessageTime(message, time);
- }
-
- lastTime = time;
- lastAuthor = message.author;
- }
- });
-
- return components;
-};
diff --git a/client/src/components/MessageList.tsx b/client/src/components/MessageList.tsx
new file mode 100644
index 0000000..6c3c8bf
--- /dev/null
+++ b/client/src/components/MessageList.tsx
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Stack } from '@mui/system';
+import dayjs, { Dayjs } from 'dayjs';
+import dayOfYear from 'dayjs/plugin/dayOfYear';
+import isBetween from 'dayjs/plugin/isBetween';
+import { Account, ConversationMember, Message } from 'jami-web-common';
+import { ReactNode, useMemo } from 'react';
+
+import {
+ MessageBubblesGroup,
+ MessageCall,
+ MessageDate,
+ MessageInitial,
+ MessageMember,
+ MessageMerge,
+ MessageTime,
+} from './Message';
+
+dayjs.extend(dayOfYear);
+dayjs.extend(isBetween);
+
+interface MessageListProps {
+ account: Account;
+ members: ConversationMember[];
+ messages: Message[];
+}
+
+export default function MessageList({ account, members, messages }: MessageListProps) {
+ const messageComponents = useMemo(() => buildMessagesList(account, members, messages), [account, members, messages]);
+ return <Stack direction="column-reverse">{messageComponents}</Stack>;
+}
+
+const buildMessagesList = (account: Account, members: ConversationMember[], messages: Message[]) => {
+ if (messages.length === 0) {
+ return [];
+ }
+
+ const messageComponents: ReactNode[] = [];
+ let lastTime = dayjs.unix(Number(messages[0].timestamp));
+ let lastAuthor = messages[0].author;
+ let messagesGroup: Message[] = [];
+
+ const pushMessageBubblesGroup = () => {
+ if (messagesGroup.length === 0) {
+ return;
+ }
+ const props = { account, members, messages: messagesGroup };
+ messageComponents.push(<MessageBubblesGroup key={`group-${messagesGroup[0].id}`} {...props} />);
+ messagesGroup = [];
+ };
+
+ const pushMessageCall = (message: Message) => {
+ const props = { message };
+ messageComponents.push(<MessageCall key={`call-${message.id}`} {...props} />);
+ };
+
+ const pushMessageMember = (message: Message) => {
+ const props = { message };
+ messageComponents.push(<MessageMember key={`member-${message.id}`} {...props} />);
+ };
+
+ const pushMessageMerge = (message: Message) => {
+ const props = { message };
+ messageComponents.push(<MessageMerge key={`merge-${message.id}`} {...props} />);
+ };
+
+ const pushMessageTime = (message: Message, time: Dayjs, hasDateOnTop = false) => {
+ const props = { time, hasDateOnTop };
+ messageComponents.push(<MessageTime key={`time-${message.id}`} {...props} />);
+ };
+
+ const pushMessageDate = (message: Message, time: Dayjs) => {
+ const props = { time };
+ messageComponents.push(<MessageDate key={`date-${message.id}`} {...props} />);
+ };
+
+ const pushMessageInitial = (message: Message) => {
+ const props = { message };
+ messageComponents.push(<MessageInitial key={`initial-${message.id}`} {...props} />);
+ };
+
+ messages.forEach((message) => {
+ // most recent messages first
+ switch (message.type) {
+ case 'text/plain':
+ case 'application/data-transfer+json':
+ if (lastAuthor !== message.author) {
+ pushMessageBubblesGroup();
+ }
+ messagesGroup.push(message);
+ break;
+ case 'application/call-history+json':
+ pushMessageBubblesGroup();
+ pushMessageCall(message);
+ break;
+ case 'member':
+ pushMessageBubblesGroup();
+ pushMessageMember(message);
+ break;
+ case 'merge':
+ pushMessageBubblesGroup();
+ pushMessageMerge(message);
+ break;
+ case 'initial':
+ default:
+ break;
+ }
+
+ const time = dayjs.unix(Number(message.timestamp));
+ if (message.type === 'initial') {
+ pushMessageBubblesGroup();
+ pushMessageTime(message, time, true);
+ pushMessageDate(message, time);
+ pushMessageInitial(message);
+ } else {
+ if (
+ // If the date is different
+ lastTime?.year() !== time.year() ||
+ lastTime?.dayOfYear() !== time.dayOfYear()
+ ) {
+ pushMessageBubblesGroup();
+ pushMessageTime(message, time, true);
+ pushMessageDate(message, time);
+ } else if (
+ // If more than 5 minutes have passed since the last message
+ !lastTime.isBetween(time, time?.add(5, 'minute'))
+ ) {
+ pushMessageBubblesGroup();
+ pushMessageTime(message, time);
+ }
+
+ lastTime = time;
+ lastAuthor = message.author;
+ }
+ });
+
+ return messageComponents;
+};